diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e56f7bac..6bb96b6c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,6 +13,7 @@ jobs:
matrix:
version:
- '1'
+ - '1.9'
os:
- ubuntu-latest
threads:
diff --git a/Project.toml b/Project.toml
index 0734b0dc..86a9e721 100644
--- a/Project.toml
+++ b/Project.toml
@@ -6,6 +6,7 @@ version = "0.3.2"
[deps]
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499"
+Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
@@ -19,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"
@@ -33,21 +35,23 @@ QuantumSavoryMakie = "Makie"
[compat]
Combinatorics = "1"
ConcurrentSim = "1.4"
+Distributions = "0.25"
DocStringExtensions = "0.9"
Graphs = "1.7.3"
IterTools = "1.4.0"
-LinearAlgebra = "1.9"
+LinearAlgebra = "1"
Makie = "0.19, 0.20"
NetworkLayout = "0.4.4"
PrecompileTools = "1"
-Printf = "1.9"
+Printf = "1"
QuantumClifford = "0.8"
QuantumInterface = "0.3.3"
QuantumOptics = "1.0.5"
QuantumOpticsBase = "0.4.17"
QuantumSymbolics = "0.2.5"
+Random = "1"
Reexport = "1.2.2"
ResumableFunctions = "0.6.1"
Statistics = "1"
-SumTypes = "0.5"
+SumTypes = "0.5.1"
julia = "1.9"
diff --git a/README.md b/README.md
index dc6e064b..f219a6ef 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@
Static analysis with |
-
+
|
diff --git a/examples/firstgenrepeater_v2/1_entangler_example.jl b/examples/firstgenrepeater_v2/1_entangler_example.jl
new file mode 100644
index 00000000..b58cac7d
--- /dev/null
+++ b/examples/firstgenrepeater_v2/1_entangler_example.jl
@@ -0,0 +1,38 @@
+include("setup.jl")
+
+using GLMakie # For plotting
+GLMakie.activate!()
+
+##
+# Demo the entangler on its own
+##
+
+sizes = [2,3,4,3,2] # Number of qubits in each register
+T2 = 10.0 # T2 dephasing time of all qubits
+#TODO F = 0.9 # Fidelity of the raw Bell pairs
+
+sim, network = simulation_setup(sizes, T2)
+
+##
+
+for (;src, dst) in edges(network)
+ eprot = EntanglerProt(sim, network, src, dst)
+ @process eprot()
+end
+
+##
+
+# set up a plot and save a handle to the plot observable
+fig = Figure(resolution=(400,400))
+_,ax,_,obs = registernetplot_axis(fig[1,1],network)
+display(fig)
+
+##
+
+# record the simulation progress
+step_ts = range(0, 4, step=0.1)
+record(fig, "firstgenrepeater_v2-01.entangler.mp4", step_ts, framerate=10, visible=true) do t
+ run(sim, t)
+ notify(obs)
+ ax.title = "t=$(t)"
+end
diff --git a/examples/firstgenrepeater_v2/2_swapper_example.jl b/examples/firstgenrepeater_v2/2_swapper_example.jl
new file mode 100644
index 00000000..4425f53f
--- /dev/null
+++ b/examples/firstgenrepeater_v2/2_swapper_example.jl
@@ -0,0 +1,42 @@
+include("setup.jl")
+
+using GLMakie # For plotting
+GLMakie.activate!()
+
+##
+# Demo the entangler on its own
+##
+
+sizes = [2,3,4,3,2] # Number of qubits in each register
+T2 = 10.0 # T2 dephasing time of all qubits
+#TODO F = 0.9 # Fidelity of the raw Bell pairs
+
+sim, network = simulation_setup(sizes, T2)
+
+##
+
+for (;src, dst) in edges(network)
+ eprot = EntanglerProt(sim, network, src, dst)
+ @process eprot()
+end
+for node in vertices(network)
+ sprot = SwapperProt(sim, network, node; nodeL = <(node), nodeR = >(node))
+ @process sprot()
+end
+
+##
+
+# set up a plot and save a handle to the plot observable
+fig = Figure(resolution=(400,400))
+_,ax,_,obs = registernetplot_axis(fig[1,1],network)
+display(fig)
+
+##
+
+# record the simulation progress
+step_ts = range(0, 30, step=0.1)
+record(fig, "firstgenrepeater-03.swapper.mp4", step_ts, framerate=10, visible=true) do t
+ run(sim, t)
+ notify(obs)
+ ax.title = "t=$(t)"
+end
diff --git a/examples/firstgenrepeater_v2/setup.jl b/examples/firstgenrepeater_v2/setup.jl
new file mode 100644
index 00000000..edc69ae1
--- /dev/null
+++ b/examples/firstgenrepeater_v2/setup.jl
@@ -0,0 +1,127 @@
+# For convenient graph data structures
+using Graphs
+
+# For discrete event simulation
+using ResumableFunctions
+using ConcurrentSim
+
+# Useful for interactive work
+# Enables automatic re-compilation of modified codes
+using Revise
+
+# The workhorse for the simulation
+using QuantumSavory
+
+# Predefined useful circuits
+using QuantumSavory.CircuitZoo: EntanglementSwap, Purify2to1
+using QuantumSavory.ProtocolZoo: EntanglerProt, SwapperProt
+
+##
+# Create a handful of qubit registers in a chain
+##
+
+"""Creates the datastructures representing the simulated network"""
+function simulation_setup(
+ sizes, # Array giving the number of qubits in each node
+ T2 # T2 dephasing times for the qubits
+ ;
+ representation = QuantumOpticsRepr # Representation to use for the qubits
+ )
+ R = length(sizes) # Number of registers
+
+ # All of the quantum register we will be simulating
+ registers = Register[]
+ for s in sizes
+ traits = [Qubit() for _ in 1:s]
+ repr = [representation() for _ in 1:s]
+ bg = [T2Dephasing(T2) for _ in 1:s]
+ push!(registers, Register(traits,repr,bg))
+ end
+
+ # A graph structure defining the connectivity among registers
+ # It is not necessary to use such a structure, however, it is a convenient way to
+ # store data about the simulation (and we have created helper plotting functions
+ # expecting such a structure).
+ graph = grid([R])
+ network = RegisterNet(graph, registers) # A graphs with extra "meta data"
+
+ # The scheduler datastructure for the discrete event simulation
+ sim = get_time_tracker(network)
+
+ sim, network
+end
+
+##
+# The Entangler
+##
+
+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 # TODO make a depolarization helper
+const XX = X⊗X
+const ZZ = Z⊗Z
+const YY = Y⊗Y
+
+##
+# The Swapper
+##
+
+
+##
+# The Purifier
+##
+
+@resumable function purifier(
+ sim::Environment, # The scheduler for all simulation events
+ network, # The graph of quantum nodes
+ nodea, # One of the nodes on which the pairs to be purified rest
+ nodeb, # The other such node
+ purifier_wait_time,# The wait time in case there are no pairs available for purification
+ purifier_busy_time # The duration of the purification circuit
+ )
+ round = 0
+ while true
+ pairs_of_bellpairs = findqubitstopurify(network,nodea,nodeb)
+ if isnothing(pairs_of_bellpairs)
+ @yield timeout(sim, purifier_wait_time)
+ continue
+ end
+ pair1qa, pair1qb, pair2qa, pair2qb = pairs_of_bellpairs
+ locks = [network[nodea][[pair1qa,pair2qa]];
+ network[nodeb][[pair1qb,pair2qb]]]
+ @yield mapreduce(request, &, locks)
+ @yield timeout(sim, purifier_busy_time)
+ rega = network[nodea]
+ regb = network[nodeb]
+ purifyerror = (:X, :Z)[round%2+1]
+ purificationcircuit = Purify2to1(purifyerror)
+ success = purificationcircuit(rega[pair1qa],regb[pair1qb],rega[pair2qa],regb[pair2qb])
+ if !success
+ network[nodea,:enttrackers][pair1qa] = nothing
+ network[nodeb,:enttrackers][pair1qb] = nothing
+ @simlog sim "failed purification at $(nodea):$(pair1qa)&$(pair2qa) and $(nodeb):$(pair1qb)&$(pair2qb)"
+ else
+ round += 1
+ @simlog sim "purification at $(nodea):$(pair1qa) $(nodeb):$(pair1qb) by sacrifice of $(nodea):$(pair1qa) $(nodeb):$(pair1qb)"
+ end
+ network[nodea,:enttrackers][pair2qa] = nothing
+ network[nodeb,:enttrackers][pair2qb] = nothing
+ release.(locks)
+ end
+end
+
+function findqubitstopurify(network,nodea,nodeb)
+ enttrackers = network[nodea,:enttrackers]
+ rega = network[nodea]
+ regb = network[nodeb]
+ enttrackers = [(i=i,n...) for (i,n) in enumerate(enttrackers)
+ if !isnothing(n) && n.node==nodeb && !islocked(rega[i]) && !islocked(regb[n.slot])]
+ if length(enttrackers)>=2
+ aqubits = [n.i for n in enttrackers[end-1:end]]
+ bqubits = [n.slot for n in enttrackers[end-1:end]]
+ return aqubits[2], bqubits[2], aqubits[1], bqubits[1]
+ else
+ return nothing
+ end
+end
diff --git a/src/CircuitZoo/CircuitZoo.jl b/src/CircuitZoo/CircuitZoo.jl
index 198bd85b..2ac374fd 100644
--- a/src/CircuitZoo/CircuitZoo.jl
+++ b/src/CircuitZoo/CircuitZoo.jl
@@ -3,7 +3,8 @@ module CircuitZoo
using QuantumSavory
using DocStringExtensions
-export EntanglementSwap, Purify2to1, Purify2to1Node, Purify3to1, Purify3to1Node,
+export EntanglementSwap, LocalEntanglementSwap,
+ Purify2to1, Purify2to1Node, Purify3to1, Purify3to1Node,
PurifyStringent, PurifyStringentNode, PurifyExpedient, PurifyExpedientNode,
SDDecode, SDEncode
@@ -33,6 +34,18 @@ end
inputqubits(::EntanglementSwap) = 4
+struct LocalEntanglementSwap <: AbstractCircuit
+end
+
+function (::LocalEntanglementSwap)(localL, lacalR)
+ apply!((localL, lacalR), CNOT)
+ xmeas = project_traceout!(localL, σˣ)
+ zmeas = project_traceout!(lacalR, σᶻ)
+ xmeas, zmeas
+end
+
+inputqubits(::LocalEntanglementSwap) = 2
+
"""
$TYPEDEF
diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl
new file mode 100644
index 00000000..1226a017
--- /dev/null
+++ b/src/ProtocolZoo/ProtocolZoo.jl
@@ -0,0 +1,308 @@
+module ProtocolZoo
+
+using QuantumSavory
+import QuantumSavory: get_time_tracker, Tag
+using QuantumSavory: Wildcard
+using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap
+
+using DocStringExtensions
+
+using Distributions: Geometric
+using ConcurrentSim: Simulation, @yield, timeout, @process, now
+import ConcurrentSim: Process
+import ResumableFunctions
+using ResumableFunctions: @resumable
+import SumTypes
+
+export EntanglerProt, SwapperProt, EntanglementTracker
+
+abstract type AbstractProtocol end
+
+get_time_tracker(prot::AbstractProtocol) = prot.sim
+
+Process(prot::AbstractProtocol, args...; kwargs...) = Process((e,a...;k...)->prot(a...;k...,_prot=prot), get_time_tracker(prot), args...; kwargs...)
+
+@kwdef struct EntanglementCounterpart
+ remote_node::Int
+ remote_slot::Int
+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)
+
+@kwdef struct EntanglementHistory
+ remote_node::Int
+ remote_slot::Int
+ swap_remote_node::Int
+ 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)")
+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
+ past_local_node::Int
+ past_local_slot::Int
+ past_remote_slot::Int
+ new_remote_node::Int
+ 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 Z$(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
+ past_local_node::Int
+ past_local_slot::Int
+ past_remote_slot::Int
+ new_remote_node::Int
+ 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 X$(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)
+
+"""
+$TYPEDEF
+
+A protocol that generates entanglement between two nodes.
+Whenever a pair of empty slots is available, the protocol locks them
+and starts probabilistic attempts to establish entanglement.
+
+$FIELDS
+"""
+@kwdef struct EntanglerProt{LT} <: AbstractProtocol where {LT<:Union{Float64,Nothing}}
+ """time-and-schedule-tracking instance from `ConcurrentSim`"""
+ sim::Simulation # TODO check that
+ """a network graph of registers"""
+ net::RegisterNet
+ """the vertex index of node A"""
+ nodeA::Int
+ """the vertex index of node B"""
+ nodeB::Int
+ """the state being generated (supports symbolic, numeric, noisy, and pure)"""
+ pairstate = StabilizerState("ZZ XX")
+ """success probability of one attempt of entanglement generation"""
+ success_prob::Float64 = 0.001
+ """duration of single entanglement attempt"""
+ attempt_time::Float64 = 0.001
+ """fixed "busy time" duration immediately before starting entanglement generation attempts"""
+ local_busy_time_pre::Float64 = 0.0
+ """fixed "busy time" duration immediately after the a successful entanglement generation attempt"""
+ local_busy_time_post::Float64 = 0.0
+ """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up)"""
+ retry_lock_time::LT = 0.1
+ """how many rounds of this protocol to run (`-1` for infinite))"""
+ rounds::Int = -1
+ """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"""
+function EntanglerProt(sim::Simulation, net::RegisterNet, nodeA::Int, nodeB::Int; rate::Union{Nothing,Float64}=nothing, kwargs...)
+ if isnothing(rate)
+ return EntanglerProt(;sim, net, nodeA, nodeB, kwargs...)
+ else
+ return EntanglerProt(;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::EntanglerProt)(;_prot::EntanglerProt=prot)
+ 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], 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
+ @yield timeout(prot.sim, prot.retry_lock_time)
+ continue
+ end
+
+ @yield lock(a) & lock(b) # this yield is expected to return immediately
+
+ @yield timeout(prot.sim, prot.local_busy_time_pre)
+ @yield timeout(prot.sim, (rand(Geometric(prot.success_prob))+1) * prot.attempt_time)
+ initialize!((a,b), prot.pairstate; time=now(prot.sim))
+ @yield timeout(prot.sim, prot.local_busy_time_post)
+
+ # tag local node a with EntanglementCounterpart remote_node_idx_b remote_slot_idx_b
+ tag!(a, EntanglementCounterpart, prot.nodeB, b.idx)
+ # tag local node b with EntanglementCounterpart remote_node_idx_a remote_slot_idx_a
+ tag!(b, EntanglementCounterpart, prot.nodeA, a.idx)
+
+ unlock(a)
+ unlock(b)
+ rounds==-1 || (rounds -= 1)
+ end
+end
+
+
+"""
+$TYPEDEF
+
+A protocol, running at a given node, that finds swappable entangled pairs and performs the swap.
+
+$FIELDS
+"""
+@kwdef struct SwapperProt{L,R,LT} <: AbstractProtocol where {L<:Union{Int,<:Function,Wildcard}, R<:Union{Int,<:Function,Wildcard}, 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 swapping is happening"""
+ node::Int
+ """the vertex of one of the remote nodes (or a predicate function or a wildcard)"""
+ nodeL::L = ❓
+ """the vertex of the other remote node (or a predicate function or a wildcard)"""
+ nodeR::R = ❓
+ """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 SwapperProt(sim::Simulation, net::RegisterNet, node::Int; kwargs...)
+ return SwapperProt(;sim, net, node, kwargs...)
+end
+
+@resumable function (prot::SwapperProt)(;_prot::SwapperProt=prot)
+ 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
+ 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
+ @yield timeout(prot.sim, prot.retry_lock_time)
+ continue
+ end
+ (q1, tag1), (q2, tag2) = qubit_pair
+ @yield lock(q1) & lock(q2) # this should not really need a yield thanks to `findswapablequbits`, but it is better to be defensive
+ @yield timeout(prot.sim, prot.local_busy_time)
+
+ untag!(q1, tag1)
+ # store a history of whom we were entangled to: remote_node_idx, remote_slot_idx, remote_swapnode_idx, remote_swapslot_idx, local_swap_idx
+ tag!(q1, EntanglementHistory, tag1[2], tag1[3], tag2[2], tag2[3], q2.idx)
+
+ untag!(q2, tag2)
+ # store a history of whom we were entangled to: remote_node_idx, remote_slot_idx, remote_swapnode_idx, remote_swapslot_idx, local_swap_idx
+ tag!(q2, EntanglementHistory, tag2[2], tag2[3], tag1[2], tag1[3], q1.idx)
+
+ uptotime!((q1, q2), now(prot.sim))
+ swapcircuit = LocalEntanglementSwap()
+ xmeas, zmeas = swapcircuit(q1, q2)
+ # send from here to new entanglement counterpart:
+ # 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 "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 "SwapperProt @$(prot.node): Send message to $(tag2[2]) | message=`$msg2`"
+ unlock(q1)
+ unlock(q2)
+ rounds==-1 || (rounds -= 1)
+ end
+end
+
+function findswapablequbits(net,node) # TODO parameterize the query predicates and the findmin/findmax
+ reg = net[node]
+
+ leftnodes = queryall(reg, EntanglementCounterpart, <(node), ❓; locked=false, assigned=true)
+ rightnodes = queryall(reg, EntanglementCounterpart, >(node), ❓; locked=false, assigned=true)
+
+ (isempty(leftnodes) || isempty(rightnodes)) && return nothing
+ _, il = findmin(n->n.tag[2], leftnodes) # TODO make [2] into a nice named property
+ _, ir = findmax(n->n.tag[2], rightnodes)
+ return leftnodes[il], rightnodes[ir]
+end
+
+
+"""
+$TYPEDEF
+
+A protocol, running at a given node, listening for messages that indicate something has happened to a remote qubit entangled with one of the local qubits.
+
+$FIELDS
+"""
+@kwdef struct EntanglementTracker <: AbstractProtocol
+ """time-and-schedule-tracking instance from `ConcurrentSim`"""
+ sim::Simulation
+ """a network graph of registers"""
+ net::RegisterNet
+ """the vertex of the node where the tracker is working"""
+ node::Int
+end
+
+@resumable function (prot::EntanglementTracker)(;_prot::EntanglementTracker=prot)
+ 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
+ nodereg = prot.net[prot.node]
+ mb = messagebuffer(prot.net, prot.node)
+ while true
+ workwasdone = true # waiting is not enough because we might have multiple rounds of work to do
+ while workwasdone
+ workwasdone = false
+ for (updatetagsymbol, updategate) in ((EntanglementUpdateX, Z), (EntanglementUpdateZ, X))
+ # look for EntanglementUpdate? past_remote_slot_idx local_slot_idx, new_remote_node, new_remote_slot_idx correction
+ msg = querydelete!(mb, updatetagsymbol, ❓, ❓, ❓, ❓, ❓, ❓)
+ isnothing(msg) && continue
+ @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 = querydelete!(localslot, EntanglementCounterpart, pastremotenode, pastremoteslotid)
+ if !isnothing(counterpart)
+ time_before_lock = now(prot.sim)
+ @debug "EntanglementTracker @$(prot.node): EntanglementCounterpart requesting lock at $(now(prot.sim))"
+ @yield lock(localslot)
+ @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.")
+ end
+ # Pauli frame correction gate
+ if correction==2
+ apply!(localslot, updategate)
+ end
+ # tag local with updated EntanglementCounterpart new_remote_node new_remote_slot_idx
+ tag!(localslot, EntanglementCounterpart, newremotenode, newremoteslotid)
+ unlock(localslot)
+ continue
+ 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 = 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"
+ _, _, _, whoweswappedwith_node, whoweswappedwith_slotidx, swappedlocal_slotidx = history
+ 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)
+ continue
+ 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
+ @debug "EntanglementTracker @$(prot.node): Starting message wait at $(now(prot.sim)) with MessageBuffer containing: $(mb.buffer)"
+ @yield wait(mb)
+ @debug "EntanglementTracker @$(prot.node): Message wait ends at $(now(prot.sim))"
+ end
+end
+
+end # module
diff --git a/src/QuantumSavory.jl b/src/QuantumSavory.jl
index 3219f191..8b315916 100644
--- a/src/QuantumSavory.jl
+++ b/src/QuantumSavory.jl
@@ -4,21 +4,24 @@ using Reexport
using IterTools
using LinearAlgebra
+using Random: randperm
using Graphs
+import ConcurrentSim
+using ConcurrentSim: Environment, Simulation, Store, DelayQueue, Resource,
+ Process, @process,
+ request, release, now, active_process, timeout, put, get
+using ResumableFunctions
+using Printf
+using SumTypes: @sum_type, isvariant, @cases
+import SumTypes
+using Combinatorics: powerset
-using QuantumInterface: basis, tensor, ⊗, apply!, traceout!,
+import QuantumInterface: basis, tensor, ⊗, apply!, traceout!, nsubsystems,
AbstractOperator, AbstractKet, AbstractSuperOperator, Basis, SpinBasis
-import QuantumInterface: nsubsystems
-export apply!, traceout!, removebackref!
+export apply!, traceout!, removebackref!, nsubsystems
export project_traceout! #TODO should move to QuantumInterface
-import ConcurrentSim
-using ResumableFunctions
-
-import SumTypes: @sum_type, isvariant
-import Combinatorics: powerset
-
@reexport using QuantumSymbolics
using QuantumSymbolics:
AbstractRepresentation, AbstractUse,
@@ -28,303 +31,47 @@ using QuantumSymbolics:
XBasisState, YBasisState, ZBasisState,
STensorOperator, SScaledOperator, SAddOperator
-export StateRef, RegRef, Register, RegisterNet
-export Qubit, Qumode, QuantumStateTrait,
+export
+ StateRef, RegRef, Register,
+ Qubit, Qumode, QuantumStateTrait,
CliffordRepr, QuantumOpticsRepr, QuantumMCRepr,
UseAsState, UseAsObservable, UseAsOperation,
- AbstractBackground
-export QuantumChannel
-export tag!, tag_types, W, ❓, query
+ AbstractBackground,
+ # networks.jl
+ RegisterNet, channel, qchannel, messagebuffer,
+ # initialize.jl
+ initialize!, newstate,
+ # subsystemcompose.jl
+ subsystemcompose,
+ # observable.jl
+ observable,
+ # uptotime.jl
+ uptotime!, overwritetime!,
+ # tags.jl and queries.jl
+ Tag, tag!, untag!, W, ❓, query, queryall, querydelete!, findfreeslot,
+ # quantumchannel.jl
+ QuantumChannel,
+ # backgrounds.jl
+ T1Decay, T2Dephasing, Depolarization, PauliNoise, AmplitudeDamping,
+ # noninstant.jl
+ AbstractNoninstantOperation, NonInstantGate, ConstantHamiltonianEvolution,
+ # plots.jl
+ 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
-"""An abstract type for the various types of states that can be given to [`Register`](@ref) slots, e.g. qubit, harmonic oscillator, etc."""
-abstract type QuantumStateTrait end
-
-"""An abstract type for the various background processes that might be inflicted upon a [`Register`](@ref) slot, e.g. decay, dephasing, etc."""
-abstract type AbstractBackground end
-
-"""Specifies that a given register slot contains qubits."""
-struct Qubit <: QuantumStateTrait end
-"""Specifies that a given register slot contains qumodes."""
-struct Qumode <: QuantumStateTrait end
-
-# TODO move these definitions to a neater place
-default_repr(::Qubit) = QuantumOpticsRepr()
-default_repr(::Qumode) = QuantumOpticsRepr()
+include("traits_and_defaults.jl")
include("tags.jl")
-# TODO better constructors
-# TODO am I overusing Ref
-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
- registerindices::Vector{Int}
- StateRef(state::Base.RefValue{S}, registers, registerindices) where {S} = new(state, registers, registerindices)
-end
-
-StateRef(state, registers, registerindices) = StateRef(Ref{Any}(copy(state)), registers, registerindices) # TODO same as above, this should not be forced to Any
-
-"""
-The main data structure in `QuantumSavory`, used to represent a quantum register in an arbitrary formalism.
-"""
-mutable struct Register # TODO better type description
- traits::Vector{Any}
- reprs::Vector{Any}
- backgrounds::Vector{Any}
- staterefs::Vector{Union{Nothing,StateRef}}
- stateindices::Vector{Int}
- accesstimes::Vector{Float64} # TODO do not hardcode the type
- env::Any
- locks::Vector{Any}
- tags::Vector{Set{Tag}}
-end
-Register(traits,reprs,bg,sr,si) = Register(traits,reprs,bg,sr,si,fill(0.0,length(traits)))
-Register(traits,reprs,bg) = Register(traits,reprs,bg,fill(nothing,length(traits)),fill(0,length(traits)),fill(0.0,length(traits)))
-Register(traits,bg::Base.AbstractVecOrTuple{<:Union{Nothing,<:AbstractBackground}}) = Register(traits,default_repr.(traits),bg,fill(nothing,length(traits)),fill(0,length(traits)),fill(0.0,length(traits)))
-Register(traits,reprs::Base.AbstractVecOrTuple{<:AbstractRepresentation}) = Register(traits,reprs,fill(nothing,length(traits)),fill(nothing,length(traits)),fill(0,length(traits)),fill(0.0,length(traits)))
-Register(traits) = Register(traits,default_repr.(traits),fill(nothing,length(traits)),fill(nothing,length(traits)),fill(0,length(traits)),fill(0.0,length(traits)))
-Register(nqubits::Int) = Register([Qubit() for _ in 1:nqubits])
-Register(nqubits::Int,repr::AbstractRepresentation) = Register(fill(Qubit(),nqubits),fill(repr,nqubits))
-Register(nqubits::Int,bg::AbstractBackground) = Register(fill(Qubit(),nqubits),fill(bg,nqubits))
-function Register(traits, reprs, bg, sr, si, at)
- env = ConcurrentSim.Simulation()
- Register(traits, reprs, bg, sr, si, at, env, [ConcurrentSim.Resource(env) for _ in traits], [Set{Tag}() for _ in traits])
-end
-
-"""
-A reference to a [`Register`](@ref) slot, convenient for use with functions like [`apply!`](@ref), etc.
-
-```jldoctest
-julia> r = Register(2)
- initialize!(r[1], X₁)
- observable(r[1], X)
-0.9999999999999998 + 0.0im
-```
-"""
-struct RegRef
- reg::Register
- idx::Int
-end
-
-"""
-A network of [`Register`](@ref)s with convenient graph API as well.
-"""
-struct RegisterNet
- graph::SimpleGraph{Int64}
- registers::Vector{Register}
- vertex_metadata::Vector{Dict{Symbol,Any}}
- edge_metadata::Dict{Tuple{Int,Int},Dict{Symbol,Any}}
- directed_edge_metadata::Dict{Pair{Int,Int},Dict{Symbol,Any}}
- function RegisterNet(graph, registers, vertex_metadata, edge_metadata, directed_edge_metadata)
- all_are_at_zero = all(iszero(ConcurrentSim.now(r.env)) && isempty(r.env.heap) && isnothing(r.env.active_proc) for r in registers)
- all_are_same = all(registers[1].env === r.env for r in registers)
- if !all_are_same
- if all_are_at_zero
- env = ConcurrentSim.Simulation()
- for r in registers
- r.env = env
- for i in eachindex(r.locks)
- r.locks[i] = ConcurrentSim.Resource(env,1)
- end
- end
- else
- error("When constructing a `RegisterNet`, the registers must either have not been used yet or have to already belong to the same simulation time tracker, which is not the case here. The simplest way to fix this error is to immediately construct the `RegisterNet` after you have constructed the registers.")
- end
- end
- new(graph, registers, vertex_metadata, edge_metadata, directed_edge_metadata)
- end
-end
-"""
-Construct a [`RegisterNet`](@ref) from a given list of [`Register`](@ref)s and a graph.
-
-```jldoctest
-julia> graph = grid([2,2]) # from Graphs.jl
-{4, 4} undirected simple Int64 graph
-
-julia> registers = [Register(1), Register(2), Register(1), Register(2)]
-4-element Vector{Register}:
- Register with 1 slots: [ Qubit ]
- Slots:
- nothing
- Register with 2 slots: [ Qubit | Qubit ]
- Slots:
- nothing
- nothing
- Register with 1 slots: [ Qubit ]
- Slots:
- nothing
- Register with 2 slots: [ Qubit | Qubit ]
- Slots:
- nothing
- nothing
-
-julia> net = RegisterNet(graph, registers)
-A network of 4 registers in a graph of 4 edges
-
-
-julia> neighbors(net, 1) # from Graphs.jl
-2-element Vector{Int64}:
- 2
- 3
-```
-"""
-function RegisterNet(graph::SimpleGraph, registers::Vector{Register})
- @assert size(graph, 1) == length(registers)
- RegisterNet(graph, registers, [Dict{Symbol,Any}() for _ in registers], Dict{Tuple{Int,Int},Dict{Symbol,Any}}(), Dict{Pair{Int,Int},Dict{Symbol,Any}}())
-end
-"""Construct a [`RegisterNet`](@ref) from a given list of [`Register`](@ref)s, defaulting to a chain topology.
-
-```jldoctest
-julia> net = RegisterNet([Register(2), Register(4), Register(2)])
-A network of 3 registers in a graph of 2 edges
-
-julia> neighbors(net,2) # from Graphs.jl
-2-element Vector{Int64}:
- 1
- 3
-```
-"""
-function RegisterNet(registers::Vector{Register})
- graph = grid([length(registers)])
- RegisterNet(graph, registers)
-end
-
-function add_register!(net::RegisterNet, r::Register)
- add_vertex!(net.graph)
- push!(net.registers, r)
- return length(Graph())
-end
-
-Graphs.add_vertex!(net::RegisterNet, a, b) = add_vertex!(net.graph, a, b)
-Graphs.vertices(net::RegisterNet) = vertices(net.graph)
-Graphs.edges(net::RegisterNet) = edges(net.graph)
-Graphs.neighbors(net::RegisterNet, v) = neighbors(net.graph, v)
-Graphs.adjacency_matrix(net::RegisterNet) = adjacency_matrix(net.graph)
-Graphs.ne(net::RegisterNet) = ne(net.graph)
-Graphs.nv(net::RegisterNet) = nv(net.graph)
-
-# Get register
-Base.getindex(net::RegisterNet, i::Int) = net.registers[i]
-# Get register slot reference
-Base.getindex(net::RegisterNet, i::Int, j::Int) = net.registers[i][j]
-# Get and set vertex metadata
-Base.getindex(net::RegisterNet, i::Int, k::Symbol) = net.vertex_metadata[i][k]
-Base.setindex!(net::RegisterNet, val, i::Int, k::Symbol) = begin net.vertex_metadata[i][k] = val end
-# Get and set edge metadata
-Base.getindex(net::RegisterNet, ij::Tuple{Int,Int}, k::Symbol) = net.edge_metadata[minmax(ij...)][k]
-function Base.setindex!(net::RegisterNet, val, ij::Tuple{Int,Int}, k::Symbol)
- edge = minmax(ij...)
- haskey(net.edge_metadata,edge) || (net.edge_metadata[edge] = Dict{Symbol,Any}())
- net.edge_metadata[edge][k] = val
-end
-# Get and set directed edge metadata
-Base.getindex(net::RegisterNet, ij::Pair{Int,Int}, k::Symbol) = net.directed_edge_metadata[ij][k]
-function Base.setindex!(net::RegisterNet, val, ij::Pair{Int,Int}, k::Symbol)
- edge = ij
- haskey(net.directed_edge_metadata,edge) || (net.directed_edge_metadata[edge] = Dict{Symbol,Any}())
- net.directed_edge_metadata[edge][k] = val
-end
-Base.getindex(net::RegisterNet, ij::Graphs.SimpleEdge, k::Symbol) = net[(ij.src, ij.dst),k]
-Base.setindex!(net::RegisterNet, val, ij::Graphs.SimpleEdge, k::Symbol) = begin net[(ij.src, ij.dst),k] = val end
-# Get and set with colon notation
-Base.getindex(net::RegisterNet, ::Colon) = net.registers
-Base.getindex(net::RegisterNet, ::Colon, j::Int) = [r[j] for r in net.registers]
-Base.getindex(net::RegisterNet, ::Colon, k::Symbol) = [m[k] for m in net.vertex_metadata]
-Base.getindex(net::RegisterNet, ::Tuple{Colon,Colon}, k::Symbol) = [net.edge_metadata[minmax(ij)...][k] for ij in edges(net)]
-Base.getindex(net::RegisterNet, ::Pair{Colon,Colon}, k::Symbol) = [net.directed_edge_metadata[ij][k] for ij in edges(net)]
-
-function Base.setindex!(net::RegisterNet, v, ::Colon, k::Symbol)
- for m in net.vertex_metadata
- m[k] = v
- end
-end
-function Base.setindex!(net::RegisterNet, v, ::Tuple{Colon,Colon}, k::Symbol)
- for ij in edges(net)
- net[ij,k] = v
- end
-end
-function Base.setindex!(net::RegisterNet, v, ::Pair{Colon,Colon}, k::Symbol)
- for ij in edges(net)
- net[ij,k] = v
- end
-end
-function Base.setindex!(net::RegisterNet, @nospecialize(f::Function), ::Colon, k::Symbol)
- for m in net.vertex_metadata
- m[k] = f()
- end
-end
-function Base.setindex!(net::RegisterNet, @nospecialize(f::Function), ::Tuple{Colon,Colon}, k::Symbol)
- for ij in edges(net)
- net[ij,k] = f()
- end
-end
-function Base.setindex!(net::RegisterNet, @nospecialize(f::Function), ::Pair{Colon,Colon}, k::Symbol)
- for ij in edges(net)
- net[ij,k] = f()
- end
-end
-
-##
-
-function Base.show(io::IO, s::StateRef)
- print(io, "State containing $(nsubsystems(s.state[])) subsystems in $(typeof(s.state[]).name.module) implementation")
- print(io, "\n In registers:")
- for (i,r) in zip(s.registerindices, s.registers)
- if isnothing(r)
- print(io, "\n not used")
- else
- print(io, "\n $(i)@$(objectid(r))")
- end
- end
-end
-
-function Base.show(io::IO, r::Register)
- print(io, "Register with $(length(r.traits)) slots") # TODO make this length call prettier
- print(io, ": [ ")
- print(io, join(string.(typeof.(r.traits)), " | "))
- print(io, " ]")
- print(io, "\n Slots:")
- for (i,s) in zip(r.stateindices, r.staterefs)
- if isnothing(s)
- print(io, "\n nothing")
- else
- print(io, "\n Subsystem $(i) of $(typeof(s.state[]).name.module).$(typeof(s.state[]).name.name) $(objectid(s.state[]))")
- end
- end
-end
-
-function Base.show(io::IO, net::RegisterNet)
- print(io, "A network of $(length(net.registers)) registers in a graph of $(length(edges(net.graph))) edges\n")
-end
-
-function Base.show(io::IO, r::RegRef)
- if get(io, :compact, false) | haskey(io, :typeinfo)
- print(io, "Slot $(r.idx)")
- else
- print(io, "Slot $(r.idx)/$(length(r.reg.traits)) of Register $(objectid(r.reg))") # TODO make this length call prettier
- print(io, "\nContent:")
- i,s = r.reg.stateindices[r.idx], r.reg.staterefs[r.idx]
- if isnothing(s)
- print(io, "\n nothing")
- else
- print(io, "\n $(i) @ $(typeof(s.state[]).name.module).$(typeof(s.state[]).name.name) $(objectid(s.state[]))")
- end
- end
-end
-
-Base.getindex(r::Register, i::Int) = RegRef(r,i)
-Base.getindex(r::Register, C) = map(i->r[i], C)
-
-#Base.:(==)(r1::Register, r2::Register) =
-
-function Base.isassigned(r::Register,i::Int) # TODO erase
- r.stateindices[i] != 0 # TODO this also usually means r.staterenfs[i] !== nothing - choose one and make things consistent
-end
-Base.isassigned(r::RegRef) = isassigned(r.reg, r.idx)
+include("states_registers.jl")
+include("quantumchannel.jl")
+include("messagebuffer.jl")
+include("networks.jl")
+include("states_registers_networks_getset.jl")
+include("states_registers_networks_shows.jl")
include("baseops/subsystemcompose.jl")
include("baseops/initialize.jl")
@@ -346,12 +93,12 @@ include("concurrentsim.jl")
include("plots.jl")
-include("quantumchannel.jl")
-
include("CircuitZoo/CircuitZoo.jl")
include("StatesZoo/StatesZoo.jl")
+include("ProtocolZoo/ProtocolZoo.jl")
+
include("precompile.jl")
end # module
diff --git a/src/backgrounds.jl b/src/backgrounds.jl
index eab243ce..73cf28dc 100644
--- a/src/backgrounds.jl
+++ b/src/backgrounds.jl
@@ -1,5 +1,3 @@
-export T1Decay, T2Dephasing, Depolarization, PauliNoise, AmplitudeDamping
-
"""A background describing the T₁ decay of a two-level system."""
struct T1Decay <: AbstractBackground
t1
diff --git a/src/baseops/apply.jl b/src/baseops/apply.jl
index 456cdfd5..961ea193 100644
--- a/src/baseops/apply.jl
+++ b/src/baseops/apply.jl
@@ -1,5 +1,3 @@
-import QuantumInterface: apply!
-
"""
Apply a given operation on the given set of register slots.
diff --git a/src/baseops/initialize.jl b/src/baseops/initialize.jl
index 36b542d7..f2b14493 100644
--- a/src/baseops/initialize.jl
+++ b/src/baseops/initialize.jl
@@ -1,5 +1,3 @@
-export initialize!, newstate
-
function newstate end
function initialize!(reg::Register,i::Int; time=nothing)
diff --git a/src/baseops/observable.jl b/src/baseops/observable.jl
index 5d11c87a..63ebe12a 100644
--- a/src/baseops/observable.jl
+++ b/src/baseops/observable.jl
@@ -1,5 +1,3 @@
-export observable
-
"""
Calculate the expectation value of a quantum observable on the given register and slot.
diff --git a/src/baseops/subsystemcompose.jl b/src/baseops/subsystemcompose.jl
index 1d44e3f2..40a9fb51 100644
--- a/src/baseops/subsystemcompose.jl
+++ b/src/baseops/subsystemcompose.jl
@@ -1,5 +1,3 @@
-export subsystemcompose, nsubsystems
-
nsubsystems(s::StateRef) = length(s.registers) # nsubsystems(s.state[]) TODO this had to change because of references to "padded" states, but we probably still want to track more detailed information (e.g. how much have we overpadded)
nsubsystems_padded(s::StateRef) = nsubsystems(s.state[])
nsubsystems(r::Register) = length(r.staterefs)
diff --git a/src/baseops/traceout.jl b/src/baseops/traceout.jl
index 1a4ebbab..09361209 100644
--- a/src/baseops/traceout.jl
+++ b/src/baseops/traceout.jl
@@ -1,5 +1,3 @@
-import QuantumInterface: traceout!
-
ispadded(::Nothing) = false # TODO consider removing this and reworking the functions that depend on it. E.g., a reason to have it when performing a project_traceout measurement on a state that contains only one subsystem
function removebackref!(s::StateRef, i) # To be used only with something that updates s.state[]
@@ -58,7 +56,11 @@ Perform a projective measurement on the given slot of the given register.
`project_traceout!(reg, slot, [stateA, stateB])` performs a projective measurement,
projecting on either `stateA` or `stateB`, returning the index of the subspace
on which the projection happened. It assumes the list of possible states forms a basis
+<<<<<<< HEAD
for the Hilbert space. The Hilbert space of the register gets automatically shrunk.
+=======
+for the Hilbert space. The Hilbert space of the register is automatically shrunk.
+>>>>>>> 2285fd9 (spelling fixes)
A basis object can be specified on its own as well, e.g.
`project_traceout!(reg, slot, basis)`.
diff --git a/src/baseops/uptotime.jl b/src/baseops/uptotime.jl
index dbc4370a..774c7009 100644
--- a/src/baseops/uptotime.jl
+++ b/src/baseops/uptotime.jl
@@ -1,5 +1,3 @@
-export uptotime!, overwritetime!
-
"""
Evolve all the states in a register to a given time, according to the various backgrounds that they might have.
diff --git a/src/concurrentsim.jl b/src/concurrentsim.jl
index 5bd4d279..c79cacb0 100644
--- a/src/concurrentsim.jl
+++ b/src/concurrentsim.jl
@@ -1,8 +1,3 @@
-using ResumableFunctions
-import ConcurrentSim # Should be using
-using ConcurrentSim: Environment, request, release, now, active_process, timeout, Store, @process, Process, put, get
-using Printf
-
export @simlog, nongreedymultilock, spinlock, get_time_tracker
macro simlog(env, msg) # this should be part of @process or @resumable and just convert @info and company
@@ -47,7 +42,7 @@ function get_time_tracker(rn::RegisterNet)
return get_time_tracker(rn.registers[1])
end
function get_time_tracker(r::Register)
- r.env
+ r.locks[1].env
end
function get_time_tracker(r::RegRef)
get_time_tracker(r.reg)
diff --git a/src/messagebuffer.jl b/src/messagebuffer.jl
new file mode 100644
index 00000000..c89501da
--- /dev/null
+++ b/src/messagebuffer.jl
@@ -0,0 +1,72 @@
+struct MessageBuffer{T}
+ sim::Simulation
+ net # TODO ::RegisterNet -- this can not be typed due to circular dependency, see https://github.com/JuliaLang/julia/issues/269
+ node::Int
+ buffer::Vector{NamedTuple{(:src,:tag), Tuple{Int,T}}}
+ waiters::IdDict{Resource,Resource}
+ no_wait::Ref{Int} # keeps track of the situation when something is pushed in the buffer and no waiters are present. In that case, when the waiters are available after it they would get locked while the code that was supposed to unlock them has already run. So, we keep track the number of times this happens and put no lock on the waiters in this situation.
+end
+
+struct ChannelForwarder
+ net # TODO type it as ::RegisterNet
+ src::Int
+ dst::Int
+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
+
+@resumable function take_loop_mb(sim, ch, src, mb)
+ while true
+ _tag = @yield take!(ch) # TODO: The type assert is necessary due to a bug in ResumableFunctions. The bug was probably introduced in https://github.com/JuliaDynamics/ResumableFunctions.jl/pull/76 which introduces type inference for resumable functions in julia >=1.10. The type assert is not necessary on julia 1.9.
+ tag = _tag::Tag
+ @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
+ @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)`."
+ if length(mb.waiters) == 0
+ mb.no_wait[] += 1
+ end
+ push!(mb.buffer, (;src,tag));
+ for waiter in keys(mb.waiters)
+ unlock(waiter)
+ end
+ end
+ end
+ end
+end
+
+function MessageBuffer(net, node::Int, qs::Vector{NamedTuple{(:src,:channel), Tuple{Int, DelayQueue{T}}}}) where {T}
+ sim = get_time_tracker(net)
+ signal = IdDict{Resource,Resource}()
+ no_wait = Ref{Int}(0)
+ mb = MessageBuffer{T}(sim, net, node, Tuple{Int,T}[], signal, no_wait)
+ for (;src, channel) in qs
+ @process take_loop_mb(sim, channel, src, mb)
+ end
+ mb
+end
+
+@resumable function wait_process(sim, mb)
+ if mb.no_wait[] != 0 # This happens only in the specific case when something is put in the buffer before there any waiters.
+ mb.no_wait[] -= 1
+ return
+ end
+ waitresource = Resource(sim)
+ lock(waitresource)
+ mb.waiters[waitresource] = waitresource
+ @yield lock(waitresource)
+ pop!(mb.waiters, waitresource)
+end
+
+function Base.wait(mb::MessageBuffer)
+ @process wait_process(mb.sim, mb)
+end
diff --git a/src/networks.jl b/src/networks.jl
new file mode 100644
index 00000000..b0f9e537
--- /dev/null
+++ b/src/networks.jl
@@ -0,0 +1,213 @@
+"""
+A network of [`Register`](@ref)s with convenient graph API as well.
+"""
+struct RegisterNet
+ graph::SimpleGraph{Int64}
+ registers::Vector{Register}
+ vertex_metadata::Vector{Dict{Symbol,Any}}
+ edge_metadata::Dict{Tuple{Int,Int},Dict{Symbol,Any}}
+ directed_edge_metadata::Dict{Pair{Int,Int},Dict{Symbol,Any}}
+ cchannels::Dict{Pair{Int,Int},DelayQueue{Tag}} # Dict{src=>dst, DelayQueue}
+ cbuffers::Dict{Int,MessageBuffer{Tag}} # Dict{dst, MessageBuffer}
+ qchannels::Dict{Pair{Int,Int},Any} # Dict{src=>dst, QuantumChannel}
+ reverse_lookup::IdDict{Register,Int}
+end
+
+function RegisterNet(graph::SimpleGraph, registers, vertex_metadata, edge_metadata, directed_edge_metadata)
+ env = get_time_tracker(registers[1])
+
+ all_are_at_zero = all(iszero(ConcurrentSim.now(get_time_tracker(r))) && isempty(get_time_tracker(r).heap) && isnothing(get_time_tracker(r).active_proc) for r in registers)
+ all_are_same = all(env === get_time_tracker(r) for r in registers)
+ if !all_are_same
+ if all_are_at_zero
+ for r in registers
+ for i in eachindex(r.locks)
+ r.locks[i] = ConcurrentSim.Resource(env,1)
+ end
+ end
+ else
+ error("When constructing a `RegisterNet`, the registers must either have not been used yet or have to already belong to the same simulation time tracker, which is not the case here. The simplest way to fix this error is to immediately construct the `RegisterNet` after you have constructed the registers.")
+ end
+ end
+
+ cchannels = Dict{Pair{Int,Int},DelayQueue{Tag}}()
+ qchannels = Dict{Pair{Int,Int},Any}()
+ cbuffers = Dict{Int,MessageBuffer{Tag}}()
+ reverse_lookup = IdDict{Register,Int}()
+
+ rn = RegisterNet(graph, registers, vertex_metadata, edge_metadata, directed_edge_metadata, cchannels, cbuffers, qchannels, reverse_lookup)
+
+ for (;src,dst) in edges(graph)
+ cchannels[src=>dst] = DelayQueue{Tag}(env, 0)
+ qchannels[src=>dst] = QuantumChannel(env, 0)
+ cchannels[dst=>src] = DelayQueue{Tag}(env, 0)
+ qchannels[dst=>src] = QuantumChannel(env, 0)
+ end
+ for (v,r) in zip(vertices(graph), registers)
+ channels = [(;src=w, channel=cchannels[w=>v]) for w in neighbors(graph, v)]
+ cbuffers[v] = MessageBuffer(rn, v, channels)
+ end
+ for (v,r) in zip(vertices(graph), registers)
+ reverse_lookup[r] = v
+ end
+
+ return rn
+end
+
+"""
+Construct a [`RegisterNet`](@ref) from a given list of [`Register`](@ref)s and a graph.
+
+```jldoctest
+julia> graph = grid([2,2]) # from Graphs.jl
+{4, 4} undirected simple Int64 graph
+
+julia> registers = [Register(1), Register(2), Register(1), Register(2)]
+4-element Vector{Register}:
+ Register with 1 slots: [ Qubit ]
+ Slots:
+ nothing
+ Register with 2 slots: [ Qubit | Qubit ]
+ Slots:
+ nothing
+ nothing
+ Register with 1 slots: [ Qubit ]
+ Slots:
+ nothing
+ Register with 2 slots: [ Qubit | Qubit ]
+ Slots:
+ nothing
+ nothing
+
+julia> net = RegisterNet(graph, registers)
+A network of 4 registers in a graph of 4 edges
+
+
+julia> neighbors(net, 1) # from Graphs.jl
+2-element Vector{Int64}:
+ 2
+ 3
+```
+"""
+function RegisterNet(graph::SimpleGraph, registers)
+ size(graph, 1) == length(registers) || ArgumentError(lazy"You attempted to construct a `RegisterNet` with a graph of $(size(graph, 1)) vertices but provided a list of $(length(registers)) `Registers`. These two numbers have to match.")
+ RegisterNet(graph, registers, empty_vmd(length(registers)), empty_emd(), empty_demd())
+end
+
+empty_vmd(n) = [Dict{Symbol,Any}() for _ in 1:n]
+empty_emd() = Dict{Tuple{Int,Int},Dict{Symbol,Any}}()
+empty_demd() = Dict{Pair{Int,Int},Dict{Symbol,Any}}()
+
+"""Construct a [`RegisterNet`](@ref) from a given list of [`Register`](@ref)s, defaulting to a chain topology.
+
+```jldoctest
+julia> net = RegisterNet([Register(2), Register(4), Register(2)])
+A network of 3 registers in a graph of 2 edges
+
+julia> neighbors(net,2) # from Graphs.jl
+2-element Vector{Int64}:
+ 1
+ 3
+```
+"""
+function RegisterNet(registers::Vector{Register})
+ graph = grid([length(registers)])
+ RegisterNet(graph, registers)
+end
+
+function add_register!(net::RegisterNet, r::Register)
+ add_vertex!(net.graph)
+ push!(net.registers, r)
+ return length(Graph())
+end
+
+## Channel accessors
+
+"""Get a handle to a classical channel between two registers.
+
+Usually used for sending classical messages between registers.
+It can be used for receiving as well, but a more convenient choice is [`messagebuffer`](@ref),
+which is a message buffer listening to **all** channels sending to a given destination register.
+
+```jldoctest
+julia> net = RegisterNet([Register(2), Register(2), Register(2)]) # defaults to a chain topology
+A network of 3 registers in a graph of 2 edges
+
+julia> channel(net, 1=>2)
+ConcurrentSim.DelayQueue{Tag}(ConcurrentSim.QueueStore{Tag, Int64}, 0.0)
+
+julia> channel(net, 1=>2)
+ConcurrentSim.DelayQueue{Tag}(ConcurrentSim.QueueStore{Tag, Int64}, 0.0)
+
+julia> channel(net, 1=>2) === channel(net, net[1]=>net[2])
+true
+```
+
+See also: [`qchannel`](@ref), [`messagebuffer`](@ref)
+"""
+function channel(net::RegisterNet, args...; permit_forward=false)
+ return achannel(net, args..., Val{:C}(); permit_forward)
+end
+
+"""Get a handle to a quantum channel between two registers.
+
+```jldoctest
+julia> net = RegisterNet([Register(2), Register(2), Register(2)]) # defaults to a chain topology
+A network of 3 registers in a graph of 2 edges
+
+julia> qchannel(net, 1=>2)
+QuantumChannel{Qubit}(Qubit(), ConcurrentSim.DelayQueue{Register}(ConcurrentSim.QueueStore{Register, Int64}, 0.0), nothing)
+
+julia> qchannel(net, 1=>2) === qchannel(net, net[1]=>net[2])
+true
+```
+
+See also: [`channel`](@ref)
+"""
+function qchannel(net::RegisterNet, args...)
+ return achannel(net, args..., Val{:Q}())
+end
+
+"""Get a handle to a classical message buffer corresponding to all channels sending to a given destination register.
+
+See also: [`channel`](@ref)
+"""
+function messagebuffer(net::RegisterNet, dst::Int)
+ return net.cbuffers[dst]
+end
+
+function achannel(net::RegisterNet, src::Int, dst::Int, ::Val{:C}; permit_forward=false)
+ pair = src=>dst
+ if permit_forward && !haskey(net.cchannels, pair)
+ return ChannelForwarder(net, src, dst)
+ elseif haskey(net.cchannels, pair)
+ return net.cchannels[pair]
+ else
+ error(lazy"There is no direct classical channel between the nodes in the request $(src)=>$(dst). Consider using `channel(...; permit_forward=true)` to instead encapsulate the message in a forwarder packet and send it to the first node in the shortest path.")
+ end
+end
+
+function achannel(net::RegisterNet, src::Int, dst::Int, ::Val{:Q})
+ return net.qchannels[src=>dst]
+end
+
+function achannel(net::RegisterNet, fromreg::Register, to::Int, v::Val{Q}; kw...) where {Q}
+ achannel(net, net.reverse_lookup[fromreg], to, v; kw...)
+end
+
+function achannel(net::RegisterNet, from::Int, toreg::Register, v::Val{Q}; kw...) where {Q}
+ achannel(net, from, net.reverse_lookup[toreg], v; kw...)
+end
+
+function achannel(net::RegisterNet, fromreg::Register, toreg::Register, v::Val{Q}; kw...) where {Q}
+ achannel(net, net.reverse_lookup[fromreg], net.reverse_lookup[toreg], v; kw...)
+end
+
+function achannel(net::RegisterNet, fromto::Edge, v::Val{Q}; kw...) where {Q}
+ (;src,dst) = fromto
+ achannel(net, src, dst, v; kw...)
+end
+
+function achannel(net::RegisterNet, fromto::Pair, v::Val{Q}; kw...) where {Q}
+ (src,dst) = fromto
+ achannel(net, src, dst, v; kw...)
+end
diff --git a/src/noninstant.jl b/src/noninstant.jl
index 2da0aefe..c993db67 100644
--- a/src/noninstant.jl
+++ b/src/noninstant.jl
@@ -1,5 +1,3 @@
-export AbstractNoninstantOperation, NonInstantGate, ConstantHamiltonianEvolution
-
abstract type AbstractNoninstantOperation end
"""Represents an gate applied instantaneously followed by a waiting period. See also [`ConstantHamiltonianEvolution`](@ref)."""
diff --git a/src/plots.jl b/src/plots.jl
index 3f793461..554a33c8 100644
--- a/src/plots.jl
+++ b/src/plots.jl
@@ -1,5 +1,3 @@
-export registernetplot, registernetplot_axis, resourceplot_axis
-
"""Draw the given registers on a given Makie axis.
Requires a Makie backend be already imported."""
diff --git a/src/quantumchannel.jl b/src/quantumchannel.jl
index 3d8f5f21..1cd2ada0 100644
--- a/src/quantumchannel.jl
+++ b/src/quantumchannel.jl
@@ -61,7 +61,7 @@ function Base.put!(qc::QuantumChannel, rref::RegRef)
put!(qc.queue, channel_reg)
end
-@resumable function post_take(env, take_event, rref)
+@resumable function post_take_qc(env, take_event, rref)
channel_reg = @yield take_event
if isassigned(rref)
error("A take! operation is being performed on a QuantumChannel in order to swap the state into a Register, but the target register slot is not empty (it is already initialized).")
@@ -71,5 +71,5 @@ end
function Base.take!(qc::QuantumChannel, rref::RegRef)
take_event = take!(qc.queue)
- @process post_take(qc.queue.store.env, take_event, rref)
+ @process post_take_qc(qc.queue.store.env, take_event, rref)
end
diff --git a/src/queries.jl b/src/queries.jl
index 4fb7dfcb..1af825ff 100644
--- a/src/queries.jl
+++ b/src/queries.jl
@@ -1,70 +1,253 @@
"""Assign a tag to a slot in a register.
-See also: [`query`](@ref), [`tag_types`](@ref)"""
+See also: [`query`](@ref)"""
function tag!(ref::RegRef, tag::Tag)
push!(ref.reg.tags[ref.idx], tag)
end
+tag!(ref, tag) = tag!(ref, Tag(tag))
+
+function untag!(ref::RegRef, tag::Tag) # TODO rather slow implementation. See issue #74
+ tags = ref.reg.tags[ref.idx]
+ i = findfirst(==(tag), tags)
+ isnothing(i) ? throw(KeyError(tag)) : deleteat!(tags, i) # TODO make sure there is a clear error message
+end
+
"""Wildcard for use with the tag querying functionality.
-See also: [`query`](@ref), [`tag!`](@ref), [`tag_types`](@ref)"""
+See also: [`query`](@ref), [`tag!`](@ref)"""
struct Wildcard end
"""A wildcard instance for use with the tag querying functionality.
-See also: [`query`](@ref), [`tag!`](@ref), [`tag_types`](@ref), [`Wildcard`](@ref)"""
+See also: [`query`](@ref), [`tag!`](@ref), [`Wildcard`](@ref)"""
const W = Wildcard()
"""A wildcard instance for use with the tag querying functionality.
-See also: [`query`](@ref), [`tag!`](@ref), [`tag_types`](@ref), [`Wildcard`](@ref)"""
+See also: [`query`](@ref), [`tag!`](@ref), [`Wildcard`](@ref)"""
const ❓ = W
-""" A query function checking for the first slot in a register that has a given tag.
+""" A query function that returns all slots of a register that have a given tag, with support for predicates and wildcards.
+
+```jldoctest
+julia> r = Register(10);
+ tag!(r[1], :symbol, 2, 3);
+ tag!(r[2], :symbol, 4, 5);
+
+julia> queryall(r, :symbol, ❓, ❓)
+2-element Vector{@NamedTuple{slot::RegRef, tag::Tag}}:
+ (slot = Slot 1, tag = SymbolIntInt(:symbol, 2, 3)::Tag)
+ (slot = Slot 2, tag = SymbolIntInt(:symbol, 4, 5)::Tag)
-It supports wildcards (instances of `Wildcard` also available as the constants [`W`](@ref) or [`❓`](@ref) which can be entered as `\\:question:` in the REPL).
+julia> queryall(r, :symbol, ❓, >(4))
+1-element Vector{@NamedTuple{slot::RegRef, tag::Tag}}:
+ (slot = Slot 2, tag = SymbolIntInt(:symbol, 4, 5)::Tag)
+
+julia> queryall(r, :symbol, ❓, >(5))
+@NamedTuple{slot::RegRef, tag::Tag}[]
+```
+"""
+queryall(args...; kwargs...) = query(args..., Val{true}(); kwargs...)
+
+
+""" A query function searching for the first slot in a register that has a given tag.
+
+Wildcards are supported (instances of `Wildcard` also available as the constants [`W`](@ref) or the emoji [`❓`](@ref) which can be entered as `\\:question:` in the REPL).
+Predicate functions are also supported (they have to be `Int`↦`Bool` functions).
+The keyword arguments `locked` and `assigned` can be used to check, respectively,
+whether the given slot is locked or whether it contains a quantum state.
```jldoctest
julia> r = Register(10);
tag!(r[1], :symbol, 2, 3);
tag!(r[2], :symbol, 4, 5);
- tag!(r[5], Int, 4, 5);
+
julia> query(r, :symbol, 4, 5)
-(Slot 2, SymbolIntInt(:symbol, 4, 5)::QuantumSavory.Tag)
+(slot = Slot 2, tag = SymbolIntInt(:symbol, 4, 5)::Tag)
+
+julia> lock(r[1]);
+
+julia> query(r, :symbol, 4, 5; locked=false) |> isnothing
+false
julia> query(r, :symbol, ❓, 3)
-(Slot 1, SymbolIntInt(:symbol, 2, 3)::QuantumSavory.Tag)
+(slot = Slot 1, tag = SymbolIntInt(:symbol, 2, 3)::Tag)
+
+julia> query(r, :symbol, ❓, 3; assigned=true) |> isnothing
+true
julia> query(r, :othersym, ❓, ❓) |> isnothing
true
+julia> tag!(r[5], Int, 4, 5);
+
julia> query(r, Float64, 4, 5) |> isnothing
true
+
+julia> query(r, Int, 4, >(7)) |> isnothing
+true
+
+julia> query(r, Int, 4, <(7))
+(slot = Slot 5, tag = TypeIntInt(Int64, 4, 5)::Tag)
+```
+
+See also: [`queryall`](@ref), [`tag!`](@ref), [`Wildcard`](@ref)
+"""
+function query(reg::Register, tag::Tag, ::Val{allB}=Val{false}(); locked::Union{Nothing,Bool}=nothing, assigned::Union{Nothing,Bool}=nothing) where {allB}
+ find = allB ? findall : findfirst
+ i = find(i -> _nothingor(locked, islocked(reg[i])) && _nothingor(assigned, isassigned(reg[i])) && tag ∈ reg.tags[i],
+ 1:length(reg))
+ if allB
+ return NamedTuple{(:slot, :tag), Tuple{RegRef, Tag}}[(slot=reg[i], tag=tag) for i in i]
+ else
+ isnothing(i) ? nothing : (;slot=reg[i], tag=tag)
+ end
+end
+
+"""A [`query`](@ref) on a single slot of a register.
+
+```jldoctest
+julia> r = Register(5);
+
+julia> tag!(r[2], :symbol, 2, 3);
+
+julia> query(r[2], :symbol, 2, 3)
+(depth = 1, tag = SymbolIntInt(:symbol, 2, 3)::Tag)
+
+julia> query(r[3], :symbol, 2, 3) === nothing
+true
+
+julia> queryall(r[2], :symbol, 2, 3)
+1-element Vector{@NamedTuple{depth::Int64, tag::Tag}}:
+ (depth = 1, tag = SymbolIntInt(:symbol, 2, 3)::Tag)
+```
+"""
+function query(ref::RegRef, tag::Tag, ::Val{allB}=Val{false}()) where {allB} # TODO there is a lot of code duplication here
+ find = allB ? findall : findfirst
+ i = find(==(tag), ref.reg.tags[ref.idx])
+ if allB
+ return NamedTuple{(:depth, :tag), Tuple{Int, Tag}}[(depth=i, tag=tag) for i in i]
+ else
+ isnothing(i) ? nothing : (;depth=i, tag=tag)
+ end
+end
+
+"""A [`query`](@ref) for classical message buffers.
+
+You are advised to actually use [`querypop!`](@ref), not `query` when working with classical message buffers."""
+function query(mb::MessageBuffer, tag::Tag)
+ i = findfirst(t->t.tag==tag, mb.buffer)
+ return isnothing(i) ? nothing : (;depth=i, src=mb.buffer[i][1], tag=mb.buffer[i][2])
+end
+
+raw"""A [`query`](@ref) for classical message buffers that also pops the message out of the buffer.
+
+```jldoctest
+julia> net = RegisterNet([Register(3), Register(2)])
+A network of 2 registers in a graph of 1 edges
+
+julia> put!(channel(net, 1=>2), Tag(:my_tag));
+
+julia> put!(channel(net, 1=>2), Tag(:another_tag, 123, 456));
+
+julia> query(messagebuffer(net, 2), :my_tag)
+
+julia> run(get_time_tracker(net))
+
+julia> query(messagebuffer(net, 2), :my_tag)
+(depth = 1, src = 1, tag = Symbol(:my_tag)::Tag)
+
+julia> querypop!(messagebuffer(net, 2), :my_tag)
+(src = 1, tag = Symbol(:my_tag)::Tag)
+
+julia> querypop!(messagebuffer(net, 2), :my_tag) === nothing
+true
+
+julia> querypop!(messagebuffer(net, 2), :another_tag, ❓, ❓)
+(src = 1, tag = SymbolIntInt(:another_tag, 123, 456)::Tag)
+
+julia> querypop!(messagebuffer(net, 2), :another_tag, ❓, ❓) === nothing
+true
+```
+
+You can also wait on a message buffer for a message to arrive before running a query:
+
+```jldoctes
+julia> net = RegisterNet([Register(3), Register(2), Register(3)])
+A network of 3 registers in a graph of 2 edges
+
+julia> env = get_time_tracker(net);
+
+julia> @resumable function receive_tags(env)
+ while true
+ mb = messagebuffer(net, 2)
+ @yield wait(mb)
+ msg = querypop!(mb, :second_tag, ❓, ❓)
+ print("t=$(now(env)): query returns ")
+ if isnothing(msg)
+ println("nothing")
+ else
+ println("$(msg.tag) received from node $(msg.src)")
+ end
+ end
+ end
+receive_tags (generic function with 1 method)
+
+julia> @resumable function send_tags(env)
+ @yield timeout(env, 1.0)
+ put!(channel(net, 1=>2), Tag(:my_tag))
+ @yield timeout(env, 2.0)
+ put!(channel(net, 3=>2), Tag(:second_tag, 123, 456))
+ end
+send_tags (generic function with 1 method)
+
+julia> @process send_tags(env);
+
+julia> @process receive_tags(env);
+
+julia> run(env, 10)
+t=1.0: query returns nothing
+t=3.0: query returns SymbolIntInt(:second_tag, 123, 456)::Tag received from node 3
```
"""
-function query(reg::Register, tag::Tag)
- i = findfirst(set -> tag ∈ set, reg.tags)
- isnothing(i) ? nothing : (reg[i], tag)
+function querydelete!(mb::MessageBuffer, args...)
+ r = query(mb, args...)
+ return isnothing(r) ? nothing : popat!(mb.buffer, r.depth)
end
-_query_all() = true
-_query_all(a::Bool) = a
-_query_all(a::Bool, b::Bool) = a && b
-_query_all(a::Bool, b::Bool, c::Bool) = a && b && c
+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
+
+
+_nothingor(l,r) = isnothing(l) || l==r
+_all() = true
+_all(a::Bool) = a
+_all(a::Bool, b::Bool) = a && b
+_all(a::Bool, b::Bool, c::Bool) = a && b && c
+_all(a::Bool, b::Bool, c::Bool, d::Bool) = a && b && c && d
+_all(a::Bool, b::Bool, c::Bool, d::Bool, e::Bool) = a && b && c && d && e
+_all(a::Bool, b::Bool, c::Bool, d::Bool, e::Bool, f::Bool) = a && b && c && d && e && f
# Create a query function for each combination of tag arguments and/or wildcard arguments
for (tagsymbol, tagvariant) in pairs(tag_types)
sig = methods(tagvariant)[1].sig.parameters[2:end]
- args = (:a, :b, :c, :d)[1:length(sig)]
+ args = (:a, :b, :c, :d, :e, :f, :g)[1:length(sig)]
argssig = [:($a::$t) for (a,t) in zip(args, sig)]
eval(quote function tag!(ref::RegRef, $(argssig...))
tag!(ref, ($tagvariant)($(args...)))
end end)
- eval(quote function query(reg::Register, $(argssig...))
- query(reg, ($tagvariant)($(args...)))
+ eval(quote function Tag($(argssig...))
+ ($tagvariant)($(args...))
+ end end)
+
+ eval(quote function query(tagcontainer, $(argssig...), ars...; kwa...)
+ query(tagcontainer, ($tagvariant)($(args...)), ars...; kwa...)
end end)
int_idx_all = [i for (i,s) in enumerate(sig) if s == Int]
@@ -72,20 +255,81 @@ for (tagsymbol, tagvariant) in pairs(tag_types)
for idx in int_idx_combs
complement_idx = tuple(setdiff(1:length(sig), idx)...)
sig_wild = collect(sig)
- sig_wild[idx] .= Wildcard
+ sig_wild[idx] .= Union{Wildcard,Function}
argssig_wild = [:($a::$t) for (a,t) in zip(args, sig_wild)]
+ wild_checks = [:(isa($(args[i]),Wildcard) || $(args[i])(tag.data[$i])) for i in idx]
nonwild_checks = [:(tag.data[$i]==$(args[i])) for i in complement_idx]
- eval(quote function query(reg::Register, $(argssig_wild...))
+ newmethod_reg = quote function query(reg::Register, $(argssig_wild...), ::Val{allB}=Val{false}(); locked::Union{Nothing,Bool}=nothing, assigned::Union{Nothing,Bool}=nothing) where {allB}
+ res = NamedTuple{(:slot, :tag), Tuple{RegRef, Tag}}[]
for (reg_idx, tags) in enumerate(reg.tags)
- for tag in tags
+ slot = reg[reg_idx]
+ for tag in tags
if isvariant(tag, ($(tagsymbol,))[1]) # a weird workaround for interpolating a symbol as a symbol
- if _query_all($(nonwild_checks...))
- return (reg[reg_idx], tag)
+ (_nothingor(locked, islocked(slot)) && _nothingor(assigned, isassigned(slot))) || continue
+ if _all($(nonwild_checks...)) && _all($(wild_checks...))
+ allB ? push!(res, (;slot, tag)) : return (;slot, tag)
end
end
- end
+ end
+ end
+ allB ? res : nothing
+ end end
+ newmethod_mb = quote function query(mb::MessageBuffer, $(argssig_wild...))
+ for (depth, (src, tag)) in pairs(mb.buffer)
+ if isvariant(tag, ($(tagsymbol,))[1]) # a weird workaround for interpolating a symbol as a symbol
+ if _all($(nonwild_checks...)) && _all($(wild_checks...))
+ return (;depth, src, tag)
+ end
+ end
end
- end end)
+ end end
+ newmethod_rr = quote function query(ref::RegRef, $(argssig_wild...), ::Val{allB}=Val{false}()) where {allB}
+ res = NamedTuple{(:depth, :tag), Tuple{Int, Tag}}[]
+ for (depth, tag) in pairs(ref.reg.tags[ref.idx])
+ if isvariant(tag, ($(tagsymbol,))[1]) # a weird workaround for interpolating a symbol as a symbol
+ if _all($(nonwild_checks...)) && _all($(wild_checks...))
+ allB ? push!(res, (;depth, tag)) : return (;depth, tag)
+ end
+ end
+ end
+ allB ? res : nothing
+ end end
+ #println(sig)
+ #println(sig_wild)
+ #println(newmethod_reg)
+ eval(newmethod_reg)
+ eval(newmethod_mb) # TODO there is a lot of code duplication here
+ eval(newmethod_rr) # TODO there is a lot of code duplication here
+ end
+end
+
+"""Find an empty unlocked slot in a given [`Register`](@ref).
+
+```jldoctest
+julia> reg = Register(3); initialize!(reg[1], X); lock(reg[2]);
+
+julia> findfreeslot(reg) == reg[3]
+true
+
+julia> lock(findfreeslot(reg));
+
+julia> findfreeslot(reg) |> isnothing
+true
+```
+"""
+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
+end
+function Base.isassigned(r::Register,i::Int) # TODO erase
+ r.stateindices[i] != 0 # TODO this also usually means r.staterenfs[i] !== nothing - choose one and make things consistent
end
+Base.isassigned(r::RegRef) = isassigned(r.reg, r.idx)
diff --git a/src/representations.jl b/src/representations.jl
index 1d0f73d4..2ad19bc1 100644
--- a/src/representations.jl
+++ b/src/representations.jl
@@ -1,5 +1,3 @@
-export express
-
function apply!(state, indices, operation::Symbolic{AbstractOperator})
repr = default_repr(state)
apply!(state, indices, express(operation, repr, UseAsOperation()))
diff --git a/src/states_registers.jl b/src/states_registers.jl
new file mode 100644
index 00000000..e5aabe8b
--- /dev/null
+++ b/src/states_registers.jl
@@ -0,0 +1,54 @@
+# TODO better constructors
+# TODO am I overusing Ref
+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
+ registerindices::Vector{Int}
+ StateRef(state::Base.RefValue{S}, registers, registerindices) where {S} = new(state, registers, registerindices)
+end
+
+StateRef(state, registers, registerindices) = StateRef(Ref{Any}(copy(state)), registers, registerindices) # TODO same as above, this should not be forced to Any
+
+"""
+The main data structure in `QuantumSavory`, used to represent a quantum register in an arbitrary formalism.
+"""
+struct Register # TODO better type description
+ traits::Vector{Any}
+ reprs::Vector{Any}
+ backgrounds::Vector{Any}
+ staterefs::Vector{Union{Nothing,StateRef}}
+ stateindices::Vector{Int}
+ accesstimes::Vector{Float64} # TODO do not hardcode the type
+ locks::Vector{Any}
+ tags::Vector{Vector{Tag}} # TODO this is a rather inefficient way to store tags, but at least it provides a FIFO ordering; see issue #74
+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], [Vector{Tag}() for _ in traits])
+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,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))
+Register(nqubits::Int) = Register([Qubit() for _ in 1:nqubits])
+Register(nqubits::Int,repr::AbstractRepresentation) = Register(fill(Qubit(),nqubits),fill(repr,nqubits))
+Register(nqubits::Int,bg::AbstractBackground) = Register(fill(Qubit(),nqubits),fill(bg,nqubits))
+
+"""
+A reference to a [`Register`](@ref) slot, convenient for use with functions like [`apply!`](@ref), etc.
+
+```jldoctest
+julia> r = Register(2)
+ initialize!(r[1], X₁)
+ observable(r[1], X)
+0.9999999999999998 + 0.0im
+```
+"""
+struct RegRef
+ reg::Register
+ idx::Int
+end
+
+#Base.:(==)(r1::Register, r2::Register) =
diff --git a/src/states_registers_networks_getset.jl b/src/states_registers_networks_getset.jl
new file mode 100644
index 00000000..9bc46658
--- /dev/null
+++ b/src/states_registers_networks_getset.jl
@@ -0,0 +1,78 @@
+## Registers
+
+Base.getindex(r::Register, i::Int) = RegRef(r,i)
+Base.getindex(r::Register, C) = map(i->r[i], C)
+Base.length(r::Register) = length(r.stateindices)
+Base.iterate(r::Register, state=1) = state > length(r) ? nothing : (r[state],state+1)
+
+## Networks
+
+# Graph interface
+Graphs.add_vertex!(net::RegisterNet, a, b) = add_vertex!(net.graph, a, b)
+Graphs.vertices(net::RegisterNet) = vertices(net.graph)
+Graphs.edges(net::RegisterNet) = edges(net.graph)
+Graphs.neighbors(net::RegisterNet, v) = neighbors(net.graph, v)
+Graphs.adjacency_matrix(net::RegisterNet) = adjacency_matrix(net.graph)
+Graphs.ne(net::RegisterNet) = ne(net.graph)
+Graphs.nv(net::RegisterNet) = nv(net.graph)
+
+# Get register
+Base.getindex(net::RegisterNet, i::Int) = net.registers[i]
+# Get register slot reference
+Base.getindex(net::RegisterNet, i::Int, j::Int) = net.registers[i][j]
+# Get and set vertex metadata
+Base.getindex(net::RegisterNet, i::Int, k::Symbol) = net.vertex_metadata[i][k]
+Base.setindex!(net::RegisterNet, val, i::Int, k::Symbol) = begin net.vertex_metadata[i][k] = val end
+# Get and set edge metadata
+Base.getindex(net::RegisterNet, ij::Tuple{Int,Int}, k::Symbol) = net.edge_metadata[minmax(ij...)][k]
+function Base.setindex!(net::RegisterNet, val, ij::Tuple{Int,Int}, k::Symbol)
+ edge = minmax(ij...)
+ haskey(net.edge_metadata,edge) || (net.edge_metadata[edge] = Dict{Symbol,Any}())
+ net.edge_metadata[edge][k] = val
+end
+# Get and set directed edge metadata
+Base.getindex(net::RegisterNet, ij::Pair{Int,Int}, k::Symbol) = net.directed_edge_metadata[ij][k]
+function Base.setindex!(net::RegisterNet, val, ij::Pair{Int,Int}, k::Symbol)
+ edge = ij
+ haskey(net.directed_edge_metadata,edge) || (net.directed_edge_metadata[edge] = Dict{Symbol,Any}())
+ net.directed_edge_metadata[edge][k] = val
+end
+Base.getindex(net::RegisterNet, ij::Graphs.SimpleEdge, k::Symbol) = net[(ij.src, ij.dst),k]
+Base.setindex!(net::RegisterNet, val, ij::Graphs.SimpleEdge, k::Symbol) = begin net[(ij.src, ij.dst),k] = val end
+# Get and set with colon notation
+Base.getindex(net::RegisterNet, ::Colon) = net.registers
+Base.getindex(net::RegisterNet, ::Colon, j::Int) = [r[j] for r in net.registers]
+Base.getindex(net::RegisterNet, ::Colon, k::Symbol) = [m[k] for m in net.vertex_metadata]
+Base.getindex(net::RegisterNet, ::Tuple{Colon,Colon}, k::Symbol) = [net.edge_metadata[minmax(ij)...][k] for ij in edges(net)]
+Base.getindex(net::RegisterNet, ::Pair{Colon,Colon}, k::Symbol) = [net.directed_edge_metadata[ij][k] for ij in edges(net)]
+
+function Base.setindex!(net::RegisterNet, v, ::Colon, k::Symbol)
+ for m in net.vertex_metadata
+ m[k] = v
+ end
+end
+function Base.setindex!(net::RegisterNet, v, ::Tuple{Colon,Colon}, k::Symbol)
+ for ij in edges(net)
+ net[ij,k] = v
+ end
+end
+function Base.setindex!(net::RegisterNet, v, ::Pair{Colon,Colon}, k::Symbol)
+ for ij in edges(net)
+ net[ij,k] = v
+ end
+end
+function Base.setindex!(net::RegisterNet, @nospecialize(f::Function), ::Colon, k::Symbol)
+ for m in net.vertex_metadata
+ m[k] = f()
+ end
+end
+function Base.setindex!(net::RegisterNet, @nospecialize(f::Function), ::Tuple{Colon,Colon}, k::Symbol)
+ for ij in edges(net)
+ net[ij,k] = f()
+ end
+end
+function Base.setindex!(net::RegisterNet, @nospecialize(f::Function), ::Pair{Colon,Colon}, k::Symbol)
+ for ij in edges(net)
+ net[ij,k] = f()
+ end
+end
diff --git a/src/states_registers_networks_shows.jl b/src/states_registers_networks_shows.jl
new file mode 100644
index 00000000..42970a8f
--- /dev/null
+++ b/src/states_registers_networks_shows.jl
@@ -0,0 +1,45 @@
+function Base.show(io::IO, s::StateRef)
+ print(io, "State containing $(nsubsystems(s.state[])) subsystems in $(typeof(s.state[]).name.module) implementation")
+ print(io, "\n In registers:")
+ for (i,r) in zip(s.registerindices, s.registers)
+ if isnothing(r)
+ print(io, "\n not used")
+ else
+ print(io, "\n $(i)@$(objectid(r))")
+ end
+ end
+end
+
+function Base.show(io::IO, r::Register)
+ print(io, "Register with $(length(r.traits)) slots") # TODO make this length call prettier
+ print(io, ": [ ")
+ print(io, join(string.(typeof.(r.traits)), " | "))
+ print(io, " ]")
+ print(io, "\n Slots:")
+ for (i,s) in zip(r.stateindices, r.staterefs)
+ if isnothing(s)
+ print(io, "\n nothing")
+ else
+ print(io, "\n Subsystem $(i) of $(typeof(s.state[]).name.module).$(typeof(s.state[]).name.name) $(objectid(s.state[]))")
+ end
+ end
+end
+
+function Base.show(io::IO, net::RegisterNet)
+ print(io, "A network of $(length(net.registers)) registers in a graph of $(length(edges(net.graph))) edges\n")
+end
+
+function Base.show(io::IO, r::RegRef)
+ if get(io, :compact, false) | haskey(io, :typeinfo)
+ print(io, "Slot $(r.idx)")
+ else
+ print(io, "Slot $(r.idx)/$(length(r.reg.traits)) of Register $(objectid(r.reg))") # TODO make this length call prettier
+ print(io, "\nContent:")
+ i,s = r.reg.stateindices[r.idx], r.reg.staterefs[r.idx]
+ if isnothing(s)
+ print(io, "\n nothing")
+ else
+ print(io, "\n $(i) @ $(typeof(s.state[]).name.module).$(typeof(s.state[]).name.name) $(objectid(s.state[]))")
+ end
+ end
+end
diff --git a/src/tags.jl b/src/tags.jl
index d8fffd63..cb9e3de3 100644
--- a/src/tags.jl
+++ b/src/tags.jl
@@ -2,15 +2,42 @@
Symbol(::Symbol)
SymbolInt(::Symbol, ::Int)
SymbolIntInt(::Symbol, ::Int, ::Int)
+ SymbolIntIntInt(::Symbol, ::Int, ::Int, ::Int)
+ SymbolIntIntIntInt(::Symbol, ::Int, ::Int, ::Int, ::Int)
+ SymbolIntIntIntIntInt(::Symbol, ::Int, ::Int, ::Int, ::Int, ::Int)
+ SymbolIntIntIntIntIntInt(::Symbol, ::Int, ::Int, ::Int, ::Int, ::Int, ::Int)
Type(::DataType)
TypeInt(::DataType, ::Int)
TypeIntInt(::DataType, ::Int, ::Int)
+ TypeIntIntInt(::DataType, ::Int, ::Int, ::Int)
+ TypeIntIntIntInt(::DataType, ::Int, ::Int, ::Int, ::Int)
+ TypeIntIntIntIntInt(::DataType, ::Int, ::Int, ::Int, ::Int, ::Int)
+ TypeIntIntIntIntIntInt(::DataType, ::Int, ::Int, ::Int, ::Int, ::Int, ::Int)
TypeSymbol(::DataType, ::Symbol)
TypeSymbolInt(::DataType, ::Symbol, ::Int)
TypeSymbolIntInt(::DataType, ::Symbol, ::Int, ::Int)
+ Forward(::Tag, ::Int)
end
"""Tag types available in the taggging and tag-querying system.
See also: [`query`](@ref), [`tag!`](@ref), [`Wildcard`](@ref)"""
const tag_types = Tag'
+
+Base.getindex(tag::Tag, i::Int) = tag.data[i]
+Base.length(tag::Tag) = length(tag.data.data)
+Base.iterate(tag::Tag, state=1) = state > length(tag) ? nothing : (tag[state],state+1)
+
+function SumTypes.show_sumtype(io::IO, x::Tag)
+ data = SumTypes.unwrap(x)
+ sym = SumTypes.get_name(data)
+ if length(data.data) == 0
+ print(io, String(sym), "::Tag")
+ else
+ if data[1] isa DataType && data[1]!==Int
+ print(io, data[1](data[2:length(x)]...))
+ else
+ print(io, String(sym), '(', join((repr(field) for field ∈ data), ", "), ")::Tag")
+ end
+ end
+end
diff --git a/src/traits_and_defaults.jl b/src/traits_and_defaults.jl
new file mode 100644
index 00000000..ab0816c4
--- /dev/null
+++ b/src/traits_and_defaults.jl
@@ -0,0 +1,14 @@
+"""An abstract type for the various types of states that can be given to [`Register`](@ref) slots, e.g. qubit, harmonic oscillator, etc."""
+abstract type QuantumStateTrait end
+
+"""An abstract type for the various background processes that might be inflicted upon a [`Register`](@ref) slot, e.g. decay, dephasing, etc."""
+abstract type AbstractBackground end
+
+"""Specifies that a given register slot contains qubits."""
+struct Qubit <: QuantumStateTrait end
+"""Specifies that a given register slot contains qumodes."""
+struct Qumode <: QuantumStateTrait end
+
+# TODO move these definitions to a neater place
+default_repr(::Qubit) = QuantumOpticsRepr()
+default_repr(::Qumode) = QuantumOpticsRepr()
diff --git a/test/runtests.jl b/test/runtests.jl
index b493e30d..7fc4b624 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -29,10 +29,12 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA
@doset "observable"
@doset "noninstant_and_backgrounds_qubit"
@doset "noninstant_and_backgrounds_qumode"
-
+@doset "messagebuffer"
@doset "tags_and_queries"
+@doset "entanglement_tracker"
@doset "circuitzoo_api"
+@doset "circuitzoo_ent_swap"
@doset "circuitzoo_purification"
@doset "circuitzoo_superdense"
diff --git a/test/test_circuitzoo_ent_swap.jl b/test/test_circuitzoo_ent_swap.jl
new file mode 100644
index 00000000..26b165c7
--- /dev/null
+++ b/test/test_circuitzoo_ent_swap.jl
@@ -0,0 +1,57 @@
+using QuantumSavory
+using QuantumSavory.CircuitZoo
+using Test
+using QuantumSavory.CircuitZoo: EntanglementSwap, LocalEntanglementSwap
+
+const perfect_pair_stab = StabilizerState("XX ZZ")
+const perfect_pair = (Z1⊗Z1 + Z2⊗Z2) / sqrt(2)
+
+for pair in (perfect_pair, perfect_pair_stab), rep in 1:10
+ net = RegisterNet([Register(1), Register(2), Register(1)])
+ initialize!((net[1][1], net[2][1]), pair)
+ initialize!((net[3][1], net[2][2]), pair)
+ EntanglementSwap()(net[2][1], net[1][1], net[2][2], net[3][1])
+ @test !isassigned(net[2][1]) && !isassigned(net[2][2])
+ @test observable((net[1][1], net[3][1]), Z⊗Z) ≈ 1
+ @test observable((net[1][1], net[3][1]), X⊗X) ≈ 1
+end
+
+for pair in (perfect_pair, perfect_pair_stab), rep in 1:10
+ net = RegisterNet([Register(1), Register(2), Register(1)])
+ initialize!((net[1][1], net[2][1]), pair)
+ initialize!((net[3][1], net[2][2]), pair)
+ mx, mz = LocalEntanglementSwap()(net[2][1], net[2][2])
+ mx == 2 && apply!(net[1][1], Z)
+ mz == 2 && apply!(net[3][1], X)
+ @test !isassigned(net[2][1]) && !isassigned(net[2][2])
+ @test observable((net[1][1], net[3][1]), Z⊗Z) ≈ 1
+ @test observable((net[1][1], net[3][1]), X⊗X) ≈ 1
+end
+
+for pair in (perfect_pair, perfect_pair_stab), n in 3:10, rep in 1:10
+ net = RegisterNet([Register(2) for i in 1:n])
+ for i in 1:n-1
+ initialize!((net[i][1], net[i+1][2]), pair)
+ end
+ for i in 2:n-1
+ EntanglementSwap()(net[i][2], net[1][1], net[i][1], net[i+1][2])
+ end
+ @test all(!isassigned(net[i][1]) & !isassigned(net[i][2]) for i in 2:n-1)
+ @test observable((net[1][1], net[n][2]), Z⊗Z) ≈ 1
+ @test observable((net[1][1], net[n][2]), X⊗X) ≈ 1
+end
+
+for pair in (perfect_pair, perfect_pair_stab), n in 3:10, rep in 1:10
+ net = RegisterNet([Register(2) for i in 1:n])
+ for i in 1:n-1
+ initialize!((net[i][1], net[i+1][2]), pair)
+ end
+ for i in 2:n-1
+ mx, mz = LocalEntanglementSwap()(net[i][2], net[i][1])
+ mx == 2 && apply!(net[1][1], Z)
+ mz == 2 && apply!(net[i+1][2], X)
+ end
+ @test all(!isassigned(net[i][1]) & !isassigned(net[i][2]) for i in 2:n-1)
+ @test observable((net[1][1], net[n][2]), Z⊗Z) ≈ 1
+ @test observable((net[1][1], net[n][2]), X⊗X) ≈ 1
+end
diff --git a/test/test_entanglement_tracker.jl b/test/test_entanglement_tracker.jl
new file mode 100644
index 00000000..3698fd06
--- /dev/null
+++ b/test/test_entanglement_tracker.jl
@@ -0,0 +1,116 @@
+using Revise
+using QuantumSavory
+using ResumableFunctions
+using ConcurrentSim
+using QuantumSavory.ProtocolZoo
+using QuantumSavory.ProtocolZoo: EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ
+using Graphs
+using Test
+
+if isinteractive()
+ using Logging
+ logger = ConsoleLogger(Logging.Debug; meta_formatter=(args...)->(:black,"",""))
+ global_logger(logger)
+ println("Logger set to debug")
+end
+
+##
+
+# without an entanglement tracker
+
+for i in 1:10
+
+ net = RegisterNet([Register(3), Register(4), Register(2), Register(3)])
+ sim = get_time_tracker(net)
+
+
+ entangler1 = EntanglerProt(sim, net, 1, 2; rounds=1)
+ @process entangler1()
+ run(sim, 20)
+
+ @test net[1].tags == [[Tag(EntanglementCounterpart, 2, 1)],[],[]]
+
+
+ entangler2 = EntanglerProt(sim, net, 2, 3; rounds=1)
+ @process entangler2()
+ run(sim, 40)
+ entangler3 = EntanglerProt(sim, net, 4, 3; rounds=1)
+ @process entangler3()
+ run(sim, 60)
+
+ @test net[1].tags == [[Tag(EntanglementCounterpart, 2, 1)],[],[]]
+ @test net[2].tags == [[Tag(EntanglementCounterpart, 1, 1)],[Tag(EntanglementCounterpart, 3, 1)],[],[]]
+ @test net[3].tags == [[Tag(EntanglementCounterpart, 2, 2)],[Tag(EntanglementCounterpart, 4, 1)]]
+ @test net[4].tags == [[Tag(EntanglementCounterpart, 3, 2)],[],[]]
+
+ @test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false
+
+
+ swapper2 = SwapperProt(sim, net, 2; rounds=1)
+ swapper3 = SwapperProt(sim, net, 3; rounds=1)
+ @process swapper2()
+ @process swapper3()
+ run(sim, 80)
+
+ # In the absence of an entanglement tracker the tags will not all be updated
+ @test net[1].tags == [[Tag(EntanglementCounterpart, 2, 1)],[],[]]
+ @test net[2].tags == [[Tag(EntanglementHistory, 1, 1, 3, 1, 2)],[Tag(EntanglementHistory, 3, 1, 1, 1, 1)],[],[]]
+ @test net[3].tags == [[Tag(EntanglementHistory, 2, 2, 4, 1, 2)],[Tag(EntanglementHistory, 4, 1, 2, 2, 1)]]
+ @test net[4].tags == [[Tag(EntanglementCounterpart, 3, 2)],[],[]]
+
+ @test isassigned(net[1][1]) && isassigned(net[4][1])
+ @test !isassigned(net[2][1]) && !isassigned(net[3][1])
+ @test !isassigned(net[2][2]) && !isassigned(net[3][2])
+
+ @test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false
+
+end
+
+##
+
+using Revise
+using QuantumSavory
+using ResumableFunctions
+using ConcurrentSim
+using QuantumSavory.ProtocolZoo
+using QuantumSavory.ProtocolZoo: EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ
+using Graphs
+using Test
+using Random
+
+if isinteractive()
+ using Logging
+ logger = ConsoleLogger(Logging.Warn; meta_formatter=(args...)->(:black,"",""))
+ global_logger(logger)
+ println("Logger set to debug")
+end
+
+##
+
+# same but this time with an entanglement tracker
+
+for i in 1:30, n in 2:30
+ #@show n, i
+ net = RegisterNet([Register(j+3) for j in 1:n])
+ sim = get_time_tracker(net)
+ 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, randomize=true)
+ @process eprot()
+ end
+ for j in 2:n-1
+ swapper = SwapperProt(sim, net, j; rounds=1)
+ @process swapper()
+ end
+ run(sim, 200)
+
+ q1 = query(net[1], EntanglementCounterpart, n, ❓)
+ q2 = query(net[n], EntanglementCounterpart, 1, ❓)
+ @test q1.tag[2] == n
+ @test q2.tag[2] == 1
+ @test observable((q1.slot, q2.slot), Z⊗Z) ≈ 1
+ @test observable((q1.slot, q2.slot), X⊗X) ≈ 1
+end
diff --git a/test/test_examples.jl b/test/test_examples.jl
index 14e087ab..4ad1e2cf 100644
--- a/test/test_examples.jl
+++ b/test/test_examples.jl
@@ -10,6 +10,13 @@
include("../examples/firstgenrepeater/6.1_compare_formalisms_noplot.jl")
end
+@safetestset "firstgenrepeater_v2" begin
+ if get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true"
+ include("../examples/firstgenrepeater_v2/1_entangler_example.jl")
+ include("../examples/firstgenrepeater_v2/2_swapper_example.jl")
+ end
+end
+
@safetestset "colorcentermodularcluster" begin
include("../examples/colorcentermodularcluster/1_time_to_connected.jl")
if get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true"
diff --git a/test/test_messagebuffer.jl b/test/test_messagebuffer.jl
new file mode 100644
index 00000000..ba1a27f6
--- /dev/null
+++ b/test/test_messagebuffer.jl
@@ -0,0 +1,31 @@
+using QuantumSavory
+using QuantumSavory: tag_types
+using ResumableFunctions, ConcurrentSim
+using Test
+
+net = RegisterNet([Register(3), Register(2), Register(3)])
+env = get_time_tracker(net);
+@resumable function receive_tags(env)
+ while true
+ mb = messagebuffer(net, 2)
+ @yield wait(mb)
+ msg = querydelete!(mb, :second_tag, ❓, ❓)
+ if isnothing(msg)
+ # println("nothing")
+ else
+ #println("$(msg.tag) received from node $(msg.src)")
+ end
+ end
+end
+@resumable function send_tags(env)
+ @yield timeout(env, 1.0)
+ put!(channel(net, 1=>2), Tag(:my_tag))
+ @yield timeout(env, 2.0)
+ put!(channel(net, 3=>2), Tag(:second_tag, 123, 456))
+end
+@process send_tags(env);
+@process receive_tags(env);
+run(env, 10)
+
+@test query(messagebuffer(net, 2), :second_tag, ❓, ❓) === nothing
+@test query(messagebuffer(net, 2), :my_tag).tag == Tag(:my_tag)
diff --git a/test/test_tags_and_queries.jl b/test/test_tags_and_queries.jl
index 1cc81b35..0d323ba4 100644
--- a/test/test_tags_and_queries.jl
+++ b/test/test_tags_and_queries.jl
@@ -1,14 +1,34 @@
using QuantumSavory
+using QuantumSavory: tag_types
using Test
+@test tag_types.SymbolIntInt(:symbol1, 4, 5) == Tag(:symbol1, 4, 5)
+
r = Register(10)
tag!(r[1], :symbol1, 2, 3)
tag!(r[2], :symbol1, 4, 5)
tag!(r[5], Int, 4, 5)
-@test query(r, :symbol1, 4, ❓) == (r[2], tag_types.SymbolIntInt(:symbol1, 4, 5))
-@test query(r, :symbol1, 4, 5) == (r[2], tag_types.SymbolIntInt(:symbol1, 4, 5))
-@test query(r, :symbol1, ❓, ❓) == (r[1], tag_types.SymbolIntInt(:symbol1, 2, 3))
+@test Tag(:symbol1, 2, 3) == tag_types.SymbolIntInt(:symbol1, 2, 3)
+@test query(r, :symbol1, 4, ❓) == (slot=r[2], tag=tag_types.SymbolIntInt(:symbol1, 4, 5))
+@test query(r, :symbol1, 4, 5) == (slot=r[2], tag=tag_types.SymbolIntInt(:symbol1, 4, 5))
+@test query(r, :symbol1, ❓, ❓) == (slot=r[1], tag=tag_types.SymbolIntInt(:symbol1, 2, 3))
@test query(r, :symbol2, ❓, ❓) == nothing
-@test query(r, Int, 4, 5) == (r[5], tag_types.TypeIntInt(Int, 4, 5))
+@test query(r, Int, 4, 5) == (slot=r[5], tag=tag_types.TypeIntInt(Int, 4, 5))
@test query(r, Float32, 4, 5) == nothing
+@test query(r, Int, 4, >(5)) == nothing
+@test query(r, Int, 4, <(6)) == (slot=r[5], tag=tag_types.TypeIntInt(Int, 4, 5))
+
+@test queryall(r, :symbol1, ❓, ❓) == [(slot=r[1], tag=tag_types.SymbolIntInt(:symbol1, 2, 3)), (slot=r[2], tag=tag_types.SymbolIntInt(:symbol1, 4, 5))]
+@test isempty(queryall(r, :symbol2, ❓, ❓))
+
+@test query(r[2], Tag(:symbol1, 4, 5)) == (depth=1, tag=Tag(:symbol1, 4, 5))
+@test queryall(r[2], Tag(:symbol1, 4, 5)) == [(depth=1, tag=Tag(:symbol1, 4, 5))]
+@test query(r[2], :symbol1, 4, 5) == (depth=1, tag=Tag(:symbol1, 4, 5))
+@test queryall(r[2], :symbol1, 4, 5) == [(depth=1, tag=Tag(:symbol1, 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 querydelete!(r[2], :symbol1, 4, ❓) == Tag(:symbol1, 4, 5)
+@test querydelete!(r[2], :symbol1, 4, ❓) === nothing