Skip to content

Quantum Operations

This page documents all quantum operations available in Squint, organized by category.


Fock Space Operations

Operations for continuous variable (CV) quantum systems using the Fock (photon number) basis. These are commonly used for simulating linear optical networks and photonic quantum computing.

Key classes:

  • FockState - Fock basis states and superpositions
  • BeamSplitter - Two-mode beam splitter
  • Phase - Single-mode phase shift
  • LinearOpticalUnitaryGate - General passive linear optical transformation

FockState

Bases: AbstractPureState

Fock (photon number) state for continuous variable systems.

Represents a pure quantum state in the Fock basis, where each mode contains a definite number of photons. Can represent single Fock states or arbitrary superpositions of Fock states.

The state is specified as a sum of basis states with amplitudes: \(\(|\psi\rangle = \sum_i a_i |n_1^{(i)}, n_2^{(i)}, \ldots\rangle\)\)

where \(n_k^{(i)}\) is the photon number in mode \(k\) for term \(i\).

Attributes:

Name Type Description
n Sequence[tuple[complex, Sequence[int]]]

List of (amplitude, basis_indices) tuples defining the state.

Example
wire0 = Wire(dim=5, idx=0)
wire1 = Wire(dim=5, idx=1)

# Single Fock state |1,0>
state = FockState(wires=(wire0, wire1), n=(1, 0))

# Vacuum state |0,0> (default)
vacuum = FockState(wires=(wire0, wire1))

# Superposition (|1,0> + |0,1>)/sqrt(2)
noon = FockState(wires=(wire0, wire1), n=[
    (1.0, (1, 0)),
    (1.0, (0, 1))
])
Source code in src/squint/ops/fock.py
class FockState(AbstractPureState):
    r"""
    Fock (photon number) state for continuous variable systems.

    Represents a pure quantum state in the Fock basis, where each mode contains
    a definite number of photons. Can represent single Fock states or arbitrary
    superpositions of Fock states.

    The state is specified as a sum of basis states with amplitudes:
    $$|\psi\rangle = \sum_i a_i |n_1^{(i)}, n_2^{(i)}, \ldots\rangle$$

    where $n_k^{(i)}$ is the photon number in mode $k$ for term $i$.

    Attributes:
        n: List of (amplitude, basis_indices) tuples defining the state.

    Example:
        ```python
        wire0 = Wire(dim=5, idx=0)
        wire1 = Wire(dim=5, idx=1)

        # Single Fock state |1,0>
        state = FockState(wires=(wire0, wire1), n=(1, 0))

        # Vacuum state |0,0> (default)
        vacuum = FockState(wires=(wire0, wire1))

        # Superposition (|1,0> + |0,1>)/sqrt(2)
        noon = FockState(wires=(wire0, wire1), n=[
            (1.0, (1, 0)),
            (1.0, (0, 1))
        ])
        ```
    """

    n: Sequence[tuple[complex, Sequence[int]]]

    @beartype
    def __init__(
        self,
        wires: Sequence[Wire],
        n: Sequence[int] | Sequence[tuple[complex | float, Sequence[int]]] = None,
    ):
        super().__init__(wires=wires)
        if n is None:
            n = [(1.0, (0,) * len(wires))]  # initialize to |vac> = |0, 0, ...> state
        elif is_bearable(n, Sequence[int]):
            n = [(1.0, n)]
        elif is_bearable(n, Sequence[tuple[complex | float, Sequence[int]]]):
            norm = jnp.sum(jnp.abs(jnp.array([amp for amp, wires in n])) ** 2)
            n = [(amp / jnp.sqrt(norm).item(), wires) for amp, wires in n]
        self.n = paramax.non_trainable(n)
        return

    def __call__(self):
        return sum(
            [
                jnp.zeros(shape=[wire.dim for wire in self.wires])
                .at[*term[1]]
                .set(term[0])
                for term in self.n
            ]
        )

FixedEnergyFockState

Bases: AbstractPureState

Fock state superposition with fixed total photon number.

Creates a parameterized superposition over all Fock basis states with a fixed total number of photons \(n\) distributed across all modes. The state is parameterized by trainable weights and phases for each basis state.

The state has the form: \(\(|\psi\rangle = \sum_{\{n_i\}: \sum_i n_i = n} w_i e^{i\phi_i} |n_1, n_2, \ldots\rangle\)\)

where the sum is over all distributions of \(n\) photons across the modes, and \(w_i\) are softmax-normalized weights.

Attributes:

Name Type Description
weights ArrayLike

Trainable weights for each basis state (before softmax).

phases ArrayLike

Trainable phases for each basis state.

n int

Total photon number (energy).

bases Sequence[tuple[complex | float, Sequence[int]]]

List of all valid photon number distributions.

Example
wire0 = Wire(dim=5, idx=0)
wire1 = Wire(dim=5, idx=1)

# Single photon distributed across two modes
state = FixedEnergyFockState(wires=(wire0, wire1), n=1)
# Creates superposition of |1,0> and |0,1>
Source code in src/squint/ops/fock.py
class FixedEnergyFockState(AbstractPureState):
    r"""
    Fock state superposition with fixed total photon number.

    Creates a parameterized superposition over all Fock basis states with a
    fixed total number of photons $n$ distributed across all modes. The state
    is parameterized by trainable weights and phases for each basis state.

    The state has the form:
    $$|\psi\rangle = \sum_{\{n_i\}: \sum_i n_i = n} w_i e^{i\phi_i} |n_1, n_2, \ldots\rangle$$

    where the sum is over all distributions of $n$ photons across the modes,
    and $w_i$ are softmax-normalized weights.

    Attributes:
        weights (ArrayLike): Trainable weights for each basis state (before softmax).
        phases (ArrayLike): Trainable phases for each basis state.
        n (int): Total photon number (energy).
        bases: List of all valid photon number distributions.

    Example:
        ```python
        wire0 = Wire(dim=5, idx=0)
        wire1 = Wire(dim=5, idx=1)

        # Single photon distributed across two modes
        state = FixedEnergyFockState(wires=(wire0, wire1), n=1)
        # Creates superposition of |1,0> and |0,1>
        ```
    """

    weights: ArrayLike
    phases: ArrayLike
    n: int
    bases: Sequence[tuple[complex | float, Sequence[int]]]

    @beartype
    def __init__(
        self,
        wires: Sequence[Wire],
        n: int = 1,
        weights: Optional[ArrayLike] = None,
        phases: Optional[ArrayLike] = None,
        key: Optional[jaxtyping.PRNGKeyArray] = None,
    ):
        super().__init__(wires=wires)

        def fixed_energy_states(length, energy):
            if length == 1:
                yield (energy,)
            else:
                for value in range(energy + 1):
                    for permutation in fixed_energy_states(length - 1, energy - value):
                        yield (value,) + permutation

        self.n = n
        self.bases = list(fixed_energy_states(len(wires), n))
        if not weights:
            weights = jnp.ones(shape=(len(self.bases),))
            # weights = jnp.linspace(1.0, 2.0, len(self.bases))
        if not phases:
            phases = jnp.zeros(shape=(len(self.bases),))
            # phases = jnp.linspace(1.0, 2.0, len(self.bases))

        if key is not None:
            subkeys = jr.split(key, 2)
            weights = jr.normal(subkeys[0], shape=weights.shape)
            phases = jr.normal(subkeys[1], shape=phases.shape)

        self.weights = weights
        self.phases = phases
        return

    def __call__(self, dim: int):
        return jnp.einsum(
            "i, i... -> ...",
            jnp.exp(1j * self.phases) * jnp.sqrt(jax.nn.softmax(self.weights)),
            jnp.array(
                [
                    jnp.zeros(shape=(dim,) * len(self.wires)).at[*basis].set(1.0)
                    for basis in self.bases
                ]
            ),
        )

