Source code for graphix.noise_models.noise_model

"""Abstract interface for noise models.

This module defines :class:`NoiseModel`, the base class used by
:class:`graphix.simulator.PatternSimulator` when running noisy
simulations. Child classes implement concrete noise processes by
overriding the abstract methods defined here.
"""

from __future__ import annotations

import dataclasses
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, ClassVar, Literal

# override introduced in Python 3.12
from typing_extensions import override

from graphix.command import BaseM, Command, CommandKind, Node, _KindChecker

if TYPE_CHECKING:
    from collections.abc import Iterable

    from numpy.random import Generator

    from graphix.channels import KrausChannel
    from graphix.measurements import Outcome


[docs] class Noise(ABC): """Abstract base class for noise.""" @property @abstractmethod def nqubits(self) -> int: """Return the number of qubits targetted by the noise."""
[docs] @abstractmethod def to_kraus_channel(self) -> KrausChannel: """Return the Kraus channel describing the noise."""
[docs] @dataclass class ApplyNoise(_KindChecker): """Apply noise command. Parameters ---------- noise : Noise noise to be applied nodes : list[Node] list of node indices on which to apply noise domain: set[Node] | None = None Optional domain for conditional noise. If ``None``, the noise is applied unconditionally. Otherwise, the noise is applied if there is an odd number of nodes among ``domain`` that have been measured with outcome 1 (as for ``X`` and ``Z`` commands). Note that the noise is never applied if ``domain`` is the empty set. """ kind: ClassVar[Literal[CommandKind.ApplyNoise]] = dataclasses.field(default=CommandKind.ApplyNoise, init=False) noise: Noise nodes: list[Node] domain: set[Node] | None = None
CommandOrNoise = Command | ApplyNoise
[docs] class NoiseModel(ABC): """Abstract base class for all noise models."""
[docs] @abstractmethod def input_nodes( self, nodes: Iterable[int], rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Return the noise to apply to input nodes."""
[docs] @abstractmethod def command( self, cmd: CommandOrNoise, rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Return the noise to apply to the command ``cmd``."""
[docs] @abstractmethod def confuse_result( self, cmd: BaseM, result: Outcome, rng: Generator | None = None, *, stacklevel: int = 1 ) -> Outcome: """Return a possibly flipped measurement outcome. Parameters ---------- result : Outcome Ideal measurement result. cmd : BaseM The measurement command that produced the given outcome. Returns ------- Outcome Possibly corrupted result. """
[docs] def transpile( self, sequence: Iterable[CommandOrNoise], rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Apply the noise to a sequence of commands and return the resulting sequence.""" return [n_cmd for cmd in sequence for n_cmd in self.command(cmd, rng=rng, stacklevel=stacklevel + 1)]
[docs] class NoiselessNoiseModel(NoiseModel): """Noise model that performs no operation."""
[docs] @override def input_nodes( self, nodes: Iterable[int], rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Return the noise to apply to input nodes.""" return []
[docs] @override def command( self, cmd: CommandOrNoise, rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Return the noise to apply to the command ``cmd``.""" return [cmd]
[docs] @override def confuse_result( self, cmd: BaseM, result: Outcome, rng: Generator | None = None, *, stacklevel: int = 1 ) -> Outcome: """Assign wrong measurement result.""" return result
[docs] @dataclass(frozen=True) class ComposeNoiseModel(NoiseModel): """Compose noise models.""" models: list[NoiseModel]
[docs] @override def input_nodes( self, nodes: Iterable[int], rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Return the noise to apply to input nodes.""" return [n_cmd for m in self.models for n_cmd in m.input_nodes(nodes, stacklevel=stacklevel + 1)]
[docs] @override def command( self, cmd: CommandOrNoise, rng: Generator | None = None, *, stacklevel: int = 1 ) -> list[CommandOrNoise]: """Return the noise to apply to the command ``cmd``.""" sequence = [cmd] for model in self.models: sequence = model.transpile(sequence, rng=rng, stacklevel=stacklevel + 1) return sequence
[docs] @override def confuse_result( self, cmd: BaseM, result: Outcome, rng: Generator | None = None, *, stacklevel: int = 1 ) -> Outcome: """Assign wrong measurement result.""" for m in self.models: result = m.confuse_result(cmd, result, rng=rng, stacklevel=stacklevel + 1) return result