Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Entanglement simulation on a grid with custom predicates #90

Merged
merged 11 commits into from
Feb 12, 2024
2 changes: 1 addition & 1 deletion examples/firstgenrepeater_v2/2_swapper_example.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ for (;src, dst) in edges(network)
@process eprot()
end
for node in vertices(network)
sprot = SwapperProt(sim, network, node; nodeL = <(node), nodeR = >(node))
sprot = SwapperProt(sim, network, node; nodeL = <(node), nodeH = >(node), chooseL = argmin, chooseH = argmax)
@process sprot()
end

Expand Down
33 changes: 20 additions & 13 deletions src/ProtocolZoo/ProtocolZoo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@
end
end

function random_index(arr)
return rand(keys(arr))
end

"""
$TYPEDEF
Expand All @@ -146,17 +149,21 @@

$FIELDS
"""
@kwdef struct SwapperProt{L,R,LT} <: AbstractProtocol where {L<:Union{Int,<:Function,Wildcard}, R<:Union{Int,<:Function,Wildcard}, LT<:Union{Float64,Nothing}}
@kwdef struct SwapperProt{NL,NH,CL,CH,LT} <: AbstractProtocol where {NL<:Union{Int,<:Function,Wildcard}, NH<:Union{Int,<:Function,Wildcard}, CL<:Function, CH<:Function, 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 = ❓
"""the vertex of one of the remote nodes for the swap, arbitrarily refered to as the "low" node (or a predicate function or a wildcard); if you are working on a repeater chain, a good choice is `<(current_node)`, i.e. any node to the "left" of the current node"""

Check warning on line 159 in src/ProtocolZoo/ProtocolZoo.jl

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"refered" should be "referred".
nodeL::NL = ❓
"""the vertex of the other remote node for the swap, the "high" counterpart of `nodeL`; if you are working on a repeater chain, a good choice is `>(current_node)`, i.e. any node to the "right" of the current node"""
nodeH::NH = ❓ # TODO consider changing the default to random
"""the `nodeL` predicate can return many positive candidates; `chooseL` picks one of them (by index into the array of filtered `nodeL` results), defaults to a random pick `arr->rand(keys(arr))`; if you are working on a repeater chain a good choice is `argmin`, i.e. the node furthest to the "left" """
chooseL::CL = random_index
"""the `nodeH` counterpart for `chooseH`; if you are working on a repeater chain a good choice is `argmax`, i.e. the node furthest to the "right" """
chooseH::CH = random_index
"""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)"""
Expand All @@ -175,7 +182,7 @@
rounds = prot.rounds
while rounds != 0
reg = prot.net[prot.node]
qubit_pair = findswapablequbits(prot.net,prot.node)
qubit_pair = findswapablequbits(prot.net, prot.node, prot.nodeL, prot.nodeH, prot.chooseL, prot.chooseH)
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)
Expand Down Expand Up @@ -212,16 +219,16 @@
end
end