TwoModeWeakThermalState

Bases: AbstractMixedState

Two-mode weak coherent source.

Source code in src/squint/ops/fock.py
class TwoModeWeakThermalState(AbstractMixedState):
    r"""
    Two-mode weak coherent source.
    """

    g: ArrayLike
    phi: ArrayLike
    epsilon: ArrayLike

    @beartype
    def __init__(
        self,
        wires: Sequence[Wire],
        epsilon: float,
        g: float,
        phi: float,
    ):
        super().__init__(wires=wires)
        self.epsilon = jnp.array(epsilon)
        self.g = jnp.array(g)
        self.phi = jnp.array(phi)
        return

    def __call__(self):
        assert len(self.wires) == 2, "not correct wires"
        # assert dim == 2, "not correct dim"
        dims = (
            self.wires[0].dim,
            self.wires[1].dim,
            self.wires[0].dim,
            self.wires[1].dim,
        )
        rho = jnp.zeros(shape=dims, dtype=jnp.complex128)
        rho = rho.at[0, 0, 0, 0].set(1 - self.epsilon)
        rho = rho.at[0, 1, 0, 1].set(self.epsilon / 2)
        rho = rho.at[1, 0, 1, 0].set(self.epsilon / 2)
        rho = rho.at[0, 1, 1, 0].set(self.g * jnp.exp(1j * self.phi) * self.epsilon / 2)
        rho = rho.at[1, 0, 0, 1].set(
            self.g * jnp.exp(-1j * self.phi) * self.epsilon / 2
        )
        return rho

TwoModeSqueezingGate

Bases: AbstractGate

TwoModeSqueezingGate

Source code in src/squint/ops/fock.py
class TwoModeSqueezingGate(AbstractGate):
    r"""
    TwoModeSqueezingGate
    """

    r: ArrayLike
    phi: ArrayLike

    @beartype
    def __init__(self, wires: tuple[Wire, Wire], r, phi):
        super().__init__(wires=wires)
        self.r = jnp.asarray(r)
        self.phi = jnp.asarray(phi)
        return

    def __call__(self):
        dims = (
            self.wires[0].dim,
            self.wires[1].dim,
            self.wires[0].dim,
            self.wires[1].dim,
        )

        s2_l = jnp.kron(create(self.wires[0].dim), create(self.wires[1].dim))
        s2_r = jnp.kron(destroy(self.wires[0].dim), destroy(self.wires[1].dim))
        u = jax.scipy.linalg.expm(
            1j * jnp.tanh(self.r) * (jnp.conjugate(self.phi) * s2_l - self.phi * s2_r)
        ).reshape(dims)
        return u

BeamSplitter

Bases: AbstractGate

Beam splitter gate for two optical modes.

Implements a lossless beam splitter that mixes two optical modes. The transformation is generated by the photon exchange Hamiltonian: \(\(H = i(a^\dagger b - a b^\dagger)\)\)

resulting in the unitary: \(\(U(r) = \exp(i r (a^\dagger b + a b^\dagger))\)\)

where \(a\), \(b\) are annihilation operators for the two modes. The parameter \(r\) controls the splitting ratio: \(r = \pi/4\) gives a 50:50 beam splitter.

Attributes:

Name Type Description
r ArrayLike

Beam splitter angle in radians. Default is \(\pi/4\) (50:50 splitter).

Example
wire0 = Wire(dim=5, idx=0)
wire1 = Wire(dim=5, idx=1)

# 50:50 beam splitter
bs = BeamSplitter(wires=(wire0, wire1), r=jnp.pi/4)

# Variable beam splitter for optimization
bs = BeamSplitter(wires=(wire0, wire1), r=0.3)
Source code in src/squint/ops/fock.py
class BeamSplitter(AbstractGate):
    r"""
    Beam splitter gate for two optical modes.

    Implements a lossless beam splitter that mixes two optical modes. The
    transformation is generated by the photon exchange Hamiltonian:
    $$H = i(a^\dagger b - a b^\dagger)$$

    resulting in the unitary:
    $$U(r) = \exp(i r (a^\dagger b + a b^\dagger))$$

    where $a$, $b$ are annihilation operators for the two modes. The parameter
    $r$ controls the splitting ratio: $r = \pi/4$ gives a 50:50 beam splitter.

    Attributes:
        r (ArrayLike): Beam splitter angle in radians. Default is $\pi/4$ (50:50 splitter).

    Example:
        ```python
        wire0 = Wire(dim=5, idx=0)
        wire1 = Wire(dim=5, idx=1)

        # 50:50 beam splitter
        bs = BeamSplitter(wires=(wire0, wire1), r=jnp.pi/4)

        # Variable beam splitter for optimization
        bs = BeamSplitter(wires=(wire0, wire1), r=0.3)
        ```
    """

    r: ArrayLike

    @beartype
    def __init__(
        self,
        wires: tuple[Wire, Wire],
        r: float | ArrayLike = jnp.pi / 4,
    ):
        super().__init__(wires=wires)
        self.r = jnp.array(r)
        return

    def __call__(self):
        dims = (
            self.wires[0].dim,
            self.wires[1].dim,
            self.wires[0].dim,
            self.wires[1].dim,
        )

        bs_l = jnp.kron(create(self.wires[0].dim), destroy(self.wires[1].dim))
        bs_r = jnp.kron(destroy(self.wires[0].dim), create(self.wires[1].dim))
        u = jax.scipy.linalg.expm(1j * self.r * (bs_l + bs_r)).reshape(dims)
        return u  # TODO: this is correct for the `mixed` backend, while... DONE: this should be correct for both backends now

LinearOpticalUnitaryGate

Bases: AbstractGate

General linear optical unitary gate acting on multiple modes.

Implements an arbitrary passive linear optical transformation defined by a unitary matrix \(U\) acting on the mode creation operators: \(\(a_j^\dagger \to \sum_k U_{jk} a_k^\dagger\)\)

The unitary on the Fock space is computed using the permanent formula for bosonic transformations. This allows implementing arbitrary linear optical networks (combinations of beam splitters and phase shifters).

Attributes:

Name Type Description
unitary_modes ArrayLike

An \(m \times m\) unitary matrix defining the mode transformation, where \(m\) is the number of modes (wires).

Example
wire0 = Wire(dim=5, idx=0)
wire1 = Wire(dim=5, idx=1)

# Hadamard-like transformation
U = jnp.array([[1, 1], [1, -1]]) / jnp.sqrt(2)
gate = LinearOpticalUnitaryGate(wires=(wire0, wire1), unitary_modes=U)

# 3-mode Fourier transform
wires = tuple(Wire(dim=4, idx=i) for i in range(3))
U = jnp.fft.fft(jnp.eye(3)) / jnp.sqrt(3)
gate = LinearOpticalUnitaryGate(wires=wires, unitary_modes=U)
Note

