1+ import Base: isapprox
12import QuantumInterface: AbstractSuperOperator
23import 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
7377Base. length (a:: SuperOperator ) = length (a. basis_l[1 ])* length (a. basis_l[2 ])* length (a. basis_r[1 ])* length (a. basis_r[2 ])
7478samebases (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
387392Superoperator 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:
388405Note 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]`
390407which 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
423442Canonicalize the set kraus operators by performing a qr decomposition.
424443A 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
431450operators returned is guaranteed to be no greater than dd' and will furthermore
432451be 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)
449468end
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
454473SuperOperator (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)
483517end
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
506537end
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)
534556end
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+
0 commit comments