Skip to content

Commit 30f832a

Browse files
committed
Implement cp, tp, tni checks and tests
1 parent 5390341 commit 30f832a

File tree

4 files changed

+116
-75
lines changed

4 files changed

+116
-75
lines changed

src/QuantumOpticsBase.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export Basis, GenericBasis, CompositeBasis, basis,
3737
#superoperators
3838
SuperOperator, DenseSuperOperator, DenseSuperOpType,
3939
SparseSuperOperator, SparseSuperOpType, ChoiState, KrausOperators,
40-
is_valid_channel, is_trace_preserving, minimize_kraus_rank,
40+
canonicalize, orthogonalize, is_cptp, is_cptni,
41+
is_completely_positive, is_trace_preserving, is_trace_nonincreasing,
4142
spre, spost, sprepost, liouvillian, identitysuperoperator,
4243
#fock
4344
FockBasis, number, destroy, create,

src/pauli.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,9 @@ ChiMatrix(ptm::DensePauliTransferMatrix) = ChiMatrix(SuperOperator(ptm))
257257

258258
"""Equality for all varieties of superoperators."""
259259
==(sop1::T, sop2::T) where T<:Union{DensePauliTransferMatrix, DenseSuperOpType, DenseChiMatrix} = sop1.data == sop2.data
260-
==(sop1::Union{DensePauliTransferMatrix, DenseSuperOpType, DenseChiMatrix}, sop2::Union{DensePauliTransferMatrix, DenseSuperOpType, DenseChiMatrix}) = false
260+
==(sop1::Union{DensePauliTransferMatrix, DenseSuperOpType, DenseChiMatrix}, sop2::Union{DensePauliTransferMatrix, DenseChiMatrix}) = false
261261

262262
"""Approximate equality for all varieties of superoperators."""
263-
function isapprox(sop1::T, sop2::T; kwargs...) where T<:Union{DensePauliTransferMatrix, DenseSuperOpType, DenseChiMatrix}
263+
function isapprox(sop1::T, sop2::T; kwargs...) where T<:Union{DensePauliTransferMatrix, DenseChiMatrix}
264264
return isapprox(sop1.data, sop2.data; kwargs...)
265265
end

src/superoperators.jl

Lines changed: 83 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Base: isapprox
12
import QuantumInterface: AbstractSuperOperator
23
import FastExpm: fastExpm
34

@@ -69,6 +70,9 @@ sparse(a::SuperOperator) = SuperOperator(a.basis_l, a.basis_r, sparse(a.data))
6970

7071
==(a::SuperOperator{B1,B2}, b::SuperOperator{B1,B2}) where {B1,B2} = (samebases(a,b) && a.data == b.data)
7172
==(a::SuperOperator, b::SuperOperator) = false
73+
isapprox(a::SuperOperator{B1,B2}, b::SuperOperator{B1,B2}; kwargs...) where {B1,B2} =
74+
(samebases(a,b) && isapprox(a.data, b.data; kwargs...))
75+
isapprox(a::SuperOperator, b::SuperOperator; kwargs...) = false
7276

7377
Base.length(a::SuperOperator) = length(a.basis_l[1])*length(a.basis_l[2])*length(a.basis_r[1])*length(a.basis_r[2])
7478
samebases(a::SuperOperator, b::SuperOperator) = samebases(a.basis_l[1], b.basis_l[1]) && samebases(a.basis_l[2], b.basis_l[2]) &&
@@ -349,9 +353,10 @@ dagger(a::ChoiState) = ChoiState(dagger(SuperOperator(a)))
349353
*(a::ChoiState, b::ChoiState) = ChoiState(SuperOperator(a)*SuperOperator(b))
350354
*(a::ChoiState, b::Operator) = SuperOperator(a)*b
351355
==(a::ChoiState, b::ChoiState) = (SuperOperator(a) == SuperOperator(b))
356+
isapprox(a::ChoiState, b::ChoiState; kwargs...) = isapprox(SuperOperator(a), SuperOperator(b); kwargs...)
352357

353358
# TOOD: decide whether to document and export this
354-
choi_to_operator(c::ChoiState) = Operator(c.basis_l[1]c.basis_l[2], c.basis_r[1]c.basis_r[2], c.data)
359+
choi_to_operator(c::ChoiState) = Operator(c.basis_l[2]c.basis_l[1], c.basis_r[2]c.basis_r[1], c.data)
355360

