Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repeater grid example and tutorial #107

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ push!(LOAD_PATH,"../src/")
using Documenter
using DocumenterCitations
using QuantumSavory
using QuantumSavory.ProtocolZoo

DocMeta.setdocmeta!(QuantumSavory, :DocTestSetup, :(using QuantumSavory); recursive=true)

Expand Down
131 changes: 131 additions & 0 deletions docs/src/howto/repeatergrid/repeatergrid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# [Entanglement Generation On A Repeater Grid](@id Entanglement-Generation-On-A-Repeater-Grid)

```@meta
DocTestSetup = quote
using QuantumSavory
end
```

This section provides a detailed walkthrough of how QuantumSavory.jl can be used to simulate entanglement generation on a network of repeaters where each repeater relies only on local knowledge of the network.

For this example, we consider a square grid topology in which each node is connected to its nearest neighbors.
The registers act as repeater nodes. The nodes on the diagonal corners are Alice and Bob, the two special nodes that the network is trying to entangle through generating link-level entanglement at each edge and performing appropriate swaps at each node.

The goal is to establish entanglement between Alice and Bob by routing entanglement through any of the possible paths(horizontal or vertical) formed by local entanglement links and then swapping those links by performing entanglement swaps.

This employs functionality from the `ProtocolZoo` module of QuantumSavory to run the following Quantum Networking protocols:

- [`EntanglerProt`](@ref): Entangler protocol to produce link level entanglement at each edge in the network

- [`SwapperProt`](@ref): Swapper protocol runs at each node except at the Alice and Bob nodes, to perform swaps. The swaps are performed only if a query deems them useful for propagating entanglement closer and closer to Alice and Bob.

- [`EntanglementTracker`](@ref) Entanglement Tracker protocol to keep track of/and update the local link state-classical knowledge by querying for "entanglement update" messages generated by the other protocols (`SwapperProt` specifically).

All of the above protocols rely on the query and tagging functionality as described in the [Tagging and Querying](@ref tagging-and-querying) section.

Other than that, `ConcurrentSim` and `ResumableFunctions` are used in the backend to run the discrete event simulation. `Graphs` helps with some functionality needed for `RegisterNet` datastructure that forms the grid. `GLMakie` and `NetworkLayout` are used for visualization along with the visualization functionality implemented in `QuantumSavory` itself.

# Custom Predicate And Choosing function

```julia
function check_nodes(net, c_node, node; low=true)
n = Int(sqrt(size(net.graph)[1])) # grid size
c_x = c_node%n == 0 ? c_node ÷ n : (c_node ÷ n) + 1
c_y = c_node - n*(c_x-1)
x = node%n == 0 ? node ÷ n : (node ÷ n) + 1
y = node - n*(x-1)
return low ? (c_x - x) >= 0 && (c_y - y) >= 0 : (c_x - x) <= 0 && (c_y - y) <= 0
end
```

The Swapper Protocol is initialized with a custom predicate function which is then placed in a call to `queryall` inside the Swapper to pick the nodes that are suitable to perform a swap with. The criteria for "suitability" is described below.

This predicate function encodes most of the "logic" a local node will be performing.

The custom predicate function shown above is parametrized with `net` and `c_node` along with the keyword argument `low`, when initializing the Swapper Protocol. This predicate function `Int->Bool` selects the target remote nodes for which a swap is appropriate. The arguments are:

- `net`: The network of register nodes representing the graph structure, an instance of `RegisterNet`.

- `c_node`: The node in which the Swapper protocol would be running.

- `node`: As the [`queryall`](@ref) function goes through all the nodes linked with the current node, the custom predicate filters them depending on whether the node is suitable for a swap or not.

- `low`: The nodes in the grid are numbered as consecutive integers starting from 1. If the Swapper is running at some node n, we want a link closest to Alice and another closest to Bob to perform a swap. We communicate whether we are looking for nodes of the first kind or the latter with the `low` keyword.

Out of all the links at some node, the suitable ones are picked by computing the difference between the coordinates of the current node with the coordinates of the candidate node. A `low` node should have both of the `x` and `y` coordinate difference positive and vice versa for a non-`low` node.