The unitary_modes matrix must be unitary (U @ U.conj().T = I). Computation uses the permanent which can be expensive for large photon numbers or many modes.

Source code in src/squint/ops/fock.py
class LinearOpticalUnitaryGate(AbstractGate):
    r"""
    General linear optical unitary gate acting on multiple modes.

    Implements an arbitrary passive linear optical transformation defined by
    a unitary matrix $U$ acting on the mode creation operators:
    $$a_j^\dagger \to \sum_k U_{jk} a_k^\dagger$$

    The unitary on the Fock space is computed using the permanent formula
    for bosonic transformations. This allows implementing arbitrary linear
    optical networks (combinations of beam splitters and phase shifters).

    Attributes:
        unitary_modes (ArrayLike): An $m \times m$ unitary matrix defining the
            mode transformation, where $m$ is the number of modes (wires).

    Example:
        ```python
        wire0 = Wire(dim=5, idx=0)
        wire1 = Wire(dim=5, idx=1)

        # Hadamard-like transformation
        U = jnp.array([[1, 1], [1, -1]]) / jnp.sqrt(2)
        gate = LinearOpticalUnitaryGate(wires=(wire0, wire1), unitary_modes=U)

        # 3-mode Fourier transform
        wires = tuple(Wire(dim=4, idx=i) for i in range(3))
        U = jnp.fft.fft(jnp.eye(3)) / jnp.sqrt(3)
        gate = LinearOpticalUnitaryGate(wires=wires, unitary_modes=U)
        ```

    Note:
        The unitary_modes matrix must be unitary (U @ U.conj().T = I).
        Computation uses the permanent which can be expensive for large
        photon numbers or many modes.
    """

    unitary_modes: ArrayLike  # unitary which acts on the optical modes

    @beartype
    def __init__(
        self,
        wires: tuple[Wire, ...],
        unitary_modes: ArrayLike,
    ):
        super().__init__(wires=wires)
        assert unitary_modes.shape == (len(wires), len(wires)), (
            "Number of wires does not match mode unitary shape."
        )
        assert jnp.allclose(
            unitary_modes @ unitary_modes.T.conj(), jnp.eye(len(wires), len(wires))
        ), "`unitary_modes` arg is not unitary."
        self.unitary_modes = unitary_modes
        return

    def _init_static_arrays(self, dim: int):
        """
        For each number of photons, n > 0, we generate all combinations and compute the A_ij square matrices.
        Next, we compute the U_{i,j} coefficients for input i and output j bases by taking the permanent.
        We insert these coefficients into the larger U_{i,j} matrix, which is a square matrix of size cut^m x cut^m.
        We need to do this for all 0 < n <= cut, where cut is the cutoff for the number of photons in each mode.
        """
        # create the indices for input and output as an ndarray. [m, dim, dim, dim, ..., dim]; we use this for the factorial and for referencing the index ordering
        m = len(self.wires)

        # generate the indices for calculating all of the factorial normalization arrays
        idx_i = jnp.indices((dim,) * m)
        idx_j = copy.deepcopy(idx_i)

        idx_i_fac = jnp.prod(jax.scipy.special.factorial(idx_i), axis=0)
        idx_j_fac = jnp.prod(jax.scipy.special.factorial(idx_j), axis=0)

        factorial_weight = jnp.einsum(
            "i,j->ij",
            1 / jnp.sqrt(idx_i_fac).reshape(dim**m),
            1 / jnp.sqrt(idx_j_fac).reshape(dim**m),
        ).reshape((dim,) * m * 2)

        # for each n <= cut, generate all combinations of indices
        # this is done for each n (number of excitations), rather than all possible number bases at once,
        # as the Aij matrix is not square, and the interferometer is by definition linear
        inds_n_i = [list(get_fixed_sum_tuples(m, n)) for n in range(dim)]
        inds_n_j = copy.deepcopy(inds_n_i)

        def pairwise_combinations(A, B):
            return jnp.stack(
                [
                    A[:, None, :].repeat(B.shape[0], axis=1),
                    B[None, :, :].repeat(A.shape[0], axis=0),
                ],
                axis=2,
            ).reshape(-1, 2, A.shape[1])

        # calculate all pairs of input & output bases, along with their transition indices for creating the Aij matrices
        pairs, transition_inds = [], []
        for n in range(dim):
            p = pairwise_combinations(jnp.array(inds_n_i[n]), jnp.array(inds_n_j[n]))
            pairs.append(p.reshape(p.shape[0], -1))

            t_inds = compile_Aij_indices(inds_n_i[n], inds_n_j[n], m, n)
            transition_inds.append(t_inds)

        return transition_inds, pairs, factorial_weight

    def __call__(self):
        # generate all of the static arrays for the indices, transition indices to create Aij for all n
        # and the factorial normalization array
        dim = self.wires[0].dim  # TODO: use the dims for all wires
        transition_inds, pairs, factorial_weight = self._init_static_arrays(dim)

        # map the unitary acting on the modes (m x m) to the unitary acting on number states,
        # computed as the Perm[Aij] for all combinations of i and j number bases
        def map_unitary(unitary_modes):
            unitary_number = jnp.zeros((dim,) * 2 * len(self.wires), dtype=jnp.complex_)
            for n in range(dim):
                coefficients = compute_transition_amplitudes(
                    unitary_modes, transition_inds[n]
                )
                unitary_number = unitary_number.at[tuple(pairs[n].T)].set(
                    coefficients.flatten()
                )
            unitary_number = unitary_number * factorial_weight
            return unitary_number

        unitary_number = map_unitary(self.unitary_modes)

        return unitary_number

Phase

Bases: AbstractGate

Phase shift gate for optical modes.

Applies a phase shift \(\phi\) to an optical mode, which is equivalent to a rotation in the optical phase space. The transformation acts on the Fock states as: \(\(|n\rangle \to e^{in\phi} |n\rangle\)\)

This is generated by the number operator: \(U(\phi) = e^{i\phi \hat{n}}\).

Attributes:

Name Type Description
phi ArrayLike

Phase shift angle in radians.

Example
wire = Wire(dim=5, idx=0)

# Fixed phase shift
phase = Phase(wires=(wire,), phi=jnp.pi/4)

# Trainable phase for optimization
phase = Phase(wires=(wire,), phi=0.0)  # Initialize to zero
Note

This gate is commonly used in Mach-Zehnder interferometers and for implementing phase estimation protocols in linear optics.

Source code in src/squint/ops/fock.py
class Phase(AbstractGate):
    r"""
    Phase shift gate for optical modes.

    Applies a phase shift $\phi$ to an optical mode, which is equivalent to
    a rotation in the optical phase space. The transformation acts on the
    Fock states as:
    $$|n\rangle \to e^{in\phi} |n\rangle$$

    This is generated by the number operator: $U(\phi) = e^{i\phi \hat{n}}$.

    Attributes:
        phi (ArrayLike): Phase shift angle in radians.

    Example:
        ```python
        wire = Wire(dim=5, idx=0)

        # Fixed phase shift
        phase = Phase(wires=(wire,), phi=jnp.pi/4)

        # Trainable phase for optimization
        phase = Phase(wires=(wire,), phi=0.0)  # Initialize to zero
        ```

    Note:
        This gate is commonly used in Mach-Zehnder interferometers and
        for implementing phase estimation protocols in linear optics.
    """

    phi: ArrayLike

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
        phi: float | int | ArrayLike = 0.0,
    ):
        super().__init__(wires=wires)
        self.phi = jnp.array(phi)
        return

    def __call__(self):
        return jnp.diag(jnp.exp(1j * bases(self.wires[0].dim) * self.phi))

