Skip to content

Commit

Permalink
Added QuantumChannel (#53)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Stefan Krastanov <stefan@krastanov.org>
ba2tro and Krastanov authored Nov 9, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent f33b181 commit bf5c68d
Showing 10 changed files with 219 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 6 additions & 0 deletions src/QuantumSavory.jl
Original file line number Diff line number Diff line change
@@ -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:
@@ -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

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

include("plots.jl")

include("quantumchannel.jl")

include("CircuitZoo/CircuitZoo.jl")

include("StatesZoo/StatesZoo.jl")
10 changes: 8 additions & 2 deletions src/baseops/subsystemcompose.jl
Original file line number Diff line number Diff line change
@@ -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
@@ -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)

3 changes: 2 additions & 1 deletion src/baseops/uptotime.jl
Original file line number Diff line number Diff line change
@@ -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)
26 changes: 1 addition & 25 deletions src/concurrentsim.jl
Original file line number Diff line number Diff line change
@@ -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)))))
@@ -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)
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
@@ -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"
7 changes: 6 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 3 additions & 1 deletion test/test_circuitzoo_api.jl
Original file line number Diff line number Diff line change
@@ -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
using ResumableFunctions
using ConcurrentSim
using Test

bell = (Z1Z1 + Z2Z2)/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)

@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)

# 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)

0 comments on commit bf5c68d

Please sign in to comment.