Skip to content

pauli_channel

Generates the Choi matrix of a Pauli channel.

pauli_channel

pauli_channel(
    prob: int | ndarray, return_kraus_ops: bool = False
) -> ndarray | tuple

Return the Choi matrix of a Pauli channel.

The Pauli channel is defined by a set of Pauli operators weighted by a 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 lexicographically 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).

Parameters:

  • prob (int | ndarray) –

    Probability vector for Pauli operators. If scalar, generates random probabilities for \(q =\) prob qubits. The probabilities correspond to Pauli operators in lexicographical order of length strictly equal to \(q\), when prob is a vector.

  • return_kraus_ops (bool, default: False ) –

    If True, return the list of Kraus operators instead of the Choi matrix.

Returns:

  • ndarray | tuple

    The Choi matrix of the Pauli channel, or its list of Kraus operators when

  • ndarray | tuple

    return_kraus_ops is True.

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.

Examples:

Generate the Choi matrix of a random single-qubit Pauli channel:

from toqito.channels import pauli_channel

print(pauli_channel(prob=1))
[[ 0.45156796+0.j  0.        +0.j  0.        +0.j -0.29460065+0.j]
 [ 0.        +0.j  0.54843204+0.j -0.10005221+0.j  0.        +0.j]
 [ 0.        +0.j -0.10005221+0.j  0.54843204+0.j  0.        +0.j]
 [-0.29460065+0.j  0.        +0.j  0.        +0.j  0.45156796+0.j]]

Apply a specific two-qubit Pauli channel to an input matrix via apply_channel:

import numpy as np
from toqito.channels import pauli_channel
from toqito.channel_ops import apply_channel

choi = pauli_channel(prob=np.array([0.1, 0.2, 0.3, 0.4]))
print(apply_channel(np.eye(2), choi))
[[1.+0.j 0.+0.j]
 [0.+0.j 1.+0.j]]
Source code in toqito/channels/pauli_channel.py
def pauli_channel(prob: int | np.ndarray, return_kraus_ops: bool = False) -> np.ndarray | tuple:
    r"""Return the Choi matrix of a Pauli channel.

    The Pauli channel is defined by a set of Pauli operators weighted by a 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 lexicographically 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).

    Args:
        prob: Probability vector for Pauli operators. If scalar, generates random probabilities for \(q =\) `prob`
            qubits. The probabilities correspond to Pauli operators in lexicographical order of length strictly equal to
            \(q\), when `prob` is a vector.
        return_kraus_ops: If `True`, return the list of Kraus operators instead of the Choi matrix.

    Returns:
        The Choi matrix of the Pauli channel, or its list of Kraus operators when
        `return_kraus_ops` is `True`.

    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``.

    Examples:
        Generate the Choi matrix of a random single-qubit Pauli channel:

        ```python exec="1" source="above" result="text"
        from toqito.channels import pauli_channel

        print(pauli_channel(prob=1))
        ```

        Apply a specific two-qubit Pauli channel to an input matrix via `apply_channel`:

        ```python exec="1" source="above" result="text"
        import numpy as np
        from toqito.channels import pauli_channel
        from toqito.channel_ops import apply_channel

        choi = pauli_channel(prob=np.array([0.1, 0.2, 0.3, 0.4]))
        print(apply_channel(np.eye(2), choi))
        ```

    """
    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).")

    # The channel's Kraus operators are sqrt(prob_j) * P_j, so its Choi matrix is the Choi of this flat (completely
    # positive) Kraus list. Building it with a single kraus_to_choi call avoids one conversion per Pauli (4^q calls).
    kraus_operators = [
        np.sqrt(prob[j]) * pauli(list(ind)) for j, ind in enumerate(itertools.product(range(4), repeat=q))
    ]
    Phi = kraus_to_choi(kraus_operators)

    if return_kraus_ops:
        return kraus_operators

    return Phi