356361
# reshape swaps within systems due to colum major ordering
357362
# https://docs.qojulia.org/quantumobjects/operators/#tensor_order
@@ -385,6 +390,18 @@ SuperOperator(op::ChoiState) = SuperOperator(_super_choi(op.basis_l, op.basis_r,
385390
KrausOperators <: AbstractSuperOperator
386391
387392
Superoperator represented as a list of Kraus operators.
393+
394+
Note that KrausOperators can only represent linear maps taking density operators to other
395+
(potentially unnormalized) density operators.
396+
In contrast the `SuperOperator` or `ChoiState` representations can represent arbitrary linear maps
397+
taking arbitrary operators defined on ``H_A \\to H_B`` to ``H_C \\to H_D``.
398+
In otherwords, the Kraus representation is only defined for completely positive linear maps of the form
399+
``(H_A \\to H_A) \\to (H_B \\to H_B)``.
400+
Thus converting from `SuperOperator` or `ChoiState` to `KrausOperators` will throw an exception if the
401+
map cannot be faithfully represented up to the specificed tolerance `tol`.
402+
403+
----------------------------
404+
Old text:
388405
Note unlike the SuperOperator or ChoiState types where it is possible to have
389406
`basis_l[1] != basis_l[2]` and `basis_r[1] != basis_r[2]`
390407
which allows representations of maps between general linear operators defined on ``H_A \\to H_B``,
@@ -416,9 +433,11 @@ dagger(a::KrausOperators) = KrausOperators(a.basis_r, a.basis_l, [dagger(op) for
416433
KrausOperators(a.basis_l, b.basis_r, [A*B for A in a.data for B in b.data])
417434
*(a::KrausOperators, b::KrausOperators) = throw(IncompatibleBases())
418435
*(a::KrausOperators{BL,BR}, b::Operator{BR,BR}) where {BL,BR} = sum(op*b*dagger(op) for op in a.data)
436+
==(a::KrausOperators, b::KrausOperators) = (SuperOperator(a) == SuperOperator(b))
437+
isapprox(a::KrausOperators, b::KrausOperators; kwargs...) = isapprox(SuperOperator(a), SuperOperator(b); kwargs...)
419438

420439
"""
421-
canonicalize(kraus::KrausOperators; tol=1e-9)
440+
canonicalize(kraus::KrausOperators; tol=1e-12)
422441
423442
Canonicalize the set kraus operators by performing a qr decomposition.
424443
A quantum channel with kraus operators ``{A_k}`` is in cannonical form if and only if
@@ -431,7 +450,7 @@ If the input dimension is d and output dimension is d' then the number of kraus
431450
operators returned is guaranteed to be no greater than dd' and will furthermore
432451
be equal the Kraus rank of the channel up to numerical imprecision controlled by `tol`.
433452
"""
434-
function canonicalize(kraus::KrausOperators; tol=1e-9)
453+
function canonicalize(kraus::KrausOperators; tol=1e-12)
435454
bl, br = kraus.basis_l, kraus.basis_r
436455
dim = length(bl)*length(br)
437456

@@ -449,7 +468,7 @@ function canonicalize(kraus::KrausOperators; tol=1e-9)
449468
end
450469

451470
# TODO: check if canonicalize and orthogonalize are equivalent
452-
orthogonalize(kraus::KrausOperators; tol=1e-9) = KrausOperators(ChoiState(kraus); tol=tol)
471+
orthogonalize(kraus::KrausOperators; tol=1e-12) = KrausOperators(ChoiState(kraus); tol=tol)
453472

454473
SuperOperator(kraus::KrausOperators) =
455474
SuperOperator((kraus.basis_l, kraus.basis_l), (kraus.basis_r, kraus.basis_r),
@@ -460,29 +479,44 @@ ChoiState(kraus::KrausOperators) =
460479
(sum((M=op.data; reshape(M, (length(M), 1))*reshape(M, (1, length(M))))
461480
for op in kraus.data)))
462481

463-
function KrausOperators(choi::ChoiState; tol=1e-9, warn=true)
464-
if (!samebases(choi.basis_l[1], choi.basis_r[1]) ||
465-
!samebases(choi.basis_l[2], choi.basis_r[2]))
482+
_choi_state_maps_density_ops(choi::ChoiState) = (samebases(choi.basis_l[1], choi.basis_r[1]) &&
483+
samebases(choi.basis_l[2], choi.basis_r[2]))
484+
485+
# TODO: consider using https://github.com/jlapeyre/IsApprox.jl
486+
_is_hermitian(M; tol=1e-12) = ishermitian(M) || isapprox(M, M', atol=tol)
487+
_is_identity(M; tol=1e-12) = isapprox(M, I, atol=tol)
488+
489+
# TODO: document
490+
function _positive_eigen(data; tol=1e-12)
491+
# TODO: figure out how to do this with sparse matrices using e.g. Arpack.jl or ArnoldiMethod.jl
492+
# I will want to run twice, first asking for smallest eigenvalue to check it is above -tol
493+
# Then run a second time with asking for maybe sqrt(N) largest eigenvalues?
494+
# If smallest of these is not smaller than tol, bail do dense method?
495+
# LinearAlgebra's eigen returns eigenvals sorted smallest to largest for Hermitian matrices
496+
vals, vecs = eigen(Hermitian(Matrix(data)))
497+
vals[1] < -tol && return vals[1]
498+
return [(val, vecs[:,i]) for (i, val) in enumerate(vals) if val > tol]
499+
end
500+
501+
function KrausOperators(choi::ChoiState; tol=1e-12)
502+
if !_choi_state_maps_density_ops(choi)
466503
throw(DimensionMismatch("Tried to convert Choi state of something that isn't a quantum channel mapping density operators to density operators"))
467504
end
468-
# TODO: consider using https://github.com/jlapeyre/IsApprox.jl
469-
if !ishermitian(choi.data) || !isapprox(choi.data, choi.data', atol=tol)
505+
if !_is_hermitian(choi.data; tol=tol)
470506
throw(ArgumentError("Tried to convert nonhermitian Choi state"))
471507
end
472508
bl, br = choi.basis_l[2], choi.basis_l[1]
473-
# TODO: figure out how to do this with sparse matrices using e.g. Arpack.jl or ArnoldiMethod.jl
474-
vals, vecs = eigen(Hermitian(Matrix(choi.data)))
475-
for val in vals
476-
if warn && (abs(val) > tol && val < 0)
477-
@warn "eigval $(val) < 0 but abs(eigval) > tol=$(tol)"
478-
end
509+
eigs = _positive_eigen(choi.data; tol=tol)
510+
if isa(eigs, Number)
511+
throw(ArgumentError("Tried to convert a non-positive semidefinite Choi state,"*
512+
"failed for smallest eigval $(eigs), consider increasing tol=$(tol)"))
479513
end
480-
ops = [Operator(bl, br, sqrt(val)*reshape(vecs[:,i], length(bl), length(br)))
481-
for (i, val) in enumerate(vals) if abs(val) > tol && val > 0]
514+
515+
ops = [Operator(bl, br, sqrt(val)*reshape(vec, length(bl), length(br))) for (val, vec) in eigs]
482516
return KrausOperators(bl, br, ops)
483517
end
484518

485-
KrausOperators(op::SuperOperator; tol=1e-9) = KrausOperators(ChoiState(op); tol=tol)
519+
KrausOperators(op::SuperOperator; tol=1e-12) = KrausOperators(ChoiState(op); tol=tol)
486520

487521
# TODO: document superoperator representation precident: everything of mixed type returns SuperOperator
488522
*(a::ChoiState, b::SuperOperator) = SuperOperator(a)*b
@@ -493,48 +527,45 @@ KrausOperators(op::SuperOperator; tol=1e-9) = KrausOperators(ChoiState(op); tol=
493527
*(a::ChoiState, b::KrausOperators) = SuperOperator(a)*SuperOperator(b)
494528

495529
# TODO: document this
496-
is_trace_preserving(kraus::KrausOperators; tol=1e-9) =
497-
isapprox.(I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data, atol=tol)
530+
is_completely_positive(choi::KrausOperators; tol=1e-12) = true
498531

499-
function is_trace_preserving(choi::ChoiState; tol=1e-9)
500-
if (!samebases(choi.basis_l[1], choi.basis_r[1]) ||
501-
!samebases(choi.basis_l[2], choi.basis_r[2]))
502-
throw(DimensionMismatch("Choi state is of something that isn't a quantum channel mapping density operators to density operators"))
503-
end
504-
bl, br = choi.basis_l[2], choi.basis_l[1]
505-
return isapprox.(I(length(br)) - ptrace(choi_to_operator(choi), 2), atol=tol)
532+
function is_completely_positive(choi::ChoiState; tol=1e-12)
533+
_choi_state_maps_density_ops(choi) || return false
534+
_is_hermitian(choi.data; tol=tol) || return false
535+
isa(_positive_eigen(choi.data; tol=tol), Number) && return false
536+
return true
506537
end
507538

508-
is_trace_preserving(super::SuperOperator; tol=1e-9) = is_trace_preserving(ChoiState(super); tol=tol)
539+
is_completely_positive(super::SuperOperator; tol=1e-12) = is_completely_positive(ChoiState(super))
540+
541+
# TODO: document this
542+
is_trace_preserving(kraus::KrausOperators; tol=1e-12) =
543+
_is_identity(sum(dagger(M)*M for M in kraus.data).data, tol=tol)
509544

510-
# this check seems suspect... since it fails while the below check on choi succeeeds
511-
#function is_valid_channel(kraus::KrausOperators; tol=1e-9)
512-
# m = I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data
513-
# eigs = eigvals(Matrix(m))
514-
# eigs[@. abs(eigs) < tol || eigs > 0] .= 0
515-
# return iszero(eigs)
516-
#end
545+
function is_trace_preserving(choi::ChoiState; tol=1e-12)
546+
is_completely_positive(choi; tol=tol) || return false
547+
return _is_identity(ptrace(choi_to_operator(choi), 1).data, tol=tol)
548+
end
517549

518-
#function is_valid_channel(choi::ChoiState; tol=1e-9)
519-
# eigs = eigvals(Hermitian(Matrix(choi.data)))
520-
# eigs[@. abs(eigs) < tol || eigs > 0] .= 0
521-
# return iszero(eigs)
522-
#end
550+
is_trace_preserving(super::SuperOperator; tol=1e-12) = is_trace_preserving(ChoiState(super); tol=tol)
523551

524-
# TODO: pull out check for valid quantum channel
525552
# TODO: document this
526-
function is_completely_positive(choi::ChoiState; tol=1e-9)
527-
if (!samebases(choi.basis_l[1], choi.basis_r[1]) ||
528-
!samebases(choi.basis_l[2], choi.basis_r[2]))
529-
throw(DimensionMismatch("Choi state is of something that isn't a quantum channel mapping density operators to density operators"))
530-
end
531-
any(abs.(choi.data - choi.data') .> tol) && return false
532-
eigs = eigvals(Hermitian(Matrix(choi.data)))
533-
return all(@. abs(eigs) < tol || eigs > 0)
553+
function is_trace_nonincreasing(kraus::KrausOperators; tol=1e-12)
554+
m = I - sum(dagger(M)*M for M in kraus.data).data
555+
return !isa(_positive_eigen(m; tol=tol), Number)
534556
end
535557

536-
is_completely_positive(kraus::KrausOperators; tol=1e-9) = is_completely_positive(ChoiState(kraus); tol=tol)
537-
is_completely_positive(super::SuperOperator; tol=1e-9) = is_completely_positive(ChoiState(super); tol=tol)
558+
function is_trace_nonincreasing(choi::ChoiState; tol=1e-12)
559+
is_completely_positive(choi; tol=tol) || return false
560+
m = I - ptrace(choi_to_operator(choi), 1).data
561+
return !isa(_positive_eigen(m; tol=tol), Number)
562+
end
563+
564+
is_trace_nonincreasing(super::SuperOperator; tol=1e-12) = is_trace_nonincreasing(ChoiState(super); tol=tol)
538565

539566
# TODO: document this
540-
is_cptp(sop) = is_completly_positive(sop) && is_trace_preserving(sop)
567+
is_cptp(sop; tol=1e-12) = is_completely_positive(sop; tol=tol) && is_trace_preserving(sop; tol=tol)
568+
569+
# TODO: document this
570+
is_cptni(sop; tol=1e-12) = is_completely_positive(sop; tol=tol) && is_trace_nonincreasing(sop; tol=tol)
571+

test/test_superoperators.jl

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ end
204204
@test tracedistance(L*ρ₀, ρ) < 1e-10
205205
@test tracedistance(ChoiState(L)*ρ₀, ρ) < 1e-10
206206

207+
# TODO: reenable these now that QuantumOptics.jl is a test dependency?
207208
# tout, ρt = timeevolution.master([0.,1.], ρ₀, H, J; reltol=1e-7)
208209
# @test tracedistance(exp(dense(L))*ρ₀, ρt[end]) < 1e-6
209210
# @test tracedistance(exp(sparse(L))*ρ₀, ρt[end]) < 1e-6
@@ -265,9 +266,9 @@ dec_kraus = KrausOperators(b_logical, b_fock, [dec_proj])
265266
@test ChoiState(dec_sup) == dagger(ChoiState(enc_sup))
266267
@test ChoiState(dec_kraus) == dagger(ChoiState(enc_kraus))
267268
@test SuperOperator(ChoiState(enc_sup)) == enc_sup
268-
@test SuperOperator(KrausOperators(enc_sup)) == enc_sup
269-
@test KrausOperators(ChoiState(enc_kraus)) == enc_kraus
270-
@test KrausOperators(SuperOperator(enc_kraus)) == enc_kraus
269+
@test SuperOperator(KrausOperators(enc_sup)) enc_sup
270+
@test KrausOperators(ChoiState(enc_kraus)) enc_kraus
271+
@test KrausOperators(SuperOperator(enc_kraus)) enc_kraus
271272
## testing multipication
272273
@test dec_sup*enc_sup dense(identitysuperoperator(b_logical))
273274
@test SuperOperator(dec_kraus*enc_kraus) dense(identitysuperoperator(b_logical))
@@ -277,34 +278,42 @@ dec_kraus = KrausOperators(b_logical, b_fock, [dec_proj])
277278
@test dec_sup*ChoiState(enc_sup) dense(identitysuperoperator(b_logical))
278279
@test ChoiState(dec_sup)*enc_sup dense(identitysuperoperator(b_logical))
279280
@test SuperOperator(ChoiState(dec_sup)*ChoiState(enc_sup)) dense(identitysuperoperator(b_logical))
280-
281-
@test avg_gate_fidelity(dec_sup*enc_sup) 1
282-
@test avg_gate_fidelity(dec_kraus*enc_kraus) 1
283-
@test avg_gate_fidelity(ChoiState(dec_sup)*ChoiState(enc_sup)) 1
281+
## testing channel checks
282+
@test is_cptp(enc_kraus)
283+
@test is_cptni(dec_kraus)
284+
@test is_cptp(enc_sup)
285+
@test is_cptni(dec_sup)
286+
@test is_cptp(ChoiState(enc_kraus))
287+
@test is_cptni(ChoiState(dec_kraus))
288+
289+
# TODO: fix avg_gate_fidelity to work with all superoperator types
290+
#@test avg_gate_fidelity(dec_sup*enc_sup) ≈ 1
291+
#@test avg_gate_fidelity(dec_kraus*enc_kraus) ≈ 1
292+
#@test avg_gate_fidelity(ChoiState(dec_sup)*ChoiState(enc_sup)) ≈ 1
284293

285294
# test amplitude damping channel
286-
function amplitude_damp_kraus_op(b, γ, l)
295+
function amplitude_damp_kraus_op(b, κ, l)
296+
γ = 1-exp(-κ)
287297
op = SparseOperator(b)
288298
for n=l:(length(b)-1)
289299
op.data[n-l+1, n+1] = sqrt(binomial(n,l) * (1-γ)^(n-l) * γ^l)
290300
end
291301
return op
292302
end
293303

294-
function test_kraus_channel(N, γ, tol)
304+
function test_amplitude_damp_kraus_channel(N, κ, tol)
295305
b = FockBasis(N)
296-
super = exp(liouvillian(identityoperator(b), [destroy(b)]))
297-
kraus = KrausOperators(b, b, [amplitude_damp_kraus_op(b, γ, i) for i=0:(N-1)])
298-
@test SuperOperator(kraus) super
299-
@test ChoiState(kraus) ChoiState(super)
300-
@test kraus KrausOperators(super; tol=tol)
301-
@test is_trace_preserving(kraus; tol=tol)
302-
@test is_valid_channel(kraus; tol=tol)
306+
super = exp(liouvillian(identityoperator(b), [destroy(b)]; rates=[κ]))
307+
kraus = KrausOperators(b, b, [amplitude_damp_kraus_op(b, κ, i) for i=0:(N-1)])
308+
@test isapprox(SuperOperator(kraus), super; atol=tol)
309+
@test isapprox(ChoiState(kraus), ChoiState(super); atol=tol)
310+
@test isapprox(kraus, KrausOperators(super; tol=tol); atol=tol)
311+
@test is_cptp(kraus; tol=tol)
303312
end
304313

305-
test_kraus_channel(10, 0.1, 1e-8)
306-
test_kraus_channel(20, 0.1, 1e-8)
307-
test_kraus_channel(10, 0.01, 1e-8)
308-
test_kraus_channel(20, 0.01, 1e-8)
314+
test_amplitude_damp_kraus_channel(10, 0.1, 1e-7)
315+
test_amplitude_damp_kraus_channel(20, 0.1, 1e-7)
316+
test_amplitude_damp_kraus_channel(10, 0.01, 1e-7)
317+
test_amplitude_damp_kraus_channel(20, 0.01, 1e-8)
309318

310319
end # testset

0 commit comments

Comments
 (0)