function findswapablequbits(net,node) # TODO parameterize the query predicates and the findmin/findmax
function findswapablequbits(net, node, pred_low, pred_high, choose_low, choose_high) # 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)
low_nodes = queryall(reg, EntanglementCounterpart, pred_low, ❓; locked=false, assigned=true)
high_nodes = queryall(reg, EntanglementCounterpart, pred_high, ❓; 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]
(isempty(low_nodes) || isempty(high_nodes)) && return nothing
_, il = choose_low(n->n.tag[2], low_nodes) # TODO make [2] into a nice named property
_, ih = choose_high(n->n.tag[2], high_nodes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, you are saying I should have removed the underscore. Indeed, the underscore should not be there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, in case of a vector of NamedTuple the output is the NamedTuple, the above snippet takes in a simple vector.

julia> using QuantumSavory; using QuantumSavory.ProtocolZoo: EntanglementCounterpart;

julia> reg = Register(5);

julia> a = [(slot=reg[9], tag=Tag(EntanglementCounterpart, 4, 1)), (slot=reg[8], tag=Tag(EntanglementCounterpart, 2, 5)), (slot=reg[4], tag=Tag(EntanglementCounterpart, 3, 1)), (slot=reg[1], tag=Tag(EntanglementCounterpart, 3, 1))]
4-element Vector{@NamedTuple{slot::RegRef, tag::Tag}}:
 (slot = Slot 9, tag = Entangled to 4.1)
 (slot = Slot 8, tag = Entangled to 2.5)
 (slot = Slot 4, tag = Entangled to 3.1)
 (slot = Slot 1, tag = Entangled to 3.1)

julia> argmax(n->n.tag[2], a)
(slot = Slot 9, tag = Entangled to 4.1)

Copy link
Member

@Krastanov Krastanov Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, it should be just il = choose_low(low_nodes). Then it all works, right? argmax has way more signatures than I expected. With that change the random_index function should also work. Apologies for the only partial update.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argmax(argmin) returns the tuple: (slot = Slot 9, tag = Entangled to 4.1)
we can't use it to index the array of NamedTuple, so it doesn't work like that, if we want to do argmax, we would have to return il and ih directly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argmax((n->n.tag[2] for n in low_nodes))

Start from scratch here: what is the point of this change -- it is to make it easy for a user to provide a chooser function that gives you the preferred node to select. You can not have such a function depend on the internals of your code, you can not expect the user to know of it. Apologies for the repeated typos though, those are on me.

Copy link
Member Author

@ba2tripleO ba2tripleO Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change totally makes sense. The problem is just that it the way it was implemented before(using argmax or argmin), it didn't work with indexing.
The third comment suggests doing il = choose_low(low_nodes), that throws an error:

using QuantumSavory; using QuantumSavory.ProtocolZoo: EntanglementCounterpart;

reg = Register(5);

low_nodes = [(slot=reg[9], tag=Tag(EntanglementCounterpart, 4, 1)), (slot=reg[8], tag=Tag(EntanglementCounterpart, 2, 5)), (slot=reg[4], tag=Tag(EntanglementCounterpart, 3, 1)), (slot=reg[1], tag=Tag(EntanglementCounterpart, 3, 1))]

choose_low = argmin
choose_low(low_nodes)
ERROR: MethodError: no method matching isless(::RegRef, ::RegRef)

Closest candidates are:
  isless(!Matched::Missing, ::Any)
   @ Base missing.jl:87
  isless(::Any, !Matched::Missing)
   @ Base missing.jl:88

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check my last comment. Does this work: argmax((n->n.tag[2] for n in low_nodes)) or something to that effect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation comment also helps with this:

the `nodeL` predicate can return many positive candidates; `chooseL` picks one of them (by index into the array of filtered `nodeL` results), defaults to a random pick `arr->rand(keys(arr))`; if you are working on a repeater chain a good choice is `argmin`, i.e. the node furthest to the "left"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks, I got really confused in the beginning, sorry about that

return low_nodes[il], high_nodes[ih]
end
Krastanov marked this conversation as resolved.
Show resolved Hide resolved


Expand Down
4 changes: 2 additions & 2 deletions src/queries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ for (tagsymbol, tagvariant) in pairs(tag_types)
sig_wild = collect(sig)
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]
wild_checks = [:(isa($(args[i]),Wildcard) || $(args[i])(tag[$i])) for i in idx]
nonwild_checks = [:(tag[$i]==$(args[i])) for i in complement_idx]
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)
Expand Down
6 changes: 3 additions & 3 deletions src/tags.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ end
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)
Base.getindex(tag::Tag, i::Int) = SumTypes.unwrap(tag)[i]
Base.length(tag::Tag) = length(SumTypes.unwrap(tag).data)
Base.iterate(tag::Tag, state=1) = state > length(tag) ? nothing : (SumTypes.unwrap(tag)[state],state+1)

function SumTypes.show_sumtype(io::IO, x::Tag)
data = SumTypes.unwrap(x)
Expand Down
6 changes: 3 additions & 3 deletions test/test_entanglement_tracker.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ for i in 1:10
@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)
swapper2 = SwapperProt(sim, net, 2; nodeL = <(2), nodeH = >(2), chooseL = argmin, chooseH = argmax, rounds = 1)
swapper3 = SwapperProt(sim, net, 3; nodeL = <(3), nodeH = >(3), chooseL = argmin, chooseH = argmax, rounds = 1)
@process swapper2()
@process swapper3()
run(sim, 80)
Expand Down Expand Up @@ -102,7 +102,7 @@ for i in 1:30, n in 2:30
@process eprot()
end
for j in 2:n-1
swapper = SwapperProt(sim, net, j; rounds=1)
swapper = SwapperProt(sim, net, j; nodeL = <(j), nodeH = >(j), chooseL = argmin, chooseH = argmax, rounds = 1)
@process swapper()
end
run(sim, 200)
Expand Down
145 changes: 145 additions & 0 deletions test/test_entanglement_tracker_grid.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
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

## Custom Predicates
function top_left(net, node, x)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am waiting on CI and will then merge. However, this is an O(n) check for something that should be O(1).

