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


#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
26 changes: 1 addition & 25 deletions src/concurrentsim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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, DelayChannel, get_time_tracker
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
return :(@info(@sprintf("t=%.4f @ %05d : %s", now($(esc(env))), active_process($(esc(env))).bev.id, $(esc(msg)))))
Expand Down 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{Register}(sim, 10.0)
DelayQueue{Register}(QueueStore{Register, Int64}, 10.0)

julia> qc = QuantumChannel(queue)
QuantumChannel(Qubit(), DelayQueue{Register}(QueueStore{Register, 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 take!(qc, regB[1])
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{T}
trait::T
queue::ConcurrentSim.DelayQueue{Register}
background::Any
end

QuantumChannel(queue::ConcurrentSim.DelayQueue{Register}, background=nothing, trait=Qubit()) = QuantumChannel(trait, queue, background)

QuantumChannel(env::ConcurrentSim.Simulation, delay, background=nothing, trait=Qubit()) = QuantumChannel(ConcurrentSim.DelayQueue{Register}(env, delay), background, trait)
Register(qc::QuantumChannel) = Register([qc.trait], [qc.background])

function Base.put!(qc::QuantumChannel, rref::RegRef)
if !isnothing(qc.background)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
uptotime!(rref, ConcurrentSim.now(qc.queue.store.env))
end

channel_reg = Register(qc)
swap!(rref, channel_reg[1])
Copy link
Member

Choose a reason for hiding this comment

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

does channel_reg has its time value correctly set after the swap!?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you mean the accesstimes attribute? No, it only swaps the staterefs and stateindices of the RegRefs, should we edit the current swap! to do so?


if !isnothing(qc.background)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
uptotime!(channel_reg[1], qc.queue.delay)
Copy link
Member

Choose a reason for hiding this comment

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

If swap! properly updates the time value, then this should be qc.queue.delay+now(qc.queue.store.env)

end

put!(qc.queue, channel_reg)
end


@resumable function post_take(env, take_event, rref)
channel_reg = @yield take_event
QuantumSavory.swap!(channel_reg[1], rref)

Check warning on line 80 in src/quantumchannel.jl

View check run for this annotation

Codecov / codecov/patch

src/quantumchannel.jl#L78-L80

Added lines #L78 - L80 were not covered by tests
end

function Base.take!(qc::QuantumChannel, rref::RegRef)
if isassigned(rref)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved
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).")
end
take_event = take!(qc.queue)
@process post_take(qc.queue.store.env, take_event, rref)
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
120 changes: 120 additions & 0 deletions test/test_quantumchannel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
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{Register}(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 take!(qc, regB[1])
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]

## Test with the second constructor
regA = Register(2)
regB = Register(2)
initialize!((regA[1], regB[2]), bell)

sim = Simulation()

qc = QuantumChannel(sim, 10.0)

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

@resumable function bob_node(env, qc)
@yield take!(qc, regB[1])
end

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

run(sim)

sref = regB.staterefs[1]

@test sref.registers[1] == sref.registers[2]

## Test with T1Decay

regA = Register(2)
regB = Register(2)
initialize!((regA[1], regB[2]), bell)

sim = Simulation()

qc = QuantumChannel(sim, 10.0, T1Decay(0.1))

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

@resumable function bob_node(env, qc)
@yield take!(qc, regB[1])
end

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

run(sim)
ba2tro marked this conversation as resolved.
Show resolved Hide resolved

reg = Register([Qubit(), Qubit()], [T1Decay(0.1), nothing])
initialize!(reg[1:2], bell)
uptotime!(reg[1], 10.0)

@test observable(reg[1:2], projector(bell)) == observable(regB[1:2], projector(bell))

## Test with T2Dephasing

regA = Register(2)
regB = Register(2)
initialize!((regA[1], regB[2]), bell)

sim = Simulation()

qc = QuantumChannel(sim, 10.0, T2Dephasing(0.1))

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

@resumable function bob_node(env, qc)
@yield take!(qc, regB[1])
end

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

run(sim)

reg = Register([Qubit(), Qubit()], [T2Dephasing(0.1), nothing])
initialize!(reg[1:2], bell)
uptotime!(reg[1], 10.0)

@test observable(reg[1:2], projector(bell)) == observable(regB[1:2], projector(bell))

@test_throws "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)." take!(qc, regB[1])
Loading