Skip to content

MBQC Purification #185

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions examples/purificationMBQC/MBQC.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using ResumableFunctions
using ConcurrentSim
using Revise

using QuantumSavory
using QuantumSavory.ProtocolZoo
import QuantumSavory: Tag

using GLMakie
using GeoMakie
GLMakie.activate!()

using Logging
global_logger(ConsoleLogger(stderr, Logging.Debug))

const perfect_pair = (Z1⊗Z1 + Z2⊗Z2) / sqrt(2)
const perfect_pair_dm = SProjector(perfect_pair)
const mixed_dm = MixedState(perfect_pair_dm)
noisy_pair_func(F) = F*perfect_pair_dm + (1-F)*mixed_dm

F = 0.9

noisy_pair = noisy_pair_func(F)

@kwdef struct MBQCSetUp
node::Int
end
Base.show(io::IO, tag::MBQCSetUp) = print(io, "Set up is completed on $(tag.node)")
Tag(tag::MBQCSetUp) = Tag(MBQCSetUp, tag.node)


@kwdef struct MBQCMeasurement
node::Int
measurement::Int
end
Base.show(io::IO, tag::MBQCMeasurement) = print(io, "Measurement for register $(tag.node) is $(tag.measurement).")
Tag(tag::MBQCMeasurement) = Tag(MBQCMeasurement, tag.node, tag.measurement)

@kwdef struct PurifiedEntalgementCounterpart
remote_node::Int
remote_slot::Int
end
Base.show(io::IO, tag::PurifiedEntalgementCounterpart) = print(io, "Entangled to $(tag.remote_node).$(tag.remote_slot)")
Tag(tag::PurifiedEntalgementCounterpart) = Tag(PurifiedEntalgementCounterpart, tag.remote_node, tag.remote_slot)

@resumable function MBQC_tracker(sim, net, node)
nodereg = net[node]
mb = messagebuffer(net, node)
while true
local_tag = query(nodereg, MBQCMeasurement, node, ❓)

if isnothing(local_tag)
@yield onchange_tag(net[node])
continue
end

msg = query(mb, MBQCMeasurement, ❓, ❓)
if isnothing(msg)
@debug "Starting message wait at $(now(sim)) with MessageBuffer containing: $(mb.buffer)"
@yield wait(mb)
@debug "Done waiting for message at $(node)"
continue
end

msg = querydelete!(mb, MBQCMeasurement, ❓, ❓)
local_measurement = local_tag.tag.data[3] # it would be better if it can be local_tag.tag.measurement
src, (_, src_node, src_measurement) = msg

if src_measurement == local_measurement
@debug "Purification was successful"
tag!(local_tag.slot, PurifiedEntalgementCounterpart, src_node, 4)

else
@debug "Purification failed."
untag!(local_tag.slot, local_tag.id)
end
end
end

@resumable function MBQC_setup(sim, net, node, duration=0.1, period=0.1)
while true
query_setup = query(net[node], MBQCSetUp, node)
if !isnothing(query_setup)
if isnothing(period)
@yield onchange_tag(net[node])
else
@yield timeout(sim, period)
end
continue
end
@debug "Setup starting at node $(node)."
initialize!(net[node, 3], X1)
initialize!(net[node, 4], X1)
apply!((net[node, 3], net[node, 4]), CPHASE)
@yield timeout(sim, duration)
tag!(net[node][4], MBQCSetUp, node)
@debug "Setup done at node $(node)."
end
end
@resumable function entangler(sim, net; pairstate=noisy_pair, period=0.1)
while true
# entangle 1 to 1 and 2 to 2
entangler1 = EntanglerProt(sim, net, 1, 2; pairstate=pairstate, chooseA=1, chooseB=1, rounds=1)
entangler2 = EntanglerProt(sim, net, 1, 2; pairstate=pairstate, chooseA=2, chooseB=2, rounds=1)
@process entangler1()
query1 = query(net[1], EntanglementCounterpart, 2, 1)
if !isnothing(query1)
@process entangler2()
end
@yield timeout(sim, period)
end
end

@resumable function MBQC_purify(sim, net, node, duration=0.1, period=0.1)
while true
query1 = queryall(net[node], EntanglementCounterpart, ❓, ❓; locked=false, assigned=true)
query2 = query(net[node], MBQCSetUp, node)
if length(query1) < 2 || isnothing(query2)
if isnothing(period)
@yield onchange_tag(net[node])
else
@yield timeout(sim, period)
end
continue
end
println(query1)
@debug "Purification starting at node $(node)."

