Skip to content

Commit a3ed53c

Browse files
committed
Create ChoiState type and conversions to/from SuperOperator
1 parent d6aadeb commit a3ed53c

File tree

3 files changed

+99
-4
lines changed

3 files changed

+99
-4
lines changed

src/QuantumOpticsBase.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export Basis, GenericBasis, CompositeBasis, basis,
3636
current_time, time_shift, time_stretch, time_restrict, static_operator,
3737
#superoperators
3838
SuperOperator, DenseSuperOperator, DenseSuperOpType,
39-
SparseSuperOperator, SparseSuperOpType, spre, spost, sprepost, liouvillian,
40-
identitysuperoperator,
39+
SparseSuperOperator, SparseSuperOpType, ChoiState,
40+
spre, spost, sprepost, liouvillian, identitysuperoperator,
4141
#fock
4242
FockBasis, number, destroy, create,
4343
fockstate, coherentstate, coherentstate!,

src/superoperators.jl

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ mutable struct SuperOperator{B1,B2,T} <: AbstractSuperOperator{B1,B2}
1111
basis_r::B2
1212
data::T
1313
function SuperOperator{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
14-
if length(basis_l[1])*length(basis_l[2]) != size(data, 1) ||
15-
length(basis_r[1])*length(basis_r[2]) != size(data, 2)
14+
if (length(basis_l) != 2 || length(basis_r) != 2 ||
15+
length(basis_l[1])*length(basis_l[2]) != size(data, 1) ||
16+
length(basis_r[1])*length(basis_r[2]) != size(data, 2))
1617
throw(DimensionMismatch("Tried to assign data of size $(size(data)) to Hilbert spaces of sizes $(length.(basis_l)), $(length.(basis_r))"))
1718
end
1819
new(basis_l, basis_r, data)
@@ -316,3 +317,62 @@ end
316317
}
317318
throw(IncompatibleBases())
318319
end
320+
321+
"""
322+
ChoiState <: AbstractSuperOperator
323+
324+
Superoperator represented as a choi state.
325+
"""
326+
mutable struct ChoiState{B1,B2,T} <: AbstractSuperOperator{B1,B2}
327+
basis_l::B1
328+
basis_r::B2
329+
data::T
330+
function ChoiState{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
331+
if (length(basis_l) != 2 || length(basis_r) != 2 ||
332+
length(basis_l[1])*length(basis_l[2]) != size(data, 1) ||
333+
length(basis_r[1])*length(basis_r[2]) != size(data, 2))
334+
throw(DimensionMismatch("Tried to assign data of size $(size(data)) to Hilbert spaces of sizes $(length.(basis_l)), $(length.(basis_r))"))
335+
end
336+
new(basis_l, basis_r, data)
337+
end
338+
end
339+
ChoiState{BL,BR}(b1::BL, b2::BR, data::T) where {BL,BR,T} = ChoiState{BL,BR,T}(b1, b2, data)
340+
ChoiState(b1::BL, b2::BR, data::T) where {BL,BR,T} = ChoiState{BL,BR,T}(b1, b2, data)
341+
342+
dense(a::ChoiState) = ChoiState(a.basis_l, a.basis_r, Matrix(a.data))
343+
sparse(a::ChoiState) = ChoiState(a.basis_l, a.basis_r, sparse(a.data))
344+
dagger(a::ChoiState) = ChoiState(dagger(SuperOperator(a)))
345+
*(a::ChoiState, b::ChoiState) = ChoiState(SuperOperator(a)*SuperOperator(b))
346+
*(a::ChoiState, b::Operator) = SuperOperator(a)*b
347+
==(a::ChoiState, b::ChoiState) = (SuperOperator(a) == SuperOperator(b))
348+
349+
# reshape swaps within systems due to colum major ordering
350+
# https://docs.qojulia.org/quantumobjects/operators/#tensor_order
351+
function _super_choi((l1, l2), (r1, r2), data)
352+
data = reshape(data, map(length, (l2, l1, r2, r1)))
353+
(l1, l2), (r1, r2) = (r2, l2), (r1, l1)
354+
data = permutedims(data, (1, 3, 2, 4))
355+
data = reshape(data, map(length, (l1l2, r1r2)))
356+
return (l1, l2), (r1, r2), data
357+
end
358+
359+
function _super_choi((r2, l2), (r1, l1), data::SparseMatrixCSC)
360+
data = _permutedims(data, map(length, (l2, r2, l1, r1)), (1, 3, 2, 4))
361+
data = reshape(data, map(length, (l1l2, r1r2)))
362+
# sparse(data) is necessary since reshape of a sparse array returns a
363+
# ReshapedSparseArray which is not a subtype of AbstractArray and so
364+
# _permutedims fails to acces the ".m" field
365+
# https://github.com/qojulia/QuantumOpticsBase.jl/pull/83
366+
# https://github.com/JuliaSparse/SparseArrays.jl/issues/24
367+
# permutedims in SparseArrays.jl only implements perm (2,1) and so
368+
# _permutedims should probably be upstreamed
369+
# https://github.com/JuliaLang/julia/issues/26534
370+
return (l1, l2), (r1, r2), sparse(data)
371+
end
372+
373+
ChoiState(op::SuperOperator) = ChoiState(_super_choi(op.basis_l, op.basis_r, op.data)...)
374+
SuperOperator(op::ChoiState) = SuperOperator(_super_choi(op.basis_l, op.basis_r, op.data)...)
375+
376+
*(a::ChoiState, b::SuperOperator) = SuperOperator(a)*b
377+
*(a::SuperOperator, b::ChoiState) = a*SuperOperator(b)
378+

test/test_superoperators.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ using SparseArrays, LinearAlgebra
88
b = GenericBasis(3)
99
@test_throws DimensionMismatch DenseSuperOperator((b, b), (b, b), zeros(ComplexF64, 3, 3))
1010
@test_throws DimensionMismatch SparseSuperOperator((b, b), (b, b), spzeros(ComplexF64, 3, 3))
11+
@test_throws DimensionMismatch ChoiState((b, b), (b, b), zeros(ComplexF64, 3, 3))
1112

1213
# Test copy, sparse and dense
1314
b1 = GenericBasis(2)
@@ -153,29 +154,45 @@ J = [Ja, Jc]
153154
ρ₀ = dm(Ψ₀)
154155

155156
@test identitysuperoperator(spinbasis)*sx == sx
157+
@test ChoiState(identitysuperoperator(spinbasis))*sx == sx
156158
@test identitysuperoperator(sparse(spre(sx)))*sx == sparse(sx)
159+
@test ChoiState(identitysuperoperator(sparse(spre(sx))))*sx == sparse(sx)
160+
@test sparse(ChoiState(identitysuperoperator(spre(sx))))*sx == sparse(sx)
157161
@test identitysuperoperator(dense(spre(sx)))*sx == dense(sx)
162+
@test ChoiState(identitysuperoperator(dense(spre(sx))))*sx == dense(sx)
163+
@test dense(ChoiState(identitysuperoperator(spre(sx))))*sx == dense(sx)
158164

159165
op1 = DenseOperator(spinbasis, [1.2+0.3im 0.7+1.2im;0.3+0.1im 0.8+3.2im])
160166
op2 = DenseOperator(spinbasis, [0.2+0.1im 0.1+2.3im; 0.8+4.0im 0.3+1.4im])
161167
@test tracedistance(spre(op1)*op2, op1*op2) < 1e-12
168+
@test tracedistance(ChoiState(spre(op1))*op2, op1*op2) < 1e-12
162169
@test tracedistance(spost(op1)*op2, op2*op1) < 1e-12
170+
@test tracedistance(ChoiState(spost(op1))*op2, op2*op1) < 1e-12
163171

164172
@test spre(sparse(op1))*op2 == op1*op2
173+
@test ChoiState(spre(sparse(op1)))*op2 == op1*op2
165174
@test spost(sparse(op1))*op2 == op2*op1
175+
@test ChoiState(spost(sparse(op1)))*op2 == op2*op1
166176
@test spre(sparse(dagger(op1)))*op2 == dagger(op1)*op2
177+
@test ChoiState(spre(sparse(dagger(op1))))*op2 == dagger(op1)*op2
167178
@test spre(dense(dagger(op1)))*op2 dagger(op1)*op2
179+
@test ChoiState(spre(dense(dagger(op1))))*op2 dagger(op1)*op2
168180
@test sprepost(sparse(op1), op1)*op2 op1*op2*op1
181+
@test ChoiState(sprepost(sparse(op1), op1))*op2 op1*op2*op1
169182

170183
@test spre(sparse(op1))*sparse(op2) == sparse(op1*op2)
184+
@test ChoiState(spre(sparse(op1)))*sparse(op2) == sparse(op1*op2)
171185
@test spost(sparse(op1))*sparse(op2) == sparse(op2*op1)
186+
@test ChoiState(spost(sparse(op1)))*sparse(op2) == sparse(op2*op1)
172187
@test sprepost(sparse(op1), sparse(op1))*sparse(op2) sparse(op1*op2*op1)
188+
@test ChoiState(sprepost(sparse(op1), sparse(op1)))*sparse(op2) sparse(op1*op2*op1)
173189

174190
@test sprepost(op1, op2) spre(op1)*spost(op2)
175191
b1 = FockBasis(1)
176192
b2 = FockBasis(5)
177193
op = fockstate(b1, 0) dagger(fockstate(b2, 0))
178194
@test sprepost(dagger(op), op)*dm(fockstate(b1, 0)) == dm(fockstate(b2, 0))
195+
@test ChoiState(sprepost(dagger(op), op))*dm(fockstate(b1, 0)) == dm(fockstate(b2, 0))
179196
@test_throws ArgumentError spre(op)
180197
@test_throws ArgumentError spost(op)
181198

@@ -185,12 +202,14 @@ for j=J
185202
ρ .+= j*ρ₀*dagger(j) - 0.5*dagger(j)*j*ρ₀ - 0.5*ρ₀*dagger(j)*j
186203
end
187204
@test tracedistance(L*ρ₀, ρ) < 1e-10
205+
@test tracedistance(ChoiState(L)*ρ₀, ρ) < 1e-10
188206

189207
# tout, ρt = timeevolution.master([0.,1.], ρ₀, H, J; reltol=1e-7)
190208
# @test tracedistance(exp(dense(L))*ρ₀, ρt[end]) < 1e-6
191209
# @test tracedistance(exp(sparse(L))*ρ₀, ρt[end]) < 1e-6
192210

193211
@test dense(spre(op1)) == spre(op1)
212+
@test dense(ChoiState(spre(op1))) == ChoiState(spre(op1))
194213

195214
@test L/2.0 == 0.5*L == L*0.5
196215
@test -L == SparseSuperOperator(L.basis_l, L.basis_r, -L.data)
@@ -228,4 +247,20 @@ N = exp(log(2) * sparse(L)) # 50% loss channel
228247
@test (0.5 - real(tr^2))) < 1e-10 # one photon state becomes maximally mixed
229248
@test tracedistance(ρ, normalize(dm(fockstate(b, 0)) + dm(fockstate(b, 1)))) < 1e-10
230249

