@@ -319,62 +319,13 @@ end
319319end
320320
321321# 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 \t o H_B,
329- a quantum channel can only act on valid density operators which live in H_A \t o H_A.
330- Thus the Kraus representation is only defined for quantum channels which map
331- (H_A \t o H_A) \t o (H_B \t o 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-
374322"""
375323 ChoiState <: AbstractSuperOperator
376324
377325Superoperator represented as a choi state.
326+
327+ The convention is chosen such that the input operators live in `(basis_l[1], basis_r[1])` while
328+ the output operators live in `(basis_r[2], basis_r[2])`.
378329"""
379330mutable struct ChoiState{B1,B2,T} <: AbstractSuperOperator{B1,B2}
380331 basis_l:: B1
@@ -399,6 +350,9 @@ dagger(a::ChoiState) = ChoiState(dagger(SuperOperator(a)))
399350* (a:: ChoiState , b:: Operator ) = SuperOperator (a)* b
400351== (a:: ChoiState , b:: ChoiState ) = (SuperOperator (a) == SuperOperator (b))
401352
353+ # 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)
355+
402356# reshape swaps within systems due to colum major ordering
403357# https://docs.qojulia.org/quantumobjects/operators/#tensor_order
404358function _super_choi ((l1, l2), (r1, r2), data)
@@ -426,50 +380,133 @@ end
426380ChoiState (op:: SuperOperator ) = ChoiState (_super_choi (op. basis_l, op. basis_r, op. data)... )
427381SuperOperator (op:: ChoiState ) = SuperOperator (_super_choi (op. basis_l, op. basis_r, op. data)... )
428382
429- * (a:: ChoiState , b:: SuperOperator ) = SuperOperator (a)* b
430- * (a:: SuperOperator , b:: ChoiState ) = a* SuperOperator (b)
383+
384+ """
385+ KrausOperators <: AbstractSuperOperator
386+
387+ Superoperator represented as a list of Kraus operators.
388+ Note unlike the SuperOperator or ChoiState types where it is possible to have
389+ `basis_l[1] != basis_l[2]` and `basis_r[1] != basis_r[2]`
390+ which allows representations of maps between general linear operators defined on ``H_A \\ to H_B``,
391+ a quantum channel can only act on valid density operators which live in ``H_A \\ to H_A``.
392+ Thus the Kraus representation is only defined for quantum channels which map
393+ ``(H_A \\ to H_A) \\ to (H_B \\ to H_B)``.
394+ """
395+ mutable struct KrausOperators{B1,B2,T} <: AbstractSuperOperator{B1,B2}
396+ basis_l:: B1
397+ basis_r:: B2
398+ data:: T
399+ function KrausOperators {BL,BR,T} (basis_l:: BL , basis_r:: BR , data:: T ) where {BL,BR,T}
400+ if (any (! samebases (basis_r, M. basis_r) for M in data) ||
401+ any (! samebases (basis_l, M. basis_l) for M in data))
402+ throw (DimensionMismatch (" Tried to assign data with incompatible bases" ))
403+ end
404+
405+ new (basis_l, basis_r, data)
406+ end
407+ end
408+ KrausOperators {BL,BR} (b1:: BL ,b2:: BR ,data:: T ) where {BL,BR,T} = KrausOperators {BL,BR,T} (b1,b2,data)
409+ KrausOperators (b1:: BL ,b2:: BR ,data:: T ) where {BL,BR,T} = KrausOperators {BL,BR,T} (b1,b2,data)
410+
411+ tensor (a:: KrausOperators , b:: KrausOperators ) =
412+ KrausOperators (a. basis_l ⊗ b. basis_l, a. basis_r ⊗ b. basis_r,
413+ [A ⊗ B for A in a. data for B in b. data])
414+ dagger (a:: KrausOperators ) = KrausOperators (a. basis_r, a. basis_l, [dagger (op) for op in a. data])
415+ * (a:: KrausOperators{B1,B2} , b:: KrausOperators{B2,B3} ) where {B1,B2,B3} =
416+ KrausOperators (a. basis_l, b. basis_r, [A* B for A in a. data for B in b. data])
417+ * (a:: KrausOperators , b:: KrausOperators ) = throw (IncompatibleBases ())
418+ * (a:: KrausOperators{BL,BR} , b:: Operator{BR,BR} ) where {BL,BR} = sum (op* b* dagger (op) for op in a. data)
419+
420+ """
421+ canonicalize(kraus::KrausOperators; tol=1e-9)
422+
423+ Canonicalize the set kraus operators by performing a qr decomposition.
424+ A quantum channel with kraus operators ``{A_k}`` is in cannonical form if and only if
425+
426+ ```math
427+ \\ Tr A_i^\\ dagger A_j \\ sim \\ delta_{i,j}
428+ ```
429+
430+ If the input dimension is d and output dimension is d' then the number of kraus
431+ operators returned is guaranteed to be no greater than dd' and will furthermore
432+ be equal the Kraus rank of the channel up to numerical imprecision controlled by `tol`.
433+ """
434+ function canonicalize (kraus:: KrausOperators ; tol= 1e-9 )
435+ bl, br = kraus. basis_l, kraus. basis_r
436+ dim = length (bl)* length (br)
437+
438+ A = stack (reshape (op. data, dim) for op in kraus. data; dims= 1 )
439+ F = qr (A; tol= tol)
440+ # rank(F) for some reason doesn't work but should
441+ rank = maximum (findnz (F. R)[1 ])
442+ # Sanity checks that help illustrate what qr() returns:
443+ # @assert (F.R ≈ (sparse(F.Q') * A[F.prow,F.pcol])[1:dim,:])
444+ # @assert (all(iszero(F.R[rank+1:end,:])))
445+
446+ ops = [Operator (bl, br, copy (reshape ( # copy materializes reshaped view
447+ F. R[i,invperm (F. pcol)], (length (bl), length (br))))) for i= 1 : rank]
448+ return KrausOperators (bl, br, ops)
449+ end
450+
451+ # TODO : check if canonicalize and orthogonalize are equivalent
452+ orthogonalize (kraus:: KrausOperators ; tol= 1e-9 ) = KrausOperators (ChoiState (kraus); tol= tol)
431453
432454SuperOperator (kraus:: KrausOperators ) =
433455 SuperOperator ((kraus. basis_l, kraus. basis_l), (kraus. basis_r, kraus. basis_r),
434456 (sum (conj (op)⊗ op for op in kraus. data)). data)
435457
436- ChoiState (kraus:: KrausOperators ; tol = 1e-9 ) =
458+ ChoiState (kraus:: KrausOperators ) =
437459 ChoiState ((kraus. basis_r, kraus. basis_l), (kraus. basis_r, kraus. basis_l),
438460 (sum ((M= op. data; reshape (M, (length (M), 1 ))* reshape (M, (1 , length (M))))
439- for op in kraus. data)); tol = tol )
461+ for op in kraus. data)))
440462
441- function KrausOperators (choi:: ChoiState ; tol= 1e-9 )
463+ function KrausOperators (choi:: ChoiState ; tol= 1e-9 , warn = true )
442464 if (! samebases (choi. basis_l[1 ], choi. basis_r[1 ]) ||
443465 ! 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" ))
466+ throw (DimensionMismatch (" Tried to convert Choi state of something that isn't a quantum channel mapping density operators to density operators" ))
467+ end
468+ # TODO : consider using https://github.com/jlapeyre/IsApprox.jl
469+ if ! ishermitian (choi. data) || ! isapprox (choi. data, choi. data' , atol= tol)
470+ throw (ArgumentError (" Tried to convert nonhermitian Choi state" ))
445471 end
446472 bl, br = choi. basis_l[2 ], choi. basis_l[1 ]
447- # ishermitian(choi.data) || @warn "ChoiState is not hermitian"
448473 # TODO : figure out how to do this with sparse matrices using e.g. Arpack.jl or ArnoldiMethod.jl
449474 vals, vecs = eigen (Hermitian (Matrix (choi. data)))
450475 for val in vals
451- (abs (val) > tol && val < 0 ) && @warn " eigval $(val) < 0 but abs(eigval) > tol=$(tol) "
476+ if warn && (abs (val) > tol && val < 0 )
477+ @warn " eigval $(val) < 0 but abs(eigval) > tol=$(tol) "
478+ end
452479 end
453480 ops = [Operator (bl, br, sqrt (val)* reshape (vecs[:,i], length (bl), length (br)))
454481 for (i, val) in enumerate (vals) if abs (val) > tol && val > 0 ]
455482 return KrausOperators (bl, br, ops)
456483end
457484
458- KrausOperators (op:: SuperOperator ; tol= 1e-9 ) = KrausOperators (ChoiState (op; tol = tol ); tol= tol)
485+ KrausOperators (op:: SuperOperator ; tol= 1e-9 ) = KrausOperators (ChoiState (op); tol= tol)
459486
487+ # TODO : document superoperator representation precident: everything of mixed type returns SuperOperator
460488* (a:: ChoiState , b:: SuperOperator ) = SuperOperator (a)* b
461489* (a:: SuperOperator , b:: ChoiState ) = a* SuperOperator (b)
462490* (a:: KrausOperators , b:: SuperOperator ) = SuperOperator (a)* b
463491* (a:: SuperOperator , b:: KrausOperators ) = a* SuperOperator (b)
464492* (a:: KrausOperators , b:: ChoiState ) = SuperOperator (a)* SuperOperator (b)
465493* (a:: ChoiState , b:: KrausOperators ) = SuperOperator (a)* SuperOperator (b)
466494
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)
495+ # 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)
498+
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)
471506end
472507
508+ is_trace_preserving (super:: SuperOperator ; tol= 1e-9 ) = is_trace_preserving (ChoiState (super); tol= tol)
509+
473510# this check seems suspect... since it fails while the below check on choi succeeeds
474511# function is_valid_channel(kraus::KrausOperators; tol=1e-9)
475512# m = I(length(kraus.basis_r)) - sum(dagger(M)*M for M in kraus.data).data
484521# return iszero(eigs)
485522# end
486523
487- function is_valid_channel (choi:: ChoiState ; tol= 1e-9 )
524+ # TODO : pull out check for valid quantum channel
525+ # 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
488531 any (abs .(choi. data - choi. data' ) .> tol) && return false
489532 eigs = eigvals (Hermitian (Matrix (choi. data)))
490533 return all (@. abs (eigs) < tol || eigs > 0 )
491534end
535+
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)
538+
539+ # TODO : document this
540+ is_cptp (sop) = is_completly_positive (sop) && is_trace_preserving (sop)
0 commit comments