Discrete Variable Operations

Operations for finite-dimensional quantum systems including qubits (dim=2) and qudits (dim>2). Includes standard gates like Pauli rotations, Hadamard, and controlled gates.

Key classes:

  • DiscreteVariableState - Computational basis states and superpositions
  • XGate, ZGate, HGate - Standard single-qubit gates (generalized for qudits)
  • RXGate, RYGate, RZGate - Parameterized rotation gates
  • CXGate, CZGate - Controlled gates

DiscreteVariableState

Bases: AbstractPureState

A pure quantum state for a discrete variable system.

\(|\psi\rangle = \sum_{i} a_i |i\rangle\) where \(a_i\) are amplitudes and \(|i\rangle\) are basis states.

Source code in src/squint/ops/dv.py
class DiscreteVariableState(AbstractPureState):
    r"""
    A pure quantum state for a discrete variable system.

    $|\psi\rangle = \sum_{i} a_i |i\rangle$ where $a_i$ are amplitudes and $|i\rangle$ are basis states.
    """

    n: Sequence[
        tuple[complex, Sequence[int]]
    ]  # todo: add superposition as n, using second typehint

    @beartype
    def __init__(
        self,
        wires: Sequence[Wire],
        n: Sequence[int] | Sequence[tuple[complex | float, Sequence[int]]] = None,
    ):
        super().__init__(wires=wires)
        if n is None:
            n = [(1.0, (0,) * len(wires))]  # initialize to |0, 0, ...> state
        elif is_bearable(n, Sequence[int]):
            n = [(1.0, n)]
        elif is_bearable(n, Sequence[tuple[complex | float, Sequence[int]]]):
            norm = jnp.sum(jnp.abs(jnp.array([i[0] for i in n])) ** 2)
            n = [((amp / jnp.sqrt(norm)).item(), basis) for amp, basis in n]
        self.n = paramax.non_trainable(n)
        return

    def __call__(self):
        return sum(
            [
                jnp.zeros(
                    # shape=(dim,) * len(self.wires)
                    shape=[wire.dim for wire in self.wires]
                )
                .at[*term[1]]
                .set(term[0])
                for term in self.n
            ]
        )

MaximallyMixedState

Bases: AbstractMixedState

The maximally mixed state for discrete variable systems.

Represents the completely mixed density matrix \(\rho = I/d\) where \(d\) is the total Hilbert space dimension. This state has maximum von Neumann entropy and represents complete ignorance about the quantum state.

The density matrix is constructed as: \(\(\rho = \frac{1}{d} \sum_{i=0}^{d-1} |i\rangle\langle i|\)\)

where \(d = \prod_i d_i\) is the product of all wire dimensions.

Example
wire = Wire(dim=2, idx=0)
state = MaximallyMixedState(wires=(wire,))
# Creates rho = [[0.5, 0], [0, 0.5]]
Note

This state requires the "mixed" backend in the circuit.

Source code in src/squint/ops/dv.py
class MaximallyMixedState(AbstractMixedState):
    r"""
    The maximally mixed state for discrete variable systems.

    Represents the completely mixed density matrix $\rho = I/d$ where $d$ is the
    total Hilbert space dimension. This state has maximum von Neumann entropy
    and represents complete ignorance about the quantum state.

    The density matrix is constructed as:
    $$\rho = \frac{1}{d} \sum_{i=0}^{d-1} |i\rangle\langle i|$$

    where $d = \prod_i d_i$ is the product of all wire dimensions.

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        state = MaximallyMixedState(wires=(wire,))
        # Creates rho = [[0.5, 0], [0, 0.5]]
        ```

    Note:
        This state requires the "mixed" backend in the circuit.
    """

    @beartype
    def __init__(
        self,
        wires: Sequence[Wire],
    ):
        super().__init__(wires=wires)

    def __call__(self):
        dims = [wire.dim for wire in self.wires]
        d = math.prod(dims)
        identity = jnp.eye(d, dtype=jnp.complex128) / d
        tensor = identity.reshape(tuple(dim for dim in dims for _ in range(2)))
        return tensor

XGate

Bases: AbstractGate

The generalized shift operator, which when dim = 2 corresponds to the standard \(X\) gate.

\(U = \sum_{k=0}^{d-1} |k\rangle \langle (k+1) \mod d|\)

Source code in src/squint/ops/dv.py
class XGate(AbstractGate):
    r"""
    The generalized shift operator, which when `dim = 2` corresponds to the standard $X$ gate.

    $U = \sum_{k=0}^{d-1} |k\rangle \langle (k+1) \mod d|$
    """

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
    ):
        super().__init__(wires=wires)
        return

    def __call__(self):
        return jnp.roll(jnp.eye(self.wires[0].dim, k=0), shift=1, axis=0)

ZGate

Bases: AbstractGate

The generalized phase operator, which when dim = 2 corresponds to the standard \(Z\) gate.

\(U = \sum_{k=0}^{d-1} e^{2\pi i k / d} |k\rangle\langle k|\)

Source code in src/squint/ops/dv.py
class ZGate(AbstractGate):
    r"""
    The generalized phase operator, which when `dim = 2` corresponds to the standard $Z$ gate.

    $U = \sum_{k=0}^{d-1} e^{2\pi i k / d} |k\rangle\langle k|$
    """

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
    ):
        super().__init__(wires=wires)
        return

    def __call__(self):
        return jnp.diag(
            jnp.exp(1j * 2 * jnp.pi * jnp.arange(self.wires[0].dim) / self.wires[0].dim)
        )

HGate

Bases: AbstractGate

The generalized discrete Fourier operator, which when dim = 2 corresponds to the standard \(H\) gate.

\(U = \frac{1}{\sqrt{d}} \sum_{j,k=0}^{d-1} e^{2\pi i jk / d} |j\rangle\langle k|\)

Source code in src/squint/ops/dv.py
class HGate(AbstractGate):
    r"""
    The generalized discrete Fourier operator, which when `dim = 2` corresponds to the standard $H$ gate.

    $U = \frac{1}{\sqrt{d}} \sum_{j,k=0}^{d-1} e^{2\pi i jk / d} |j\rangle\langle k|$
    """

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
    ):
        super().__init__(wires=wires)
        return

    def __call__(self):
        dim = self.wires[0].dim
        return jnp.exp(
            1j
            * 2
            * jnp.pi
            / dim
            * jnp.einsum("a,b->ab", jnp.arange(dim), jnp.arange(dim))
        ) / jnp.sqrt(dim)

Conditional

Bases: AbstractGate

The generalized conditional operator. Applies gate \(U\) raised to a power conditional on the control state: \(U = \sum_{k=0}^{d-1} |k\rangle\langle k| \otimes U^k\)