apply!((net[node, 3], net[node, 1]), CPHASE)
apply!((net[node, 3], net[node, 2]), CPHASE)

m1 = project_traceout!(net[node, 1], X)
m2 = project_traceout!(net[node, 3], X)

if m1 == 2
apply!(net[node, 4], Z)
apply!(net[node, 2], Z)
end
if m2 == 2
apply!(net[node, 4], X)
end
untag!(query1[1].slot, query1[1].id)
untag!(query1[2].slot, query1[2].id)
m = project_traceout!(net[node, 2], X)
tag!(net[node][4], MBQCMeasurement, node, m)

if node == 1
other = 2
else
other = 1
end
@debug "Purification done at node $(node)."
put!(channel(net, node=>other), Tag(MBQCMeasurement, node, m))
@yield timeout(sim, duration)
end
end




regL = Register(4)
regR = Register(4)
net = RegisterNet([regL, regR])
sim = get_time_tracker(net)

@process entangler(sim, net)
@process MBQC_tracker(sim, net, 1)
@process MBQC_tracker(sim, net, 2)



@process MBQC_setup(sim, net, 1)
@process MBQC_setup(sim, net, 2)

@process MBQC_purify(sim, net, 1)
@process MBQC_purify(sim, net, 2)

purified_consumer = EntanglementConsumer(sim, net, 1, 2; period=3, tag=PurifiedEntalgementCounterpart)
@process purified_consumer()

run(sim, 2)

observable([net[1], net[2]], [1, 1], projector(perfect_pair))
observable([net[1], net[2]], [2, 2], projector(perfect_pair))

# has not been consumed yet
observable([net[1], net[2]], [4, 4], projector(perfect_pair))


run(sim, 4)

# should be consumed and return nothing
observable([net[1], net[2]], [4, 4], projector(perfect_pair))

26 changes: 18 additions & 8 deletions src/ProtocolZoo/ProtocolZoo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ $TYPEDFIELDS
rounds::Int = -1
"""maximum number of attempts to make per round (`-1` for infinite)"""
attempts::Int = -1
"""function or index of the slot to choose an available free slot in node A"""
chooseA::Union{Int,<:Function} = minimum
"""function or index of the slot to choose an available free slot in node B"""
chooseB::Union{Int,<:Function} = minimum
"""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."""
Expand All @@ -204,8 +208,10 @@ end
while rounds != 0
isentangled = !isnothing(query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, ❓; 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_ = findfreeslot(prot.net[prot.nodeA]; filter=prot.chooseA, randomize=prot.randomize, margin=margin)
b_ = findfreeslot(prot.net[prot.nodeB]; filter=prot.chooseB, randomize=prot.randomize, margin=margin)
#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_)
if isnothing(prot.retry_lock_time)
Expand Down Expand Up @@ -291,7 +297,6 @@ end
@debug "EntanglementTracker @$(prot.node): Received from $(msg.src).$(msg.tag[3]) | message=`$(msg.tag)` | time=$(now(prot.sim))"
workwasdone = true
localslot = nodereg[localslotid]

# Check if the local slot is still present and believed to be entangled.
# We will need to perform a correction operation due to the swap or a deletion due to the qubit being thrown out,
# but there will be no message forwarding necessary.
Expand All @@ -314,8 +319,10 @@ end
if correction==2
apply!(localslot, updategate)
end
# tag local with updated EntanglementCounterpart new_remote_node new_remote_slot_idx
tag!(localslot, EntanglementCounterpart, newremotenode, newremoteslotid)
if newremotenode != -1 #TODO: this is a bit hacky, also add tracking for GHZ state
# tag local with updated EntanglementCounterpart new_remote_node new_remote_slot_idx
tag!(localslot, EntanglementCounterpart, newremotenode, newremoteslotid)
end
else # EntanglementDelete
traceout!(localslot)
end
Expand Down Expand Up @@ -389,6 +396,8 @@ $FIELDS
nodeB::Int
"""time period between successive queries on the nodes (`nothing` for queuing up and waiting for available pairs)"""
period::LT = 0.1
"""tag for which the consumer is consuming"""
tag::Any = EntanglementCounterpart
"""stores the time and resulting observable from querying nodeA and nodeB for `EntanglementCounterpart`"""
log::Vector{Tuple{Float64, Float64, Float64}} = Tuple{Float64, Float64, Float64}[]
end
Expand All @@ -402,7 +411,7 @@ end

@resumable function (prot::EntanglementConsumer)()
while true
query1 = query(prot.net[prot.nodeA], EntanglementCounterpart, prot.nodeB, ❓; locked=false, assigned=true) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below
query1 = query(prot.net[prot.nodeA], prot.tag, prot.nodeB, ❓; locked=false, assigned=true) # TODO Need a `querydelete!` dispatch on `Register` rather than using `query` here followed by `untag!` below
if isnothing(query1)
if isnothing(prot.period)
@debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on first node found no entanglement. Waiting on tag updates in $(prot.nodeA)."
Expand All @@ -413,7 +422,7 @@ end
end
continue
else
query2 = query(prot.net[prot.nodeB], EntanglementCounterpart, prot.nodeA, query1.slot.idx; locked=false, assigned=true)
query2 = query(prot.net[prot.nodeB], prot.tag, prot.nodeA, query1.slot.idx; locked=false, assigned=true)
if isnothing(query2) # in case EntanglementUpdate hasn't reached the second node yet, but the first node has the EntanglementCounterpart
if isnothing(prot.period)
@debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on second node found no entanglement (yet...). Waiting on tag updates in $(prot.nodeB)."
Expand All @@ -438,6 +447,7 @@ end
ob1 = real(observable((q1, q2), Z⊗Z))
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))
unlock(q1)
Expand All @@ -454,4 +464,4 @@ include("swapping.jl")
include("switches.jl")
using .Switches