As the Swapper gets a list of suitable candidates for a swap in each direction, the one with the furthest distance from the current node is chosen by summing the x distance and y-distance.

```julia
function choose_node(net, node, arr; low=true)
grid_size = Int(sqrt(size(net.graph)[1]))
return low ? argmax((distance.(grid_size, node, arr))) : argmin((distance.(grid_size, node, arr)))
end

function distance(n, a, b)
x1 = a%n == 0 ? a ÷ n : (a ÷ n) + 1
x2 = b%n == 0 ? b ÷ n : (b ÷ n) + 1
y1 = a - n*(x1-1)
y2 = b - n*(x2-1)

return x1 - x2 + y1 - y2
end
```

# Simulation and Visualization

```julia
n = 6

graph = grid([n,n])

net = RegisterNet(graph, [Register(8) for i in 1:n^2])

sim = get_time_tracker(net)

for (;src, dst) in edges(net)
eprot = EntanglerProt(sim, net, src, dst; rounds=5, randomize=true) # A single round doesn't always get the ends entangled, when number of nodes is high
@process eprot()
end

for i in 2:(size(graph)[1] - 1)
l(x) = check_nodes(net, i, x)
h(x) = check_nodes(net, i, x; low=false)
cL(arr) = choose_node(net, i, arr)
cH(arr) = choose_node(net, i, arr; low=false)
swapper = SwapperProt(sim, net, i; nodeL = l, nodeH = h, chooseL = cL, chooseH = cH, rounds = 5) # A single round doesn't always get the ends entangled, when number of nodes is high
@process swapper()
end

for v in vertices(net)
tracker = EntanglementTracker(sim, net, v)
@process tracker()
end
```

We set up the simulation to run with a 6x6 grid of nodes above. Here, each node has 8 qubit slots.
Each vertical and horizontal edge runs an entanglement generation protocol. Each node in the network runs an entanglement tracker protocol and all of the nodes except the nodes that we're trying to connect, i.e., Alice' and Bob's nodes which are at the diagonal ends of the grid run the swapper protocol. The code that runs and visualizes this simulation is shown below

```julia
layout = SquareGrid(cols=:auto, dx=10.0, dy=-10.0)(graph)
fig = Figure(resolution=(600, 600))
_, ax, _, obs = registernetplot_axis(fig[1,1], net;registercoords=layout)

display(fig)

step_ts = range(0, 10, step=0.1)
record(fig, "grid_sim6x6hv.mp4", step_ts; framerate=10, visible=true) do t
run(sim, t)
notify(obs)
end
```

# Complete Code and Result

```@repl
include("../../../../examples/repeatergrid/repeatergrid.jl")
```

```@raw html
<video src="../grid_sim6x6hv.mp4" autoplay loop muted></video>
```
6 changes: 0 additions & 6 deletions docs/src/visualizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,6 @@ fig

And here with some extra tag metadata.

```@example vis
tag!(net[2,3], :specialplace, 1, 2)
tag!(net[2,3], :otherdata, 3, 4)
QuantumSavory.showmetadata(fig,ax,plt,2,3)
fig
```

## The state of locks and various metadata in the network

Expand Down
109 changes: 109 additions & 0 deletions examples/repeatergrid/repeatergrid.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using QuantumSavory
ba2tripleO marked this conversation as resolved.
Show resolved Hide resolved

# For Simulation
using ResumableFunctions
using ConcurrentSim
using QuantumSavory.ProtocolZoo
using Graphs

# For Plotting
using GLMakie
GLMakie.activate!()
using NetworkLayout

## Custom Predicates used for local decisions in the swapper protocol running at each node

"""A predicate function that checks if a remote node is in the appropriate quadrant with respect to the local node."""
function check_nodes(net, c_node, node; low=true)
n = Int(sqrt(size(net.graph)[1])) # grid size
c_x = c_node%n == 0 ? c_node ÷ n : (c_node ÷ n) + 1
c_y = c_node - n*(c_x-1)
x = node%n == 0 ? node ÷ n : (node ÷ n) + 1
y = node - n*(x-1)
return low ? (c_x - x) >= 0 && (c_y - y) >= 0 : (c_x - x) <= 0 && (c_y - y) <= 0
end

