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 716f233b..da8db3fc 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" diff --git a/examples/piecemakerswitch/Project.toml b/examples/piecemakerswitch/Project.toml new file mode 100644 index 00000000..8bf43bd7 --- /dev/null +++ b/examples/piecemakerswitch/Project.toml @@ -0,0 +1,10 @@ +[deps] +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" +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 diff --git a/examples/piecemakerswitch/README.md b/examples/piecemakerswitch/README.md new file mode 100644 index 00000000..5ef6fa9e --- /dev/null +++ b/examples/piecemakerswitch/README.md @@ -0,0 +1,52 @@ +# 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. 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. + +### Protocol flow + +```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 new file mode 100644 index 00000000..f45a3a04 --- /dev/null +++ b/examples/piecemakerswitch/setup.jl @@ -0,0 +1,42 @@ +using QuantumSavory +using QuantumSavory.ProtocolZoo +using Graphs +using ConcurrentSim +using ResumableFunctions +using Distributions +using DataFrames +using CSV + + +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 + + # The graph of network connectivity. Index 1 corresponds to the switch. + 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:nclients] #Depolarization(1/r_depol) + 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) + + # 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 + 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 new file mode 100644 index 00000000..bb958c1f --- /dev/null +++ b/examples/piecemakerswitch/simple_run.jl @@ -0,0 +1,59 @@ +include("setup.jl") +using DataFrames +using CSV + + +# Set up the simulation parameters +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 + distribution_times = Float64[] + fidelities = Float64[] + elapsed_times = Float64[] + + 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 + + # Initialize 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 + +# 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/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..0cbb863f 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 using QuantumSavory: Wildcard -using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap +using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap, EntanglementFusion 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 @@ -16,11 +16,11 @@ import SumTypes export # protocols - EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer, CutoffProt, + EntanglerProt, SelectedEntanglerProt, SwapperProt, FusionProt, EntanglementTracker, EntanglementConsumer, FusionConsumer, CutoffProt, # tags - EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, + EntanglementCounterpart, FusionCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ, # from Switches - SimpleSwitchDiscreteProt, SwitchRequest + SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest abstract type AbstractProtocol end @@ -45,6 +45,24 @@ 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 @@ -297,15 +315,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) @@ -313,6 +333,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) @@ -435,6 +456,266 @@ 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 FusionConsumer{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""" + piecemaker::RegRef + """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; kwargs...) + return FusionConsumer(;sim, net, piecemaker, kwargs...) +end +function FusionConsumer(net::RegisterNet, piecemaker::RegRef; kwargs...) + return FusionConsumer(get_time_tracker(net), net, piecemaker; 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 + 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 "FusionConsumer between $(prot.piecemaker): query on piecemaker slot found no entanglement" + #@yield timeout(prot.sim, prot.period) + 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] + + # 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 "FusionConsumer 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 + result = real(observable(client_slots, projector(1/sqrt(2)*(reduce(⊗, [fill(Z2,nclients)...]) + reduce(⊗,[fill(Z1,nclients)...]))))) + @debug "FusionConsumer: expectation value $(result)" + + # 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]...) + for k in 2:nclients+1 + unlock(prot.net[k][1]) + end + unlock(prot.piecemaker) + + # 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 +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 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) # 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) + continue + end + + (q, id, tag) = fusable_qubit.slot, fusable_qubit.id, fusable_qubit.tag + @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, piecemaker.idx, q.idx) + tag!(piecemaker, FusionCounterpart, tag[2], tag[3]) + + @debug "FusionProt @$(prot.node): Entangled .$(q.idx) and .$(piecemaker.idx) @ $(now(prot.sim))" + fuscircuit = EntanglementFusion() + 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, 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(piecemaker) + rounds==-1 || (rounds -= 1) + round += 1 + end +end + +function findfusablequbit(net, node, pred_client) + reg = net[node] + nodes = queryall(reg, EntanglementCounterpart, pred_client, ❓; locked=false) + 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 +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") include("swapping.jl") diff --git a/src/ProtocolZoo/switches.jl b/src/ProtocolZoo/switches.jl index 2b1ac824..b1f58cf8 100644 --- a/src/ProtocolZoo/switches.jl +++ b/src/ProtocolZoo/switches.jl @@ -2,17 +2,17 @@ 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 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 -export SimpleSwitchDiscreteProt, SwitchRequest +export SimpleSwitchDiscreteProt, FusionSwitchDiscreteProt, SwitchRequest """ A wrapper around a matrix, ensuring that it is symmetric. @@ -124,6 +124,75 @@ 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 @@ -284,6 +353,62 @@ 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. +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) + 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. @@ -329,6 +454,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 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