Source code in src/squint/ops/dv.py
class Conditional(AbstractGate):
    r"""
    The generalized conditional operator.
    Applies gate $U$ raised to a power conditional on the control state:
    $U = \sum_{k=0}^{d-1} |k\rangle\langle k| \otimes U^k$
    """

    gate: Union[XGate, ZGate]  # type: ignore

    @beartype
    def __init__(
        self,
        gate: Union[Type[XGate], Type[ZGate]],
        wires: tuple[Wire, Wire] = (0, 1),
    ):
        super().__init__(wires=wires)
        self.gate = gate(wires=(wires[1],))
        return

    def __call__(self):
        u = sum(
            [
                jnp.einsum(
                    "ac,bd -> abcd",
                    jnp.zeros(shape=(self.wires[0].dim, self.wires[0].dim))
                    .at[i, i]
                    .set(1.0),
                    jnp.linalg.matrix_power(self.gate(), i),
                )
                for i in range(self.wires[0].dim)
            ]
        )

        return u

CXGate

Bases: Conditional

Controlled-X (CNOT) gate for qubits and qudits.

Applies an X gate to the target qubit/qudit conditional on the control. For qubits (dim=2), this is the standard CNOT gate: \(\(CX = |0\rangle\langle 0| \otimes I + |1\rangle\langle 1| \otimes X\)\)

For qudits (dim>2), this generalizes to: \(\(CX = \sum_{k=0}^{d-1} |k\rangle\langle k| \otimes X^k\)\)

Parameters:

Name Type Description Default
wires tuple[Wire, Wire]

Tuple of (control_wire, target_wire).

(0, 1)
Example
control = Wire(dim=2, idx=0)
target = Wire(dim=2, idx=1)
cx = CXGate(wires=(control, target))
Source code in src/squint/ops/dv.py
class CXGate(Conditional):
    r"""
    Controlled-X (CNOT) gate for qubits and qudits.

    Applies an X gate to the target qubit/qudit conditional on the control.
    For qubits (dim=2), this is the standard CNOT gate:
    $$CX = |0\rangle\langle 0| \otimes I + |1\rangle\langle 1| \otimes X$$

    For qudits (dim>2), this generalizes to:
    $$CX = \sum_{k=0}^{d-1} |k\rangle\langle k| \otimes X^k$$

    Args:
        wires: Tuple of (control_wire, target_wire).

    Example:
        ```python
        control = Wire(dim=2, idx=0)
        target = Wire(dim=2, idx=1)
        cx = CXGate(wires=(control, target))
        ```
    """

    @beartype
    def __init__(
        self,
        wires: tuple[Wire, Wire] = (0, 1),
    ):
        super().__init__(wires=wires, gate=XGate)

CZGate

Bases: Conditional

Controlled-Z gate for qubits and qudits.

Applies a Z gate to the target qubit/qudit conditional on the control. For qubits (dim=2), this is the standard CZ gate: \(\(CZ = |0\rangle\langle 0| \otimes I + |1\rangle\langle 1| \otimes Z\)\)

For qudits (dim>2), this generalizes to: \(\(CZ = \sum_{k=0}^{d-1} |k\rangle\langle k| \otimes Z^k\)\)

Parameters:

Name Type Description Default
wires tuple[Wire, Wire]

Tuple of (control_wire, target_wire).

(0, 1)
Example
control = Wire(dim=2, idx=0)
target = Wire(dim=2, idx=1)
cz = CZGate(wires=(control, target))
Source code in src/squint/ops/dv.py
class CZGate(Conditional):
    r"""
    Controlled-Z gate for qubits and qudits.

    Applies a Z gate to the target qubit/qudit conditional on the control.
    For qubits (dim=2), this is the standard CZ gate:
    $$CZ = |0\rangle\langle 0| \otimes I + |1\rangle\langle 1| \otimes Z$$

    For qudits (dim>2), this generalizes to:
    $$CZ = \sum_{k=0}^{d-1} |k\rangle\langle k| \otimes Z^k$$

    Args:
        wires: Tuple of (control_wire, target_wire).

    Example:
        ```python
        control = Wire(dim=2, idx=0)
        target = Wire(dim=2, idx=1)
        cz = CZGate(wires=(control, target))
        ```
    """

    @beartype
    def __init__(
        self,
        wires: tuple[Wire, Wire] = (0, 1),
    ):
        super().__init__(wires=wires, gate=ZGate)

RZGate

Bases: AbstractGate

Rotation gate around the Z-axis for qubits and qudits.

For qubits (dim=2), this implements the standard RZ rotation: \(\(R_Z(\phi) = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\phi} \end{pmatrix}\)\)

For qudits (dim>2), this generalizes to: \(\(R_Z(\phi) = \sum_{k=0}^{d-1} e^{ik\phi} |k\rangle\langle k|\)\)

Attributes:

Name Type Description
phi ArrayLike

The rotation angle in radians.

Example
wire = Wire(dim=2, idx=0)
rz = RZGate(wires=(wire,), phi=jnp.pi/4)
Source code in src/squint/ops/dv.py
class RZGate(AbstractGate):
    r"""
    Rotation gate around the Z-axis for qubits and qudits.

    For qubits (dim=2), this implements the standard RZ rotation:
    $$R_Z(\phi) = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\phi} \end{pmatrix}$$

    For qudits (dim>2), this generalizes to:
    $$R_Z(\phi) = \sum_{k=0}^{d-1} e^{ik\phi} |k\rangle\langle k|$$

    Attributes:
        phi (ArrayLike): The rotation angle in radians.

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        rz = RZGate(wires=(wire,), phi=jnp.pi/4)
        ```
    """

    phi: ArrayLike

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
        phi: float | int = 0.0,
    ):
        super().__init__(wires=wires)
        self.phi = jnp.array(phi)
        return

    def __call__(self):
        return jnp.diag(jnp.exp(1j * bases(self.wires[0].dim) * self.phi))

RXGate

Bases: AbstractGate

Rotation gate around the X-axis for qubits.

Implements the standard RX rotation: \(\(R_X(\phi) = \cos(\phi/2) I - i \sin(\phi/2) X = \begin{pmatrix} \cos(\phi/2) & -i\sin(\phi/2) \\ -i\sin(\phi/2) & \cos(\phi/2) \end{pmatrix}\)\)

Attributes:

Name Type Description
phi ArrayLike

The rotation angle in radians.

Note

This gate is only defined for qubits (dim=2).

Example
wire = Wire(dim=2, idx=0)
rx = RXGate(wires=(wire,), phi=jnp.pi/2)
Source code in src/squint/ops/dv.py
class RXGate(AbstractGate):
    r"""
    Rotation gate around the X-axis for qubits.

    Implements the standard RX rotation:
    $$R_X(\phi) = \cos(\phi/2) I - i \sin(\phi/2) X = \begin{pmatrix} \cos(\phi/2) & -i\sin(\phi/2) \\ -i\sin(\phi/2) & \cos(\phi/2) \end{pmatrix}$$

    Attributes:
        phi (ArrayLike): The rotation angle in radians.

    Note:
        This gate is only defined for qubits (dim=2).

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        rx = RXGate(wires=(wire,), phi=jnp.pi/2)
        ```
    """

    phi: ArrayLike

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
        phi: float | int = 0.0,
    ):
        assert wires[0].dim == 2, "RXGate only defined for dim=2."
        super().__init__(wires=wires)
        self.phi = jnp.array(phi)
        return

    def __call__(self):
        return (
            jnp.cos(self.phi / 2) * basis_operators(self.wires[0].dim)[3]  # identity
            - 1j * jnp.sin(self.phi / 2) * basis_operators(self.wires[0].dim)[2]  # X
        )

