# src/graphcalc/quantum/channel_properties.py
from __future__ import annotations
from math import isclose
import numpy as np
from graphcalc.metadata import invariant_metadata
from graphcalc.quantum.channels import QuantumChannel
__all__ = [
"is_completely_positive",
"is_trace_preserving",
"is_unital",
"is_quantum_channel",
"is_unitary_channel",
]
[docs]
@invariant_metadata(
display_name="Complete positivity",
notation=r"\mathrm{CP}(\Phi)",
category="quantum channel properties",
aliases=("is completely positive",),
definition=(
"A linear map Φ is completely positive if its Choi matrix J(Φ) is positive semidefinite."
),
)
def is_completely_positive(channel: QuantumChannel, *, tol: float | None = None) -> bool:
"""
Return whether the channel is completely positive.
Parameters
----------
channel : QuantumChannel
Input channel.
tol : float | None, default=None
Numerical tolerance used for positive semidefiniteness. If omitted,
``channel.tol`` is used.
Notes
-----
By the Choi theorem, a linear map is completely positive if and only if
its Choi matrix is positive semidefinite.
"""
use_tol = channel.tol if tol is None else float(tol)
choi = channel.choi
herm = 0.5 * (choi + choi.conj().T)
evals = np.linalg.eigvalsh(herm)
return bool(np.all(evals >= -use_tol))
[docs]
@invariant_metadata(
display_name="Trace-preserving property",
notation=r"\mathrm{TP}(\Phi)",
category="quantum channel properties",
aliases=("is trace preserving",),
definition=(
"A linear map Φ is trace preserving if it preserves the trace of every input operator."
),
)
def is_trace_preserving(channel: QuantumChannel, *, tol: float | None = None) -> bool:
"""
Return whether the channel is trace preserving.
Parameters
----------
channel : QuantumChannel
Input channel.
tol : float | None, default=None
Numerical tolerance for matrix comparison. If omitted, ``channel.tol``
is used.
Notes
-----
For the Choi convention used in ``QuantumChannel``, trace preservation is
equivalent to the partial trace over the output subsystem equaling the
identity on the input space.
"""
use_tol = channel.tol if tol is None else float(tol)
ptr_out = channel._partial_trace_output()
ident = np.eye(channel.input_dim, dtype=complex)
return bool(np.allclose(ptr_out, ident, atol=use_tol, rtol=0.0))
[docs]
@invariant_metadata(
display_name="Unital property",
notation=r"\mathrm{unital}(\Phi)",
category="quantum channel properties",
aliases=("is unital",),
definition=(
"A linear map Φ is unital if it preserves the identity operator."
),
)
def is_unital(channel: QuantumChannel, *, tol: float | None = None) -> bool:
"""
Return whether the channel is unital.
Parameters
----------
channel : QuantumChannel
Input channel.
tol : float | None, default=None
Numerical tolerance for matrix comparison. If omitted, ``channel.tol``
is used.
Notes
-----
A channel is unital if it preserves the identity operator. For the Choi
convention used in ``QuantumChannel``, this is equivalent to the partial
trace over the input subsystem equaling the identity on the output space.
"""
use_tol = channel.tol if tol is None else float(tol)
ptr_in = channel._partial_trace_input()
ident = np.eye(channel.output_dim, dtype=complex)
return bool(np.allclose(ptr_in, ident, atol=use_tol, rtol=0.0))
[docs]
@invariant_metadata(
display_name="Quantum channel property",
notation=r"\mathrm{CPTP}(\Phi)",
category="quantum channel properties",
aliases=("is quantum channel", "is CPTP"),
definition=(
"A quantum channel is a completely positive trace-preserving linear map."
),
)
def is_quantum_channel(channel: QuantumChannel, *, tol: float | None = None) -> bool:
"""
Return whether the input defines a completely positive trace-preserving map.
Parameters
----------
channel : QuantumChannel
Input channel.
tol : float | None, default=None
Numerical tolerance used in the underlying tests.
Notes
-----
In this module, “quantum channel” means a completely positive,
trace-preserving linear map.
"""
return is_completely_positive(channel, tol=tol) and is_trace_preserving(
channel, tol=tol
)
[docs]
@invariant_metadata(
display_name="Unitary channel property",
notation=r"\mathrm{unitary}(\Phi)",
category="quantum channel properties",
aliases=("is unitary channel",),
definition=(
"A quantum channel Φ is unitary if there exists a unitary operator U such that Φ(ρ) = UρU^† for all input states ρ."
),
)
def is_unitary_channel(channel: QuantumChannel, *, tol: float | None = None) -> bool:
"""
Return whether the channel is a unitary channel.
Parameters
----------
channel : QuantumChannel
Input channel.
tol : float | None, default=None
Numerical tolerance used in rank and matrix comparisons. If omitted,
``channel.tol`` is used.
Notes
-----
A channel is a unitary channel if it has the form
``Phi(rho) = U rho U^dagger``
for some unitary matrix ``U``.
For finite-dimensional channels, this is equivalent to the existence of a
Kraus representation with a single Kraus operator that is unitary.
With the Choi representation, a necessary condition is that the Choi matrix
has rank one. For a CPTP square channel, rank one is sufficient to recover
a single Kraus operator, which is then tested for unitarity.
"""
use_tol = channel.tol if tol is None else float(tol)
if channel.input_dim != channel.output_dim:
return False
if not is_quantum_channel(channel, tol=use_tol):
return False
if channel.choi_rank != 1:
return False
kraus = channel.kraus_operators()
if len(kraus) != 1:
return False
op = kraus[0]
ident = np.eye(channel.input_dim, dtype=complex)
return bool(
np.allclose(op.conj().T @ op, ident, atol=use_tol, rtol=0.0)
and np.allclose(op @ op.conj().T, ident, atol=use_tol, rtol=0.0)
)