Source code for graphcalc.quantum.channel_generators

from __future__ import annotations

from typing import Sequence

import numpy as np

from graphcalc.quantum.channels import QuantumChannel

__all__ = [
    "identity_channel",
    "depolarizing_channel",
    "bit_flip_channel",
    "phase_flip_channel",
    "bit_phase_flip_channel",
    "phase_damping_channel",
    "amplitude_damping_channel",
]


[docs] def identity_channel(dim: int, *, tol: float = 1e-9) -> QuantumChannel: """ Return the identity channel on a ``dim``-dimensional system. """ return QuantumChannel.identity(dim, tol=tol)
[docs] def depolarizing_channel( p: float, *, dim: int = 2, tol: float = 1e-9, ) -> QuantumChannel: r""" Return the depolarizing channel on a ``dim``-dimensional system. Parameters ---------- p : float Mixing parameter. Must satisfy ``0 <= p <= 1``. dim : int, default=2 Hilbert-space dimension. tol : float, default=1e-9 Numerical tolerance passed to ``QuantumChannel``. Notes ----- This implementation uses the convention ``Phi(rho) = (1 - p) rho + p * Tr(rho) * I / dim``. For density operators with ``Tr(rho)=1``, this is ``Phi(rho) = (1 - p) rho + p I / dim``. A Kraus representation is given by - ``K0 = sqrt(1-p) I`` - ``K_{ij} = sqrt(p/dim) E_{ij}`` for all ``0 <= i,j < dim``, where ``E_{ij}`` is the matrix unit with a 1 in position ``(i,j)`` and zeros elsewhere. """ if not 0.0 <= p <= 1.0: raise ValueError("p must satisfy 0 <= p <= 1.") if dim <= 0: raise ValueError("dim must be positive.") kraus = [np.sqrt(1.0 - p) * np.eye(dim, dtype=complex)] coeff = np.sqrt(p / dim) for i in range(dim): for j in range(dim): eij = np.zeros((dim, dim), dtype=complex) eij[i, j] = 1.0 kraus.append(coeff * eij) return QuantumChannel.from_kraus(kraus, input_dim=dim, output_dim=dim, tol=tol)
[docs] def bit_flip_channel(p: float, *, tol: float = 1e-9) -> QuantumChannel: r""" Return the qubit bit-flip channel. Parameters ---------- p : float Flip probability. Must satisfy ``0 <= p <= 1``. tol : float, default=1e-9 Numerical tolerance passed to ``QuantumChannel``. Notes ----- This channel is defined by ``Phi(rho) = (1 - p) rho + p X rho X``, where ``X`` is the Pauli X operator. """ if not 0.0 <= p <= 1.0: raise ValueError("p must satisfy 0 <= p <= 1.") I = np.eye(2, dtype=complex) X = np.array([[0, 1], [1, 0]], dtype=complex) kraus = [ np.sqrt(1.0 - p) * I, np.sqrt(p) * X, ] return QuantumChannel.from_kraus(kraus, tol=tol)
[docs] def phase_flip_channel(p: float, *, tol: float = 1e-9) -> QuantumChannel: r""" Return the qubit phase-flip channel. Parameters ---------- p : float Flip probability. Must satisfy ``0 <= p <= 1``. tol : float, default=1e-9 Numerical tolerance passed to ``QuantumChannel``. Notes ----- This channel is defined by ``Phi(rho) = (1 - p) rho + p Z rho Z``, where ``Z`` is the Pauli Z operator. """ if not 0.0 <= p <= 1.0: raise ValueError("p must satisfy 0 <= p <= 1.") I = np.eye(2, dtype=complex) Z = np.array([[1, 0], [0, -1]], dtype=complex) kraus = [ np.sqrt(1.0 - p) * I, np.sqrt(p) * Z, ] return QuantumChannel.from_kraus(kraus, tol=tol)
[docs] def bit_phase_flip_channel(p: float, *, tol: float = 1e-9) -> QuantumChannel: r""" Return the qubit bit-phase-flip channel. Parameters ---------- p : float Flip probability. Must satisfy ``0 <= p <= 1``. tol : float, default=1e-9 Numerical tolerance passed to ``QuantumChannel``. Notes ----- This channel is defined by ``Phi(rho) = (1 - p) rho + p Y rho Y``, where ``Y`` is the Pauli Y operator. """ if not 0.0 <= p <= 1.0: raise ValueError("p must satisfy 0 <= p <= 1.") I = np.eye(2, dtype=complex) Y = np.array([[0, -1j], [1j, 0]], dtype=complex) kraus = [ np.sqrt(1.0 - p) * I, np.sqrt(p) * Y, ] return QuantumChannel.from_kraus(kraus, tol=tol)
[docs] def phase_damping_channel(gamma: float, *, tol: float = 1e-9) -> QuantumChannel: r""" Return the qubit phase-damping channel. Parameters ---------- gamma : float Dephasing parameter. Must satisfy ``0 <= gamma <= 1``. tol : float, default=1e-9 Numerical tolerance passed to ``QuantumChannel``. Notes ----- This channel preserves populations and damps coherences: ``[[rho00, rho01], [rho10, rho11]] -> [[rho00, sqrt(1-gamma) rho01], [sqrt(1-gamma) rho10, rho11]]``. A Kraus representation is ``K0 = diag(1, sqrt(1-gamma))``, ``K1 = diag(0, sqrt(gamma))``. """ if not 0.0 <= gamma <= 1.0: raise ValueError("gamma must satisfy 0 <= gamma <= 1.") k0 = np.array( [[1.0, 0.0], [0.0, np.sqrt(1.0 - gamma)]], dtype=complex, ) k1 = np.array( [[0.0, 0.0], [0.0, np.sqrt(gamma)]], dtype=complex, ) return QuantumChannel.from_kraus([k0, k1], tol=tol)
[docs] def amplitude_damping_channel(gamma: float, *, tol: float = 1e-9) -> QuantumChannel: r""" Return the qubit amplitude-damping channel. Parameters ---------- gamma : float Damping parameter. Must satisfy ``0 <= gamma <= 1``. tol : float, default=1e-9 Numerical tolerance passed to ``QuantumChannel``. Notes ----- This channel models relaxation from ``|1>`` to ``|0>`` with probability ``gamma``. A Kraus representation is ``K0 = [[1, 0], [0, sqrt(1-gamma)]]``, ``K1 = [[0, sqrt(gamma)], [0, 0]]``. In particular, - ``|0><0|`` is fixed, - ``|1><1|`` is mapped to ``gamma |0><0| + (1-gamma) |1><1|``. This channel is trace preserving and completely positive, but in general it is not unital. """ if not 0.0 <= gamma <= 1.0: raise ValueError("gamma must satisfy 0 <= gamma <= 1.") k0 = np.array( [[1.0, 0.0], [0.0, np.sqrt(1.0 - gamma)]], dtype=complex, ) k1 = np.array( [[0.0, np.sqrt(gamma)], [0.0, 0.0]], dtype=complex, ) return QuantumChannel.from_kraus([k0, k1], tol=tol)