diff --git a/.github/workflows/benchmark-comment.yml b/.github/workflows/benchmark-comment.yml index 15b203d2..66a25078 100644 --- a/.github/workflows/benchmark-comment.yml +++ b/.github/workflows/benchmark-comment.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 # restore records from the artifacts - - uses: dawidd6/action-download-artifact@v2 + - uses: dawidd6/action-download-artifact@v3 with: workflow: benchmark.yml name: performance-tracking diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index fe57d565..aa1040df 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,7 +48,7 @@ jobs: run: echo ${{ github.event.pull_request.number }} > ./pull-request-number.artifact # save as artifacts (performance tracking (comment) workflow will use it) - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: performance-tracking path: ./*.artifact diff --git a/LICENSE b/LICENSE index a845b418..126e4348 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Abhishek Bhatt +Copyright (c) 2023 Stefan Krastanov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Project.toml b/Project.toml index f552dc98..4eca0f6a 100644 --- a/Project.toml +++ b/Project.toml @@ -20,6 +20,7 @@ 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" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" @@ -48,6 +49,7 @@ QuantumInterface = "0.3.3" QuantumOptics = "1.0.5" QuantumOpticsBase = "0.4.17" QuantumSymbolics = "0.2.5" +Random = "1.9" Reexport = "1.2.2" ResumableFunctions = "0.6.1" Statistics = "1" diff --git a/README.md b/README.md index f7afc768..f219a6ef 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Continuous integration GitHub Workflow Status - Buildkite Workflow Status + Buildkite Workflow Status diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 08e6bfef..09466cde 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -26,7 +26,7 @@ Process(prot::AbstractProtocol, args...; kwargs...) = Process((e,a...;k...)->pro remote_node::Int remote_slot::Int end -Base.show(io::IO, tag::EntanglementCounterpart) = print(io, "Entangled to $(tag.remote_node)|$(tag.remote_slot)") +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) @kwdef struct EntanglementHistory @@ -36,7 +36,7 @@ Tag(tag::EntanglementCounterpart) = Tag(EntanglementCounterpart, tag.remote_node swap_remote_slot::Int swapped_local::Int end -Base.show(io::IO, tag::EntanglementHistory) = print(io, "Was entangled to $(tag.remote_node)|$(tag.remote_slot), but swapped with |$(tag.swapped_local) which was entangled to $(tag.swap_remote_node)|$(tag.swap_remote_slot)") +Base.show(io::IO, tag::EntanglementHistory) = print(io, "Was entangled to $(tag.remote_node).$(tag.remote_slot), but swapped with .$(tag.swapped_local) which was entangled to $(tag.swap_remote_node).$(tag.swap_remote_slot)") Tag(tag::EntanglementHistory) = Tag(EntanglementHistory, tag.remote_node, tag.remote_slot, tag.swap_remote_node, tag.swap_remote_slot, tag.swapped_local) @kwdef struct EntanglementUpdateX @@ -47,7 +47,7 @@ Tag(tag::EntanglementHistory) = Tag(EntanglementHistory, tag.remote_node, tag.re new_remote_slot::Int correction::Int end -Base.show(io::IO, tag::EntanglementUpdateX) = print(io, "Update slot |$(tag.past_remote_slot) which used to be entangled to $(tag.past_local_node)|$(tag.past_local_slot) to be entangled to $(tag.new_remote_node)|$(tag.new_remote_slot) and apply correction X$(tag.correction)") +Base.show(io::IO, tag::EntanglementUpdateX) = print(io, "Update slot .$(tag.past_remote_slot) which used to be entangled to $(tag.past_local_node).$(tag.past_local_slot) to be entangled to $(tag.new_remote_node).$(tag.new_remote_slot) and apply correction X$(tag.correction)") Tag(tag::EntanglementUpdateX) = Tag(EntanglementUpdateX, tag.past_local_node, tag.past_local_slot, tag.past_remote_slot, tag.new_remote_node, tag.new_remote_slot, tag.correction) @kwdef struct EntanglementUpdateZ @@ -58,7 +58,7 @@ Tag(tag::EntanglementUpdateX) = Tag(EntanglementUpdateX, tag.past_local_node, ta new_remote_slot::Int correction::Int end -Base.show(io::IO, tag::EntanglementUpdateZ) = print(io, "Update slot |$(tag.past_remote_slot) which used to be entangled to $(tag.past_local_node)|$(tag.past_local_slot) to be entangled to $(tag.new_remote_node)|$(tag.new_remote_slot) and apply correction Z$(tag.correction)") +Base.show(io::IO, tag::EntanglementUpdateZ) = print(io, "Update slot .$(tag.past_remote_slot) which used to be entangled to $(tag.past_local_node).$(tag.past_local_slot) to be entangled to $(tag.new_remote_node).$(tag.new_remote_slot) and apply correction Z$(tag.correction)") Tag(tag::EntanglementUpdateZ) = Tag(EntanglementUpdateZ, tag.past_local_node, tag.past_local_slot, tag.past_remote_slot, tag.new_remote_node, tag.new_remote_slot, tag.correction) """ @@ -93,6 +93,8 @@ $FIELDS retry_lock_time::LT = 0.1 """how many rounds of this protocol to run (`-1` for infinite))""" rounds::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 end """Convenience constructor for specifying `rate` of generation instead of success probability and time""" @@ -110,10 +112,10 @@ end prot = _prot # weird workaround for no support for `struct A a::Int end; @resumable function (fa::A) return fa.a end`; see https://github.com/JuliaDynamics/ResumableFunctions.jl/issues/77 rounds = prot.rounds while rounds != 0 - a = findfreeslot(prot.net[prot.nodeA]) - b = findfreeslot(prot.net[prot.nodeB]) + a = findfreeslot(prot.net[prot.nodeA], randomize=prot.randomize) + b = findfreeslot(prot.net[prot.nodeB], randomize=prot.randomize) if isnothing(a) || isnothing(b) - isnothing(prot.retry_lock_time) && error("we do not yet support waiting on register to make qubits available") # TODO + 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 @@ -175,7 +177,7 @@ end reg = prot.net[prot.node] qubit_pair = findswapablequbits(prot.net,prot.node) if isnothing(qubit_pair) - isnothing(prot.retry_lock_time) && error("we do not yet support waiting on register to make qubits available") # TODO + 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 @@ -198,12 +200,12 @@ end # tag with EntanglementUpdateX past_local_node, past_local_slot_idx past_remote_slot_idx new_remote_node, new_remote_slot, correction msg1 = Tag(EntanglementUpdateX, prot.node, q1.idx, tag1[3], tag2[2], tag2[3], xmeas) put!(channel(prot.net, prot.node=>tag1[2]; permit_forward=true), msg1) - @debug "swapper @$(prot.node) send msg to $(tag1[2]): $msg1" + @debug "SwapperProt @$(prot.node): Send message to $(tag1[2]) | message=`$msg1`" # send from here to new entanglement counterpart: # tag with EntanglementUpdateZ past_local_node, past_local_slot_idx past_remote_slot_idx new_remote_node, new_remote_slot, correction msg2 = Tag(EntanglementUpdateZ, prot.node, q2.idx, tag2[3], tag1[2], tag1[3], zmeas) put!(channel(prot.net, prot.node=>tag2[2]; permit_forward=true), msg2) - @debug "swapper @$(prot.node) send msg to $(tag2[2]): $msg2" + @debug "SwapperProt @$(prot.node): Send message to $(tag2[2]) | message=`$msg2`" unlock(q1) unlock(q2) rounds==-1 || (rounds -= 1) @@ -249,20 +251,23 @@ end workwasdone = false for (updatetagsymbol, updategate) in ((EntanglementUpdateX, X), (EntanglementUpdateZ, Z)) # look for EntanglementUpdate? past_remote_slot_idx local_slot_idx, new_remote_node, new_remote_slot_idx correction - msg = querypop!(mb, updatetagsymbol, ❓, ❓, ❓, ❓, ❓, ❓) + msg = querydelete!(mb, updatetagsymbol, ❓, ❓, ❓, ❓, ❓, ❓) isnothing(msg) && continue - @debug "tracker @$(prot.node) received from $(msg.src) msg: $(msg.tag)" + @debug "EntanglementTracker @$(prot.node): Received from $(msg.src).$(msg.tag[3]) | message=`$(msg.tag)`" workwasdone = true (src, (_, pastremotenode, pastremoteslotid, localslotid, newremotenode, newremoteslotid, correction)) = msg 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, # but there will be no message forwarding necessary. - counterpart = querypop!(localslot, EntanglementCounterpart, pastremotenode, pastremoteslotid) + counterpart = querydelete!(localslot, EntanglementCounterpart, pastremotenode, pastremoteslotid) if !isnothing(counterpart) - @debug "tracker @$(prot.node) counterpart requesting lock at $(now(prot.sim))" + time_before_lock = now(prot.sim) + @debug "EntanglementTracker @$(prot.node): EntanglementCounterpart requesting lock at $(now(prot.sim))" @yield lock(localslot) - @debug "tracker @$(prot.node) counterpart getting lock at $(now(prot.sim))" + @debug "EntanglementTracker @$(prot.node): EntanglementCounterpart getting lock at $(now(prot.sim))" + 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.") @@ -275,14 +280,16 @@ end end # If not, check if we have a record of the entanglement being swapped to a different remote node, # and forward the message to that node. - history = querypop!(localslot, EntanglementHistory, + history = querydelete!(localslot, EntanglementHistory, pastremotenode, pastremoteslotid, # who we were entangled to (node, slot) ❓, ❓, # who we swapped with (node, slot) ❓) # which local slot used to be entangled with whom we swapped with if !isnothing(history) - @debug "tracker @$(prot.node) history: $(history) | msg: $msg" + # @debug "tracker @$(prot.node) history: $(history) | msg: $msg" _, _, _, whoweswappedwith_node, whoweswappedwith_slotidx, swappedlocal_slotidx = history - msghist = Tag(updatetagsymbol, pastremotenode, swappedlocal_slotidx, whoweswappedwith_slotidx, newremotenode, newremoteslotid, correction) + tag!(localslot, EntanglementHistory, newremotenode, newremoteslotid, whoweswappedwith_node, whoweswappedwith_slotidx, swappedlocal_slotidx) + @debug "EntanglementTracker @$(prot.node): history=`$(history)` | message=`$msg` | Sending to $(whoweswappedwith_node).$(whoweswappedwith_slotidx)" + msghist = Tag(updatetagsymbol, pastremotenode, pastremoteslotid, whoweswappedwith_slotidx, newremotenode, newremoteslotid, correction) put!(channel(prot.net, prot.node=>whoweswappedwith_node; permit_forward=true), msghist) #println(" history sends to $whoweswappedwith_node: ", msghist) continue @@ -290,9 +297,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` tags). This is a bug in the protocol and should not happen -- please report an issue at QuantumSavory's repository.") end end - #println("tracker @$(prot.node) starting message wait at ", now(prot.sim), " with mb: ", mb.buffer) + @debug "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)" @yield wait(mb) - #println("tracker @$(prot.node) message wait ends at ", now(prot.sim)) + @debug "EntanglementTracker @$(prot.node): Message wait ends at $(now(prot.sim))" end end diff --git a/src/QuantumSavory.jl b/src/QuantumSavory.jl index 05c7d1c0..8b315916 100644 --- a/src/QuantumSavory.jl +++ b/src/QuantumSavory.jl @@ -4,6 +4,7 @@ using Reexport using IterTools using LinearAlgebra +using Random: randperm using Graphs import ConcurrentSim using ConcurrentSim: Environment, Simulation, Store, DelayQueue, Resource, @@ -47,7 +48,7 @@ export # uptotime.jl uptotime!, overwritetime!, # tags.jl and queries.jl - Tag, tag!, untag!, W, ❓, query, queryall, querypop!, findfreeslot, + Tag, tag!, untag!, W, ❓, query, queryall, querydelete!, findfreeslot, # quantumchannel.jl QuantumChannel, # backgrounds.jl diff --git a/src/messagebuffer.jl b/src/messagebuffer.jl index 99c614d7..f1651cb1 100644 --- a/src/messagebuffer.jl +++ b/src/messagebuffer.jl @@ -15,6 +15,7 @@ end function Base.put!(cf::ChannelForwarder, tag::Tag) # shortest path calculated by Graphs.a_star nexthop = first(a_star(cf.net.graph, cf.src, cf.dst)) + @debug "ChannelForwarder: Forwarding message from node $(nexthop.src) to node $(nexthop.dst) | message=$(tag)| end destination=$(cf.dst)" put!(channel(cf.net, cf.src=>nexthop.dst; permit_forward=false), tag_types.Forward(tag, cf.dst)) end @@ -23,10 +24,13 @@ end tag = @yield take!(ch) @cases tag begin Forward(innertag, enddestination) => begin # inefficient -- it recalculates the a_star at each hop TODO provide some caching mechanism + @debug "MessageBuffer @$(mb.node) at t=$(now(mb.sim)): Forwarding message to node $(enddestination) | message=`$(tag)`" put!(channel(mb.net, mb.node=>enddestination; permit_forward=true), innertag) end _ => begin #println("from $src storing in mb: $tag at $(now(sim))") + @debug "MessageBuffer @$(mb.node) at t=$(now(mb.sim)): Receiving from source $(src) | message=`$(tag)`" + length(mb.waiters) == 0 && @debug "MessageBuffer @$(mb.node) received a message, but there is no one waiting on that message buffer. The message was `$(tag)`." push!(mb.buffer, (;src,tag)); for waiter in keys(mb.waiters) unlock(waiter) @@ -47,6 +51,7 @@ function MessageBuffer(net, node::Int, qs::Vector{NamedTuple{(:src,:channel), Tu end @resumable function wait_process(sim, mb::MessageBuffer) + length(mb.buffer) != 0 && return waitresource = Resource(sim) lock(waitresource) mb.waiters[waitresource] = waitresource diff --git a/src/queries.jl b/src/queries.jl index f39fa1d5..f8bcb424 100644 --- a/src/queries.jl +++ b/src/queries.jl @@ -212,12 +212,12 @@ t=1.0: query returns nothing t=3.0: query returns SymbolIntInt(:second_tag, 123, 456)::Tag received from node 3 ``` """ -function querypop!(mb::MessageBuffer, args...) +function querydelete!(mb::MessageBuffer, args...) r = query(mb, args...) return isnothing(r) ? nothing : popat!(mb.buffer, r.depth) end -function querypop!(ref::RegRef, args...) # TODO there is a lot of code duplication here +function querydelete!(ref::RegRef, args...) # TODO there is a lot of code duplication here r = query(ref, args...) return isnothing(r) ? nothing : popat!(ref.reg.tags[ref.idx], r.depth) end @@ -317,7 +317,13 @@ julia> findfreeslot(reg) |> isnothing true ``` """ -function findfreeslot(reg::Register) +function findfreeslot(reg::Register; randomize=false) + if randomize + for i in randperm(length(reg.staterefs)) + slot = reg[i] + islocked(slot) || isassigned(slot) || return slot + end + end for slot in reg islocked(slot) || isassigned(slot) || return slot end diff --git a/test/runtests.jl b/test/runtests.jl index 6bb8ab1a..02dce471 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,6 +31,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "noninstant_and_backgrounds_qumode" @doset "messagebuffer" @doset "tags_and_queries" +@doset "entanglement_tracker" @doset "circuitzoo_api" @doset "circuitzoo_purification" diff --git a/test/test_entanglement_tracker.jl b/test/test_entanglement_tracker.jl index dbfb746b..7434ef50 100644 --- a/test/test_entanglement_tracker.jl +++ b/test/test_entanglement_tracker.jl @@ -7,9 +7,9 @@ using QuantumSavory.ProtocolZoo: EntanglementCounterpart, EntanglementHistory, E using Graphs using Test -using Logging -logger = ConsoleLogger(Logging.Debug; meta_formatter=(args...)->(:black,"","")) -global_logger(logger) +# using Logging +# logger = ConsoleLogger(Logging.Debug; meta_formatter=(args...)->(:black,"","")) +# global_logger(logger) ## @@ -71,30 +71,33 @@ using Graphs using Test using Random -using Logging -logger = ConsoleLogger(Logging.Debug; meta_formatter=(args...)->(:black,"","")) -global_logger(logger) +# using Logging +# logger = ConsoleLogger(Logging.Debug; meta_formatter=(args...)->(:black,"","")) +# global_logger(logger) # same but this time with an entanglement tracker -n = 5 # works at <5 -for i in 1:1000 - println("new =======================\n\n") - net = RegisterNet([Register(i+3) for i in 1:n]) + +for i in 1:2 + n = 6 + net = RegisterNet([Register(j+3) for j in 1:n]) sim = get_time_tracker(net) - for i in vertices(net) - tracker = EntanglementTracker(sim, net, i) + for j in vertices(net) + tracker = EntanglementTracker(sim, net, j) @process tracker() end for e in edges(net) - eprot = EntanglerProt(sim, net, e.src, e.dst; rounds=1) + eprot = EntanglerProt(sim, net, e.src, e.dst; rounds=1, randomize=true) @process eprot() end - for i in 2:n-1 - swapper = SwapperProt(sim, net, i; rounds=1) + for j in 2:n-1 + swapper = SwapperProt(sim, net, j; rounds=1) @process swapper() end run(sim, 200) - @test net[1].tags[1] == [Tag(EntanglementCounterpart, n, 1)] - @test net[n].tags[1] == [Tag(EntanglementCounterpart, 1, 1)] + @test query(net[1], EntanglementCounterpart, n, ❓).tag[2] == n + @test query(net[n], EntanglementCounterpart, 1, ❓).tag[2] == 1 + + # @test net[1].tags[1] == [Tag(EntanglementCounterpart, n, 1)] + # @test net[n].tags[1] == [Tag(EntanglementCounterpart, 1, 1)] #@test observable((net[1][1], net[n][1]), Z⊗Z) ≈ 1 end diff --git a/test/test_messagebuffer.jl b/test/test_messagebuffer.jl index 34bcfe26..5bc04f1c 100644 --- a/test/test_messagebuffer.jl +++ b/test/test_messagebuffer.jl @@ -9,8 +9,7 @@ env = get_time_tracker(net); while true mb = messagebuffer(net, 2) @yield wait(mb) - msg = querypop!(mb, :second_tag, ❓, ❓) - print("t=$(now(env)): query returns ") + msg = querydelete!(mb, :second_tag, ❓, ❓) if isnothing(msg) #println("nothing") else diff --git a/test/test_tags_and_queries.jl b/test/test_tags_and_queries.jl index 18ea8b33..0d323ba4 100644 --- a/test/test_tags_and_queries.jl +++ b/test/test_tags_and_queries.jl @@ -30,5 +30,5 @@ tag!(r[5], Int, 4, 5) @test query(r[2], :symbol1, 4, ❓) == (depth=1, tag=Tag(:symbol1, 4, 5)) @test queryall(r[2], :symbol1, 4, ❓) == [(depth=1, tag=Tag(:symbol1, 4, 5))] -@test querypop!(r[2], :symbol1, 4, ❓) == Tag(:symbol1, 4, 5) -@test querypop!(r[2], :symbol1, 4, ❓) === nothing +@test querydelete!(r[2], :symbol1, 4, ❓) == Tag(:symbol1, 4, 5) +@test querydelete!(r[2], :symbol1, 4, ❓) === nothing