From 3c963698343440935b2f053fea22db0cef7eabc5 Mon Sep 17 00:00:00 2001 From: Abhishek Bhatt Date: Mon, 12 Aug 2024 21:46:24 -0400 Subject: [PATCH] first commit on control plane protocols --- examples/controlplane/1a_cdd_interactive.jl | 46 ++++++++ examples/controlplane/1b_cdd_wglmakie.jl | 117 ++++++++++++++++++++ examples/controlplane/2a_cnc_interactive.jl | 37 +++++++ examples/controlplane/2b_cnc_wglmakie.jl | 106 ++++++++++++++++++ examples/controlplane/Readme.md | 9 ++ examples/controlplane/setup.jl | 90 +++++++++++++++ src/ProtocolZoo/ProtocolZoo.jl | 99 ++++++++++++++++- src/ProtocolZoo/controllers.jl | 41 +++++++ 8 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 examples/controlplane/1a_cdd_interactive.jl create mode 100644 examples/controlplane/1b_cdd_wglmakie.jl create mode 100644 examples/controlplane/2a_cnc_interactive.jl create mode 100644 examples/controlplane/2b_cnc_wglmakie.jl create mode 100644 examples/controlplane/Readme.md create mode 100644 examples/controlplane/setup.jl create mode 100644 src/ProtocolZoo/controllers.jl 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..96f23c73 --- /dev/null +++ b/examples/controlplane/1b_cdd_wglmakie.jl @@ -0,0 +1,117 @@ +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. The physical graph is shown below: + + $(gplot(graph, layout=layout, nodelabel=nodelabel)) + + [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..a03e82f7 --- /dev/null +++ b/examples/controlplane/2a_cnc_interactive.jl @@ -0,0 +1,37 @@ +include("setup.jl") + +controller = NetController(sim, net, 3, 6, 0.1) +@process controller() + +consumer = EntanglementConsumer(sim, net, 1, 8; period=0.5) +@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..107fc61b --- /dev/null +++ b/examples/controlplane/2b_cnc_wglmakie.jl @@ -0,0 +1,106 @@ +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 = NetController(sim, net, 3, 6, 0.1) +@process controller() + +consumer = EntanglementConsumer(sim, net, 1, 8; period=0.5) +@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. The physical graph is shown below: + + $(gplot(graph, layout=layout, nodelabel=nodelabel)) + + [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/Readme.md b/examples/controlplane/Readme.md new file mode 100644 index 00000000..a4c47a37 --- /dev/null +++ b/examples/controlplane/Readme.md @@ -0,0 +1,9 @@ +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 \ No newline at end of file diff --git a/examples/controlplane/setup.jl b/examples/controlplane/setup.jl new file mode 100644 index 00000000..4e0cf779 --- /dev/null +++ b/examples/controlplane/setup.jl @@ -0,0 +1,90 @@ +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) + +using GraphPlot +nodelabel = ["Alice", 2, 3, 4, 5, 6, 7, "Bob"] +layout=(args...)->spring_layout(args...; C=20) + +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 bf4275eb..4fc93a37 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -13,15 +13,18 @@ import ConcurrentSim: Process import ResumableFunctions using ResumableFunctions: @resumable import SumTypes +using Graphs +using Random export # protocols - EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, + EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, RequestTracker, # tags - EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, + EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, EntanglementRequest, SwapRequest, # from Switches - SimpleSwitchDiscreteProt, SwitchRequest - + SimpleSwitchDiscreteProt, SwitchRequest, + # controllers + NetController abstract type AbstractProtocol end get_time_tracker(prot::AbstractProtocol) = prot.sim @@ -147,6 +150,45 @@ Tag(tag::EntanglementDelete) = Tag(EntanglementDelete, tag.send_node, tag.send_s """ $TYPEDEF +A message sent from a controller to the [`EntanglementTracker`](@ref) 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 [`EntanglementTracker`](@ref) 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 +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) + +""" +$TYPEDEF + A protocol that generates entanglement between two nodes. Whenever a pair of empty slots is available, the protocol locks them and starts probabilistic attempts to establish entanglement. @@ -430,10 +472,59 @@ end end end +""" +$TYPEDEF + +A protocol, running at a given node, listening for messages requesting entanglement generation or swaps. + +$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) + 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, ❓, ❓) + 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, ❓) + isnothing(msg) && continue + workwasdone = true + (src, (_, rounds)) = msg + @debug "RequestTracker @$(prot.node): Performing a swap" + swapper = SwapperProt(prot.sim, prot.net, prot.node; nodeL = <(prot.node), nodeH = >(prot.node), chooseL=argmin, chooseH=argmax, rounds=rounds) + @process swapper() + end + end + 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("cutoff.jl") include("swapping.jl") include("switches.jl") +include("controllers.jl") using .Switches end # module diff --git a/src/ProtocolZoo/controllers.jl b/src/ProtocolZoo/controllers.jl new file mode 100644 index 00000000..7cc6307f --- /dev/null +++ b/src/ProtocolZoo/controllers.jl @@ -0,0 +1,41 @@ +""" +$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 + sim::Simulation + net::RegisterNet + n::Int + node::Int # The node in the network where the control protocol is physically located/running from, ideally a node central in the network. + ticktock::Float64 +end + +@resumable function (prot::NetController)() + paths = collect(Graphs.all_simple_paths(prot.net.graph, 1, 8)) + n_reg = length(prot.net.registers) + while true + draw = (randperm(n_reg))[1:prot.n] + for i in 1:prot.n #parallelize this + path = paths[draw[i]] + @debug "Running Entanglement Distrubution on path $(path) @ $(now(prot.sim))" + for i in 1:length(path)-1 + msg = Tag(EntanglementRequest, path[i], path[i+1], 1) + put!(channel(prot.net, prot.node=>msg[2]; permit_forward=true), msg) + end + @yield timeout(prot.sim, prot.ticktock) + for i in 2:length(path)-1 + msg = Tag(SwapRequest, path[i], 1) + put!(channel(prot.net, prot.node=>msg[2];permit_forward=true), msg) + end + @yield timeout(prot.sim, prot.ticktock) + end + end +end \ No newline at end of file