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

Added QuantumChannel #53

Merged
merged 14 commits into from
Nov 9, 2023
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
QuantumSavoryMakie = "Makie"

[compat]
ConcurrentSim = "1.1"
ConcurrentSim = "1.4"
DocStringExtensions = "0.9"
Graphs = "1.7.3"
IterTools = "1.4.0"
Expand Down
6 changes: 6 additions & 0 deletions src/QuantumSavory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export apply!, traceout!, removebackref!
export project_traceout! #TODO should move to QuantumInterface

import ConcurrentSim
using ResumableFunctions

@reexport using QuantumSymbolics
using QuantumSymbolics:
Expand All @@ -29,6 +30,9 @@ export Qubit, Qumode, QuantumStateTrait,
CliffordRepr, QuantumOpticsRepr, QuantumMCRepr,
UseAsState, UseAsObservable, UseAsOperation,
AbstractBackground
export QuantumChannel, take!, put!
ba2tro marked this conversation as resolved.
Show resolved Hide resolved


#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

Expand Down Expand Up @@ -330,6 +334,8 @@ include("concurrentsim.jl")

include("plots.jl")

include("quantumchannel.jl")

include("CircuitZoo/CircuitZoo.jl")

include("StatesZoo/StatesZoo.jl")
Expand Down
24 changes: 0 additions & 24 deletions src/concurrentsim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,6 @@ end
end
end

mutable struct DelayChannel{T}
delay::Float64
store::Store{T}
end
function DelayChannel(env::Environment, delay::Float64)
return DelayChannel(delay, Store{Any}(env))
end
function DelayChannel{T}(env::Environment, delay::Float64) where T
return DelayChannel(delay, Store{T}(env))
end

@resumable function latency(env::Environment, channel::DelayChannel, value)
@yield timeout(channel.store.env, channel.delay)
put(channel.store, value)
end

function ConcurrentSim.put(channel::DelayChannel, value) # TODO rename to the ones from Base
@process latency(channel.store.env, channel, value) # results in the scheduling of all events generated by latency
end

function ConcurrentSim.get(channel::DelayChannel) # TODO rename to the ones from Base
get(channel.store) # returns an element stored in the cable store
end

##

function get_time_tracker(rn::RegisterNet)
Expand Down
89 changes: 89 additions & 0 deletions src/quantumchannel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
QuantumChannel for transmitting qubits as `RegRef`s from one register to another in a quantum protocol simulation,
with a channel delay and under the influence of a background process.
The function `put!` is used to put the `RegRef` containing the qubit in the channel, which can then be received by
the receiving register after a specified delay using the take! method in a synchronous way.

```jldoctest
julia> using QuantumSavory; using ResumableFunctions; using ConcurrentSim

julia> bell = (Z1⊗Z1 + Z2⊗Z2)/sqrt(2.0); regA = Register(2); regB = Register(2); initialize!((regA[1], regB[2]), bell);
ba2tro marked this conversation as resolved.
Show resolved Hide resolved

julia> sim = Simulation();

julia> queue = DelayQueue{RegRef}(sim, 10.0)
DelayQueue{RegRef}(QueueStore{RegRef, Int64}, 10.0)

julia> qc = QuantumChannel(queue)
QuantumChannel(Qubit(), DelayQueue{RegRef}(QueueStore{RegRef, Int64}, 10.0), nothing)

julia> @resumable function alice_node(env, qc)
println("Putting Alice's qubit in the channel at ", now(env))
put!(qc, regA[1])
end
alice_node (generic function with 1 method)

julia> @resumable function bob_node(env, qc)
@yield @process take!(env, qc, regB[1]) # wait for the process with delay to complete
println("Taking the qubit from alice at ", now(env))
end
bob_node (generic function with 1 method)

julia> @process alice_node(sim, qc); @process bob_node(sim, qc);

julia> run(sim)
Putting Alice's qubit in the channel at 0.0
Taking the qubit from alice at 10.0

julia> regA
Register with 2 slots: [ Qubit | Qubit ]
Slots:
nothing
nothing

julia> regB
Register with 2 slots: [ Qubit | Qubit ]
Slots:
Subsystem 1 of QuantumOpticsBase.Ket 12382959472027850978
Subsystem 2 of QuantumOpticsBase.Ket 12382959472027850978
```
"""
struct QuantumChannel
trait::Qubit
queue::ConcurrentSim.DelayQueue{RegRef}
background::Any
end

function QuantumChannel(queue::ConcurrentSim.DelayQueue{RegRef}, background=nothing)
QuantumChannel(Qubit(), queue, background)
end

function Base.put!(qc::QuantumChannel, rref::RegRef, Δt=nothing)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
if xor(isnothing(qc.background), isnothing(Δt))
throw(ArgumentError(lazy"""
Either both background and Δt should be nothing or both should be initialized to appropriate values
"""))
elseif !isnothing(Δt) # if both are not nothing
uptotime!(rref.reg.staterefs[rref.idx], rref.reg.stateindices[rref.idx], qc.background, Δt)
end

put!(qc.queue, rref)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
end

# should we mandate that rref_rec.reg is not initialized beforehand
# should other register attributes of the sent regref be copied over like reprs[idx], backgrounds[idx], accesstimes[idx], env and locks[idx]
@resumable function Base.take!(env, qc::QuantumChannel, rref_rec::RegRef)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
rref = @yield take!(qc.queue)

rref_rec.reg.staterefs[rref_rec.idx] = rref.reg.staterefs[rref.idx]
rref_rec.reg.stateindices[rref_rec.idx] = rref.reg.stateindices[rref.idx]

# update the stateref
sref = rref.reg.staterefs[rref.idx]
sref.registers[rref.idx] = rref_rec.reg
sref.registerindices[rref.idx] = rref_rec.idx

# erase the state from the sending register
rref.reg.staterefs[rref.idx] = nothing
rref.reg.stateindices[rref.idx] = 0
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ end

println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREADS = $(Sys.CPU_THREADS)`...")

@doset "quantumchannel"
@doset "register_interface"
@doset "project_traceout"
@doset "observable"
Expand Down
34 changes: 34 additions & 0 deletions test/test_quantumchannel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using QuantumSavory
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
using ResumableFunctions
using ConcurrentSim
using Test

bell = (Z1⊗Z1 + Z2⊗Z2)/sqrt(2.0)
regA = Register(2)
regB = Register(2)
initialize!((regA[1], regB[2]), bell)

sim = Simulation()

# Delay queue for quantum channel
queue = DelayQueue{RegRef}(sim, 10.0)

qc = QuantumChannel(queue)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved

@resumable function alice_node(env, qc)
put!(qc, regA[1])
end

@resumable function bob_node(env, qc)
@yield @process take!(env, qc, regB[1])
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
end

@process alice_node(sim, qc)
@process bob_node(sim, qc)

run(sim)

sref = regB.staterefs[1]

# the above code puts both the qubits of the state in the same register
@test sref.registers[1] == sref.registers[2]
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
Loading