RYGate

Bases: AbstractGate

Rotation gate around the Y-axis for qubits.

Implements the standard RY rotation: \(\(R_Y(\phi) = \cos(\phi/2) I - i \sin(\phi/2) Y = \begin{pmatrix} \cos(\phi/2) & -\sin(\phi/2) \\ \sin(\phi/2) & \cos(\phi/2) \end{pmatrix}\)\)

Attributes:

Name Type Description
phi ArrayLike

The rotation angle in radians.

Note

This gate is only defined for qubits (dim=2).

Example
wire = Wire(dim=2, idx=0)
ry = RYGate(wires=(wire,), phi=jnp.pi/2)
Source code in src/squint/ops/dv.py
class RYGate(AbstractGate):
    r"""
    Rotation gate around the Y-axis for qubits.

    Implements the standard RY rotation:
    $$R_Y(\phi) = \cos(\phi/2) I - i \sin(\phi/2) Y = \begin{pmatrix} \cos(\phi/2) & -\sin(\phi/2) \\ \sin(\phi/2) & \cos(\phi/2) \end{pmatrix}$$

    Attributes:
        phi (ArrayLike): The rotation angle in radians.

    Note:
        This gate is only defined for qubits (dim=2).

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        ry = RYGate(wires=(wire,), phi=jnp.pi/2)
        ```
    """

    phi: ArrayLike

    @beartype
    def __init__(
        self,
        wires: tuple[Wire] = (0,),
        phi: float | int = 0.0,
    ):
        assert wires[0].dim == 2, "RYGate only defined for dim=2."

        super().__init__(wires=wires)
        self.phi = jnp.array(phi)
        return

    def __call__(self):
        return (
            jnp.cos(self.phi / 2) * basis_operators(self.wires[0].dim)[3]  # identity
            - 1j * jnp.sin(self.phi / 2) * basis_operators(self.wires[0].dim)[1]  # Y
        )

TwoLocalHermitianBasisGate

Bases: AbstractGate

Two-qubit/qudit gate generated by a tensor product of Gell-Mann basis operators.

Implements gates of the form: \(\(U(\theta) = \exp(-i \theta \cdot G_i \otimes G_j)\)\)

where \(G_i\) and \(G_j\) are Gell-Mann basis operators (generalized Pauli matrices) acting on the first and second wire respectively. For qubits (dim=2), the Gell-Mann operators reduce to the Pauli matrices.

This is the base class for specific two-qubit interaction gates like RXXGate and RZZGate.

Attributes:

Name Type Description
angles ArrayLike

The rotation angle(s) in radians.

_basis_op_indices tuple[int, int]

Indices of the Gell-Mann basis operators to use on each wire. For dim=2: 0=Z, 1=Y, 2=X, 3=I.

Example
wire0 = Wire(dim=2, idx=0)
wire1 = Wire(dim=2, idx=1)
# Create an XX interaction gate
gate = TwoLocalHermitianBasisGate(
    wires=(wire0, wire1),
    angles=jnp.pi/4,
    _basis_op_indices=(2, 2)  # X tensor X
)
Source code in src/squint/ops/dv.py
class TwoLocalHermitianBasisGate(AbstractGate):
    r"""
    Two-qubit/qudit gate generated by a tensor product of Gell-Mann basis operators.

    Implements gates of the form:
    $$U(\theta) = \exp(-i \theta \cdot G_i \otimes G_j)$$

    where $G_i$ and $G_j$ are Gell-Mann basis operators (generalized Pauli matrices)
    acting on the first and second wire respectively. For qubits (dim=2), the
    Gell-Mann operators reduce to the Pauli matrices.

    This is the base class for specific two-qubit interaction gates like RXXGate
    and RZZGate.

    Attributes:
        angles (ArrayLike): The rotation angle(s) in radians.
        _basis_op_indices (tuple[int, int]): Indices of the Gell-Mann basis operators
            to use on each wire. For dim=2: 0=Z, 1=Y, 2=X, 3=I.

    Example:
        ```python
        wire0 = Wire(dim=2, idx=0)
        wire1 = Wire(dim=2, idx=1)
        # Create an XX interaction gate
        gate = TwoLocalHermitianBasisGate(
            wires=(wire0, wire1),
            angles=jnp.pi/4,
            _basis_op_indices=(2, 2)  # X tensor X
        )
        ```
    """

    angles: ArrayLike
    _basis_op_indices: tuple[
        int, int
    ]  # index of basis (Gell-Mann) ops to apply on the first and second wires, respectively

    @beartype
    def __init__(
        self,
        wires: tuple[Wire, Wire],
        angles: Union[float, int, Sequence[int], Sequence[float], ArrayLike],
        _basis_op_indices: tuple[int, int] = (2, 2),
    ):
        super().__init__(wires=wires)

        self.angles = jnp.array(angles)
        self._basis_op_indices = _basis_op_indices
        return

    def _hermitian_op(self):
        return jnp.kron(
            basis_operators(self.wires[0].dim)[self._basis_op_indices[0]],
            basis_operators(self.wires[1].dim)[self._basis_op_indices[1]],
        )

    def _rearrange(self, tensor: ArrayLike):
        return tensor.reshape(
            self.wires[0].dim,
            self.wires[1].dim,
            self.wires[0].dim,
            self.wires[1].dim,
        )

    # def _dim_check(self, dim: int):
    # raise NotImplementedError()

    def __call__(self):
        # return self._rearrange(self._hermitian_op(dim), dim)
        # return self._hermitian_op(dim)
        # self._dim_check(dim)
        return self._rearrange(
            jsp.linalg.expm(-1j * self.angles * self._hermitian_op())
        )

Noise Channels

Quantum noise channels for modeling decoherence and errors. These require the "mixed" backend as they produce density matrices.

Key classes:

  • BitFlipChannel - Random X errors with probability p
  • PhaseFlipChannel - Random Z errors (dephasing) with probability p
  • DepolarizingChannel - Symmetric Pauli noise
  • ErasureChannel - Traces out (erases) specified wires

ErasureChannel

Bases: AbstractErasureChannel

Erasure channel that traces out specified wires.

This channel performs a partial trace over the specified wires, effectively "erasing" those subsystems from the quantum state. It models complete photon loss or the discarding of quantum information in those modes.

Mathematically, for a density matrix \(\rho\) on wires A and B, tracing out B gives: \(\(\text{Tr}_B[\rho] = \sum_i \langle i_B | \rho | i_B \rangle\)\)

Note

This channel requires the "mixed" backend in the circuit, as it produces a reduced density matrix.

