Skip to content

Noise Models

quanta.simulator.noise

quanta.simulator.noise — Quantum noise models.

Example

from quanta.simulator.noise import NoiseModel, Depolarizing noise = NoiseModel() result = run(bell, noise=noise)

AmplitudeDamping dataclass

Bases: NoiseChannel

Amplitude damping: energy loss (T1 decay).

Attributes:

Source code in quanta/simulator/noise.py
@dataclass
class AmplitudeDamping(NoiseChannel):
    """Amplitude damping: energy loss (T1 decay).


    Attributes:
    """

    gamma: float = 0.01

    @property
    def name(self) -> str:
        return f"AmplitudeDamping(γ={self.gamma})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        """Applies amplitude damping using Kraus operators.

        K0 = [[1, 0], [0, √(1-γ)]]  (no-decay branch)
        K1 = [[0, √γ], [0, 0]]      (decay branch)

        Statevector application: randomly apply K0 or K1 based
        on the probability of decay, then renormalize.
        """
        n = num_qubits
        dim = len(state)
        sqrt_1mg = np.sqrt(1 - self.gamma)
        sqrt_g = np.sqrt(self.gamma)

        # Compute probability of decay (K1 branch)
        p_decay = 0.0
        for i in range(dim):
            if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
                p_decay += self.gamma * abs(state[i]) ** 2

        new_state = state.copy()

        if rng.random() < p_decay and p_decay > 1e-15:
            # Apply K1: |1⟩ → |0⟩ transition
            for i in range(dim):
                if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
                    j = i ^ (1 << (n - 1 - qubit))  # paired |0⟩ index
                    new_state[j] = sqrt_g * state[i]
                    new_state[i] = 0
                # |0⟩ states already zeroed by K1
                else:
                    new_state[i] = 0
        else:
            # Apply K0: dampen |1⟩ amplitude
            for i in range(dim):
                if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
                    new_state[i] = sqrt_1mg * state[i]

        # Normalize
        norm = np.linalg.norm(new_state)
        if norm > 1e-15:
            new_state /= norm

        return new_state
apply
apply(
    state: ndarray,
    qubit: int,
    num_qubits: int,
    rng: Generator,
) -> np.ndarray

Applies amplitude damping using Kraus operators.

K0 = [[1, 0], [0, √(1-γ)]] (no-decay branch) K1 = [[0, √γ], [0, 0]] (decay branch)

Statevector application: randomly apply K0 or K1 based on the probability of decay, then renormalize.

Source code in quanta/simulator/noise.py
def apply(
    self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
) -> np.ndarray:
    """Applies amplitude damping using Kraus operators.

    K0 = [[1, 0], [0, √(1-γ)]]  (no-decay branch)
    K1 = [[0, √γ], [0, 0]]      (decay branch)

    Statevector application: randomly apply K0 or K1 based
    on the probability of decay, then renormalize.
    """
    n = num_qubits
    dim = len(state)
    sqrt_1mg = np.sqrt(1 - self.gamma)
    sqrt_g = np.sqrt(self.gamma)

    # Compute probability of decay (K1 branch)
    p_decay = 0.0
    for i in range(dim):
        if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
            p_decay += self.gamma * abs(state[i]) ** 2

    new_state = state.copy()

    if rng.random() < p_decay and p_decay > 1e-15:
        # Apply K1: |1⟩ → |0⟩ transition
        for i in range(dim):
            if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
                j = i ^ (1 << (n - 1 - qubit))  # paired |0⟩ index
                new_state[j] = sqrt_g * state[i]
                new_state[i] = 0
            # |0⟩ states already zeroed by K1
            else:
                new_state[i] = 0
    else:
        # Apply K0: dampen |1⟩ amplitude
        for i in range(dim):
            if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
                new_state[i] = sqrt_1mg * state[i]

    # Normalize
    norm = np.linalg.norm(new_state)
    if norm > 1e-15:
        new_state /= norm

    return new_state

BitFlip dataclass

Bases: NoiseChannel

Bit-flip channel: random |0⟩↔|1⟩ flip (X error).

Attributes:

Source code in quanta/simulator/noise.py
@dataclass
class BitFlip(NoiseChannel):
    """Bit-flip channel: random |0⟩↔|1⟩ flip (X error).

    Attributes:
    """

    probability: float = 0.01

    @property
    def name(self) -> str:
        return f"BitFlip(p={self.probability})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        if rng.random() > self.probability:
            return state
        return _apply_single_qubit_error(state, qubit, num_qubits, pauli=0)

Crosstalk dataclass

Bases: NoiseChannel

Crosstalk noise: correlated errors between neighboring qubits.

When a gate operates on a qubit, crosstalk causes unintended rotations on physically adjacent qubits. This is a major error source in superconducting processors.

In real hardware
  • ZZ crosstalk: ~0.1-1% per gate
  • Frequency collision: ~0.01-0.1%

Attributes:

Name Type Description
probability float

Probability of crosstalk per gate.

neighbor_offset int

Which neighbor is affected (+1 = next qubit).

Source code in quanta/simulator/noise.py
@dataclass
class Crosstalk(NoiseChannel):
    """Crosstalk noise: correlated errors between neighboring qubits.

    When a gate operates on a qubit, crosstalk causes unintended
    rotations on physically adjacent qubits. This is a major error
    source in superconducting processors.

    In real hardware:
      - ZZ crosstalk: ~0.1-1% per gate
      - Frequency collision: ~0.01-0.1%

    Attributes:
        probability: Probability of crosstalk per gate.
        neighbor_offset: Which neighbor is affected (+1 = next qubit).
    """

    probability: float = 0.005
    neighbor_offset: int = 1

    @property
    def name(self) -> str:
        return f"Crosstalk(p={self.probability}, offset={self.neighbor_offset})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        if rng.random() > self.probability:
            return state  # No crosstalk

        neighbor = qubit + self.neighbor_offset
        if neighbor < 0 or neighbor >= num_qubits:
            return state  # No neighbor to affect

        # ZZ-type crosstalk: small Z rotation on neighbor
        # conditioned on the state of the target qubit
        n = num_qubits
        new_state = state.copy()
        angle = rng.uniform(0.01, 0.1) * np.pi  # Small unwanted rotation

        for i in range(len(state)):
            target_bit = (i >> (n - 1 - qubit)) & 1
            neighbor_bit = (i >> (n - 1 - neighbor)) & 1
            # ZZ interaction: phase depends on both qubits
            if target_bit and neighbor_bit:
                new_state[i] *= np.exp(1j * angle)
            elif target_bit or neighbor_bit:
                new_state[i] *= np.exp(-1j * angle / 2)

        return new_state

Depolarizing dataclass

Bases: NoiseChannel

Depolarizing channel: random Pauli (X, Y, Z) error.

Attributes:

Source code in quanta/simulator/noise.py
@dataclass
class Depolarizing(NoiseChannel):
    """Depolarizing channel: random Pauli (X, Y, Z) error.


    Attributes:
    """

    probability: float = 0.01

    @property
    def name(self) -> str:
        return f"Depolarizing(p={self.probability})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        if rng.random() > self.probability:
            return state  # No error

        pauli = rng.integers(0, 3)
        return _apply_single_qubit_error(state, qubit, num_qubits, pauli)

NoiseChannel

Bases: ABC

Abstract interface for a single noise channel.

Source code in quanta/simulator/noise.py
class NoiseChannel(ABC):
    """Abstract interface for a single noise channel.

    """

    @abstractmethod
    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        """Applies noise to the statevector.

        Args:
            qubit: Target qubit index.
            num_qubits: Total qubit count.

        Returns:
        """
        ...

    @property
    @abstractmethod
    def name(self) -> str:
        """Channel name."""
        ...
name abstractmethod property
name: str

Channel name.

apply abstractmethod
apply(
    state: ndarray,
    qubit: int,
    num_qubits: int,
    rng: Generator,
) -> np.ndarray

Applies noise to the statevector.

Parameters:

Name Type Description Default
qubit int

Target qubit index.

required
num_qubits int

Total qubit count.

required

Returns:

Source code in quanta/simulator/noise.py
@abstractmethod
def apply(
    self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
) -> np.ndarray:
    """Applies noise to the statevector.

    Args:
        qubit: Target qubit index.
        num_qubits: Total qubit count.

    Returns:
    """
    ...

NoiseModel

Noise model: manages multiple channels.

Supports builder pattern and preset profiles:

# Builder pattern
>>> model = (NoiseModel.builder()
...     .depolarizing(0.01)
...     .with_readout(0.01, 0.02)
...     .build())

# Preset profiles
>>> ibm = NoiseModel.ibm_heron()
>>> ionq = NoiseModel.ionq_aria()
Example

model = NoiseModel() model.add(Depolarizing(0.01)) model.add(AmplitudeDamping(0.005))

Source code in quanta/simulator/noise.py
class NoiseModel:
    """Noise model: manages multiple channels.

    Supports builder pattern and preset profiles:

        # Builder pattern
        >>> model = (NoiseModel.builder()
        ...     .depolarizing(0.01)
        ...     .with_readout(0.01, 0.02)
        ...     .build())

        # Preset profiles
        >>> ibm = NoiseModel.ibm_heron()
        >>> ionq = NoiseModel.ionq_aria()

    Example:
        >>> model = NoiseModel()
        >>> model.add(Depolarizing(0.01))
        >>> model.add(AmplitudeDamping(0.005))
    """

    def __init__(self) -> None:
        self._channels: list[NoiseChannel] = []

    def add(self, channel: NoiseChannel) -> NoiseModel:
        """Adds a channel. Chainable API."""
        self._channels.append(channel)
        return self

    @property
    def channels(self) -> list[NoiseChannel]:
        """Returns the list of noise channels."""
        return self._channels

    def apply_noise(
        self,
        state: np.ndarray,
        qubits: tuple[int, ...],
        num_qubits: int,
        rng: np.random.Generator,
    ) -> np.ndarray:
        """Applies all channels to the specified qubits."""
        for channel in self._channels:
            for qubit in qubits:
                state = channel.apply(state, qubit, num_qubits, rng)
        return state

    def describe(self) -> str:
        """Returns a formatted table of noise channels.

        Example:
            >>> NoiseModel.ibm_heron().describe()
            '=== Noise Model (3 channels) ===\\n...'
        """
        lines = [f"=== Noise Model ({len(self._channels)} channels) ==="]
        lines.append(f"  {'#':<4} {'Channel':<35} {'Type':<15}")
        lines.append("  " + "─" * 54)
        for i, ch in enumerate(self._channels):
            ch_type = type(ch).__name__
            lines.append(f"  {i + 1:<4} {ch.name:<35} {ch_type:<15}")
        return "\n".join(lines)

    # ── Builder Pattern ──

    @classmethod
    def builder(cls) -> _NoiseModelBuilder:
        """Start building a noise model with fluent API.

        Example:
            >>> model = (NoiseModel.builder()
            ...     .depolarizing(0.01)
            ...     .with_amplitude_damping(0.005)
            ...     .with_readout(0.01, 0.02)
            ...     .build())
        """
        return _NoiseModelBuilder()

    # ── Preset Profiles ──

    @classmethod
    def ibm_heron(cls) -> NoiseModel:
        """IBM Heron r3 noise profile.

        Based on ibm_fez calibration data (2026):
          - 1Q gate error: ~0.02%
          - 2Q gate error: ~0.5%
          - T1: ~300 μs, T2: ~200 μs
          - Readout error: ~0.8%
        """
        return (
            cls.builder()
            .depolarizing(0.005)
            .with_amplitude_damping(0.003)
            .with_t2(0.005)
            .with_readout(0.008, 0.012)
            .build()
        )

    @classmethod
    def ionq_aria(cls) -> NoiseModel:
        """IonQ Aria noise profile.

        Based on IonQ Aria-1 specs (2026):
          - 1Q gate fidelity: 99.97%
          - 2Q gate fidelity: 99.4%
          - All-to-all connectivity (no SWAP overhead)
        """
        return (
            cls.builder()
            .depolarizing(0.003)
            .with_amplitude_damping(0.001)
            .build()
        )

    @classmethod
    def google_willow(cls) -> NoiseModel:
        """Google Willow noise profile.

        Based on Google Willow specs (2026):
          - 1Q gate error: ~0.03%
          - 2Q gate error: ~0.3%
          - T1: ~70 μs
          - Below-threshold QEC demonstrated
        """
        return (
            cls.builder()
            .depolarizing(0.003)
            .with_amplitude_damping(0.005)
            .with_t2(0.008)
            .with_crosstalk(0.002)
            .build()
        )

    def __repr__(self) -> str:
        names = [ch.name for ch in self._channels]
        return f"NoiseModel(channels={names})"
channels property
channels: list[NoiseChannel]

Returns the list of noise channels.

add
add(channel: NoiseChannel) -> NoiseModel

Adds a channel. Chainable API.

Source code in quanta/simulator/noise.py
def add(self, channel: NoiseChannel) -> NoiseModel:
    """Adds a channel. Chainable API."""
    self._channels.append(channel)
    return self
apply_noise
apply_noise(
    state: ndarray,
    qubits: tuple[int, ...],
    num_qubits: int,
    rng: Generator,
) -> np.ndarray

Applies all channels to the specified qubits.

Source code in quanta/simulator/noise.py
def apply_noise(
    self,
    state: np.ndarray,
    qubits: tuple[int, ...],
    num_qubits: int,
    rng: np.random.Generator,
) -> np.ndarray:
    """Applies all channels to the specified qubits."""
    for channel in self._channels:
        for qubit in qubits:
            state = channel.apply(state, qubit, num_qubits, rng)
    return state
builder classmethod
builder() -> _NoiseModelBuilder

Start building a noise model with fluent API.

Example

model = (NoiseModel.builder() ... .depolarizing(0.01) ... .with_amplitude_damping(0.005) ... .with_readout(0.01, 0.02) ... .build())

Source code in quanta/simulator/noise.py
@classmethod
def builder(cls) -> _NoiseModelBuilder:
    """Start building a noise model with fluent API.

    Example:
        >>> model = (NoiseModel.builder()
        ...     .depolarizing(0.01)
        ...     .with_amplitude_damping(0.005)
        ...     .with_readout(0.01, 0.02)
        ...     .build())
    """
    return _NoiseModelBuilder()
describe
describe() -> str

Returns a formatted table of noise channels.

Example

NoiseModel.ibm_heron().describe() '=== Noise Model (3 channels) ===\n...'

Source code in quanta/simulator/noise.py
def describe(self) -> str:
    """Returns a formatted table of noise channels.

    Example:
        >>> NoiseModel.ibm_heron().describe()
        '=== Noise Model (3 channels) ===\\n...'
    """
    lines = [f"=== Noise Model ({len(self._channels)} channels) ==="]
    lines.append(f"  {'#':<4} {'Channel':<35} {'Type':<15}")
    lines.append("  " + "─" * 54)
    for i, ch in enumerate(self._channels):
        ch_type = type(ch).__name__
        lines.append(f"  {i + 1:<4} {ch.name:<35} {ch_type:<15}")
    return "\n".join(lines)
google_willow classmethod
google_willow() -> NoiseModel

Google Willow noise profile.

Based on Google Willow specs (2026): - 1Q gate error: ~0.03% - 2Q gate error: ~0.3% - T1: ~70 μs - Below-threshold QEC demonstrated

Source code in quanta/simulator/noise.py
@classmethod
def google_willow(cls) -> NoiseModel:
    """Google Willow noise profile.

    Based on Google Willow specs (2026):
      - 1Q gate error: ~0.03%
      - 2Q gate error: ~0.3%
      - T1: ~70 μs
      - Below-threshold QEC demonstrated
    """
    return (
        cls.builder()
        .depolarizing(0.003)
        .with_amplitude_damping(0.005)
        .with_t2(0.008)
        .with_crosstalk(0.002)
        .build()
    )
ibm_heron classmethod
ibm_heron() -> NoiseModel

IBM Heron r3 noise profile.

Based on ibm_fez calibration data (2026): - 1Q gate error: ~0.02% - 2Q gate error: ~0.5% - T1: ~300 μs, T2: ~200 μs - Readout error: ~0.8%

Source code in quanta/simulator/noise.py
@classmethod
def ibm_heron(cls) -> NoiseModel:
    """IBM Heron r3 noise profile.

    Based on ibm_fez calibration data (2026):
      - 1Q gate error: ~0.02%
      - 2Q gate error: ~0.5%
      - T1: ~300 μs, T2: ~200 μs
      - Readout error: ~0.8%
    """
    return (
        cls.builder()
        .depolarizing(0.005)
        .with_amplitude_damping(0.003)
        .with_t2(0.005)
        .with_readout(0.008, 0.012)
        .build()
    )
ionq_aria classmethod
ionq_aria() -> NoiseModel

IonQ Aria noise profile.

Based on IonQ Aria-1 specs (2026): - 1Q gate fidelity: 99.97% - 2Q gate fidelity: 99.4% - All-to-all connectivity (no SWAP overhead)

Source code in quanta/simulator/noise.py
@classmethod
def ionq_aria(cls) -> NoiseModel:
    """IonQ Aria noise profile.

    Based on IonQ Aria-1 specs (2026):
      - 1Q gate fidelity: 99.97%
      - 2Q gate fidelity: 99.4%
      - All-to-all connectivity (no SWAP overhead)
    """
    return (
        cls.builder()
        .depolarizing(0.003)
        .with_amplitude_damping(0.001)
        .build()
    )

PhaseFlip dataclass

Bases: NoiseChannel

Phase-flip channel: random phase error (Z error).

Attributes:

Source code in quanta/simulator/noise.py
@dataclass
class PhaseFlip(NoiseChannel):
    """Phase-flip channel: random phase error (Z error).

    Attributes:
    """

    probability: float = 0.01

    @property
    def name(self) -> str:
        return f"PhaseFlip(p={self.probability})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        if rng.random() > self.probability:
            return state
        return _apply_single_qubit_error(state, qubit, num_qubits, pauli=2)

ReadoutError dataclass

Bases: NoiseChannel

Readout error: measurement bit-flip during readout.

Models the classical error that occurs when reading out a qubit. The qubit state is correct, but the measurement result may flip.

In real hardware
  • IBM Eagle: ~0.5-2% readout error
  • Google Sycamore: ~0.5-1%

Attributes:

Name Type Description
p0_to_1 float

Probability of reading |0⟩ as |1⟩.

p1_to_0 float

Probability of reading |1⟩ as |0⟩.

Source code in quanta/simulator/noise.py
@dataclass
class ReadoutError(NoiseChannel):
    """Readout error: measurement bit-flip during readout.

    Models the classical error that occurs when reading out a qubit.
    The qubit state is correct, but the measurement result may flip.

    In real hardware:
      - IBM Eagle: ~0.5-2% readout error
      - Google Sycamore: ~0.5-1%

    Attributes:
        p0_to_1: Probability of reading |0⟩ as |1⟩.
        p1_to_0: Probability of reading |1⟩ as |0⟩.
    """

    p0_to_1: float = 0.01
    p1_to_0: float = 0.02

    @property
    def name(self) -> str:
        return f"ReadoutError(p01={self.p0_to_1}, p10={self.p1_to_0})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        # ReadoutError doesn't modify the quantum state.
        # It modifies measurement results (applied post-measurement).
        return state

    def apply_to_counts(
        self,
        counts: dict[str, int],
        rng: np.random.Generator,
    ) -> dict[str, int]:
        """Applies readout error to measurement counts.

        Flips individual bits in measurement results based on
        per-bit error probabilities.

        Args:
            counts: Measurement result counts.
            rng: Random number generator.

        Returns:
            Modified counts with readout errors applied.
        """
        noisy_counts: dict[str, int] = {}

        for bitstring, count in counts.items():
            for _ in range(count):
                noisy_bits = list(bitstring)
                for idx, bit in enumerate(noisy_bits):
                    if bit == "0" and rng.random() < self.p0_to_1:
                        noisy_bits[idx] = "1"
                    elif bit == "1" and rng.random() < self.p1_to_0:
                        noisy_bits[idx] = "0"
                result = "".join(noisy_bits)
                noisy_counts[result] = noisy_counts.get(result, 0) + 1

        return noisy_counts
apply_to_counts
apply_to_counts(
    counts: dict[str, int], rng: Generator
) -> dict[str, int]

Applies readout error to measurement counts.

Flips individual bits in measurement results based on per-bit error probabilities.

Parameters:

Name Type Description Default
counts dict[str, int]

Measurement result counts.

required
rng Generator

Random number generator.

required

Returns:

Type Description
dict[str, int]

Modified counts with readout errors applied.

Source code in quanta/simulator/noise.py
def apply_to_counts(
    self,
    counts: dict[str, int],
    rng: np.random.Generator,
) -> dict[str, int]:
    """Applies readout error to measurement counts.

    Flips individual bits in measurement results based on
    per-bit error probabilities.

    Args:
        counts: Measurement result counts.
        rng: Random number generator.

    Returns:
        Modified counts with readout errors applied.
    """
    noisy_counts: dict[str, int] = {}

    for bitstring, count in counts.items():
        for _ in range(count):
            noisy_bits = list(bitstring)
            for idx, bit in enumerate(noisy_bits):
                if bit == "0" and rng.random() < self.p0_to_1:
                    noisy_bits[idx] = "1"
                elif bit == "1" and rng.random() < self.p1_to_0:
                    noisy_bits[idx] = "0"
            result = "".join(noisy_bits)
            noisy_counts[result] = noisy_counts.get(result, 0) + 1

    return noisy_counts

T2Relaxation dataclass

Bases: NoiseChannel

T2 relaxation (pure dephasing) channel.

Models decoherence where the qubit loses phase information without energy loss. T2 is always <= 2*T1.

In real hardware
  • IBM Eagle: T2 ~ 100-200 μs
  • Google Sycamore: T2 ~ 10-20 μs

Attributes:

Name Type Description
gamma float

Dephasing rate (probability of phase randomization).

Source code in quanta/simulator/noise.py
@dataclass
class T2Relaxation(NoiseChannel):
    """T2 relaxation (pure dephasing) channel.

    Models decoherence where the qubit loses phase information
    without energy loss. T2 is always <= 2*T1.

    In real hardware:
      - IBM Eagle: T2 ~ 100-200 μs
      - Google Sycamore: T2 ~ 10-20 μs

    Attributes:
        gamma: Dephasing rate (probability of phase randomization).
    """

    gamma: float = 0.01

    @property
    def name(self) -> str:
        return f"T2Relaxation(γ={self.gamma})"

    def apply(
        self, state: np.ndarray, qubit: int, num_qubits: int, rng: np.random.Generator
    ) -> np.ndarray:
        if rng.random() > self.gamma:
            return state  # No dephasing

        # Pure dephasing: apply random phase to |1⟩ component
        n = num_qubits
        new_state = state.copy()
        phase = np.exp(1j * rng.uniform(0, 2 * np.pi))

        for i in range(len(state)):
            if (i >> (n - 1 - qubit)) & 1:  # qubit in |1⟩
                new_state[i] *= phase

        return new_state