"""A "cost" function for choosing the furthest node in the appropriate quadrant."""
function distance(n, a, b)
x1 = a%n == 0 ? a ÷ n : (a ÷ n) + 1
x2 = b%n == 0 ? b ÷ n : (b ÷ n) + 1
y1 = a - n*(x1-1)
y2 = b - n*(x2-1)
return x1 - x2 + y1 - y2
end

"""A function that chooses the node in the appropriate quadrant that is furthest from the local node."""
function choose_node(net, node, arr; low=true)
grid_size = Int(sqrt(size(net.graph)[1]))
return low ? argmax((distance.(grid_size, node, arr))) : argmin((distance.(grid_size, node, arr)))
end

## Simulation

n = 6 # the size of the square grid network (n × n)
regsize = 8 # the size of the quantum registers at each node

graph = grid([n,n])

net = RegisterNet(graph, [Register(regsize) for i in 1:n^2])

sim = get_time_tracker(net)

# each edge is capable of generating raw link-level entanglement
for (;src, dst) in edges(net)
eprot = EntanglerProt(sim, net, src, dst; rounds=-1, randomize=true, margin=regsize÷2, hardmargin=regsize÷4)
@process eprot()
end

# each node except the corners on one of the diagonals is capable of swapping entanglement
for i in 2:(n^2 - 1)
l(x) = check_nodes(net, i, x)
h(x) = check_nodes(net, i, x; low=false)
cL(arr) = choose_node(net, i, arr)
cH(arr) = choose_node(net, i, arr; low=false)
swapper = SwapperProt(sim, net, i; nodeL = l, nodeH = h, chooseL = cL, chooseH = cH, rounds=-1)
@process swapper()
end

# each node is running entanglement tracking to keep track of classical data about the entanglement
for v in vertices(net)
tracker = EntanglementTracker(sim, net, v)
@process tracker()
end

# a mock entanglement consumer between the two corners of the grid
consumer = EntanglementConsumer(sim, net, 1, n^2)
@process consumer()

# By modifying the `period` of `EntanglementConsumer`, and `rate` of `EntanglerProt`, you can study the effect of different entanglement generation rates on the network

# Visualization

fig = Figure(;size=(600, 600))

# the network part of the visualization
layout = SquareGrid(cols=:auto, dx=10.0, dy=-10.0)(graph) # provided by NetworkLayout, meant to simplify plotting of graphs in 2D
_, ax, _, obs = registernetplot_axis(fig[1:2,1], net;registercoords=layout)

# the performance log part of the visualization
entlog = Observable(consumer.log) # Observables are used by Makie to update the visualization in real-time in an automated reactive way
ts = @lift [e[1] for e in $entlog] # TODO this needs a better interface, something less cluncky, maybe also a whole Makie recipe
tzzs = @lift [Point2f(e[1],e[2]) for e in $entlog]
txxs = @lift [Point2f(e[1],e[3]) for e in $entlog]
Δts = @lift length($ts)>1 ? $ts[2:end] .- $ts[1:end-1] : [0.0]
entlogaxis = Axis(fig[1,2], xlabel="Time", ylabel="Entanglement", title="Entanglement Successes")
ylims!(entlogaxis, (-1.04,1.04))
stem!(entlogaxis, tzzs)
histaxis = Axis(fig[2,2], xlabel="ΔTime", title="Histogram of Time to Successes")
hist!(histaxis, Δts)

display(fig)

step_ts = range(0, 200, step=0.1)
record(fig, "grid_sim6x6hv.mp4", step_ts; framerate=10, visible=true) do t
run(sim, t)
notify.((obs,entlog))
ylims!(entlogaxis, (-1.04,1.04))
xlims!(entlogaxis, max(0,t-50), 1+t)
autolimits!(histaxis)
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a"
Quantikz = "b0d11df0-eea3-4d79-b4a5-421488cbf74b"
QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1"
QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c"
Expand Down
6 changes: 6 additions & 0 deletions test/test_examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ end
include("../examples/congestionchain/1_visualization.jl")
end
end

@safetestset "repeatergrid" begin
if get(ENV, "QUANTUMSAVORY_PLOT_TEST","")=="true"
include("../examples/repeatergrid/repeatergrid.jl")
end
end
Loading