From 067051d511f824ed92c616bc84805dc1b749db51 Mon Sep 17 00:00:00 2001 From: hanakl Date: Fri, 20 Sep 2024 12:35:25 -0500 Subject: [PATCH 01/13] Add tag_waiter --- src/ProtocolZoo/ProtocolZoo.jl | 12 ++++++++++-- src/queries.jl | 2 ++ src/semaphore.jl | 29 +++++++++++++++++++++++++++++ src/states_registers.jl | 3 ++- 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/semaphore.jl diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index dee968ac..294583c3 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -404,13 +404,21 @@ end 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 if isnothing(query1) @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on first node found no entanglement" - @yield timeout(prot.sim, prot.period) + if isnothing(prot.period) + @yield lock(prot.nodeA.tag_waiter) + else + @yield timeout(prot.sim, prot.period) + end continue else query2 = query(prot.net[prot.nodeB], EntanglementCounterpart, 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 @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on second node found no entanglement (yet...)" - @yield timeout(prot.sim, prot.period) + if isnothing(prot.period) + @yield lock(prot.nodeB.tag_waiter) + else + @yield timeout(prot.sim, prot.period) + end continue end end diff --git a/src/queries.jl b/src/queries.jl index 56aee963..3ab544a6 100644 --- a/src/queries.jl +++ b/src/queries.jl @@ -20,6 +20,7 @@ function tag!(ref::RegRef, tag) id = guid() push!(ref.reg.guids, id) ref.reg.tag_info[id] = (;tag, slot=ref.idx, time=now(get_time_tracker(ref))) + unlock(ref.reg.tag_waiter) return id end @@ -41,6 +42,7 @@ function untag!(ref::RegOrRegRef, id::Integer) isnothing(i) ? throw(QueryError("Attempted to delete a nonexistent tag id", untag!, id)) : deleteat!(reg.guids, i) # TODO make sure there is a clear error message to_be_deleted = reg.tag_info[id] delete!(reg.tag_info, id) + unlock(ref.reg.tag_waiter) return to_be_deleted end diff --git a/src/semaphore.jl b/src/semaphore.jl new file mode 100644 index 00000000..19c1d209 --- /dev/null +++ b/src/semaphore.jl @@ -0,0 +1,29 @@ +using ConcurrentSim +using ResumableFunctions +import Base: unlock, lock + +"""Multiple processes can wait on this semaphore for a permission to run given by another process""" +struct AsymmetricSemaphore + nbwaiters::Ref{Int} + lock::Resource +end +AsymmetricSemaphore(sim) = AsymmetricSemaphore(Ref(0), Resource(sim,1,level=1)) # start locked + +function Base.lock(s::AsymmetricSemaphore) + return @process _lock(s.lock.env, s) +end + +@resumable function _lock(sim, s::AsymmetricSemaphore) + s.nbwaiters[] += 1 + @yield lock(s.lock) + s.nbwaiters[] -= 1 + if s.nbwaiters[] > 0 + unlock(s.lock) + end +end + +function unlock(s::AsymmetricSemaphore) + if s.nbwaiters[] > 0 + unlock(s.lock) + end +end \ No newline at end of file diff --git a/src/states_registers.jl b/src/states_registers.jl index ea61fae5..c008c6e1 100644 --- a/src/states_registers.jl +++ b/src/states_registers.jl @@ -23,11 +23,12 @@ struct Register # TODO better type description tag_info::Dict{Int128, @NamedTuple{tag::Tag, slot::Int, time::Float64}} guids::Vector{Int128} netparent::Ref{Any} + tag_waiter::AsymmetricSemaphore end function Register(traits, reprs, bg, sr, si, at) env = ConcurrentSim.Simulation() - Register(traits, reprs, bg, sr, si, at, [ConcurrentSim.Resource(env) for _ in traits], Dict{Int128, Tuple{Tag, Int64, Float64}}(), [], nothing) + Register(traits, reprs, bg, sr, si, at, [ConcurrentSim.Resource(env) for _ in traits], Dict{Int128, Tuple{Tag, Int64, Float64}}(), [], nothing, AsymmetricSemaphore(env)) end Register(traits,reprs,bg,sr,si) = Register(traits,reprs,bg,sr,si,zeros(length(traits))) From b48b23ea4ca857a8f3c4d796a8adc408e58e43c3 Mon Sep 17 00:00:00 2001 From: hanakl Date: Mon, 23 Sep 2024 02:12:18 -0500 Subject: [PATCH 02/13] Add a waiter for stateindices --- src/ProtocolZoo/ProtocolZoo.jl | 16 ++++++---- src/queries.jl | 2 +- src/states_registers.jl | 55 +++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 294583c3..a55867f8 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -208,9 +208,14 @@ end 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 - @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) + if isnothing(prot.retry_lock_time) + @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a_ \n2.\t $b_ \n waiting..." + @yield lock(prot.nodeA.stateindices.waiter) + @yield lock(prot.nodeB.stateindices.waiter) + else + @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) + end continue end # we are now certain that a_ and b_ are not nothing. The compiler is not smart enough to figure this out @@ -397,14 +402,12 @@ function EntanglementConsumer(net::RegisterNet, nodeA::Int, nodeB::Int; kwargs.. end @resumable function (prot::EntanglementConsumer)() - if isnothing(prot.period) - error("In `EntanglementConsumer` we do not yet support waiting on register to make qubits available") # TODO - end 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 if isnothing(query1) @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on first node found no entanglement" if isnothing(prot.period) + @debug "Waiting on changes in $(prot.nodeA)" @yield lock(prot.nodeA.tag_waiter) else @yield timeout(prot.sim, prot.period) @@ -415,6 +418,7 @@ end if isnothing(query2) # in case EntanglementUpdate hasn't reached the second node yet, but the first node has the EntanglementCounterpart @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on second node found no entanglement (yet...)" if isnothing(prot.period) + @debug "Waiting on changes in $(prot.nodeB)" @yield lock(prot.nodeB.tag_waiter) else @yield timeout(prot.sim, prot.period) diff --git a/src/queries.jl b/src/queries.jl index 3ab544a6..87064048 100644 --- a/src/queries.jl +++ b/src/queries.jl @@ -42,7 +42,7 @@ function untag!(ref::RegOrRegRef, id::Integer) isnothing(i) ? throw(QueryError("Attempted to delete a nonexistent tag id", untag!, id)) : deleteat!(reg.guids, i) # TODO make sure there is a clear error message to_be_deleted = reg.tag_info[id] delete!(reg.tag_info, id) - unlock(ref.reg.tag_waiter) + unlock(reg.tag_waiter) return to_be_deleted end diff --git a/src/states_registers.jl b/src/states_registers.jl index c008c6e1..21c0c7bb 100644 --- a/src/states_registers.jl +++ b/src/states_registers.jl @@ -1,5 +1,58 @@ +#using QuantumSavory: AsymmetricSemaphore # TODO better constructors # TODO am I overusing Ref + +using ConcurrentSim +using ResumableFunctions +import Base: unlock, lock +import Base: getindex, setindex! + +"""Multiple processes can wait on this semaphore for a permission to run given by another process""" +struct AsymmetricSemaphore + nbwaiters::Ref{Int} + lock::Resource +end +AsymmetricSemaphore(sim) = AsymmetricSemaphore(Ref(0), Resource(sim,1,level=1)) # start locked + +function Base.lock(s::AsymmetricSemaphore) + return @process _lock(s.lock.env, s) +end + +@resumable function _lock(sim, s::AsymmetricSemaphore) + s.nbwaiters[] += 1 + @yield lock(s.lock) + s.nbwaiters[] -= 1 + if s.nbwaiters[] > 0 + unlock(s.lock) + end +end + +function unlock(s::AsymmetricSemaphore) + if s.nbwaiters[] > 0 + unlock(s.lock) + end +end + +"""Vector with a semaphore where processes can wait on until there's a change in the vector""" +struct StateIndexVector + data::Vector{Int} + waiter::AsymmetricSemaphore +end + +function StateIndexVector(data::Vector{Int}) + env = ConcurrentSim.Simulation() + return StateIndexVector(data, AsymmetricSemaphore(env)) +end + +function getindex(vec::StateIndexVector, index::Int) + return vec.data[index] +end + +function setindex!(vec::StateIndexVector, value::Int, index::Int) + vec.data[index] = value + unlock(vec.waiter) +end + struct StateRef state::Base.RefValue{Any} # TODO it would be nice if this was not abstract but `uptotime!` converts between types... maybe make StateRef{T} state::RefValue{T} and a new function that swaps away the backpointers in the appropriate registers registers::Vector{Any} # TODO Should be Vector{Register}, but right now we occasionally set it to nothing to deal with padded storage @@ -32,7 +85,7 @@ function Register(traits, reprs, bg, sr, si, at) end Register(traits,reprs,bg,sr,si) = Register(traits,reprs,bg,sr,si,zeros(length(traits))) -Register(traits,reprs,bg) = Register(traits,reprs,bg,fill(nothing,length(traits)),zeros(Int,length(traits)),zeros(length(traits))) +Register(traits,reprs,bg) = Register(traits,reprs,bg,fill(nothing,length(traits)),StateIndexVector(zeros(Int,length(traits))),zeros(length(traits))) Register(traits,bg::Base.AbstractVecOrTuple{<:Union{Nothing,<:AbstractBackground}}) = Register(traits,default_repr.(traits),bg) Register(traits,reprs::Base.AbstractVecOrTuple{<:AbstractRepresentation}) = Register(traits,reprs,fill(nothing,length(traits))) Register(traits) = Register(traits,default_repr.(traits)) From c11673f4825669e7aa63f00ed86218fd4ca39ffd Mon Sep 17 00:00:00 2001 From: hanakl Date: Mon, 23 Sep 2024 21:47:26 -0500 Subject: [PATCH 03/13] Fixes on including AsymmetricSemaphore and StateIndexVector base methods --- src/QuantumSavory.jl | 4 +++- src/states_registers.jl | 45 +++++++++++++---------------------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/QuantumSavory.jl b/src/QuantumSavory.jl index aea3770c..89a58b50 100644 --- a/src/QuantumSavory.jl +++ b/src/QuantumSavory.jl @@ -71,7 +71,6 @@ export # plots.jl registernetplot, registernetplot!, registernetplot_axis, resourceplot_axis - #TODO you can not assume you can always in-place modify a state. Have all these functions work on stateref, not stateref[] # basically all ::QuantumOptics... should be turned into ::Ref{...}... but an abstract ref @@ -79,6 +78,8 @@ include("traits_and_defaults.jl") include("tags.jl") +include("semaphore.jl") + include("states_registers.jl") include("quantumchannel.jl") include("messagebuffer.jl") @@ -116,4 +117,5 @@ include("ProtocolZoo/ProtocolZoo.jl") include("precompile.jl") + end # module diff --git a/src/states_registers.jl b/src/states_registers.jl index 21c0c7bb..f059ac7c 100644 --- a/src/states_registers.jl +++ b/src/states_registers.jl @@ -2,39 +2,10 @@ # TODO better constructors # TODO am I overusing Ref -using ConcurrentSim -using ResumableFunctions -import Base: unlock, lock -import Base: getindex, setindex! - -"""Multiple processes can wait on this semaphore for a permission to run given by another process""" -struct AsymmetricSemaphore - nbwaiters::Ref{Int} - lock::Resource -end -AsymmetricSemaphore(sim) = AsymmetricSemaphore(Ref(0), Resource(sim,1,level=1)) # start locked - -function Base.lock(s::AsymmetricSemaphore) - return @process _lock(s.lock.env, s) -end - -@resumable function _lock(sim, s::AsymmetricSemaphore) - s.nbwaiters[] += 1 - @yield lock(s.lock) - s.nbwaiters[] -= 1 - if s.nbwaiters[] > 0 - unlock(s.lock) - end -end - -function unlock(s::AsymmetricSemaphore) - if s.nbwaiters[] > 0 - unlock(s.lock) - end -end +import Base: getindex, setindex!, size, length, eltype """Vector with a semaphore where processes can wait on until there's a change in the vector""" -struct StateIndexVector +struct StateIndexVector <: AbstractVector{Int64} data::Vector{Int} waiter::AsymmetricSemaphore end @@ -53,6 +24,18 @@ function setindex!(vec::StateIndexVector, value::Int, index::Int) unlock(vec.waiter) end +function size(vec::StateIndexVector) + return size(vec.data) +end + +function length(vec::StateIndexVector) + return length(vec.data) +end + +function eltype(::Type{StateIndexVector}) + return Int64 +end + struct StateRef state::Base.RefValue{Any} # TODO it would be nice if this was not abstract but `uptotime!` converts between types... maybe make StateRef{T} state::RefValue{T} and a new function that swaps away the backpointers in the appropriate registers registers::Vector{Any} # TODO Should be Vector{Register}, but right now we occasionally set it to nothing to deal with padded storage From 6388b6fbcde569380fe669e3cf864b402a8f0d22 Mon Sep 17 00:00:00 2001 From: hanakl Date: Wed, 25 Sep 2024 11:48:30 -0500 Subject: [PATCH 04/13] Remove StateIndexVector --- src/ProtocolZoo/ProtocolZoo.jl | 3 +-- src/states_registers.jl | 36 +--------------------------------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index a55867f8..a3436fc5 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -210,8 +210,7 @@ end if isnothing(a_) || isnothing(b_) if isnothing(prot.retry_lock_time) @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a_ \n2.\t $b_ \n waiting..." - @yield lock(prot.nodeA.stateindices.waiter) - @yield lock(prot.nodeB.stateindices.waiter) + @yield lock(prot.nodeA.tag_waiter) | lock(prot.nodeB.tag_waiter) else @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) diff --git a/src/states_registers.jl b/src/states_registers.jl index f059ac7c..889e5666 100644 --- a/src/states_registers.jl +++ b/src/states_registers.jl @@ -2,40 +2,6 @@ # TODO better constructors # TODO am I overusing Ref -import Base: getindex, setindex!, size, length, eltype - -"""Vector with a semaphore where processes can wait on until there's a change in the vector""" -struct StateIndexVector <: AbstractVector{Int64} - data::Vector{Int} - waiter::AsymmetricSemaphore -end - -function StateIndexVector(data::Vector{Int}) - env = ConcurrentSim.Simulation() - return StateIndexVector(data, AsymmetricSemaphore(env)) -end - -function getindex(vec::StateIndexVector, index::Int) - return vec.data[index] -end - -function setindex!(vec::StateIndexVector, value::Int, index::Int) - vec.data[index] = value - unlock(vec.waiter) -end - -function size(vec::StateIndexVector) - return size(vec.data) -end - -function length(vec::StateIndexVector) - return length(vec.data) -end - -function eltype(::Type{StateIndexVector}) - return Int64 -end - struct StateRef state::Base.RefValue{Any} # TODO it would be nice if this was not abstract but `uptotime!` converts between types... maybe make StateRef{T} state::RefValue{T} and a new function that swaps away the backpointers in the appropriate registers registers::Vector{Any} # TODO Should be Vector{Register}, but right now we occasionally set it to nothing to deal with padded storage @@ -68,7 +34,7 @@ function Register(traits, reprs, bg, sr, si, at) end Register(traits,reprs,bg,sr,si) = Register(traits,reprs,bg,sr,si,zeros(length(traits))) -Register(traits,reprs,bg) = Register(traits,reprs,bg,fill(nothing,length(traits)),StateIndexVector(zeros(Int,length(traits))),zeros(length(traits))) +Register(traits,reprs,bg) = Register(traits,reprs,bg,fill(nothing,length(traits)),zeros(Int,length(traits)),zeros(length(traits))) Register(traits,bg::Base.AbstractVecOrTuple{<:Union{Nothing,<:AbstractBackground}}) = Register(traits,default_repr.(traits),bg) Register(traits,reprs::Base.AbstractVecOrTuple{<:AbstractRepresentation}) = Register(traits,reprs,fill(nothing,length(traits))) Register(traits) = Register(traits,default_repr.(traits)) From 66024d3ac9008f8fc00ead5a71aec533c8fe9a30 Mon Sep 17 00:00:00 2001 From: hanakl Date: Wed, 25 Sep 2024 12:48:08 -0500 Subject: [PATCH 05/13] Add tests for AsymmetricSemaphore --- test/runtests.jl | 2 + test/test_semaphore.jl | 149 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 test/test_semaphore.jl diff --git a/test/runtests.jl b/test/runtests.jl index 942565ec..c6b80054 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -46,6 +46,8 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "stateszoo_api" +@doset "semaphore" + if get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" using Pkg Pkg.add("GLMakie") diff --git a/test/test_semaphore.jl b/test/test_semaphore.jl new file mode 100644 index 00000000..874397d3 --- /dev/null +++ b/test/test_semaphore.jl @@ -0,0 +1,149 @@ +using QuantumSavory: AsymmetricSemaphore +using Test +using ConcurrentSim +using ResumableFunctions +import Base: unlock, lock + +@testset "AsymmetricSemaphore functionality" begin + sim = Simulation() + waiter = AsymmetricSemaphore(sim) + log = [] + + @resumable function trigger(sim) + push!(log, (now(sim), "t: start")) + @yield timeout(sim, 10) + push!(log, (now(sim), "t: waited 10")) + unlock(waiter) + push!(log, (now(sim), "t: first trigger")) + @yield timeout(sim, 20) + push!(log, (now(sim), "t: waited 20")) + unlock(waiter) + push!(log, (now(sim), "t: second trigger")) + @yield timeout(sim, 15) + push!(log, (now(sim), "t: waited 15")) + unlock(waiter) + push!(log, (now(sim), "t: third trigger")) + push!(log, (now(sim), "t: end")) + end + + @resumable function proc1(sim) + push!(log, (now(sim), "proc1: start")) + @yield lock(waiter) + push!(log, (now(sim), "proc1: first lock")) + @yield lock(waiter) + push!(log, (now(sim), "proc1: second lock")) + @yield timeout(sim, 10) + push!(log, (now(sim), "proc1: waited 10")) + @yield lock(waiter) + push!(log, (now(sim), "proc1: third lock")) + push!(log, (now(sim), "proc1: end")) + end + + @resumable function proc2(sim) + push!(log, (now(sim), "proc2: start")) + @yield lock(waiter) + push!(log, (now(sim), "proc2: first lock")) + @yield timeout(sim, 25) + push!(log, (now(sim), "proc2: waited 25")) + @yield lock(waiter) + push!(log, (now(sim), "proc2: second lock")) # should be after third trigger + push!(log, (now(sim), "proc2: end")) + end + + @process trigger(sim) + @process proc1(sim) + @process proc2(sim) + + run(sim) + + + expected_log = [ + (0.0, "t: start"), + (0.0, "proc1: start"), + (0.0, "proc2: start"), + (10.0, "t: waited 10"), + (10.0, "t: first trigger"), + (10.0, "proc1: first lock"), + (10.0, "proc2: first lock"), + (30.0, "t: waited 20"), + (30.0, "t: second trigger"), + (30.0, "proc1: second lock"), + (35.0, "proc2: waited 25"), + (40.0, "proc1: waited 10"), + (45.0, "t: waited 15"), + (45.0, "t: third trigger"), + (45.0, "t: end"), + (45.0, "proc2: second lock"), + (45.0, "proc2: end"), + (45.0, "proc1: third lock"), + (45.0, "proc1: end") + ] + + + @test length(log) == length(expected_log) + + for i in 1:length(log) + @test log[i] == expected_log[i] + end + +end + +@testset "Multiple AsymmetricSemaphores" begin + sim = Simulation() + waiter1 = AsymmetricSemaphore(sim) + waiter2 = AsymmetricSemaphore(sim) + log = [] + + @resumable function trigger(sim) + push!(log, (now(sim), "t: start")) + @yield timeout(sim, 10) + push!(log, (now(sim), "t: waited 10")) + unlock(waiter1) + push!(log, (now(sim), "t: unlock first waiter")) + @yield timeout(sim, 5) + push!(log, (now(sim), "t: waited 5")) + unlock(waiter2) + push!(log, (now(sim), "t: unlock second waiter")) + push!(log, (now(sim), "t: end")) + end + + @resumable function proc1(sim) + push!(log, (now(sim), "proc1: start")) + @yield lock(waiter1) | lock(waiter2) + push!(log, (now(sim), "proc1: got a lock")) + push!(log, (now(sim), "proc1: end")) + end + + @resumable function proc2(sim) + push!(log, (now(sim), "proc2: start")) + @yield lock(waiter1) & lock(waiter2) + push!(log, (now(sim), "proc2: got both locks")) + push!(log, (now(sim), "proc2: end")) + end + @process trigger(sim) + @process proc1(sim) + @process proc2(sim) + + run(sim) + + expected_log = [ + (0.0, "t: start"), + (0.0, "proc1: start"), + (0.0, "proc2: start"), + (10.0, "t: waited 10"), + (10.0, "t: unlock first waiter"), + (10.0, "proc1: got a lock"), + (10.0, "proc1: end"), + (15.0, "t: waited 5"), + (15.0, "t: unlock second waiter"), + (15.0, "t: end"), + (15.0, "proc2: got both locks"), + (15.0, "proc2: end") + ] + + @test length(log) == length(expected_log) + + for i in 1:length(log) + @test log[i] == expected_log[i] + end +end From 9e628a59aaed40a527e8e1740ce0fa6b354b8e81 Mon Sep 17 00:00:00 2001 From: hanakl Date: Wed, 25 Sep 2024 16:50:35 -0500 Subject: [PATCH 06/13] Add tests for entanglement_consumer --- src/ProtocolZoo/ProtocolZoo.jl | 11 +++-- .../test_protocolzoo_entanglement_consumer.jl | 48 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index a3436fc5..c381b409 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -210,7 +210,7 @@ end if isnothing(a_) || isnothing(b_) if isnothing(prot.retry_lock_time) @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a_ \n2.\t $b_ \n waiting..." - @yield lock(prot.nodeA.tag_waiter) | lock(prot.nodeB.tag_waiter) + @yield lock(prot.net[prot.nodeA]tag_waiter) | lock(prot.net[prot.nodeB].tag_waiter) else @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) @@ -407,7 +407,7 @@ end @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on first node found no entanglement" if isnothing(prot.period) @debug "Waiting on changes in $(prot.nodeA)" - @yield lock(prot.nodeA.tag_waiter) + @yield lock(prot.net[prot.nodeA].tag_waiter) else @yield timeout(prot.sim, prot.period) end @@ -418,7 +418,7 @@ end @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on second node found no entanglement (yet...)" if isnothing(prot.period) @debug "Waiting on changes in $(prot.nodeB)" - @yield lock(prot.nodeB.tag_waiter) + @yield lock(prot.net[prot.nodeB].tag_waiter) else @yield timeout(prot.sim, prot.period) end @@ -442,7 +442,10 @@ end push!(prot.log, (now(prot.sim), ob1, ob2)) unlock(q1) unlock(q2) - @yield timeout(prot.sim, prot.period) + @yield timeout(prot.sim, 0.1) + if !isnothing(prot.period) + @yield timeout(prot.sim, prot.period) + end end end diff --git a/test/test_protocolzoo_entanglement_consumer.jl b/test/test_protocolzoo_entanglement_consumer.jl index bc3878f4..830011c8 100644 --- a/test/test_protocolzoo_entanglement_consumer.jl +++ b/test/test_protocolzoo_entanglement_consumer.jl @@ -2,6 +2,7 @@ using QuantumSavory using QuantumSavory.ProtocolZoo: EntanglerProt, SwapperProt, EntanglementTracker, EntanglementConsumer using Graphs using ConcurrentSim +using ResumableFunctions using Test if isinteractive() @@ -44,3 +45,50 @@ for n in 3:30 end end + +# test for period=nothing +for n in 3:30 + println(n) + regsize = 10 + net = RegisterNet([Register(regsize) for j in 1:n]) + sim = get_time_tracker(net) + + @resumable function delayedProts(sim) + @yield timeout(sim, 10) + for e in edges(net) + eprot = EntanglerProt(sim, net, e.src, e.dst; rounds=-1, randomize=true, margin=5, hardmargin=3) + @process eprot() + end + + for v in 2:n-1 + sprot = SwapperProt(sim, net, v; nodeL = <(v), nodeH = >(v), chooseL = argmin, chooseH = argmax, rounds = -1) + @process sprot() + end + + for v in vertices(net) + etracker = EntanglementTracker(sim, net, v) + @process etracker() + end + end + econ = EntanglementConsumer(sim, net, 1, n; period=nothing) + @process econ() + @process delayedProts(sim) + + run(sim, 1000) + + @test econ.log[1][1] > 10 #the process should start after 10 + for i in 1:length(econ.log) + @test econ.log[i][2] ≈ 1.0 + @test econ.log[i][3] ≈ 1.0 + end + +end + + + + +@test econ.log[1][1] > 10 #the process should start after 10 +for i in 1:length(econ.log) + @test econ.log[i][2] ≈ 1.0 + @test econ.log[i][3] ≈ 1.0 +end \ No newline at end of file From 06b1f0c57b92fbf91b439708b84d72b686101c6a Mon Sep 17 00:00:00 2001 From: hanakl Date: Thu, 26 Sep 2024 17:48:36 -0500 Subject: [PATCH 07/13] Fix a typo in protocolzoo --- src/ProtocolZoo/ProtocolZoo.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index c381b409..0336bd06 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -210,7 +210,7 @@ end if isnothing(a_) || isnothing(b_) if isnothing(prot.retry_lock_time) @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a_ \n2.\t $b_ \n waiting..." - @yield lock(prot.net[prot.nodeA]tag_waiter) | lock(prot.net[prot.nodeB].tag_waiter) + @yield lock(prot.net[prot.nodeA].tag_waiter) | lock(prot.net[prot.nodeB].tag_waiter) else @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) @@ -455,4 +455,4 @@ include("swapping.jl") include("switches.jl") using .Switches -end # module +end # module \ No newline at end of file From 708a12c9067eedb43173c6fb87e93cd2f8ae5bf8 Mon Sep 17 00:00:00 2001 From: hanakl Date: Thu, 26 Sep 2024 17:52:37 -0500 Subject: [PATCH 08/13] Fix a typo --- src/ProtocolZoo/ProtocolZoo.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 0336bd06..d597eb11 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -442,7 +442,6 @@ end push!(prot.log, (now(prot.sim), ob1, ob2)) unlock(q1) unlock(q2) - @yield timeout(prot.sim, 0.1) if !isnothing(prot.period) @yield timeout(prot.sim, prot.period) end From 68af5bd98bac62c4c166bbe0f9327204b59feb4f Mon Sep 17 00:00:00 2001 From: hanakl Date: Thu, 26 Sep 2024 18:50:31 -0500 Subject: [PATCH 09/13] Implement waiting logic to swapping and cutoff protocols --- src/ProtocolZoo/cutoff.jl | 7 +++---- src/ProtocolZoo/swapping.jl | 7 +++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ProtocolZoo/cutoff.jl b/src/ProtocolZoo/cutoff.jl index da17356c..4d399475 100644 --- a/src/ProtocolZoo/cutoff.jl +++ b/src/ProtocolZoo/cutoff.jl @@ -33,9 +33,6 @@ function CutoffProt(sim::Simulation, net::RegisterNet, node::Int; kwargs...) end @resumable function (prot::CutoffProt)() - if isnothing(prot.period) - error("In `CutoffProt` we do not yet support quing up and waiting on register") # TODO - end reg = prot.net[prot.node] while true for slot in reg # TODO these should be done in parallel, otherwise we will be waiting on each slot, greatly slowing down the cutoffs @@ -72,6 +69,8 @@ end unlock(slot) end - @yield timeout(prot.sim, prot.period) + if !isnothing(prot.period) + @yield timeout(prot.sim, prot.period) + end end end diff --git a/src/ProtocolZoo/swapping.jl b/src/ProtocolZoo/swapping.jl index e7330604..64d46256 100644 --- a/src/ProtocolZoo/swapping.jl +++ b/src/ProtocolZoo/swapping.jl @@ -68,8 +68,11 @@ end while rounds != 0 qubit_pair_ = findswapablequbits(prot.net, prot.node, prot.nodeL, prot.nodeH, prot.chooseL, prot.chooseH; agelimit=prot.agelimit) if isnothing(qubit_pair_) - 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) + if isnothing(prot.retry_lock_time) + @yield lock(prot.net[prot.node].tag_waiter) + else + @yield timeout(prot.sim, prot.retry_lock_time) + end continue end # The compiler is not smart enough to figure out that qubit_pair_ is not nothing, so we need to tell it explicitly. A new variable name is needed due to @resumable. From ed372e6026a9319e355bbf5fddc2ce210f1d0672 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 23 Oct 2024 19:12:46 -0400 Subject: [PATCH 10/13] first stage of debugging - ensuring we have the same discrete event simulation object for all locks check for comments marked as "Step x" in swapping.jl and queries.jl --- debug_example.jl | 54 ++++++++++++++++++++++++++++++++++ src/ProtocolZoo/ProtocolZoo.jl | 8 ++--- src/ProtocolZoo/swapping.jl | 8 ++++- src/concurrentsim.jl | 2 ++ src/networks.jl | 1 + src/queries.jl | 4 +-- src/semaphore.jl | 5 +++- src/states_registers.jl | 4 +-- 8 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 debug_example.jl diff --git a/debug_example.jl b/debug_example.jl new file mode 100644 index 00000000..d10230d7 --- /dev/null +++ b/debug_example.jl @@ -0,0 +1,54 @@ + +using Revise +using QuantumSavory +using ResumableFunctions +using ConcurrentSim +using QuantumSavory.ProtocolZoo +using QuantumSavory.ProtocolZoo: EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ +using Graphs +using Test +using Random +using Logging + +## + +peekalltags(node) = vcat([QuantumSavory.peektags(node[i]) for i in 1:length(node)]...) # I use this inside some library code by accessing it through the `Main` module -- it is a neat hack for interactive debugging + +## + +#global_logger(ConsoleLogger(stderr, Logging.Debug)) +global_logger(ConsoleLogger(stderr, Logging.Info)) + +## + +n = 3 +regsize = 10 +net = RegisterNet([Register(regsize) for j in 1:n]) +sim = get_time_tracker(net) + +for e in edges(net) + eprot = EntanglerProt(sim, net, e.src, e.dst; success_prob=1.0, rounds=1, randomize=true, margin=5, hardmargin=3) + @process eprot() +end + +for v in 2:n-1 + sprot = SwapperProt(sim, net, v; nodeL = <(v), nodeH = >(v), chooseL = argmin, chooseH = argmax, rounds =1, retry_lock_time=nothing) + @process sprot() +end + +for v in vertices(net) + etracker = EntanglementTracker(sim, net, v) + @process etracker() +end + +econ = EntanglementConsumer(sim, net, 1, n; period=0.1) +@process econ() + +run(sim, 100) + +econ.log + +## + +using CairoMakie +registernetplot(net) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index d597eb11..7898c66f 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -210,7 +210,7 @@ end if isnothing(a_) || isnothing(b_) if isnothing(prot.retry_lock_time) @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a_ \n2.\t $b_ \n waiting..." - @yield lock(prot.net[prot.nodeA].tag_waiter) | lock(prot.net[prot.nodeB].tag_waiter) + @yield lock(prot.net[prot.nodeA].tag_waiter[]) | lock(prot.net[prot.nodeB].tag_waiter[]) # TODO instead of lock(...) it should be a neater API like `onchange_tag(prot.net[prot.nodeB])` or some similar new public function else @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) @@ -407,7 +407,7 @@ end @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on first node found no entanglement" if isnothing(prot.period) @debug "Waiting on changes in $(prot.nodeA)" - @yield lock(prot.net[prot.nodeA].tag_waiter) + @yield lock(prot.net[prot.nodeA].tag_waiter[]) else @yield timeout(prot.sim, prot.period) end @@ -418,7 +418,7 @@ end @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on second node found no entanglement (yet...)" if isnothing(prot.period) @debug "Waiting on changes in $(prot.nodeB)" - @yield lock(prot.net[prot.nodeB].tag_waiter) + @yield lock(prot.net[prot.nodeB].tag_waiter[]) else @yield timeout(prot.sim, prot.period) end @@ -454,4 +454,4 @@ include("swapping.jl") include("switches.jl") using .Switches -end # module \ No newline at end of file +end # module diff --git a/src/ProtocolZoo/swapping.jl b/src/ProtocolZoo/swapping.jl index 64d46256..bc3dba56 100644 --- a/src/ProtocolZoo/swapping.jl +++ b/src/ProtocolZoo/swapping.jl @@ -67,9 +67,15 @@ end round = 1 while rounds != 0 qubit_pair_ = findswapablequbits(prot.net, prot.node, prot.nodeL, prot.nodeH, prot.chooseL, prot.chooseH; agelimit=prot.agelimit) + println("===") + println(get_time_tracker(prot.net)===prot.net[prot.node].tag_waiter[].lock.env) # during debugging I actually used the line below, but after my changes that line does not work anymore + # println(get_time_tracker(prot.net)===prot.net[prot.node].tag_waiter.lock.env) # Step 3: This should be true, but it is false... If the lock is never reached, it must be because the corresponding simulation environment is not running... but why would there be multiple independent sim environments + println(Main.peekalltags(prot.net[prot.node])) if isnothing(qubit_pair_) if isnothing(prot.retry_lock_time) - @yield lock(prot.net[prot.node].tag_waiter) + println("locked") + @yield lock(prot.net[prot.node].tag_waiter[]) + println("proceeding") # Step 1: this is not reached else @yield timeout(prot.sim, prot.retry_lock_time) end diff --git a/src/concurrentsim.jl b/src/concurrentsim.jl index ed96b13f..a7bb375e 100644 --- a/src/concurrentsim.jl +++ b/src/concurrentsim.jl @@ -39,9 +39,11 @@ end ## function get_time_tracker(rn::RegisterNet) + # TODO assert they are all the same return get_time_tracker(rn.registers[1]) end function get_time_tracker(r::Register) + # TODO assert all locks and tag_waiters and similar have the same env r.locks[1].env::Simulation end function get_time_tracker(r::RegRef) diff --git a/src/networks.jl b/src/networks.jl index a10dc369..b587d648 100644 --- a/src/networks.jl +++ b/src/networks.jl @@ -21,6 +21,7 @@ function RegisterNet(graph::SimpleGraph, registers, vertex_metadata, edge_metada if !all_are_same if all_are_at_zero for r in registers + r.tag_waiter[] = AsymmetricSemaphore(env) for i in eachindex(r.locks) r.locks[i] = ConcurrentSim.Resource(env,1) end diff --git a/src/queries.jl b/src/queries.jl index 87064048..362fd5b1 100644 --- a/src/queries.jl +++ b/src/queries.jl @@ -20,7 +20,7 @@ function tag!(ref::RegRef, tag) id = guid() push!(ref.reg.guids, id) ref.reg.tag_info[id] = (;tag, slot=ref.idx, time=now(get_time_tracker(ref))) - unlock(ref.reg.tag_waiter) + unlock(ref.reg.tag_waiter[]) return id end @@ -42,7 +42,7 @@ function untag!(ref::RegOrRegRef, id::Integer) isnothing(i) ? throw(QueryError("Attempted to delete a nonexistent tag id", untag!, id)) : deleteat!(reg.guids, i) # TODO make sure there is a clear error message to_be_deleted = reg.tag_info[id] delete!(reg.tag_info, id) - unlock(reg.tag_waiter) + unlock(reg.tag_waiter[]) return to_be_deleted end diff --git a/src/semaphore.jl b/src/semaphore.jl index 19c1d209..e4f19142 100644 --- a/src/semaphore.jl +++ b/src/semaphore.jl @@ -10,10 +10,12 @@ end AsymmetricSemaphore(sim) = AsymmetricSemaphore(Ref(0), Resource(sim,1,level=1)) # start locked function Base.lock(s::AsymmetricSemaphore) + println("calling the lock") return @process _lock(s.lock.env, s) end @resumable function _lock(sim, s::AsymmetricSemaphore) + println("S locking") # Step 2: This is not reached s.nbwaiters[] += 1 @yield lock(s.lock) s.nbwaiters[] -= 1 @@ -23,7 +25,8 @@ end end function unlock(s::AsymmetricSemaphore) + println("S unlocking") if s.nbwaiters[] > 0 unlock(s.lock) end -end \ No newline at end of file +end diff --git a/src/states_registers.jl b/src/states_registers.jl index 889e5666..aed41211 100644 --- a/src/states_registers.jl +++ b/src/states_registers.jl @@ -25,12 +25,12 @@ struct Register # TODO better type description tag_info::Dict{Int128, @NamedTuple{tag::Tag, slot::Int, time::Float64}} guids::Vector{Int128} netparent::Ref{Any} - tag_waiter::AsymmetricSemaphore + tag_waiter::Ref{AsymmetricSemaphore} # TODO This being a ref is a bit of code smell... but we also want to be able to have registers that are not linked to a net so we need to be able to have this field un-initialized end function Register(traits, reprs, bg, sr, si, at) env = ConcurrentSim.Simulation() - Register(traits, reprs, bg, sr, si, at, [ConcurrentSim.Resource(env) for _ in traits], Dict{Int128, Tuple{Tag, Int64, Float64}}(), [], nothing, AsymmetricSemaphore(env)) + Register(traits, reprs, bg, sr, si, at, [ConcurrentSim.Resource(env) for _ in traits], Dict{Int128, Tuple{Tag, Int64, Float64}}(), [], nothing, Ref(AsymmetricSemaphore(env))) end Register(traits,reprs,bg,sr,si) = Register(traits,reprs,bg,sr,si,zeros(length(traits))) From badb9a6347dbfb7e1864cac8428a5e478e661c42 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 23 Oct 2024 19:58:54 -0400 Subject: [PATCH 11/13] some notes after further checks --- src/ProtocolZoo/swapping.jl | 8 +++----- src/semaphore.jl | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ProtocolZoo/swapping.jl b/src/ProtocolZoo/swapping.jl index bc3dba56..49672eac 100644 --- a/src/ProtocolZoo/swapping.jl +++ b/src/ProtocolZoo/swapping.jl @@ -67,15 +67,13 @@ end round = 1 while rounds != 0 qubit_pair_ = findswapablequbits(prot.net, prot.node, prot.nodeL, prot.nodeH, prot.chooseL, prot.chooseH; agelimit=prot.agelimit) - println("===") - println(get_time_tracker(prot.net)===prot.net[prot.node].tag_waiter[].lock.env) # during debugging I actually used the line below, but after my changes that line does not work anymore - # println(get_time_tracker(prot.net)===prot.net[prot.node].tag_waiter.lock.env) # Step 3: This should be true, but it is false... If the lock is never reached, it must be because the corresponding simulation environment is not running... but why would there be multiple independent sim environments + println("===", qubit_pair_) println(Main.peekalltags(prot.net[prot.node])) if isnothing(qubit_pair_) if isnothing(prot.retry_lock_time) - println("locked") + println("IN ", now(get_time_tracker(prot.net))) @yield lock(prot.net[prot.node].tag_waiter[]) - println("proceeding") # Step 1: this is not reached + println("OUT ", now(get_time_tracker(prot.net))) # TODO why is this released instantly the second time... is there a problem with entering the AsymmetricSemaphore a second time? else @yield timeout(prot.sim, prot.retry_lock_time) end diff --git a/src/semaphore.jl b/src/semaphore.jl index e4f19142..136694b6 100644 --- a/src/semaphore.jl +++ b/src/semaphore.jl @@ -15,7 +15,7 @@ function Base.lock(s::AsymmetricSemaphore) end @resumable function _lock(sim, s::AsymmetricSemaphore) - println("S locking") # Step 2: This is not reached + println("S locking") s.nbwaiters[] += 1 @yield lock(s.lock) s.nbwaiters[] -= 1 From ac025b511f5bbc761ac88397ed07c1845f7734ec Mon Sep 17 00:00:00 2001 From: hanakl Date: Tue, 29 Oct 2024 23:06:54 -0500 Subject: [PATCH 12/13] Remove debug statements --- src/ProtocolZoo/swapping.jl | 5 ++--- test/test_protocolzoo_entanglement_consumer.jl | 18 ++++-------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/ProtocolZoo/swapping.jl b/src/ProtocolZoo/swapping.jl index 49672eac..247783a0 100644 --- a/src/ProtocolZoo/swapping.jl +++ b/src/ProtocolZoo/swapping.jl @@ -71,9 +71,8 @@ end println(Main.peekalltags(prot.net[prot.node])) if isnothing(qubit_pair_) if isnothing(prot.retry_lock_time) - println("IN ", now(get_time_tracker(prot.net))) - @yield lock(prot.net[prot.node].tag_waiter[]) - println("OUT ", now(get_time_tracker(prot.net))) # TODO why is this released instantly the second time... is there a problem with entering the AsymmetricSemaphore a second time? + @debug "SwapperProt: no swappable qubits found. waiting..." + @yield onchange_tag(prot.net[prot.node]) else @yield timeout(prot.sim, prot.retry_lock_time) end diff --git a/test/test_protocolzoo_entanglement_consumer.jl b/test/test_protocolzoo_entanglement_consumer.jl index 830011c8..9dc99fed 100644 --- a/test/test_protocolzoo_entanglement_consumer.jl +++ b/test/test_protocolzoo_entanglement_consumer.jl @@ -33,7 +33,7 @@ for n in 3:30 @process etracker() end - econ = EntanglementConsumer(sim, net, 1, n; period=1.0) + econ = EntanglementConsumer(sim, net, 1, n; period=0.1) @process econ() run(sim, 100) @@ -48,13 +48,12 @@ end # test for period=nothing for n in 3:30 - println(n) regsize = 10 net = RegisterNet([Register(regsize) for j in 1:n]) sim = get_time_tracker(net) @resumable function delayedProts(sim) - @yield timeout(sim, 10) + @yield timeout(sim, 5) for e in edges(net) eprot = EntanglerProt(sim, net, e.src, e.dst; rounds=-1, randomize=true, margin=5, hardmargin=3) @process eprot() @@ -74,21 +73,12 @@ for n in 3:30 @process econ() @process delayedProts(sim) - run(sim, 1000) + run(sim, 100) - @test econ.log[1][1] > 10 #the process should start after 10 + @test econ.log[1][1] > 5 # the process should start after 5 for i in 1:length(econ.log) @test econ.log[i][2] ≈ 1.0 @test econ.log[i][3] ≈ 1.0 end -end - - - - -@test econ.log[1][1] > 10 #the process should start after 10 -for i in 1:length(econ.log) - @test econ.log[i][2] ≈ 1.0 - @test econ.log[i][3] ≈ 1.0 end \ No newline at end of file From ecfddb6f311e3a6405e51a14c7d4391b87cc399a Mon Sep 17 00:00:00 2001 From: hanakl Date: Tue, 29 Oct 2024 08:55:02 -0500 Subject: [PATCH 13/13] Implement ontag_change --- src/ProtocolZoo/ProtocolZoo.jl | 8 ++++---- src/QuantumSavory.jl | 1 + src/states_registers.jl | 5 +++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 7898c66f..fbd8fb10 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -1,7 +1,7 @@ module ProtocolZoo using QuantumSavory -import QuantumSavory: get_time_tracker, Tag, isolderthan +import QuantumSavory: get_time_tracker, Tag, isolderthan, onchange_tag using QuantumSavory: Wildcard using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap @@ -210,7 +210,7 @@ end if isnothing(a_) || isnothing(b_) if isnothing(prot.retry_lock_time) @debug "EntanglerProt between $(prot.nodeA) and $(prot.nodeB)|round $(round): Failed to find free slots. \nGot:\n1. \t $a_ \n2.\t $b_ \n waiting..." - @yield lock(prot.net[prot.nodeA].tag_waiter[]) | lock(prot.net[prot.nodeB].tag_waiter[]) # TODO instead of lock(...) it should be a neater API like `onchange_tag(prot.net[prot.nodeB])` or some similar new public function + @yield onchange_tag(prot.net[prot.nodeA]) | onchange_tag(prot.net[prot.nodeB]) else @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) @@ -407,7 +407,7 @@ end @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on first node found no entanglement" if isnothing(prot.period) @debug "Waiting on changes in $(prot.nodeA)" - @yield lock(prot.net[prot.nodeA].tag_waiter[]) + @yield onchange_tag(prot.net[prot.nodeA]) else @yield timeout(prot.sim, prot.period) end @@ -418,7 +418,7 @@ end @debug "EntanglementConsumer between $(prot.nodeA) and $(prot.nodeB): query on second node found no entanglement (yet...)" if isnothing(prot.period) @debug "Waiting on changes in $(prot.nodeB)" - @yield lock(prot.net[prot.nodeB].tag_waiter[]) + @yield onchange_tag(prot.net[prot.nodeB]) else @yield timeout(prot.sim, prot.period) end diff --git a/src/QuantumSavory.jl b/src/QuantumSavory.jl index 89a58b50..5579ca6b 100644 --- a/src/QuantumSavory.jl +++ b/src/QuantumSavory.jl @@ -48,6 +48,7 @@ export CliffordRepr, QuantumOpticsRepr, QuantumMCRepr, UseAsState, UseAsObservable, UseAsOperation, AbstractBackground, + onchange_tag, # networks.jl RegisterNet, channel, qchannel, messagebuffer, # initialize.jl diff --git a/src/states_registers.jl b/src/states_registers.jl index aed41211..d11252c1 100644 --- a/src/states_registers.jl +++ b/src/states_registers.jl @@ -63,3 +63,8 @@ get_register(r::RegRef) = r.reg get_register(r::Register) = r #Base.:(==)(r1::Register, r2::Register) = + +function onchange_tag(r::RegOrRegRef) + register = get_register(r) + return lock(register.tag_waiter[]) +end \ No newline at end of file