Source code for toqito.channels.pauli_channel
"""Generates and applies Pauli Channel to a matrix."""
import numpy as np
from scipy import sparse
from toqito.channel_ops.kraus_to_choi import kraus_to_choi
from toqito.helper.update_odometer import update_odometer
from toqito.matrices.pauli import pauli
[docs]
def pauli_channel(
prob: int | np.ndarray, return_kraus_ops: bool = False, input_mat: np.ndarray | None = None
) -> np.ndarray | sparse.csc_matrix | tuple:
r"""Generate and apply a Pauli channel to a matrix.
Generates the Choi matrix of a Pauli channel with given probabilities and optionally applies it
to an input matrix. The Pauli channel is defined by the set of Pauli operators weighted by
the probability vector. For a given probability vector \((p_0, \ldots, p_{4^q -1 })\), the
channel is defined as shown below. Where, $q$ is the number of qubits.
\[
\Phi(\rho) = \sum_{i=0}^{4^q - 1} p_i P_i \rho P_i^*
\]
where \(P_i\) are Pauli operators generated by a lexographically increasing sequence of pauli operators of
length strictly equal to \(q\), and \(p_i\) is the corresponding probability of that operator.
For example, when \(q = 2\),
\(P_{0} = I \otimes I\), \(P_{1} = I \otimes X\), \(P_{2} = I \otimes Y\), \(P_{3} = I \otimes Z\),
\(P_{4} = X \otimes I\), \(P_{5} = X \otimes Y , \ldots P_{15} = Z \otimes Z\)
If `prob` is a scalar, it generates a random `prob`-qubit Pauli channel.
The length of the probability vector (if provided) must be \(4^q\) for some
integer \(q\) (number of qubits).
Examples:
Generate a random single-qubit Pauli channel:
```python exec="1" source="above"
from toqito.channels import pauli_channel
print(pauli_channel(prob=1))
```
Apply a specific two-qubit Pauli channel to an input matrix:
```python exec="1" source="above"
import numpy as np
from toqito.channels import pauli_channel
_, output = pauli_channel(
prob=np.array([0.1, 0.2, 0.3, 0.4]), input_mat=np.eye(2)
)
print(output)
```
Raises:
ValueError: If probabilities are negative or don't sum to 1.
ValueError: If length of probability vector is not ``4^q`` for some integer ``q``.
Args:
prob: Probability vector for Pauli operators. If scalar, generates random probabilities for \(q =\) `prob`
qubits. The probabilities correspond to Pauli operators in lexographical order of length strictly equal to \(q\)
,when `prob` is a vector.
return_kraus_ops: Flag to return Kraus operators. Default is ``False``.
input_mat: Optional input matrix to apply the channel to. Default is ``None``.
Returns:
The Choi matrix of the channel. If ``input_mat`` is provided, also returns the output matrix.
If ``return_kraus_ops`` is ``True``, returns Kraus operators as well.
"""
if not isinstance(prob, np.ndarray):
if np.isscalar(prob):
q = prob
prob = np.random.rand(4**q)
prob /= np.sum(prob)
else:
prob = np.array(prob)
if np.any(prob < 0) or not np.isclose(np.sum(prob), 1):
raise ValueError("Probabilities must be non-negative and sum to 1.")
q = int(np.round(np.log2(len(prob)) / 2))
if len(prob) != 4**q:
raise ValueError("The length of the probability vector must be 4^q for some integer q (number of qubits).")
Phi = sparse.csc_matrix((4**q, 4**q), dtype=complex)
kraus_operators = []
ind = np.zeros(q, dtype=int)
for j in range(len(prob)):
pauli_op = pauli(ind.tolist())
kraus_operators.append(np.sqrt(prob[j]) * pauli_op)
Phi += prob[j] * kraus_to_choi([[pauli_op, pauli_op.conj().T]])
ind = update_odometer(ind, 4 * np.ones(q, dtype=int))
output_mat = None
if input_mat is not None:
output_mat = sum(k @ input_mat @ k.conj().T for k in kraus_operators)
if return_kraus_ops:
return (Phi, output_mat, kraus_operators) if input_mat is not None else (Phi, kraus_operators)
return (Phi, output_mat) if input_mat is not None else Phi