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
10 changes: 8 additions & 2 deletions src/baseops/subsystemcompose.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ nsubsystems(r::Register) = length(r.staterefs)
nsubsystems(r::RegRef) = 1
nsubsystems(::Nothing) = 1 # 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 swap!(reg1::Register, reg2::Register, i1::Int, i2::Int)
function swap!(reg1::Register, reg2::Register, i1::Int, i2::Int; time=nothing)
if reg1===reg2 && i1==i2
return
end
if reg1.accesstimes[i1] != reg2.accesstimes[i2]
maxtime = max(reg1.accesstimes[i1], reg2.accesstimes[i2])
maxtime = isnothing(time) ? maxtime : max(maxtime, time)
uptotime!(reg1[i1], maxtime)
uptotime!(reg2[i2], maxtime)
end
state1, state2 = reg1.staterefs[i1], reg2.staterefs[i2]
stateind1, stateind2 = reg1.stateindices[i1], reg2.stateindices[i2]
reg1.staterefs[i1], reg2.staterefs[i2] = state2, state1
Expand All @@ -23,7 +29,7 @@ function swap!(reg1::Register, reg2::Register, i1::Int, i2::Int)
state2.registerindices[stateind2] = i1
end
end
swap!(r1::RegRef, r2::RegRef) = swap!(r1.reg, r2.reg, r1.idx, r2.idx)
swap!(r1::RegRef, r2::RegRef; time=nothing) = swap!(r1.reg, r2.reg, r1.idx, r2.idx; time)

#subsystemcompose(s...) = reduce(subsystemcompose, s)

Expand Down
3 changes: 2 additions & 1 deletion src/baseops/uptotime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ end

function uptotime!(registers, indices::Base.AbstractVecOrTuple{Int}, now)
staterecords = [(state=r.staterefs[i], idx=r.stateindices[i], bg=r.backgrounds[i], t=r.accesstimes[i])
for (r,i) in zip(registers, indices)]
for (r,i) in zip(registers, indices)
if isassigned(r,i)]
for stategroup in groupby(x->x.state, staterecords) # TODO check this is grouping by ===... Actually, make sure that == for StateRef is the same as ===
state = stategroup[1].state
timegroups = sort!(collect(groupby(x->x.t, stategroup)), by=x->x[1].t)
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
80 changes: 80 additions & 0 deletions src/quantumchannel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Quantum channel for transmitting quantum states from one register to another.

Delay and background noise processes are supported.

The function `put!` is used to take the contents of a `RegRef` and put it in the channel.
That state can can then be received by a register (after a delay) using the `take!` method.

```jldoctest; filter = r"(\\d{4})\\d+" => s"at some memory address"
julia> using QuantumSavory, ResumableFunctions, ConcurrentSim

julia> regA = Register(1); regB = Register(1);

julia> initialize!(regA[1], Z1);

julia> sim = Simulation();

julia> qc = QuantumChannel(sim, 10.0) # a delay of 10 units
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 1 slots: [ Qubit ]
Slots:
nothing

julia> regB
Register with 1 slots: [ Qubit ]
Slots:
Subsystem 1 of QuantumOpticsBase.Ket 7474956998997307987
```
"""
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)
time = ConcurrentSim.now(qc.queue.store.env)
channel_reg = Register(qc)
swap!(rref, channel_reg[1]; time)
uptotime!(channel_reg[1], time+qc.queue.delay)
put!(qc.queue, channel_reg)
end

@resumable function post_take(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).")
end
swap!(channel_reg[1], rref; time=now(env))
end

function Base.take!(qc::QuantumChannel, rref::RegRef)
take_event = take!(qc.queue)
@process post_take(qc.queue.store.env, take_event, rref)
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
7 changes: 6 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ 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"
@doset "noninstant_and_backgrounds_qubit"
@doset "noninstant_and_backgrounds_qumode"
@doset "circuitzoo_superdense"

@doset "circuitzoo_api"
@doset "circuitzoo_purification"
@doset "circuitzoo_superdense"

@doset "stateszoo_api"

@doset "examples"
get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" && @doset "plotting_cairo"
get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" && @doset "plotting_gl"
Expand Down
4 changes: 3 additions & 1 deletion test/test_circuitzoo_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ for T in subtypes(AbstractCircuit)
ms = methods(circ)
@test length(ms) == 1 # this can be relaxed one day, but for now it can check we are not doing weird stuff
m = first(ms)
@test m.isva || inputqubits(circ) == m.nargs-1
if hasmethod(inputqubits, Tuple{T}) # TODO should all of them have this method?
@test m.isva || inputqubits(circ) == m.nargs-1
end
end
111 changes: 111 additions & 0 deletions test/test_quantumchannel.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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)

## Manually construct a QuantumChannel and test a simple put/take

sim = Simulation()
regA = Register(1)
regB = Register(2)
initialize!((regA[1], regB[2]), bell)
# 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)

# the above code puts both the qubits of the state in the same register
sref = regB.staterefs[1]
@test sref.registers[1] == sref.registers[2]
@test !isassigned(regA, 1)

## Test with the second constructor

regA = Register(1)
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 !isassigned(regA, 1)

## Test with T1Decay

regA = Register(1)
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

# compare against a stationary qubit experiencing the same T1 decay
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 for slot availability

sim = Simulation()
qc = QuantumChannel(sim, 10.0, T2Dephasing(0.1))
regC = Register(1)
initialize!(regC[1], Z1)
put!(qc, regC[1])
take!(qc, regB[1])
@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)." run(sim)
Loading