Skip to content

Commit c12a150

Browse files
committed
Implement KrausOperators, conversions, is_trace_preserving, is_valid_channel
1 parent 1f06f07 commit c12a150

File tree

2 files changed

+81
-10
lines changed

2 files changed

+81
-10
lines changed

src/QuantumOpticsBase.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export Basis, GenericBasis, CompositeBasis, basis,
3636
#superoperators
3737
SuperOperator, DenseSuperOperator, DenseSuperOpType,
3838
SparseSuperOperator, SparseSuperOpType,
39-
ChoiState,
39+
ChoiState, KrausOperators, is_trace_preserving, is_valid_channel,
4040
spre, spost, sprepost, liouvillian, identitysuperoperator,
4141
#fock
4242
FockBasis, number, destroy, create,

src/superoperators.jl

Lines changed: 80 additions & 9 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)
@@ -318,26 +319,72 @@ end
318319
end
319320

320321
# TODO should all of PauliTransferMatrix, ChiMatrix, ChoiState, and KrausOperators subclass AbstractSuperOperator?
322+
321323
"""
322-
Base class for the Choi representation of superoperators.
324+
KrausOperators(B1, B2, data)
325+
326+
Superoperator represented as a list of Kraus operators.
327+
Note unlike the SuperOperator or ChoiState types where
328+
its possible to have basis_l[1] != basis_l[2] and basis_r[1] != basis_r[2]
329+
which allows representations of maps between general linear operators defined on H_A \to H_B,
330+
a quantum channel can only act on valid density operators which live in H_A \to H_A.
331+
Thus the Kraus representation is only defined for quantum channels which map
332+
(H_A \to H_A) \to (H_B \to H_B).
333+
"""
334+
mutable struct KrausOperators{B1,B2,T} <: AbstractSuperOperator{B1,B2}
335+
basis_l::B1
336+
basis_r::B2
337+
data::T
338+
function KrausOperators{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
339+
if (any(!samebases(basis_r, M.basis_r) for M in data) ||
340+
any(!samebases(basis_l, M.basis_l) for M in data))
341+
throw(DimensionMismatch("Tried to assign data with incompatible bases"))
342+
end
343+
344+
new(basis_l, basis_r, data)
345+
end
346+
end
347+
KrausOperators{BL,BR}(b1::BL,b2::BR,data::T) where {BL,BR,T} = KrausOperators{BL,BR,T}(b1,b2,data)
348+
KrausOperators(b1::BL,b2::BR,data::T) where {BL,BR,T} = KrausOperators{BL,BR,T}(b1,b2,data)
349+
KrausOperators(b,data) = KrausOperators(b,b,data)
350+
351+
function is_trace_preserving(kraus::KrausOperators; tol=1e-9)
352+
m = I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data
353+
m[abs.(m) .< tol] .= 0
354+
return iszero(m)
355+
end
356+
357+
function is_valid_channel(kraus::KrausOperators; tol=1e-9)
358+
m = I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data
359+
eigs = eigvals(Matrix(m))
360+
eigs[@. abs(eigs) < tol || eigs > 0] .= 0
361+
return iszero(eigs)
362+
end
363+
323364
"""
365+
ChoiState(B1, B2, data)
324366
367+
Superoperator represented as a choi state stored as a sparse or dense matrix.
368+
"""
325369
mutable struct ChoiState{B1,B2,T} <: AbstractSuperOperator{B1,B2}
326370
basis_l::B1
327371
basis_r::B2
328372
data::T
329-
function ChoiState{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
373+
function ChoiState{BL,BR,T}(basis_l::BL, basis_r::BR, data::T; tol=1e-9) where {BL,BR,T}
330374
if (length(basis_l) != 2 || length(basis_r) != 2 ||
331375
length(basis_l[1])*length(basis_l[2]) != size(data, 1) ||
332376
length(basis_r[1])*length(basis_r[2]) != size(data, 2))
333377
throw(DimensionMismatch("Tried to assign data of size $(size(data)) to Hilbert spaces of sizes $(length.(basis_l)), $(length.(basis_r))"))
334378
end
335-
new(basis_l, basis_r, data)
379+
if any(abs.(data - data') .> tol)
380+
@warn "Trying to construct ChoiState from non-hermitian data"
381+
end
382+
new(basis_l, basis_r, Hermitian(data))
336383
end
337384
end
338-
ChoiState{BL,BR}(b1::BL,b2::BR,data::T) where {BL,BR,T} = ChoiState{BL,BR,T}(b1,b2,data)
339-
ChoiState(b1::BL,b2::BR,data::T) where {BL,BR,T} = ChoiState{BL,BR,T}(b1,b2,data)
340-
ChoiState(b,data) = ChoiState(b,b,data)
385+
ChoiState{BL,BR}(b1::BL,b2::BR,data::T; tol=1e-9) where {BL,BR,T} = ChoiState{BL,BR,T}(b1,b2,data;tol=tol)
386+
ChoiState(b1::BL,b2::BR,data::T; tol=1e-9) where {BL,BR,T} = ChoiState{BL,BR,T}(b1,b2,data; tol=tol)
387+
ChoiState(b,data; tol=tol) = ChoiState(b,b,data; tol=tol)
341388

342389
# TODO: document why we have super_to_choi return non-trace one density matrices.
343390
# https://forest-benchmarking.readthedocs.io/en/latest/superoperator_representations.html
@@ -367,5 +414,29 @@ function _super_choi((r2, l2), (r1, l1), data::SparseMatrixCSC)
367414
return (l1, l2), (r1, r2), sparse(data)
368415
end
369416

370-
ChoiState(op::SuperOperator) = ChoiState(_super_choi(op.basis_l, op.basis_r, op.data)...)
417+
ChoiState(op::SuperOperator; tol=1e-9) = ChoiState(_super_choi(op.basis_l, op.basis_r, op.data)...; tol=tol)
418+
SuperOperator(kraus::KrausOperators) =
419+
SuperOperator((kraus.basis_l, kraus.basis_l), (kraus.basis_r, kraus.basis_r),
420+
(sum(conj(op)op for op in kraus.data)).data)
421+
371422
SuperOperator(op::ChoiState) = SuperOperator(_super_choi(op.basis_l, op.basis_r, op.data)...)
423+
ChoiState(kraus::KrausOperators) = ChoiState(SuperOperator(kraus))
424+
425+
function KrausOperators(choi::ChoiState; tol=1e-9)
426+
if (!samebases(choi.basis_l[1], choi.basis_l[2]) ||
427+
!samebases(choi.basis_r[1], choi.basis_r[2]))
428+
throw(DimensionMismatch("Tried to convert choi state of something that isn't a quantum channel mapping density operators to density operators"))
429+
end
430+
bl, br = choi.basis_l[1], choi.basis_r[1]
431+
#ishermitian(choi.data) || @warn "ChoiState is not hermitian"
432+
# TODO: figure out how to do this with sparse matrices using e.g. Arpack.jl or ArnoldiMethod.jl
433+
vals, vecs = eigen(Hermitian(Matrix(choi.data)))
434+
for val in vals
435+
(abs(val) > tol && val < 0) && @warn "eigval $(val) < 0 but abs(eigval) > tol=$(tol)"
436+
end
437+
ops = [Operator(bl, br, sqrt(val)*reshape(vecs[:,i], length(bl), length(br)))
438+
for (i, val) in enumerate(vals) if abs(val) > tol && val > 0]
439+
return KrausOperators(bl, br, ops)
440+
end
441+
442+
KrausOperators(op::SuperOperator; tol=1e-9) = KrausOperators(ChoiState(op; tol=tol); tol=tol)

0 commit comments

Comments
 (0)