torch_vn_algebra.channels¶
Source: torch_vn_algebra/channels.py. Signatures and docstrings as in the source.
Module documentation¶
Quantum channels on a Type I algebra M = (+)_c M_{k_c} that preserve the sectors.
A channel is stored by its Kraus operators, one tensor of shape
(batch, r, C, k_max, k_max): r Kraus operators K_i = (+)_c K_{i,c}, block c in the
top-left k_c x k_c corner as for operators. In the Schroedinger picture
Phi(rho) = sum_i K_i rho K_i^*,
trace preserving iff sum_i K_i^* K_i = 1 and unital iff sum_i K_i K_i^* = 1.
The Heisenberg-picture (dual) channel has Kraus operators K_i^*. Because every block is
mapped to itself, the duality Tr(Phi(rho) A) = Tr(rho Phi^*(A)) holds blockwise and hence for
each of the traces Tr_blunt, Tr_norm and tau_vN.
The Kraus batch dimension is 1 (one channel for all samples) or equal to the batch of the
operators it acts on.
Channel¶
class Channel(algebra: TypeIAlgebra, kraus: torch.Tensor, trace_preserving: Optional[bool]=None)
Sector-preserving completely positive map given by Kraus operators.
Channel.batch_size¶
@property
def batch_size(self) -> int
Channel.kraus_rank¶
@property
def kraus_rank(self) -> int
Channel.apply¶
def apply(self, op: Operator) -> Operator
Schroedinger picture: rho -> sum_i K_i rho K_i^* (lazy).
A DensityMatrix is mapped to a DensityMatrix if the channel is trace preserving;
otherwise the result is a plain Operator and a warning is issued.
Channel.adjoint¶
def adjoint(self) -> 'Channel'
Heisenberg-picture channel A -> sum_i K_i^* A K_i.
Channel.compose¶
def compose(self, other: 'Channel') -> 'Channel'
self o other: first `other`, then `self` (Kraus operators K_i L_j).
Channel.to_inter_sector¶
def to_inter_sector(self) -> 'InterSectorChannel'
The same map as an InterSectorChannel with algebra_in = algebra_out.
Channel.mix¶
def mix(self, other: 'Channel', p: float) -> 'Channel'
Convex combination (1 - p) self + p other.
Channel.trace_preservation_error¶
def trace_preservation_error(self) -> torch.Tensor
max |sum_i K_i^* K_i - 1| per channel in the batch.
Channel.unitality_error¶
def unitality_error(self) -> torch.Tensor
max |sum_i K_i K_i^* - 1| per channel in the batch.
Channel.is_trace_preserving¶
def is_trace_preserving(self, tol: Optional[float]=None) -> bool
Channel.is_unital¶
def is_unital(self, tol: Optional[float]=None) -> bool
Channel.choi¶
def choi(self) -> List[torch.Tensor]
Choi matrices per channel c, J_c = sum_ij |i><j| (x) Phi(|i><j|), shape (batch, k_c^2, k_c^2).
Positive semidefinite for a completely positive map; Tr_out J = 1 iff trace preserving.
Channel.superoperator¶
def superoperator(self) -> List[torch.Tensor]
Matrix of Phi on row-major vec(rho), per channel: S_c = sum_i K_i (x) conj(K_i), (batch, k_c^2, k_c^2).
Channel.from_superoperator¶
@staticmethod
def from_superoperator(algebra: TypeIAlgebra, blocks: Sequence[torch.Tensor], tol: Optional[float]=None) -> 'Channel'
Channel from per-channel superoperators S_c of shape (batch, k_c^2, k_c^2) acting on row-major
vec(rho). Kraus operators are obtained from the eigendecomposition of the Choi matrix; S_c must
describe a completely positive map. A Choi matrix J with ||J - J^*|| > 2 tol ||J|| (the map does not
preserve Hermiticity) or with an eigenvalue below -tol ||J|| raises, where ||.|| is the spectral norm
(taken at least 1); by default tol = 1e-10 for double and 1e-4 for single-precision input. Small
negative eigenvalues within the tolerance are clamped to zero.
identity_channel¶
def identity_channel(alg: TypeIAlgebra) -> Channel
unitary_channel¶
def unitary_channel(U: Operator) -> Channel
rho -> U rho U^* (U unitary in the algebra, possibly batched).
kraus_channel¶
def kraus_channel(ops: Sequence[Operator]) -> Channel
Channel with the given Kraus operators (all from the same algebra).
dephasing_channel¶
def dephasing_channel(alg: TypeIAlgebra, p: float) -> Channel
rho_c -> (1 - p) rho_c + p diag(rho_c) in the standard basis of each block (unital).
depolarizing_channel¶
def depolarizing_channel(alg: TypeIAlgebra, p: float) -> Channel
rho_c -> (1 - p) rho_c + p Tr(rho_c) 1/k_c in every block (unital, keeps the weight of each
sector). p = 1 is the trace-preserving conditional expectation onto the centre of M.
center_expectation¶
def center_expectation(alg: TypeIAlgebra) -> Channel
Conditional expectation onto the centre Z(M): A -> sum_c (Tr A_c / k_c) P_c.
amplitude_damping_channel¶
def amplitude_damping_channel(alg: TypeIAlgebra, gamma: float) -> Channel
Decay of every level i > 0 to the level 0 of its block with probability gamma:
K_0 = |0><0| + sqrt(1 - gamma) sum_{i>0} |i><i|, K_i = sqrt(gamma) |0><i|.
Trace preserving, not unital for gamma > 0.
measurement_channel¶
def measurement_channel(projections: Sequence[Operator]) -> Channel
Non-selective Lueders measurement rho -> sum_j P_j rho P_j for a resolution of the identity.
random_channel¶
def random_channel(alg: TypeIAlgebra, kraus_rank: int, batch_size: int=1) -> Channel
Random trace-preserving channel from a Haar-random Stinespring isometry
V_c: C^{k_c} -> C^r (x) C^{k_c} in every block (environment first), K_{i,c} = (<i| (x) 1) V_c.
random_mixed_unitary_channel¶
def random_mixed_unitary_channel(alg: TypeIAlgebra, n_unitaries: int, batch_size: int=1, weights: Optional[torch.Tensor]=None) -> Channel
rho -> sum_j p_j U_j rho U_j^* with Haar unitaries in M and Dirichlet(1) (or given) weights; unital.
InterSectorChannel¶
class InterSectorChannel(algebra_in: TypeIAlgebra, algebra_out: TypeIAlgebra, kraus: torch.Tensor, trace_preserving: Optional[bool]=None)
Completely positive map Phi: M_in = (+)_c M_{k_c} -> M_out = (+)_d M_{m_d} between (possibly
different) Type I algebras, in Schroedinger form
Phi(rho)_d = sum_c sum_i K_i^{dc} rho_c (K_i^{dc})^*, K_i^{dc}: C^{k_c} -> C^{m_d}.
Every normal CP map between such algebras has this form: Phi = sum_{d,c} Phi_{dc} with
Phi_{dc}: M_{k_c} -> M_{m_d} CP. Kraus operators are stored as one tensor of shape
(batch, r, D, C, m_max, k_max); blocks of rank < r are zero padded. The classical part of
Phi is the matrix of sector transitions T[d, c] = Tr Phi_{dc}(rho_c) / Tr rho_c.
Trace preserving iff sum_{d,i} K_i^{dc*} K_i^{dc} = 1_c for every input sector c.
InterSectorChannel.from_blocks¶
@staticmethod
def from_blocks(algebra_in: TypeIAlgebra, algebra_out: TypeIAlgebra, blocks: dict, batch_size: int=1) -> 'InterSectorChannel'
Build from {(d, c): [K_1, K_2, ...]} with K_i of shape (m_d, k_c) or (batch, m_d, k_c);
missing pairs (d, c) are zero maps.
InterSectorChannel.apply¶
def apply(self, op: Operator) -> Operator
Schroedinger picture (lazy). Trace-preserving maps send a DensityMatrix to a DensityMatrix.
InterSectorChannel.adjoint¶
def adjoint(self, trace: str='blunt') -> 'InterSectorChannel'
Dual map Phi^*: M_out -> M_in with tr_out(Phi(rho) A) = tr_in(rho Phi^*(A)) for the chosen
trace ('blunt', 'norm' or 'tau_vN' on both algebras). With weights w_c of the trace,
Phi^*(A)_c = sum_{d,i} (w^out_d / w^in_c) K_i^{dc*} A_d K_i^{dc}.
InterSectorChannel.compose¶
def compose(self, other: 'InterSectorChannel') -> 'InterSectorChannel'
self o other. Kraus operators K_j^{ed} L_i^{dc}, indexed by (j, i, d).
InterSectorChannel.trace_preservation_error¶
def trace_preservation_error(self) -> torch.Tensor
InterSectorChannel.is_trace_preserving¶
def is_trace_preserving(self, tol: Optional[float]=None) -> bool
InterSectorChannel.is_unital¶
def is_unital(self, tol: Optional[float]=None) -> bool
Phi(1) = 1 (Schroedinger picture, blunt trace).
InterSectorChannel.transition_matrix¶
def transition_matrix(self, rho: Optional[Operator]=None) -> torch.Tensor
Sector transition probabilities T[b, d, c] = Tr Phi_{dc}(rho_c) / Tr rho_c, for the given state
or, by default, for the maximally mixed state of every input sector. Columns sum to 1 for a
trace-preserving map.
random_inter_sector_channel¶
def random_inter_sector_channel(algebra_in: TypeIAlgebra, algebra_out: TypeIAlgebra, kraus_rank: int=1, batch_size: int=1) -> InterSectorChannel
Random trace-preserving map from a Haar-random Stinespring isometry per input sector,
V_c: C^{k_c} -> C^r (x) ((+)_d C^{m_d}) (environment first), split into the blocks
K_i^{dc} = (<i| (x) P_d) V_c.