Example
wire0 = Wire(dim=2, idx=0)
wire1 = Wire(dim=2, idx=1)
circuit = Circuit(backend="mixed")
# ... add states and operations ...
circuit.add(ErasureChannel(wires=(wire1,)))  # Trace out wire1
Source code in src/squint/ops/noise.py
class ErasureChannel(AbstractErasureChannel):
    r"""
    Erasure channel that traces out specified wires.

    This channel performs a partial trace over the specified wires, effectively
    "erasing" those subsystems from the quantum state. It models complete photon
    loss or the discarding of quantum information in those modes.

    Mathematically, for a density matrix $\rho$ on wires A and B, tracing out B gives:
    $$\text{Tr}_B[\rho] = \sum_i \langle i_B | \rho | i_B \rangle$$

    Note:
        This channel requires the "mixed" backend in the circuit, as it produces
        a reduced density matrix.

    Example:
        ```python
        wire0 = Wire(dim=2, idx=0)
        wire1 = Wire(dim=2, idx=1)
        circuit = Circuit(backend="mixed")
        # ... add states and operations ...
        circuit.add(ErasureChannel(wires=(wire1,)))  # Trace out wire1
        ```
    """

    @beartype
    def __init__(self, wires: Sequence[Wire]):
        super().__init__(wires=wires)
        return

    def __call__(self):
        subscripts = [
            get_symbol(2 * i) + get_symbol(2 * i + 1) for i in range(len(self.wires))
        ]
        return jnp.einsum(
            f"{','.join(subscripts)} -> {''.join(subscripts)}",
            *(jnp.identity(wire.dim) for wire in self.wires),
        )

BitFlipChannel

Bases: AbstractKrausChannel

Qubit bit flip channel.

Models random bit flip errors with probability \(p\). The channel flips the qubit state \(|0\rangle \leftrightarrow |1\rangle\) with probability \(p\) and leaves it unchanged with probability \(1-p\).

Kraus operators: \(\(K_0 = \sqrt{1-p} \cdot I, \quad K_1 = \sqrt{p} \cdot X\)\)

The channel acts on a density matrix as: \(\(\mathcal{E}(\rho) = (1-p)\rho + p X\rho X\)\)

Attributes:

Name Type Description
p ArrayLike

Bit flip probability, must be in [0, 1].

Note

This channel is only defined for qubits (dim=2) and requires the "mixed" backend.

Example
wire = Wire(dim=2, idx=0)
circuit = Circuit(backend="mixed")
# ... add state ...
circuit.add(BitFlipChannel(wires=(wire,), p=0.1))  # 10% bit flip probability
Source code in src/squint/ops/noise.py
class BitFlipChannel(AbstractKrausChannel):
    r"""
    Qubit bit flip channel.

    Models random bit flip errors with probability $p$. The channel flips
    the qubit state $|0\rangle \leftrightarrow |1\rangle$ with probability $p$
    and leaves it unchanged with probability $1-p$.

    Kraus operators:
    $$K_0 = \sqrt{1-p} \cdot I, \quad K_1 = \sqrt{p} \cdot X$$

    The channel acts on a density matrix as:
    $$\mathcal{E}(\rho) = (1-p)\rho + p X\rho X$$

    Attributes:
        p (ArrayLike): Bit flip probability, must be in [0, 1].

    Note:
        This channel is only defined for qubits (dim=2) and requires
        the "mixed" backend.

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        circuit = Circuit(backend="mixed")
        # ... add state ...
        circuit.add(BitFlipChannel(wires=(wire,), p=0.1))  # 10% bit flip probability
        ```
    """

    p: ArrayLike

    @beartype
    def __init__(self, wires: tuple[Wire], p: float):
        assert wires[0].dim == 2, "BitFlipChannel only valid for dim=2"

        super().__init__(wires=wires)
        self.p = jnp.array(p)
        # self.p = p  #paramax.non_trainable(p)
        return

    def __call__(self):
        return jnp.array(
            [
                jnp.sqrt(1 - self.p)
                * basis_operators(self.wires[0].dim)[3],  # identity
                jnp.sqrt(self.p) * basis_operators(self.wires[0].dim)[2],  # X
            ]
        )

PhaseFlipChannel

Bases: AbstractKrausChannel

Qubit phase flip (dephasing) channel.

Models random phase flip errors with probability \(p\). The channel applies a Z gate (phase flip) with probability \(p\) and leaves the state unchanged with probability \(1-p\).

Kraus operators: \(\(K_0 = \sqrt{1-p} \cdot I, \quad K_1 = \sqrt{p} \cdot Z\)\)

The channel acts on a density matrix as: \(\(\mathcal{E}(\rho) = (1-p)\rho + p Z\rho Z\)\)

This channel preserves populations but decoheres superpositions in the computational basis.

Attributes:

Name Type Description
p ArrayLike

Phase flip probability, must be in [0, 1].

Note

This channel is only defined for qubits (dim=2) and requires the "mixed" backend.

Example
wire = Wire(dim=2, idx=0)
circuit = Circuit(backend="mixed")
# ... add state ...
circuit.add(PhaseFlipChannel(wires=(wire,), p=0.1))  # 10% dephasing
Source code in src/squint/ops/noise.py
class PhaseFlipChannel(AbstractKrausChannel):
    r"""
    Qubit phase flip (dephasing) channel.

    Models random phase flip errors with probability $p$. The channel applies
    a Z gate (phase flip) with probability $p$ and leaves the state unchanged
    with probability $1-p$.

    Kraus operators:
    $$K_0 = \sqrt{1-p} \cdot I, \quad K_1 = \sqrt{p} \cdot Z$$

    The channel acts on a density matrix as:
    $$\mathcal{E}(\rho) = (1-p)\rho + p Z\rho Z$$

    This channel preserves populations but decoheres superpositions in the
    computational basis.

    Attributes:
        p (ArrayLike): Phase flip probability, must be in [0, 1].

    Note:
        This channel is only defined for qubits (dim=2) and requires
        the "mixed" backend.

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        circuit = Circuit(backend="mixed")
        # ... add state ...
        circuit.add(PhaseFlipChannel(wires=(wire,), p=0.1))  # 10% dephasing
        ```
    """

    p: ArrayLike

    @beartype
    def __init__(self, wires: tuple[Wire], p: float):
        assert wires[0].dim == 2, "PhaseFlipChannel only valid for dim=2"

        super().__init__(wires=wires)
        self.p = jnp.array(p)
        # self.p = p  #paramax.non_trainable(p)
        return

    def __call__(self):
        return jnp.array(
            [
                jnp.sqrt(1 - self.p)
                * basis_operators(self.wires[0].dim)[3],  # identity
                jnp.sqrt(self.p) * basis_operators(self.wires[0].dim)[0],  # Z
            ]
        )

DepolarizingChannel

Bases: AbstractKrausChannel

Qubit depolarizing channel.

Models symmetric noise that randomly applies one of the three Pauli errors (X, Y, or Z) each with probability \(p/4\), or leaves the state unchanged with probability \(1 - 3p/4\). At \(p=1\), the state is fully depolarized to the maximally mixed state.

Kraus operators: \(\(K_0 = \sqrt{1-3p/4} \cdot I, \quad K_1 = \sqrt{p/4} \cdot X\)\) \(\(K_2 = \sqrt{p/4} \cdot Y, \quad K_3 = \sqrt{p/4} \cdot Z\)\)

The channel acts on a density matrix as: \(\(\mathcal{E}(\rho) = (1-p)\rho + \frac{p}{3}(X\rho X + Y\rho Y + Z\rho Z)\)\)

which can also be written as: \(\(\mathcal{E}(\rho) = (1-p)\rho + p \cdot \frac{I}{2}\)\)

Attributes:

Name Type Description
p ArrayLike

Depolarizing probability, must be in [0, 1]. At p=0, no noise. At p=1, output is maximally mixed.

