diff --git a/CHANGELOG.md b/CHANGELOG.md index 09480f72..be751ccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## v0.5.1-dev +- Simplify one of the switch protocols to avoid dependence on GraphMatching.jl which does not install well on non-linux systems. Do not rely on the default `SimpleSwitchDiscreteProt` for the time being. +- Implement a network control protocol that is connection-oriented, centralized and non-distributed and another that is connection-less, centralized and non-distributed +- Implement protocols: request generator and request tracker for simulation with the above control protocol in an asynchronous way. +- Add `PhysicalGraph` struct for storing network metadata as the simulation evolves. +- New tags: `EntanglementRequest`,`SwapRequest`, `DistributionRequest` and `RequestCompletion` - Add `classical_delay` and `quantum_delay` as keyword arguments to the `RegisterNet` constructor to set a default global network edge latency. ## v0.5.0 - 2024-10-16 diff --git a/Project.toml b/Project.toml index 658dc935..c18fd1fe 100644 --- a/Project.toml +++ b/Project.toml @@ -11,6 +11,7 @@ DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Memoize = "c03570c3-d221-55d1-a50c-7939bbd78826" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" @@ -40,6 +41,7 @@ Graphs = "1.9" IterTools = "1.4.0" LinearAlgebra = "1" Makie = "0.20, 0.21" +Memoize = "0.4.4" NetworkLayout = "0.4.4" PrecompileTools = "1" Printf = "1" diff --git a/examples/congestionchain/1_visualization.jl b/examples/congestionchain/1_visualization.jl index c1df5d8d..2e1790dd 100644 --- a/examples/congestionchain/1_visualization.jl +++ b/examples/congestionchain/1_visualization.jl @@ -44,7 +44,7 @@ scatter!(ax_fidZZ,ts,fidZZ,label="ZZ",color=(c2,0.1)) display(fig) -step_ts = range(0, 1000, step=0.1) +step_ts = range(0.0, 1000.0, step=0.1) record(fig, "congestionchain.mp4", step_ts; framerate=50, visible=true) do t run(sim, t) diff --git a/examples/controlplane/1a_cdd_interactive.jl b/examples/controlplane/1a_cdd_interactive.jl new file mode 100644 index 00000000..4c3ca8e4 --- /dev/null +++ b/examples/controlplane/1a_cdd_interactive.jl @@ -0,0 +1,46 @@ +include("setup.jl") + +succ_prob = Observable(0.001) +for (;src, dst) in edges(net) + eprot = EntanglerProt(sim, net, src, dst; rounds=-1, randomize=true, success_prob=succ_prob[]) + @process eprot() +end + +local_busy_time = Observable(0.0) +retry_lock_time = Observable(0.1) +for node in 2:7 + swapper = SwapperProt(sim, net, node; nodeL = <(node), nodeH = >(node), chooseL = argmin, chooseH = argmax, rounds=-1, local_busy_time=local_busy_time[], + retry_lock_time=retry_lock_time[]) + @process swapper() +end + +for v in vertices(net) + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +period_cons = Observable(0.1) +consumer = EntanglementConsumer(sim, net, 1, 8; period=period_cons[]) +@process consumer() + +period_dec = Observable(0.1) +for v in vertices(net) + cutoff = CutoffProt(sim, net, v; period=period_dec[]) + @process cutoff() +end +params = [succ_prob, local_busy_time, retry_lock_time, period_cons, period_dec] +sim, net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig = prepare_vis(consumer, params) + +step_ts = range(0.0, 1000.0, step=0.1) +record(fig, "sim.mp4", step_ts; framerate=10, visible=true) do t + run(sim, t) + notify.((obs,entlog)) + notify.(params) + ylims!(entlogaxis, (-1.04,1.04)) + xlims!(entlogaxis, max(0,t-50), 1+t) + ylims!(fid_axis, (0, 1.04)) + xlims!(fid_axis, max(0, t-50), 1+t) + autolimits!(histaxis) + ylims!(num_epr_axis, (0, 4)) + xlims!(num_epr_axis, max(0, t-50), 1+t) +end \ No newline at end of file diff --git a/examples/controlplane/1b_cdd_wglmakie.jl b/examples/controlplane/1b_cdd_wglmakie.jl new file mode 100644 index 00000000..32bc5bc6 --- /dev/null +++ b/examples/controlplane/1b_cdd_wglmakie.jl @@ -0,0 +1,115 @@ +using WGLMakie +WGLMakie.activate!() +import Bonito +using Markdown + +include("setup.jl") + + +const custom_css = Bonito.DOM.style("ul {list-style: circle !important;}") # TODO remove after fix of bug in JSServe https://github.com/SimonDanisch/JSServe.jl/issues/178 + +succ_prob = Observable(0.001) +for (;src, dst) in edges(net) + eprot = EntanglerProt(sim, net, src, dst; rounds=-1, randomize=true, success_prob=succ_prob[]) + @process eprot() +end + +local_busy_time = Observable(0.0) +retry_lock_time = Observable(0.1) +for node in 2:7 + swapper = SwapperProt(sim, net, node; nodeL = <(node), nodeH = >(node), chooseL = argmin, chooseH = argmax, rounds=-1, local_busy_time=local_busy_time[], + retry_lock_time=retry_lock_time[]) + @process swapper() +end + +for v in vertices(net) + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +period_cons = Observable(0.1) +consumer = EntanglementConsumer(sim, net, 1, 8; period=period_cons[]) +@process consumer() + +period_dec = Observable(0.1) +for v in vertices(net) + cutoff = CutoffProt(sim, net, v; period=period_dec[]) + @process cutoff() +end +params = [succ_prob, local_busy_time, retry_lock_time, period_cons, period_dec] + +# All the calls that happen in the main event loop of the simulation, +# encapsulated here so that we can conveniently pause the simulation from the WGLMakie app. +function continue_singlerun!(sim, obs, entlog, params, entlogaxis, fid_axis, histaxis, num_epr_axis, running) + step_ts = range(0, 1000, step=0.1) + for t in step_ts + run(sim, t) + notify.((obs,entlog)) + notify.(params) + ylims!(entlogaxis, (-1.04,1.04)) + xlims!(entlogaxis, max(0,t-50), 1+t) + ylims!(fid_axis, (0, 1.04)) + xlims!(fid_axis, max(0, t-50), 1+t) + autolimits!(histaxis) + ylims!(num_epr_axis, (0, 4)) + xlims!(num_epr_axis, max(0, t-50), 1+t) + end + running[] = nothing +end + +# +landing = Bonito.App() do + + sim, net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig = prepare_vis(consumer, params) + + running = Observable{Any}(false) + fig[5,1] = buttongrid = GridLayout(tellwidth = false) + buttongrid[1,1] = b = Makie.Button(fig, label = @lift(isnothing($running) ? "Done" : $running ? "Running..." : "Run once"), height=30, tellwidth=false) + + on(b.clicks) do _ + if !running[] + running[] = true + end + end + on(running) do r + if r + Threads.@spawn begin + continue_singlerun!( + sim, obs, entlog, params, entlogaxis, fid_axis, histaxis, num_epr_axis, running) + end + end + end + + + content = md""" + Pick simulation settings and hit run (see below for technical details). + + $(fig.scene) + + # Connectionless, Distributed and Decentralized Control Plane for Entanglement Distribution + + The above simulation visualizes entanglement distribution between Alice and Bob on an arbitrary network topology + given by the adjacency matrix of the graph. The control plane architecture used for this simulation is connectionless, + distributed and decentralized. The node representing Alice is the node on the top left and the bottom right is Bob. + The actual connectivity of the physical graph isn't fully captured by the visualization above as we use edges only to + show the virtual graph. + + [See and modify the code for this simulation on github.](https://github.com/QuantumSavory/QuantumSavory.jl/tree/master/examples/controlplane/1b_cdd_wglmakie.jl) + """ + return Bonito.DOM.div(Bonito.MarkdownCSS, Bonito.Styling, custom_css, content) +end; + +# +# Serve the Makie app + +isdefined(Main, :server) && close(server); +port = parse(Int, get(ENV, "CDD_PORT", "8888")) +interface = get(ENV, "CDD_IP", "127.0.0.1") +proxy_url = get(ENV, "CDD_PROXY", "") +server = Bonito.Server(interface, port; proxy_url); +Bonito.HTTPServer.start(server) +Bonito.route!(server, "/" => landing); + +## + +wait(server) \ No newline at end of file diff --git a/examples/controlplane/2a_cnc_interactive.jl b/examples/controlplane/2a_cnc_interactive.jl new file mode 100644 index 00000000..ee4dfe83 --- /dev/null +++ b/examples/controlplane/2a_cnc_interactive.jl @@ -0,0 +1,40 @@ +include("setup.jl") + +controller = Controller(sim, net, 6, zeros(8,8)) +@process controller() + +req_gen = RequestGeneratorCO(sim, net, 1, 8, 6) +@process req_gen() + +consumer = EntanglementConsumer(sim, net, 1, 8) +@process consumer() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +sim, net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig = prepare_vis(consumer) + +step_ts = range(0.0, 1000.0, step=0.1) +record(fig, "sim.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) + ylims!(fid_axis, (0, 1.04)) + xlims!(fid_axis, max(0, t-50), 1+t) + autolimits!(histaxis) + ylims!(num_epr_axis, (0, 4)) + xlims!(num_epr_axis, max(0, t-50), 1+t) +end \ No newline at end of file diff --git a/examples/controlplane/2b_cnc_wglmakie.jl b/examples/controlplane/2b_cnc_wglmakie.jl new file mode 100644 index 00000000..99d840f5 --- /dev/null +++ b/examples/controlplane/2b_cnc_wglmakie.jl @@ -0,0 +1,107 @@ +using WGLMakie +WGLMakie.activate!() +import Bonito +using Markdown + +include("setup.jl") + +const custom_css = Bonito.DOM.style("ul {list-style: circle !important;}") # TODO remove after fix of bug in JSServe https://github.com/SimonDanisch/JSServe.jl/issues/178 + +controller = Controller(sim, net, 6, zeros(8,8)) +@process controller() + +req_gen = RequestGenerator(sim, net, 1, 8, 6) +@process req_gen() + +consumer = EntanglementConsumer(sim, net, 1, 8) +@process consumer() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +# All the calls that happen in the main event loop of the simulation, +# encapsulated here so that we can conveniently pause the simulation from the WGLMakie app. +function continue_singlerun!(sim, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, running) + step_ts = range(0, 1000, step=0.1) + for t in step_ts + run(sim, t) + notify.((obs,entlog)) + ylims!(entlogaxis, (-1.04,1.04)) + xlims!(entlogaxis, max(0,t-50), 1+t) + ylims!(fid_axis, (0, 1.04)) + xlims!(fid_axis, max(0, t-50), 1+t) + autolimits!(histaxis) + ylims!(num_epr_axis, (0, 4)) + xlims!(num_epr_axis, max(0, t-50), 1+t) + end + running[] = nothing +end + +# +landing = Bonito.App() do + + sim, net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig = prepare_vis(consumer) + + running = Observable{Any}(false) + fig[5,1] = buttongrid = GridLayout(tellwidth = false) + buttongrid[1,1] = b = Makie.Button(fig, label = @lift(isnothing($running) ? "Done" : $running ? "Running..." : "Run once"), height=30, tellwidth=false) + + on(b.clicks) do _ + if !running[] + running[] = true + end + end + on(running) do r + if r + Threads.@spawn begin + continue_singlerun!( + sim, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, running) + end + end + end + + + content = md""" + Pick simulation settings and hit run (see below for technical details). + + $(fig.scene) + + # Connection-Oriented, Non-Distributed and Centralized Control Plane for Entanglement Distribution + + The above simulation visualizes entanglement distribution between Alice and Bob on an arbitrary network topology + given by the adjacency matrix of the graph. The control plane architecture used for this simulation is connection-oriented, + non-distributed and centralized. The node representing Alice is the node on the top left and the bottom right is Bob. + The actual connectivity of the physical graph isn't fully captured by the visualization above as we use edges only to + show the virtual graph. + + [See and modify the code for this simulation on github.](https://github.com/QuantumSavory/QuantumSavory.jl/tree/master/examples/controlplane/2b_cnc_wglmakie.jl) + """ + return Bonito.DOM.div(Bonito.MarkdownCSS, Bonito.Styling, custom_css, content) +end; + +# +# Serve the Makie app + +isdefined(Main, :server) && close(server); +port = parse(Int, get(ENV, "CNC_PORT", "8888")) +interface = get(ENV, "CNC_IP", "127.0.0.1") +proxy_url = get(ENV, "CNC_PROXY", "") +server = Bonito.Server(interface, port; proxy_url); +Bonito.HTTPServer.start(server) +Bonito.route!(server, "/" => landing); + +## + +wait(server) diff --git a/examples/controlplane/3a_cl_interactive.jl b/examples/controlplane/3a_cl_interactive.jl new file mode 100644 index 00000000..30b90d96 --- /dev/null +++ b/examples/controlplane/3a_cl_interactive.jl @@ -0,0 +1,45 @@ +include("setup.jl") + +for (;src, dst) in edges(net) + entangler = EntanglerProt(sim, net, src, dst; rounds=-1, randomize=true) + @process entangler() +end + +controller = CLController(sim, net, 6) +@process controller() + +req_gen = RequestGeneratorCL(sim, net, 1, 8, 6) +@process req_gen() + +consumer = EntanglementConsumer(sim, net, 1, 8) +@process consumer() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +sim, net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig = prepare_vis(consumer) + +step_ts = range(0.0, 1000.0, step=0.1) +record(fig, "sim2.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) + ylims!(fid_axis, (0, 1.04)) + xlims!(fid_axis, max(0, t-50), 1+t) + autolimits!(histaxis) + ylims!(num_epr_axis, (0, 4)) + xlims!(num_epr_axis, max(0, t-50), 1+t) +end \ No newline at end of file diff --git a/examples/controlplane/3b_cl_wglmakie.jl b/examples/controlplane/3b_cl_wglmakie.jl new file mode 100644 index 00000000..dead673d --- /dev/null +++ b/examples/controlplane/3b_cl_wglmakie.jl @@ -0,0 +1,112 @@ +using WGLMakie +WGLMakie.activate!() +import Bonito +using Markdown + +include("setup.jl") + +const custom_css = Bonito.DOM.style("ul {list-style: circle !important;}") # TODO remove after fix of bug in JSServe https://github.com/SimonDanisch/JSServe.jl/issues/178 + +for (;src, dst) in edges(net) + entangler = EntanglerProt(sim, net, src, dst; rounds=-1, randomize=true) + @process entangler() +end + +controller = CLController(sim, net, 6) +@process controller() + +req_gen = RequestGenerator(sim, net, 1, 8, 6) +@process req_gen() + +consumer = EntanglementConsumer(sim, net, 1, 8) +@process consumer() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +# All the calls that happen in the main event loop of the simulation, +# encapsulated here so that we can conveniently pause the simulation from the WGLMakie app. +function continue_singlerun!(sim, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, running) + step_ts = range(0, 1000, step=0.1) + for t in step_ts + run(sim, t) + notify.((obs,entlog)) + ylims!(entlogaxis, (-1.04,1.04)) + xlims!(entlogaxis, max(0,t-50), 1+t) + ylims!(fid_axis, (0, 1.04)) + xlims!(fid_axis, max(0, t-50), 1+t) + autolimits!(histaxis) + ylims!(num_epr_axis, (0, 4)) + xlims!(num_epr_axis, max(0, t-50), 1+t) + end + running[] = nothing +end + +# +landing = Bonito.App() do + + sim, net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig = prepare_vis(consumer) + + running = Observable{Any}(false) + fig[5,1] = buttongrid = GridLayout(tellwidth = false) + buttongrid[1,1] = b = Makie.Button(fig, label = @lift(isnothing($running) ? "Done" : $running ? "Running..." : "Run once"), height=30, tellwidth=false) + + on(b.clicks) do _ + if !running[] + running[] = true + end + end + on(running) do r + if r + Threads.@spawn begin + continue_singlerun!( + sim, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, running) + end + end + end + + + content = md""" + Pick simulation settings and hit run (see below for technical details). + + $(fig.scene) + + # Connection-Less, Non-Distributed and Centralized Control Plane for Entanglement Distribution + + The above simulation visualizes entanglement distribution between Alice and Bob on an arbitrary network topology + given by the adjacency matrix of the graph. The control plane architecture used for this simulation is connection-less, + non-distributed and centralized. The node representing Alice is the node on the top left and the bottom right is Bob. + The actual connectivity of the physical graph isn't fully captured by the visualization above as we use edges only to + show the virtual graph. + + [See and modify the code for this simulation on github.](https://github.com/QuantumSavory/QuantumSavory.jl/tree/master/examples/controlplane/3b_cl_wglmakie.jl) + """ + return Bonito.DOM.div(Bonito.MarkdownCSS, Bonito.Styling, custom_css, content) +end; + +# +# Serve the Makie app + +isdefined(Main, :server) && close(server); +port = parse(Int, get(ENV, "CNC_PORT", "8888")) +interface = get(ENV, "CNC_IP", "127.0.0.1") +proxy_url = get(ENV, "CNC_PROXY", "") +server = Bonito.Server(interface, port; proxy_url); +Bonito.HTTPServer.start(server) +Bonito.route!(server, "/" => landing); + +## + +wait(server) diff --git a/examples/controlplane/4_exp.jl b/examples/controlplane/4_exp.jl new file mode 100644 index 00000000..30f971f1 --- /dev/null +++ b/examples/controlplane/4_exp.jl @@ -0,0 +1,137 @@ +using QuantumSavory +using QuantumSavory.ProtocolZoo +using ConcurrentSim +using ResumableFunctions + +using Graphs +using GLMakie +GLMakie.activate!() + +using NetworkLayout +using Random + +adjm = [0 1 0 0 1 0 0 0 + 1 0 1 0 0 0 0 0 + 0 1 0 1 0 1 0 0 + 0 0 1 0 0 0 1 1 + 1 0 0 0 0 1 0 1 + 0 0 1 0 1 0 1 0 + 0 0 0 1 0 1 0 1 + 0 0 0 1 1 0 1 0] +graph = SimpleGraph(adjm) + +regsize = 20 + + +net = RegisterNet(graph, [Register(regsize, T1Decay(10.0)) for i in 1:8]) +sim = get_time_tracker(net) + + +controller = Controller(sim, net, 6, zeros(8,8)) +@process controller() + +req_gen = RequestGeneratorCO(sim, net, 1, 8, 6) +@process req_gen() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +consumerCO = EntanglementConsumer(sim, net, 1, 8) +@process consumerCO() + +run(sim, 500) +###################################### + +net = RegisterNet(graph, [Register(regsize, T1Decay(10.0)) for i in 1:8]) +sim = get_time_tracker(net) + + +for (;src, dst) in edges(net) + entangler = EntanglerProt(sim, net, src, dst; rounds=-1, randomize=true) + @process entangler() +end + +controller = CLController(sim, net, 6) +@process controller() + +req_gen = RequestGeneratorCL(sim, net, 1, 8, 6) +@process req_gen() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +consumerCL = EntanglementConsumer(sim, net, 1, 8) +@process consumerCL() + +run(sim, 500) + +####return 3 metrics: Time to success, Time evolution of average fidelity, avg number of pairs per unit time +function metrics(consumer) + entlog = consumer.log + ts = [e[1] for e in entlog] + txxs = [Point2f(e[1],e[3]) for e in entlog] + Δts = length(ts)>1 ? ts[2:end] .- ts[1:end-1] : [0.0] #Times to success + + ### + avg_fids = cumsum([e[3] for e in entlog])./cumsum(ones(length(entlog))) #avg fidelity per unit time + fid_info = [Point2f(t,f) for (t,f) in zip(ts, avg_fids)] + + ## + num_epr = cumsum(ones(length(entlog)))./(ts) #avg number of pairs per unit time + num_epr_info = [Point2f(t,n) for (t,n) in zip(ts, num_epr)] + + return Δts, fid_info, num_epr_info +end + +Δts_co, fid_co, num_epr_co = metrics(consumerCO) +Δts_cl, fid_cl, num_epr_cl = metrics(consumerCL) + + +fig = Figure(;size=(1200, 1500)) + +histaxis1 = Axis(fig[1,1], xlabel="ΔTime", title="Histogram of Time to Successes(Connection-Oriented)") +hist!(histaxis1, Δts_co) + +histaxis2 = Axis(fig[1,2], xlabel="ΔTime", title="Histogram of Time to Successes(Connection-Less)") +hist!(histaxis2, Δts_cl) + +fid_axis1 = Axis(fig[2,1], xlabel="Time", ylabel="Avg. Fidelity", title="Time evolution of Average Fidelity") +ylims!(fid_axis1, (0.0, 1.0)) +lines!(fid_axis1, fid_co) + +fid_axis2 = Axis(fig[2,2], xlabel="Time", ylabel="Avg. Fidelity", title="Time evolution of Average Fidelity") +ylims!(fid_axis2, (0.0, 1.0)) +lines!(fid_axis2, fid_cl) + +num_epr_axis1 = Axis(fig[3,1], xlabel="Time", title="Avg. Number of Entangled Pairs between Alice and Bob") +lines!(num_epr_axis1, num_epr_co) + +num_epr_axis2 = Axis(fig[3,2], xlabel="Time", title="Avg. Number of Entangled Pairs between Alice and Bob") +lines!(num_epr_axis2, num_epr_cl) + +display(fig) +save("fig.png", fig) \ No newline at end of file diff --git a/examples/controlplane/5_exp.jl b/examples/controlplane/5_exp.jl new file mode 100644 index 00000000..67e629bb --- /dev/null +++ b/examples/controlplane/5_exp.jl @@ -0,0 +1,149 @@ +using QuantumSavory +using QuantumSavory.ProtocolZoo +using ConcurrentSim +using ResumableFunctions + +using Graphs +using GLMakie +GLMakie.activate!() + +using NetworkLayout +using Random + +###################################### +adjm = [0 1 0 0 1 0 0 0 + 1 0 1 0 0 0 0 0 + 0 1 0 1 0 1 0 0 + 0 0 1 0 0 0 1 1 + 1 0 0 0 0 1 0 1 + 0 0 1 0 1 0 1 0 + 0 0 0 1 0 1 0 1 + 0 0 0 1 1 0 1 0] +graph = SimpleGraph(adjm) + +regsize = 20 +net = RegisterNet(graph, [Register(regsize, T1Decay(10.0)) for i in 1:8]) +sim = get_time_tracker(net) + + +controller = Controller(sim, net, 6, zeros(8,8)) +@process controller() + +req_gen18 = RequestGeneratorCO(sim, net, 1, 8, 6) +@process req_gen18() + +req_gen27 = RequestGeneratorCO(sim, net, 2, 7, 6) +@process req_gen27() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +consumerCO18 = EntanglementConsumer(sim, net, 1, 8) +@process consumerCO18() + +consumerCO27 = EntanglementConsumer(sim, net, 2, 7) +@process consumerCO27() + +run(sim, 500) + +######################################################################## +net = RegisterNet(graph, [Register(regsize, T1Decay(10.0)) for i in 1:8]) +sim = get_time_tracker(net) + + +controller = Controller(sim, net, 6, zeros(8,8)) +@process controller() + +req_gen = RequestGeneratorCO(sim, net, 1, 8, 6) +@process req_gen() + +for node in 1:7 + tracker = RequestTracker(sim, net, node) + @process tracker() +end + +for v in 1:8 + tracker = EntanglementTracker(sim, net, v) + @process tracker() +end + +for v in 1:8 + c_prot = CutoffProt(sim, net, v) + @process c_prot() +end + +consumerCOsin = EntanglementConsumer(sim, net, 1, 8) +@process consumerCOsin() + + +run(sim, 500) + +####return 3 metrics: Time to success, Time evolution of average fidelity, avg number of pairs per unit time +function metrics(consumer) + entlog = consumer.log + ts = [e[1] for e in entlog] + txxs = [Point2f(e[1],e[3]) for e in entlog] + Δts = length(ts)>1 ? ts[2:end] .- ts[1:end-1] : [0.0] #Times to success + + ### + avg_fids = cumsum([e[3] for e in entlog])./cumsum(ones(length(entlog))) #avg fidelity per unit time + fid_info = [Point2f(t,f) for (t,f) in zip(ts, avg_fids)] + + ## + num_epr = cumsum(ones(length(entlog)))./(ts) #avg number of pairs per unit time + num_epr_info = [Point2f(t,n) for (t,n) in zip(ts, num_epr)] + + return Δts, fid_info, num_epr_info +end + +Δts_co, fid_co, num_epr_co = metrics(consumerCOsin) +Δts_co18, fid_co18, num_epr_co18 = metrics(consumerCO18) +Δts_co27, fid_co27, num_epr_co27 = metrics(consumerCO27) + + +fig = Figure(;size=(1200, 1500)) + +histaxis = Axis(fig[1,1], xlabel="ΔTime", title="Histogram of Time to Successes(Single User Pair 1-8)") +hist!(histaxis, Δts_co) + +histaxis1 = Axis(fig[1,2], xlabel="ΔTime", title="Histogram of Time to Successes(Pair 1-8)") +hist!(histaxis1, Δts_co18) + +histaxis2 = Axis(fig[1,3], xlabel="ΔTime", title="Histogram of Time to Successes(Pair 2-7)") +hist!(histaxis2, Δts_co27) + +fid_axis = Axis(fig[2,1], xlabel="Time", ylabel="Avg. Fidelity", title="Time evolution of Average Fidelity") +ylims!(fid_axis, (0.0, 1.0)) +lines!(fid_axis, fid_co) + +fid_axis1 = Axis(fig[2,2], xlabel="Time", ylabel="Avg. Fidelity", title="Time evolution of Average Fidelity") +ylims!(fid_axis1, (0.0, 1.0)) +lines!(fid_axis1, fid_co18) + +fid_axis2 = Axis(fig[2,3], xlabel="Time", ylabel="Avg. Fidelity", title="Time evolution of Average Fidelity") +ylims!(fid_axis2, (0.0, 1.0)) +lines!(fid_axis2, fid_co27) + +num_epr_axis = Axis(fig[3,1], xlabel="Time", title="Avg. Number of Entangled Pairs between Alice and Bob") +lines!(num_epr_axis, num_epr_co) + +num_epr_axis1 = Axis(fig[3,2], xlabel="Time", title="Avg. Number of Entangled Pairs between Alice and Bob") +lines!(num_epr_axis1, num_epr_co18) + +num_epr_axis2 = Axis(fig[3,3], xlabel="Time", title="Avg. Number of Entangled Pairs between Alice and Bob") +lines!(num_epr_axis2, num_epr_co27) + +display(fig) +save("fig.png", fig) \ No newline at end of file diff --git a/examples/controlplane/Project.toml b/examples/controlplane/Project.toml new file mode 100644 index 00000000..6e6b0c0e --- /dev/null +++ b/examples/controlplane/Project.toml @@ -0,0 +1,15 @@ +[deps] +Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" +ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c" +QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" +QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" +ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008" + +[compat] +Bonito = "3.1" \ No newline at end of file diff --git a/examples/controlplane/Readme.md b/examples/controlplane/Readme.md new file mode 100644 index 00000000..3b709b24 --- /dev/null +++ b/examples/controlplane/Readme.md @@ -0,0 +1,43 @@ +Different control plane architectures with arbitrary network topologies can be simulated using QuantumSavory. The `setup.jl` contains basic functionality required by the simulations. The other files are described below: + +1a. A simulation that generates an interactive visualization for a connectionless, distributed and decentralized entanglement distribution network + +1b. An interactive web app with the same simulation as 1a + +2a. A simulation that generates an interactive visualization for a connection-oriented, non-distributed and centralized entanglement distribution network + +2b. An interactive web app with the same simulation as 2a + +3a. A simulation that generates an interactive visualization for a connection-less, non-distributed and centralized entanglement distribution network + +3b. An interactive web app with the same simulation as 3a + +4 A script that generates a visualization for side-by-side comparison of the connection-oriented and connection-less approaches with a single user-pair. + +5 A script that generates a visualization for the connection-oriented approach comparing performance in case of single user pair vs two user pairs. + +The connection-oriented control protocol is illustrated at a high level by the sequence diagram below: + +```mermaid +sequenceDiagram +Alice(Request Generator)->>+Controller: DistributionRequest(path_id) +Controller-->>+RequestTracker1: EntanglementRequest +Controller-->>+RequestTracker2: EntanglementRequest +Controller-->>+RequestTrackerN: EntanglementRequest +Controller-->>+RequestTracker1: SwapRequest +Controller-->>+RequestTracker2: SwapRequest +Controller-->>+RequestTrackerN: SwapRequest +``` +The `RequestGenerator` (Alice) sends a message to the controller, requesting entanglement generation with an end node (Bob). +The message contains index of the path selected by Alice and the controller sends `EntanglementRequest`s to the nodes on the path followed by `SwapRequest`s + +For the connection-less protocol we have pre-emptive entanglement generation and swaps are performed at all the nodes except the user pair when a request is received at the controller. + +```mermaid +sequenceDiagram +Alice(Request Generator)->>+Controller: DistributionRequest(path_id) +Controller-->>+RequestTracker1: SwapRequest +Controller-->>+RequestTracker2: SwapRequest +Controller-->>+RequestTrackerN: SwapRequest +``` +Note: The connection-less controller is currently limited to only one user pair since having multiple distinct user pairs run swaps at the user pair of other requests, which leads to cyclic swaps(two qubits of the same node getting entangled to each other through swaps due to different swap predicate priorities for different user pairs) \ No newline at end of file diff --git a/examples/controlplane/setup.jl b/examples/controlplane/setup.jl new file mode 100644 index 00000000..6f8e48c2 --- /dev/null +++ b/examples/controlplane/setup.jl @@ -0,0 +1,86 @@ +using QuantumSavory +using QuantumSavory.ProtocolZoo +using ConcurrentSim +using ResumableFunctions + +using Graphs +using GLMakie +GLMakie.activate!() + +using NetworkLayout +using Random + +adjm = [0 1 0 0 1 0 0 0 + 1 0 1 0 0 0 0 0 + 0 1 0 1 0 1 0 0 + 0 0 1 0 0 0 1 1 + 1 0 0 0 0 1 0 1 + 0 0 1 0 1 0 1 0 + 0 0 0 1 0 1 0 1 + 0 0 0 1 1 0 1 0] +graph = SimpleGraph(adjm) + +regsize = 20 +net = RegisterNet(graph, [Register(regsize, T1Decay(10.0)) for i in 1:8]) +sim = get_time_tracker(net) + +function prepare_vis(consumer::EntanglementConsumer, params=nothing) + ### + fig = Figure(;size=(1200, 1100)) + + # the network part of the visualization + layout = SquareGrid(cols=:auto, dx=30.0, dy=-30.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, txxs) + histaxis = Axis(fig[2,2], xlabel="ΔTime", title="Histogram of Time to Successes") + hist!(histaxis, Δts) + + avg_fids = @lift cumsum([e[3] for e in $entlog])./cumsum(ones(length($entlog))) #avg fidelity per unit time + fid_info = @lift [Point2f(t,f) for (t,f) in zip($ts, $avg_fids)] + fid_axis = Axis(fig[3,1], xlabel="Time", ylabel="Avg. Fidelity", title="Time evolution of Average Fidelity") + lines!(fid_axis, fid_info) + + num_epr = @lift cumsum(ones(length($entlog)))./($ts) #avg number of pairs per unit time + num_epr_info = @lift [Point2f(t,n) for (t,n) in zip($ts, $num_epr)] + num_epr_axis = Axis(fig[3,2], xlabel="Time", title="Avg. Number of Entangled Pairs between Alice and Bob") + lines!(num_epr_axis, num_epr_info) + + if !isnothing(params) + # sliders + sg = SliderGrid( + fig[4,1], + (label="Probability of success of Entanglement generation at each attempt", + range=0.001:0.05:1.0, format="{:.3f}", startvalue=0.001), + (label="Local busy time for swapper", + range=0.001:0.5:10.0, format="{:.3f}", startvalue=0.001), + (label="Wait time after failure to lock qubits for a swap", + range=0.1:0.05:1.0, format="{:.2f}", startvalue=0.1), + (label="Period of time between subsequent queries at the consumer", + range=0.001:0.05:1.0, format="{:.3f}", startvalue=0.001), + (label="Period of time between subsequent queries at the DecoherenceProtocol", + range=0.001:0.05:1.0, format="{:.3f}", startvalue=0.001), + + width = 600, + tellheight = true) + + for (param, slider) in zip(params, sg.sliders) + on(slider.value) do val + param[] = val + end + end + end + + + display(fig) + + return consumer.sim, consumer.net, obs, entlog, entlogaxis, fid_axis, histaxis, num_epr_axis, fig +end \ No newline at end of file diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index dee968ac..b4e8f838 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -7,20 +7,26 @@ using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap using DocStringExtensions -using Distributions: Geometric +using Distributions: Geometric, Exponential using ConcurrentSim: Simulation, @yield, timeout, @process, now import ConcurrentSim: Process import ResumableFunctions using ResumableFunctions: @resumable import SumTypes +using Graphs +using Memoize export # protocols - EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, + EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, RequestTracker, RequestGeneratorCO, RequestGeneratorCL, # tags - EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, + EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, EntanglementRequest, SwapRequest, DistributionRequest, # from Switches - SimpleSwitchDiscreteProt, SwitchRequest + SimpleSwitchDiscreteProt, SwitchRequest, + # controllers + NetController, Controller, CLController, + #utils + PathMetadata, path_selection, swap_predicate, swap_choose abstract type AbstractProtocol end @@ -145,6 +151,72 @@ end Base.show(io::IO, tag::EntanglementDelete) = print(io, "Deleted $(tag.send_node).$(tag.send_slot) which was entangled to $(tag.rec_node).$(tag.rec_slot)") Tag(tag::EntanglementDelete) = Tag(EntanglementDelete, tag.send_node, tag.send_slot, tag.rec_node, tag.rec_slot) +""" +$TYPEDEF + +A message sent from a controller to the [`RequestTracker`](@ref) at a node requesting the generation of an entanglement link between the receiving node +and one of its next-hop neighbors on the physical graph, as mentioned in the request + +$TYPEDFIELDS + +See also: [EntanglerProt](@ref), [`EntanglementTracker`](@ref), [`SwapRequest`](@ref) +""" +@kwdef struct EntanglementRequest + "The id of the node receiving the request" + receiver::Int + "The id of the node with which the entanglement link should be established" + neighbor::Int + "The number of rounds the Entangler should run for" + rounds::Int +end +Base.show(io::IO, tag::EntanglementRequest) = print(io, "$(tag.receiver) attempt entanglement generation with $(tag.neighbor)") +Tag(tag::EntanglementRequest) = Tag(EntanglementRequest, tag.receiver, tag.neighbor, tag.rounds) + +""" +$TYPEDEF + +A message sent from a controller to the [`RequestTracker`](@ref) at a node requesting it to perform a swap + +$TYPEDFIELDS + +See also: [`SwapperProt`](@ref), [`EntanglementTracker`](@ref), [`EntanglementRequest`](@ref) +""" +@kwdef struct SwapRequest + """The id of the node instructed to perform a swap""" + swapping_node::Int + """The number of rounds the swapper should run for""" + rounds::Int + """source node for the `DistributionRequest`""" + src::Int + """destination node for the `DistributionRequest`""" + dst::Int + """whether the request is from a connection-oriented(0) or connection-less controller(1)""" + conn::Int +end +Base.show(io::IO, tag::SwapRequest) = print(io, "Node $(tag.swapping_node) perform a swap") +Tag(tag::SwapRequest) = Tag(SwapRequest, tag.swapping_node, tag.rounds, tag.src, tag.dst) + +""" +$TYPEDEF + +A message sent from a node to a control protocol requesting bipartite entanglement with a remote destination node through entanglement distribution. + +$TYPEDFIELDS + +See also: [`EntanglementRequest`](@ref), [`SwapRequest`] +""" +@kwdef struct DistributionRequest + """The node generating the request""" + src::Int + """The node with which entanglement is to be generated""" + dst::Int + """The number of rounds of swaps requested""" + rounds::Int = 1 +end +Base.show(io::IO, tag::DistributionRequest) = print(io, "Node $(tag.src) requesting entanglement with $(tag.dst)") +Tag(tag::DistributionRequest) = Tag(DistributionRequest, tag.src, tag.dst, tag.rounds) + + """ $TYPEDEF @@ -372,7 +444,7 @@ $TYPEDEF A protocol running between two nodes, checking periodically for any entangled pairs between the two nodes and consuming/emptying the qubit slots. -$FIELDS +$TYPEDFIELDS """ @kwdef struct EntanglementConsumer{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} """time-and-schedule-tracking instance from `ConcurrentSim`""" @@ -435,9 +507,153 @@ end end end +""" +$TYPEDEF + +A protocol running at a node, listening for incoming entanglement generation and swap requests and serving +them in an asynchronous way, without waiting for the completion of the instantiated entanglement generation or swapping processes to complete + +$TYPEDFIELDS +""" +@kwdef struct RequestTracker <: AbstractProtocol + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """a network graph of registers""" + net::RegisterNet + """the vertex of the node where the tracker is working""" + node::Int +end + +@resumable function (prot::RequestTracker)() + mb = messagebuffer(prot.net, prot.node) + isinf = false + while true + workwasdone = true # waiting is not enough because we might have multiple rounds of work to do + while workwasdone + workwasdone = false # if there is nothing in the mb queue(querydelete returns nothing) we skip to waiting, otherwise we keep querying until the queue is empty + for requesttagsymbol in (EntanglementRequest, SwapRequest) + if requesttagsymbol == EntanglementRequest + msg = querydelete!(mb, requesttagsymbol, ❓, ❓, ❓) + @debug "RequestTracker @$(prot.node): Received $msg" + isnothing(msg) && continue + workwasdone = true + (src, (_, _, neighbor, rounds)) = msg + @debug "RequestTracker @$(prot.node): Generating entanglement with $(neighbor)" + entangler = EntanglerProt(prot.sim, prot.net, prot.node, neighbor; rounds=rounds, randomize=true) + @process entangler() + else + msg = querydelete!(mb, requesttagsymbol, ❓, ❓, ❓, ❓, ❓) + @debug "RequestTracker @$(prot.node): Received $msg" + isnothing(msg) && continue + workwasdone = true + (msg_src, (_, _, rounds, req_src, req_dst, conn)) = msg + @debug "RequestTracker @$(prot.node): Performing a swap" + if conn == 0 # connection-oriented + swapper = SwapperProt(prot.sim, prot.net, prot.node; nodeL = req_src, nodeH = req_dst, rounds=rounds) + @process swapper() + else # connection-less + if rounds == -1 + isinf = true + else + isinf = false + end + pred_low = swap_predicate(prot.net.graph, req_src, req_dst, prot.node) + pred_high = swap_predicate(prot.net.graph, req_src, req_dst, prot.node; low=false) + choose_low = swap_choose(prot.net.graph, req_src) + choose_high = swap_choose(prot.net.graph, req_dst) + swapper = SwapperProt(prot.sim, prot.net, prot.node; nodeL = pred_low, nodeH = pred_high, chooseL=choose_low, chooseH=choose_high, rounds=rounds) + @process swapper() + end + end + end + end + if isinf @yield timeout(prot.sim, 5.0) end + @debug "RequestTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" + @yield wait(mb) + @debug "RequestTracker @$(prot.node): Message wait ends at $(now(prot.sim))" + end +end + +include("utils.jl") + +""" +$TYPEDEF + +Protocol for the simulation of request traffic for a controller in a connection-oriented network for bipartite entanglement distribution. The requests are considered to be generated according to the Poisson model with rate λ, hence the inter-arrival time is +sampled from an exponential distribution. Physically, the request is generated at the source node(Alice) and is classically communicated to the node where the controller is located. Multiple `RequestGenerator`s can be instantiated for simulation with multiple +user pairs in the same network. + +$TYPEDFIELDS +""" +@kwdef struct RequestGeneratorCO <: AbstractProtocol + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """a network graph of registers""" + net::RegisterNet + """The source node(and the node where this protocol runs) of the user pair, commonly called Alice""" + src::Int + """The destination node, commonly called Bob""" + dst::Int + """The node at which the controller is located""" + controller::Int + """rate of arrival of requests/number of requests sent unit time""" + λ::Int = 3 +end + +function RequestGeneratorCO(sim, net, src, dst, controller; kwargs...) + return RequestGeneratorCO(;sim, net, src, dst, controller, kwargs...) +end + +@resumable function (prot::RequestGeneratorCO)() + d = Exponential(inv(prot.λ)) # Parametrized with the scale which is inverse of the rate + mb = messagebuffer(prot.net, prot.src) + while true + msg = Tag(DistributionRequest, prot.src, prot.dst, 1) + put!(channel(prot.net, prot.src=>prot.controller; permit_forward=true), msg) + + @yield timeout(prot.sim, rand(d)) + end +end + + +""" +$TYPEDEF + +Protocol for the simulation of request traffic for a controller in a connection-oriented network for bipartite entanglement distribution. The requests are considered to be generated according to the Poisson model with rate λ, hence the inter-arrival time is +sampled from an exponential distribution. Physically, the request is generated at the source node(Alice) and is classically communicated to the node where the controller is located. Multiple `RequestGenerator`s can be instantiated for simulation with multiple +user pairs in the same network. + +$TYPEDFIELDS +""" +@kwdef struct RequestGeneratorCL <: AbstractProtocol + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """a network graph of registers""" + net::RegisterNet + """The source node(and the node where this protocol runs) of the user pair, commonly called Alice""" + src::Int + """The destination node, commonly called Bob""" + dst::Int + """The node at which the controller is located""" + controller::Int + """The number of rounds of swaps to be requested, -1 for infinite""" + rounds::Int = -1 +end + +function RequestGeneratorCL(sim, net, src, dst, controller; kwargs...) + return RequestGeneratorCL(;sim, net, src, dst, controller, kwargs...) +end + +@resumable function (prot::RequestGeneratorCL)() + mb = messagebuffer(prot.net, prot.src) + msg = Tag(DistributionRequest, prot.src, prot.dst, prot.rounds) + put!(channel(prot.net, prot.src=>prot.controller; permit_forward=true), msg) +end + include("cutoff.jl") include("swapping.jl") +include("controllers.jl") include("switches.jl") using .Switches diff --git a/src/ProtocolZoo/controllers.jl b/src/ProtocolZoo/controllers.jl new file mode 100644 index 00000000..ebd86ac9 --- /dev/null +++ b/src/ProtocolZoo/controllers.jl @@ -0,0 +1,167 @@ +""" +$TYPEDEF + +A network control protocol that is connection oriented, non-distributed and centralized. The generation of +random requests is abstracted with picking a random path from all available paths in the arbitrary network +between Alice and Bob. The controller is located at one of the nodes in the network from where it messages all +the other nodes. + +$TYPEDFIELDS + +See also [`RequestTracker`](@ref) +""" +@kwdef struct NetController <: AbstractProtocol + """Time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """A network graph of registers""" + net::RegisterNet + """The number of requests to be generated per cycle""" + n::Int + """The node in the network where the control protocol is physically located, ideally a centrally located node""" + node::Int + """duration of a single full cycle of entanglement generation and swapping along a specific path""" + ticktock::Float64 +end + +@resumable function (prot::NetController)() + paths = collect(Graphs.all_simple_paths(prot.net.graph, 1, 8)) + n_reg = length(prot.net.registers) + mb = messagebuffer(prot.net, prot.node) + while true + draw = (randperm(n_reg))[1:prot.n] + for i in 1:prot.n + path = paths[draw[i]] + @debug "Running Entanglement Distribution on path $(path) @ $(now(prot.sim))" + for i in 1:length(path)-1 + msg = Tag(EntanglementRequest, path[i], path[i+1], 1) + if prot.node == path[i] + put!(mb, msg) + else + put!(channel(prot.net, prot.node=>msg[2]; permit_forward=true), msg) + end + end + + for i in 2:length(path)-1 + msg = Tag(SwapRequest, path[i], 1) + if prot.node == path[i] + put!(mb, msg) + else + put!(channel(prot.net, prot.node=>msg[2];permit_forward=true), msg) + end + end + @yield timeout(prot.sim, prot.ticktock) + end + end +end + +""" +$TYPEDEF + +A network control protocol that is connection oriented, non-distributed and centralized. The controller is located at one of the nodes in the network from where it messages all +the other nodes' [`RequestTracker`](@ref) protocols when it receives [`DistributionRequest`](@ref) from [`RequestGeneratorCO`](@ref). + +$TYPEDFIELDS + +See also [`RequestGeneratorCO`](@ref), [`RequestGeneratorCL`](@ref), [`RequestTracker`](@ref) +""" +@kwdef struct Controller <: AbstractProtocol + """Time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """A network graph of registers""" + net::RegisterNet + """The node in the network where the control protocol is physically located, ideally a centrally located node""" + node::Int + """A matrix for the object containing physical graph metadata for the network""" + path_mat::Matrix{Union{Float64, PathMetadata}} +end + +@resumable function (prot::Controller)() + mb = messagebuffer(prot.net, prot.node) + while true + workwasdone = true + while workwasdone + workwasdone = false + msg = querydelete!(mb, DistributionRequest, ❓, ❓, ❓) + if !isnothing(msg) + (msg_src, (_, req_src, req_dst, rounds)) = msg + if typeof(prot.path_mat[req_src, req_dst]) <: Number + prot.path_mat[req_src, req_dst] = PathMetadata(prot.net.graph, req_src, req_dst, Int(length(prot.net[1].staterefs)/2)) + end + path_id = path_selection(prot.sim, prot.path_mat[req_src, req_dst]) + path = prot.path_mat[req_src, req_dst].paths[path_id] + if isnothing(path_id) + @debug "Request failed, all paths reserved" + end + + @debug "Running Entanglement Distribution on path $(path) @ $(now(prot.sim))" + for i in 1:length(path)-1 + msg = Tag(EntanglementRequest, path[i], path[i+1], 1) + if prot.node == path[i] + put!(mb, msg) + else + put!(channel(prot.net, prot.node=>msg[2]; permit_forward=true), msg) + end + end + + for i in 2:length(path)-1 + msg = Tag(SwapRequest, path[i], rounds, path[i-1], path[i+1], 0) + if prot.node == path[i] + put!(mb, msg) + else + put!(channel(prot.net, prot.node=>msg[2];permit_forward=true), msg) + end + end + end + @debug "Controller @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" + @yield wait(mb) + @debug "Controller @$(prot.node): Message wait ends at $(now(prot.sim))" + end + end +end + + +""" +$TYPEDEF + +A network control protocol that is connection less, non-distributed and centralized. The controller is located at one of the nodes in the network from where it messages all +the other nodes' [`RequestTracker`](@ref) protocols when it receives [`DistributionRequest`](@ref) from the [`RequestGeneratorCL`](@ref). + +$TYPEDFIELDS + +See also [`RequestGeneratorCO`](@ref), [`RequestGeneratorCL`](@ref), [`RequestTracker`](@ref) +""" +@kwdef struct CLController <: AbstractProtocol + """Time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """A network graph of registers""" + net::RegisterNet + """The node in the network where the control protocol is physically located, ideally a centrally located node""" + node::Int +end + +@resumable function (prot::CLController)() + mb = messagebuffer(prot.net, prot.node) + while true + workwasdone = true + while workwasdone + workwasdone = false + msg = querydelete!(mb, DistributionRequest, ❓, ❓, ❓) + if !isnothing(msg) + (msg_src, (_, req_src, req_dst, rounds)) = msg + for v in vertices(prot.net) + if v != req_src && v != req_dst + msg = Tag(SwapRequest, v, rounds, req_src, req_dst, 1) + if prot.node == v + put!(mb, msg) + else + put!(channel(prot.net, prot.node=>msg[2];permit_forward=true), msg) + end + end + end + end + @debug "Controller @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" + @yield wait(mb) + @debug "Controller @$(prot.node): Message wait ends at $(now(prot.sim))" + end + end +end \ No newline at end of file diff --git a/src/ProtocolZoo/cutoff.jl b/src/ProtocolZoo/cutoff.jl index da17356c..5e1bcc6e 100644 --- a/src/ProtocolZoo/cutoff.jl +++ b/src/ProtocolZoo/cutoff.jl @@ -34,7 +34,7 @@ end @resumable function (prot::CutoffProt)() if isnothing(prot.period) - error("In `CutoffProt` we do not yet support quing up and waiting on register") # TODO + error("In `CutoffProt` we do not yet support queuing up and waiting on register") # TODO end reg = prot.net[prot.node] while true diff --git a/src/ProtocolZoo/swapping.jl b/src/ProtocolZoo/swapping.jl index 948556b8..978fabe3 100644 --- a/src/ProtocolZoo/swapping.jl +++ b/src/ProtocolZoo/swapping.jl @@ -1,26 +1,3 @@ -function random_index(arr) - return rand(keys(arr)) -end - - -function findswapablequbits(net, node, pred_low, pred_high, choose_low, choose_high; agelimit=nothing) - reg = net[node] - low_nodes = [ - n for n in queryall(reg, EntanglementCounterpart, pred_low, ❓; locked=false, assigned=true) - if isnothing(agelimit) || !isolderthan(n.slot, agelimit) # TODO add age limit to query and queryall - ] - high_nodes = [ - n for n in queryall(reg, EntanglementCounterpart, pred_high, ❓; locked=false, assigned=true) - if isnothing(agelimit) || !isolderthan(n.slot, agelimit) # TODO add age limit to query and queryall - ] - - (isempty(low_nodes) || isempty(high_nodes)) && return nothing - il = choose_low((n.tag[2] for n in low_nodes)) # TODO make [2] into a nice named property - ih = choose_high((n.tag[2] for n in high_nodes)) - return (low_nodes[il], high_nodes[ih]) -end - - """ $TYPEDEF diff --git a/src/ProtocolZoo/utils.jl b/src/ProtocolZoo/utils.jl new file mode 100644 index 00000000..76750b93 --- /dev/null +++ b/src/ProtocolZoo/utils.jl @@ -0,0 +1,99 @@ +""" +$TYPEDEF + +A struct containing the physical graph metadata for a network. The latest workload data is only available +at the node where the [`Controller`](@ref) runs, but every node has access to a copy for referencing paths based on indices +passed through the `DistributionRequest` tag/message. + +$TYPEDFIELDS +""" +@kwdef struct PathMetadata + """The vector of paths between the user pair""" + paths::Vector{Vector{Int}} + """The vector containing the workload information of a path""" + workloads::Vector{Int} + """The number of slots available at each node. Scalar if all are same, vector otherwise.""" + capacity::Union{Vector{Int}, Int} + """Number of failed requests due to high request traffic""" + failures::Ref{Int} +end + +function PathMetadata(graph::SimpleGraph{Int64}, src::Int, dst::Int, caps::Union{Vector{Int}, Int}; failures=Ref{Int}(0)) + paths = sort(collect(all_simple_paths(graph, src, dst)); by = x->length(x)) + workloads = zeros(length(paths)) + PathMetadata(paths, workloads, caps, failures) +end + + +""" +A simple path selection algorithm for connection oriented networks. +""" +function path_selection(sim, pathobj::PathMetadata) + for i in 1:length(pathobj.paths) + capacity = isa(pathobj.capacity, Number) ? pathobj.capacity : pathobj.capacity[i] + if pathobj.workloads[i] begin + d_src = get_distance(graph, src, node) + d_dst = get_distance(graph, dst, node) + is_closer = low ? d_src < get_distance(graph, src, curr_node) : d_dst < get_distance(graph, dst, curr_node) + res = d_src <= d_dst + low ? res & is_closer : !res & is_closer + end +end + +""" +A generic choosing function for any arbitrary topology and entanglement flow. Returns the index of the node closest to `target_node` +for performing a swap. +""" +function swap_choose(graph, target_node) + return arr -> argmin([get_distance(graph, target_node, node) for node in arr]) +end + +@memoize function get_distance(graph, nodeA, nodeB) + return length(a_star(graph, nodeA, nodeB)) +end \ No newline at end of file diff --git a/test/test_controlplane.jl b/test/test_controlplane.jl new file mode 100644 index 00000000..2b6bc0c8 --- /dev/null +++ b/test/test_controlplane.jl @@ -0,0 +1,94 @@ +@testitem "Control Protocols" tags=[:controlplane] begin + +using QuantumSavory +using QuantumSavory.ProtocolZoo +using ConcurrentSim +using ResumableFunctions + +using Graphs + +if isinteractive() + using Logging + logger = ConsoleLogger(Logging.Warn; meta_formatter=(args...)->(:black,"","")) + global_logger(logger) + println("Logger set to debug") +end + +adjm = [0 1 0 0 1 0 0 0 + 1 0 1 0 0 0 0 0 + 0 1 0 1 0 1 0 0 + 0 0 1 0 0 0 1 1 + 1 0 0 0 0 1 0 1 + 0 0 1 0 1 0 1 0 + 0 0 0 1 0 1 0 1 + 0 0 0 1 1 0 1 0] +graph = SimpleGraph(adjm) + +regsize = 20 +net = RegisterNet(graph, [Register(regsize) for i in 1:8]) +sim = get_time_tracker(net) + +# controller +controller = Controller(sim, net, 6, zeros(8,8)) +@process controller() + +# RequestGenerator for the user pair (1,8) +req_gen = RequestGeneratorCO(sim, net, 1, 8, 6) +@process req_gen() + +# consumer +consumer = EntanglementConsumer(sim, net, 1, 8) +@process consumer() + +# entanglement and request trackers, cutoff protocol +for v in 1:8 + etracker = EntanglementTracker(sim, net, v) + rtracker = RequestTracker(sim, net, v) + cutoff = CutoffProt(sim, net, v) + @process etracker() + @process rtracker() + @process cutoff() +end + +run(sim, 1000) + +for i in 1:length(consumer.log) + @test consumer.log[i][2] ≈ 1.0 + @test consumer.log[i][3] ≈ 1.0 +end + +#### Connection less controller + +net = RegisterNet(graph, [Register(regsize) for i in 1:8]) +sim = get_time_tracker(net) + +# controller +controller = CLController(sim, net, 6) +@process controller() + +# RequestGenerator for the user pair (1,8) +req_gen = RequestGeneratorCL(sim, net, 1, 8, 6) +@process req_gen() + +# consumer +consumer = EntanglementConsumer(sim, net, 1, 8) +@process consumer() + +# entanglement and request trackers, cutoff protocol +for v in 1:8 + etracker = EntanglementTracker(sim, net, v) + rtracker = RequestTracker(sim, net, v) + cutoff = CutoffProt(sim, net, v) + @process etracker() + @process rtracker() + @process cutoff() +end + +run(sim, 1000) + +for i in 1:length(consumer.log) + @test consumer.log[i][2] ≈ 1.0 + @test consumer.log[i][3] ≈ 1.0 +end + +end \ No newline at end of file diff --git a/test/test_examples.jl b/test/test_examples.jl index e9bbba30..53bc12d3 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -41,7 +41,7 @@ end end end -@safetestset "repeatergrid" begin +@testitem "Examples - repeatergrid" tags=[:examples] begin if get(ENV, "QUANTUMSAVORY_PLOT_TEST","")=="true" include("setup_plotting.jl") include("../examples/repeatergrid/1a_async_interactive_visualization.jl") @@ -49,6 +49,14 @@ end end end +@testitem "Examples - controlplane" tags=[:examples] begin + if get(ENV, "QUANTUMSAVORY_PLOT_TEST","")=="true" + include("../examples/controlplane/1a_cdd_interactive.jl") + include("../examples/controlplane/2a_cnc_interactive.jl") + include("../examples/controlplane/3a_cl_interactive.jl") + end +end + if get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" import GLMakie GLMakie.closeall() # to avoid errors when running headless