end # module
end # module
30 changes: 23 additions & 7 deletions src/queries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,30 @@ julia> findfreeslot(reg) |> isnothing
true
```
"""
function findfreeslot(reg::Register; randomize=false, margin=0)
function findfreeslot(reg::Register; filter=minimum::Union{Int,<:Function}, randomize=false, margin=0)
n_slots = length(reg.staterefs)
freeslots = sum((!isassigned(reg[i]) for i in 1:n_slots))
if freeslots >= margin
perm = randomize ? randperm : (x->1:x)
for i in perm(n_slots)
slot = reg[i]
islocked(slot) || isassigned(slot) || return slot
n_freeslots = sum((!isassigned(reg[i]) for i in 1:n_slots))
if n_freeslots < margin
return nothing
end
freeslots = [i for i in 1:n_slots if !islocked(reg[i]) && !isassigned(reg[i])]
if isempty(freeslots)
return nothing
end
if filter isa Int
return filter in freeslots ? reg[filter] : nothing
else
filtered_slots = filter(freeslots)
if filtered_slots === nothing || isempty(filtered_slots)
return nothing
end
if isa(filtered_slots, Integer)
filtered_slots = [filtered_slots]
end
if randomize
return reg[rand(filtered_slots)]
else
return reg[filtered_slots[1]]
end
end
end
Expand Down
18 changes: 18 additions & 0 deletions test/test_protocolzoo_entangler.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@testitem "ProtocolZoo Entangler - EntanglerProt" tags=[:protocolzoo_entangler] begin
using Revise
using QuantumSavory.ProtocolZoo: EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer
using ConcurrentSim
using ResumableFunctions

net = RegisterNet([Register(5), Register(5)])
sim = get_time_tracker(net)
eprot1 = EntanglerProt(sim, net, 1, 2; chooseA=1, chooseB=5, rounds=1, success_prob=1.)
eprot2 = EntanglerProt(sim, net, 1, 2; chooseA=3, chooseB=3, rounds=1, success_prob=1.)
@process eprot1()
@process eprot2()

run(sim, 3)
@test observable([net[1], net[2]], [1, 5], projector((Z1⊗Z1 + Z2⊗Z2) / sqrt(2))) ≈ 1.0
@test observable([net[1], net[2]], [3, 3], projector((Z1⊗Z1 + Z2⊗Z2) / sqrt(2))) ≈ 1.0

end
11 changes: 11 additions & 0 deletions test/test_tags_and_queries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ id3 = tag!(reg[4], :symB, 4, 5)
@test_throws "Attempted to delete a nonexistent" untag!(reg, -1)
end

##
# findfreeslot tests
reg = Register(5)
initialize!(reg[1], X)
lock(reg[3])

@test findfreeslot(reg).idx == 2
@test findfreeslot(reg, filter=maximum).idx == 5
@test findfreeslot(reg, filter=1) == nothing
@test findfreeslot(reg, filter=2).idx == 2

f()
#using BenchmarkTools
#@benchmark f()
Expand Down
Loading