Note

This channel is only defined for qubits (dim=2) and requires the "mixed" backend.

Example
wire = Wire(dim=2, idx=0)
circuit = Circuit(backend="mixed")
# ... add state ...
circuit.add(DepolarizingChannel(wires=(wire,), p=0.1))  # 10% depolarization
Source code in src/squint/ops/noise.py
class DepolarizingChannel(AbstractKrausChannel):
    r"""
    Qubit depolarizing channel.

    Models symmetric noise that randomly applies one of the three Pauli errors
    (X, Y, or Z) each with probability $p/4$, or leaves the state unchanged
    with probability $1 - 3p/4$. At $p=1$, the state is fully depolarized to
    the maximally mixed state.

    Kraus operators:
    $$K_0 = \sqrt{1-3p/4} \cdot I, \quad K_1 = \sqrt{p/4} \cdot X$$
    $$K_2 = \sqrt{p/4} \cdot Y, \quad K_3 = \sqrt{p/4} \cdot Z$$

    The channel acts on a density matrix as:
    $$\mathcal{E}(\rho) = (1-p)\rho + \frac{p}{3}(X\rho X + Y\rho Y + Z\rho Z)$$

    which can also be written as:
    $$\mathcal{E}(\rho) = (1-p)\rho + p \cdot \frac{I}{2}$$

    Attributes:
        p (ArrayLike): Depolarizing probability, must be in [0, 1].
            At p=0, no noise. At p=1, output is maximally mixed.

    Note:
        This channel is only defined for qubits (dim=2) and requires
        the "mixed" backend.

    Example:
        ```python
        wire = Wire(dim=2, idx=0)
        circuit = Circuit(backend="mixed")
        # ... add state ...
        circuit.add(DepolarizingChannel(wires=(wire,), p=0.1))  # 10% depolarization
        ```
    """

    p: ArrayLike

    @beartype
    def __init__(self, wires: tuple[Wire], p: float):
        assert wires[0].dim == 2, "DepolarizingChannel only valid for dim=2"

        super().__init__(wires=wires)
        self.p = jnp.array(p)
        return

    def __call__(self):
        return jnp.array(
            [
                jnp.sqrt(1 - 3 * self.p / 4)
                * basis_operators(self.wires[0].dim)[3],  # identity
                jnp.sqrt(self.p / 4) * basis_operators(self.wires[0].dim)[0],  # Z
                jnp.sqrt(self.p / 4) * basis_operators(self.wires[0].dim)[1],  # Y
                jnp.sqrt(self.p / 4) * basis_operators(self.wires[0].dim)[2],  # X
            ]
        )

Blocks

Blocks allow grouping multiple operations for organizational purposes.

brickwork(wires: Sequence[Wire], depth: int, LocalGates: Union[Type[AbstractGate], Sequence[Type[AbstractGate]]], CouplingGate: Type[AbstractGate], periodic: bool = False) -> Block

Create a brickwork block with the specified local and coupling gates.

Parameters:

Name Type Description Default
wires Sequence[Wire]

The wires to apply the gates to.

required
depth int

The depth of the brickwork block.

required
LocalGates Union[Type[AbstractGate], Sequence[Type[AbstractGate]]]

The local gates to apply to each wire.

required
CouplingGate Type[AbstractGate]

The coupling gate to apply to pairs of wires.

required
periodic bool

Whether to use periodic boundary conditions.

False

Returns: Block: A block containing the specified brickwork structure.

Source code in src/squint/blocks.py
@beartype
def brickwork(
    wires: Sequence[Wire],
    depth: int,
    LocalGates: Union[Type[AbstractGate], Sequence[Type[AbstractGate]]],
    CouplingGate: Type[AbstractGate],
    periodic: bool = False,
) -> Block:
    """
    Create a brickwork block with the specified local and coupling gates.

    Args:
        wires (Sequence[Wire]): The wires to apply the gates to.
        depth (int): The depth of the brickwork block.
        LocalGates (Union[Type[AbstractGate], Sequence[Type[AbstractGate]]]): The local gates to apply to each wire.
        CouplingGate (Type[AbstractGate]): The coupling gate to apply to pairs of wires.
        periodic (bool): Whether to use periodic boundary conditions.
    Returns:
        Block: A block containing the specified brickwork structure.
    """
    block = Block()
    pairs1, pairs2 = _chunk_pairs(tuple(wires), periodic=periodic)

    if not is_bearable(LocalGates, Sequence[Type[AbstractGate]]):
        LocalGates = (LocalGates,)

    for _layer in range(depth):
        for wire in wires:
            for Gate in LocalGates:
                block.add(Gate(wires=(wire,)))
        for pairs in (pairs1, pairs2):
            for pair in pairs:
                block.add(CouplingGate(wires=pair))

    return block

brickwork_type(wires: Sequence[Wire], depth: int, ansatz: Literal[hea, rxx, rzz], periodic: bool = False)

Create a brickwork block with the specified ansatz type. Ansatz can be one of 'hea', 'rxx', or 'rzz'. - 'hea' uses RX and RY gates for one-qubit gates and CZ for two-qubit gates. - 'rxx' uses RX and RY gates for one-qubit gates and RXX for two-qubit gates. - 'rzz' uses RZ gates for one-qubit gates and RZZ for two-qubit gates.

Parameters:

Name Type Description Default
wires Sequence[Wire]

The wires to apply the gates to.

required
depth int

The depth of the brickwork block.

required
ansatz Literal[hea, rxx, rzz]

The type of ansatz to use.

required
periodic bool

Whether to use periodic boundary conditions.

False

Returns:

Name Type Description
Block

A block containing the specified brickwork ansatz.

Source code in src/squint/blocks.py
@beartype
def brickwork_type(
    wires: Sequence[Wire],
    depth: int,
    ansatz: Literal["hea", "rxx", "rzz"],
    periodic: bool = False,
):
    """
    Create a brickwork block with the specified ansatz type.
    Ansatz can be one of 'hea', 'rxx', or 'rzz'.
    - 'hea' uses RX and RY gates for one-qubit gates and CZ for two-qubit gates.
    - 'rxx' uses RX and RY gates for one-qubit gates and RXX for two-qubit gates.
    - 'rzz' uses RZ gates for one-qubit gates and RZZ for two-qubit gates.

    Args:
        wires (Sequence[Wire]): The wires to apply the gates to.
        depth (int): The depth of the brickwork block.
        ansatz (Literal['hea', 'rxx', 'rzz']): The type of ansatz to use.
        periodic (bool): Whether to use periodic boundary conditions.

    Returns:
        Block: A block containing the specified brickwork ansatz.
    """
    match ansatz:
        case "hea":
            return brickwork(
                wires=wires,
                depth=depth,
                one_qubit_gates=[dv.RXGate, dv.RYGate],
                two_qubit_gates=dv.CZGate,
                periodic=periodic,
            )
        case "rxx":
            return brickwork(
                wires=wires,
                depth=depth,
                LocalGates=(dv.RXGate, dv.RYGate),
                CouplingGate=dv.RXXGate,
                periodic=periodic,
            )
        case "rzz":
            return brickwork(
                wires=wires,
                depth=depth,
                LocalGates=(dv.RXGate, dv.RYGate),
                CouplingGate=dv.RZZGate,
                periodic=periodic,
            )