Skip to content

Commit ef13d6b

Browse files
committed
Implement KrausOperators and conversions
1 parent a3ed53c commit ef13d6b

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

src/QuantumOpticsBase.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +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, ChoiState,
39+
SparseSuperOperator, SparseSuperOpType, ChoiState, KrausOperators,
40+
is_valid_channel, is_trace_preserving, minimize_kraus_rank,
4041
spre, spost, sprepost, liouvillian, identitysuperoperator,
4142
#fock
4243
FockBasis, number, destroy, create,

src/superoperators.jl

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,59 @@ end
318318
throw(IncompatibleBases())
319319
end
320320

321+
# TODO should all of PauliTransferMatrix, ChiMatrix, ChoiState, and KrausOperators subclass AbstractSuperOperator?
322+
"""
323+
KrausOperators(B1, B2, data)
324+
325+
Superoperator represented as a list of Kraus operators.
326+
Note unlike the SuperOperator or ChoiState types where
327+
its possible to have basis_l[1] != basis_l[2] and basis_r[1] != basis_r[2]
328+
which allows representations of maps between general linear operators defined on H_A \to H_B,
329+
a quantum channel can only act on valid density operators which live in H_A \to H_A.
330+
Thus the Kraus representation is only defined for quantum channels which map
331+
(H_A \to H_A) \to (H_B \to H_B).
332+
"""
333+
mutable struct KrausOperators{B1,B2,T} <: AbstractSuperOperator{B1,B2}
334+
basis_l::B1
335+
basis_r::B2
336+
data::T
337+
function KrausOperators{BL,BR,T}(basis_l::BL, basis_r::BR, data::T) where {BL,BR,T}
338+
if (any(!samebases(basis_r, M.basis_r) for M in data) ||
339+
any(!samebases(basis_l, M.basis_l) for M in data))
340+
throw(DimensionMismatch("Tried to assign data with incompatible bases"))
341+
end
342+
343+
new(basis_l, basis_r, data)
344+
end
345+
end
346+
KrausOperators{BL,BR}(b1::BL,b2::BR,data::T) where {BL,BR,T} = KrausOperators{BL,BR,T}(b1,b2,data)
347+
KrausOperators(b1::BL,b2::BR,data::T) where {BL,BR,T} = KrausOperators{BL,BR,T}(b1,b2,data)
348+
KrausOperators(b,data) = KrausOperators(b,b,data)
349+
tensor(a::KrausOperators, b::KrausOperators) =
350+
KrausOperators(a.basis_l b.basis_l, a.basis_r b.basis_r,
351+
[A B for A in a.data for B in b.data])
352+
dagger(a::KrausOperators) = KrausOperators(a.basis_r, a.basis_l, [dagger(op) for op in a.data])
353+
*(a::KrausOperators{B1,B2}, b::KrausOperators{B2,B3}) where {B1,B2,B3} =
354+
KrausOperators(a.basis_l, b.basis_r, [A*B for A in a.data for B in b.data])
355+
*(a::KrausOperators, b::KrausOperators) = throw(IncompatibleBases())
356+
*(a::KrausOperators{BL,BR}, b::Operator{BR,BR}) where {BL,BR} =
357+
sum(op*b*dagger(op) for op in a.data)
358+
359+
function minimize_kraus_rank(kraus; tol=1e-9)
360+
bl, br = kraus.basis_l, kraus.basis_r
361+
dim = length(bl)*length(br)
362+
363+
A = stack(reshape(op.data, dim) for op in kraus.data; dims=1)
364+
F = qr(A; tol=tol)
365+
rank = maximum(findnz(F.R)[1]) # rank(F) doesn't work but should
366+
#@assert (F.R ≈ (sparse(F.Q') * A[F.prow,F.pcol])[1:dim,:])
367+
#@assert (all(iszero(F.R[rank+1:end,:])))
368+
369+
ops = [Operator(bl, br, copy(reshape( # copy materializes reshaped view
370+
F.R[i,invperm(F.pcol)], (length(bl), length(br))))) for i=1:rank]
371+
return KrausOperators(bl, br, ops)
372+
end
373+
321374
"""
322375
ChoiState <: AbstractSuperOperator
323376
@@ -376,3 +429,63 @@ SuperOperator(op::ChoiState) = SuperOperator(_super_choi(op.basis_l, op.basis_r,
376429
*(a::ChoiState, b::SuperOperator) = SuperOperator(a)*b
377430
*(a::SuperOperator, b::ChoiState) = a*SuperOperator(b)
378431

432+
SuperOperator(kraus::KrausOperators) =
433+
SuperOperator((kraus.basis_l, kraus.basis_l), (kraus.basis_r, kraus.basis_r),
434+
(sum(conj(op)op for op in kraus.data)).data)
435+
436+
ChoiState(kraus::KrausOperators; tol=1e-9) =
437+
ChoiState((kraus.basis_r, kraus.basis_l), (kraus.basis_r, kraus.basis_l),
438+
(sum((M=op.data; reshape(M, (length(M), 1))*reshape(M, (1, length(M))))
439+
for op in kraus.data)); tol=tol)
440+
441+
function KrausOperators(choi::ChoiState; tol=1e-9)
442+
if (!samebases(choi.basis_l[1], choi.basis_r[1]) ||
443+
!samebases(choi.basis_l[2], choi.basis_r[2]))
444+
throw(DimensionMismatch("Tried to convert choi state of something that isn't a quantum channel mapping density operators to density operators"))
445+
end
446+
bl, br = choi.basis_l[2], choi.basis_l[1]
447+
#ishermitian(choi.data) || @warn "ChoiState is not hermitian"
448+
# TODO: figure out how to do this with sparse matrices using e.g. Arpack.jl or ArnoldiMethod.jl
449+
vals, vecs = eigen(Hermitian(Matrix(choi.data)))
450+
for val in vals
451+
(abs(val) > tol && val < 0) && @warn "eigval $(val) < 0 but abs(eigval) > tol=$(tol)"
452+
end
453+
ops = [Operator(bl, br, sqrt(val)*reshape(vecs[:,i], length(bl), length(br)))
454+
for (i, val) in enumerate(vals) if abs(val) > tol && val > 0]
455+
return KrausOperators(bl, br, ops)
456+
end
457+
458+
KrausOperators(op::SuperOperator; tol=1e-9) = KrausOperators(ChoiState(op; tol=tol); tol=tol)
459+
460+
*(a::ChoiState, b::SuperOperator) = SuperOperator(a)*b
461+
*(a::SuperOperator, b::ChoiState) = a*SuperOperator(b)
462+
*(a::KrausOperators, b::SuperOperator) = SuperOperator(a)*b
463+
*(a::SuperOperator, b::KrausOperators) = a*SuperOperator(b)
464+
*(a::KrausOperators, b::ChoiState) = SuperOperator(a)*SuperOperator(b)
465+
*(a::ChoiState, b::KrausOperators) = SuperOperator(a)*SuperOperator(b)
466+
467+
function is_trace_preserving(kraus::KrausOperators; tol=1e-9)
468+
m = I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data
469+
m[abs.(m) .< tol] .= 0
470+
return iszero(m)
471+
end
472+
473+
# this check seems suspect... since it fails while the below check on choi succeeeds
474+
#function is_valid_channel(kraus::KrausOperators; tol=1e-9)
475+
# m = I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data
476+
# eigs = eigvals(Matrix(m))
477+
# eigs[@. abs(eigs) < tol || eigs > 0] .= 0
478+
# return iszero(eigs)
479+
#end
480+
481+
#function is_valid_channel(choi::ChoiState; tol=1e-9)
482+
# eigs = eigvals(Hermitian(Matrix(choi.data)))
483+
# eigs[@. abs(eigs) < tol || eigs > 0] .= 0
484+
# return iszero(eigs)
485+
#end
486+
487+
function is_valid_channel(choi::ChoiState; tol=1e-9)
488+
any(abs.(choi.data - choi.data') .> tol) && return false
489+
eigs = eigvals(Hermitian(Matrix(choi.data)))
490+
return all(@. abs(eigs) < tol || eigs > 0)
491+
end

0 commit comments

Comments
 (0)