From d8b724e2cb1336553825a9a10e1469af94d3a633 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 17 Sep 2024 14:54:43 +0200 Subject: [PATCH 01/38] added new interactive example (no visualization yet) termed 'piecemaker' --- examples/piecemakerswitch/3_simple_run.jl | 9 + examples/piecemakerswitch/README.md | 11 + examples/piecemakerswitch/setup.jl | 54 ++++ src/CircuitZoo/CircuitZoo.jl | 12 +- src/ProtocolZoo/ProtocolZoo.jl | 334 +++++++++++++++++++++- src/ProtocolZoo/switches.jl | 144 +++++++++- 6 files changed, 557 insertions(+), 7 deletions(-) create mode 100644 examples/piecemakerswitch/3_simple_run.jl create mode 100644 examples/piecemakerswitch/README.md create mode 100644 examples/piecemakerswitch/setup.jl diff --git a/examples/piecemakerswitch/3_simple_run.jl b/examples/piecemakerswitch/3_simple_run.jl new file mode 100644 index 00000000..da2fa5b7 --- /dev/null +++ b/examples/piecemakerswitch/3_simple_run.jl @@ -0,0 +1,9 @@ +include("setup.jl") + +# Prepare all of the simulation components +n, sim = prepare_simulation() + +step_ts = range(0, 10, step=0.1) +for t in step_ts + run(sim, t) +end diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md new file mode 100644 index 00000000..0c85d4d5 --- /dev/null +++ b/examples/piecemakerswitch/README.md @@ -0,0 +1,11 @@ +# System Overview + +A central switch node connects to **n** clients. The switch possesses **m = n + 1** qubit slots, while each client has a single qubit slot. + +# Entanglement Initiation + +At each clock tick, the switch initiates entanglement attempts with each of the **n** clients, resulting in **n** entanglement processes per cycle. Successful entanglement links are then merged into a GHZ (Greenberger–Horne–Zeilinger) state using an additional "piecemaker" qubit located in the \((n + 1)\)th slot of the switch node. This fusion process is assumed to occur instantaneously. + +# Fusion Operation + +The fusion operation consists of applying a **CNOT** gate followed by a measurement. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states at the switch node: in each iteration, the piecemaker qubit (initialized in the state \(|+\rangle\)) is fused with one of the existing entangled states. Once all clients went through the fusion operation, the piecemaker qubit is measured out. This completes the fusing process and all nodes are sharing an n-GHZ state diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl new file mode 100644 index 00000000..3123a807 --- /dev/null +++ b/examples/piecemakerswitch/setup.jl @@ -0,0 +1,54 @@ +using QuantumSavory +using QuantumSavory.ProtocolZoo +using Graphs +using ConcurrentSim +using ResumableFunctions +using Distributions + +@resumable function init_piecemaker(sim, net, m) + while true + @yield lock(net[1][m]) + if !isassigned(net[1][m]) + initialize!(net[1][m], X1) + tag!(net[1][m], Piecemaker, 1, m) + unlock(net[1][m]) + @yield timeout(sim, 0.1) + else + unlock(net[1][m]) + @yield timeout(sim, 0.1) + end + end +end + + +function prepare_simulation() + n = 4 # number of clients + m = n+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit + + # The graph of network connectivity. Index 1 corresponds to the switch. + graph = star_graph(n+1) + + switch_register = Register(m) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit + client_registers = [Register(1) for _ in 1:n] + net = RegisterNet(graph, [switch_register, client_registers...]) + sim = get_time_tracker(net) + + # Set up the initial |+> state of the piecemaker qubit + @process init_piecemaker(sim, net, m) + + # Set up the entanglement trackers at each client + trackers = [EntanglementTracker(sim, net, k) for k in 2:n+1] + for tracker in trackers + @process tracker() + end + + # Set up an entanglement consumer between each unordered pair of clients + consumer = GHZConsumer(net, net[1][m]) + @process consumer() + + # Finally, set up the switch without assignments + switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(0.7, n)) + @process switch_protocol() + + return n, sim +end diff --git a/src/CircuitZoo/CircuitZoo.jl b/src/CircuitZoo/CircuitZoo.jl index 9966b4bb..71eabea9 100644 --- a/src/CircuitZoo/CircuitZoo.jl +++ b/src/CircuitZoo/CircuitZoo.jl @@ -3,7 +3,7 @@ module CircuitZoo using QuantumSavory using DocStringExtensions -export EntanglementSwap, LocalEntanglementSwap, +export EntanglementFusion, EntanglementSwap, LocalEntanglementSwap, Purify2to1, Purify2to1Node, Purify3to1, Purify3to1Node, PurifyStringent, PurifyStringentNode, PurifyExpedient, PurifyExpedientNode, SDDecode, SDEncode @@ -46,6 +46,16 @@ end inputqubits(::LocalEntanglementSwap) = 2 +struct EntanglementFusion <: AbstractCircuit +end + +function (::EntanglementFusion)(localq, piecemaker) + apply!((piecemaker, localq), CNOT) + zmeas = project_traceout!(localq, σᶻ) + zmeas +end + +inputqubits(::EntanglementFusion) = 2 """ $TYPEDEF diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index dee968ac..9f8d9d76 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -3,7 +3,7 @@ module ProtocolZoo using QuantumSavory import QuantumSavory: get_time_tracker, Tag, isolderthan using QuantumSavory: Wildcard -using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap +using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap, EntanglementFusion using DocStringExtensions @@ -16,11 +16,11 @@ import SumTypes export # protocols - EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, + EntanglerProt, SelectedEntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, GHZConsumer, CutoffProt, # tags - EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, + EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, Piecemaker, # from Switches - SimpleSwitchDiscreteProt, SwitchRequest + SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest abstract type AbstractProtocol end @@ -45,6 +45,41 @@ end Base.show(io::IO, tag::EntanglementCounterpart) = print(io, "Entangled to $(tag.remote_node).$(tag.remote_slot)") Tag(tag::EntanglementCounterpart) = Tag(EntanglementCounterpart, tag.remote_node, tag.remote_slot) + +""" +$TYPEDEF + +Indicates the current entanglement status with a remote node's slot. Added when a new qubit is fused into the GHZ state through [`FusionProt`](@ref). + +$TYPEDFIELDS +""" +@kwdef struct FusionCounterpart + "the id of the remote node to which we are entangled" + remote_node::Int + "the slot in the remote node containing the qubit we are entangled to" + remote_slot::Int +end +Base.show(io::IO, tag::FusionCounterpart) = print(io, "GHZ state shared with $(tag.remote_node).$(tag.remote_slot)") +Tag(tag::FusionCounterpart) = Tag(FusionCounterpart, tag.remote_node, tag.remote_slot) + +""" +$TYPEDEF + +Indicates the piecemaker responsible for fusions of a remote node's slot. + +$TYPEDFIELDS +""" +@kwdef struct Piecemaker + "the id of the switch node" + node::Int + "the slot in the switch node containing piecemaker qubit" + slot::Int +end +Base.show(io::IO, tag::Piecemaker) = print(io, "Piecemaker slot at $(tag.node).$(tag.slot)") +Tag(tag::Piecemaker) = Tag(Piecemaker, tag.node, tag.slot) + + + """ $TYPEDEF @@ -435,6 +470,297 @@ end end end +""" +$TYPEDEF + +A protocol running between two nodes, checking periodically for any entangled states (GHZ states) between all nodes and consuming/emptying the qubit slots. + +$FIELDS +""" +@kwdef struct GHZConsumer{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """a network graph of registers""" + net::RegisterNet + """the piecemaker qubit slot (RegRef)""" + piecemaker::RegRef + """time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)""" + period::LT = 0.1 + """stores the time and resulting observable from querying the piecemaker qubit for `EntanglementCounterpart`""" + log::Vector{Tuple{Float64, Float64, Float64}} = Tuple{Float64, Float64, Float64}[] +end + +function GHZConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef; kwargs...) + return GHZConsumer(;sim, net, piecemaker, kwargs...) +end +function GHZConsumer(net::RegisterNet, piecemaker::RegRef; kwargs...) + return GHZConsumer(get_time_tracker(net), net, piecemaker; kwargs...) +end + +@resumable function (prot::GHZConsumer)() + if isnothing(prot.period) + error("In `GHZConsumer` we do not yet support waiting on register to make qubits available") # TODO + end + while true + nclients = nsubsystems(prot.net[1])-1 + qparticipating = queryall(prot.piecemaker, FusionCounterpart, ❓, ❓) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below + if isnothing(qparticipating) + @debug "GHZConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" + @yield timeout(prot.sim, prot.period) + return + elseif length(qparticipating) == nclients + @info "All clients are now part of the GHZ state." + client_slots = [prot.net[k][1] for k in 2:nclients+1] + + # Wait for all locks to complete + tasks = [] + for resource in client_slots + push!(tasks, lock(resource)) + end + push!(tasks, lock(prot.piecemaker)) + all_locks = reduce(&, tasks) + @yield all_locks + + @debug "GHZConsumer of $(prot.piecemaker): queries successful, consuming entanglement" + for q in qparticipating + untag!(prot.piecemaker, q.id) + end + + # when all qubits have arrived, we measure out the central qubit + zmeas = project_traceout!(prot.piecemaker, σˣ) + if zmeas == 2 + apply!(prot.net[2][1], Z) # apply correction on arbitrary client slot + end + pm = queryall(prot.piecemaker, ❓, ❓, ❓) + @assert length(pm) < 2 "More than one entry for piecemaker in database." + (slot, id, tag) = pm[1] + untag!(prot.piecemaker, id) + + ob1 = real(observable(client_slots, tensor(collect(fill(Z, nclients))...))) + ob2 = real(observable(client_slots, tensor(collect(fill(X, nclients))...))) + # if nclients-GHZ state achieved both observables equal 1 + @info "GHZConsumer: expectation values $(ob1) $(ob2)" + + # delete tags and free client slots + for k in 2:nclients+1 + queries = queryall(prot.net[k], EntanglementCounterpart, ❓, ❓) + for q in queries + untag!(q.slot, q.id) + end + end + + traceout!([prot.net[k][1] for k in 2:nclients+1]...) + push!(prot.log, (now(prot.sim), ob1, ob2)) + + for k in 2:nclients+1 + unlock(prot.net[k][1]) + end + unlock(prot.piecemaker) + @yield timeout(prot.sim, prot.period) + end + + # @debug "GHZConsumer of $(prot.node): queries successful, consuming entanglement" + # untag!(q, query.id) + # # TODO do we need to add EntanglementHistory and should that be a different EntanglementHistory since the current one is specifically for SwapperProt + # # TODO currently when calculating the observable we assume that EntanglerProt.pairstate is always (|00⟩ + |11⟩)/√2, make it more general for other states + # ob1 = real(observable((q1, q2), tensor(collect(fill(Z, nclients))...)) + # ob2 = real(observable((q1, q2), X⊗X)) + + # traceout!(prot.net[prot.nodeA][q1.idx], prot.net[prot.nodeB][q2.idx]) + # push!(prot.log, (now(prot.sim), ob1, ob2)) + @yield timeout(prot.sim, prot.period) + end +end + +# """ +# $TYPEDEF + +# Helper function to return a random key of a dictionary. + +# $TYPEDFIELDS +# """ + +# function random_index(arr) +# return rand(keys(arr)) +# end + +""" +$TYPEDEF + +A protocol, running at a given node, that finds fusable entangled pairs and performs entanglement fusion. + +$TYPEDFIELDS +""" +@kwdef struct FusionProt{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation + """a network graph of registers""" + net::RegisterNet + """the vertex of the node where fusion is happening""" + node::Int + """the query for fusable qubits can return many positive candidates; `choose` picks one of them, defaults to a random pick `arr->rand(keys(arr))`""" + choose = random_index + """the vertex of the remote node for the fusion""" + nodeC::Int + """fixed "busy time" duration immediately before starting entanglement generation attempts""" + local_busy_time::Float64 = 0.0 # TODO the gates should have that busy time built in + """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up and waiting)""" + retry_lock_time::LT = 0.1 + """how many rounds of this protocol to run (`-1` for infinite))""" + rounds::Int = -1 +end + +#TODO "convenience constructor for the missing things and finish this docstring" +function FusionProt(sim::Simulation, net::RegisterNet, node::Int; kwargs...) + return FusionProt(;sim, net, node, kwargs...) +end + +@resumable function (prot::FusionProt)() + rounds = prot.rounds + round = 1 + while rounds != 0 + fusable_qubit, piecemaker = findfusablequbit(prot.net, prot.node, prot.nodeC, prot.choose) # request client slot on switch node + if isnothing(fusable_qubit) + isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO + @yield timeout(prot.sim, prot.retry_lock_time) + continue + end + + (q, id, tag) = fusable_qubit.slot, fusable_qubit.id, fusable_qubit.tag + (q_pm, id_pm, tag_pm) = piecemaker.slot, piecemaker.id, piecemaker.tag + @yield lock(q) & lock(q_pm) # this should not really need a yield thanks to `findswapablequbits`, but it is better to be defensive + @yield timeout(prot.sim, prot.local_busy_time) + + untag!(q, id) + # store a history of whom we were entangled to for both client slot and piecemaker + tag!(q, EntanglementHistory, tag[2], tag[3], prot.node, q_pm.idx, q.idx) + tag!(q_pm, FusionCounterpart, tag[2], tag[3]) + + uptotime!((q, q_pm), now(prot.sim)) + fuscircuit = EntanglementFusion() + zmeas = fuscircuit(q, q_pm) + # send from here to client node + # tag with EntanglementUpdateX past_local_node, past_local_slot_idx, past_remote_slot_idx, new_remote_node, new_remote_slot, correction + msg = Tag(EntanglementUpdateZ, prot.node, q.idx, tag[3], prot.node, q_pm.idx, zmeas) + put!(channel(prot.net, prot.node=>tag[2]; permit_forward=true), msg) + @debug "FusionProt @$(prot.node)|round $(round): Send message to $(tag[2]) | message=`$msg`" + unlock(q) + unlock(q_pm) + rounds==-1 || (rounds -= 1) + round += 1 + end +end + +function findfusablequbit(net, node, pred_client, choose_random) + reg = net[node] + nodes = queryall(reg, EntanglementCounterpart, pred_client, ❓; locked=false) + piecemaker = query(reg, Piecemaker, ❓, ❓) + isempty(nodes) && return nothing + @assert length(nodes) == 1 "Client seems to be entangled multiple times" + return nodes[1], piecemaker +end + +""" +$TYPEDEF + +A protocol that generates entanglement between two nodes. +Whenever the selected client slot and the associated slot on the remote node are free, the protocol locks them +and starts probabilistic attempts to establish entanglement. + +$TYPEDFIELDS +""" +@kwdef struct SelectedEntanglerProt{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation # TODO check that + """a network graph of registers""" + net::RegisterNet + """the vertex index of node A""" + nodeA::Int + """the vertex index of node B""" + nodeB::Int + """the state being generated (supports symbolic, numeric, noisy, and pure)""" + pairstate = StabilizerState("ZZ XX") + """success probability of one attempt of entanglement generation""" + success_prob::Float64 = 0.001 + """duration of single entanglement attempt""" + attempt_time::Float64 = 0.001 + """fixed "busy time" duration immediately before starting entanglement generation attempts""" + local_busy_time_pre::Float64 = 0.0 + """fixed "busy time" duration immediately after the a successful entanglement generation attempt""" + local_busy_time_post::Float64 = 0.0 + """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up)""" + retry_lock_time::LT = 0.1 + """how many rounds of this protocol to run (`-1` for infinite)""" + rounds::Int = -1 + """maximum number of attempts to make per round (`-1` for infinite)""" + attempts::Int = -1 + """whether the protocol should find the first available free slots in the nodes to be entangled or check for free slots randomly from the available slots""" + randomize::Bool = false + """Repeated rounds of this protocol may lead to monopolizing all slots of a pair of registers, starving or deadlocking other protocols. This field can be used to always leave a minimum number of slots free if there already exists entanglement between the current pair of nodes.""" + margin::Int = 0 + """Like `margin`, but it is enforced even when no entanglement has been established yet. Usually smaller than `margin`.""" + hardmargin::Int = 0 +end + +"""Convenience constructor for specifying `rate` of generation instead of success probability and time""" +function SelectedEntanglerProt(sim::Simulation, net::RegisterNet, nodeA::Int, nodeB::Int; rate::Union{Nothing,Float64}=nothing, kwargs...) + if isnothing(rate) + return SelectedEntanglerProt(;sim, net, nodeA, nodeB, kwargs...) + else + return SelectedEntanglerProt(;sim, net, nodeA, nodeB, kwargs..., success_prob=0.001, attempt_time=0.001/rate) + end +end + +#TODO """Convenience constructor for specifying `fidelity` of generation instead of success probability and time""" + +@resumable function (prot::SelectedEntanglerProt)() + rounds = prot.rounds + round = 1 + while rounds != 0 + isentangled = !isnothing(query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, ❓; assigned=true)) + #margin = isentangled ? prot.margin : prot.hardmargin + a = prot.net[prot.nodeA][prot.nodeB-1] #findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) # + b = prot.net[prot.nodeB][1]#findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) # + + if isnothing(a) || isnothing(b) + isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO + @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a \n2.\t $b \n retrying..." + @yield timeout(prot.sim, prot.retry_lock_time) + continue + end + + @yield lock(a) & lock(b) # this yield is expected to return immediately + + @yield timeout(prot.sim, prot.local_busy_time_pre) + attempts = if isone(prot.success_prob) + 1 + else + rand(Geometric(prot.success_prob))+1 + end + + if (prot.attempts == -1 || prot.attempts >= attempts) && !isassigned(b) && !isassigned(a) + @yield timeout(prot.sim, attempts * prot.attempt_time) + initialize!((a,b), prot.pairstate; time=now(prot.sim)) + @yield timeout(prot.sim, prot.local_busy_time_post) + + # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b + tag!(a, EntanglementCounterpart, prot.nodeB, b.idx) + # tag local node b with EntanglementCounterpart remote_node_idx_a remote_slot_idx_a + tag!(b, EntanglementCounterpart, prot.nodeA, a.idx) + + @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Entangled .$(a.idx) and .$(b.idx)" + else + @yield timeout(prot.sim, prot.attempts * prot.attempt_time) + @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Performed the maximum number of attempts and gave up" + end + unlock(a) + unlock(b) + @yield timeout(prot.sim, prot.retry_lock_time) + rounds==-1 || (rounds -= 1) + round += 1 + end +end + include("cutoff.jl") include("swapping.jl") diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index 2b1ac824..a5a46c4a 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -2,7 +2,7 @@ module Switches using QuantumSavory using QuantumSavory.ProtocolZoo -using QuantumSavory.ProtocolZoo: EntanglementCounterpart, AbstractProtocol +using QuantumSavory.ProtocolZoo: EntanglementCounterpart, FusionCounterpart, AbstractProtocol using Graphs: edges, complete_graph, neighbors #using GraphsMatching: maximum_weight_matching # TODO-MATCHING due to the dependence on BlossomV.jl this has trouble installing. See https://github.com/JuliaGraphs/GraphsMatching.jl/issues/14 using Combinatorics: combinations @@ -12,7 +12,7 @@ using ConcurrentSim: @process, timeout, Simulation, Process using ResumableFunctions using Random -export SimpleSwitchDiscreteProt, SwitchRequest +export SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest """ A wrapper around a matrix, ensuring that it is symmetric. @@ -124,6 +124,76 @@ function capture_stdout(f) return r end +""" +$TYPEDEF + +A switch protocol running on a given switch node, and attempting to serve n neighboring clients by executing fusion operations +with each of them to generate a shared n-GHZ state. The protocol proceeds in discrete time intervals. +First, clients attempt link-level entanglement with the switch. Next, successful clients undergo a fusion operation. +It merges two GHZ states into a single GHZ state (modulo Pauli corrections). + + +$TYPEDFIELDS +""" +@kwdef struct FusionSwitchDiscreteProt <: AbstractProtocol + """time-and-schedule-tracking instance from `ConcurrentSim`""" + sim::Simulation # TODO check that + """a network graph of registers""" + net::RegisterNet + """the vertex index of the switch""" + switchnode::Int + """the vertex indices of the clients""" + clientnodes::Vector{Int} + """best-guess about success of establishing raw entanglement between client and switch""" + success_probs::Vector{Float64} + """duration of a single full cycle of the switching decision algorithm""" + ticktock::Float64 = 1 + """how many rounds of this protocol to run (`-1` for infinite)""" + rounds::Int = -1 + """the algorithm to use for memory slot assignment, defaulting to `promponas_bruteforce_choice`""" + #assignment_algorithm::AA = promponas_bruteforce_choice # TODO: not needed in the fusion protocol -- how to delete without breaking anything? + #backlog::SymMatrix{Matrix{Int}} + function FusionSwitchDiscreteProt(sim, net, switchnode, clientnodes, success_probs, ticktock, rounds) + length(unique(clientnodes)) == length(clientnodes) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `clientnodes` must be unique!")) + all(in(neighbors(net, switchnode)), clientnodes) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `clientnodes` must be directly connected to the `switchnode`!")) + 0 < ticktock || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested protocol period `ticktock` must be positive!")) + 0 < rounds || rounds == -1 || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested number of rounds `rounds` must be positive or `-1` for infinite!")) + length(clientnodes) == length(success_probs) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `success_probs` must have the same length as `clientnodes`!")) + all(0 .<= success_probs .<= 1) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `success_probs` must be in the range [0,1]!")) + new(sim, net, switchnode, clientnodes, success_probs, ticktock, rounds) + end +end + +function FusionSwitchDiscreteProt(sim, net, switchnode, clientnodes, success_probs; kwrags...) + FusionSwitchDiscreteProt(;sim, net, switchnode, clientnodes=collect(clientnodes), success_probs=collect(success_probs), kwrags...) +end +FusionSwitchDiscreteProt(net, switchnode, clientnodes, success_probs; kwrags...) = FusionSwitchDiscreteProt(get_time_tracker(net), net, switchnode, clientnodes, success_probs; kwrags...) + +@resumable function (prot::FusionSwitchDiscreteProt)() + rounds = prot.rounds + clientnodes = prot.clientnodes + reverseclientindex = Dict{Int,Int}(c=>i for (i,c) in enumerate(clientnodes)) + + while rounds != 0 + rounds==-1 || (rounds -= 1) + + # run entangler without requests (=no assignment) + _switch_entangler_all_selected(prot) + @yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net + + # read which entanglements were successful + matches = _switch_successful_entanglements(prot, reverseclientindex) + if isnothing(matches) + @yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net + continue + end + + # perform fusions + _switch_run_fusions(prot, matches) + @yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net + end +end + """ $TYPEDEF @@ -284,6 +354,59 @@ function _switch_entangler(prot, assignment) end end +""" +Run the entangler protocol between the switch and all clients (no assignment). +""" +function _switch_entangler_all(prot) + @assert length(prot.clientnodes) == nsubsystems(prot.net[prot.switchnode])-1 "Number of clientnodes needs to equal the number of switch registers." + for (id, client) in enumerate(prot.clientnodes) + entangler = EntanglerProt( + sim=prot.sim, net=prot.net, + nodeA=prot.switchnode, nodeB=client, + rounds=1, attempts=1, success_prob=prot.success_probs[id], + attempt_time=prot.ticktock/10 # TODO this is a pretty arbitrary value + ) + @process entangler() + end +end + +""" +Run the entangler protocol between the switch and all clients (no assignment) where there is one respective client slot selected at the switch node. +""" +function _switch_entangler_all_selected(prot) + @assert length(prot.clientnodes) == nsubsystems(prot.net[prot.switchnode])-1 "Number of clientnodes needs to equal the number of switch registers." + for (id, client) in enumerate(prot.clientnodes) + entangler = SelectedEntanglerProt( + sim=prot.sim, net=prot.net, + nodeA=prot.switchnode, nodeB=client, + rounds=1, attempts=1, success_prob=prot.success_probs[id], + attempt_time=prot.ticktock/10 # TODO this is a pretty arbitrary value + ) + @process entangler() + end +end + +""" +Run `queryall(switch, EntanglemetnCounterpart, ...)` +to find out which clients the switch has successfully entangled with. +""" + +function _switch_successful_entanglements(prot, reverseclientindex) + switch = prot.net[prot.switchnode] + successes = queryall(switch, EntanglementCounterpart, in(prot.clientnodes), ❓) + entangled_clients = [r.tag[2] for r in successes] # RegRef (qubit slot) + if isempty(entangled_clients) + @debug "Switch $(prot.switchnode) failed to entangle with any clients" + return nothing + end + # get the maximum match for the actually connected nodes + ne = length(entangled_clients) + if ne < 1 return nothing end + entangled_clients_revindex = [reverseclientindex[k] for k in entangled_clients] + @info "Switch $(prot.switchnode) successfully entangled with clients $entangled_clients" + return entangled_clients_revindex +end + """ Run `queryall(switch, EntanglemetnCounterpart, ...)` to find out which clients the switch has successfully entangled with. @@ -329,6 +452,23 @@ function _switch_run_swaps(prot, match) end end +""" +Assuming the clientnodes are entangled, +perform fusion to connect them with piecemaker qubit (no backlog discounter yet!). +""" +function _switch_run_fusions(prot, matches) + @debug "Switch $(prot.switchnode) performs fusions for clients in $(match)" + for i in matches + @debug "Enter fusion protocol with client $(i)" + fusion = FusionProt( # TODO be more careful about how much simulated time this takes + sim=prot.sim, net=prot.net, node=prot.switchnode, + nodeC=prot.clientnodes[i], + rounds=1 + ) + @process fusion() + end +end + """ $TYPEDEF From 337fc92f939223d22fa72b8f343879fcf1ee4ed9 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 1 Oct 2024 18:44:38 +0200 Subject: [PATCH 02/38] added depolarization channel for stabilizer state repr and updated fidelity computation (new observable is projector of nclient-GHZ state) --- Project.toml | 3 ++- examples/piecemakerswitch/setup.jl | 9 +++++---- src/ProtocolZoo/ProtocolZoo.jl | 12 ++++++++---- src/backends/quantumoptics/uptotime.jl | 5 +++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 716f233b..c5ab1c42 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumSavory" uuid = "2de2e421-972c-4cb5-a0c3-999c85908079" authors = ["Stefan Krastanov "] -version = "0.6" +version = "0.6.0" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" @@ -22,6 +22,7 @@ QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 3123a807..1f72fa5c 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -22,14 +22,15 @@ end function prepare_simulation() - n = 4 # number of clients + n = 5 # number of clients m = n+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit + r_depol = 0#1e-5 # depolarization rate # The graph of network connectivity. Index 1 corresponds to the switch. graph = star_graph(n+1) - switch_register = Register(m) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit - client_registers = [Register(1) for _ in 1:n] + switch_register = Register(m, Depolarization(1/r_depol)) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit + client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] net = RegisterNet(graph, [switch_register, client_registers...]) sim = get_time_tracker(net) @@ -47,7 +48,7 @@ function prepare_simulation() @process consumer() # Finally, set up the switch without assignments - switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(0.7, n)) + switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(0.1, n)) @process switch_protocol() return n, sim diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 9f8d9d76..5a61a6a4 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -536,10 +536,13 @@ end (slot, id, tag) = pm[1] untag!(prot.piecemaker, id) - ob1 = real(observable(client_slots, tensor(collect(fill(Z, nclients))...))) - ob2 = real(observable(client_slots, tensor(collect(fill(X, nclients))...))) + result = observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z1,nclients)...]) + reduce(⊗,[fill(Z2,nclients)...])))) + @info result + + # ob1 = real(observable(client_slots, tensor(collect(fill(Z, nclients))...))) + # ob2 = real(observable(client_slots, tensor(collect(fill(X, nclients))...))) # if nclients-GHZ state achieved both observables equal 1 - @info "GHZConsumer: expectation values $(ob1) $(ob2)" + @info "GHZConsumer: expectation value $(result)" # delete tags and free client slots for k in 2:nclients+1 @@ -550,7 +553,8 @@ end end traceout!([prot.net[k][1] for k in 2:nclients+1]...) - push!(prot.log, (now(prot.sim), ob1, ob2)) + push!(prot.log, (now(prot.sim), result, 0.)) + @info prot.log for k in 2:nclients+1 unlock(prot.net[k][1]) diff --git a/src/backends/quantumoptics/uptotime.jl b/src/backends/quantumoptics/uptotime.jl index 4bee6ab0..27748a61 100644 --- a/src/backends/quantumoptics/uptotime.jl +++ b/src/backends/quantumoptics/uptotime.jl @@ -72,3 +72,8 @@ function krausops(d::AmplitudeDamping, Δt, basis) # https://quantumcomputing.st end # TODO add an amplitude damping example of transduction + +function krausops(Depol::Depolarization, Δt) + p = 1-exp(-Δt/Depol.τ) # TODO check this + [√(1-3*p/4) * _id, √(p/4) * _x, √(p/4) * _y, √(p/4) * _z] +end From 39ae2981f2da985611173399146bcb7398cc746e Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Fri, 4 Oct 2024 18:19:08 +0200 Subject: [PATCH 03/38] execute ghz generation only once, only entanglement generation should consume simulation time --- Project.toml | 1 + examples/piecemakerswitch/3_simple_run.jl | 9 ---- examples/piecemakerswitch/setup.jl | 53 ++++++++++-------- examples/piecemakerswitch/simple_run.jl | 9 ++++ src/ProtocolZoo/ProtocolZoo.jl | 65 ++++++++++++----------- src/ProtocolZoo/switches.jl | 13 ++--- 6 files changed, 80 insertions(+), 70 deletions(-) delete mode 100644 examples/piecemakerswitch/3_simple_run.jl create mode 100644 examples/piecemakerswitch/simple_run.jl diff --git a/Project.toml b/Project.toml index c5ab1c42..e955ef66 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.6.0" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" diff --git a/examples/piecemakerswitch/3_simple_run.jl b/examples/piecemakerswitch/3_simple_run.jl deleted file mode 100644 index da2fa5b7..00000000 --- a/examples/piecemakerswitch/3_simple_run.jl +++ /dev/null @@ -1,9 +0,0 @@ -include("setup.jl") - -# Prepare all of the simulation components -n, sim = prepare_simulation() - -step_ts = range(0, 10, step=0.1) -for t in step_ts - run(sim, t) -end diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 1f72fa5c..bb0ee7a6 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -4,38 +4,47 @@ using Graphs using ConcurrentSim using ResumableFunctions using Distributions +using DataFrames -@resumable function init_piecemaker(sim, net, m) - while true - @yield lock(net[1][m]) - if !isassigned(net[1][m]) - initialize!(net[1][m], X1) - tag!(net[1][m], Piecemaker, 1, m) - unlock(net[1][m]) - @yield timeout(sim, 0.1) - else - unlock(net[1][m]) - @yield timeout(sim, 0.1) - end - end -end +# @resumable function init_piecemaker(sim, net, m) +# while true +# @yield lock(net[1][m]) +# if !isassigned(net[1][m]) +# initialize!(net[1][m], X1) +# tag!(net[1][m], Piecemaker, 1, m) +# unlock(net[1][m]) +# @yield timeout(sim, 1e-9) +# else +# unlock(net[1][m]) +# @yield timeout(sim, 1e-9) +# end +# end +# end function prepare_simulation() - n = 5 # number of clients + n = 10 # number of clients m = n+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit - r_depol = 0#1e-5 # depolarization rate + depolar_prob = 0.1 + r_depol = - log(1 - depolar_prob) # depolarization rate # The graph of network connectivity. Index 1 corresponds to the switch. graph = star_graph(n+1) switch_register = Register(m, Depolarization(1/r_depol)) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit - client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] + client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] + @info switch_register.reprs[1] net = RegisterNet(graph, [switch_register, client_registers...]) sim = get_time_tracker(net) # Set up the initial |+> state of the piecemaker qubit - @process init_piecemaker(sim, net, m) + initialize!(net[1][m], X1) + tag!(net[1][m], Piecemaker, 1, m) + + event_ghz_state = Event(sim) + + # Set up the initial |+> state of the piecemaker qubit + # @process init_piecemaker(sim, net, m) # Set up the entanglement trackers at each client trackers = [EntanglementTracker(sim, net, k) for k in 2:n+1] @@ -44,12 +53,14 @@ function prepare_simulation() end # Set up an entanglement consumer between each unordered pair of clients - consumer = GHZConsumer(net, net[1][m]) + consumer = GHZConsumer(net, net[1][m], event_ghz_state) @process consumer() # Finally, set up the switch without assignments - switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(0.1, n)) + switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(0.5, n)) @process switch_protocol() + + - return n, sim + return n, sim, consumer end diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl new file mode 100644 index 00000000..83a76537 --- /dev/null +++ b/examples/piecemakerswitch/simple_run.jl @@ -0,0 +1,9 @@ +include("setup.jl") + +# Prepare all of the simulation components +n, sim, consumer = prepare_simulation() + +run(sim) + +df = DataFrame(consumer.log, [:DistributionTime, :Fidelity, :NaN]) +@info df \ No newline at end of file diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 5a61a6a4..e82aa4b6 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -8,7 +8,7 @@ using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap, Entangl using DocStringExtensions using Distributions: Geometric -using ConcurrentSim: Simulation, @yield, timeout, @process, now +using ConcurrentSim: Simulation, @yield, timeout, @process, now, Event, succeed, state, idle, StopSimulation import ConcurrentSim: Process import ResumableFunctions using ResumableFunctions: @resumable @@ -396,9 +396,9 @@ end error("`EntanglementTracker` on node $(prot.node) received a message $(msg) that it does not know how to handle (due to the absence of corresponding `EntanglementCounterpart` or `EntanglementHistory` or `EntanglementDelete` tags). This might have happened due to `CutoffProt` deleting qubits while swaps are happening. Make sure that the retention times in `CutoffProt` are sufficiently larger than the `agelimit` in `SwapperProt`. Otherwise, this is a bug in the protocol and should not happen -- please report an issue at QuantumSavory's repository.") end end - @debug "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" + @info "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" @yield wait(mb) - @debug "EntanglementTracker @$(prot.node): Message wait ends at $(now(prot.sim))" + @info "EntanglementTracker @$(prot.node): Message wait ends at $(now(prot.sim))" end end @@ -482,22 +482,25 @@ $FIELDS sim::Simulation """a network graph of registers""" net::RegisterNet - """the piecemaker qubit slot (RegRef)""" + """the piecemaker qubit slot""" piecemaker::RegRef + """event when all users are sharing a ghz state""" + event_ghz_state::Event """time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)""" - period::LT = 0.1 + period::LT = 1e-6 """stores the time and resulting observable from querying the piecemaker qubit for `EntanglementCounterpart`""" log::Vector{Tuple{Float64, Float64, Float64}} = Tuple{Float64, Float64, Float64}[] end -function GHZConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef; kwargs...) - return GHZConsumer(;sim, net, piecemaker, kwargs...) +function GHZConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) + return GHZConsumer(;sim, net, piecemaker, event_ghz_state, kwargs...) end -function GHZConsumer(net::RegisterNet, piecemaker::RegRef; kwargs...) - return GHZConsumer(get_time_tracker(net), net, piecemaker; kwargs...) +function GHZConsumer(net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) + return GHZConsumer(get_time_tracker(net), net, piecemaker, event_ghz_state; kwargs...) end @resumable function (prot::GHZConsumer)() + t_now = 0 if isnothing(prot.period) error("In `GHZConsumer` we do not yet support waiting on register to make qubits available") # TODO end @@ -537,12 +540,11 @@ end untag!(prot.piecemaker, id) result = observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z1,nclients)...]) + reduce(⊗,[fill(Z2,nclients)...])))) - @info result # ob1 = real(observable(client_slots, tensor(collect(fill(Z, nclients))...))) # ob2 = real(observable(client_slots, tensor(collect(fill(X, nclients))...))) # if nclients-GHZ state achieved both observables equal 1 - @info "GHZConsumer: expectation value $(result)" + @debug "GHZConsumer: expectation value $(result)" # delete tags and free client slots for k in 2:nclients+1 @@ -552,26 +554,26 @@ end end end + traceout!([prot.net[k][1] for k in 2:nclients+1]...) - push!(prot.log, (now(prot.sim), result, 0.)) - @info prot.log + if t_now == 0 + push!(prot.log, (now(prot.sim), result, 0.)) + else + t_elapsed = now(prot.sim) - t_now + push!(prot.log, (t_elapsed, result, 0.)) + end + t_now = now(prot.sim) for k in 2:nclients+1 unlock(prot.net[k][1]) end unlock(prot.piecemaker) - @yield timeout(prot.sim, prot.period) + + succeed(prot.event_ghz_state) + if state(prot.event_ghz_state) != idle + throw(StopSimulation("GHZ state shared among all users!")) + end end - - # @debug "GHZConsumer of $(prot.node): queries successful, consuming entanglement" - # untag!(q, query.id) - # # TODO do we need to add EntanglementHistory and should that be a different EntanglementHistory since the current one is specifically for SwapperProt - # # TODO currently when calculating the observable we assume that EntanglerProt.pairstate is always (|00⟩ + |11⟩)/√2, make it more general for other states - # ob1 = real(observable((q1, q2), tensor(collect(fill(Z, nclients))...)) - # ob2 = real(observable((q1, q2), X⊗X)) - - # traceout!(prot.net[prot.nodeA][q1.idx], prot.net[prot.nodeB][q2.idx]) - # push!(prot.log, (now(prot.sim), ob1, ob2)) @yield timeout(prot.sim, prot.period) end end @@ -609,7 +611,7 @@ $TYPEDFIELDS """fixed "busy time" duration immediately before starting entanglement generation attempts""" local_busy_time::Float64 = 0.0 # TODO the gates should have that busy time built in """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up and waiting)""" - retry_lock_time::LT = 0.1 + retry_lock_time::LT = 0.0 """how many rounds of this protocol to run (`-1` for infinite))""" rounds::Int = -1 end @@ -722,20 +724,19 @@ end round = 1 while rounds != 0 isentangled = !isnothing(query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, ❓; assigned=true)) - #margin = isentangled ? prot.margin : prot.hardmargin - a = prot.net[prot.nodeA][prot.nodeB-1] #findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) # - b = prot.net[prot.nodeB][1]#findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) # + a = prot.net[prot.nodeA][prot.nodeB-1] + b = prot.net[prot.nodeB][1] if isnothing(a) || isnothing(b) isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a \n2.\t $b \n retrying..." - @yield timeout(prot.sim, prot.retry_lock_time) + #@yield timeout(prot.sim, prot.retry_lock_time) continue end @yield lock(a) & lock(b) # this yield is expected to return immediately - @yield timeout(prot.sim, prot.local_busy_time_pre) + #@yield timeout(prot.sim, prot.local_busy_time_pre) attempts = if isone(prot.success_prob) 1 else @@ -745,7 +746,7 @@ end if (prot.attempts == -1 || prot.attempts >= attempts) && !isassigned(b) && !isassigned(a) @yield timeout(prot.sim, attempts * prot.attempt_time) initialize!((a,b), prot.pairstate; time=now(prot.sim)) - @yield timeout(prot.sim, prot.local_busy_time_post) + #@yield timeout(prot.sim, prot.local_busy_time_post) # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b tag!(a, EntanglementCounterpart, prot.nodeB, b.idx) @@ -759,7 +760,7 @@ end end unlock(a) unlock(b) - @yield timeout(prot.sim, prot.retry_lock_time) + #@yield timeout(prot.sim, prot.retry_lock_time) rounds==-1 || (rounds -= 1) round += 1 end diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index a5a46c4a..e99b9889 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -150,9 +150,6 @@ $TYPEDFIELDS ticktock::Float64 = 1 """how many rounds of this protocol to run (`-1` for infinite)""" rounds::Int = -1 - """the algorithm to use for memory slot assignment, defaulting to `promponas_bruteforce_choice`""" - #assignment_algorithm::AA = promponas_bruteforce_choice # TODO: not needed in the fusion protocol -- how to delete without breaking anything? - #backlog::SymMatrix{Matrix{Int}} function FusionSwitchDiscreteProt(sim, net, switchnode, clientnodes, success_probs, ticktock, rounds) length(unique(clientnodes)) == length(clientnodes) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `clientnodes` must be unique!")) all(in(neighbors(net, switchnode)), clientnodes) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `clientnodes` must be directly connected to the `switchnode`!")) @@ -179,18 +176,18 @@ FusionSwitchDiscreteProt(net, switchnode, clientnodes, success_probs; kwrags...) # run entangler without requests (=no assignment) _switch_entangler_all_selected(prot) - @yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net + @yield timeout(prot.sim, prot.ticktock) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net # read which entanglements were successful matches = _switch_successful_entanglements(prot, reverseclientindex) if isnothing(matches) - @yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net + matches = [] continue end # perform fusions _switch_run_fusions(prot, matches) - @yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net + #@yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net end end @@ -380,7 +377,7 @@ function _switch_entangler_all_selected(prot) sim=prot.sim, net=prot.net, nodeA=prot.switchnode, nodeB=client, rounds=1, attempts=1, success_prob=prot.success_probs[id], - attempt_time=prot.ticktock/10 # TODO this is a pretty arbitrary value + attempt_time=prot.ticktock ) @process entangler() end @@ -401,9 +398,9 @@ function _switch_successful_entanglements(prot, reverseclientindex) end # get the maximum match for the actually connected nodes ne = length(entangled_clients) + @info "Switch $(prot.switchnode) successfully entangled with $ne clients" if ne < 1 return nothing end entangled_clients_revindex = [reverseclientindex[k] for k in entangled_clients] - @info "Switch $(prot.switchnode) successfully entangled with clients $entangled_clients" return entangled_clients_revindex end From 54d9ad2e034722e76c746ac73dd98c4995d68480 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 8 Oct 2024 13:51:31 +0200 Subject: [PATCH 04/38] simple run finished, runs fusion protocol multiple times and collects distribution time and ghz-state fidelity as dataframe --- examples/piecemakerswitch/setup.jl | 1 - examples/piecemakerswitch/simple_run.jl | 16 +++++++++++---- src/ProtocolZoo/ProtocolZoo.jl | 27 +++++++++---------------- src/ProtocolZoo/switches.jl | 1 - 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index bb0ee7a6..4fa7af19 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -33,7 +33,6 @@ function prepare_simulation() switch_register = Register(m, Depolarization(1/r_depol)) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] - @info switch_register.reprs[1] net = RegisterNet(graph, [switch_register, client_registers...]) sim = get_time_tracker(net) diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index 83a76537..b78469f2 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -1,9 +1,17 @@ include("setup.jl") +results = DataFrame(DistributionTime=Float64[], Fidelity=Float64[]) +nruns = 10 + # Prepare all of the simulation components -n, sim, consumer = prepare_simulation() +for i in 1:nruns + n, sim, consumer = prepare_simulation() + run(sim) + + tuple_result = consumer.log[1] # Since it's just one tuple -run(sim) + df_run = DataFrame(consumer.log, [:DistributionTime, :Fidelity]) + append!(results, df_run) +end -df = DataFrame(consumer.log, [:DistributionTime, :Fidelity, :NaN]) -@info df \ No newline at end of file +@info results \ No newline at end of file diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index e82aa4b6..32f74981 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -396,9 +396,9 @@ end error("`EntanglementTracker` on node $(prot.node) received a message $(msg) that it does not know how to handle (due to the absence of corresponding `EntanglementCounterpart` or `EntanglementHistory` or `EntanglementDelete` tags). This might have happened due to `CutoffProt` deleting qubits while swaps are happening. Make sure that the retention times in `CutoffProt` are sufficiently larger than the `agelimit` in `SwapperProt`. Otherwise, this is a bug in the protocol and should not happen -- please report an issue at QuantumSavory's repository.") end end - @info "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" + @debug "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" @yield wait(mb) - @info "EntanglementTracker @$(prot.node): Message wait ends at $(now(prot.sim))" + @debug "EntanglementTracker @$(prot.node): Message wait ends at $(now(prot.sim))" end end @@ -487,9 +487,9 @@ $FIELDS """event when all users are sharing a ghz state""" event_ghz_state::Event """time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)""" - period::LT = 1e-6 + period::LT = 0.1 """stores the time and resulting observable from querying the piecemaker qubit for `EntanglementCounterpart`""" - log::Vector{Tuple{Float64, Float64, Float64}} = Tuple{Float64, Float64, Float64}[] + log::Vector{Tuple{Float64, Float64}} = Tuple{Float64, Float64}[] end function GHZConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) @@ -539,11 +539,7 @@ end (slot, id, tag) = pm[1] untag!(prot.piecemaker, id) - result = observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z1,nclients)...]) + reduce(⊗,[fill(Z2,nclients)...])))) - - # ob1 = real(observable(client_slots, tensor(collect(fill(Z, nclients))...))) - # ob2 = real(observable(client_slots, tensor(collect(fill(X, nclients))...))) - # if nclients-GHZ state achieved both observables equal 1 + result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z1,nclients)...]) + reduce(⊗,[fill(Z2,nclients)...]))))) @debug "GHZConsumer: expectation value $(result)" # delete tags and free client slots @@ -554,13 +550,12 @@ end end end - traceout!([prot.net[k][1] for k in 2:nclients+1]...) if t_now == 0 - push!(prot.log, (now(prot.sim), result, 0.)) + push!(prot.log, (now(prot.sim), result,)) else t_elapsed = now(prot.sim) - t_now - push!(prot.log, (t_elapsed, result, 0.)) + push!(prot.log, (t_elapsed, result,)) end t_now = now(prot.sim) @@ -604,14 +599,12 @@ $TYPEDFIELDS net::RegisterNet """the vertex of the node where fusion is happening""" node::Int - """the query for fusable qubits can return many positive candidates; `choose` picks one of them, defaults to a random pick `arr->rand(keys(arr))`""" - choose = random_index """the vertex of the remote node for the fusion""" nodeC::Int """fixed "busy time" duration immediately before starting entanglement generation attempts""" local_busy_time::Float64 = 0.0 # TODO the gates should have that busy time built in """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up and waiting)""" - retry_lock_time::LT = 0.0 + retry_lock_time::LT = 0.1 """how many rounds of this protocol to run (`-1` for infinite))""" rounds::Int = -1 end @@ -625,7 +618,7 @@ end rounds = prot.rounds round = 1 while rounds != 0 - fusable_qubit, piecemaker = findfusablequbit(prot.net, prot.node, prot.nodeC, prot.choose) # request client slot on switch node + fusable_qubit, piecemaker = findfusablequbit(prot.net, prot.node, prot.nodeC) # request client slots on switch node if isnothing(fusable_qubit) isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO @yield timeout(prot.sim, prot.retry_lock_time) @@ -657,7 +650,7 @@ end end end -function findfusablequbit(net, node, pred_client, choose_random) +function findfusablequbit(net, node, pred_client) reg = net[node] nodes = queryall(reg, EntanglementCounterpart, pred_client, ❓; locked=false) piecemaker = query(reg, Piecemaker, ❓, ❓) diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index e99b9889..6690781f 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -187,7 +187,6 @@ FusionSwitchDiscreteProt(net, switchnode, clientnodes, success_probs; kwrags...) # perform fusions _switch_run_fusions(prot, matches) - #@yield timeout(prot.sim, prot.ticktock/2) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net end end From 307fd093656260fa85db8e347c6163b22a36e44e Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 8 Oct 2024 15:22:15 +0200 Subject: [PATCH 05/38] updated README --- examples/piecemakerswitch/README.md | 13 +++++++++---- src/ProtocolZoo/ProtocolZoo.jl | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 0c85d4d5..fc972c3e 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -1,11 +1,16 @@ # System Overview - A central switch node connects to **n** clients. The switch possesses **m = n + 1** qubit slots, while each client has a single qubit slot. # Entanglement Initiation - -At each clock tick, the switch initiates entanglement attempts with each of the **n** clients, resulting in **n** entanglement processes per cycle. Successful entanglement links are then merged into a GHZ (Greenberger–Horne–Zeilinger) state using an additional "piecemaker" qubit located in the \((n + 1)\)th slot of the switch node. This fusion process is assumed to occur instantaneously. +At each clock tick, the switch initiates entanglement attempts with each of the **n** clients, resulting in **n** entanglement processes per cycle. Successful entanglement links are then merged into a GHZ (Greenberger–Horne–Zeilinger) state using an additional "piecemaker" qubit located in the \((n + 1)\)th slot of the switch node. This fusion process is assumed to occur instantaneously. Once all clients went through the fusion operation, the piecemaker qubit is measured out. This completes the fusing process and all nodes are sharing an n-GHZ state. # Fusion Operation +The fusion operation consists of applying a **CNOT** gate followed by a measurement in the computational basis. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states with the switch node: in each iteration, the piecemaker qubit (initialized in the state \(|+\rangle\)) is fused with one of the existing entangled states. + +# Noise +The memories residing the nodes' `Register`s suffer from depolarizing noise. The latter is modelled via Kraus operators applied to the current state's density matrix. + +### Troubleshooting +In the current implementation, sending and receiving classical measurement messages is achieved via a `DelayQueue` channel connected to `MessageBuffer`s at the nodes. The `MessageBuffer` acts as a buffer that holds incoming messages and manages processes waiting for messages. -The fusion operation consists of applying a **CNOT** gate followed by a measurement. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states at the switch node: in each iteration, the piecemaker qubit (initialized in the state \(|+\rangle\)) is fused with one of the existing entangled states. Once all clients went through the fusion operation, the piecemaker qubit is measured out. This completes the fusing process and all nodes are sharing an n-GHZ state +Important Note: By design, message passing should not involve simulated time delays by default. Messages are expected to be delivered instantaneously in simulation time unless explicitly specified otherwise. However, during simulation, the following debug output from the EntanglementTracker indicates that a delay is being introduced unexpectedly: `@debug "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)"` in the `EntanglementTracker`. This results in distribution times > # rounds of entanglement attempts, which should not be the case. \ No newline at end of file diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 32f74981..3038fcef 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -627,8 +627,8 @@ end (q, id, tag) = fusable_qubit.slot, fusable_qubit.id, fusable_qubit.tag (q_pm, id_pm, tag_pm) = piecemaker.slot, piecemaker.id, piecemaker.tag - @yield lock(q) & lock(q_pm) # this should not really need a yield thanks to `findswapablequbits`, but it is better to be defensive - @yield timeout(prot.sim, prot.local_busy_time) + #@yield lock(q) & lock(q_pm) # this should not really need a yield thanks to `findswapablequbits`, but it is better to be defensive + #@yield timeout(prot.sim, prot.local_busy_time) untag!(q, id) # store a history of whom we were entangled to for both client slot and piecemaker From f7a3f47e815addff9ce75c4ba1b419b78e669185 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Thu, 31 Oct 2024 18:15:05 +0100 Subject: [PATCH 06/38] adjustements in simulation setup and run script, noise applied after first round of entanglement --- .DS_Store | Bin 0 -> 6148 bytes Project.toml | 2 + examples/piecemakerswitch/README.md | 42 +++++++++++++-- examples/piecemakerswitch/setup.jl | 51 +++++++++--------- examples/piecemakerswitch/simple_run.jl | 56 +++++++++++++++---- src/ProtocolZoo/ProtocolZoo.jl | 68 +++++++++--------------- src/ProtocolZoo/switches.jl | 22 +++++--- 7 files changed, 153 insertions(+), 88 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0"] version = "0.6.0" [deps] +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" @@ -25,6 +26,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" [weakdeps] diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index fc972c3e..d86a98e2 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -10,7 +10,43 @@ The fusion operation consists of applying a **CNOT** gate followed by a measurem # Noise The memories residing the nodes' `Register`s suffer from depolarizing noise. The latter is modelled via Kraus operators applied to the current state's density matrix. -### Troubleshooting -In the current implementation, sending and receiving classical measurement messages is achieved via a `DelayQueue` channel connected to `MessageBuffer`s at the nodes. The `MessageBuffer` acts as a buffer that holds incoming messages and manages processes waiting for messages. +### Protocol flow -Important Note: By design, message passing should not involve simulated time delays by default. Messages are expected to be delivered instantaneously in simulation time unless explicitly specified otherwise. However, during simulation, the following debug output from the EntanglementTracker indicates that a delay is being introduced unexpectedly: `@debug "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)"` in the `EntanglementTracker`. This results in distribution times > # rounds of entanglement attempts, which should not be the case. \ No newline at end of file +```mermaid +sequenceDiagram + participant Client1 + participant ClientN + + participant SwitchNode + participant Log + + Note over Client1,SwitchNode: Round 1 (1 unit time) + par Entanglement Generation + Client1->>+SwitchNode: Try to generate entanglement + ClientN->>+SwitchNode: Try to generate entanglement + end + + SwitchNode->>SwitchNode: Run fusions with successful clients + + par Send Measurement Outcomes + SwitchNode-->>-Client1: Send measurement outcomes + SwitchNode-->>-ClientN: Send measurement outcomes + end + + par Apply Corrections (No time cost) + Client1->>Client1: Apply correction gates + ClientN->>ClientN: Apply correction gates + end + + loop Check Fusion Status (No time cost) + SwitchNode->>SwitchNode: Check if all clients are fused + alt All clients fused + SwitchNode->>SwitchNode: Measure piecemaker + SwitchNode->>SwitchNode: Compute fidelity to GHZ state + SwitchNode->>Log: Log fidelity and time + SwitchNode->>SwitchNode: Trigger STOP + else + SwitchNode->>SwitchNode: Keep checking + end + end +``` \ No newline at end of file diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 4fa7af19..f7a8211b 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -5,34 +5,39 @@ using ConcurrentSim using ResumableFunctions using Distributions using DataFrames +using CSV +using StatsPlots -# @resumable function init_piecemaker(sim, net, m) -# while true -# @yield lock(net[1][m]) -# if !isassigned(net[1][m]) -# initialize!(net[1][m], X1) -# tag!(net[1][m], Piecemaker, 1, m) -# unlock(net[1][m]) -# @yield timeout(sim, 1e-9) -# else -# unlock(net[1][m]) -# @yield timeout(sim, 1e-9) -# end -# end -# end +@resumable function init_piecemaker(sim, net, m) + while true + @yield lock(net[1][m]) + if !isassigned(net[1][m]) + initialize!(net[1][m], X1) + tag!(net[1][m], Piecemaker, 1, m) + unlock(net[1][m]) + @yield timeout(sim, 1) + else + unlock(net[1][m]) + @yield timeout(sim, 1) + end + end +end -function prepare_simulation() - n = 10 # number of clients +function prepare_simulation(nclients=2) + name = "qs_piecemeal" + nruns = 1000 + n = nclients # number of clients m = n+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit - depolar_prob = 0.1 - r_depol = - log(1 - depolar_prob) # depolarization rate + mem_depolar_prob = 0.1 + r_depol = - log(1 - mem_depolar_prob) # depolarization rate + link_success_prob = 0.5 # The graph of network connectivity. Index 1 corresponds to the switch. graph = star_graph(n+1) switch_register = Register(m, Depolarization(1/r_depol)) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit - client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] + client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] #Depolarization(1/r_depol) net = RegisterNet(graph, [switch_register, client_registers...]) sim = get_time_tracker(net) @@ -52,14 +57,12 @@ function prepare_simulation() end # Set up an entanglement consumer between each unordered pair of clients - consumer = GHZConsumer(net, net[1][m], event_ghz_state) + consumer = GHZConsumer(net, net[1][m], event_ghz_state; period=1) @process consumer() # Finally, set up the switch without assignments - switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(0.5, n)) + switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(link_success_prob, n); ticktock=1) @process switch_protocol() - - - return n, sim, consumer + return name, nruns, n, link_success_prob, mem_depolar_prob, sim, consumer end diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index b78469f2..b3ad0213 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -1,17 +1,53 @@ include("setup.jl") +using DataFrames +using CSV -results = DataFrame(DistributionTime=Float64[], Fidelity=Float64[]) -nruns = 10 +results_per_client = DataFrame[] +for nclients in 2:3 + # Prepare simulation components + type, nruns, n, link_success_prob, mem_depolar_prob, _, _ = prepare_simulation(nclients) # Assuming `n` is consistent across runs + distribution_times = Float64[] + fidelities = Float64[] + elapsed_times = Float64[] -# Prepare all of the simulation components -for i in 1:nruns - n, sim, consumer = prepare_simulation() - run(sim) + for i in 1:nruns + sim, consumer = prepare_simulation(nclients)[end-1:end] + elapsed_time = @elapsed run(sim) - tuple_result = consumer.log[1] # Since it's just one tuple + # Extract data from consumer.log + distribution_time, fidelity = consumer.log[1] + append!(distribution_times, distribution_time) + append!(fidelities, fidelity) + append!(elapsed_times, elapsed_time) + @info "Run $i completed" + end - df_run = DataFrame(consumer.log, [:DistributionTime, :Fidelity]) - append!(results, df_run) + # Initialize results DataFrame + results = DataFrame( + distribution_times = distribution_times, + fidelities = fidelities, + elapsed_times = elapsed_times + ) + results.num_remote_nodes .= n + results.link_success_prob .= link_success_prob + results.mem_depolar_prob .= mem_depolar_prob + results.type .= type + + push!(results_per_client, results) + @info "Clients $nclients completed" end +results_total = vcat(results_per_client...) + +# Group and summarize the data +grouped_df = groupby(results_total, [:num_remote_nodes, :distribution_times]) +summary_df = combine( + grouped_df, + :fidelities => mean => :mean_fidelities, + :fidelities => std => :std_fidelities +) + +@info summary_df -@info results \ No newline at end of file +# Write results to CSV +# CSV.write("examples/piecemakerswitch/output/piecemaker2-9.csv", results_total) +# CSV.write("examples/piecemakerswitch/output/piecemaker2-9_summary.csv", summary_df) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 3038fcef..f244279e 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -332,15 +332,17 @@ end counterpart = querydelete!(localslot, EntanglementCounterpart, pastremotenode, pastremoteslotid) unlock(localslot) if !isnothing(counterpart) - # time_before_lock = now(prot.sim) + time_before_lock = now(prot.sim) @yield lock(localslot) - # time_after_lock = now(prot.sim) - # time_before_lock != time_after_lock && @debug "EntanglementTracker @$(prot.node): Needed Δt=$(time_after_lock-time_before_lock) to get a lock" + time_after_lock = now(prot.sim) + time_before_lock != time_after_lock && @debug "EntanglementTracker @$(prot.node): Needed Δt=$(time_after_lock-time_before_lock) to get a lock" if !isassigned(localslot) unlock(localslot) error("There was an error in the entanglement tracking protocol `EntanglementTracker`. We were attempting to forward a classical message from a node that performed a swap to the remote entangled node. However, on reception of that message it was found that the remote node has lost track of its part of the entangled state although it still keeps a `Tag` as a record of it being present.") # TODO make it configurable whether an error is thrown and plug it into the logging module end + @debug "EntanglementTracker @$(prot.node): updategate = $(updategate)" if !isnothing(updategate) # EntanglementUpdate + @debug "Entanglement updated for $(prot.node).$(localslot.idx) 2" # Pauli frame correction gate if correction==2 apply!(localslot, updategate) @@ -348,6 +350,7 @@ end # tag local with updated EntanglementCounterpart new_remote_node new_remote_slot_idx tag!(localslot, EntanglementCounterpart, newremotenode, newremoteslotid) else # EntanglementDelete + @debug "Entanglement deleted" traceout!(localslot) end unlock(localslot) @@ -487,7 +490,7 @@ $FIELDS """event when all users are sharing a ghz state""" event_ghz_state::Event """time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)""" - period::LT = 0.1 + period::LT = 0.00001 """stores the time and resulting observable from querying the piecemaker qubit for `EntanglementCounterpart`""" log::Vector{Tuple{Float64, Float64}} = Tuple{Float64, Float64}[] end @@ -500,7 +503,6 @@ function GHZConsumer(net::RegisterNet, piecemaker::RegRef, event_ghz_state::Even end @resumable function (prot::GHZConsumer)() - t_now = 0 if isnothing(prot.period) error("In `GHZConsumer` we do not yet support waiting on register to make qubits available") # TODO end @@ -509,10 +511,11 @@ end qparticipating = queryall(prot.piecemaker, FusionCounterpart, ❓, ❓) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below if isnothing(qparticipating) @debug "GHZConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" - @yield timeout(prot.sim, prot.period) + #@yield timeout(prot.sim, prot.period) + continue return elseif length(qparticipating) == nclients - @info "All clients are now part of the GHZ state." + @debug "All clients are now part of the GHZ state." client_slots = [prot.net[k][1] for k in 2:nclients+1] # Wait for all locks to complete @@ -534,13 +537,14 @@ end if zmeas == 2 apply!(prot.net[2][1], Z) # apply correction on arbitrary client slot end + result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z2,nclients)...]) + reduce(⊗,[fill(Z1,nclients)...]))))) + @debug "GHZConsumer: expectation value $(result)" + pm = queryall(prot.piecemaker, ❓, ❓, ❓) @assert length(pm) < 2 "More than one entry for piecemaker in database." (slot, id, tag) = pm[1] + @debug "GHZConsumer: piecemaker qubit state real($(observable(slot, X1)))" untag!(prot.piecemaker, id) - - result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z1,nclients)...]) + reduce(⊗,[fill(Z2,nclients)...]))))) - @debug "GHZConsumer: expectation value $(result)" # delete tags and free client slots for k in 2:nclients+1 @@ -551,23 +555,15 @@ end end traceout!([prot.net[k][1] for k in 2:nclients+1]...) - if t_now == 0 - push!(prot.log, (now(prot.sim), result,)) - else - t_elapsed = now(prot.sim) - t_now - push!(prot.log, (t_elapsed, result,)) - end - t_now = now(prot.sim) - for k in 2:nclients+1 unlock(prot.net[k][1]) end unlock(prot.piecemaker) - - succeed(prot.event_ghz_state) - if state(prot.event_ghz_state) != idle - throw(StopSimulation("GHZ state shared among all users!")) - end + + # log results + push!(prot.log, (now(prot.sim), result,)) + throw(StopSimulation("GHZ state shared among all users!")) + @yield timeout(prot.sim, prot.period) end @yield timeout(prot.sim, prot.period) end @@ -635,9 +631,10 @@ end tag!(q, EntanglementHistory, tag[2], tag[3], prot.node, q_pm.idx, q.idx) tag!(q_pm, FusionCounterpart, tag[2], tag[3]) - uptotime!((q, q_pm), now(prot.sim)) + @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(q_pm.idx) @ $(now(prot.sim))" fuscircuit = EntanglementFusion() zmeas = fuscircuit(q, q_pm) + @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(q_pm.idx) @ $(now(prot.sim))" # send from here to client node # tag with EntanglementUpdateX past_local_node, past_local_slot_idx, past_remote_slot_idx, new_remote_node, new_remote_slot, correction msg = Tag(EntanglementUpdateZ, prot.node, q.idx, tag[3], prot.node, q_pm.idx, zmeas) @@ -668,7 +665,7 @@ and starts probabilistic attempts to establish entanglement. $TYPEDFIELDS """ -@kwdef struct SelectedEntanglerProt{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} +@kwdef struct SelectedEntanglerProt <: AbstractProtocol """time-and-schedule-tracking instance from `ConcurrentSim`""" sim::Simulation # TODO check that """a network graph of registers""" @@ -683,22 +680,10 @@ $TYPEDFIELDS success_prob::Float64 = 0.001 """duration of single entanglement attempt""" attempt_time::Float64 = 0.001 - """fixed "busy time" duration immediately before starting entanglement generation attempts""" - local_busy_time_pre::Float64 = 0.0 - """fixed "busy time" duration immediately after the a successful entanglement generation attempt""" - local_busy_time_post::Float64 = 0.0 - """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up)""" - retry_lock_time::LT = 0.1 """how many rounds of this protocol to run (`-1` for infinite)""" rounds::Int = -1 """maximum number of attempts to make per round (`-1` for infinite)""" attempts::Int = -1 - """whether the protocol should find the first available free slots in the nodes to be entangled or check for free slots randomly from the available slots""" - randomize::Bool = false - """Repeated rounds of this protocol may lead to monopolizing all slots of a pair of registers, starving or deadlocking other protocols. This field can be used to always leave a minimum number of slots free if there already exists entanglement between the current pair of nodes.""" - margin::Int = 0 - """Like `margin`, but it is enforced even when no entanglement has been established yet. Usually smaller than `margin`.""" - hardmargin::Int = 0 end """Convenience constructor for specifying `rate` of generation instead of success probability and time""" @@ -723,13 +708,10 @@ end if isnothing(a) || isnothing(b) isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a \n2.\t $b \n retrying..." - #@yield timeout(prot.sim, prot.retry_lock_time) continue end @yield lock(a) & lock(b) # this yield is expected to return immediately - - #@yield timeout(prot.sim, prot.local_busy_time_pre) attempts = if isone(prot.success_prob) 1 else @@ -737,9 +719,9 @@ end end if (prot.attempts == -1 || prot.attempts >= attempts) && !isassigned(b) && !isassigned(a) - @yield timeout(prot.sim, attempts * prot.attempt_time) + initialize!((a,b), prot.pairstate; time=now(prot.sim)) - #@yield timeout(prot.sim, prot.local_busy_time_post) + @yield timeout(prot.sim, attempts * prot.attempt_time) # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b tag!(a, EntanglementCounterpart, prot.nodeB, b.idx) @@ -748,12 +730,12 @@ end @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Entangled .$(a.idx) and .$(b.idx)" else + uptotime!((a,b), now(prot.sim)) @yield timeout(prot.sim, prot.attempts * prot.attempt_time) @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Performed the maximum number of attempts and gave up" end unlock(a) unlock(b) - #@yield timeout(prot.sim, prot.retry_lock_time) rounds==-1 || (rounds -= 1) round += 1 end diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index 6690781f..b1f58cf8 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -7,7 +7,7 @@ using Graphs: edges, complete_graph, neighbors #using GraphsMatching: maximum_weight_matching # TODO-MATCHING due to the dependence on BlossomV.jl this has trouble installing. See https://github.com/JuliaGraphs/GraphsMatching.jl/issues/14 using Combinatorics: combinations using DocStringExtensions: TYPEDEF, TYPEDFIELDS -using ConcurrentSim: @process, timeout, Simulation, Process +using ConcurrentSim: @process, timeout, Simulation, Process, now #using ResumableFunctions: @resumable, @yield # TODO serious bug that makes it not work without full `using` using ResumableFunctions using Random @@ -174,19 +174,22 @@ FusionSwitchDiscreteProt(net, switchnode, clientnodes, success_probs; kwrags...) while rounds != 0 rounds==-1 || (rounds -= 1) + @debug "Node $(prot.switchnode) starts a new entanglement round at time $(now(prot.sim))" # run entangler without requests (=no assignment) _switch_entangler_all_selected(prot) - @yield timeout(prot.sim, prot.ticktock) # TODO this is a pretty arbitrary value # TODO timeouts should work on prot and on net - + @yield timeout(prot.sim, prot.ticktock/2) + @debug "Node $(prot.switchnode) stops at time $(now(prot.sim))" # read which entanglements were successful matches = _switch_successful_entanglements(prot, reverseclientindex) if isnothing(matches) - matches = [] + @yield timeout(prot.sim, prot.ticktock/2) continue end - + @debug "Node $(prot.switchnode) performs fusions at time $(now(prot.sim))" # perform fusions _switch_run_fusions(prot, matches) + @yield timeout(prot.sim, prot.ticktock/2) + @debug "Node $(prot.switchnode) is done with fusions at time $(now(prot.sim))" end end @@ -369,14 +372,15 @@ end """ Run the entangler protocol between the switch and all clients (no assignment) where there is one respective client slot selected at the switch node. """ -function _switch_entangler_all_selected(prot) +function _switch_entangler_all_selected(prot, initial_round=false) @assert length(prot.clientnodes) == nsubsystems(prot.net[prot.switchnode])-1 "Number of clientnodes needs to equal the number of switch registers." + for (id, client) in enumerate(prot.clientnodes) entangler = SelectedEntanglerProt( sim=prot.sim, net=prot.net, nodeA=prot.switchnode, nodeB=client, rounds=1, attempts=1, success_prob=prot.success_probs[id], - attempt_time=prot.ticktock + attempt_time=prot.ticktock/2-0.00001; ) @process entangler() end @@ -385,6 +389,8 @@ end """ Run `queryall(switch, EntanglemetnCounterpart, ...)` to find out which clients the switch has successfully entangled with. +Then returns returns a list of indices corresponding to the successful clients. +We use this list of indices e.g., in `FusionSwitchDiscreteProt` to perform fusions with currently entangled client slots. """ function _switch_successful_entanglements(prot, reverseclientindex) @@ -397,7 +403,7 @@ function _switch_successful_entanglements(prot, reverseclientindex) end # get the maximum match for the actually connected nodes ne = length(entangled_clients) - @info "Switch $(prot.switchnode) successfully entangled with $ne clients" + @debug "Switch $(prot.switchnode) successfully entangled with $ne clients" if ne < 1 return nothing end entangled_clients_revindex = [reverseclientindex[k] for k in entangled_clients] return entangled_clients_revindex From 8a141608aff7a2d920865070f09bca2b5f232b3d Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 6 Nov 2024 10:06:43 +0100 Subject: [PATCH 07/38] deleted init function, moved param value setting to run script --- examples/piecemakerswitch/setup.jl | 38 ++++++------------------- examples/piecemakerswitch/simple_run.jl | 12 +++++--- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index f7a8211b..7baaa706 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -7,37 +7,18 @@ using Distributions using DataFrames using CSV using StatsPlots - -@resumable function init_piecemaker(sim, net, m) - while true - @yield lock(net[1][m]) - if !isassigned(net[1][m]) - initialize!(net[1][m], X1) - tag!(net[1][m], Piecemaker, 1, m) - unlock(net[1][m]) - @yield timeout(sim, 1) - else - unlock(net[1][m]) - @yield timeout(sim, 1) - end - end -end -function prepare_simulation(nclients=2) - name = "qs_piecemeal" - nruns = 1000 - n = nclients # number of clients - m = n+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit - mem_depolar_prob = 0.1 +function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_prob = 0.5) + + m = nclients+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit r_depol = - log(1 - mem_depolar_prob) # depolarization rate - link_success_prob = 0.5 # The graph of network connectivity. Index 1 corresponds to the switch. - graph = star_graph(n+1) + graph = star_graph(nclients+1) switch_register = Register(m, Depolarization(1/r_depol)) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit - client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:n] #Depolarization(1/r_depol) + client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:nclients] #Depolarization(1/r_depol) net = RegisterNet(graph, [switch_register, client_registers...]) sim = get_time_tracker(net) @@ -47,11 +28,8 @@ function prepare_simulation(nclients=2) event_ghz_state = Event(sim) - # Set up the initial |+> state of the piecemaker qubit - # @process init_piecemaker(sim, net, m) - # Set up the entanglement trackers at each client - trackers = [EntanglementTracker(sim, net, k) for k in 2:n+1] + trackers = [EntanglementTracker(sim, net, k) for k in 2:nclients+1] for tracker in trackers @process tracker() end @@ -61,8 +39,8 @@ function prepare_simulation(nclients=2) @process consumer() # Finally, set up the switch without assignments - switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:n+1, fill(link_success_prob, n); ticktock=1) + switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:nclients+1, fill(link_success_prob, nclients); ticktock=1) @process switch_protocol() - return name, nruns, n, link_success_prob, mem_depolar_prob, sim, consumer + return sim, consumer end diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index b3ad0213..9827242e 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -2,16 +2,20 @@ include("setup.jl") using DataFrames using CSV +name = "qs_piecemeal" +nruns = 1000 +mem_depolar_prob = 0.1 +link_success_prob = 0.5 + results_per_client = DataFrame[] for nclients in 2:3 # Prepare simulation components - type, nruns, n, link_success_prob, mem_depolar_prob, _, _ = prepare_simulation(nclients) # Assuming `n` is consistent across runs distribution_times = Float64[] fidelities = Float64[] elapsed_times = Float64[] for i in 1:nruns - sim, consumer = prepare_simulation(nclients)[end-1:end] + sim, consumer = prepare_simulation(nclients, mem_depolar_prob, link_success_prob) elapsed_time = @elapsed run(sim) # Extract data from consumer.log @@ -28,10 +32,10 @@ for nclients in 2:3 fidelities = fidelities, elapsed_times = elapsed_times ) - results.num_remote_nodes .= n + results.num_remote_nodes .= nclients results.link_success_prob .= link_success_prob results.mem_depolar_prob .= mem_depolar_prob - results.type .= type + results.type .= name push!(results_per_client, results) @info "Clients $nclients completed" From 0957cf69981b45e4ca54f645551e4dc29c49c07a Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 6 Nov 2024 10:09:24 +0100 Subject: [PATCH 08/38] changed GHZConsumer to FusionConsumer --- examples/piecemakerswitch/setup.jl | 2 +- src/ProtocolZoo/ProtocolZoo.jl | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 7baaa706..de7484c9 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -35,7 +35,7 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro end # Set up an entanglement consumer between each unordered pair of clients - consumer = GHZConsumer(net, net[1][m], event_ghz_state; period=1) + consumer = FusionConsumer(net, net[1][m], event_ghz_state; period=1) @process consumer() # Finally, set up the switch without assignments diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index f244279e..00f5670f 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -16,7 +16,7 @@ import SumTypes export # protocols - EntanglerProt, SelectedEntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, GHZConsumer, CutoffProt, + EntanglerProt, SelectedEntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, FusionConsumer, CutoffProt, # tags EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, Piecemaker, # from Switches @@ -480,7 +480,7 @@ A protocol running between two nodes, checking periodically for any entangled st $FIELDS """ -@kwdef struct GHZConsumer{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} +@kwdef struct FusionConsumer{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}} """time-and-schedule-tracking instance from `ConcurrentSim`""" sim::Simulation """a network graph of registers""" @@ -495,22 +495,22 @@ $FIELDS log::Vector{Tuple{Float64, Float64}} = Tuple{Float64, Float64}[] end -function GHZConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) - return GHZConsumer(;sim, net, piecemaker, event_ghz_state, kwargs...) +function FusionConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) + return FusionConsumer(;sim, net, piecemaker, event_ghz_state, kwargs...) end -function GHZConsumer(net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) - return GHZConsumer(get_time_tracker(net), net, piecemaker, event_ghz_state; kwargs...) +function FusionConsumer(net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) + return FusionConsumer(get_time_tracker(net), net, piecemaker, event_ghz_state; kwargs...) end -@resumable function (prot::GHZConsumer)() +@resumable function (prot::FusionConsumer)() if isnothing(prot.period) - error("In `GHZConsumer` we do not yet support waiting on register to make qubits available") # TODO + error("In `FusionConsumer` we do not yet support waiting on register to make qubits available") # TODO end while true nclients = nsubsystems(prot.net[1])-1 qparticipating = queryall(prot.piecemaker, FusionCounterpart, ❓, ❓) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below if isnothing(qparticipating) - @debug "GHZConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" + @debug "FusionConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" #@yield timeout(prot.sim, prot.period) continue return @@ -527,7 +527,7 @@ end all_locks = reduce(&, tasks) @yield all_locks - @debug "GHZConsumer of $(prot.piecemaker): queries successful, consuming entanglement" + @debug "FusionConsumer of $(prot.piecemaker): queries successful, consuming entanglement" for q in qparticipating untag!(prot.piecemaker, q.id) end @@ -538,12 +538,12 @@ end apply!(prot.net[2][1], Z) # apply correction on arbitrary client slot end result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z2,nclients)...]) + reduce(⊗,[fill(Z1,nclients)...]))))) - @debug "GHZConsumer: expectation value $(result)" + @debug "FusionConsumer: expectation value $(result)" pm = queryall(prot.piecemaker, ❓, ❓, ❓) @assert length(pm) < 2 "More than one entry for piecemaker in database." (slot, id, tag) = pm[1] - @debug "GHZConsumer: piecemaker qubit state real($(observable(slot, X1)))" + @debug "FusionConsumer: piecemaker qubit state real($(observable(slot, X1)))" untag!(prot.piecemaker, id) # delete tags and free client slots From 0aaeae14223d9b31487d0dbbf672444d0eed8d43 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 6 Nov 2024 10:57:30 +0100 Subject: [PATCH 09/38] deleted tag Piecemaker --- examples/piecemakerswitch/setup.jl | 9 +++--- src/ProtocolZoo/ProtocolZoo.jl | 47 ++++++++---------------------- 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index de7484c9..bb539d8d 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -24,7 +24,6 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro # Set up the initial |+> state of the piecemaker qubit initialize!(net[1][m], X1) - tag!(net[1][m], Piecemaker, 1, m) event_ghz_state = Event(sim) @@ -34,13 +33,13 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro @process tracker() end - # Set up an entanglement consumer between each unordered pair of clients - consumer = FusionConsumer(net, net[1][m], event_ghz_state; period=1) - @process consumer() - # Finally, set up the switch without assignments switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:nclients+1, fill(link_success_prob, nclients); ticktock=1) @process switch_protocol() + + # Set up an entanglement consumer between each unordered pair of clients + consumer = FusionConsumer(net, net[1][m], event_ghz_state; period=1) + @process consumer() return sim, consumer end diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 00f5670f..30e520d8 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -18,7 +18,7 @@ export # protocols EntanglerProt, SelectedEntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, FusionConsumer, CutoffProt, # tags - EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, Piecemaker, + EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, # from Switches SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest @@ -62,23 +62,6 @@ end Base.show(io::IO, tag::FusionCounterpart) = print(io, "GHZ state shared with $(tag.remote_node).$(tag.remote_slot)") Tag(tag::FusionCounterpart) = Tag(FusionCounterpart, tag.remote_node, tag.remote_slot) -""" -$TYPEDEF - -Indicates the piecemaker responsible for fusions of a remote node's slot. - -$TYPEDFIELDS -""" -@kwdef struct Piecemaker - "the id of the switch node" - node::Int - "the slot in the switch node containing piecemaker qubit" - slot::Int -end -Base.show(io::IO, tag::Piecemaker) = print(io, "Piecemaker slot at $(tag.node).$(tag.slot)") -Tag(tag::Piecemaker) = Tag(Piecemaker, tag.node, tag.slot) - - """ $TYPEDEF @@ -540,12 +523,6 @@ end result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z2,nclients)...]) + reduce(⊗,[fill(Z1,nclients)...]))))) @debug "FusionConsumer: expectation value $(result)" - pm = queryall(prot.piecemaker, ❓, ❓, ❓) - @assert length(pm) < 2 "More than one entry for piecemaker in database." - (slot, id, tag) = pm[1] - @debug "FusionConsumer: piecemaker qubit state real($(observable(slot, X1)))" - untag!(prot.piecemaker, id) - # delete tags and free client slots for k in 2:nclients+1 queries = queryall(prot.net[k], EntanglementCounterpart, ❓, ❓) @@ -622,26 +599,25 @@ end end (q, id, tag) = fusable_qubit.slot, fusable_qubit.id, fusable_qubit.tag - (q_pm, id_pm, tag_pm) = piecemaker.slot, piecemaker.id, piecemaker.tag - #@yield lock(q) & lock(q_pm) # this should not really need a yield thanks to `findswapablequbits`, but it is better to be defensive - #@yield timeout(prot.sim, prot.local_busy_time) + @yield lock(q) & lock(piecemaker) # this should not really need a yield thanks to `findfusablequbit`, but it is better to be defensive + @yield timeout(prot.sim, prot.local_busy_time) untag!(q, id) # store a history of whom we were entangled to for both client slot and piecemaker - tag!(q, EntanglementHistory, tag[2], tag[3], prot.node, q_pm.idx, q.idx) - tag!(q_pm, FusionCounterpart, tag[2], tag[3]) + tag!(q, EntanglementHistory, tag[2], tag[3], prot.node, piecemaker.idx, q.idx) + tag!(piecemaker, FusionCounterpart, tag[2], tag[3]) - @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(q_pm.idx) @ $(now(prot.sim))" + @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" fuscircuit = EntanglementFusion() - zmeas = fuscircuit(q, q_pm) - @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(q_pm.idx) @ $(now(prot.sim))" + zmeas = fuscircuit(q, piecemaker) + @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" # send from here to client node # tag with EntanglementUpdateX past_local_node, past_local_slot_idx, past_remote_slot_idx, new_remote_node, new_remote_slot, correction - msg = Tag(EntanglementUpdateZ, prot.node, q.idx, tag[3], prot.node, q_pm.idx, zmeas) + msg = Tag(EntanglementUpdateZ, prot.node, q.idx, tag[3], prot.node, piecemaker.idx, zmeas) put!(channel(prot.net, prot.node=>tag[2]; permit_forward=true), msg) @debug "FusionProt @$(prot.node)|round $(round): Send message to $(tag[2]) | message=`$msg`" unlock(q) - unlock(q_pm) + unlock(piecemaker) rounds==-1 || (rounds -= 1) round += 1 end @@ -650,7 +626,8 @@ end function findfusablequbit(net, node, pred_client) reg = net[node] nodes = queryall(reg, EntanglementCounterpart, pred_client, ❓; locked=false) - piecemaker = query(reg, Piecemaker, ❓, ❓) + index_piecemaker = nsubsystems(net[1]) + piecemaker = net[1][index_piecemaker] isempty(nodes) && return nothing @assert length(nodes) == 1 "Client seems to be entangled multiple times" return nodes[1], piecemaker From be08d75a96009fda90c519c8bb53fe772b8b1b9b Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 6 Nov 2024 14:47:07 +0100 Subject: [PATCH 10/38] reset project env in parent directory and added specific dependencies in piecemaker env; updated gitignore --- .gitignore | 3 +- .vscode/settings.json | 3 ++ Project.toml | 4 -- examples/piecemakerswitch/Project.toml | 60 +++++++++++++++++++++++++ examples/piecemakerswitch/simple_run.jl | 2 + 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 examples/piecemakerswitch/Project.toml diff --git a/.gitignore b/.gitignore index 46c9f72e..6c731034 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ Manifest.toml build .gitignore ROADMAP.md -coverage \ No newline at end of file +coverage +.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6095fb3e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "julia.environmentPath": "/Users/localadmin/Documents/github/QuantumSavory.jl/examples/piecemakerswitch" +} \ No newline at end of file diff --git a/Project.toml b/Project.toml index eeda94c5..da8db3fc 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,8 @@ authors = ["Stefan Krastanov "] version = "0.6.0" [deps] -CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" @@ -24,9 +22,7 @@ QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" [weakdeps] diff --git a/examples/piecemakerswitch/Project.toml b/examples/piecemakerswitch/Project.toml new file mode 100644 index 00000000..eeda94c5 --- /dev/null +++ b/examples/piecemakerswitch/Project.toml @@ -0,0 +1,60 @@ +name = "QuantumSavory" +uuid = "2de2e421-972c-4cb5-a0c3-999c85908079" +authors = ["Stefan Krastanov "] +version = "0.6.0" + +[deps] +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" +ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" +QuantumInterface = "5717a53b-5d69-4fa3-b976-0bf2f97ca1e5" +QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c" +QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" +QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" +SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" + +[weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" + +[extensions] +QuantumSavoryMakie = "Makie" + +[compat] +Combinatorics = "1" +ConcurrentSim = "1.4.1" +Distributions = "0.25.90" +DocStringExtensions = "0.9" +Graphs = "1.9" +IterTools = "1.4.0" +LinearAlgebra = "1" +Makie = "0.20, 0.21" +NetworkLayout = "0.4.4" +PrecompileTools = "1" +Printf = "1" +QuantumClifford = "0.9.9" +QuantumInterface = "0.3.5" +QuantumOptics = "1.1.0" +QuantumOpticsBase = "0.5.3" +QuantumSymbolics = "0.4.3" +Random = "1" +Reexport = "1.2.2" +ResumableFunctions = "0.6.9" +Statistics = "1" +SumTypes = "0.5.5" +julia = "1.10" diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index 9827242e..bb958c1f 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -2,6 +2,8 @@ include("setup.jl") using DataFrames using CSV + +# Set up the simulation parameters name = "qs_piecemeal" nruns = 1000 mem_depolar_prob = 0.1 From 81f12d146ef251dbff7194f0cbec31d1ef29bd03 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 6 Nov 2024 16:42:41 +0100 Subject: [PATCH 11/38] deleted .DS_Store; updated local env --- .DS_Store | Bin 6148 -> 0 bytes examples/piecemakerswitch/Project.toml | 56 ++----------------------- examples/piecemakerswitch/setup.jl | 5 +-- src/ProtocolZoo/ProtocolZoo.jl | 13 +++--- 4 files changed, 10 insertions(+), 64 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0"] -version = "0.6.0" - [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" -ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" -DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" -IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" -PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" -Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" -QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" -QuantumInterface = "5717a53b-5d69-4fa3-b976-0bf2f97ca1e5" -QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c" -QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" -QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" -Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" -SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" - -[weakdeps] -Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" - -[extensions] -QuantumSavoryMakie = "Makie" - -[compat] -Combinatorics = "1" -ConcurrentSim = "1.4.1" -Distributions = "0.25.90" -DocStringExtensions = "0.9" -Graphs = "1.9" -IterTools = "1.4.0" -LinearAlgebra = "1" -Makie = "0.20, 0.21" -NetworkLayout = "0.4.4" -PrecompileTools = "1" -Printf = "1" -QuantumClifford = "0.9.9" -QuantumInterface = "0.3.5" -QuantumOptics = "1.1.0" -QuantumOpticsBase = "0.5.3" -QuantumSymbolics = "0.4.3" -Random = "1" -Reexport = "1.2.2" -ResumableFunctions = "0.6.9" -Statistics = "1" -SumTypes = "0.5.5" -julia = "1.10" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" \ No newline at end of file diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index bb539d8d..f45a3a04 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -6,7 +6,6 @@ using ResumableFunctions using Distributions using DataFrames using CSV -using StatsPlots function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_prob = 0.5) @@ -25,8 +24,6 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro # Set up the initial |+> state of the piecemaker qubit initialize!(net[1][m], X1) - event_ghz_state = Event(sim) - # Set up the entanglement trackers at each client trackers = [EntanglementTracker(sim, net, k) for k in 2:nclients+1] for tracker in trackers @@ -38,7 +35,7 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro @process switch_protocol() # Set up an entanglement consumer between each unordered pair of clients - consumer = FusionConsumer(net, net[1][m], event_ghz_state; period=1) + consumer = FusionConsumer(net, net[1][m]; period=1) @process consumer() return sim, consumer diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 30e520d8..465e8f7a 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -470,19 +470,17 @@ $FIELDS net::RegisterNet """the piecemaker qubit slot""" piecemaker::RegRef - """event when all users are sharing a ghz state""" - event_ghz_state::Event """time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)""" period::LT = 0.00001 """stores the time and resulting observable from querying the piecemaker qubit for `EntanglementCounterpart`""" log::Vector{Tuple{Float64, Float64}} = Tuple{Float64, Float64}[] end -function FusionConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) - return FusionConsumer(;sim, net, piecemaker, event_ghz_state, kwargs...) +function FusionConsumer(sim::Simulation, net::RegisterNet, piecemaker::RegRef; kwargs...) + return FusionConsumer(;sim, net, piecemaker, kwargs...) end -function FusionConsumer(net::RegisterNet, piecemaker::RegRef, event_ghz_state::Event; kwargs...) - return FusionConsumer(get_time_tracker(net), net, piecemaker, event_ghz_state; kwargs...) +function FusionConsumer(net::RegisterNet, piecemaker::RegRef; kwargs...) + return FusionConsumer(get_time_tracker(net), net, piecemaker; kwargs...) end @resumable function (prot::FusionConsumer)() @@ -699,7 +697,8 @@ end initialize!((a,b), prot.pairstate; time=now(prot.sim)) @yield timeout(prot.sim, attempts * prot.attempt_time) - + + @info "test" # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b tag!(a, EntanglementCounterpart, prot.nodeB, b.idx) # tag local node b with EntanglementCounterpart remote_node_idx_a remote_slot_idx_a From cef2e068dd8cb7131ce10a21321a87356e18a3f0 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 6 Nov 2024 17:48:36 +0100 Subject: [PATCH 12/38] updated readme --- examples/piecemakerswitch/README.md | 2 +- src/ProtocolZoo/ProtocolZoo.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index d86a98e2..5ef6fa9e 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -8,7 +8,7 @@ At each clock tick, the switch initiates entanglement attempts with each of the The fusion operation consists of applying a **CNOT** gate followed by a measurement in the computational basis. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states with the switch node: in each iteration, the piecemaker qubit (initialized in the state \(|+\rangle\)) is fused with one of the existing entangled states. # Noise -The memories residing the nodes' `Register`s suffer from depolarizing noise. The latter is modelled via Kraus operators applied to the current state's density matrix. +The memories residing the nodes' `Register`s suffer from depolarizing noise. ### Protocol flow diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 465e8f7a..0cbb863f 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -698,7 +698,6 @@ end initialize!((a,b), prot.pairstate; time=now(prot.sim)) @yield timeout(prot.sim, attempts * prot.attempt_time) - @info "test" # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b tag!(a, EntanglementCounterpart, prot.nodeB, b.idx) # tag local node b with EntanglementCounterpart remote_node_idx_a remote_slot_idx_a From 467a31491f5a4363f6cde0e6047057b7ba92b21d Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Thu, 21 Nov 2024 13:52:47 +0100 Subject: [PATCH 13/38] Uptdated to event-driven implementation; added slot variables to EntanglerProt (and therefore deleted SelectedEntanglerProt); added Depolarization test to the kraus_lindblad test set --- examples/piecemakerswitch/Project.toml | 5 +- examples/piecemakerswitch/setup.jl | 54 ++++++-- examples/piecemakerswitch/simple_run.jl | 15 +-- src/ProtocolZoo/ProtocolZoo.jl | 116 ++++-------------- src/backends/quantumoptics/uptotime.jl | 5 + test/test_noninstant_and_backgrounds_qubit.jl | 4 + 6 files changed, 85 insertions(+), 114 deletions(-) diff --git a/examples/piecemakerswitch/Project.toml b/examples/piecemakerswitch/Project.toml index 8bf43bd7..28cbcdee 100644 --- a/examples/piecemakerswitch/Project.toml +++ b/examples/piecemakerswitch/Project.toml @@ -1,10 +1,11 @@ [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" \ No newline at end of file +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index f45a3a04..ecc11bc5 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -6,7 +6,41 @@ using ResumableFunctions using Distributions using DataFrames using CSV - +using Profile +using NetworkLayout + +@resumable function entangle_and_fuse(sim, net, client, link_success_prob) + + # Set up the entanglement trackers at each client + tracker = EntanglementTracker(sim, net, client) + @process tracker() + + # Set up the entangler and fuser protocols at each client + entangler = EntanglerProt( + sim=sim, net=net, nodeA=1, slotA=client-1, nodeB=client, + success_prob=link_success_prob, rounds=1, attempts=-1, + start_timer_after_first_attempt=true, # TODO: for some reason attempt_timeout=1 is not working, hardcoded in constructor for now + ) + @yield @process entangler() + + fuser = FusionProt( + sim=sim, net=net, node=1, + nodeC=client, + rounds=1 + ) + @yield @process fuser() +end + + +@resumable function run_protocols(sim, net, nclients, link_success_prob) + # Run entangler and fusion for each client and wait for all to finish + procs_succeeded = [] + for k in 2:nclients+1 + proc_succeeded = @process entangle_and_fuse(sim, net, k, link_success_prob) + push!(procs_succeeded, proc_succeeded) + end + @yield reduce(&, procs_succeeded) +end function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_prob = 0.5) @@ -23,20 +57,14 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro # Set up the initial |+> state of the piecemaker qubit initialize!(net[1][m], X1) + + # Run entangler and fusion for each client and wait for all to finish + @process run_protocols(sim, net, nclients, link_success_prob) - # Set up the entanglement trackers at each client - trackers = [EntanglementTracker(sim, net, k) for k in 2:nclients+1] - for tracker in trackers - @process tracker() - end - - # Finally, set up the switch without assignments - switch_protocol = FusionSwitchDiscreteProt(net, 1, 2:nclients+1, fill(link_success_prob, nclients); ticktock=1) - @process switch_protocol() - - # Set up an entanglement consumer between each unordered pair of clients + # Set up the consumer to measure final entangled state consumer = FusionConsumer(net, net[1][m]; period=1) @process consumer() - + return sim, consumer end + diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index bb958c1f..facf6702 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -10,16 +10,16 @@ mem_depolar_prob = 0.1 link_success_prob = 0.5 results_per_client = DataFrame[] -for nclients in 2:3 - # Prepare simulation components +for nclients in 2:9 + # Prepare simulation data storage distribution_times = Float64[] fidelities = Float64[] elapsed_times = Float64[] + # Run the simulation nruns times for i in 1:nruns sim, consumer = prepare_simulation(nclients, mem_depolar_prob, link_success_prob) - elapsed_time = @elapsed run(sim) - + elapsed_time = @elapsed run(sim) # Extract data from consumer.log distribution_time, fidelity = consumer.log[1] append!(distribution_times, distribution_time) @@ -28,7 +28,7 @@ for nclients in 2:3 @info "Run $i completed" end - # Initialize results DataFrame + # Fill the results DataFrame results = DataFrame( distribution_times = distribution_times, fidelities = fidelities, @@ -55,5 +55,6 @@ summary_df = combine( @info summary_df # Write results to CSV -# CSV.write("examples/piecemakerswitch/output/piecemaker2-9.csv", results_total) -# CSV.write("examples/piecemakerswitch/output/piecemaker2-9_summary.csv", summary_df) +# CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven.csv", results_total) +# CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven_summary.csv", summary_df) + diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 0cbb863f..2ed45866 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -16,7 +16,7 @@ import SumTypes export # protocols - EntanglerProt, SelectedEntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, FusionConsumer, CutoffProt, + EntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, FusionConsumer, CutoffProt, # tags EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, # from Switches @@ -181,12 +181,16 @@ $TYPEDFIELDS nodeA::Int """the vertex index of node B""" nodeB::Int + """the slot index of node A""" + slotA::Union{Int,Wildcard} = ❓ + """the slot index of node B""" + slotB::Union{Int,Wildcard} = ❓ """the state being generated (supports symbolic, numeric, noisy, and pure)""" pairstate = StabilizerState("ZZ XX") """success probability of one attempt of entanglement generation""" success_prob::Float64 = 0.001 """duration of single entanglement attempt""" - attempt_time::Float64 = 0.001 + attempt_time::Float64 = 1.0 """fixed "busy time" duration immediately before starting entanglement generation attempts""" local_busy_time_pre::Float64 = 0.0 """fixed "busy time" duration immediately after the a successful entanglement generation attempt""" @@ -203,6 +207,8 @@ $TYPEDFIELDS margin::Int = 0 """Like `margin`, but it is enforced even when no entanglement has been established yet. Usually smaller than `margin`.""" hardmargin::Int = 0 + """It might be useful to start the simulation time after the first round of attempt.""" + start_timer_after_first_attempt::Bool = false end """Convenience constructor for specifying `rate` of generation instead of success probability and time""" @@ -220,10 +226,15 @@ end rounds = prot.rounds round = 1 while rounds != 0 - isentangled = !isnothing(query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, ❓; assigned=true)) + + isentangled = !isnothing(query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, prot.slotB; assigned=true)) margin = isentangled ? prot.margin : prot.hardmargin - a_ = findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) - b_ = findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) + + a_ = (prot.slotA == ❓) ? findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) : prot.net[prot.nodeA][prot.slotA] + b_ = (prot.slotB == ❓) ? findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) : prot.net[prot.nodeB][prot.slotB] + + # a_ = findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) + # b_ = findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) if isnothing(a_) || isnothing(b_) isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO @@ -241,12 +252,15 @@ end @yield timeout(prot.sim, prot.local_busy_time_pre) attempts = if isone(prot.success_prob) 1 + elseif prot.start_timer_after_first_attempt + rand(Geometric(prot.success_prob)) else rand(Geometric(prot.success_prob))+1 end if prot.attempts == -1 || prot.attempts >= attempts - @yield timeout(prot.sim, attempts * prot.attempt_time) initialize!((a,b), prot.pairstate; time=now(prot.sim)) + @yield timeout(prot.sim, attempts * prot.attempt_time) + #uptotime!((a,b), now(prot.sim)) @yield timeout(prot.sim, prot.local_busy_time_post) # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b @@ -471,7 +485,7 @@ $FIELDS """the piecemaker qubit slot""" piecemaker::RegRef """time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)""" - period::LT = 0.00001 + period::LT = 1.0 """stores the time and resulting observable from querying the piecemaker qubit for `EntanglementCounterpart`""" log::Vector{Tuple{Float64, Float64}} = Tuple{Float64, Float64}[] end @@ -484,6 +498,7 @@ function FusionConsumer(net::RegisterNet, piecemaker::RegRef; kwargs...) end @resumable function (prot::FusionConsumer)() + if isnothing(prot.period) error("In `FusionConsumer` we do not yet support waiting on register to make qubits available") # TODO end @@ -492,7 +507,6 @@ end qparticipating = queryall(prot.piecemaker, FusionCounterpart, ❓, ❓) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below if isnothing(qparticipating) @debug "FusionConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" - #@yield timeout(prot.sim, prot.period) continue return elseif length(qparticipating) == nclients @@ -529,7 +543,7 @@ end end end - traceout!([prot.net[k][1] for k in 2:nclients+1]...) + #traceout!([prot.net[k][1] for k in 2:nclients+1]...) for k in 2:nclients+1 unlock(prot.net[k][1]) end @@ -608,6 +622,7 @@ end @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" fuscircuit = EntanglementFusion() zmeas = fuscircuit(q, piecemaker) + uptotime!((q, piecemaker), now(prot.sim)) @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" # send from here to client node # tag with EntanglementUpdateX past_local_node, past_local_slot_idx, past_remote_slot_idx, new_remote_node, new_remote_slot, correction @@ -631,90 +646,7 @@ function findfusablequbit(net, node, pred_client) return nodes[1], piecemaker end -""" -$TYPEDEF - -A protocol that generates entanglement between two nodes. -Whenever the selected client slot and the associated slot on the remote node are free, the protocol locks them -and starts probabilistic attempts to establish entanglement. - -$TYPEDFIELDS -""" -@kwdef struct SelectedEntanglerProt <: AbstractProtocol - """time-and-schedule-tracking instance from `ConcurrentSim`""" - sim::Simulation # TODO check that - """a network graph of registers""" - net::RegisterNet - """the vertex index of node A""" - nodeA::Int - """the vertex index of node B""" - nodeB::Int - """the state being generated (supports symbolic, numeric, noisy, and pure)""" - pairstate = StabilizerState("ZZ XX") - """success probability of one attempt of entanglement generation""" - success_prob::Float64 = 0.001 - """duration of single entanglement attempt""" - attempt_time::Float64 = 0.001 - """how many rounds of this protocol to run (`-1` for infinite)""" - rounds::Int = -1 - """maximum number of attempts to make per round (`-1` for infinite)""" - attempts::Int = -1 -end - -"""Convenience constructor for specifying `rate` of generation instead of success probability and time""" -function SelectedEntanglerProt(sim::Simulation, net::RegisterNet, nodeA::Int, nodeB::Int; rate::Union{Nothing,Float64}=nothing, kwargs...) - if isnothing(rate) - return SelectedEntanglerProt(;sim, net, nodeA, nodeB, kwargs...) - else - return SelectedEntanglerProt(;sim, net, nodeA, nodeB, kwargs..., success_prob=0.001, attempt_time=0.001/rate) - end -end - -#TODO """Convenience constructor for specifying `fidelity` of generation instead of success probability and time""" - -@resumable function (prot::SelectedEntanglerProt)() - rounds = prot.rounds - round = 1 - while rounds != 0 - isentangled = !isnothing(query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, ❓; assigned=true)) - a = prot.net[prot.nodeA][prot.nodeB-1] - b = prot.net[prot.nodeB][1] - if isnothing(a) || isnothing(b) - isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO - @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a \n2.\t $b \n retrying..." - continue - end - - @yield lock(a) & lock(b) # this yield is expected to return immediately - attempts = if isone(prot.success_prob) - 1 - else - rand(Geometric(prot.success_prob))+1 - end - - if (prot.attempts == -1 || prot.attempts >= attempts) && !isassigned(b) && !isassigned(a) - - initialize!((a,b), prot.pairstate; time=now(prot.sim)) - @yield timeout(prot.sim, attempts * prot.attempt_time) - - # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b - tag!(a, EntanglementCounterpart, prot.nodeB, b.idx) - # tag local node b with EntanglementCounterpart remote_node_idx_a remote_slot_idx_a - tag!(b, EntanglementCounterpart, prot.nodeA, a.idx) - - @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Entangled .$(a.idx) and .$(b.idx)" - else - uptotime!((a,b), now(prot.sim)) - @yield timeout(prot.sim, prot.attempts * prot.attempt_time) - @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Performed the maximum number of attempts and gave up" - end - unlock(a) - unlock(b) - rounds==-1 || (rounds -= 1) - round += 1 - end -end include("cutoff.jl") diff --git a/src/backends/quantumoptics/uptotime.jl b/src/backends/quantumoptics/uptotime.jl index 27748a61..c278efb4 100644 --- a/src/backends/quantumoptics/uptotime.jl +++ b/src/backends/quantumoptics/uptotime.jl @@ -25,6 +25,11 @@ function uptotime!(state::Operator, idx::Int, background, Δt) mul!(tmpr,tmpl,k',1,0) nstate.data .+= tmpr.data end + # Suggestion for alternative implementation: + # for k in Ks + # k = e ? embed(b, [idx], k) : k + # nstate .+= k * state * adjoint(k) + # end end @assert abs(tr(nstate)) ≈ 1. # TODO maybe put under a debug flag nstate diff --git a/test/test_noninstant_and_backgrounds_qubit.jl b/test/test_noninstant_and_backgrounds_qubit.jl index 6c38b620..50bc1beb 100644 --- a/test/test_noninstant_and_backgrounds_qubit.jl +++ b/test/test_noninstant_and_backgrounds_qubit.jl @@ -54,3 +54,7 @@ kraus_lindblad_test(T2Dephasing(1.0),Z1) kraus_lindblad_test(T2Dephasing(1.0),Z2) kraus_lindblad_test(T2Dephasing(1.0),X1) kraus_lindblad_test(T2Dephasing(1.0),X2) +# kraus_lindblad_test(Depolarization(1.0), Z1) +# kraus_lindblad_test(Depolarization(1.0),Z2) +# kraus_lindblad_test(Depolarization(1.0),X1) +# kraus_lindblad_test(Depolarization(1.0),X2) From 77631f22ea1d54315ae45dff8af7fa20198b9b23 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 3 Dec 2024 13:05:23 +0100 Subject: [PATCH 14/38] cleaned up code; deleted FusionSwitchDiscreteProtocol as it is not needed anymore for event driven simulation; added small testfile to see workings of initiate(); --- .gitignore | 3 +- examples/piecemakerswitch/setup.jl | 17 +++-- examples/piecemakerswitch/simple_run.jl | 8 +-- examples/piecemakerswitch/test_initialize.jl | 30 ++++++++ src/ProtocolZoo/ProtocolZoo.jl | 32 ++++----- src/ProtocolZoo/switches.jl | 73 +------------------- 6 files changed, 64 insertions(+), 99 deletions(-) create mode 100644 examples/piecemakerswitch/test_initialize.jl diff --git a/.gitignore b/.gitignore index 6c731034..bf700eb2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ build .gitignore ROADMAP.md coverage -.DS_Store \ No newline at end of file +.DS_Store +.vscode \ No newline at end of file diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index ecc11bc5..0bfe0d5d 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -9,6 +9,11 @@ using CSV using Profile using NetworkLayout +@resumable function init_state(sim, net, nclients, delay) + @yield timeout(sim, delay) + initialize!(net[1][nclients+1], X1; time=now(sim)) +end + @resumable function entangle_and_fuse(sim, net, client, link_success_prob) # Set up the entanglement trackers at each client @@ -18,11 +23,10 @@ using NetworkLayout # Set up the entangler and fuser protocols at each client entangler = EntanglerProt( sim=sim, net=net, nodeA=1, slotA=client-1, nodeB=client, - success_prob=link_success_prob, rounds=1, attempts=-1, - start_timer_after_first_attempt=true, # TODO: for some reason attempt_timeout=1 is not working, hardcoded in constructor for now + success_prob=link_success_prob, rounds=1, attempts=-1, attempt_time=1.0 ) @yield @process entangler() - + fuser = FusionProt( sim=sim, net=net, node=1, nodeC=client, @@ -46,6 +50,7 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro m = nclients+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit r_depol = - log(1 - mem_depolar_prob) # depolarization rate + delay = 1 # initialize the piecemaker |+> after one time unit (in order to provide fidelity ==1 if success probability = 1) # The graph of network connectivity. Index 1 corresponds to the switch. graph = star_graph(nclients+1) @@ -55,16 +60,16 @@ function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_pro net = RegisterNet(graph, [switch_register, client_registers...]) sim = get_time_tracker(net) - # Set up the initial |+> state of the piecemaker qubit - initialize!(net[1][m], X1) + @process init_state(sim, net, nclients, delay) # Run entangler and fusion for each client and wait for all to finish @process run_protocols(sim, net, nclients, link_success_prob) # Set up the consumer to measure final entangled state - consumer = FusionConsumer(net, net[1][m]; period=1) + consumer = FusionConsumer(net, net[1][m]; period=0.001) @process consumer() return sim, consumer end + diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index facf6702..b5afa69d 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -5,7 +5,8 @@ using CSV # Set up the simulation parameters name = "qs_piecemeal" -nruns = 1000 + +nruns = 10 mem_depolar_prob = 0.1 link_success_prob = 0.5 @@ -54,7 +55,6 @@ summary_df = combine( @info summary_df -# Write results to CSV +# Uncomment to write results to CSV # CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven.csv", results_total) -# CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven_summary.csv", summary_df) - +# CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven_summary.csv", summary_df) \ No newline at end of file diff --git a/examples/piecemakerswitch/test_initialize.jl b/examples/piecemakerswitch/test_initialize.jl new file mode 100644 index 00000000..a15a1ffe --- /dev/null +++ b/examples/piecemakerswitch/test_initialize.jl @@ -0,0 +1,30 @@ +using QuantumSavory +using QuantumSavory.ProtocolZoo +using Graphs +using ConcurrentSim +using ResumableFunctions +using Distributions +using DataFrames +using CSV +using Profile +using NetworkLayout + + + +@resumable function init_state(sim) + @yield timeout(sim, 1.) + initialize!(slot[1], Z1; time=now(sim)) + res = observable(slot[1], projector(Z1); time=now(sim)) + @info res +end + +mem_depolar_prob = 0.5 +r_depol = - log(1 - mem_depolar_prob) +print(r_depol) +slot = Register(1, Depolarization(1/r_depol)) + +sim = get_time_tracker(slot) + +@process init_state(sim) +run(sim) + diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 2ed45866..23571b0e 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -8,7 +8,7 @@ using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap, Entangl using DocStringExtensions using Distributions: Geometric -using ConcurrentSim: Simulation, @yield, timeout, @process, now, Event, succeed, state, idle, StopSimulation +using ConcurrentSim: Simulation, @yield, timeout, @process, now, StopSimulation import ConcurrentSim: Process import ResumableFunctions using ResumableFunctions: @resumable @@ -20,7 +20,7 @@ export # tags EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, # from Switches - SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest + SimpleSwitchDiscreteProt, SwitchRequest abstract type AbstractProtocol end @@ -50,6 +50,7 @@ Tag(tag::EntanglementCounterpart) = Tag(EntanglementCounterpart, tag.remote_node $TYPEDEF Indicates the current entanglement status with a remote node's slot. Added when a new qubit is fused into the GHZ state through [`FusionProt`](@ref). +The [`EntanglementTracker`](@ref) receives an [`EntanglementUpdate`] message: the receives the tag pointing to a client slot it has already performed fusion with. $TYPEDFIELDS """ @@ -190,7 +191,7 @@ $TYPEDFIELDS """success probability of one attempt of entanglement generation""" success_prob::Float64 = 0.001 """duration of single entanglement attempt""" - attempt_time::Float64 = 1.0 + attempt_time::Float64 = 0.001 """fixed "busy time" duration immediately before starting entanglement generation attempts""" local_busy_time_pre::Float64 = 0.0 """fixed "busy time" duration immediately after the a successful entanglement generation attempt""" @@ -207,8 +208,6 @@ $TYPEDFIELDS margin::Int = 0 """Like `margin`, but it is enforced even when no entanglement has been established yet. Usually smaller than `margin`.""" hardmargin::Int = 0 - """It might be useful to start the simulation time after the first round of attempt.""" - start_timer_after_first_attempt::Bool = false end """Convenience constructor for specifying `rate` of generation instead of success probability and time""" @@ -233,7 +232,7 @@ end a_ = (prot.slotA == ❓) ? findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) : prot.net[prot.nodeA][prot.slotA] b_ = (prot.slotB == ❓) ? findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) : prot.net[prot.nodeB][prot.slotB] - # a_ = findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) + # a_ = findfreeslot(prot.net[prot.nodeA]; randomize=prot.randomize, margin=margin) # TODO: old version, delete before merge # b_ = findfreeslot(prot.net[prot.nodeB]; randomize=prot.randomize, margin=margin) if isnothing(a_) || isnothing(b_) @@ -252,15 +251,14 @@ end @yield timeout(prot.sim, prot.local_busy_time_pre) attempts = if isone(prot.success_prob) 1 - elseif prot.start_timer_after_first_attempt - rand(Geometric(prot.success_prob)) else rand(Geometric(prot.success_prob))+1 end if prot.attempts == -1 || prot.attempts >= attempts - initialize!((a,b), prot.pairstate; time=now(prot.sim)) + @yield timeout(prot.sim, attempts * prot.attempt_time) - #uptotime!((a,b), now(prot.sim)) + initialize!((a,b), prot.pairstate; time=now(prot.sim)) + @yield timeout(prot.sim, prot.local_busy_time_post) # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b @@ -473,7 +471,7 @@ end """ $TYPEDEF -A protocol running between two nodes, checking periodically for any entangled states (GHZ states) between all nodes and consuming/emptying the qubit slots. +A protocol running on a (switch) node with a dedicated 'piecemaker' qubit state. Queries periodically how many nodes have undergone fusion with the latter. When all nodes are fused with the piecemaker qubit it is measured out and the correction gate is performed at a slot of one of the entangled nodes. $FIELDS """ @@ -504,14 +502,15 @@ end end while true nclients = nsubsystems(prot.net[1])-1 - qparticipating = queryall(prot.piecemaker, FusionCounterpart, ❓, ❓) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below + qparticipating = queryall(prot.piecemaker, FusionCounterpart, ❓, ❓) if isnothing(qparticipating) @debug "FusionConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" continue return elseif length(qparticipating) == nclients @debug "All clients are now part of the GHZ state." - client_slots = [prot.net[k][1] for k in 2:nclients+1] + # use query tag "remote_node.remote_slot" to access the clients that have been fused with the piecemaker + client_slots = [prot.net[q.tag[2]][q.tag[3]] for q in qparticipating] #[prot.net[k][1] for k in 2:nclients+1] # Wait for all locks to complete tasks = [] @@ -532,7 +531,7 @@ end if zmeas == 2 apply!(prot.net[2][1], Z) # apply correction on arbitrary client slot end - result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z2,nclients)...]) + reduce(⊗,[fill(Z1,nclients)...]))))) + result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z2,nclients)...]) + reduce(⊗,[fill(Z1,nclients)...]))); time=now(prot.sim))) @debug "FusionConsumer: expectation value $(result)" # delete tags and free client slots @@ -550,9 +549,8 @@ end unlock(prot.piecemaker) # log results - push!(prot.log, (now(prot.sim), result,)) + push!(prot.log, (floor(now(prot.sim)), result,)) throw(StopSimulation("GHZ state shared among all users!")) - @yield timeout(prot.sim, prot.period) end @yield timeout(prot.sim, prot.period) end @@ -622,7 +620,7 @@ end @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" fuscircuit = EntanglementFusion() zmeas = fuscircuit(q, piecemaker) - uptotime!((q, piecemaker), now(prot.sim)) + #uptotime!((q, piecemaker), now(prot.sim)) @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" # send from here to client node # tag with EntanglementUpdateX past_local_node, past_local_slot_idx, past_remote_slot_idx, new_remote_node, new_remote_slot, correction diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index b1f58cf8..5c358d3b 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -7,12 +7,12 @@ using Graphs: edges, complete_graph, neighbors #using GraphsMatching: maximum_weight_matching # TODO-MATCHING due to the dependence on BlossomV.jl this has trouble installing. See https://github.com/JuliaGraphs/GraphsMatching.jl/issues/14 using Combinatorics: combinations using DocStringExtensions: TYPEDEF, TYPEDFIELDS -using ConcurrentSim: @process, timeout, Simulation, Process, now +using ConcurrentSim: @process, timeout, Simulation, Process #using ResumableFunctions: @resumable, @yield # TODO serious bug that makes it not work without full `using` using ResumableFunctions using Random -export SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest +export SimpleSwitchDiscreteProt, SwitchRequest """ A wrapper around a matrix, ensuring that it is symmetric. @@ -124,75 +124,6 @@ function capture_stdout(f) return r end -""" -$TYPEDEF - -A switch protocol running on a given switch node, and attempting to serve n neighboring clients by executing fusion operations -with each of them to generate a shared n-GHZ state. The protocol proceeds in discrete time intervals. -First, clients attempt link-level entanglement with the switch. Next, successful clients undergo a fusion operation. -It merges two GHZ states into a single GHZ state (modulo Pauli corrections). - - -$TYPEDFIELDS -""" -@kwdef struct FusionSwitchDiscreteProt <: AbstractProtocol - """time-and-schedule-tracking instance from `ConcurrentSim`""" - sim::Simulation # TODO check that - """a network graph of registers""" - net::RegisterNet - """the vertex index of the switch""" - switchnode::Int - """the vertex indices of the clients""" - clientnodes::Vector{Int} - """best-guess about success of establishing raw entanglement between client and switch""" - success_probs::Vector{Float64} - """duration of a single full cycle of the switching decision algorithm""" - ticktock::Float64 = 1 - """how many rounds of this protocol to run (`-1` for infinite)""" - rounds::Int = -1 - function FusionSwitchDiscreteProt(sim, net, switchnode, clientnodes, success_probs, ticktock, rounds) - length(unique(clientnodes)) == length(clientnodes) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `clientnodes` must be unique!")) - all(in(neighbors(net, switchnode)), clientnodes) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `clientnodes` must be directly connected to the `switchnode`!")) - 0 < ticktock || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested protocol period `ticktock` must be positive!")) - 0 < rounds || rounds == -1 || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested number of rounds `rounds` must be positive or `-1` for infinite!")) - length(clientnodes) == length(success_probs) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `success_probs` must have the same length as `clientnodes`!")) - all(0 .<= success_probs .<= 1) || throw(ArgumentError("In the preparation of `FusionSwitchDiscreteProt` switch protocol, the requested `success_probs` must be in the range [0,1]!")) - new(sim, net, switchnode, clientnodes, success_probs, ticktock, rounds) - end -end - -function FusionSwitchDiscreteProt(sim, net, switchnode, clientnodes, success_probs; kwrags...) - FusionSwitchDiscreteProt(;sim, net, switchnode, clientnodes=collect(clientnodes), success_probs=collect(success_probs), kwrags...) -end -FusionSwitchDiscreteProt(net, switchnode, clientnodes, success_probs; kwrags...) = FusionSwitchDiscreteProt(get_time_tracker(net), net, switchnode, clientnodes, success_probs; kwrags...) - -@resumable function (prot::FusionSwitchDiscreteProt)() - rounds = prot.rounds - clientnodes = prot.clientnodes - reverseclientindex = Dict{Int,Int}(c=>i for (i,c) in enumerate(clientnodes)) - - while rounds != 0 - rounds==-1 || (rounds -= 1) - - @debug "Node $(prot.switchnode) starts a new entanglement round at time $(now(prot.sim))" - # run entangler without requests (=no assignment) - _switch_entangler_all_selected(prot) - @yield timeout(prot.sim, prot.ticktock/2) - @debug "Node $(prot.switchnode) stops at time $(now(prot.sim))" - # read which entanglements were successful - matches = _switch_successful_entanglements(prot, reverseclientindex) - if isnothing(matches) - @yield timeout(prot.sim, prot.ticktock/2) - continue - end - @debug "Node $(prot.switchnode) performs fusions at time $(now(prot.sim))" - # perform fusions - _switch_run_fusions(prot, matches) - @yield timeout(prot.sim, prot.ticktock/2) - @debug "Node $(prot.switchnode) is done with fusions at time $(now(prot.sim))" - end -end - """ $TYPEDEF From e7d43492d7f458843c6cd1e1da17e7bc9455d93b Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 3 Dec 2024 13:11:54 +0100 Subject: [PATCH 15/38] deleted functions not in use (entangler functions and fusion function) --- src/ProtocolZoo/ProtocolZoo.jl | 5 ++-- src/ProtocolZoo/switches.jl | 50 ---------------------------------- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 23571b0e..9d1019e0 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -255,10 +255,9 @@ end rand(Geometric(prot.success_prob))+1 end if prot.attempts == -1 || prot.attempts >= attempts - + @yield timeout(prot.sim, attempts * prot.attempt_time) - initialize!((a,b), prot.pairstate; time=now(prot.sim)) - + initialize!((a,b), prot.pairstate; time=now(prot.sim)) @yield timeout(prot.sim, prot.local_busy_time_post) # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index 5c358d3b..21b6ac7a 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -284,39 +284,6 @@ function _switch_entangler(prot, assignment) end end -""" -Run the entangler protocol between the switch and all clients (no assignment). -""" -function _switch_entangler_all(prot) - @assert length(prot.clientnodes) == nsubsystems(prot.net[prot.switchnode])-1 "Number of clientnodes needs to equal the number of switch registers." - for (id, client) in enumerate(prot.clientnodes) - entangler = EntanglerProt( - sim=prot.sim, net=prot.net, - nodeA=prot.switchnode, nodeB=client, - rounds=1, attempts=1, success_prob=prot.success_probs[id], - attempt_time=prot.ticktock/10 # TODO this is a pretty arbitrary value - ) - @process entangler() - end -end - -""" -Run the entangler protocol between the switch and all clients (no assignment) where there is one respective client slot selected at the switch node. -""" -function _switch_entangler_all_selected(prot, initial_round=false) - @assert length(prot.clientnodes) == nsubsystems(prot.net[prot.switchnode])-1 "Number of clientnodes needs to equal the number of switch registers." - - for (id, client) in enumerate(prot.clientnodes) - entangler = SelectedEntanglerProt( - sim=prot.sim, net=prot.net, - nodeA=prot.switchnode, nodeB=client, - rounds=1, attempts=1, success_prob=prot.success_probs[id], - attempt_time=prot.ticktock/2-0.00001; - ) - @process entangler() - end -end - """ Run `queryall(switch, EntanglemetnCounterpart, ...)` to find out which clients the switch has successfully entangled with. @@ -385,23 +352,6 @@ function _switch_run_swaps(prot, match) end end -""" -Assuming the clientnodes are entangled, -perform fusion to connect them with piecemaker qubit (no backlog discounter yet!). -""" -function _switch_run_fusions(prot, matches) - @debug "Switch $(prot.switchnode) performs fusions for clients in $(match)" - for i in matches - @debug "Enter fusion protocol with client $(i)" - fusion = FusionProt( # TODO be more careful about how much simulated time this takes - sim=prot.sim, net=prot.net, node=prot.switchnode, - nodeC=prot.clientnodes[i], - rounds=1 - ) - @process fusion() - end -end - """ $TYPEDEF From 5ab85ceb0d3ef1d82ce35fbbdbb4f1dd0cef1668 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 3 Dec 2024 13:14:20 +0100 Subject: [PATCH 16/38] deleted .vscode --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 6095fb3e..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "julia.environmentPath": "/Users/localadmin/Documents/github/QuantumSavory.jl/examples/piecemakerswitch" -} \ No newline at end of file From 46681bb1eda9754389994f621b043add20923e94 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 3 Dec 2024 13:35:54 +0100 Subject: [PATCH 17/38] added explanation to tag FusionCounterpart --- src/ProtocolZoo/ProtocolZoo.jl | 8 ++++---- src/ProtocolZoo/switches.jl | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 9d1019e0..02f717da 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -49,8 +49,8 @@ Tag(tag::EntanglementCounterpart) = Tag(EntanglementCounterpart, tag.remote_node """ $TYPEDEF -Indicates the current entanglement status with a remote node's slot. Added when a new qubit is fused into the GHZ state through [`FusionProt`](@ref). -The [`EntanglementTracker`](@ref) receives an [`EntanglementUpdate`] message: the receives the tag pointing to a client slot it has already performed fusion with. +Indicates the current entanglement status with a remote node's slot. Added when a new qubit is fused into the multipartide state through [`FusionProt`](@ref). +The [`EntanglementTracker`](@ref) receives an [`EntanglementUpdate`] message: a tag pointing to a remote node slot it has performed fusion with. For example, in the `piecemakerswitch` setup the piecemaker slot in the central node is taged with all the client nodes it is fused with. $TYPEDFIELDS """ @@ -60,7 +60,7 @@ $TYPEDFIELDS "the slot in the remote node containing the qubit we are entangled to" remote_slot::Int end -Base.show(io::IO, tag::FusionCounterpart) = print(io, "GHZ state shared with $(tag.remote_node).$(tag.remote_slot)") +Base.show(io::IO, tag::FusionCounterpart) = print(io, "Fused with $(tag.remote_node).$(tag.remote_slot)") Tag(tag::FusionCounterpart) = Tag(FusionCounterpart, tag.remote_node, tag.remote_slot) @@ -257,7 +257,7 @@ end if prot.attempts == -1 || prot.attempts >= attempts @yield timeout(prot.sim, attempts * prot.attempt_time) - initialize!((a,b), prot.pairstate; time=now(prot.sim)) + initialize!((a,b), prot.pairstate; time=now(prot.sim)) @yield timeout(prot.sim, prot.local_busy_time_post) # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index 21b6ac7a..101d6c2c 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -288,7 +288,6 @@ end Run `queryall(switch, EntanglemetnCounterpart, ...)` to find out which clients the switch has successfully entangled with. Then returns returns a list of indices corresponding to the successful clients. -We use this list of indices e.g., in `FusionSwitchDiscreteProt` to perform fusions with currently entangled client slots. """ function _switch_successful_entanglements(prot, reverseclientindex) From badefaaf9497db73e4f7fc401effa9cf9c628280 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 3 Dec 2024 14:41:18 +0100 Subject: [PATCH 18/38] changed order of parameters in fusioncircuit to (control, target); added docstring and jldoctest to fusioncircuit --- examples/piecemakerswitch/README.md | 6 +-- examples/piecemakerswitch/simple_run.jl | 2 +- .../piecemakerswitch/test_fusioncircuit.jl | 15 ++++++++ src/CircuitZoo/CircuitZoo.jl | 38 +++++++++++++++++-- src/ProtocolZoo/ProtocolZoo.jl | 2 +- 5 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 examples/piecemakerswitch/test_fusioncircuit.jl diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 5ef6fa9e..317f11ea 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -1,11 +1,11 @@ # System Overview -A central switch node connects to **n** clients. The switch possesses **m = n + 1** qubit slots, while each client has a single qubit slot. +A central switch node connects to $n$ clients. The switch possesses $m = n + 1$ qubit slots, while each client has a single qubit slot. # Entanglement Initiation -At each clock tick, the switch initiates entanglement attempts with each of the **n** clients, resulting in **n** entanglement processes per cycle. Successful entanglement links are then merged into a GHZ (Greenberger–Horne–Zeilinger) state using an additional "piecemaker" qubit located in the \((n + 1)\)th slot of the switch node. This fusion process is assumed to occur instantaneously. Once all clients went through the fusion operation, the piecemaker qubit is measured out. This completes the fusing process and all nodes are sharing an n-GHZ state. +At each clock tick, the switch initiates entanglement attempts with each of the $n$ clients, resulting in $n$ entanglement processes per cycle. Successful entanglement links are then merged into a GHZ (Greenberger–Horne–Zeilinger) state using an additional "piecemaker" qubit located in the $(n + 1)$th slot of the switch node. This fusion process is assumed to occur instantaneously. Once all clients went through the fusion operation, the piecemaker qubit is measured out. This completes the fusing process and all nodes are sharing an n-GHZ state. # Fusion Operation -The fusion operation consists of applying a **CNOT** gate followed by a measurement in the computational basis. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states with the switch node: in each iteration, the piecemaker qubit (initialized in the state \(|+\rangle\)) is fused with one of the existing entangled states. +The fusion operation consists of applying a `CNOT` gate followed by a measurement in the computational basis. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states with the switch node: in each iteration, the piecemaker qubit (initialized in the state $|+\rangle$) is fused with one of the existing entangled states. # Noise The memories residing the nodes' `Register`s suffer from depolarizing noise. diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index b5afa69d..6058d622 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -11,7 +11,7 @@ mem_depolar_prob = 0.1 link_success_prob = 0.5 results_per_client = DataFrame[] -for nclients in 2:9 +for nclients in 2:2 # Prepare simulation data storage distribution_times = Float64[] fidelities = Float64[] diff --git a/examples/piecemakerswitch/test_fusioncircuit.jl b/examples/piecemakerswitch/test_fusioncircuit.jl new file mode 100644 index 00000000..d0eb098c --- /dev/null +++ b/examples/piecemakerswitch/test_fusioncircuit.jl @@ -0,0 +1,15 @@ +using QuantumSavory +using QuantumSavory.CircuitZoo + +a = Register(1) +b = Register(2) +bell = (Z₁⊗Z₁+Z₂⊗Z₂)/√2 +initialize!(a[1], X1) # Initialize `a[1]` in |+⟩ state +initialize!((b[1], b[2]), bell) # Initialize `b` with a bell pair + +correction = EntanglementFusion()(a[1], b[1]) +isassigned(b[1])==false # the target qubit is traced out +if correction==2 apply!(b[2], X) end # apply correction if needed + +# Now bell pair is fused into a +real(observable((a[1], b[2]), projector(bell))) \ No newline at end of file diff --git a/src/CircuitZoo/CircuitZoo.jl b/src/CircuitZoo/CircuitZoo.jl index 71eabea9..3e1e40d4 100644 --- a/src/CircuitZoo/CircuitZoo.jl +++ b/src/CircuitZoo/CircuitZoo.jl @@ -46,12 +46,44 @@ end inputqubits(::LocalEntanglementSwap) = 2 +""" $TYPEDEF + +## Fields: + +$FIELDS + +A circuit that combines two multipartide entangled states (e.g., GHZ states) into one, up to some Pauli correction. +The circuit applies a CNOT gate, measures the target qubit in the Z basis and traces out the latter (removed from the register). +The measurement result (1 for |0⟩ and 2 for |1⟩) is returned by the circuit. +By measuring and discarding the target qubit, the entanglement is effectively transferred to the control qubit. + +This circuit is useful in protocols where two multipartide entangled states are combined into one, e.g., when generating graph states. + +```jldoctest +julia> a = Register(1) + b = Register(2) + bell = (Z₁⊗Z₁+Z₂⊗Z₂)/√2 + initialize!(a[1], X1) # Initialize `a[1]` in |+⟩ state. + initialize!((b[1], b[2]), bell) # Initialize `b` with a bell pair. + +julia> correction = EntanglementFusion()(a[1], b[1]) # Apply fusion and receive measurement outcome. + +julia> isassigned(b[1]) # The `b[1]` qubit has been traced out. +false + +julia> if correction==2 apply!(b[2], X) end # Apply correction. + +julia> real(observable((a[1], b[2]), projector(bell))) # Now bell pair is fused into a. +1.0 +``` + +""" struct EntanglementFusion <: AbstractCircuit end -function (::EntanglementFusion)(localq, piecemaker) - apply!((piecemaker, localq), CNOT) - zmeas = project_traceout!(localq, σᶻ) +function (::EntanglementFusion)(control, target) + apply!((control, target), CNOT) + zmeas = project_traceout!(target, σᶻ) zmeas end diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 02f717da..c60fb19d 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -618,7 +618,7 @@ end @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" fuscircuit = EntanglementFusion() - zmeas = fuscircuit(q, piecemaker) + zmeas = fuscircuit(piecemaker, q) #uptotime!((q, piecemaker), now(prot.sim)) @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" # send from here to client node From 3a406439ca72592779602a5da4bbdefcbc5e4c3c Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 3 Dec 2024 19:14:47 +0100 Subject: [PATCH 19/38] extended function apply_noninstant! to work with depolarizing noise (multiple lindblad operators); added kraus-vs-lindblad evolution test to --- src/CircuitZoo/CircuitZoo.jl | 1 - src/backends/quantumoptics/noninstant.jl | 21 ++++++++++++++----- test/test_noninstant_and_backgrounds_qubit.jl | 8 +++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/CircuitZoo/CircuitZoo.jl b/src/CircuitZoo/CircuitZoo.jl index 3e1e40d4..54784424 100644 --- a/src/CircuitZoo/CircuitZoo.jl +++ b/src/CircuitZoo/CircuitZoo.jl @@ -76,7 +76,6 @@ julia> if correction==2 apply!(b[2], X) end # Apply correction. julia> real(observable((a[1], b[2]), projector(bell))) # Now bell pair is fused into a. 1.0 ``` - """ struct EntanglementFusion <: AbstractCircuit end diff --git a/src/backends/quantumoptics/noninstant.jl b/src/backends/quantumoptics/noninstant.jl index 0dcc997b..7a047674 100644 --- a/src/backends/quantumoptics/noninstant.jl +++ b/src/backends/quantumoptics/noninstant.jl @@ -3,14 +3,25 @@ export lindbladop function apply_noninstant!(state::Operator, state_indices::Vector{Int}, operation::ConstantHamiltonianEvolution, backgrounds) Δt = operation.duration base = basis(state) - e = isa(base,CompositeBasis) - lindbladians = [e ? embed(base,[i],lindbladop(bg)) : lindbladop(bg) for (i,bg) in zip(state_indices,backgrounds) if !isnothing(bg)] + e = isa(base, CompositeBasis) + lindbladians = [] + for (i, bg) in zip(state_indices, backgrounds) + if !isnothing(bg) + ops = lindbladop(bg, base) + # Ensure ops is always a list + ops = typeof(ops) <: AbstractArray ? ops : [ops] + # Embed if necessary + ops = e ? [embed(base, [i], op) for op in ops] : ops + append!(lindbladians, ops) + end + end ham = express(operation.hamiltonian, QOR) - ham = e ? embed(base,state_indices,ham) : ham - _, sol = timeevolution.master([0,Δt], state, ham, lindbladians) + ham = e ? embed(base, state_indices, ham) : ham + _, sol = timeevolution.master([0, Δt], state, ham, lindbladians) sol[end] end + function apply_noninstant!(state::Ket, state_indices::Vector{Int}, operation::ConstantHamiltonianEvolution, backgrounds) apply_noninstant!(dm(state), state_indices, operation, backgrounds) end @@ -32,7 +43,7 @@ function lindbladop(T2::T2Dephasing) # TODO pay attention to the √2 necessary end function lindbladop(D::Depolarization) - # TODO + 1/√D.τ .* (0.5 * [_x, _y, _z]) end function lindbladop(P::PauliNoise) diff --git a/test/test_noninstant_and_backgrounds_qubit.jl b/test/test_noninstant_and_backgrounds_qubit.jl index 50bc1beb..d87ebe09 100644 --- a/test/test_noninstant_and_backgrounds_qubit.jl +++ b/test/test_noninstant_and_backgrounds_qubit.jl @@ -54,7 +54,7 @@ kraus_lindblad_test(T2Dephasing(1.0),Z1) kraus_lindblad_test(T2Dephasing(1.0),Z2) kraus_lindblad_test(T2Dephasing(1.0),X1) kraus_lindblad_test(T2Dephasing(1.0),X2) -# kraus_lindblad_test(Depolarization(1.0), Z1) -# kraus_lindblad_test(Depolarization(1.0),Z2) -# kraus_lindblad_test(Depolarization(1.0),X1) -# kraus_lindblad_test(Depolarization(1.0),X2) +kraus_lindblad_test(Depolarization(1.), Z1) +kraus_lindblad_test(Depolarization(1.),Z2) +kraus_lindblad_test(Depolarization(1.),X1) +kraus_lindblad_test(Depolarization(1.),X2) From 9a12dd62bba882aee2ab1a3f88ed59a6c9666109 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 4 Dec 2024 13:00:41 +0100 Subject: [PATCH 20/38] updated readme --- examples/piecemakerswitch/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 317f11ea..a92426fd 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -1,14 +1,16 @@ # System Overview -A central switch node connects to $n$ clients. The switch possesses $m = n + 1$ qubit slots, while each client has a single qubit slot. +The goal is to share a GHZ state among multiple users. To do so, the clients connect to a central switch node, which then produces the GHZ state for them. + +In this setup, a number of clients connect to a central hub, which we call the switch node. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. This makes up for $n$ memory qubits on the switch node. However, the switch contains an additional qubit, $n+1$ which we call the 'piecemaker' slot - a qubit in the $|+\rangle$ state, which is needed in the GHZ generation process. # Entanglement Initiation -At each clock tick, the switch initiates entanglement attempts with each of the $n$ clients, resulting in $n$ entanglement processes per cycle. Successful entanglement links are then merged into a GHZ (Greenberger–Horne–Zeilinger) state using an additional "piecemaker" qubit located in the $(n + 1)$th slot of the switch node. This fusion process is assumed to occur instantaneously. Once all clients went through the fusion operation, the piecemaker qubit is measured out. This completes the fusing process and all nodes are sharing an n-GHZ state. +At each clock tick, the switch initiates entanglement attempts with each of the $n$ clients. So we have $n$ entanglement processes running in parallel per cycle. Successful entanglement links are immediately fused with the piecemaker qubit. Once all clients went through this fusion operation, we measure the piecemaker qubit. The latter projects the state back to the clients, resulting in the desired shared GHZ state. # Fusion Operation -The fusion operation consists of applying a `CNOT` gate followed by a measurement in the computational basis. This procedure allows the merging of two GHZ states into a single GHZ state, modulo any required Pauli corrections. We iterate over all existing entangled states with the switch node: in each iteration, the piecemaker qubit (initialized in the state $|+\rangle$) is fused with one of the existing entangled states. +The fusion operation is performed on the switch node. Let's take a client who just managed to generate a bipartide entangled state between its storage qubit and the associated qubit at the switch. The switch then executes a `CNOT` on the a client's qubit and the piecemaker. Then the switch measures the client qubit in the computational basis and sends the outcome to the client where the correction gate is applied. This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. # Noise -The memories residing the nodes' `Register`s suffer from depolarizing noise. +The memories residing in the nodes' `Register`s suffer from depolarizing noise. ### Protocol flow From 751c5b13f8e9522c03c303fd92ad12bb26dd5562 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Wed, 4 Dec 2024 13:03:37 +0100 Subject: [PATCH 21/38] updated readme --- examples/piecemakerswitch/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index a92426fd..2275f285 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -7,7 +7,7 @@ In this setup, a number of clients connect to a central hub, which we call the s At each clock tick, the switch initiates entanglement attempts with each of the $n$ clients. So we have $n$ entanglement processes running in parallel per cycle. Successful entanglement links are immediately fused with the piecemaker qubit. Once all clients went through this fusion operation, we measure the piecemaker qubit. The latter projects the state back to the clients, resulting in the desired shared GHZ state. # Fusion Operation -The fusion operation is performed on the switch node. Let's take a client who just managed to generate a bipartide entangled state between its storage qubit and the associated qubit at the switch. The switch then executes a `CNOT` on the a client's qubit and the piecemaker. Then the switch measures the client qubit in the computational basis and sends the outcome to the client where the correction gate is applied. This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. +The fusion operation is performed on the switch node. Let's take a client who just managed to generate a bipartide entangled state with its associated qubit at the switch. The switch then immediately executes a `CNOT` gate on the client's qubit and the piecemaker qubit. Next, the switch measures the client qubit in the computational basis and sends the outcome to the client (in order to apply the necessary Pauli correction). This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. # Noise The memories residing in the nodes' `Register`s suffer from depolarizing noise. From 83276fee3ce654eae92011d7b51c430d12818ae3 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 14 Jul 2025 20:30:20 -0400 Subject: [PATCH 22/38] reverted to whats on master --- examples/piecemakerswitch/setup.jl | 2 +- src/backends/quantumoptics/noninstant.jl | 16 +++------------- src/backends/quantumoptics/uptotime.jl | 6 ------ 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 0bfe0d5d..9975af79 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -9,7 +9,7 @@ using CSV using Profile using NetworkLayout -@resumable function init_state(sim, net, nclients, delay) +@resumable function init_state(sim, net, nclients::Int, delay::Real) @yield timeout(sim, delay) initialize!(net[1][nclients+1], X1; time=now(sim)) end diff --git a/src/backends/quantumoptics/noninstant.jl b/src/backends/quantumoptics/noninstant.jl index 37f78211..20fbbf2a 100644 --- a/src/backends/quantumoptics/noninstant.jl +++ b/src/backends/quantumoptics/noninstant.jl @@ -1,20 +1,10 @@ function apply_noninstant!(state::Operator, state_indices::Vector{Int}, operation::ConstantHamiltonianEvolution, backgrounds) Δt = operation.duration base = basis(state) - e = isa(base, CompositeBasis) - lindbladians = [] - for (i, bg) in zip(state_indices, backgrounds) - if !isnothing(bg) - ops = lindbladop(bg, base) - # Ensure ops is always a list - ops = typeof(ops) <: AbstractArray ? ops : [ops] - # Embed if necessary - ops = e ? [embed(base, [i], op) for op in ops] : ops - append!(lindbladians, ops) - end - end + e = isa(base,CompositeBasis) + lindbladians = [e ? embed(base,[i],lindbladop(bg)) : lindbladop(bg) for (i,bg) in zip(state_indices,backgrounds) if !isnothing(bg)] ham = express(operation.hamiltonian, QOR) - ham = e ? embed(base, state_indices, ham) : ham + ham = e ? embed(base,state_indices,ham) : ham _, sol = timeevolution.master([0, Δt], state, ham, lindbladians) sol[end] end diff --git a/src/backends/quantumoptics/uptotime.jl b/src/backends/quantumoptics/uptotime.jl index 3ecb7cf3..77e6fd99 100644 --- a/src/backends/quantumoptics/uptotime.jl +++ b/src/backends/quantumoptics/uptotime.jl @@ -23,12 +23,6 @@ function uptotime!(state::Operator, idx::Int, background, Δt) mul!(tmpr,tmpl,k',1,0) nstate.data .+= tmpr.data end - # Suggestion for alternative implementation: - # for k in Ks - # k = e ? embed(b, [idx], k) : k - # nstate .+= k * state * adjoint(k) - # end - end @assert abs(tr(nstate)) ≈ 1. # TODO maybe put under a debug flag nstate end From c032be4c6a62f74a05c3655f5d83b7f5772d2084 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 14 Jul 2025 20:31:23 -0400 Subject: [PATCH 23/38] fixed open loop --- src/backends/quantumoptics/uptotime.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backends/quantumoptics/uptotime.jl b/src/backends/quantumoptics/uptotime.jl index 77e6fd99..a433acad 100644 --- a/src/backends/quantumoptics/uptotime.jl +++ b/src/backends/quantumoptics/uptotime.jl @@ -23,6 +23,7 @@ function uptotime!(state::Operator, idx::Int, background, Δt) mul!(tmpr,tmpl,k',1,0) nstate.data .+= tmpr.data end + end @assert abs(tr(nstate)) ≈ 1. # TODO maybe put under a debug flag nstate end From b81b390831d2b0f71ff57def6f948623c3cf3237 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 14 Jul 2025 20:40:09 -0400 Subject: [PATCH 24/38] updated ProtocolZoo to align with original source --- src/ProtocolZoo/ProtocolZoo.jl | 32 +++--------------------- src/backends/quantumoptics/noninstant.jl | 3 +-- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index f7a1c63a..e9c7b9c0 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -3,12 +3,12 @@ module ProtocolZoo using QuantumSavory import QuantumSavory: get_time_tracker, Tag, isolderthan, onchange_tag using QuantumSavory: Wildcard -using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap, EntanglementFusion +using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap using DocStringExtensions using Distributions: Geometric -using ConcurrentSim: Simulation, @yield, timeout, @process, now, StopSimulation +using ConcurrentSim: Simulation, @yield, timeout, @process, now import ConcurrentSim: Process import ResumableFunctions using ResumableFunctions: @resumable @@ -16,9 +16,9 @@ import SumTypes export # protocols - EntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, FusionConsumer, CutoffProt, + EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, # tags - EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, + EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, # from Switches SimpleSwitchDiscreteProt, SwitchRequest, # from QTCP @@ -50,25 +50,6 @@ end Base.show(io::IO, tag::EntanglementCounterpart) = print(io, "Entangled to $(tag.remote_node).$(tag.remote_slot)") Tag(tag::EntanglementCounterpart) = Tag(EntanglementCounterpart, tag.remote_node, tag.remote_slot) - -""" -$TYPEDEF - -Indicates the current entanglement status with a remote node's slot. Added when a new qubit is fused into the multipartide state through [`FusionProt`](@ref). -The [`EntanglementTracker`](@ref) receives an [`EntanglementUpdate`] message: a tag pointing to a remote node slot it has performed fusion with. For example, in the `piecemakerswitch` setup the piecemaker slot in the central node is taged with all the client nodes it is fused with. - -$TYPEDFIELDS -""" -@kwdef struct FusionCounterpart - "the id of the remote node to which we are entangled" - remote_node::Int - "the slot in the remote node containing the qubit we are entangled to" - remote_slot::Int -end -Base.show(io::IO, tag::FusionCounterpart) = print(io, "Fused with $(tag.remote_node).$(tag.remote_slot)") -Tag(tag::FusionCounterpart) = Tag(FusionCounterpart, tag.remote_node, tag.remote_slot) - - """ $TYPEDEF @@ -187,10 +168,6 @@ $TYPEDFIELDS nodeA::Int """the vertex index of node B""" nodeB::Int - """the slot index of node A""" - slotA::Union{Int,Wildcard} = ❓ - """the slot index of node B""" - slotB::Union{Int,Wildcard} = ❓ """the state being generated (supports symbolic, numeric, noisy, and pure)""" pairstate = StabilizerState("ZZ XX") """success probability of one attempt of entanglement generation""" @@ -266,7 +243,6 @@ end rand(Geometric(prot.success_prob))+1 end if prot.attempts == -1 || prot.attempts >= attempts - @yield timeout(prot.sim, attempts * prot.attempt_time) initialize!((a,b), prot.pairstate; time=now(prot.sim)) @yield timeout(prot.sim, prot.local_busy_time_post) diff --git a/src/backends/quantumoptics/noninstant.jl b/src/backends/quantumoptics/noninstant.jl index 20fbbf2a..a936f754 100644 --- a/src/backends/quantumoptics/noninstant.jl +++ b/src/backends/quantumoptics/noninstant.jl @@ -5,11 +5,10 @@ function apply_noninstant!(state::Operator, state_indices::Vector{Int}, operatio lindbladians = [e ? embed(base,[i],lindbladop(bg)) : lindbladop(bg) for (i,bg) in zip(state_indices,backgrounds) if !isnothing(bg)] ham = express(operation.hamiltonian, QOR) ham = e ? embed(base,state_indices,ham) : ham - _, sol = timeevolution.master([0, Δt], state, ham, lindbladians) + _, sol = timeevolution.master([0,Δt], state, ham, lindbladians) sol[end] end - function apply_noninstant!(state::Ket, state_indices::Vector{Int}, operation::ConstantHamiltonianEvolution, backgrounds) apply_noninstant!(dm(state), state_indices, operation, backgrounds) end From 87a58166490481b992cbe05568276b7e47e95fd0 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 14 Jul 2025 20:43:12 -0400 Subject: [PATCH 25/38] updated switches.jl to align with original source --- examples/piecemakerswitch/setup.jl | 23 +++++++++++++++++++++++ src/ProtocolZoo/ProtocolZoo.jl | 9 +++------ src/ProtocolZoo/switches.jl | 22 ---------------------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 9975af79..ca074723 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -9,6 +9,29 @@ using CSV using Profile using NetworkLayout + +# """ +# Run `queryall(switch, EntanglemetnCounterpart, ...)` +# to find out which clients the switch has successfully entangled with. +# Then returns returns a list of indices corresponding to the successful clients. +# """ + +# function _switch_successful_entanglements(prot, reverseclientindex) +# switch = prot.net[prot.switchnode] +# successes = queryall(switch, EntanglementCounterpart, in(prot.clientnodes), ❓) +# entangled_clients = [r.tag[2] for r in successes] # RegRef (qubit slot) +# if isempty(entangled_clients) +# @debug "Switch $(prot.switchnode) failed to entangle with any clients" +# return nothing +# end +# # get the maximum match for the actually connected nodes +# ne = length(entangled_clients) +# @debug "Switch $(prot.switchnode) successfully entangled with $ne clients" +# if ne < 1 return nothing end +# entangled_clients_revindex = [reverseclientindex[k] for k in entangled_clients] +# return entangled_clients_revindex +# end + @resumable function init_state(sim, net, nclients::Int, delay::Real) @yield timeout(sim, delay) initialize!(net[1][nclients+1], X1; time=now(sim)) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index e9c7b9c0..c2f0d36d 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -316,17 +316,15 @@ end counterpart = querydelete!(localslot, EntanglementCounterpart, pastremotenode, pastremoteslotid) unlock(localslot) if !isnothing(counterpart) - time_before_lock = now(prot.sim) + # time_before_lock = now(prot.sim) @yield lock(localslot) - time_after_lock = now(prot.sim) - time_before_lock != time_after_lock && @debug "EntanglementTracker @$(prot.node): Needed Δt=$(time_after_lock-time_before_lock) to get a lock" + # time_after_lock = now(prot.sim) + # time_before_lock != time_after_lock && @debug "EntanglementTracker @$(prot.node): Needed Δt=$(time_after_lock-time_before_lock) to get a lock" if !isassigned(localslot) unlock(localslot) error("There was an error in the entanglement tracking protocol `EntanglementTracker`. We were attempting to forward a classical message from a node that performed a swap to the remote entangled node. However, on reception of that message it was found that the remote node has lost track of its part of the entangled state although it still keeps a `Tag` as a record of it being present.") # TODO make it configurable whether an error is thrown and plug it into the logging module end - @debug "EntanglementTracker @$(prot.node): updategate = $(updategate)" if !isnothing(updategate) # EntanglementUpdate - @debug "Entanglement updated for $(prot.node).$(localslot.idx) 2" # Pauli frame correction gate if correction==2 apply!(localslot, updategate) @@ -336,7 +334,6 @@ end tag!(localslot, EntanglementCounterpart, newremotenode, newremoteslotid) end else # EntanglementDelete - @debug "Entanglement deleted" traceout!(localslot) end unlock(localslot) diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index 82faae7f..31a043ee 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -284,28 +284,6 @@ function _switch_entangler(prot, assignment) end end -""" -Run `queryall(switch, EntanglemetnCounterpart, ...)` -to find out which clients the switch has successfully entangled with. -Then returns returns a list of indices corresponding to the successful clients. -""" - -function _switch_successful_entanglements(prot, reverseclientindex) - switch = prot.net[prot.switchnode] - successes = queryall(switch, EntanglementCounterpart, in(prot.clientnodes), ❓) - entangled_clients = [r.tag[2] for r in successes] # RegRef (qubit slot) - if isempty(entangled_clients) - @debug "Switch $(prot.switchnode) failed to entangle with any clients" - return nothing - end - # get the maximum match for the actually connected nodes - ne = length(entangled_clients) - @debug "Switch $(prot.switchnode) successfully entangled with $ne clients" - if ne < 1 return nothing end - entangled_clients_revindex = [reverseclientindex[k] for k in entangled_clients] - return entangled_clients_revindex -end - """ Run `queryall(switch, EntanglemetnCounterpart, ...)` to find out which clients the switch has successfully entangled with. From 47e61344e4882e2b8c7db63cb63aac1908b238c3 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 14 Jul 2025 20:46:24 -0400 Subject: [PATCH 26/38] updated CircuitZoo to align with original source --- examples/piecemakerswitch/setup.jl | 43 +++++++++++++++++++++++++++++ src/CircuitZoo/CircuitZoo.jl | 44 +----------------------------- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index ca074723..a8abdd2e 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -10,6 +10,49 @@ using Profile using NetworkLayout +# """ $TYPEDEF + +# ## Fields: + +# $FIELDS + +# A circuit that combines two multipartide entangled states (e.g., GHZ states) into one, up to some Pauli correction. +# The circuit applies a CNOT gate, measures the target qubit in the Z basis and traces out the latter (removed from the register). +# The measurement result (1 for |0⟩ and 2 for |1⟩) is returned by the circuit. +# By measuring and discarding the target qubit, the entanglement is effectively transferred to the control qubit. + +# This circuit is useful in protocols where two multipartide entangled states are combined into one, e.g., when generating graph states. + +# ```jldoctest +# julia> a = Register(1) +# b = Register(2) +# bell = (Z₁⊗Z₁+Z₂⊗Z₂)/√2 +# initialize!(a[1], X1) # Initialize `a[1]` in |+⟩ state. +# initialize!((b[1], b[2]), bell) # Initialize `b` with a bell pair. + +# julia> correction = EntanglementFusion()(a[1], b[1]) # Apply fusion and receive measurement outcome. + +# julia> isassigned(b[1]) # The `b[1]` qubit has been traced out. +# false + +# julia> if correction==2 apply!(b[2], X) end # Apply correction. + +# julia> real(observable((a[1], b[2]), projector(bell))) # Now bell pair is fused into a. +# 1.0 +# ``` +# """ +# struct EntanglementFusion <: AbstractCircuit +# end + +# function (::EntanglementFusion)(control, target) +# apply!((control, target), CNOT) +# zmeas = project_traceout!(target, σᶻ) +# zmeas +# end + +# inputqubits(::EntanglementFusion) = 2 + + # """ # Run `queryall(switch, EntanglemetnCounterpart, ...)` # to find out which clients the switch has successfully entangled with. diff --git a/src/CircuitZoo/CircuitZoo.jl b/src/CircuitZoo/CircuitZoo.jl index 54784424..f0a3cd7a 100644 --- a/src/CircuitZoo/CircuitZoo.jl +++ b/src/CircuitZoo/CircuitZoo.jl @@ -3,7 +3,7 @@ module CircuitZoo using QuantumSavory using DocStringExtensions -export EntanglementFusion, EntanglementSwap, LocalEntanglementSwap, +export EntanglementSwap, LocalEntanglementSwap, Purify2to1, Purify2to1Node, Purify3to1, Purify3to1Node, PurifyStringent, PurifyStringentNode, PurifyExpedient, PurifyExpedientNode, SDDecode, SDEncode @@ -46,48 +46,6 @@ end inputqubits(::LocalEntanglementSwap) = 2 -""" $TYPEDEF - -## Fields: - -$FIELDS - -A circuit that combines two multipartide entangled states (e.g., GHZ states) into one, up to some Pauli correction. -The circuit applies a CNOT gate, measures the target qubit in the Z basis and traces out the latter (removed from the register). -The measurement result (1 for |0⟩ and 2 for |1⟩) is returned by the circuit. -By measuring and discarding the target qubit, the entanglement is effectively transferred to the control qubit. - -This circuit is useful in protocols where two multipartide entangled states are combined into one, e.g., when generating graph states. - -```jldoctest -julia> a = Register(1) - b = Register(2) - bell = (Z₁⊗Z₁+Z₂⊗Z₂)/√2 - initialize!(a[1], X1) # Initialize `a[1]` in |+⟩ state. - initialize!((b[1], b[2]), bell) # Initialize `b` with a bell pair. - -julia> correction = EntanglementFusion()(a[1], b[1]) # Apply fusion and receive measurement outcome. - -julia> isassigned(b[1]) # The `b[1]` qubit has been traced out. -false - -julia> if correction==2 apply!(b[2], X) end # Apply correction. - -julia> real(observable((a[1], b[2]), projector(bell))) # Now bell pair is fused into a. -1.0 -``` -""" -struct EntanglementFusion <: AbstractCircuit -end - -function (::EntanglementFusion)(control, target) - apply!((control, target), CNOT) - zmeas = project_traceout!(target, σᶻ) - zmeas -end - -inputqubits(::EntanglementFusion) = 2 - """ $TYPEDEF From 1847cccf786f78cc5455886fedd8b529f78d90fa Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 14 Jul 2025 20:47:13 -0400 Subject: [PATCH 27/38] fix small typo --- src/CircuitZoo/CircuitZoo.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CircuitZoo/CircuitZoo.jl b/src/CircuitZoo/CircuitZoo.jl index f0a3cd7a..9966b4bb 100644 --- a/src/CircuitZoo/CircuitZoo.jl +++ b/src/CircuitZoo/CircuitZoo.jl @@ -46,6 +46,7 @@ end inputqubits(::LocalEntanglementSwap) = 2 + """ $TYPEDEF From 38d0784b454e5b1f34716858ee19a6575519a9fa Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Fri, 8 Aug 2025 14:46:43 +0200 Subject: [PATCH 28/38] simple piecemaker updated --- examples/piecemakerswitch/Project.toml | 1 + examples/piecemakerswitch/setup.jl | 264 ++++++++++++++----------- 2 files changed, 148 insertions(+), 117 deletions(-) diff --git a/examples/piecemakerswitch/Project.toml b/examples/piecemakerswitch/Project.toml index 28cbcdee..f4569970 100644 --- a/examples/piecemakerswitch/Project.toml +++ b/examples/piecemakerswitch/Project.toml @@ -5,6 +5,7 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" +QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index a8abdd2e..beab20d1 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -3,139 +3,169 @@ using QuantumSavory.ProtocolZoo using Graphs using ConcurrentSim using ResumableFunctions -using Distributions -using DataFrames -using CSV -using Profile using NetworkLayout +using DataFrames +using Random +using QuantumClifford: ghz +const ghzs = [ghz(n) for n in 1:7] # make const in order to not build new every time -# """ $TYPEDEF - -# ## Fields: - -# $FIELDS - -# A circuit that combines two multipartide entangled states (e.g., GHZ states) into one, up to some Pauli correction. -# The circuit applies a CNOT gate, measures the target qubit in the Z basis and traces out the latter (removed from the register). -# The measurement result (1 for |0⟩ and 2 for |1⟩) is returned by the circuit. -# By measuring and discarding the target qubit, the entanglement is effectively transferred to the control qubit. - -# This circuit is useful in protocols where two multipartide entangled states are combined into one, e.g., when generating graph states. - -# ```jldoctest -# julia> a = Register(1) -# b = Register(2) -# bell = (Z₁⊗Z₁+Z₂⊗Z₂)/√2 -# initialize!(a[1], X1) # Initialize `a[1]` in |+⟩ state. -# initialize!((b[1], b[2]), bell) # Initialize `b` with a bell pair. - -# julia> correction = EntanglementFusion()(a[1], b[1]) # Apply fusion and receive measurement outcome. - -# julia> isassigned(b[1]) # The `b[1]` qubit has been traced out. -# false - -# julia> if correction==2 apply!(b[2], X) end # Apply correction. - -# julia> real(observable((a[1], b[2]), projector(bell))) # Now bell pair is fused into a. -# 1.0 -# ``` -# """ -# struct EntanglementFusion <: AbstractCircuit -# end - -# function (::EntanglementFusion)(control, target) -# apply!((control, target), CNOT) -# zmeas = project_traceout!(target, σᶻ) -# zmeas -# end - -# inputqubits(::EntanglementFusion) = 2 - - -# """ -# Run `queryall(switch, EntanglemetnCounterpart, ...)` -# to find out which clients the switch has successfully entangled with. -# Then returns returns a list of indices corresponding to the successful clients. -# """ - -# function _switch_successful_entanglements(prot, reverseclientindex) -# switch = prot.net[prot.switchnode] -# successes = queryall(switch, EntanglementCounterpart, in(prot.clientnodes), ❓) -# entangled_clients = [r.tag[2] for r in successes] # RegRef (qubit slot) -# if isempty(entangled_clients) -# @debug "Switch $(prot.switchnode) failed to entangle with any clients" -# return nothing -# end -# # get the maximum match for the actually connected nodes -# ne = length(entangled_clients) -# @debug "Switch $(prot.switchnode) successfully entangled with $ne clients" -# if ne < 1 return nothing end -# entangled_clients_revindex = [reverseclientindex[k] for k in entangled_clients] -# return entangled_clients_revindex -# end - -@resumable function init_state(sim, net, nclients::Int, delay::Real) - @yield timeout(sim, delay) - initialize!(net[1][nclients+1], X1; time=now(sim)) +function fusion(piecemaker_slot, client_slot) + apply!((piecemaker_slot, client_slot), CNOT) + res = project_traceout!(client_slot, σᶻ) + return res end -@resumable function entangle_and_fuse(sim, net, client, link_success_prob) - - # Set up the entanglement trackers at each client - tracker = EntanglementTracker(sim, net, client) - @process tracker() - - # Set up the entangler and fuser protocols at each client - entangler = EntanglerProt( - sim=sim, net=net, nodeA=1, slotA=client-1, nodeB=client, - success_prob=link_success_prob, rounds=1, attempts=-1, attempt_time=1.0 - ) - @yield @process entangler() - - fuser = FusionProt( - sim=sim, net=net, node=1, - nodeC=client, - rounds=1 - ) - @yield @process fuser() +""" + EntanglementCorrector(sim, net, node) + +A resumable protocol process that listens for entanglement correction instructions at a given network node. + +Upon receiving a message tagged `:updateX`, the protocol checks the received value and, if the value equals `2`, applies an X correction to the node's qubit. The process locks the qubit during the correction to ensure thread safety, logs the correction event, and then terminates. +""" +@resumable function EntanglementCorrector(sim, net, node) + while true + @yield onchange_tag(net[node][1]) + msg = querydelete!(net[node][1], :updateX, ❓) + if !isnothing(msg) + value = msg[3][2] + @yield lock(net[node][1]) + @info "X received at node $(node), with value $(value)" + value == 2 && apply!(net[node][1], X) + unlock(net[node][1]) + break + end + end end - -@resumable function run_protocols(sim, net, nclients, link_success_prob) - # Run entangler and fusion for each client and wait for all to finish - procs_succeeded = [] - for k in 2:nclients+1 - proc_succeeded = @process entangle_and_fuse(sim, net, k, link_success_prob) - push!(procs_succeeded, proc_succeeded) +""" + Logger(sim, net, node, n, logging, start_of_round) + +A resumable protocol process that listens for entanglement measurement instructions at a given network node and logs the fidelity of the resulting state. + +Upon receiving a message tagged `:updateZ`, the protocol checks the received value and, if the value equals `2`, applies a Z correction to the node's qubit. The process then measures the fidelity of the current state against the ideal GHZ state, logs the result, and terminates. +""" +@resumable function Logger(sim, net, node, n, logging, start_of_round) + msg = querydelete!(net[node], :updateZ, ❓) + if isnothing(msg) + error("No message received at node $(node) with tag :updateZ.") + else + value = msg[3][2] + @info "Z received at node $(node), with value $(value)" + @yield lock(net[node][1]) + value == 2 && apply!(net[node][1], Z) + unlock(net[node][1]) + + # Measure the fidelity to the GHZ state + @yield reduce(&, [lock(q) for q in net[2]]) + obs = SProjector(StabilizerState(ghzs[n])) # GHZ state projector to measure + fidelity = real(observable([net[i+1][1] for i in 1:n], obs; time=now(sim))) + @info "Fidelity: $(fidelity)" + + # Log outcome + push!( + logging, + ( + now(sim)-start_of_round, fidelity + ) + ) end - @yield reduce(&, procs_succeeded) end -function prepare_simulation(nclients=2, mem_depolar_prob = 0.1, link_success_prob = 0.5) +@resumable function PiecemakerProt(sim, n, net, link_success_prob, logging, rounds) + + while rounds != 0 + @info "round $(rounds)" + start = now(sim) + + for i in 1:n # entangle each client with the switch + entangler = EntanglerProt( + sim=sim, net=net, nodeA=1, chooseA=i, nodeB=1+i, chooseB=1, + success_prob=link_success_prob, rounds=1, attempts=-1, attempt_time=1.0, + ) + @process entangler() + end + + for i in 1:n + @process EntanglementCorrector(sim, net, 1+i) # start entanglement correctors at each client + end + + while true + # Look for EntanglementCounterpart changed on switch + counter = 0 + while counter < n # until all clients are entangled + @yield onchange_tag(net[1]) + if counter == 0 # initialize piecemaker + # Initialize "piecemaker" qubit in |+> state when first qubit arrived s.t. if p=1 fidelity=1 + initialize!(net[1][n+1], X1, time=now(sim)) + end + + while true + counterpart = querydelete!(net[1], EntanglementCounterpart, ❓, ❓) + if !isnothing(counterpart) + slot, _, _ = counterpart + + # fuse the qubit with the piecemaker qubit + @yield lock(net[1][n+1]) & lock(net[1][slot.idx]) + res = fusion(net[1][n+1], net[1][slot.idx]) + unlock(net[1][n+1]) + unlock(net[1][slot.idx]) + tag!(net[1+slot.idx][1], Tag(:updateX, res)) # communicate change to client node + counter += 1 + @debug "Fused client $(slot.idx) with piecemaker qubit" + else + break + end + end + end + + @debug "All clients entangled, measuring piecemaker | time: $(now(sim)-start)" + @yield lock(net[1][n+1]) + res = project_traceout!(net[1][n+1], σˣ) + unlock(net[1][n+1]) + tag!(net[2][1], Tag(:updateZ, res)) # communicate change to client node + break + end + + @yield @process Logger(sim, net, 2, n, logging, start) # start logger + + foreach(q -> (traceout!(q); unlock(q)), net[1]) + foreach(q -> (traceout!(q); unlock(q)), [net[1+i][1] for i in 1:n]) + + rounds -= 1 + @debug "Round $(rounds) finished" + end - m = nclients+1 # memory slots in switch is equal to the number of clients + 1 slot for piecemaker qubit - r_depol = - log(1 - mem_depolar_prob) # depolarization rate - delay = 1 # initialize the piecemaker |+> after one time unit (in order to provide fidelity ==1 if success probability = 1) +end - # The graph of network connectivity. Index 1 corresponds to the switch. - graph = star_graph(nclients+1) +function prepare_sim(n::Int, states_representation::AbstractRepresentation, noise_model::Union{AbstractBackground, Nothing}, + link_success_prob::Float64, seed::Int, logging::DataFrame, rounds::Int) - switch_register = Register(m, Depolarization(1/r_depol)) # the first slot is reserved for the 'piecemaker' qubit used as fusion qubit - client_registers = [Register(1, Depolarization(1/r_depol)) for _ in 1:nclients] #Depolarization(1/r_depol) - net = RegisterNet(graph, [switch_register, client_registers...]) - sim = get_time_tracker(net) + # Set a random seed + Random.seed!(seed) - @process init_state(sim, net, nclients, delay) + switch = Register([Qubit() for _ in 1:(n+1)], [states_representation for _ in 1:(n+1)], [noise_model for _ in 1:(n+1)]) # storage qubits at the switch, first qubit is the "piecemaker" qubit + clients = [Register([Qubit()], [states_representation], [noise_model]) for _ in 1:n] # client qubits - # Run entangler and fusion for each client and wait for all to finish - @process run_protocols(sim, net, nclients, link_success_prob) - - # Set up the consumer to measure final entangled state - consumer = FusionConsumer(net, net[1][m]; period=0.001) - @process consumer() + graph = star_graph(n+1) + net = RegisterNet(graph, [switch, clients...]) + @info net + sim = get_time_tracker(net) - return sim, consumer + # Start the piecemaker protocol + @process PiecemakerProt(sim, n, net, link_success_prob, logging, rounds) + return sim end +mem_depolar_prob = 0.0 # memory depolarization probability +decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates +noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits +logging = DataFrame(Δt=[], fidelity=[]) + +sim = prepare_sim( + 5, QuantumOpticsRepr(), noise_model, 0.5, 42, logging, 10 +) +timed = @elapsed run(sim) +println("Simulation finished in $(timed) seconds") +@info logging \ No newline at end of file From 315a4ab2870ed6f21bc47993b40c418f6eb3a934 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 12 Aug 2025 17:19:48 +0200 Subject: [PATCH 29/38] added small vizualisation --- examples/piecemakerswitch/Project.toml | 3 + .../live_vizualisation_network.jl | 200 ++++++++++++++++++ examples/piecemakerswitch/setup.jl | 30 +-- examples/piecemakerswitch/static_viz.jl | 33 +++ 4 files changed, 244 insertions(+), 22 deletions(-) create mode 100644 examples/piecemakerswitch/live_vizualisation_network.jl create mode 100644 examples/piecemakerswitch/static_viz.jl diff --git a/examples/piecemakerswitch/Project.toml b/examples/piecemakerswitch/Project.toml index f4569970..012c05c2 100644 --- a/examples/piecemakerswitch/Project.toml +++ b/examples/piecemakerswitch/Project.toml @@ -1,8 +1,11 @@ [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" +GraphMakie = "1ecd5474-83a3-4783-bb4f-06765db800d2" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" diff --git a/examples/piecemakerswitch/live_vizualisation_network.jl b/examples/piecemakerswitch/live_vizualisation_network.jl new file mode 100644 index 00000000..e754eb6b --- /dev/null +++ b/examples/piecemakerswitch/live_vizualisation_network.jl @@ -0,0 +1,200 @@ +# run_with_live_plot_and_registernetplot.jl + +using QuantumSavory +using QuantumSavory.ProtocolZoo +using QuantumClifford: ghz +using Graphs +using ConcurrentSim +using ResumableFunctions +using DataFrames +using Random + +using GLMakie +GLMakie.activate!() # OpenGL window + +const fidelity_points = Observable(Point2f[]) + +const ghzs = [ghz(n) for n in 1:7] # precompute GHZ targets + +function fusion(piecemaker_slot, client_slot) + apply!((piecemaker_slot, client_slot), CNOT) + res = project_traceout!(client_slot, σᶻ) + return res +end + +@resumable function EntanglementCorrector(sim, net, node) + while true + @yield onchange_tag(net[node][1]) + msg = querydelete!(net[node][1], :updateX, ❓) + if !isnothing(msg) + value = msg[3][2] + @yield lock(net[node][1]) + @debug "X received at node $(node), with value $(value)" + value == 2 && apply!(net[node][1], X) + unlock(net[node][1]) + break + end + end +end + +@resumable function Logger(sim, net, node, n, logging, start_of_round, net_obs) + msg = querydelete!(net[node], :updateZ, ❓) + if isnothing(msg) + error("No message received at node $(node) with tag :updateZ.") + else + value = msg[3][2] + @debug "Z received at node $(node), with value $(value)" + @yield lock(net[node][1]) + value == 2 && apply!(net[node][1], Z) + unlock(net[node][1]) + + # Measure fidelity to GHZ + @yield reduce(&, [lock(q) for q in net[2]]) + obs_proj = SProjector(StabilizerState(ghzs[n])) + fidelity = real(observable([net[i+1][1] for i in 1:n], obs_proj; time = now(sim))) + t = now(sim) - start_of_round + @info "Fidelity: $(fidelity)" + push!(logging, (t, fidelity)) + + sleep(0.5) + notify(net_obs) # show post-measurement state + sleep(0.5) + + # live update + push!(fidelity_points[], Point2f(t, fidelity)) + notify(fidelity_points) + end +end + +# NOTE: we’ll pass the `obs` from registernetplot_axis into the protocol so we can call notify(obs) +@resumable function PiecemakerProt(sim, n, net, link_success_prob, logging, rounds, net_obs) + while rounds != 0 + @info "round $(rounds)" + start = now(sim) + + # entangle each client with its designated switch slot i + for i in 1:n + entangler = EntanglerProt( + sim = sim, net = net, nodeA = 1, chooseA = i, nodeB = 1 + i, chooseB = 1, + success_prob = link_success_prob, rounds = 1, attempts = -1, attempt_time = 1.0, + ) + @process entangler() + end + + for i in 1:n + @process EntanglementCorrector(sim, net, 1 + i) + end + + while true + counter = 0 + while counter < n + @yield onchange_tag(net[1]) + if counter == 0 + # Initialize piecemaker |+> (slot n+1 at the switch) + initialize!(net[1][n+1], X1, time = now(sim)) + #notify(net_obs) # refresh view + end + + while true + counterpart = querydelete!(net[1], EntanglementCounterpart, ❓, ❓) + if !isnothing(counterpart) + slot, _, _ = counterpart + i = slot.idx # client index (1..n) + + # At this point the link (switch slot i) <-> (client i) exists -> show it + notify(net_obs) + sleep(0.5) # slow down so the link appearance is visible + + @yield lock(net[1][n+1]) & lock(net[1][slot.idx]) + res = fusion(net[1][n+1], net[1][slot.idx]) + unlock(net[1][n+1]); unlock(net[1][slot.idx]) + + tag!(net[1 + slot.idx][1], Tag(:updateX, res)) + counter += 1 + @debug "Fused client $(slot.idx) with piecemaker qubit" + + # After fusion, the entanglement “moves” to include the piecemaker slot. + # The plot reflects current net state; just notify to refresh. + notify(net_obs) + sleep(0.5) + else + break + end + end + end + + @debug "All clients entangled, measuring piecemaker | time: $(now(sim)-start)" + @yield lock(net[1][n+1]) + res = project_traceout!(net[1][n+1], σˣ) + unlock(net[1][n+1]) + tag!(net[2][1], Tag(:updateZ, res)) + break + end + + @yield @process Logger(sim, net, 2, n, logging, start, net_obs) + + # cleanup qubits (plot will update on next round’s actions) + foreach(q -> (traceout!(q); unlock(q)), net[1]) + foreach(q -> (traceout!(q); unlock(q)), [net[1 + i][1] for i in 1:n]) + notify(net_obs) + + rounds -= 1 + @debug "Round $(rounds) finished" + end + # between rounds (real time pause so it’s watchable) + sleep(0.7) + fidelity_points[] = Point2f[] # clear points for next round + notify(fidelity_points) +end + +function prepare_sim(n::Int, repr::AbstractRepresentation, noise::Union{AbstractBackground,Nothing}, + p_link::Float64, seed::Int, logging::DataFrame, rounds::Int) + + Random.seed!(seed) + + switch = Register([Qubit() for _ in 1:(n+1)], [repr for _ in 1:(n+1)], [noise for _ in 1:(n+1)]) + clients = [Register([Qubit()], [repr], [noise]) for _ in 1:n] + + graph = star_graph(n + 1) + net = RegisterNet(graph, [switch, clients...]) + sim = get_time_tracker(net) + + # create a temporary figure slot to hold obs after we have net + fig = Figure(resolution = (1200, 520)) + ax_fid = Axis(fig[1, 1], xlabel="Δt (simulation time)", ylabel="Fidelity to GHZₙ", title="Fidelity (live)") + scatter!(ax_fid, fidelity_points, markersize=8); ylims!(ax_fid, 0, 1) + + # Now attach the network plot to net and capture its obs + _, ax_net, _, net_obs = registernetplot_axis(fig[1, 2], net) + + @process PiecemakerProt(sim, n, net, p_link, logging, rounds, net_obs) + + ax_net.title = "Network (live)" + display(fig) + return sim +end + +function main(; n = 5, link_success_prob = 0.5, rounds = 5, seed = 42) + # noise + mem_depolar_prob = 0.5 + decoherence_rate = -log(1 - mem_depolar_prob) + noise_model = Depolarization(1 / decoherence_rate) + + logging = DataFrame(Δt = Float64[], fidelity = Float64[]) + + sim = prepare_sim(n, QuantumOpticsRepr(), noise_model, link_success_prob, seed, logging, rounds) + + # Run in background so UI can repaint + simtask = @async begin + t = @elapsed run(sim) + @info "Simulation finished in $(t) seconds" + end + wait(simtask) + + if !Base.isinteractive() + @info "Press Enter to close the window…" + readline() + end +end + +main() \ No newline at end of file diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index beab20d1..0fce828a 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -60,15 +60,15 @@ Upon receiving a message tagged `:updateZ`, the protocol checks the received val @yield reduce(&, [lock(q) for q in net[2]]) obs = SProjector(StabilizerState(ghzs[n])) # GHZ state projector to measure fidelity = real(observable([net[i+1][1] for i in 1:n], obs; time=now(sim))) + t = now(sim) - start_of_round @info "Fidelity: $(fidelity)" - # Log outcome - push!( - logging, - ( - now(sim)-start_of_round, fidelity - ) - ) + push!(logging, (t, fidelity)) + + # update for visualization + pts = fidelity_points[] # current vector of Point2f + push!(pts, Point2f(t, fidelity)) + fidelity_points[] = pts # notify Makie plot end end @@ -149,23 +149,9 @@ function prepare_sim(n::Int, states_representation::AbstractRepresentation, nois graph = star_graph(n+1) net = RegisterNet(graph, [switch, clients...]) - @info net sim = get_time_tracker(net) # Start the piecemaker protocol @process PiecemakerProt(sim, n, net, link_success_prob, logging, rounds) return sim -end - -mem_depolar_prob = 0.0 # memory depolarization probability -decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates -noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits -logging = DataFrame(Δt=[], fidelity=[]) - -sim = prepare_sim( - 5, QuantumOpticsRepr(), noise_model, 0.5, 42, logging, 10 -) - -timed = @elapsed run(sim) -println("Simulation finished in $(timed) seconds") -@info logging \ No newline at end of file +end \ No newline at end of file diff --git a/examples/piecemakerswitch/static_viz.jl b/examples/piecemakerswitch/static_viz.jl new file mode 100644 index 00000000..8d177cd5 --- /dev/null +++ b/examples/piecemakerswitch/static_viz.jl @@ -0,0 +1,33 @@ +include("setup.jl") +using GLMakie +GLMakie.activate!(inline=false) +using DataFrames + +mem_depolar_prob = 0.1 # memory depolarization probability +decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates +noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits +logging = DataFrame(Δt=[], fidelity=[]) + +sim = prepare_sim( + 5, QuantumOpticsRepr(), noise_model, 0.5, 42, logging, 10 +) + +timed = @elapsed run(sim) +println("Simulation finished in $(timed) seconds") +@info logging + +function plot_fidelity(logging::DataFrame) + fig = Figure(resolution = (800, 450)) + ax = Axis(fig[1, 1], xlabel = "Δt (simulation time)", ylabel = "Fidelity to GHZₙ", + title = "Entanglement fidelity over time") + scatter!(ax, logging.Δt, logging.fidelity, markersize = 8) + ylims!(ax, 0, 1) + fig +end + +fig = plot_fidelity(logging) +display(fig) +wait() # keeps REPL open until the figure is closed + +# optional: save it +# save("examples/piecemakerswitch/fidelity.png", fig) \ No newline at end of file From 720fd3ecb5ee1e5269cb488bf31c29ddf0e7ffff Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 12 Aug 2025 17:32:35 +0200 Subject: [PATCH 30/38] typo in file name --- ...ive_vizualisation_network.jl => live_visualization_network.jl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/piecemakerswitch/{live_vizualisation_network.jl => live_visualization_network.jl} (100%) diff --git a/examples/piecemakerswitch/live_vizualisation_network.jl b/examples/piecemakerswitch/live_visualization_network.jl similarity index 100% rename from examples/piecemakerswitch/live_vizualisation_network.jl rename to examples/piecemakerswitch/live_visualization_network.jl From 1b4b4778f332e1b3ca44377dc7d42d6576a19b80 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 6 Oct 2025 11:01:00 +0200 Subject: [PATCH 31/38] deleted depr tests --- .../live_visualization_network.jl | 8 +---- examples/piecemakerswitch/setup.jl | 2 +- .../piecemakerswitch/test_fusioncircuit.jl | 15 ---------- examples/piecemakerswitch/test_initialize.jl | 30 ------------------- 4 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 examples/piecemakerswitch/test_fusioncircuit.jl delete mode 100644 examples/piecemakerswitch/test_initialize.jl diff --git a/examples/piecemakerswitch/live_visualization_network.jl b/examples/piecemakerswitch/live_visualization_network.jl index e754eb6b..86319617 100644 --- a/examples/piecemakerswitch/live_visualization_network.jl +++ b/examples/piecemakerswitch/live_visualization_network.jl @@ -1,5 +1,4 @@ -# run_with_live_plot_and_registernetplot.jl - +# Live visualization of the piecemaker switch protocol using QuantumSavory using QuantumSavory.ProtocolZoo using QuantumClifford: ghz @@ -190,11 +189,6 @@ function main(; n = 5, link_success_prob = 0.5, rounds = 5, seed = 42) @info "Simulation finished in $(t) seconds" end wait(simtask) - - if !Base.isinteractive() - @info "Press Enter to close the window…" - readline() - end end main() \ No newline at end of file diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 0fce828a..69e896f7 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -96,7 +96,7 @@ end while counter < n # until all clients are entangled @yield onchange_tag(net[1]) if counter == 0 # initialize piecemaker - # Initialize "piecemaker" qubit in |+> state when first qubit arrived s.t. if p=1 fidelity=1 + # Initialize "piecemaker" qubit in |+> state when first qubit arrived initialize!(net[1][n+1], X1, time=now(sim)) end diff --git a/examples/piecemakerswitch/test_fusioncircuit.jl b/examples/piecemakerswitch/test_fusioncircuit.jl deleted file mode 100644 index d0eb098c..00000000 --- a/examples/piecemakerswitch/test_fusioncircuit.jl +++ /dev/null @@ -1,15 +0,0 @@ -using QuantumSavory -using QuantumSavory.CircuitZoo - -a = Register(1) -b = Register(2) -bell = (Z₁⊗Z₁+Z₂⊗Z₂)/√2 -initialize!(a[1], X1) # Initialize `a[1]` in |+⟩ state -initialize!((b[1], b[2]), bell) # Initialize `b` with a bell pair - -correction = EntanglementFusion()(a[1], b[1]) -isassigned(b[1])==false # the target qubit is traced out -if correction==2 apply!(b[2], X) end # apply correction if needed - -# Now bell pair is fused into a -real(observable((a[1], b[2]), projector(bell))) \ No newline at end of file diff --git a/examples/piecemakerswitch/test_initialize.jl b/examples/piecemakerswitch/test_initialize.jl deleted file mode 100644 index a15a1ffe..00000000 --- a/examples/piecemakerswitch/test_initialize.jl +++ /dev/null @@ -1,30 +0,0 @@ -using QuantumSavory -using QuantumSavory.ProtocolZoo -using Graphs -using ConcurrentSim -using ResumableFunctions -using Distributions -using DataFrames -using CSV -using Profile -using NetworkLayout - - - -@resumable function init_state(sim) - @yield timeout(sim, 1.) - initialize!(slot[1], Z1; time=now(sim)) - res = observable(slot[1], projector(Z1); time=now(sim)) - @info res -end - -mem_depolar_prob = 0.5 -r_depol = - log(1 - mem_depolar_prob) -print(r_depol) -slot = Register(1, Depolarization(1/r_depol)) - -sim = get_time_tracker(slot) - -@process init_state(sim) -run(sim) - From 97caf9ec1d80e2969199abd7fc4d69823708a27d Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Thu, 9 Oct 2025 11:00:54 +0200 Subject: [PATCH 32/38] updated simple runs and added interactive html with wgl makie --- examples/piecemakerswitch/Project.toml | 2 + examples/piecemakerswitch/README.md | 12 +- .../live_visualization_network.jl | 194 ---------- .../live_visualization_network_interactive.jl | 356 ++++++++++++++++++ examples/piecemakerswitch/simple_run.jl | 1 - 5 files changed, 363 insertions(+), 202 deletions(-) delete mode 100644 examples/piecemakerswitch/live_visualization_network.jl create mode 100644 examples/piecemakerswitch/live_visualization_network_interactive.jl diff --git a/examples/piecemakerswitch/Project.toml b/examples/piecemakerswitch/Project.toml index 012c05c2..631c595a 100644 --- a/examples/piecemakerswitch/Project.toml +++ b/examples/piecemakerswitch/Project.toml @@ -1,4 +1,5 @@ [deps] +Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" @@ -13,3 +14,4 @@ QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008" diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 2275f285..40866878 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -1,16 +1,14 @@ # System Overview -The goal is to share a GHZ state among multiple users. To do so, the clients connect to a central switch node, which then produces the GHZ state for them. +The goal is to share a GHZ state among multiple users. To do so, the clients connect to a central switch node, which then serves the GHZ state to them. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. In addition the switch's register holds a dedicated 'piecemaker' slot - a qubit in the $|+\rangle$ state, which all successfull clients fuse their switch'side qubits with. -In this setup, a number of clients connect to a central hub, which we call the switch node. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. This makes up for $n$ memory qubits on the switch node. However, the switch contains an additional qubit, $n+1$ which we call the 'piecemaker' slot - a qubit in the $|+\rangle$ state, which is needed in the GHZ generation process. - -# Entanglement Initiation -At each clock tick, the switch initiates entanglement attempts with each of the $n$ clients. So we have $n$ entanglement processes running in parallel per cycle. Successful entanglement links are immediately fused with the piecemaker qubit. Once all clients went through this fusion operation, we measure the piecemaker qubit. The latter projects the state back to the clients, resulting in the desired shared GHZ state. +# Entanglement Distribution +In each time step, $n$ entanglement processes run in parallel. Upon completion of an entanglement link, it is fused with the piecemaker qubit. Once all clients went through this fusion operation, the piecemaker qubit is measured. This projects the state back to the clients, resulting in the desired shared GHZ state. # Fusion Operation -The fusion operation is performed on the switch node. Let's take a client who just managed to generate a bipartide entangled state with its associated qubit at the switch. The switch then immediately executes a `CNOT` gate on the client's qubit and the piecemaker qubit. Next, the switch measures the client qubit in the computational basis and sends the outcome to the client (in order to apply the necessary Pauli correction). This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. +The fusion operation is performed on the switch node. Let's take a client who just managed to generate a bipartide entangled state -- entangled link -- with its associated qubit at the switch side. The switch executes a `CNOT` gate on the client's qubit (target) and the piecemaker qubit (control). Next, the switch measures the client qubit in the computational basis and sends the outcome to the client (in order to apply the necessary Pauli correction). This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. # Noise -The memories residing in the nodes' `Register`s suffer from depolarizing noise. +The memories residing in the nodes' `Register`s suffer from depolarizing noise, see [`Depolarization`](https://github.com/QuantumSavory/QuantumSavory.jl/blob/2d40bb77b2abdebdd92a0d32830d97a9234d2fa0/src/backgrounds.jl#L18). ### Protocol flow diff --git a/examples/piecemakerswitch/live_visualization_network.jl b/examples/piecemakerswitch/live_visualization_network.jl deleted file mode 100644 index 86319617..00000000 --- a/examples/piecemakerswitch/live_visualization_network.jl +++ /dev/null @@ -1,194 +0,0 @@ -# Live visualization of the piecemaker switch protocol -using QuantumSavory -using QuantumSavory.ProtocolZoo -using QuantumClifford: ghz -using Graphs -using ConcurrentSim -using ResumableFunctions -using DataFrames -using Random - -using GLMakie -GLMakie.activate!() # OpenGL window - -const fidelity_points = Observable(Point2f[]) - -const ghzs = [ghz(n) for n in 1:7] # precompute GHZ targets - -function fusion(piecemaker_slot, client_slot) - apply!((piecemaker_slot, client_slot), CNOT) - res = project_traceout!(client_slot, σᶻ) - return res -end - -@resumable function EntanglementCorrector(sim, net, node) - while true - @yield onchange_tag(net[node][1]) - msg = querydelete!(net[node][1], :updateX, ❓) - if !isnothing(msg) - value = msg[3][2] - @yield lock(net[node][1]) - @debug "X received at node $(node), with value $(value)" - value == 2 && apply!(net[node][1], X) - unlock(net[node][1]) - break - end - end -end - -@resumable function Logger(sim, net, node, n, logging, start_of_round, net_obs) - msg = querydelete!(net[node], :updateZ, ❓) - if isnothing(msg) - error("No message received at node $(node) with tag :updateZ.") - else - value = msg[3][2] - @debug "Z received at node $(node), with value $(value)" - @yield lock(net[node][1]) - value == 2 && apply!(net[node][1], Z) - unlock(net[node][1]) - - # Measure fidelity to GHZ - @yield reduce(&, [lock(q) for q in net[2]]) - obs_proj = SProjector(StabilizerState(ghzs[n])) - fidelity = real(observable([net[i+1][1] for i in 1:n], obs_proj; time = now(sim))) - t = now(sim) - start_of_round - @info "Fidelity: $(fidelity)" - push!(logging, (t, fidelity)) - - sleep(0.5) - notify(net_obs) # show post-measurement state - sleep(0.5) - - # live update - push!(fidelity_points[], Point2f(t, fidelity)) - notify(fidelity_points) - end -end - -# NOTE: we’ll pass the `obs` from registernetplot_axis into the protocol so we can call notify(obs) -@resumable function PiecemakerProt(sim, n, net, link_success_prob, logging, rounds, net_obs) - while rounds != 0 - @info "round $(rounds)" - start = now(sim) - - # entangle each client with its designated switch slot i - for i in 1:n - entangler = EntanglerProt( - sim = sim, net = net, nodeA = 1, chooseA = i, nodeB = 1 + i, chooseB = 1, - success_prob = link_success_prob, rounds = 1, attempts = -1, attempt_time = 1.0, - ) - @process entangler() - end - - for i in 1:n - @process EntanglementCorrector(sim, net, 1 + i) - end - - while true - counter = 0 - while counter < n - @yield onchange_tag(net[1]) - if counter == 0 - # Initialize piecemaker |+> (slot n+1 at the switch) - initialize!(net[1][n+1], X1, time = now(sim)) - #notify(net_obs) # refresh view - end - - while true - counterpart = querydelete!(net[1], EntanglementCounterpart, ❓, ❓) - if !isnothing(counterpart) - slot, _, _ = counterpart - i = slot.idx # client index (1..n) - - # At this point the link (switch slot i) <-> (client i) exists -> show it - notify(net_obs) - sleep(0.5) # slow down so the link appearance is visible - - @yield lock(net[1][n+1]) & lock(net[1][slot.idx]) - res = fusion(net[1][n+1], net[1][slot.idx]) - unlock(net[1][n+1]); unlock(net[1][slot.idx]) - - tag!(net[1 + slot.idx][1], Tag(:updateX, res)) - counter += 1 - @debug "Fused client $(slot.idx) with piecemaker qubit" - - # After fusion, the entanglement “moves” to include the piecemaker slot. - # The plot reflects current net state; just notify to refresh. - notify(net_obs) - sleep(0.5) - else - break - end - end - end - - @debug "All clients entangled, measuring piecemaker | time: $(now(sim)-start)" - @yield lock(net[1][n+1]) - res = project_traceout!(net[1][n+1], σˣ) - unlock(net[1][n+1]) - tag!(net[2][1], Tag(:updateZ, res)) - break - end - - @yield @process Logger(sim, net, 2, n, logging, start, net_obs) - - # cleanup qubits (plot will update on next round’s actions) - foreach(q -> (traceout!(q); unlock(q)), net[1]) - foreach(q -> (traceout!(q); unlock(q)), [net[1 + i][1] for i in 1:n]) - notify(net_obs) - - rounds -= 1 - @debug "Round $(rounds) finished" - end - # between rounds (real time pause so it’s watchable) - sleep(0.7) - fidelity_points[] = Point2f[] # clear points for next round - notify(fidelity_points) -end - -function prepare_sim(n::Int, repr::AbstractRepresentation, noise::Union{AbstractBackground,Nothing}, - p_link::Float64, seed::Int, logging::DataFrame, rounds::Int) - - Random.seed!(seed) - - switch = Register([Qubit() for _ in 1:(n+1)], [repr for _ in 1:(n+1)], [noise for _ in 1:(n+1)]) - clients = [Register([Qubit()], [repr], [noise]) for _ in 1:n] - - graph = star_graph(n + 1) - net = RegisterNet(graph, [switch, clients...]) - sim = get_time_tracker(net) - - # create a temporary figure slot to hold obs after we have net - fig = Figure(resolution = (1200, 520)) - ax_fid = Axis(fig[1, 1], xlabel="Δt (simulation time)", ylabel="Fidelity to GHZₙ", title="Fidelity (live)") - scatter!(ax_fid, fidelity_points, markersize=8); ylims!(ax_fid, 0, 1) - - # Now attach the network plot to net and capture its obs - _, ax_net, _, net_obs = registernetplot_axis(fig[1, 2], net) - - @process PiecemakerProt(sim, n, net, p_link, logging, rounds, net_obs) - - ax_net.title = "Network (live)" - display(fig) - return sim -end - -function main(; n = 5, link_success_prob = 0.5, rounds = 5, seed = 42) - # noise - mem_depolar_prob = 0.5 - decoherence_rate = -log(1 - mem_depolar_prob) - noise_model = Depolarization(1 / decoherence_rate) - - logging = DataFrame(Δt = Float64[], fidelity = Float64[]) - - sim = prepare_sim(n, QuantumOpticsRepr(), noise_model, link_success_prob, seed, logging, rounds) - - # Run in background so UI can repaint - simtask = @async begin - t = @elapsed run(sim) - @info "Simulation finished in $(t) seconds" - end - wait(simtask) -end - -main() \ No newline at end of file diff --git a/examples/piecemakerswitch/live_visualization_network_interactive.jl b/examples/piecemakerswitch/live_visualization_network_interactive.jl new file mode 100644 index 00000000..a59b9b2c --- /dev/null +++ b/examples/piecemakerswitch/live_visualization_network_interactive.jl @@ -0,0 +1,356 @@ +# Live visualization of the piecemaker switch protocol +using QuantumSavory +using QuantumSavory.ProtocolZoo +using QuantumClifford: ghz +using Graphs +using ConcurrentSim +using ResumableFunctions +using DataFrames +using Random + +using Base.Threads +using WGLMakie +WGLMakie.activate!() + +using Bonito +using Markdown +const custom_css = Bonito.DOM.style("ul {list-style: circle !important;}") + +const ghzs = [ghz(n) for n in 1:7] # precompute GHZ targets +const fidelity_points = Observable(Point2f[]) # for live plotting + +""" + fusion(piecemaker_slot, client_slot) -> Int + +Fuse the client's switch-side qubit into the piecemaker via CNOT, then project +and trace out the client in the Z basis. Returns the projective outcome (1 or 2). + +- Arguments: + - piecemaker_slot: Qubit register slot at the switch (the piecemaker). + - client_slot: Qubit register slot at the switch for the given client. +- Behavior: apply!((piecemaker_slot, client_slot), CNOT); project_traceout!(client_slot, σᶻ). +- Returns: Int in {1, 2}, used to decide an X correction at the client. +""" +function fusion(piecemaker_slot, client_slot) + apply!((piecemaker_slot, client_slot), CNOT) + res = project_traceout!(client_slot, σᶻ) + return res +end + +""" + EntanglementCorrector(sim, net, node) + +Resumable process that waits for a Tag(:updateX, outcome) at `node`. +Locks the node’s qubit, applies X if `outcome == 2`, unlocks, and terminates. + +- Arguments: + - sim: ConcurrentSim time tracker. + - net: RegisterNet with registers per node. + - node: Client node index (1-based in `net`). +- Behavior: listens for :updateX on `net[node][1]`, ensures thread-safety via lock. +""" +@resumable function EntanglementCorrector(sim, net, node) + while true + @yield onchange_tag(net[node][1]) + msg = querydelete!(net[node][1], :updateX, ❓) + if !isnothing(msg) + value = msg[3][2] + @yield lock(net[node][1]) + @debug "X received at node $(node), with value $(value)" + value == 2 && apply!(net[node][1], X) + unlock(net[node][1]) + break + end + end +end + +""" + Logger(sim, net, node, n, logging, start_of_round, net_obs) + +Resumable process that waits for Tag(:updateZ, outcome) at `node`, applies a Z +correction if needed, computes fidelity to the n-qubit GHZ target, logs it, and +updates the live plot and network view. + +- Arguments: + - sim: ConcurrentSim time tracker. + - net: RegisterNet. + - node: Node index that receives the final X-basis measurement outcome. + - n: Number of clients/qubits in the GHZ state. + - logging: DataFrame to push (Δt, fidelity) rows. + - start_of_round: Simulation time when the round started. + - net_obs: Observable from registernetplot_axis to force redraws. +- Returns: nothing, updates `fidelity_points` and `logging`. +""" +@resumable function Logger(sim, net, node, n, logging, start_of_round, net_obs) + msg = querydelete!(net[node], :updateZ, ❓) + if isnothing(msg) + error("No message received at node $(node) with tag :updateZ.") + else + value = msg[3][2] + @debug "Z received at node $(node), with value $(value)" + @yield lock(net[node][1]) + value == 2 && apply!(net[node][1], Z) + unlock(net[node][1]) + + # Measure fidelity to GHZ + @yield reduce(&, [lock(q) for q in net[2]]) + obs_proj = SProjector(StabilizerState(ghzs[n])) + fidelity = real(observable([net[i+1][1] for i in 1:n], obs_proj; time = now(sim))) + t = now(sim) - start_of_round + @info "Fidelity: $(fidelity)" + push!(logging, (t, fidelity)) + + sleep(0.5) # slow down so network state change is visible + notify(net_obs) # show post-measurement state + sleep(0.5) + + # live update + push!(fidelity_points[], Point2f(t, fidelity)) + notify(fidelity_points) + end +end + +""" + PiecemakerProt(sim, n, net, link_success_prob, logging, rounds, net_obs) + +Piecemaker protocol for `n` clients. Repeatedly attempts link generation, fuses successful links into the piecemaker, +measures the piecemaker in X, triggers final correction via `Logger`, and logs fidelity. + +- Arguments: + - sim: ConcurrentSim time tracker. + - n: Number of clients. + - net: RegisterNet (star topology: switch + clients). + - link_success_prob: Per-attempt success probability for heralded links. + - logging: DataFrame to record (Δt, fidelity). + - rounds: Number of rounds to run (typically 1 per button click in the UI). + - net_obs: Observable from registernetplot_axis to force redraws. +- Note: Uses `EntanglerProt` for attempts and `fusion` for CNOT+Z projection. +""" +@resumable function PiecemakerProt(sim, n, net, link_success_prob, logging, rounds, net_obs) + while rounds != 0 + @info "round $(rounds)" + start = now(sim) + + # entangle each client with its designated switch slot i + for i in 1:n + entangler = EntanglerProt( + sim = sim, net = net, nodeA = 1, chooseA = i, nodeB = 1 + i, chooseB = 1, + success_prob = link_success_prob, rounds = 1, attempts = -1, attempt_time = 1.0, + ) + @process entangler() + end + + for i in 1:n + @process EntanglementCorrector(sim, net, 1 + i) + end + + while true + counter = 0 + while counter < n + @yield onchange_tag(net[1]) + if counter == 0 + # Initialize piecemaker |+> (slot n+1 at the switch) + initialize!(net[1][n+1], X1, time = now(sim)) + end + + while true + counterpart = querydelete!(net[1], EntanglementCounterpart, ❓, ❓) + if !isnothing(counterpart) + slot, _, _ = counterpart + + # At this point the link (switch slot i) <-> (client i) exists -> show it + notify(net_obs) + sleep(0.5) # slow down so the link appearance is visible + + @yield lock(net[1][n+1]) & lock(net[1][slot.idx]) + res = fusion(net[1][n+1], net[1][slot.idx]) + unlock(net[1][n+1]); unlock(net[1][slot.idx]) + + tag!(net[1 + slot.idx][1], Tag(:updateX, res)) + counter += 1 + @debug "Fused client $(slot.idx) with piecemaker qubit" + + # After fusion, the entanglement “moves” to include the piecemaker slot. + notify(net_obs) + sleep(0.5) + else + break + end + end + end + + @debug "All clients entangled, measuring piecemaker | time: $(now(sim)-start)" + @yield lock(net[1][n+1]) + res = project_traceout!(net[1][n+1], σˣ) + unlock(net[1][n+1]) + tag!(net[2][1], Tag(:updateZ, res)) + break + end + + @yield @process Logger(sim, net, 2, n, logging, start, net_obs) + + # cleanup qubits + foreach(q -> (traceout!(q); unlock(q)), net[1]) + foreach(q -> (traceout!(q); unlock(q)), [net[1 + i][1] for i in 1:n]) + notify(net_obs) + + rounds -= 1 + @debug "Round $(rounds) finished" + end + + sleep(0.7) + if rounds > 0 + fidelity_points[] = Point2f[] # clear points for next round + end + notify(fidelity_points) +end + +""" + prepare_sim(fig, n, link_success_prob, mem_depolar_prob, seed) -> (sim, net, logging) + +Build the star network (switch + `n` clients), attach the live network plot into `fig[1,2]`, +configure plotting limits and interactions, start the protocol process, and return the +simulation handle. + +- Arguments: + - fig: Makie Figure; network view is placed into `fig[1,2]`. + - n: Number of clients (and GHZ size). + - link_success_prob: Per-attempt entanglement success probability. + - mem_depolar_prob: Per-step memory depolarization probability; converted internally to T. + - seed: RNG seed. +- Returns: (sim, net, logging::DataFrame). +""" +function prepare_sim(fig::Figure, n::Int, link_success_prob::Float64, mem_depolar_prob::Float64, seed::Int) + Random.seed!(seed) + + repr = QuantumOpticsRepr() + + decoherence_rate = -log(1 - mem_depolar_prob) + noise_model = Depolarization(1 / decoherence_rate) + + logging = DataFrame(Δt = Float64[], fidelity = Float64[]) + + switch = Register([Qubit() for _ in 1:(n+1)], [repr for _ in 1:(n+1)], [noise_model for _ in 1:(n+1)]) + clients = [Register([Qubit()], [repr], [noise_model]) for _ in 1:n] + + graph = star_graph(n + 1) + net = RegisterNet(graph, [switch, clients...]) + sim = get_time_tracker(net) + + # Attach the network plot to net and capture its obs + _, ax_net, _, net_obs = registernetplot_axis(fig[1, 2], net) + ax_net.title = "Network of n=5 users (live)" + # Fix the visible ranges + xlims!(ax_net, -15, 15) + ylims!(ax_net, -15, 15) + ax_net.aspect = Makie.DataAspect() # keep aspect ratio + Makie.deregister_interaction!(ax_net, :scrollzoom) # disable zoom and pan interactions + Makie.deregister_interaction!(ax_net, :dragpan) + + @process PiecemakerProt(sim, n, net, link_success_prob, logging, 1, net_obs) # set rounds=1 + + return sim, net, logging +end + +# A helper to add parameter sliders to visualizations +function add_conf_sliders(fig) + conf = Dict( + :link_success_prob => 0.5, + :mem_depolar_prob => 0.1, + ) + conf_obs = Observable(conf) + sg = SliderGrid( + fig, + (label = "link success prob", + range = 0.05:0.05:1.0, format = "{:.2f}", startvalue = conf[:link_success_prob]), + (label = "mem depolar prob", + range = 0.05:0.05:1.0, format = "{:.2f}", startvalue = conf[:mem_depolar_prob]), + width = 300, + ) + + names = [:link_success_prob, :mem_depolar_prob] + for (name,slider) in zip(names,sg.sliders) + on(slider.value) do val + conf_obs[][name] = val + end + end + conf_obs +end + +# Serve the Makie app +landing = Bonito.App() do + + fig = Figure(resolution = (800, 600)) + ax_fid = Axis(fig[1, 1], xlabel="Δt (time steps)", ylabel="Fidelity to GHZₙ", title="Fidelity") + scatter!(ax_fid, fidelity_points, markersize = 8) + ylims!(ax_fid, 0, 1.05) + + running = Observable{Union{Bool,Nothing}}(false) + fig[2, 1] = buttongrid = GridLayout(tellwidth = false) + buttongrid[1,1] = b = Makie.Button( + fig, + label = @lift($running ? "Running..." : "Run once"), + height = 30, tellwidth = false, + ) + + conf_obs = add_conf_sliders(fig[2, 2]) + + on(b.clicks) do _ + if running[] # ignore while already running + return + end + running[] = true + @async begin + try # run the sim + sim, net, _ = prepare_sim(fig, 5, conf_obs[][:link_success_prob], conf_obs[][:mem_depolar_prob], 42) + run(sim) + finally + running[] = false + end + end + end + + content = md""" + Pick simulation settings and hit “Run once”. The left panel plots the running fidelity to the target GHZ state; the right panel shows the network state as it evolves. + + $(fig.scene) + + # GHZ state distribution with a quantum entanglement switch + + This demo simulates a star-shaped network with a central switch node and n client nodes. Each client holds one memory qubit locally and one at the switch. The switch has an extra “piecemaker” qubit (slot n+1) that is initialized in the |+⟩ state; it is used to “stitch together” all successful links into an n-party GHZ state. + + What happens during one run: + - Per time step, the switch attempts to entangle with each client in parallel (success probability set by the slider “link success prob”). + - When a client<>switch entanglement attempt succeeds, the switch immediately fuses the client’s switch-side qubit with the piecemaker via a CNOT, measures the client qubit in Z, and sends the outcome to the client. The client applies necessary corrections. + - After all clients have been fused, the piecemaker is measured in X. The first client receives that outcome and applies a Z correction if needed. + - The current n-qubit state (the clients’ memory qubits) is compared to the ideal GHZₙ target state. The resulting fidelity is plotted as a point on the left over the number of taken time steps Δt. + + Noise model: + - Memory qubits are subject to depolarizing noise ([`Depolarization`](https://github.com/QuantumSavory/QuantumSavory.jl/blob/2d40bb77b2abdebdd92a0d32830d97a9234d2fa0/src/backgrounds.jl#L18) background). The slider “mem depolar prob” controls the memory depolarization probability. + + UI guide: + - Left: fidelity vs simulation time Δt. Points accumulate across runs so you can compare settings. + - Right: network snapshot. Edges appear when links are established; fusions and measurements trigger visual updates. + - Sliders: tune link success probability and memory depolarization probability before each run. + - Button: starts a single run with the current settings. + + NOTE that this is a simplified simulation for demonstration purposes. In particular, it assumes instantaneous gates as well as classical communication. The only time inducing steps are the attempts for heralded entanglement generation (Δt = 1 time step each). + + [Browse or modify the code for this simulation on GitHub.](https://github.com/QuantumSavory/QuantumSavory.jl/tree/master/examples/piecemakerswitch/live_visualization_network_interactive.jl) + """ + return Bonito.DOM.div(Bonito.MarkdownCSS, Bonito.Styling, custom_css, content) +end; + + +isdefined(Main, :server) && close(server); +port = parse(Int, get(ENV, "QS_GHZSWITCH_PORT", "3000")) +interface = get(ENV, "QS_GHZSWITCH_IP", "0.0.0.0") # Bind to all interfaces +proxy_url = get(ENV, "QS_GHZSWITCH_PROXY", "") +server = Bonito.Server(interface, port; proxy_url); +Bonito.HTTPServer.start(server) +Bonito.route!(server, "/" => landing); + + +@info "app server is running on http://$(interface):$(port) | proxy_url=`$(proxy_url)`" + +wait(server) \ No newline at end of file diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index 6058d622..aad11d94 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -2,7 +2,6 @@ include("setup.jl") using DataFrames using CSV - # Set up the simulation parameters name = "qs_piecemeal" From ca48d0d8ad6efd78bae11c76c8dbfdcbb344506c Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 20 Oct 2025 16:09:16 +0200 Subject: [PATCH 33/38] updated readme, renamed web-app version --- examples/piecemakerswitch/README.md | 27 +++++++++++++------ ...e.jl => live_visualization_interactive.jl} | 0 2 files changed, 19 insertions(+), 8 deletions(-) rename examples/piecemakerswitch/{live_visualization_network_interactive.jl => live_visualization_interactive.jl} (100%) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 40866878..9042ed4b 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -1,14 +1,25 @@ -# System Overview -The goal is to share a GHZ state among multiple users. To do so, the clients connect to a central switch node, which then serves the GHZ state to them. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. In addition the switch's register holds a dedicated 'piecemaker' slot - a qubit in the $|+\rangle$ state, which all successfull clients fuse their switch'side qubits with. +# Distributing a GHZ state with a quantum entanglement switch -# Entanglement Distribution -In each time step, $n$ entanglement processes run in parallel. Upon completion of an entanglement link, it is fused with the piecemaker qubit. Once all clients went through this fusion operation, the piecemaker qubit is measured. This projects the state back to the clients, resulting in the desired shared GHZ state. +Live version is at [areweentangledyet.com/piecemakerswitch/](https://areweentangledyet.com/piecemakerswitch/). -# Fusion Operation -The fusion operation is performed on the switch node. Let's take a client who just managed to generate a bipartide entangled state -- entangled link -- with its associated qubit at the switch side. The switch executes a `CNOT` gate on the client's qubit (target) and the piecemaker qubit (control). Next, the switch measures the client qubit in the computational basis and sends the outcome to the client (in order to apply the necessary Pauli correction). This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. +In this setup, multiple nodes (user nodes) connect to a central node (switch node) trough bipartite entangled states. The switch node performs fusion operations on these shared states to create a GHZ state among the user nodes. The goal is to do the latter as memory efficient as possible. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. In addition the switch's register holds a dedicated 'piecemaker' slot - a qubit in the $|+\rangle$ state, which all successfull clients fuse their switch'side qubits with. We term this protocol the _piecemaker_ protocol, see ref.[1]. -# Noise -The memories residing in the nodes' `Register`s suffer from depolarizing noise, see [`Depolarization`](https://github.com/QuantumSavory/QuantumSavory.jl/blob/2d40bb77b2abdebdd92a0d32830d97a9234d2fa0/src/backgrounds.jl#L18). +In each time step, $n$ entanglement generation processes run in parallel. Upon creation of an entanglement link, it is fused with the piecemaker qubit. Once all clients went through this fusion operation, the piecemaker qubit is measured. This projects the state back to the clients, resulting in the desired shared GHZ state. +The fusion operation is performed on the switch node: Let's take a client who just managed to generate a bipartide entangled state -- entangled link -- with its associated qubit at the switch side. The switch executes a `CNOT` gate on the client's qubit (target) and the piecemaker qubit (control). Next, the switch measures the client qubit in the computational basis and sends the outcome to the client (in order to apply the necessary Pauli correction). This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. + +NOTE: The memories residing in the nodes' `Register`s suffer from depolarizing noise, see [`Depolarization`](https://qs.quantumsavory.org/stable/API/#QuantumSavory.Depolarization). + +The `setup.jl` file implements all necessary base functionality. +The other files run the simulation and generate visuals in a number of different circumstances: +1. `simple_run.jl` A single simulator script convenient for exploratory coding, running a numer of Monte Carlo simulations; +2. `live_visualization_interactive` A web-app version of the simulator; +3. `static_viz.jl` A script running a number of of simulations like the ones in point 1, followed by plotting the fidelity over the protocol's execution time. + +Documentation: +- [The "How To" doc page on setting up this simulation](https://qs.quantumsavory.org/dev/howto/piecemakerswitch/piecemakerswitch) + + +[1] [Prielinger et al., 2025](https://arxiv.org/abs/2508.14737) ### Protocol flow diff --git a/examples/piecemakerswitch/live_visualization_network_interactive.jl b/examples/piecemakerswitch/live_visualization_interactive.jl similarity index 100% rename from examples/piecemakerswitch/live_visualization_network_interactive.jl rename to examples/piecemakerswitch/live_visualization_interactive.jl From 6ae9e1a3bb53ef70a371e941196ad249695a77e1 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Mon, 20 Oct 2025 20:01:53 +0200 Subject: [PATCH 34/38] consolidated web-app with setup; small updates to static plot and simple run --- examples/piecemakerswitch/README.md | 8 +- .../live_visualization_interactive.jl | 248 ++---------------- examples/piecemakerswitch/setup.jl | 78 +++--- examples/piecemakerswitch/simple_run.jl | 56 +--- examples/piecemakerswitch/static_viz.jl | 11 +- 5 files changed, 75 insertions(+), 326 deletions(-) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 9042ed4b..8edbcf48 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -2,7 +2,7 @@ Live version is at [areweentangledyet.com/piecemakerswitch/](https://areweentangledyet.com/piecemakerswitch/). -In this setup, multiple nodes (user nodes) connect to a central node (switch node) trough bipartite entangled states. The switch node performs fusion operations on these shared states to create a GHZ state among the user nodes. The goal is to do the latter as memory efficient as possible. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. In addition the switch's register holds a dedicated 'piecemaker' slot - a qubit in the $|+\rangle$ state, which all successfull clients fuse their switch'side qubits with. We term this protocol the _piecemaker_ protocol, see ref.[1]. +In this setup, multiple nodes (user nodes) connect to a central node (switch node) trough bipartite entangled states. The switch node performs fusion operations on these shared states to create a GHZ state among the user nodes. The goal is to do the latter as memory efficient as possible. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. In addition the switch's register holds a dedicated 'piecemaker' slot - a qubit in the $|+\rangle$ state, which all successfull clients fuse their switch'side qubits with. We term this protocol the _piecemaker_ protocol, see [Prielinger et al., 2025](https://arxiv.org/abs/2508.14737). In each time step, $n$ entanglement generation processes run in parallel. Upon creation of an entanglement link, it is fused with the piecemaker qubit. Once all clients went through this fusion operation, the piecemaker qubit is measured. This projects the state back to the clients, resulting in the desired shared GHZ state. The fusion operation is performed on the switch node: Let's take a client who just managed to generate a bipartide entangled state -- entangled link -- with its associated qubit at the switch side. The switch executes a `CNOT` gate on the client's qubit (target) and the piecemaker qubit (control). Next, the switch measures the client qubit in the computational basis and sends the outcome to the client (in order to apply the necessary Pauli correction). This procedure merges the bipartide state into the (entangled) state that the piecemaker qubit is currently part of, modulo any required Pauli corrections. @@ -18,9 +18,6 @@ The other files run the simulation and generate visuals in a number of different Documentation: - [The "How To" doc page on setting up this simulation](https://qs.quantumsavory.org/dev/howto/piecemakerswitch/piecemakerswitch) - -[1] [Prielinger et al., 2025](https://arxiv.org/abs/2508.14737) - ### Protocol flow ```mermaid @@ -54,8 +51,7 @@ sequenceDiagram alt All clients fused SwitchNode->>SwitchNode: Measure piecemaker SwitchNode->>SwitchNode: Compute fidelity to GHZ state - SwitchNode->>Log: Log fidelity and time - SwitchNode->>SwitchNode: Trigger STOP + SwitchNode->>Log: Log fidelity and time [END OF PROTOCOL] else SwitchNode->>SwitchNode: Keep checking end diff --git a/examples/piecemakerswitch/live_visualization_interactive.jl b/examples/piecemakerswitch/live_visualization_interactive.jl index a59b9b2c..026662fa 100644 --- a/examples/piecemakerswitch/live_visualization_interactive.jl +++ b/examples/piecemakerswitch/live_visualization_interactive.jl @@ -1,12 +1,5 @@ # Live visualization of the piecemaker switch protocol -using QuantumSavory -using QuantumSavory.ProtocolZoo -using QuantumClifford: ghz -using Graphs -using ConcurrentSim -using ResumableFunctions -using DataFrames -using Random +include("setup.jl") using Base.Threads using WGLMakie @@ -16,220 +9,19 @@ using Bonito using Markdown const custom_css = Bonito.DOM.style("ul {list-style: circle !important;}") -const ghzs = [ghz(n) for n in 1:7] # precompute GHZ targets -const fidelity_points = Observable(Point2f[]) # for live plotting - -""" - fusion(piecemaker_slot, client_slot) -> Int - -Fuse the client's switch-side qubit into the piecemaker via CNOT, then project -and trace out the client in the Z basis. Returns the projective outcome (1 or 2). - -- Arguments: - - piecemaker_slot: Qubit register slot at the switch (the piecemaker). - - client_slot: Qubit register slot at the switch for the given client. -- Behavior: apply!((piecemaker_slot, client_slot), CNOT); project_traceout!(client_slot, σᶻ). -- Returns: Int in {1, 2}, used to decide an X correction at the client. -""" -function fusion(piecemaker_slot, client_slot) - apply!((piecemaker_slot, client_slot), CNOT) - res = project_traceout!(client_slot, σᶻ) - return res -end +logging = Observable(Point2f[]) # for plotting -""" - EntanglementCorrector(sim, net, node) - -Resumable process that waits for a Tag(:updateX, outcome) at `node`. -Locks the node’s qubit, applies X if `outcome == 2`, unlocks, and terminates. - -- Arguments: - - sim: ConcurrentSim time tracker. - - net: RegisterNet with registers per node. - - node: Client node index (1-based in `net`). -- Behavior: listens for :updateX on `net[node][1]`, ensures thread-safety via lock. -""" -@resumable function EntanglementCorrector(sim, net, node) - while true - @yield onchange_tag(net[node][1]) - msg = querydelete!(net[node][1], :updateX, ❓) - if !isnothing(msg) - value = msg[3][2] - @yield lock(net[node][1]) - @debug "X received at node $(node), with value $(value)" - value == 2 && apply!(net[node][1], X) - unlock(net[node][1]) - break - end - end +function push_to_logging!(logging::Observable, t::Float64, fidelity::Float64) + push!(logging[], Point2f(t, fidelity)) end -""" - Logger(sim, net, node, n, logging, start_of_round, net_obs) - -Resumable process that waits for Tag(:updateZ, outcome) at `node`, applies a Z -correction if needed, computes fidelity to the n-qubit GHZ target, logs it, and -updates the live plot and network view. - -- Arguments: - - sim: ConcurrentSim time tracker. - - net: RegisterNet. - - node: Node index that receives the final X-basis measurement outcome. - - n: Number of clients/qubits in the GHZ state. - - logging: DataFrame to push (Δt, fidelity) rows. - - start_of_round: Simulation time when the round started. - - net_obs: Observable from registernetplot_axis to force redraws. -- Returns: nothing, updates `fidelity_points` and `logging`. -""" -@resumable function Logger(sim, net, node, n, logging, start_of_round, net_obs) - msg = querydelete!(net[node], :updateZ, ❓) - if isnothing(msg) - error("No message received at node $(node) with tag :updateZ.") - else - value = msg[3][2] - @debug "Z received at node $(node), with value $(value)" - @yield lock(net[node][1]) - value == 2 && apply!(net[node][1], Z) - unlock(net[node][1]) - - # Measure fidelity to GHZ - @yield reduce(&, [lock(q) for q in net[2]]) - obs_proj = SProjector(StabilizerState(ghzs[n])) - fidelity = real(observable([net[i+1][1] for i in 1:n], obs_proj; time = now(sim))) - t = now(sim) - start_of_round - @info "Fidelity: $(fidelity)" - push!(logging, (t, fidelity)) - - sleep(0.5) # slow down so network state change is visible - notify(net_obs) # show post-measurement state - sleep(0.5) - - # live update - push!(fidelity_points[], Point2f(t, fidelity)) - notify(fidelity_points) - end -end - -""" - PiecemakerProt(sim, n, net, link_success_prob, logging, rounds, net_obs) - -Piecemaker protocol for `n` clients. Repeatedly attempts link generation, fuses successful links into the piecemaker, -measures the piecemaker in X, triggers final correction via `Logger`, and logs fidelity. - -- Arguments: - - sim: ConcurrentSim time tracker. - - n: Number of clients. - - net: RegisterNet (star topology: switch + clients). - - link_success_prob: Per-attempt success probability for heralded links. - - logging: DataFrame to record (Δt, fidelity). - - rounds: Number of rounds to run (typically 1 per button click in the UI). - - net_obs: Observable from registernetplot_axis to force redraws. -- Note: Uses `EntanglerProt` for attempts and `fusion` for CNOT+Z projection. -""" -@resumable function PiecemakerProt(sim, n, net, link_success_prob, logging, rounds, net_obs) - while rounds != 0 - @info "round $(rounds)" - start = now(sim) - - # entangle each client with its designated switch slot i - for i in 1:n - entangler = EntanglerProt( - sim = sim, net = net, nodeA = 1, chooseA = i, nodeB = 1 + i, chooseB = 1, - success_prob = link_success_prob, rounds = 1, attempts = -1, attempt_time = 1.0, - ) - @process entangler() - end - - for i in 1:n - @process EntanglementCorrector(sim, net, 1 + i) - end - - while true - counter = 0 - while counter < n - @yield onchange_tag(net[1]) - if counter == 0 - # Initialize piecemaker |+> (slot n+1 at the switch) - initialize!(net[1][n+1], X1, time = now(sim)) - end - - while true - counterpart = querydelete!(net[1], EntanglementCounterpart, ❓, ❓) - if !isnothing(counterpart) - slot, _, _ = counterpart - - # At this point the link (switch slot i) <-> (client i) exists -> show it - notify(net_obs) - sleep(0.5) # slow down so the link appearance is visible - - @yield lock(net[1][n+1]) & lock(net[1][slot.idx]) - res = fusion(net[1][n+1], net[1][slot.idx]) - unlock(net[1][n+1]); unlock(net[1][slot.idx]) - - tag!(net[1 + slot.idx][1], Tag(:updateX, res)) - counter += 1 - @debug "Fused client $(slot.idx) with piecemaker qubit" - - # After fusion, the entanglement “moves” to include the piecemaker slot. - notify(net_obs) - sleep(0.5) - else - break - end - end - end - - @debug "All clients entangled, measuring piecemaker | time: $(now(sim)-start)" - @yield lock(net[1][n+1]) - res = project_traceout!(net[1][n+1], σˣ) - unlock(net[1][n+1]) - tag!(net[2][1], Tag(:updateZ, res)) - break - end - - @yield @process Logger(sim, net, 2, n, logging, start, net_obs) - - # cleanup qubits - foreach(q -> (traceout!(q); unlock(q)), net[1]) - foreach(q -> (traceout!(q); unlock(q)), [net[1 + i][1] for i in 1:n]) - notify(net_obs) - - rounds -= 1 - @debug "Round $(rounds) finished" - end - - sleep(0.7) - if rounds > 0 - fidelity_points[] = Point2f[] # clear points for next round - end - notify(fidelity_points) -end - -""" - prepare_sim(fig, n, link_success_prob, mem_depolar_prob, seed) -> (sim, net, logging) - -Build the star network (switch + `n` clients), attach the live network plot into `fig[1,2]`, -configure plotting limits and interactions, start the protocol process, and return the -simulation handle. - -- Arguments: - - fig: Makie Figure; network view is placed into `fig[1,2]`. - - n: Number of clients (and GHZ size). - - link_success_prob: Per-attempt entanglement success probability. - - mem_depolar_prob: Per-step memory depolarization probability; converted internally to T. - - seed: RNG seed. -- Returns: (sim, net, logging::DataFrame). -""" -function prepare_sim(fig::Figure, n::Int, link_success_prob::Float64, mem_depolar_prob::Float64, seed::Int) - Random.seed!(seed) +function prepare_sim(fig::Figure, n::Int, link_success_prob::Float64, mem_depolar_prob::Float64) repr = QuantumOpticsRepr() decoherence_rate = -log(1 - mem_depolar_prob) noise_model = Depolarization(1 / decoherence_rate) - logging = DataFrame(Δt = Float64[], fidelity = Float64[]) - switch = Register([Qubit() for _ in 1:(n+1)], [repr for _ in 1:(n+1)], [noise_model for _ in 1:(n+1)]) clients = [Register([Qubit()], [repr], [noise_model]) for _ in 1:n] @@ -239,7 +31,7 @@ function prepare_sim(fig::Figure, n::Int, link_success_prob::Float64, mem_depola # Attach the network plot to net and capture its obs _, ax_net, _, net_obs = registernetplot_axis(fig[1, 2], net) - ax_net.title = "Network of n=5 users (live)" + ax_net.title = "Network of n=5 users (live Δt = 0.1s)" # Fix the visible ranges xlims!(ax_net, -15, 15) ylims!(ax_net, -15, 15) @@ -247,9 +39,9 @@ function prepare_sim(fig::Figure, n::Int, link_success_prob::Float64, mem_depola Makie.deregister_interaction!(ax_net, :scrollzoom) # disable zoom and pan interactions Makie.deregister_interaction!(ax_net, :dragpan) - @process PiecemakerProt(sim, n, net, link_success_prob, logging, 1, net_obs) # set rounds=1 + @process PiecemakerProt(sim, n, net, link_success_prob, -1) # set rounds=1 - return sim, net, logging + return sim, net, net_obs end # A helper to add parameter sliders to visualizations @@ -280,10 +72,13 @@ end # Serve the Makie app landing = Bonito.App() do + n = 5 # number of clients + fig = Figure(resolution = (800, 600)) ax_fid = Axis(fig[1, 1], xlabel="Δt (time steps)", ylabel="Fidelity to GHZₙ", title="Fidelity") - scatter!(ax_fid, fidelity_points, markersize = 8) + scatter!(ax_fid, logging, markersize = 8) ylims!(ax_fid, 0, 1.05) + xlims!(ax_fid, 0, 30) running = Observable{Union{Bool,Nothing}}(false) fig[2, 1] = buttongrid = GridLayout(tellwidth = false) @@ -302,10 +97,21 @@ landing = Bonito.App() do running[] = true @async begin try # run the sim - sim, net, _ = prepare_sim(fig, 5, conf_obs[][:link_success_prob], conf_obs[][:mem_depolar_prob], 42) - run(sim) + sim, net, net_obs = prepare_sim(fig, 5, conf_obs[][:link_success_prob], conf_obs[][:mem_depolar_prob]) + t = 0 + while true + t += 1 + if length(logging[]) > 10 + break + end + run(sim, t) + notify(net_obs) + notify(logging) + sleep(0.1) + end finally running[] = false + logging[] = Point2f[] # clear points for next run end end end @@ -326,11 +132,11 @@ landing = Bonito.App() do - The current n-qubit state (the clients’ memory qubits) is compared to the ideal GHZₙ target state. The resulting fidelity is plotted as a point on the left over the number of taken time steps Δt. Noise model: - - Memory qubits are subject to depolarizing noise ([`Depolarization`](https://github.com/QuantumSavory/QuantumSavory.jl/blob/2d40bb77b2abdebdd92a0d32830d97a9234d2fa0/src/backgrounds.jl#L18) background). The slider “mem depolar prob” controls the memory depolarization probability. + - Memory qubits are subject to depolarizing noise ([`Depolarization`](https://qs.quantumsavory.org/stable/API/#QuantumSavory.Depolarization) background). The slider “mem depolar prob” controls the memory depolarization probability. UI guide: - Left: fidelity vs simulation time Δt. Points accumulate across runs so you can compare settings. - - Right: network snapshot. Edges appear when links are established; fusions and measurements trigger visual updates. + - Right: network snapshot. Edges appear when links are established; updates every 0.1s of real time. - Sliders: tune link success probability and memory depolarization probability before each run. - Button: starts a single run with the current settings. diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 69e896f7..31591f8a 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -10,19 +10,16 @@ using QuantumClifford: ghz const ghzs = [ghz(n) for n in 1:7] # make const in order to not build new every time +function push_to_logging!(logging::Vector, t::Float64, fidelity::Float64) + push!(logging, Point2f(t, fidelity)) +end + function fusion(piecemaker_slot, client_slot) apply!((piecemaker_slot, client_slot), CNOT) res = project_traceout!(client_slot, σᶻ) return res end -""" - EntanglementCorrector(sim, net, node) - -A resumable protocol process that listens for entanglement correction instructions at a given network node. - -Upon receiving a message tagged `:updateX`, the protocol checks the received value and, if the value equals `2`, applies an X correction to the node's qubit. The process locks the qubit during the correction to ensure thread safety, logs the correction event, and then terminates. -""" @resumable function EntanglementCorrector(sim, net, node) while true @yield onchange_tag(net[node][1]) @@ -30,7 +27,7 @@ Upon receiving a message tagged `:updateX`, the protocol checks the received val if !isnothing(msg) value = msg[3][2] @yield lock(net[node][1]) - @info "X received at node $(node), with value $(value)" + @debug "X received at node $(node), with value $(value)" value == 2 && apply!(net[node][1], X) unlock(net[node][1]) break @@ -38,56 +35,48 @@ Upon receiving a message tagged `:updateX`, the protocol checks the received val end end -""" - Logger(sim, net, node, n, logging, start_of_round) - -A resumable protocol process that listens for entanglement measurement instructions at a given network node and logs the fidelity of the resulting state. - -Upon receiving a message tagged `:updateZ`, the protocol checks the received value and, if the value equals `2`, applies a Z correction to the node's qubit. The process then measures the fidelity of the current state against the ideal GHZ state, logs the result, and terminates. -""" -@resumable function Logger(sim, net, node, n, logging, start_of_round) +@resumable function Logger(sim, net, node, n, start_of_round) msg = querydelete!(net[node], :updateZ, ❓) if isnothing(msg) error("No message received at node $(node) with tag :updateZ.") else value = msg[3][2] - @info "Z received at node $(node), with value $(value)" + @debug "Z received at node $(node), with value $(value)" @yield lock(net[node][1]) value == 2 && apply!(net[node][1], Z) unlock(net[node][1]) # Measure the fidelity to the GHZ state @yield reduce(&, [lock(q) for q in net[2]]) - obs = SProjector(StabilizerState(ghzs[n])) # GHZ state projector to measure - fidelity = real(observable([net[i+1][1] for i in 1:n], obs; time=now(sim))) + obs_proj = SProjector(StabilizerState(ghzs[n])) # GHZ state projector to measure + fidelity = real(observable([net[i+1][1] for i in 1:n], obs_proj; time = now(sim))) t = now(sim) - start_of_round - @info "Fidelity: $(fidelity)" - - push!(logging, (t, fidelity)) - - # update for visualization - pts = fidelity_points[] # current vector of Point2f - push!(pts, Point2f(t, fidelity)) - fidelity_points[] = pts # notify Makie plot + @debug "Fidelity: $(fidelity)" + push_to_logging!(logging, t, fidelity) end end -@resumable function PiecemakerProt(sim, n, net, link_success_prob, logging, rounds) +function clear_up_qubits!(net, n) + # cleanup qubits + foreach(q -> (traceout!(q); unlock(q)), net[1]) + foreach(q -> (traceout!(q); unlock(q)), [net[1 + i][1] for i in 1:n]) +end +@resumable function PiecemakerProt(sim, n, net, link_success_prob, rounds) while rounds != 0 - @info "round $(rounds)" + @debug "round $(rounds)" start = now(sim) - for i in 1:n # entangle each client with the switch + for i in 1:n entangler = EntanglerProt( - sim=sim, net=net, nodeA=1, chooseA=i, nodeB=1+i, chooseB=1, - success_prob=link_success_prob, rounds=1, attempts=-1, attempt_time=1.0, - ) + sim = sim, net = net, nodeA = 1, chooseA = i, nodeB = 1 + i, chooseB = 1, + success_prob = link_success_prob, rounds = 1, attempts = -1, attempt_time = 1.0, + ) @process entangler() end for i in 1:n - @process EntanglementCorrector(sim, net, 1+i) # start entanglement correctors at each client + @process EntanglementCorrector(sim, net, 1 + i) end while true @@ -95,9 +84,9 @@ end counter = 0 while counter < n # until all clients are entangled @yield onchange_tag(net[1]) - if counter == 0 # initialize piecemaker + if counter == 0 # Initialize "piecemaker" qubit in |+> state when first qubit arrived - initialize!(net[1][n+1], X1, time=now(sim)) + initialize!(net[1][n+1], X1, time = now(sim)) end while true @@ -108,9 +97,8 @@ end # fuse the qubit with the piecemaker qubit @yield lock(net[1][n+1]) & lock(net[1][slot.idx]) res = fusion(net[1][n+1], net[1][slot.idx]) - unlock(net[1][n+1]) - unlock(net[1][slot.idx]) - tag!(net[1+slot.idx][1], Tag(:updateX, res)) # communicate change to client node + unlock(net[1][n+1]); unlock(net[1][slot.idx]) + tag!(net[1 + slot.idx][1], Tag(:updateX, res)) # communicate change to client node counter += 1 @debug "Fused client $(slot.idx) with piecemaker qubit" else @@ -127,19 +115,17 @@ end break end - @yield @process Logger(sim, net, 2, n, logging, start) # start logger - - foreach(q -> (traceout!(q); unlock(q)), net[1]) - foreach(q -> (traceout!(q); unlock(q)), [net[1+i][1] for i in 1:n]) + @yield @process Logger(sim, net, 2, n, start) + # cleanup qubits + clear_up_qubits!(net, n) rounds -= 1 @debug "Round $(rounds) finished" end - end function prepare_sim(n::Int, states_representation::AbstractRepresentation, noise_model::Union{AbstractBackground, Nothing}, - link_success_prob::Float64, seed::Int, logging::DataFrame, rounds::Int) + link_success_prob::Float64, seed::Int, rounds::Int) # Set a random seed Random.seed!(seed) @@ -152,6 +138,6 @@ function prepare_sim(n::Int, states_representation::AbstractRepresentation, nois sim = get_time_tracker(net) # Start the piecemaker protocol - @process PiecemakerProt(sim, n, net, link_success_prob, logging, rounds) + @process PiecemakerProt(sim, n, net, link_success_prob, rounds) return sim end \ No newline at end of file diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index aad11d94..db37befe 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -1,13 +1,11 @@ include("setup.jl") -using DataFrames -using CSV -# Set up the simulation parameters -name = "qs_piecemeal" - -nruns = 10 -mem_depolar_prob = 0.1 +logging = Point2f[] # for plotting +mem_depolar_prob = 0.1 # memory depolarization probability +decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates +noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits link_success_prob = 0.5 +rounds = 100 # number of rounds to run results_per_client = DataFrame[] for nclients in 2:2 @@ -16,44 +14,6 @@ for nclients in 2:2 fidelities = Float64[] elapsed_times = Float64[] - # Run the simulation nruns times - for i in 1:nruns - sim, consumer = prepare_simulation(nclients, mem_depolar_prob, link_success_prob) - elapsed_time = @elapsed run(sim) - # Extract data from consumer.log - distribution_time, fidelity = consumer.log[1] - append!(distribution_times, distribution_time) - append!(fidelities, fidelity) - append!(elapsed_times, elapsed_time) - @info "Run $i completed" - end - - # Fill the results DataFrame - results = DataFrame( - distribution_times = distribution_times, - fidelities = fidelities, - elapsed_times = elapsed_times - ) - results.num_remote_nodes .= nclients - results.link_success_prob .= link_success_prob - results.mem_depolar_prob .= mem_depolar_prob - results.type .= name - - push!(results_per_client, results) - @info "Clients $nclients completed" -end -results_total = vcat(results_per_client...) - -# Group and summarize the data -grouped_df = groupby(results_total, [:num_remote_nodes, :distribution_times]) -summary_df = combine( - grouped_df, - :fidelities => mean => :mean_fidelities, - :fidelities => std => :std_fidelities -) - -@info summary_df - -# Uncomment to write results to CSV -# CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven.csv", results_total) -# CSV.write("examples/piecemakerswitch/output/piecemaker-eventdriven_summary.csv", summary_df) \ No newline at end of file + sim = prepare_sim(nclients, QuantumOpticsRepr(), noise_model, link_success_prob, 42, rounds) + elapsed_time = @elapsed run(sim) +end \ No newline at end of file diff --git a/examples/piecemakerswitch/static_viz.jl b/examples/piecemakerswitch/static_viz.jl index 8d177cd5..f40692b5 100644 --- a/examples/piecemakerswitch/static_viz.jl +++ b/examples/piecemakerswitch/static_viz.jl @@ -1,26 +1,27 @@ include("setup.jl") using GLMakie GLMakie.activate!(inline=false) -using DataFrames + +logging = Point2f[] # for plotting mem_depolar_prob = 0.1 # memory depolarization probability decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits -logging = DataFrame(Δt=[], fidelity=[]) +rounds = 10 # number of rounds to run sim = prepare_sim( - 5, QuantumOpticsRepr(), noise_model, 0.5, 42, logging, 10 + 5, QuantumOpticsRepr(), noise_model, 0.5, 42, rounds ) timed = @elapsed run(sim) println("Simulation finished in $(timed) seconds") @info logging -function plot_fidelity(logging::DataFrame) +function plot_fidelity(logging::Vector{Point2f}) fig = Figure(resolution = (800, 450)) ax = Axis(fig[1, 1], xlabel = "Δt (simulation time)", ylabel = "Fidelity to GHZₙ", title = "Entanglement fidelity over time") - scatter!(ax, logging.Δt, logging.fidelity, markersize = 8) + scatter!(ax, logging, markersize = 8) ylims!(ax, 0, 1) fig end From 31b6b2a0db14c1d5cfdb1b8c970db0f3ef24472b Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 21 Oct 2025 11:48:09 +0200 Subject: [PATCH 35/38] added examples to test_examples --- examples/piecemakerswitch/README.md | 2 +- test/test_examples.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md index 8edbcf48..4dc50eb2 100644 --- a/examples/piecemakerswitch/README.md +++ b/examples/piecemakerswitch/README.md @@ -1,6 +1,6 @@ # Distributing a GHZ state with a quantum entanglement switch -Live version is at [areweentangledyet.com/piecemakerswitch/](https://areweentangledyet.com/piecemakerswitch/). +Live version is available at [areweentangledyet.com/piecemakerswitch/](https://areweentangledyet.com/piecemakerswitch/). In this setup, multiple nodes (user nodes) connect to a central node (switch node) trough bipartite entangled states. The switch node performs fusion operations on these shared states to create a GHZ state among the user nodes. The goal is to do the latter as memory efficient as possible. Each of the $n$ clients can store one memory qubit in its memory buffer and one qubit at the switch side. In addition the switch's register holds a dedicated 'piecemaker' slot - a qubit in the $|+\rangle$ state, which all successfull clients fuse their switch'side qubits with. We term this protocol the _piecemaker_ protocol, see [Prielinger et al., 2025](https://arxiv.org/abs/2508.14737). diff --git a/test/test_examples.jl b/test/test_examples.jl index cb54912b..0ad91983 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -48,6 +48,14 @@ end include("../examples/simpleswitch/1_interactive_visualization.jl") end +@testitem "Examples - piecemakerswitch 1" tags=[:examples_plotting] begin + include("../examples/piecemakerswitch/static_viz.jl") +end + +@testitem "Examples - piecemakerswitch 2" tags=[:examples_plotting] begin + include("../examples/piecemakerswitch/live_visualization_network.jl") +end + @safetestset "Examples - repeatergrid 1a" tags=[:examples_plotting] begin include("../examples/repeatergrid/1a_async_interactive_visualization.jl") end From 5c23612222afe7e8ed7781fbf2833c13ade09894 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 21 Oct 2025 11:58:59 +0200 Subject: [PATCH 36/38] added docstrings to setup file --- examples/piecemakerswitch/setup.jl | 107 +++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 5 deletions(-) diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 31591f8a..09de70a1 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -10,17 +10,56 @@ using QuantumClifford: ghz const ghzs = [ghz(n) for n in 1:7] # make const in order to not build new every time +""" + push_to_logging!(logging::Vector, t::Float64, fidelity::Float64) + +Append a time-fidelity data point to the logging vector. + +# Arguments +- `logging::Vector`: Vector of `Point2f` storing (time, fidelity) pairs +- `t::Float64`: Simulation time at which the measurement was taken +- `fidelity::Float64`: Measured fidelity to the target GHZ state +""" function push_to_logging!(logging::Vector, t::Float64, fidelity::Float64) push!(logging, Point2f(t, fidelity)) end -function fusion(piecemaker_slot, client_slot) +""" + fusion(piecemaker_slot::Int, client_slot::Int) + +Perform a fusion operation between a piecemaker qubit and a client qubit. + +Applies a CNOT gate with the piecemaker qubit as control and the client qubit +as target, then measures the client qubit in the Z basis and traces it out. + +# Arguments +- `piecemaker_slot::Int`: Register slot index containing the piecemaker qubit +- `client_slot::Int`: Register slot index containing the client qubit to be fused + +# Returns +- Measurement outcome (1 or 2) from projecting the client qubit onto the Z basis +""" +function fusion(piecemaker_slot::Int, client_slot::Int) apply!((piecemaker_slot, client_slot), CNOT) res = project_traceout!(client_slot, σᶻ) return res end -@resumable function EntanglementCorrector(sim, net, node) +""" + EntanglementCorrector(sim, net::RegisterNet, node::Int) + +Resumable function that waits for X correction messages and applies them. + +This protocol monitors a client node for `:updateX` tags sent by the switch +after fusion operations. When received, it applies an X gate if needed (when +the measurement outcome is 2) to correct the client's qubit state. + +# Arguments +- `sim`: ConcurrentSim simulation environment +- `net::RegisterNet`: Network containing all nodes +- `node::Int`: Index of the client node to monitor and correct +""" +@resumable function EntanglementCorrector(sim, net::RegisterNet, node::Int) while true @yield onchange_tag(net[node][1]) msg = querydelete!(net[node][1], :updateX, ❓) @@ -35,7 +74,25 @@ end end end -@resumable function Logger(sim, net, node, n, start_of_round) +""" + Logger(sim, net::RegisterNet, node::Int, n::Int, start_of_round) + +Resumable function that applies Z corrections and measures final GHZ fidelity. + +Waits for a `:updateZ` tag from the switch (sent after measuring the piecemaker +qubit in the X basis), applies the necessary Z correction if the measurement +outcome is 2, then measures the fidelity of the resulting n-qubit state to the +target GHZ state and logs it. Pushes a (time, fidelity) data point to the global +`logging` vector via `push_to_logging!` + +# Arguments +- `sim`: ConcurrentSim simulation environment +- `net::RegisterNet`: Network containing all nodes +- `node::Int`: Index of the client node receiving the Z correction +- `n::Int`: Number of clients in the GHZ state +- `start_of_round`: Simulation time when the current round started +""" +@resumable function Logger(sim, net::RegisterNet, node::Int, n::Int, start_of_round) msg = querydelete!(net[node], :updateZ, ❓) if isnothing(msg) error("No message received at node $(node) with tag :updateZ.") @@ -56,13 +113,53 @@ end end end -function clear_up_qubits!(net, n) +""" + clear_up_qubits!(net::RegisterNet, n::Int) + +Clean up all qubits at the switch and client nodes after a round. + +Traces out and unlocks all storage qubits at the switch (node 1) and the +first qubit at each of the n client nodes, preparing the network for the +next round or simulation end. + +# Arguments +- `net::RegisterNet`: Network containing all nodes +- `n::Int`: Number of client nodes +""" +function clear_up_qubits!(net::RegisterNet, n::Int) # cleanup qubits foreach(q -> (traceout!(q); unlock(q)), net[1]) foreach(q -> (traceout!(q); unlock(q)), [net[1 + i][1] for i in 1:n]) end -@resumable function PiecemakerProt(sim, n, net, link_success_prob, rounds) +""" + PiecemakerProt(sim, n::Int, net::RegisterNet, link_success_prob::Float64, rounds::Int) + +Main resumable protocol for the piecemaker quantum switching scheme. + +Orchestrates the generation of n-qubit GHZ states across client nodes using +a central switch node. Each round: +1. Establishes entanglement between the switch and each client in parallel +2. Fuses successful client qubits with a piecemaker qubit at the switch via CNOT +3. Measures the piecemaker qubit in the X basis to project clients into a GHZ state +4. Communicates measurement outcomes for local corrections +5. Logs the fidelity to the target GHZ state and cleans up resources + +# Arguments +- `sim`: ConcurrentSim simulation environment +- `n::Int`: Number of client nodes (GHZ state size) +- `net::RegisterNet`: Network with star topology (switch at center, clients at leaves) +- `link_success_prob::Float64`: Probability of successful entanglement per attempt +- `rounds::Int`: Number of GHZ generation rounds to execute + +# Protocol Overview +The piecemaker protocol generates multipartite entanglement by: +- Creating Bell pairs between switch and each client using `EntanglerProt` +- Fusing these pairs at the switch using a |+⟩ "piecemaker" qubit (slot n+1) +- Measuring the piecemaker in the X basis to project clients into GHZ +- Communicating measurement outcomes via tags for local X and Z corrections +""" +@resumable function PiecemakerProt(sim, n::Int, net::RegisterNet, link_success_prob::Float64, rounds::Int) while rounds != 0 @debug "round $(rounds)" start = now(sim) From 669528cb484a87f7359494ee57b5fbb462dca309 Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Tue, 21 Oct 2025 12:34:31 +0200 Subject: [PATCH 37/38] small final updates --- .../live_visualization_interactive.jl | 8 ++++---- examples/piecemakerswitch/setup.jl | 8 ++++---- examples/piecemakerswitch/simple_run.jl | 17 +++++++++++++---- examples/piecemakerswitch/static_viz.jl | 2 +- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/piecemakerswitch/live_visualization_interactive.jl b/examples/piecemakerswitch/live_visualization_interactive.jl index 026662fa..f26eac8d 100644 --- a/examples/piecemakerswitch/live_visualization_interactive.jl +++ b/examples/piecemakerswitch/live_visualization_interactive.jl @@ -97,7 +97,7 @@ landing = Bonito.App() do running[] = true @async begin try # run the sim - sim, net, net_obs = prepare_sim(fig, 5, conf_obs[][:link_success_prob], conf_obs[][:mem_depolar_prob]) + sim, net, net_obs = prepare_sim(fig, n, conf_obs[][:link_success_prob], conf_obs[][:mem_depolar_prob]) t = 0 while true t += 1 @@ -117,13 +117,13 @@ landing = Bonito.App() do end content = md""" - Pick simulation settings and hit “Run once”. The left panel plots the running fidelity to the target GHZ state; the right panel shows the network state as it evolves. + Pick simulation settings and hit “Run once”. The left panel plots the running fidelity to the target GHZ state; the right panel shows the network state as it evolves over 10 simulation rounds. $(fig.scene) # GHZ state distribution with a quantum entanglement switch - This demo simulates a star-shaped network with a central switch node and n client nodes. Each client holds one memory qubit locally and one at the switch. The switch has an extra “piecemaker” qubit (slot n+1) that is initialized in the |+⟩ state; it is used to “stitch together” all successful links into an n-party GHZ state. + This demo simulates 10 rounds of GHZ-state distribution in a star-shaped network with a central switch node and n client nodes. Each client holds one memory qubit locally and one at the switch. The switch has an extra “piecemaker” qubit (slot n+1) that is initialized in the |+⟩ state; it is used to fuse all successful links into an n-party GHZ state. What happens during one run: - Per time step, the switch attempts to entangle with each client in parallel (success probability set by the slider “link success prob”). @@ -142,7 +142,7 @@ landing = Bonito.App() do NOTE that this is a simplified simulation for demonstration purposes. In particular, it assumes instantaneous gates as well as classical communication. The only time inducing steps are the attempts for heralded entanglement generation (Δt = 1 time step each). - [Browse or modify the code for this simulation on GitHub.](https://github.com/QuantumSavory/QuantumSavory.jl/tree/master/examples/piecemakerswitch/live_visualization_network_interactive.jl) + [Browse or modify the code for this simulation on GitHub.](https://github.com/QuantumSavory/QuantumSavory.jl/tree/master/examples/piecemakerswitch/live_visualization_interactive.jl) """ return Bonito.DOM.div(Bonito.MarkdownCSS, Bonito.Styling, custom_css, content) end; diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 09de70a1..50156596 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -21,11 +21,11 @@ Append a time-fidelity data point to the logging vector. - `fidelity::Float64`: Measured fidelity to the target GHZ state """ function push_to_logging!(logging::Vector, t::Float64, fidelity::Float64) - push!(logging, Point2f(t, fidelity)) + push!(logging, (t, fidelity)) end """ - fusion(piecemaker_slot::Int, client_slot::Int) + fusion(piecemaker_slot::RegRef, client_slot::RegRef) Perform a fusion operation between a piecemaker qubit and a client qubit. @@ -39,7 +39,7 @@ as target, then measures the client qubit in the Z basis and traces it out. # Returns - Measurement outcome (1 or 2) from projecting the client qubit onto the Z basis """ -function fusion(piecemaker_slot::Int, client_slot::Int) +function fusion(piecemaker_slot::RegRef, client_slot::RegRef) apply!((piecemaker_slot, client_slot), CNOT) res = project_traceout!(client_slot, σᶻ) return res @@ -217,7 +217,7 @@ The piecemaker protocol generates multipartite entanglement by: # cleanup qubits clear_up_qubits!(net, n) rounds -= 1 - @debug "Round $(rounds) finished" + @info "Round $(rounds) finished" end end diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index db37befe..615e3a87 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -1,14 +1,15 @@ include("setup.jl") -logging = Point2f[] # for plotting mem_depolar_prob = 0.1 # memory depolarization probability decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits link_success_prob = 0.5 rounds = 100 # number of rounds to run -results_per_client = DataFrame[] -for nclients in 2:2 +results_per_client = DataFrame(nclients = Int[], Δt = Float64[], fidelity = Float64[], avg_elapsed_time = Float64[]) +for nclients in 2:5 + logging = Tuple[] # for plotting + # Prepare simulation data storage distribution_times = Float64[] fidelities = Float64[] @@ -16,4 +17,12 @@ for nclients in 2:2 sim = prepare_sim(nclients, QuantumOpticsRepr(), noise_model, link_success_prob, 42, rounds) elapsed_time = @elapsed run(sim) -end \ No newline at end of file + + # Add logging data to DataFrame + for point in logging + push!(results_per_client, (nclients = nclients, Δt = point[1], fidelity = point[2], avg_elapsed_time = elapsed_time/rounds)) + end + + @info "Simulation with $(nclients) clients finished in $(elapsed_time) seconds" +end +println(results_per_client) \ No newline at end of file diff --git a/examples/piecemakerswitch/static_viz.jl b/examples/piecemakerswitch/static_viz.jl index f40692b5..f9112401 100644 --- a/examples/piecemakerswitch/static_viz.jl +++ b/examples/piecemakerswitch/static_viz.jl @@ -7,7 +7,7 @@ logging = Point2f[] # for plotting mem_depolar_prob = 0.1 # memory depolarization probability decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits -rounds = 10 # number of rounds to run +rounds = 100 # number of rounds to run sim = prepare_sim( 5, QuantumOpticsRepr(), noise_model, 0.5, 42, rounds From 502d33fb528d4ad752cefe5215bf96934629633c Mon Sep 17 00:00:00 2001 From: Luise Prielinger Date: Sun, 2 Nov 2025 20:14:04 +0100 Subject: [PATCH 38/38] consolidated print statements --- examples/Project.toml | 3 ++ .../live_visualization_interactive.jl | 11 ++++---- examples/piecemakerswitch/setup.jl | 28 +++++++++---------- examples/piecemakerswitch/simple_run.jl | 2 +- examples/piecemakerswitch/static_viz.jl | 8 ++++-- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/examples/Project.toml b/examples/Project.toml index 4b8a1019..45f4bd83 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -5,12 +5,15 @@ ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" +GraphMakie = "1ecd5474-83a3-4783-bb4f-06765db800d2" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" +QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c" QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" diff --git a/examples/piecemakerswitch/live_visualization_interactive.jl b/examples/piecemakerswitch/live_visualization_interactive.jl index f26eac8d..1bb2d2f6 100644 --- a/examples/piecemakerswitch/live_visualization_interactive.jl +++ b/examples/piecemakerswitch/live_visualization_interactive.jl @@ -84,14 +84,15 @@ landing = Bonito.App() do fig[2, 1] = buttongrid = GridLayout(tellwidth = false) buttongrid[1,1] = b = Makie.Button( fig, - label = @lift($running ? "Running..." : "Run once"), + label = @lift($running ? "Stop" : "Run"), height = 30, tellwidth = false, ) conf_obs = add_conf_sliders(fig[2, 2]) on(b.clicks) do _ - if running[] # ignore while already running + if running[] # if already running, stop it + running[] = false return end running[] = true @@ -99,9 +100,9 @@ landing = Bonito.App() do try # run the sim sim, net, net_obs = prepare_sim(fig, n, conf_obs[][:link_success_prob], conf_obs[][:mem_depolar_prob]) t = 0 - while true + while running[] == true t += 1 - if length(logging[]) > 10 + if length(logging[]) > 100 break end run(sim, t) @@ -111,7 +112,6 @@ landing = Bonito.App() do end finally running[] = false - logging[] = Point2f[] # clear points for next run end end end @@ -159,4 +159,5 @@ Bonito.route!(server, "/" => landing); @info "app server is running on http://$(interface):$(port) | proxy_url=`$(proxy_url)`" + wait(server) \ No newline at end of file diff --git a/examples/piecemakerswitch/setup.jl b/examples/piecemakerswitch/setup.jl index 50156596..1332eb60 100644 --- a/examples/piecemakerswitch/setup.jl +++ b/examples/piecemakerswitch/setup.jl @@ -221,20 +221,20 @@ The piecemaker protocol generates multipartite entanglement by: end end -function prepare_sim(n::Int, states_representation::AbstractRepresentation, noise_model::Union{AbstractBackground, Nothing}, - link_success_prob::Float64, seed::Int, rounds::Int) +# function prepare_sim(n::Int, states_representation::AbstractRepresentation, noise_model::Union{AbstractBackground, Nothing}, +# link_success_prob::Float64, seed::Int, rounds::Int) - # Set a random seed - Random.seed!(seed) +# # Set a random seed +# Random.seed!(seed) - switch = Register([Qubit() for _ in 1:(n+1)], [states_representation for _ in 1:(n+1)], [noise_model for _ in 1:(n+1)]) # storage qubits at the switch, first qubit is the "piecemaker" qubit - clients = [Register([Qubit()], [states_representation], [noise_model]) for _ in 1:n] # client qubits +# switch = Register([Qubit() for _ in 1:(n+1)], [states_representation for _ in 1:(n+1)], [noise_model for _ in 1:(n+1)]) # storage qubits at the switch, first qubit is the "piecemaker" qubit +# clients = [Register([Qubit()], [states_representation], [noise_model]) for _ in 1:n] # client qubits - graph = star_graph(n+1) - net = RegisterNet(graph, [switch, clients...]) - sim = get_time_tracker(net) - - # Start the piecemaker protocol - @process PiecemakerProt(sim, n, net, link_success_prob, rounds) - return sim -end \ No newline at end of file +# graph = star_graph(n+1) +# net = RegisterNet(graph, [switch, clients...]) +# sim = get_time_tracker(net) + +# # Start the piecemaker protocol +# @process PiecemakerProt(sim, n, net, link_success_prob, rounds) +# return sim +# end \ No newline at end of file diff --git a/examples/piecemakerswitch/simple_run.jl b/examples/piecemakerswitch/simple_run.jl index 615e3a87..287d806d 100644 --- a/examples/piecemakerswitch/simple_run.jl +++ b/examples/piecemakerswitch/simple_run.jl @@ -25,4 +25,4 @@ for nclients in 2:5 @info "Simulation with $(nclients) clients finished in $(elapsed_time) seconds" end -println(results_per_client) \ No newline at end of file +@info("Results per client:\n", results_per_client) \ No newline at end of file diff --git a/examples/piecemakerswitch/static_viz.jl b/examples/piecemakerswitch/static_viz.jl index f9112401..e2dfa4dd 100644 --- a/examples/piecemakerswitch/static_viz.jl +++ b/examples/piecemakerswitch/static_viz.jl @@ -4,17 +4,21 @@ GLMakie.activate!(inline=false) logging = Point2f[] # for plotting + +# Set simulation parameters +nusers = 5 +link_success_prob = 0.5 # probability of successful entanglement per attempt mem_depolar_prob = 0.1 # memory depolarization probability decoherence_rate = - log(1 - mem_depolar_prob) # decoherence rates noise_model = Depolarization(1/decoherence_rate) # noise model applied to the memory qubits rounds = 100 # number of rounds to run sim = prepare_sim( - 5, QuantumOpticsRepr(), noise_model, 0.5, 42, rounds + nusers, QuantumOpticsRepr(), noise_model, link_success_prob, 42, rounds ) timed = @elapsed run(sim) -println("Simulation finished in $(timed) seconds") +@info("Simulation finished in $(timed) seconds") @info logging function plot_fidelity(logging::Vector{Point2f})