From a node index get its x y coordinates (currently you get only one of them).
Then do the same for the other node.

Then just compare them to verify that you are to the left (one comparison) and to the top (second comparison).

No need for a for loop.

Submit a PR with the fix and feel free to merge it. It should also make this function much simpler.

n = sqrt(size(net.graph)[1]) # grid size
a = (node ÷ n) + 1 # row number
for i in 1:a-1
if x == (i-1)*n + i
return true
end
end
return false
end

function bottom_right(net, node, x)
n = sqrt(size(net.graph)[1]) # grid size
a = (node ÷ n) + 1 # row number
for i in a+1:n
if x == (i-1)*n + i
return true
end
end
return false
end

## Simulation

#without entanglement tracker
for i in 1:10
graph = grid([4, 4])
add_edge!(graph, 1, 6)
add_edge!(graph, 6, 11)
add_edge!(graph, 11, 16)

net = RegisterNet(graph, [Register(3) for i in 1:16])
sim = get_time_tracker(net)


entangler1 = EntanglerProt(sim, net, 1, 6; rounds=1)
@process entangler1()
run(sim, 20)

@test net[1].tags == [[Tag(EntanglementCounterpart, 6, 1)],[],[]]


entangler2 = EntanglerProt(sim, net, 6, 11; rounds=1)
@process entangler2()
run(sim, 40)
entangler3 = EntanglerProt(sim, net, 11, 16; rounds=1)
@process entangler3()
run(sim, 60)

@test net[1].tags == [[Tag(EntanglementCounterpart, 6, 1)],[],[]]
@test net[6].tags == [[Tag(EntanglementCounterpart, 1, 1)],[Tag(EntanglementCounterpart, 11, 1)],[]]
@test net[11].tags == [[Tag(EntanglementCounterpart, 6, 2)],[Tag(EntanglementCounterpart, 16, 1)], []]
@test net[16].tags == [[Tag(EntanglementCounterpart, 11, 2)],[],[]]

@test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false

l1(x) = top_left(net, 6, x)
h1(x) = bottom_right(net, 6, x)
swapper2 = SwapperProt(sim, net, 6; nodeL=l1, nodeH=h1, rounds=1)
l2(x) = top_left(net, 11, x)
h2(x) = bottom_right(net, 11, x)
swapper3 = SwapperProt(sim, net, 11; nodeL=l2, nodeH=h2, 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, 6, 1)],[],[]]
@test net[6].tags == [[Tag(EntanglementHistory, 1, 1, 11, 1, 2)],[Tag(EntanglementHistory, 11, 1, 1, 1, 1)],[]]
@test net[11].tags == [[Tag(EntanglementHistory, 6, 2, 16, 1, 2)],[Tag(EntanglementHistory, 16, 1, 6, 2, 1)], []]
@test net[16].tags == [[Tag(EntanglementCounterpart, 11, 2)],[],[]]

@test isassigned(net[1][1]) && isassigned(net[16][1])
@test !isassigned(net[6][1]) && !isassigned(net[11][1])
@test !isassigned(net[6][2]) && !isassigned(net[11][2])

@test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false

end

# with entanglement tracker
for n in 4:10
graph = grid([n,n])

diag_pairs = []
diag_nodes = []
reg_num = 1 # starting register
for i in 1:n-1 # a grid with n nodes has n-1 pairs of diagonal nodes
push!(diag_pairs, (reg_num, reg_num+n+1))
push!(diag_nodes, reg_num)
reg_num += n + 1
end
push!(diag_nodes, n^2)

for (src, dst) in diag_pairs # need edges down the diagonal to establish cchannels and qchannels between the diagonal nodes
add_edge!(graph, src, dst)
end

net = RegisterNet(graph, [Register(8) for i in 1:n^2])

sim = get_time_tracker(net)

for (src, dst) in diag_pairs
eprot = EntanglerProt(sim, net, src, dst; rounds=1, randomize=true)
@process eprot()
end

for i in 2:n-1
l(x) = top_left(net, diag_nodes[i], x)
h(x) = bottom_right(net, diag_nodes[i], x)
swapper = SwapperProt(sim, net, diag_nodes[i]; nodeL = l, nodeH = h, rounds = 1)
@process swapper()
end

for v in diag_nodes
tracker = EntanglementTracker(sim, net, v)
@process tracker()
end

run(sim, 200)

q1 = query(net[1], EntanglementCounterpart, diag_nodes[n], ❓)
q2 = query(net[diag_nodes[n]], EntanglementCounterpart, 1, ❓)
@test q1.tag[2] == diag_nodes[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
Loading