250+
# Testing 0-2-4 binomial code encoder
251+
b_logical = SpinBasis(1//2)
252+
b_fock = FockBasis(5)
253+
z_l = normalize(fockstate(b_fock, 0) + fockstate(b_fock, 4))
254+
o_l = fockstate(b_fock, 2)
255+
encoder_kraus = z_l dagger(spinup(b_logical)) + o_l dagger(spindown(b_logical))
256+
encoder_sup = sprepost(encoder_kraus, dagger(encoder_kraus))
257+
decoder_sup = sprepost(dagger(encoder_kraus), encoder_kraus)
258+
@test SuperOperator(ChoiState(encoder_sup)).data == encoder_sup.data
259+
@test decoder_sup == dagger(encoder_sup)
260+
@test ChoiState(decoder_sup) == dagger(ChoiState(encoder_sup))
261+
@test decoder_sup*encoder_sup dense(identitysuperoperator(b_logical))
262+
@test decoder_sup*ChoiState(encoder_sup) dense(identitysuperoperator(b_logical))
263+
@test ChoiState(decoder_sup)*encoder_sup dense(identitysuperoperator(b_logical))
264+
@test SuperOperator(ChoiState(decoder_sup)*ChoiState(encoder_sup)) dense(identitysuperoperator(b_logical))
265+
231266
end # testset

0 commit comments

Comments
 (0)