From 0f27921bf5a9bb0caa89eed512862a21d5ec9bec Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 3 Dec 2025 12:58:14 +0100 Subject: [PATCH 01/36] trying to remove BackendState still causing problems --- .vscode/settings.json | 7 ++ graphix/pattern.py | 54 ++++++++++++++-- graphix/sim/__init__.py | 8 +-- graphix/sim/base_backend.py | 43 +++--------- graphix/sim/tensornet.py | 6 +- graphix/simulator.py | 126 ++++++++++++++++++++++++------------ 6 files changed, 152 insertions(+), 92 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..9b388533a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/graphix/pattern.py b/graphix/pattern.py index 907d39f31..87f3dc528 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -13,7 +13,7 @@ from dataclasses import dataclass from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, SupportsFloat, TypeVar +from typing import TYPE_CHECKING, SupportsFloat, overload import networkx as nx from typing_extensions import assert_never @@ -38,11 +38,11 @@ from numpy.random import Generator - from graphix.parameter import ExpressionOrFloat, ExpressionOrSupportsFloat, Parameter - from graphix.sim import Backend, BackendState, Data - - -_StateT_co = TypeVar("_StateT_co", bound="BackendState", covariant=True) + from graphix.parameter import ExpressionOrFloat, ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter + from graphix.sim import Backend, Data, DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend + from graphix.sim.base_backend import _StateT_co + from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend + from graphix.states import State class Pattern: @@ -1336,13 +1336,53 @@ def space_list(self) -> list[int]: n_list.append(nodes) return n_list + @overload + def simulate_pattern( + self, + backend: StatevectorBackend, + input_state: State | Statevec | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None = None, + **kwargs: Any, + ) -> Statevec: + ... + + @overload + def simulate_pattern( + self, + backend: DensityMatrixBackend, + input_state: State | DensityMatrix | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None, + **kwargs: Any, + ) -> DensityMatrix: + ... + + @overload + def simulate_pattern( + self, + backend: TensorNetworkBackend, + input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None, + **kwargs: Any, + ) -> MBQCTensorNet: + ... + + @overload + def simulate_pattern( + self, + backend: str, + input_state: Data, + rng: Generator | None, + **kwargs: Any, + ) -> Statevec | DensityMatrix | MBQCTensorNet: + ... + def simulate_pattern( self, backend: Backend[_StateT_co] | str = "statevector", input_state: Data = BasicStates.PLUS, rng: Generator | None = None, **kwargs: Any, - ) -> BackendState: + ) -> Statevec | DensityMatrix | MBQCTensorNet: """Simulate the execution of the pattern by using :class:`graphix.simulator.PatternSimulator`. Available backend: ['statevector', 'densitymatrix', 'tensornetwork'] diff --git a/graphix/sim/__init__.py b/graphix/sim/__init__.py index e54f62cab..d9c88f6d3 100644 --- a/graphix/sim/__init__.py +++ b/graphix/sim/__init__.py @@ -2,9 +2,9 @@ from __future__ import annotations -from graphix.sim.base_backend import Backend, BackendState +from graphix.sim.base_backend import Backend from graphix.sim.data import Data -from graphix.sim.density_matrix import DensityMatrix -from graphix.sim.statevec import Statevec +from graphix.sim.density_matrix import DensityMatrix, DensityMatrixBackend +from graphix.sim.statevec import Statevec, StatevectorBackend -__all__ = ["Backend", "BackendState", "Data", "DensityMatrix", "Statevec"] +__all__ = ["Backend", "Data", "DensityMatrix", "DensityMatrixBackend", "Statevec", "StatevectorBackend"] diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index ef5a39172..4626d47f5 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -38,6 +38,8 @@ Matrix: TypeAlias = npt.NDArray[np.object_ | np.complex128] +_StateT_co = TypeVar("_StateT_co", covariant=True) + def tensordot(op: Matrix, psi: Matrix, axes: tuple[int | Sequence[int], int | Sequence[int]]) -> Matrix: """Tensor dot product that preserves the type of `psi`. @@ -326,37 +328,7 @@ def __str__(self) -> str: return "This backend does not support noise." -class BackendState(ABC): - """ - Abstract base class for representing the quantum state of a backend. - - `BackendState` defines the interface for quantum state representations used by - various backend implementations. It provides a common foundation for different - simulation strategies, such as dense linear algebra or tensor network contraction. - - Concrete subclasses must implement the storage and manipulation logic appropriate - for a specific backend and representation strategy. - - Notes - ----- - This class is abstract and cannot be instantiated directly. - - Examples of concrete subclasses include: - - :class:`Statevec` (for pure states represented as state vectors) - - :class:`DensityMatrix` (for mixed states represented as density matrices) - - :class:`MBQCTensorNet` (for compressed representations using tensor networks) - - See Also - -------- - :class:`DenseState`, :class:`MBQCTensorNet`, :class:`Statevec`, :class:`DensityMatrix` - """ - - @abstractmethod - def flatten(self) -> Matrix: - """Return flattened state.""" - - -class DenseState(BackendState): +class DenseState(ABC): """ Abstract base class for quantum states with full dense representations. @@ -374,7 +346,7 @@ class DenseState(BackendState): ----- This class is abstract and cannot be instantiated directly. - Not all :class:`BackendState` subclasses are dense. For example, :class:`MBQCTensorNet` is a + Not all internal states are dense. For example, :class:`MBQCTensorNet` is a `BackendState` that represents the quantum state using a tensor network, rather than a single dense array. @@ -389,6 +361,10 @@ class DenseState(BackendState): def nqubit(self) -> int: """Return the number of qubits.""" + @abstractmethod + def flatten(self) -> Matrix: + """Return flattened state.""" + @abstractmethod def add_nodes(self, nqubit: int, data: Data) -> None: """ @@ -561,9 +537,6 @@ def f_expectation0() -> float: return result -_StateT_co = TypeVar("_StateT_co", bound="BackendState", covariant=True) - - @dataclass(frozen=True) class Backend(Generic[_StateT_co]): """ diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index c5158f09a..671122321 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -7,7 +7,7 @@ from abc import ABC from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, SupportsComplex, TypeAlias +from typing import TYPE_CHECKING, Generic, SupportsComplex, TypeAlias import numpy as np import numpy.typing as npt @@ -21,7 +21,7 @@ from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.ops import Ops from graphix.parameter import Expression -from graphix.sim.base_backend import Backend, BackendState +from graphix.sim.base_backend import Backend, _StateT_co from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: @@ -39,7 +39,7 @@ PrepareState: TypeAlias = str | npt.NDArray[np.complex128] -class MBQCTensorNet(BackendState, TensorNetwork): +class MBQCTensorNet(TensorNetwork): """Tensor Network Simulator interface for MBQC patterns, using quimb.tensor.core.TensorNetwork.""" _dangling: dict[str, str] diff --git a/graphix/simulator.py b/graphix/simulator.py index 640f5fc41..80a03641c 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,7 +8,7 @@ import abc import warnings -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, Generic, overload import numpy as np @@ -35,10 +35,9 @@ from graphix.command import BaseN from graphix.noise_models.noise_model import CommandOrNoise, NoiseModel from graphix.pattern import Pattern - from graphix.sim import BackendState, Data - - -_StateT_co = TypeVar("_StateT_co", bound="BackendState", covariant=True) + from graphix.sim import Backend, Data, DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend + from graphix.sim.base_backend import _StateT_co + from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend class PrepareMethod(abc.ABC): @@ -219,7 +218,7 @@ class PatternSimulator: def __init__( self, pattern: Pattern, - backend: Backend[BackendState] | str = "statevector", + backend: Backend[_StateT_co] | str = "statevector", prepare_method: PrepareMethod | None = None, measure_method: MeasureMethod | None = None, noise_model: NoiseModel | None = None, @@ -254,43 +253,7 @@ def __init__( :class:`graphix.sim.tensornet.TensorNetworkBackend`\ :class:`graphix.sim.density_matrix.DensityMatrixBackend`\ """ - - def initialize_backend() -> Backend[BackendState]: - nonlocal backend, branch_selector, graph_prep, noise_model - if isinstance(backend, Backend): - if branch_selector is not None: - raise ValueError("`branch_selector` cannot be specified if `backend` is already instantiated.") - if graph_prep is not None: - raise ValueError("`graph_prep` cannot be specified if `backend` is already instantiated.") - if symbolic: - raise ValueError("`symbolic` cannot be specified if `backend` is already instantiated.") - return backend - if branch_selector is None: - branch_selector = RandomBranchSelector() - if backend in {"tensornetwork", "mps"}: - if noise_model is not None: - raise ValueError("`noise_model` cannot be specified for tensor network backend.") - if symbolic: - raise ValueError("`symbolic` cannot be specified for tensor network backend.") - if graph_prep is None: - graph_prep = "auto" - return TensorNetworkBackend(pattern, branch_selector=branch_selector, graph_prep=graph_prep) - if graph_prep is not None: - raise ValueError("`graph_prep` can only be specified for tensor network backend.") - if backend == "statevector": - if noise_model is not None: - raise ValueError("`noise_model` cannot be specified for state vector backend.") - return StatevectorBackend(branch_selector=branch_selector, symbolic=symbolic) - if backend == "densitymatrix": - if noise_model is None: - warnings.warn( - "Simulating using densitymatrix backend with no noise. To add noise to the simulation, give an object of `graphix.noise_models.Noisemodel` to `noise_model` keyword argument.", - stacklevel=1, - ) - return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) - raise ValueError(f"Unknown backend {backend}.") - - self.backend = initialize_backend() + self.backend = self.initialize_backend(pattern, backend, noise_model, branch_selector, graph_prep, symbolic) self.noise_model = noise_model self.__pattern = pattern if prepare_method is None: @@ -310,6 +273,83 @@ def measure_method(self) -> MeasureMethod: """Return the measure method.""" return self.__measure_method + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: StatevectorBackend, noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: + ... + + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: DensityMatrixBackend, noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> DensityMatrixBackend: + ... + + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: TensorNetworkBackend, noise_model: None, branch_selector: BranchSelector, graph_prep: str | None, symbolic: bool) -> TensorNetworkBackend: + ... + + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: + ... + + @staticmethod + def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[_StateT_co]: + """ + Initialize the backend. + + Parameters + ---------- + backend: :class:`Backend` object, + 'statevector', or 'densitymatrix', or 'tensornetwork' + simulation backend (optional), default is 'statevector'. + noise_model: :class:`NoiseModel`, optional + [Density matrix backend only] Noise model used by the simulator. + branch_selector: :class:`BranchSelector`, optional + Branch selector used for measurements. Can only be specified if ``backend`` is not an already instantiated :class:`Backend` object. Default is :class:`RandomBranchSelector`. + graph_prep: str, optional + [Tensor network backend only] Strategy for preparing the graph state. See :class:`TensorNetworkBackend`. + symbolic : bool, optional + [State vector and density matrix backends only] If True, support arbitrary objects (typically, symbolic expressions) in measurement angles. + + Returns + ------- + :class:`Backend` + matching the appropriate backend + """ + if isinstance(backend, Backend): + if branch_selector is not None: + raise ValueError("`branch_selector` cannot be specified if `backend` is already instantiated.") + if graph_prep is not None: + raise ValueError("`graph_prep` cannot be specified if `backend` is already instantiated.") + if symbolic: + raise ValueError("`symbolic` cannot be specified if `backend` is already instantiated.") + return backend + if branch_selector is None: + branch_selector = RandomBranchSelector() + if backend in {"tensornetwork", "mps"}: + if noise_model is not None: + raise ValueError("`noise_model` cannot be specified for tensor network backend.") + if symbolic: + raise ValueError("`symbolic` cannot be specified for tensor network backend.") + if graph_prep is None: + graph_prep = "auto" + return TensorNetworkBackend(pattern, branch_selector=branch_selector, graph_prep=graph_prep) + if graph_prep is not None: + raise ValueError("`graph_prep` can only be specified for tensor network backend.") + if backend == "statevector": + if noise_model is not None: + raise ValueError("`noise_model` cannot be specified for state vector backend.") + return StatevectorBackend(branch_selector=branch_selector, symbolic=symbolic) + if backend == "densitymatrix": + if noise_model is None: + warnings.warn( + "Simulating using densitymatrix backend with no noise. To add noise to the simulation, give an object of `graphix.noise_models.Noisemodel` to `noise_model` keyword argument.", + stacklevel=1, + ) + return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) + raise ValueError(f"Unknown backend {backend}.") + def set_noise_model(self, model: NoiseModel | None) -> None: """Set a noise model.""" self.noise_model = model From b88df058630a81ff0f996d067b8973c0c589be67 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 3 Dec 2025 13:30:14 +0100 Subject: [PATCH 02/36] trying to remove BackendState bad fixed version --- graphix/simulator.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/graphix/simulator.py b/graphix/simulator.py index 80a03641c..03432b5d0 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,7 +8,7 @@ import abc import warnings -from typing import TYPE_CHECKING, Generic, overload +from typing import TYPE_CHECKING, Any import numpy as np @@ -273,28 +273,8 @@ def measure_method(self) -> MeasureMethod: """Return the measure method.""" return self.__measure_method - @overload @staticmethod - def initialize_backend(pattern: Pattern, backend: StatevectorBackend, noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: - ... - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: DensityMatrixBackend, noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> DensityMatrixBackend: - ... - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: TensorNetworkBackend, noise_model: None, branch_selector: BranchSelector, graph_prep: str | None, symbolic: bool) -> TensorNetworkBackend: - ... - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: - ... - - @staticmethod - def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[_StateT_co]: + def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[Any]: """ Initialize the backend. From 4dc8ee599e6513fff8f66e61ebaad3184ebef4e0 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 13:11:37 +0100 Subject: [PATCH 03/36] Fixing typing. --- graphix/simulator.py | 49 +++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/graphix/simulator.py b/graphix/simulator.py index 03432b5d0..538d6aaa8 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -35,9 +35,34 @@ from graphix.command import BaseN from graphix.noise_models.noise_model import CommandOrNoise, NoiseModel from graphix.pattern import Pattern - from graphix.sim import Backend, Data, DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend + from graphix.sim import Data from graphix.sim.base_backend import _StateT_co - from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend + + +class PrepareMethod(abc.ABC): + """Prepare method used by the simulator. + + See `DefaultPrepareMethod` for the default prepare method that implements MBQC. + + To be overwritten by custom preparation methods in the case of delegated QC protocols. + + Example: class `ClientPrepareMethod` in https://github.com/qat-inria/veriphix + """ + + @abc.abstractmethod + def prepare(self, backend: Backend[_StateT_co], cmd: BaseN, rng: Generator | None = None) -> None: + """Prepare a node.""" + + +class DefaultPrepareMethod(PrepareMethod): + """Default prepare method implementing standard preparation for MBQC.""" + + @override + def prepare(self, backend: Backend[_StateT_co], cmd: BaseN, rng: Generator | None = None) -> None: + """Prepare a node.""" + if not isinstance(cmd, N): + raise TypeError("The default prepare method requires all preparation commands to be of type `N`.") + backend.add_nodes(nodes=[cmd.node], data=cmd.state) class PrepareMethod(abc.ABC): @@ -263,16 +288,6 @@ def __init__( measure_method = DefaultMeasureMethod(pattern.results) self.__measure_method = measure_method - @property - def pattern(self) -> Pattern: - """Return the pattern.""" - return self.__pattern - - @property - def measure_method(self) -> MeasureMethod: - """Return the measure method.""" - return self.__measure_method - @staticmethod def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[Any]: """ @@ -330,6 +345,16 @@ def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noi return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) raise ValueError(f"Unknown backend {backend}.") + @property + def pattern(self) -> Pattern: + """Return the pattern.""" + return self.__pattern + + @property + def measure_method(self) -> MeasureMethod: + """Return the measure method.""" + return self.__measure_method + def set_noise_model(self, model: NoiseModel | None) -> None: """Set a noise model.""" self.noise_model = model From 003f71e20178c83a0048aa52d6a4512203700da3 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 13:05:03 +0100 Subject: [PATCH 04/36] Fixed typing after removing backend state. --- graphix/pattern.py | 52 +++++++++++++++++++++++----------------- graphix/sim/tensornet.py | 4 ++-- graphix/simulator.py | 13 ++++++---- tests/test_pattern.py | 4 +--- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 87f3dc528..957a82342 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -13,7 +13,7 @@ from dataclasses import dataclass from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, SupportsFloat, overload +from typing import TYPE_CHECKING, Literal, SupportsFloat, overload import networkx as nx from typing_extensions import assert_never @@ -1339,42 +1339,49 @@ def space_list(self) -> list[int]: @overload def simulate_pattern( self, - backend: StatevectorBackend, - input_state: State | Statevec | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], - rng: Generator | None = None, + backend: StatevectorBackend | Literal["statevector"], + input_state: State + | Statevec + | Iterable[State] + | Iterable[ExpressionOrSupportsComplex] + | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None = ..., **kwargs: Any, - ) -> Statevec: - ... + ) -> Statevec: ... @overload def simulate_pattern( self, - backend: DensityMatrixBackend, - input_state: State | DensityMatrix | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], - rng: Generator | None, + backend: DensityMatrixBackend | Literal["densitymatrix"], + input_state: State + | DensityMatrix + | Iterable[State] + | Iterable[ExpressionOrSupportsComplex] + | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None = ..., **kwargs: Any, - ) -> DensityMatrix: - ... + ) -> DensityMatrix: ... @overload def simulate_pattern( self, - backend: TensorNetworkBackend, - input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], - rng: Generator | None, + backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], + input_state: State + | Iterable[State] + | Iterable[ExpressionOrSupportsComplex] + | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None = ..., **kwargs: Any, - ) -> MBQCTensorNet: - ... + ) -> MBQCTensorNet: ... @overload def simulate_pattern( self, - backend: str, - input_state: Data, - rng: Generator | None, + backend: str = ..., + input_state: Data = ..., + rng: Generator | None = ..., **kwargs: Any, - ) -> Statevec | DensityMatrix | MBQCTensorNet: - ... + ) -> Statevec | DensityMatrix | MBQCTensorNet: ... def simulate_pattern( self, @@ -1406,7 +1413,8 @@ def simulate_pattern( """ sim = PatternSimulator(self, backend=backend, **kwargs) sim.run(input_state, rng=rng) - return sim.backend.state + state: Statevec | DensityMatrix | MBQCTensorNet = sim.backend.state + return state def perform_pauli_measurements(self, leave_input: bool = False, ignore_pauli_with_deps: bool = False) -> None: """Perform Pauli measurements in the pattern using efficient stabilizer simulator. diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 671122321..c0a261b28 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -7,7 +7,7 @@ from abc import ABC from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, Generic, SupportsComplex, TypeAlias +from typing import TYPE_CHECKING, SupportsComplex, TypeAlias import numpy as np import numpy.typing as npt @@ -21,7 +21,7 @@ from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.ops import Ops from graphix.parameter import Expression -from graphix.sim.base_backend import Backend, _StateT_co +from graphix.sim.base_backend import Backend from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: diff --git a/graphix/simulator.py b/graphix/simulator.py index 538d6aaa8..418d49023 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -21,9 +21,7 @@ from graphix.clifford import Clifford from graphix.command import BaseM, CommandKind, MeasureUpdate, N from graphix.measurements import Measurement, Outcome -from graphix.sim.base_backend import Backend -from graphix.sim.density_matrix import DensityMatrixBackend -from graphix.sim.statevec import StatevectorBackend +from graphix.sim import Backend, DensityMatrixBackend, StatevectorBackend from graphix.sim.tensornet import TensorNetworkBackend from graphix.states import BasicStates @@ -289,7 +287,14 @@ def __init__( self.__measure_method = measure_method @staticmethod - def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[Any]: + def initialize_backend( + pattern: Pattern, + backend: Backend[_StateT_co] | str, + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, + ) -> Backend[Any]: """ Initialize the backend. diff --git a/tests/test_pattern.py b/tests/test_pattern.py index fc4a61b5a..89424957b 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -27,10 +27,8 @@ if TYPE_CHECKING: from collections.abc import Sequence - from graphix.sim.base_backend import BackendState - -def compare_backend_result_with_statevec(backend_state: BackendState, statevec: Statevec) -> float: +def compare_backend_result_with_statevec(backend_state: object, statevec: Statevec) -> float: if isinstance(backend_state, Statevec): return float(np.abs(np.dot(backend_state.flatten().conjugate(), statevec.flatten()))) if isinstance(backend_state, DensityMatrix): From 604110bb8b208311bf0116535c88a9f359f7a050 Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Thu, 4 Dec 2025 14:45:35 +0100 Subject: [PATCH 05/36] Introduce `BaseN` and `PrepareMethod` (#383) This commit introduces the classes `BaseN` and `PrepareMethod`, which allow the user to customize how `N` commands are handled. For instance, in the VBQC context of Veriphix, `N` commands do not necessarily prepare the node in the |+> state: for each prepared qubit, the preparation should be performed by the client, so that the server does not even see the state. --- graphix/simulator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graphix/simulator.py b/graphix/simulator.py index 418d49023..a177e1638 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -241,7 +241,11 @@ class PatternSimulator: def __init__( self, pattern: Pattern, +<<<<<<< HEAD backend: Backend[_StateT_co] | str = "statevector", +======= + backend: Backend[BackendState] | str = "statevector", +>>>>>>> a437e92 (Introduce `BaseN` and `PrepareMethod` (#383)) prepare_method: PrepareMethod | None = None, measure_method: MeasureMethod | None = None, noise_model: NoiseModel | None = None, From a0622f30a57adfd06a7aee5eb2391154124cdcc3 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 3 Dec 2025 12:58:14 +0100 Subject: [PATCH 06/36] trying to remove BackendState still causing problems --- graphix/pattern.py | 40 ++++++++++++++++++ graphix/sim/tensornet.py | 6 ++- graphix/simulator.py | 90 +++++++++++++++++----------------------- 3 files changed, 82 insertions(+), 54 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 957a82342..29e37e91a 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -13,7 +13,11 @@ from dataclasses import dataclass from enum import Enum from pathlib import Path +<<<<<<< HEAD from typing import TYPE_CHECKING, Literal, SupportsFloat, overload +======= +from typing import TYPE_CHECKING, SupportsFloat, overload +>>>>>>> 8353f6e (trying to remove BackendState still causing problems) import networkx as nx from typing_extensions import assert_never @@ -1339,6 +1343,7 @@ def space_list(self) -> list[int]: @overload def simulate_pattern( self, +<<<<<<< HEAD backend: StatevectorBackend | Literal["statevector"], input_state: State | Statevec @@ -1348,10 +1353,19 @@ def simulate_pattern( rng: Generator | None = ..., **kwargs: Any, ) -> Statevec: ... +======= + backend: StatevectorBackend, + input_state: State | Statevec | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None = None, + **kwargs: Any, + ) -> Statevec: + ... +>>>>>>> 8353f6e (trying to remove BackendState still causing problems) @overload def simulate_pattern( self, +<<<<<<< HEAD backend: DensityMatrixBackend | Literal["densitymatrix"], input_state: State | DensityMatrix @@ -1361,10 +1375,19 @@ def simulate_pattern( rng: Generator | None = ..., **kwargs: Any, ) -> DensityMatrix: ... +======= + backend: DensityMatrixBackend, + input_state: State | DensityMatrix | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None, + **kwargs: Any, + ) -> DensityMatrix: + ... +>>>>>>> 8353f6e (trying to remove BackendState still causing problems) @overload def simulate_pattern( self, +<<<<<<< HEAD backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], input_state: State | Iterable[State] @@ -1373,15 +1396,32 @@ def simulate_pattern( rng: Generator | None = ..., **kwargs: Any, ) -> MBQCTensorNet: ... +======= + backend: TensorNetworkBackend, + input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], + rng: Generator | None, + **kwargs: Any, + ) -> MBQCTensorNet: + ... +>>>>>>> 8353f6e (trying to remove BackendState still causing problems) @overload def simulate_pattern( self, +<<<<<<< HEAD backend: str = ..., input_state: Data = ..., rng: Generator | None = ..., **kwargs: Any, ) -> Statevec | DensityMatrix | MBQCTensorNet: ... +======= + backend: str, + input_state: Data, + rng: Generator | None, + **kwargs: Any, + ) -> Statevec | DensityMatrix | MBQCTensorNet: + ... +>>>>>>> 8353f6e (trying to remove BackendState still causing problems) def simulate_pattern( self, diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index c0a261b28..de0728499 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -7,7 +7,7 @@ from abc import ABC from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, SupportsComplex, TypeAlias +from typing import TYPE_CHECKING, Generic, SupportsComplex, TypeAlias import numpy as np import numpy.typing as npt @@ -21,7 +21,11 @@ from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.ops import Ops from graphix.parameter import Expression +<<<<<<< HEAD from graphix.sim.base_backend import Backend +======= +from graphix.sim.base_backend import Backend, _StateT_co +>>>>>>> 8353f6e (trying to remove BackendState still causing problems) from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: diff --git a/graphix/simulator.py b/graphix/simulator.py index a177e1638..2c022276c 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,7 +8,7 @@ import abc import warnings -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Generic, Literal, overload import numpy as np @@ -33,34 +33,9 @@ from graphix.command import BaseN from graphix.noise_models.noise_model import CommandOrNoise, NoiseModel from graphix.pattern import Pattern - from graphix.sim import Data + from graphix.sim import Backend, Data, DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend from graphix.sim.base_backend import _StateT_co - - -class PrepareMethod(abc.ABC): - """Prepare method used by the simulator. - - See `DefaultPrepareMethod` for the default prepare method that implements MBQC. - - To be overwritten by custom preparation methods in the case of delegated QC protocols. - - Example: class `ClientPrepareMethod` in https://github.com/qat-inria/veriphix - """ - - @abc.abstractmethod - def prepare(self, backend: Backend[_StateT_co], cmd: BaseN, rng: Generator | None = None) -> None: - """Prepare a node.""" - - -class DefaultPrepareMethod(PrepareMethod): - """Default prepare method implementing standard preparation for MBQC.""" - - @override - def prepare(self, backend: Backend[_StateT_co], cmd: BaseN, rng: Generator | None = None) -> None: - """Prepare a node.""" - if not isinstance(cmd, N): - raise TypeError("The default prepare method requires all preparation commands to be of type `N`.") - backend.add_nodes(nodes=[cmd.node], data=cmd.state) + from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend class PrepareMethod(abc.ABC): @@ -241,11 +216,7 @@ class PatternSimulator: def __init__( self, pattern: Pattern, -<<<<<<< HEAD backend: Backend[_StateT_co] | str = "statevector", -======= - backend: Backend[BackendState] | str = "statevector", ->>>>>>> a437e92 (Introduce `BaseN` and `PrepareMethod` (#383)) prepare_method: PrepareMethod | None = None, measure_method: MeasureMethod | None = None, noise_model: NoiseModel | None = None, @@ -283,22 +254,45 @@ def __init__( self.backend = self.initialize_backend(pattern, backend, noise_model, branch_selector, graph_prep, symbolic) self.noise_model = noise_model self.__pattern = pattern - if prepare_method is None: - prepare_method = DefaultPrepareMethod() - self.__prepare_method = prepare_method if measure_method is None: measure_method = DefaultMeasureMethod(pattern.results) self.__measure_method = measure_method + if prepare_method is None: + prepare_method = DefaultPrepareMethod() + self.__prepare_method = prepare_method + @property + def pattern(self) -> Pattern: + """Return the pattern.""" + return self.__pattern + + @property + def measure_method(self) -> MeasureMethod: + """Return the measure method.""" + return self.__measure_method + + @overload @staticmethod - def initialize_backend( - pattern: Pattern, - backend: Backend[_StateT_co] | str, - noise_model: NoiseModel | None, - branch_selector: BranchSelector | None, - graph_prep: str | None, - symbolic: bool, - ) -> Backend[Any]: + def initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: + ... + + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: DensityMatrixBackend | Literal["densitymatrix"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> DensityMatrixBackend: + ... + + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: TensorNetworkBackend | Literal["tensornetwork"], noise_model: None, branch_selector: BranchSelector, graph_prep: str | None, symbolic: bool) -> TensorNetworkBackend: + ... + + @overload + @staticmethod + def initialize_backend(pattern: Pattern, backend: str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: + ... + + @staticmethod + def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: """ Initialize the backend. @@ -354,16 +348,6 @@ def initialize_backend( return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) raise ValueError(f"Unknown backend {backend}.") - @property - def pattern(self) -> Pattern: - """Return the pattern.""" - return self.__pattern - - @property - def measure_method(self) -> MeasureMethod: - """Return the measure method.""" - return self.__measure_method - def set_noise_model(self, model: NoiseModel | None) -> None: """Set a noise model.""" self.noise_model = model From 87fdc74a2fa13c82cea90c2b7f1545e847ab5798 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 2 Dec 2025 15:46:51 +0100 Subject: [PATCH 07/36] Refactor of flow tools - `OpenGraph.isclose` (#374) This commit adapts the existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. Additionally, it introduces the new methods `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs, and `graphix.fundamentals.AbstractMeasurement.isclose` which defaults to `==` comparison. --- CHANGELOG.md | 7 ++++ tests/test_opengraph.py | 77 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6423ac8..a6515155f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +<<<<<<< HEAD - #378: - Introduced new method `graphix.flow.core.PauliFlow.check_well_formed`, `graphix.flow.core.GFlow.check_well_formed` and `graphix.flow.core.CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. - Introduced new method `graphix.flow.core.PauliFlow.is_well_formed` which verify the correctness of flow objects and returns a boolean when the flow is incorrect. @@ -22,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 defaults to `DefaultPrepareMethod`) to customize how `N` commands are handled, and the class `BaseN` can be used as a base class for custom preparation commands. +======= +- #347: + - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. + - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. +>>>>>>> 7cb93c6 (Refactor of flow tools - `OpenGraph.isclose` (#374)) ### Fixed @@ -40,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.3] - 2025-10-23 +- #347: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. ### Added - #343: Circuit exporter to OpenQASM3: diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 8a91794b4..12bb5c489 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -907,6 +907,7 @@ def test_isclose_measurement(self) -> None: assert og_1.isclose(og_2, abs_tol=0.1) assert not og_1.isclose(og_2) assert not og_2.isclose(og_3) +<<<<<<< HEAD def test_isclose_plane(self) -> None: og_1 = OpenGraph( @@ -921,6 +922,82 @@ def test_isclose_plane(self) -> None: output_nodes=[3], measurements=dict.fromkeys(range(3), Plane.XZ), ) +======= + + def test_isclose_plane(self) -> None: + og_1 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Plane.XY), + ) + og_2 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Plane.XZ), + ) + + assert not og_1.isclose(og_2) + assert og_1.isclose(og_1) + + def test_isclose_axis(self) -> None: + og_1 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Axis.X), + ) + og_2 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Axis.Y), + ) + + assert not og_1.isclose(og_2) + assert og_1.isclose(og_1) + assert og_2.isclose(og_2) + + def test_is_equal_structurally(self) -> None: + og_1 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Measurement(0.15, Plane.XY)), + ) + og_2 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Measurement(0.1, Plane.XY)), + ) + og_3 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Plane.XY), + ) + og_4 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Axis.X), + ) + og_5 = OpenGraph( + graph=nx.Graph([(0, 1), (1, 2), (2, 3), (0, 3)]), + input_nodes=[0], + output_nodes=[3], + measurements=dict.fromkeys(range(3), Axis.X), + ) + assert og_1.is_equal_structurally(og_2) + assert og_1.is_equal_structurally(og_3) + assert og_1.is_equal_structurally(og_4) + assert not og_1.is_equal_structurally(og_5) + + +# TODO: rewrite as parametric tests +>>>>>>> 7cb93c6 (Refactor of flow tools - `OpenGraph.isclose` (#374)) assert not og_1.isclose(og_2) assert og_1.isclose(og_1) From 7a29de280b6c5659039d9133c4283f4a5eac9928 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 2 Dec 2025 16:24:16 +0100 Subject: [PATCH 08/36] Refactor of flow tools - `OpenGraph.compose` (#375) This commit adapts the existing method `:func: OpenGraph.compose` to the new API introduced in #358. --- CHANGELOG.md | 6 +++++- tests/test_opengraph.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6515155f..8e665f3b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added <<<<<<< HEAD +<<<<<<< HEAD - #378: - Introduced new method `graphix.flow.core.PauliFlow.check_well_formed`, `graphix.flow.core.GFlow.check_well_formed` and `graphix.flow.core.CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. - Introduced new method `graphix.flow.core.PauliFlow.is_well_formed` which verify the correctness of flow objects and returns a boolean when the flow is incorrect. - Introduced new module `graphix.flow.exceptions` grouping flow exceptions. - Introduced new methods `graphix.flow.core.PauliFlow.get_measurement_label` and `graphix.flow.core.GFlow.get_measurement_label` which return the measurement label of a given node following same criteria employed in the flow-finding algorithms. +======= +>>>>>>> 1af27db (Refactor of flow tools - `OpenGraph.compose` (#375)) - #374: - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. @@ -39,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 commands without domain information. ### Changed +- #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. +- #375: Adapted existing method `graphix.opengraph.OpenGraph.compose` to the new API introduced in #358. - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. @@ -46,7 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.3] - 2025-10-23 -- #347: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. ### Added - #343: Circuit exporter to OpenQASM3: diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 12bb5c489..437dc93ee 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -995,7 +995,15 @@ def test_is_equal_structurally(self) -> None: assert og_1.is_equal_structurally(og_4) assert not og_1.is_equal_structurally(og_5) + @pytest.mark.parametrize("test_case", OPEN_GRAPH_COMPOSE_TEST_CASES) + def test_compose(self, test_case: OpenGraphComposeTestCase) -> None: + og1, og2, og_ref, mapping = test_case + og, mapping_complete = og1.compose(og2, mapping) + assert og.isclose(og_ref) + assert mapping.keys() <= mapping_complete.keys() + assert set(mapping.values()) <= set(mapping_complete.values()) +<<<<<<< HEAD # TODO: rewrite as parametric tests >>>>>>> 7cb93c6 (Refactor of flow tools - `OpenGraph.isclose` (#374)) @@ -1084,6 +1092,28 @@ def test_compose_exception(self) -> None: og3 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XY}) og4 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XZ}) +======= + def test_compose_exception(self) -> None: + g: nx.Graph[int] = nx.Graph([(0, 1)]) + inputs = [0] + outputs = [1] + mapping = {0: 0, 1: 1} + + og1 = OpenGraph(g, inputs, outputs, measurements={0: Measurement(0, Plane.XY)}) + og2 = OpenGraph(g, inputs, outputs, measurements={0: Measurement(0.5, Plane.XY)}) + + with pytest.raises( + OpenGraphError, + match=re.escape( + "Attempted to merge nodes with different measurements: (0, Measurement(angle=0.5, plane=Plane.XY)) -> (0, Measurement(angle=0, plane=Plane.XY))." + ), + ): + og1.compose(og2, mapping) + + og3 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XY}) + og4 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XZ}) + +>>>>>>> 1af27db (Refactor of flow tools - `OpenGraph.compose` (#375)) with pytest.raises( OpenGraphError, match=re.escape("Attempted to merge nodes with different measurements: (0, Plane.XZ) -> (0, Plane.XY)."), From 2e63924b2855dbe1ba427b483cd58caa198e56f8 Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Tue, 2 Dec 2025 17:28:32 +0100 Subject: [PATCH 09/36] Fix #349: ensure flow for patterns transpiled from circuit (#362) This commit fixes domains in the transpiler so that every pattern transpiled from a circuit has a flow. Previously, some domains were incompatible with any flow, despite the patterns being deterministic, because some measurement angles were zero, causing the measurements to ignore certain signals. This commit also adds `optimization.remove_useless_domains` to remove the domains ignored by measurements with angle zero, to recover the same "optimized" patterns as before when needed. This commit also adds `rand_state_vector` to draw a random state vector. --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e665f3b0..a56c57fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD - #378: - Introduced new method `graphix.flow.core.PauliFlow.check_well_formed`, `graphix.flow.core.GFlow.check_well_formed` and `graphix.flow.core.CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. - Introduced new method `graphix.flow.core.PauliFlow.is_well_formed` which verify the correctness of flow objects and returns a boolean when the flow is incorrect. @@ -18,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ======= >>>>>>> 1af27db (Refactor of flow tools - `OpenGraph.compose` (#375)) +======= + +>>>>>>> ee04322 (Fix #349: ensure flow for patterns transpiled from circuit (#362)) - #374: - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. @@ -38,11 +42,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #349, #362: Patterns transpiled from circuits always have causal flow. +<<<<<<< HEAD - #383: `Pattern.check_runnability` no longer fails on custom `BaseM` commands without domain information. +======= +>>>>>>> ee04322 (Fix #349: ensure flow for patterns transpiled from circuit (#362)) ### Changed + - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. + - #375: Adapted existing method `graphix.opengraph.OpenGraph.compose` to the new API introduced in #358. - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. From 23294b2a8294c84224a5a3f018114bed2febd2d1 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 13:11:37 +0100 Subject: [PATCH 10/36] Fixing typing. --- CHANGELOG.md | 9 +++++++++ graphix/simulator.py | 25 ++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a56c57fe3..48fb9f19c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,11 +30,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 defaults to `DefaultPrepareMethod`) to customize how `N` commands are handled, and the class `BaseN` can be used as a base class for custom preparation commands. +<<<<<<< HEAD ======= - #347: - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. >>>>>>> 7cb93c6 (Refactor of flow tools - `OpenGraph.isclose` (#374)) +======= +>>>>>>> 8340e29 (Fixing typing.) ### Fixed @@ -42,12 +45,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #349, #362: Patterns transpiled from circuits always have causal flow. +<<<<<<< HEAD <<<<<<< HEAD - #383: `Pattern.check_runnability` no longer fails on custom `BaseM` commands without domain information. ======= >>>>>>> ee04322 (Fix #349: ensure flow for patterns transpiled from circuit (#362)) +======= +- #383: `Pattern.check_runnability` no longer fails on custom `BaseM` + commands without domain information. + +>>>>>>> 8340e29 (Fixing typing.) ### Changed - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. diff --git a/graphix/simulator.py b/graphix/simulator.py index 2c022276c..8bfaac140 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -33,7 +33,7 @@ from graphix.command import BaseN from graphix.noise_models.noise_model import CommandOrNoise, NoiseModel from graphix.pattern import Pattern - from graphix.sim import Backend, Data, DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend + from graphix.sim import Data from graphix.sim.base_backend import _StateT_co from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend @@ -254,6 +254,9 @@ def __init__( self.backend = self.initialize_backend(pattern, backend, noise_model, branch_selector, graph_prep, symbolic) self.noise_model = noise_model self.__pattern = pattern + if prepare_method is None: + prepare_method = DefaultPrepareMethod() + self.__prepare_method = prepare_method if measure_method is None: measure_method = DefaultMeasureMethod(pattern.results) self.__measure_method = measure_method @@ -261,16 +264,6 @@ def __init__( prepare_method = DefaultPrepareMethod() self.__prepare_method = prepare_method - @property - def pattern(self) -> Pattern: - """Return the pattern.""" - return self.__pattern - - @property - def measure_method(self) -> MeasureMethod: - """Return the measure method.""" - return self.__measure_method - @overload @staticmethod def initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: @@ -348,6 +341,16 @@ def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noi return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) raise ValueError(f"Unknown backend {backend}.") + @property + def pattern(self) -> Pattern: + """Return the pattern.""" + return self.__pattern + + @property + def measure_method(self) -> MeasureMethod: + """Return the measure method.""" + return self.__measure_method + def set_noise_model(self, model: NoiseModel | None) -> None: """Set a noise model.""" self.noise_model = model From 67412d31639701a9ebfb8331627013823fd4d984 Mon Sep 17 00:00:00 2001 From: matulni Date: Mon, 8 Dec 2025 17:50:00 +0100 Subject: [PATCH 11/36] Refactor of flow tools - Verification of flow objects (#378) This PR introduces the methods `:func: PauliFlow.check_well_formed`, `:func: GFlow.check_well_formed` and `:func: CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. Exception classes are grouped in the new module `graphix.flow.exceptions`. The error messages explain which proposition in the flow is violated. Flow-finding algorithms always (in principle) output well-formed flows, but it is possible to instantiate flow objects by passing arbitrary parameters to the constructors. In such cases, there is not any guarantee that the flow objects are well formed. The methods introduced here can be useful for debugging or researching. The exception handling adapts the pattern introduced in #364. The new methods subsume `:func: gflow.verify_flow`, `:func: gflow.verify_gflow`, `:func: gflow.verify_pauli_flow`. Additionally, this PR introduces the methods `:func: PauliFlow.get_measurement_label`, `:func: GFlow.get_measurement_label` which return the measurement label of a given node. They follow same criteria employed in the flow-finding algorithms, that is, querying this method on a node with a measurement `Measurement(0, Plane.XY)` will return `Plane.XY` in a `GFlow` or `CausalFlow` and `Axis.X` in a `PauliFlow`. **Additional information on the exception management** ```mermaid --- config: layout: elk elk: mergeEdges: false nodePlacementStrategy: LINEAR_SEGMENTS --- flowchart TD a0(**Exception**) n0(**FlowError**) n1(FlowGenericError) n2(FlowPropositionError) n3(FlowPropositionOrderError) n4(PartialOrderError) n5(PartialOrderLayerError) a0 --> n0 n0 --FGEReason--> n1 n0 --FPEReason--> n2 n0 --FPOEReason--> n3 n0 --POEReason--> n4 n0 --POLEReason--> n5 ``` - Arrows indicate inheritance (from parent to child). - "Reasons" (`FlowPropositionErrorReason`, `PartialOrderLayerErrorReason`, etc.) are `Enum` classes. - Error subclasses: - `FlowPropositionError` - Violations of the flow-definition propositions which concern the correction function only (C0, C1, G1, G3, G4, G5, P4, P5, P6, P7, P8, P9). - Additional parameters: - `node` - `correction_set` - `FlowPropositionOrderError` - Violations of the flow-definition propositions which concern the correction function and the partial order (C2, C3, G1, G2, P1, P2, P3). - Additional parameters: - `node` - `correction_set` - `past_and_present_nodes` - `FlowGenericError` - General errors in the flow correction function, XY planes in causal flow. - Does not require additional parameters. - `PartialOrderError` - General flow and XZ-corrections errors in the partial order. - Does not require additional parameters. - `PartialOrderLayerError` - Flow and XZ-corrections errors concerning a specific layer of the partial order. - Additional parameters: - `layer_index` - `layer` --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48fb9f19c..3cba26b20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 <<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> 9fdb980 (Refactor of flow tools - Verification of flow objects (#378)) - #378: - Introduced new method `graphix.flow.core.PauliFlow.check_well_formed`, `graphix.flow.core.GFlow.check_well_formed` and `graphix.flow.core.CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. - Introduced new method `graphix.flow.core.PauliFlow.is_well_formed` which verify the correctness of flow objects and returns a boolean when the flow is incorrect. From 8f60ccbe17ab617948e89c308fa77c90a1985b62 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 13:05:03 +0100 Subject: [PATCH 12/36] Fixed typing after removing backend state. --- graphix/pattern.py | 36 ++++++++++++++++++++++++++++++++++++ graphix/sim/tensornet.py | 6 +++++- graphix/simulator.py | 11 +++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 29e37e91a..01a0ea78b 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -14,10 +14,14 @@ from enum import Enum from pathlib import Path <<<<<<< HEAD +<<<<<<< HEAD from typing import TYPE_CHECKING, Literal, SupportsFloat, overload ======= from typing import TYPE_CHECKING, SupportsFloat, overload >>>>>>> 8353f6e (trying to remove BackendState still causing problems) +======= +from typing import TYPE_CHECKING, Literal, SupportsFloat, overload +>>>>>>> fef9623 (Fixed typing after removing backend state.) import networkx as nx from typing_extensions import assert_never @@ -1344,6 +1348,9 @@ def space_list(self) -> list[int]: def simulate_pattern( self, <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> fef9623 (Fixed typing after removing backend state.) backend: StatevectorBackend | Literal["statevector"], input_state: State | Statevec @@ -1351,6 +1358,7 @@ def simulate_pattern( | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], rng: Generator | None = ..., +<<<<<<< HEAD **kwargs: Any, ) -> Statevec: ... ======= @@ -1361,11 +1369,18 @@ def simulate_pattern( ) -> Statevec: ... >>>>>>> 8353f6e (trying to remove BackendState still causing problems) +======= + **kwargs: Any, + ) -> Statevec: ... +>>>>>>> fef9623 (Fixed typing after removing backend state.) @overload def simulate_pattern( self, <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> fef9623 (Fixed typing after removing backend state.) backend: DensityMatrixBackend | Literal["densitymatrix"], input_state: State | DensityMatrix @@ -1373,6 +1388,7 @@ def simulate_pattern( | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], rng: Generator | None = ..., +<<<<<<< HEAD **kwargs: Any, ) -> DensityMatrix: ... ======= @@ -1383,17 +1399,25 @@ def simulate_pattern( ) -> DensityMatrix: ... >>>>>>> 8353f6e (trying to remove BackendState still causing problems) +======= + **kwargs: Any, + ) -> DensityMatrix: ... +>>>>>>> fef9623 (Fixed typing after removing backend state.) @overload def simulate_pattern( self, <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> fef9623 (Fixed typing after removing backend state.) backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], rng: Generator | None = ..., +<<<<<<< HEAD **kwargs: Any, ) -> MBQCTensorNet: ... ======= @@ -1404,10 +1428,15 @@ def simulate_pattern( ) -> MBQCTensorNet: ... >>>>>>> 8353f6e (trying to remove BackendState still causing problems) +======= + **kwargs: Any, + ) -> MBQCTensorNet: ... +>>>>>>> fef9623 (Fixed typing after removing backend state.) @overload def simulate_pattern( self, +<<<<<<< HEAD <<<<<<< HEAD backend: str = ..., input_state: Data = ..., @@ -1422,6 +1451,13 @@ def simulate_pattern( ) -> Statevec | DensityMatrix | MBQCTensorNet: ... >>>>>>> 8353f6e (trying to remove BackendState still causing problems) +======= + backend: str = ..., + input_state: Data = ..., + rng: Generator | None = ..., + **kwargs: Any, + ) -> Statevec | DensityMatrix | MBQCTensorNet: ... +>>>>>>> fef9623 (Fixed typing after removing backend state.) def simulate_pattern( self, diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index de0728499..bd6ac4f30 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -7,7 +7,7 @@ from abc import ABC from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, Generic, SupportsComplex, TypeAlias +from typing import TYPE_CHECKING, SupportsComplex, TypeAlias import numpy as np import numpy.typing as npt @@ -22,10 +22,14 @@ from graphix.ops import Ops from graphix.parameter import Expression <<<<<<< HEAD +<<<<<<< HEAD from graphix.sim.base_backend import Backend ======= from graphix.sim.base_backend import Backend, _StateT_co >>>>>>> 8353f6e (trying to remove BackendState still causing problems) +======= +from graphix.sim.base_backend import Backend +>>>>>>> fef9623 (Fixed typing after removing backend state.) from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: diff --git a/graphix/simulator.py b/graphix/simulator.py index 8bfaac140..6035038c6 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -266,6 +266,7 @@ def __init__( @overload @staticmethod +<<<<<<< HEAD def initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: ... @@ -286,6 +287,16 @@ def initialize_backend(pattern: Pattern, backend: str, noise_model: NoiseModel | @staticmethod def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: +======= + def initialize_backend( + pattern: Pattern, + backend: Backend[_StateT_co] | str, + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, + ) -> Backend[Any]: +>>>>>>> fef9623 (Fixed typing after removing backend state.) """ Initialize the backend. From 9b4e07866dafa955ac64bfb38af6c16584b2897a Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 13:45:11 +0100 Subject: [PATCH 13/36] trying to rebase --- graphix/pattern.py | 76 -------------------------------------------- graphix/simulator.py | 11 ------- 2 files changed, 87 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 01a0ea78b..957a82342 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -13,15 +13,7 @@ from dataclasses import dataclass from enum import Enum from pathlib import Path -<<<<<<< HEAD -<<<<<<< HEAD from typing import TYPE_CHECKING, Literal, SupportsFloat, overload -======= -from typing import TYPE_CHECKING, SupportsFloat, overload ->>>>>>> 8353f6e (trying to remove BackendState still causing problems) -======= -from typing import TYPE_CHECKING, Literal, SupportsFloat, overload ->>>>>>> fef9623 (Fixed typing after removing backend state.) import networkx as nx from typing_extensions import assert_never @@ -1347,10 +1339,6 @@ def space_list(self) -> list[int]: @overload def simulate_pattern( self, -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> fef9623 (Fixed typing after removing backend state.) backend: StatevectorBackend | Literal["statevector"], input_state: State | Statevec @@ -1358,29 +1346,12 @@ def simulate_pattern( | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], rng: Generator | None = ..., -<<<<<<< HEAD - **kwargs: Any, - ) -> Statevec: ... -======= - backend: StatevectorBackend, - input_state: State | Statevec | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], - rng: Generator | None = None, - **kwargs: Any, - ) -> Statevec: - ... ->>>>>>> 8353f6e (trying to remove BackendState still causing problems) -======= **kwargs: Any, ) -> Statevec: ... ->>>>>>> fef9623 (Fixed typing after removing backend state.) @overload def simulate_pattern( self, -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> fef9623 (Fixed typing after removing backend state.) backend: DensityMatrixBackend | Literal["densitymatrix"], input_state: State | DensityMatrix @@ -1388,76 +1359,29 @@ def simulate_pattern( | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], rng: Generator | None = ..., -<<<<<<< HEAD **kwargs: Any, ) -> DensityMatrix: ... -======= - backend: DensityMatrixBackend, - input_state: State | DensityMatrix | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], - rng: Generator | None, - **kwargs: Any, - ) -> DensityMatrix: - ... ->>>>>>> 8353f6e (trying to remove BackendState still causing problems) -======= - **kwargs: Any, - ) -> DensityMatrix: ... ->>>>>>> fef9623 (Fixed typing after removing backend state.) @overload def simulate_pattern( self, -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> fef9623 (Fixed typing after removing backend state.) backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], rng: Generator | None = ..., -<<<<<<< HEAD - **kwargs: Any, - ) -> MBQCTensorNet: ... -======= - backend: TensorNetworkBackend, - input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]], - rng: Generator | None, - **kwargs: Any, - ) -> MBQCTensorNet: - ... ->>>>>>> 8353f6e (trying to remove BackendState still causing problems) -======= **kwargs: Any, ) -> MBQCTensorNet: ... ->>>>>>> fef9623 (Fixed typing after removing backend state.) @overload def simulate_pattern( self, -<<<<<<< HEAD -<<<<<<< HEAD - backend: str = ..., - input_state: Data = ..., - rng: Generator | None = ..., - **kwargs: Any, - ) -> Statevec | DensityMatrix | MBQCTensorNet: ... -======= - backend: str, - input_state: Data, - rng: Generator | None, - **kwargs: Any, - ) -> Statevec | DensityMatrix | MBQCTensorNet: - ... ->>>>>>> 8353f6e (trying to remove BackendState still causing problems) -======= backend: str = ..., input_state: Data = ..., rng: Generator | None = ..., **kwargs: Any, ) -> Statevec | DensityMatrix | MBQCTensorNet: ... ->>>>>>> fef9623 (Fixed typing after removing backend state.) def simulate_pattern( self, diff --git a/graphix/simulator.py b/graphix/simulator.py index 6035038c6..8bfaac140 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -266,7 +266,6 @@ def __init__( @overload @staticmethod -<<<<<<< HEAD def initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: ... @@ -287,16 +286,6 @@ def initialize_backend(pattern: Pattern, backend: str, noise_model: NoiseModel | @staticmethod def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: -======= - def initialize_backend( - pattern: Pattern, - backend: Backend[_StateT_co] | str, - noise_model: NoiseModel | None, - branch_selector: BranchSelector | None, - graph_prep: str | None, - symbolic: bool, - ) -> Backend[Any]: ->>>>>>> fef9623 (Fixed typing after removing backend state.) """ Initialize the backend. From 5d726e3fad91ced4926af7e61805f3446ee82707 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 14:30:24 +0100 Subject: [PATCH 14/36] fixed rebasing and conflicts --- graphix/sim/tensornet.py | 10 +-- graphix/simulator.py | 162 +++++++++++++++++++-------------------- tests/test_opengraph.py | 107 -------------------------- 3 files changed, 80 insertions(+), 199 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index bd6ac4f30..671122321 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -7,7 +7,7 @@ from abc import ABC from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, SupportsComplex, TypeAlias +from typing import TYPE_CHECKING, Generic, SupportsComplex, TypeAlias import numpy as np import numpy.typing as npt @@ -21,15 +21,7 @@ from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.ops import Ops from graphix.parameter import Expression -<<<<<<< HEAD -<<<<<<< HEAD -from graphix.sim.base_backend import Backend -======= from graphix.sim.base_backend import Backend, _StateT_co ->>>>>>> 8353f6e (trying to remove BackendState still causing problems) -======= -from graphix.sim.base_backend import Backend ->>>>>>> fef9623 (Fixed typing after removing backend state.) from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: diff --git a/graphix/simulator.py b/graphix/simulator.py index 8bfaac140..3852f2834 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,7 +8,7 @@ import abc import warnings -from typing import TYPE_CHECKING, Generic, Literal, overload +from typing import TYPE_CHECKING, Literal, overload import numpy as np @@ -35,7 +35,6 @@ from graphix.pattern import Pattern from graphix.sim import Data from graphix.sim.base_backend import _StateT_co - from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend class PrepareMethod(abc.ABC): @@ -251,7 +250,7 @@ def __init__( :class:`graphix.sim.tensornet.TensorNetworkBackend`\ :class:`graphix.sim.density_matrix.DensityMatrixBackend`\ """ - self.backend = self.initialize_backend(pattern, backend, noise_model, branch_selector, graph_prep, symbolic) + self.backend = _initialize_backend(pattern, backend, noise_model, branch_selector, graph_prep, symbolic) self.noise_model = noise_model self.__pattern = pattern if prepare_method is None: @@ -260,86 +259,6 @@ def __init__( if measure_method is None: measure_method = DefaultMeasureMethod(pattern.results) self.__measure_method = measure_method - if prepare_method is None: - prepare_method = DefaultPrepareMethod() - self.__prepare_method = prepare_method - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> StatevectorBackend: - ... - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: DensityMatrixBackend | Literal["densitymatrix"], noise_model: NoiseModel | None, branch_selector: None, graph_prep: None, symbolic: bool) -> DensityMatrixBackend: - ... - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: TensorNetworkBackend | Literal["tensornetwork"], noise_model: None, branch_selector: BranchSelector, graph_prep: str | None, symbolic: bool) -> TensorNetworkBackend: - ... - - @overload - @staticmethod - def initialize_backend(pattern: Pattern, backend: str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: - ... - - @staticmethod - def initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> Backend[object]: - """ - Initialize the backend. - - Parameters - ---------- - backend: :class:`Backend` object, - 'statevector', or 'densitymatrix', or 'tensornetwork' - simulation backend (optional), default is 'statevector'. - noise_model: :class:`NoiseModel`, optional - [Density matrix backend only] Noise model used by the simulator. - branch_selector: :class:`BranchSelector`, optional - Branch selector used for measurements. Can only be specified if ``backend`` is not an already instantiated :class:`Backend` object. Default is :class:`RandomBranchSelector`. - graph_prep: str, optional - [Tensor network backend only] Strategy for preparing the graph state. See :class:`TensorNetworkBackend`. - symbolic : bool, optional - [State vector and density matrix backends only] If True, support arbitrary objects (typically, symbolic expressions) in measurement angles. - - Returns - ------- - :class:`Backend` - matching the appropriate backend - """ - if isinstance(backend, Backend): - if branch_selector is not None: - raise ValueError("`branch_selector` cannot be specified if `backend` is already instantiated.") - if graph_prep is not None: - raise ValueError("`graph_prep` cannot be specified if `backend` is already instantiated.") - if symbolic: - raise ValueError("`symbolic` cannot be specified if `backend` is already instantiated.") - return backend - if branch_selector is None: - branch_selector = RandomBranchSelector() - if backend in {"tensornetwork", "mps"}: - if noise_model is not None: - raise ValueError("`noise_model` cannot be specified for tensor network backend.") - if symbolic: - raise ValueError("`symbolic` cannot be specified for tensor network backend.") - if graph_prep is None: - graph_prep = "auto" - return TensorNetworkBackend(pattern, branch_selector=branch_selector, graph_prep=graph_prep) - if graph_prep is not None: - raise ValueError("`graph_prep` can only be specified for tensor network backend.") - if backend == "statevector": - if noise_model is not None: - raise ValueError("`noise_model` cannot be specified for state vector backend.") - return StatevectorBackend(branch_selector=branch_selector, symbolic=symbolic) - if backend == "densitymatrix": - if noise_model is None: - warnings.warn( - "Simulating using densitymatrix backend with no noise. To add noise to the simulation, give an object of `graphix.noise_models.Noisemodel` to `noise_model` keyword argument.", - stacklevel=1, - ) - return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) - raise ValueError(f"Unknown backend {backend}.") @property def pattern(self) -> Pattern: @@ -406,3 +325,80 @@ def run(self, input_state: Data = BasicStates.PLUS, rng: Generator | None = None else: assert_never(cmd.kind) self.backend.finalize(output_nodes=self.pattern.output_nodes) + + +@overload +def _initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: None, branch_selector: BranchSelector | None, graph_prep: None, symbolic: bool) -> StatevectorBackend: + ... + + +@overload +def _initialize_backend(pattern: Pattern, backend: DensityMatrixBackend | Literal["densitymatrix"], noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: None, symbolic: bool) -> DensityMatrixBackend: + ... + + +@overload +def _initialize_backend(pattern: Pattern, backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], noise_model: None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> TensorNetworkBackend: + ... + + +@overload +def _initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: + ... + + +def _initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: + """ + Initialize the backend. + + Parameters + ---------- + backend: :class:`Backend` object, + 'statevector', or 'densitymatrix', or 'tensornetwork' + simulation backend (optional), default is 'statevector'. + noise_model: :class:`NoiseModel`, optional + [Density matrix backend only] Noise model used by the simulator. + branch_selector: :class:`BranchSelector`, optional + Branch selector used for measurements. Can only be specified if ``backend`` is not an already instantiated :class:`Backend` object. Default is :class:`RandomBranchSelector`. + graph_prep: str, optional + [Tensor network backend only] Strategy for preparing the graph state. See :class:`TensorNetworkBackend`. + symbolic : bool, optional + [State vector and density matrix backends only] If True, support arbitrary objects (typically, symbolic expressions) in measurement angles. + + Returns + ------- + :class:`Backend` + matching the appropriate backend + """ + if isinstance(backend, (StatevectorBackend, DensityMatrixBackend, TensorNetworkBackend)): + if branch_selector is not None: + raise ValueError("`branch_selector` cannot be specified if `backend` is already instantiated.") + if graph_prep is not None: + raise ValueError("`graph_prep` cannot be specified if `backend` is already instantiated.") + if symbolic: + raise ValueError("`symbolic` cannot be specified if `backend` is already instantiated.") + return backend + if branch_selector is None: + branch_selector = RandomBranchSelector() + if backend in {"tensornetwork", "mps"}: + if noise_model is not None: + raise ValueError("`noise_model` cannot be specified for tensor network backend.") + if symbolic: + raise ValueError("`symbolic` cannot be specified for tensor network backend.") + if graph_prep is None: + graph_prep = "auto" + return TensorNetworkBackend(pattern, branch_selector=branch_selector, graph_prep=graph_prep) + if graph_prep is not None: + raise ValueError("`graph_prep` can only be specified for tensor network backend.") + if backend == "statevector": + if noise_model is not None: + raise ValueError("`noise_model` cannot be specified for state vector backend.") + return StatevectorBackend(branch_selector=branch_selector, symbolic=symbolic) + if backend == "densitymatrix": + if noise_model is None: + warnings.warn( + "Simulating using densitymatrix backend with no noise. To add noise to the simulation, give an object of `graphix.noise_models.Noisemodel` to `noise_model` keyword argument.", + stacklevel=1, + ) + return DensityMatrixBackend(branch_selector=branch_selector, symbolic=symbolic) + raise ValueError(f"Unknown backend {backend}.") diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 437dc93ee..8a91794b4 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -907,7 +907,6 @@ def test_isclose_measurement(self) -> None: assert og_1.isclose(og_2, abs_tol=0.1) assert not og_1.isclose(og_2) assert not og_2.isclose(og_3) -<<<<<<< HEAD def test_isclose_plane(self) -> None: og_1 = OpenGraph( @@ -922,90 +921,6 @@ def test_isclose_plane(self) -> None: output_nodes=[3], measurements=dict.fromkeys(range(3), Plane.XZ), ) -======= - - def test_isclose_plane(self) -> None: - og_1 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Plane.XY), - ) - og_2 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Plane.XZ), - ) - - assert not og_1.isclose(og_2) - assert og_1.isclose(og_1) - - def test_isclose_axis(self) -> None: - og_1 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Axis.X), - ) - og_2 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Axis.Y), - ) - - assert not og_1.isclose(og_2) - assert og_1.isclose(og_1) - assert og_2.isclose(og_2) - - def test_is_equal_structurally(self) -> None: - og_1 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Measurement(0.15, Plane.XY)), - ) - og_2 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Measurement(0.1, Plane.XY)), - ) - og_3 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Plane.XY), - ) - og_4 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Axis.X), - ) - og_5 = OpenGraph( - graph=nx.Graph([(0, 1), (1, 2), (2, 3), (0, 3)]), - input_nodes=[0], - output_nodes=[3], - measurements=dict.fromkeys(range(3), Axis.X), - ) - assert og_1.is_equal_structurally(og_2) - assert og_1.is_equal_structurally(og_3) - assert og_1.is_equal_structurally(og_4) - assert not og_1.is_equal_structurally(og_5) - - @pytest.mark.parametrize("test_case", OPEN_GRAPH_COMPOSE_TEST_CASES) - def test_compose(self, test_case: OpenGraphComposeTestCase) -> None: - og1, og2, og_ref, mapping = test_case - og, mapping_complete = og1.compose(og2, mapping) - assert og.isclose(og_ref) - assert mapping.keys() <= mapping_complete.keys() - assert set(mapping.values()) <= set(mapping_complete.values()) - -<<<<<<< HEAD -# TODO: rewrite as parametric tests ->>>>>>> 7cb93c6 (Refactor of flow tools - `OpenGraph.isclose` (#374)) assert not og_1.isclose(og_2) assert og_1.isclose(og_1) @@ -1092,28 +1007,6 @@ def test_compose_exception(self) -> None: og3 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XY}) og4 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XZ}) -======= - def test_compose_exception(self) -> None: - g: nx.Graph[int] = nx.Graph([(0, 1)]) - inputs = [0] - outputs = [1] - mapping = {0: 0, 1: 1} - - og1 = OpenGraph(g, inputs, outputs, measurements={0: Measurement(0, Plane.XY)}) - og2 = OpenGraph(g, inputs, outputs, measurements={0: Measurement(0.5, Plane.XY)}) - - with pytest.raises( - OpenGraphError, - match=re.escape( - "Attempted to merge nodes with different measurements: (0, Measurement(angle=0.5, plane=Plane.XY)) -> (0, Measurement(angle=0, plane=Plane.XY))." - ), - ): - og1.compose(og2, mapping) - - og3 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XY}) - og4 = OpenGraph(g, inputs, outputs, measurements={0: Plane.XZ}) - ->>>>>>> 1af27db (Refactor of flow tools - `OpenGraph.compose` (#375)) with pytest.raises( OpenGraphError, match=re.escape("Attempted to merge nodes with different measurements: (0, Plane.XZ) -> (0, Plane.XY)."), From 97c1314f0b9c92636d2a384210457c6efabe88dc Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 14:43:45 +0100 Subject: [PATCH 15/36] fixed changelog and vscode --- .vscode/settings.json | 7 ------- CHANGELOG.md | 23 +---------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9b388533a..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "tests" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true -} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cba26b20..a5a2dec5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,23 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -======= ->>>>>>> 9fdb980 (Refactor of flow tools - Verification of flow objects (#378)) - #378: - Introduced new method `graphix.flow.core.PauliFlow.check_well_formed`, `graphix.flow.core.GFlow.check_well_formed` and `graphix.flow.core.CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. - Introduced new method `graphix.flow.core.PauliFlow.is_well_formed` which verify the correctness of flow objects and returns a boolean when the flow is incorrect. - Introduced new module `graphix.flow.exceptions` grouping flow exceptions. - Introduced new methods `graphix.flow.core.PauliFlow.get_measurement_label` and `graphix.flow.core.GFlow.get_measurement_label` which return the measurement label of a given node following same criteria employed in the flow-finding algorithms. -======= ->>>>>>> 1af27db (Refactor of flow tools - `OpenGraph.compose` (#375)) -======= - ->>>>>>> ee04322 (Fix #349: ensure flow for patterns transpiled from circuit (#362)) - #374: - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. @@ -33,14 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 defaults to `DefaultPrepareMethod`) to customize how `N` commands are handled, and the class `BaseN` can be used as a base class for custom preparation commands. -<<<<<<< HEAD -======= + - #347: - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. ->>>>>>> 7cb93c6 (Refactor of flow tools - `OpenGraph.isclose` (#374)) -======= ->>>>>>> 8340e29 (Fixing typing.) ### Fixed @@ -48,18 +33,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #349, #362: Patterns transpiled from circuits always have causal flow. -<<<<<<< HEAD -<<<<<<< HEAD - #383: `Pattern.check_runnability` no longer fails on custom `BaseM` commands without domain information. -======= ->>>>>>> ee04322 (Fix #349: ensure flow for patterns transpiled from circuit (#362)) -======= - #383: `Pattern.check_runnability` no longer fails on custom `BaseM` commands without domain information. ->>>>>>> 8340e29 (Fixing typing.) ### Changed - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. From 0d70214325dee75fa038326927bc9d17c976e57f Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 14:44:52 +0100 Subject: [PATCH 16/36] final fix of changelog --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5a2dec5a..e0e89679e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,9 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #349, #362: Patterns transpiled from circuits always have causal flow. -- #383: `Pattern.check_runnability` no longer fails on custom `BaseM` - commands without domain information. - - #383: `Pattern.check_runnability` no longer fails on custom `BaseM` commands without domain information. @@ -45,10 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #375: Adapted existing method `graphix.opengraph.OpenGraph.compose` to the new API introduced in #358. -- #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. - -- #375: Adapted existing method `graphix.opengraph.OpenGraph.compose` to the new API introduced in #358. - ## [0.3.3] - 2025-10-23 ### Added From f5956495b35d11c1bbef6d01cbcda6fde3478b32 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 14:49:05 +0100 Subject: [PATCH 17/36] aligning changelog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0e89679e..ed6423ac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,10 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 handled, and the class `BaseN` can be used as a base class for custom preparation commands. -- #347: - - Introduced new method `graphix.opengraph.OpenGraph.is_equal_structurally` which compares the underlying structure of two open graphs. - - Added new method `isclose` to `graphix.fundamentals.AbstractMeasurement` which defaults to `==` comparison. - ### Fixed - #347: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. From bab5b19c96f9d0d8c3f1360815c189f416831ca4 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 16:49:46 +0100 Subject: [PATCH 18/36] Ruff fixes --- graphix/sim/tensornet.py | 4 +-- graphix/simulator.py | 53 +++++++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index 671122321..c0a261b28 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -7,7 +7,7 @@ from abc import ABC from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, Generic, SupportsComplex, TypeAlias +from typing import TYPE_CHECKING, SupportsComplex, TypeAlias import numpy as np import numpy.typing as npt @@ -21,7 +21,7 @@ from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.ops import Ops from graphix.parameter import Expression -from graphix.sim.base_backend import Backend, _StateT_co +from graphix.sim.base_backend import Backend from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: diff --git a/graphix/simulator.py b/graphix/simulator.py index 3852f2834..2969a77f2 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -328,26 +328,57 @@ def run(self, input_state: Data = BasicStates.PLUS, rng: Generator | None = None @overload -def _initialize_backend(pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], noise_model: None, branch_selector: BranchSelector | None, graph_prep: None, symbolic: bool) -> StatevectorBackend: - ... +def _initialize_backend( + pattern: Pattern, + backend: StatevectorBackend | Literal["statevector"], + noise_model: None, + branch_selector: BranchSelector | None, + graph_prep: None, + symbolic: bool, +) -> StatevectorBackend: ... @overload -def _initialize_backend(pattern: Pattern, backend: DensityMatrixBackend | Literal["densitymatrix"], noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: None, symbolic: bool) -> DensityMatrixBackend: - ... +def _initialize_backend( + pattern: Pattern, + backend: DensityMatrixBackend | Literal["densitymatrix"], + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: None, + symbolic: bool, +) -> DensityMatrixBackend: ... @overload -def _initialize_backend(pattern: Pattern, backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], noise_model: None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> TensorNetworkBackend: - ... +def _initialize_backend( + pattern: Pattern, + backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], + noise_model: None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> TensorNetworkBackend: ... @overload -def _initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: - ... - - -def _initialize_backend(pattern: Pattern, backend: Backend[_StateT_co] | str, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: +def _initialize_backend( + pattern: Pattern, + backend: Backend[_StateT_co] | str, + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: ... + + +def _initialize_backend( + pattern: Pattern, + backend: Backend[_StateT_co] | str, + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: """ Initialize the backend. From 8b04861f97eae37f0d4bcfd619bea2b9295ce258 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 10 Dec 2025 17:19:00 +0100 Subject: [PATCH 19/36] added change to CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6423ac8..7e8c78c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added + + - #378: - Introduced new method `graphix.flow.core.PauliFlow.check_well_formed`, `graphix.flow.core.GFlow.check_well_formed` and `graphix.flow.core.CausalFlow.check_well_formed` which verify the correctness of flow objects and raise exceptions when the flow is incorrect. - Introduced new method `graphix.flow.core.PauliFlow.is_well_formed` which verify the correctness of flow objects and returns a boolean when the flow is incorrect. @@ -34,6 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- #396: Removed generic `BackendState` from `graphix.sim` modules. + - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. - #375: Adapted existing method `graphix.opengraph.OpenGraph.compose` to the new API introduced in #358. From d14ae0848369da1250671b3234d14ae67941505e Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 16 Dec 2025 16:25:35 +0100 Subject: [PATCH 20/36] Fixed typing of str and Backend functions in and --- graphix/pattern.py | 32 ++++++++++++++++++++------------ graphix/sim/__init__.py | 19 ++++++++++++++++++- graphix/simulator.py | 29 +++++++++++++++-------------- tests/test_branch_selector.py | 13 +++++++------ tests/test_pattern.py | 15 +++++++-------- 5 files changed, 67 insertions(+), 41 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 220da1068..b5fc0b8a8 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -39,7 +39,16 @@ from numpy.random import Generator from graphix.parameter import ExpressionOrFloat, ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter - from graphix.sim import Backend, Data, DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend + from graphix.sim import ( + Backend, + Data, + DensityMatrix, + DensityMatrixBackend, + Statevec, + StatevectorBackend, + _BackendLiteral, + _BuiltinBackendState, + ) from graphix.sim.base_backend import _StateT_co from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend from graphix.states import State @@ -1348,12 +1357,12 @@ def space_list(self) -> list[int]: @overload def simulate_pattern( self, - backend: StatevectorBackend | Literal["statevector"], + backend: StatevectorBackend | Literal["statevector"] = "statevector", input_state: State | Statevec | Iterable[State] | Iterable[ExpressionOrSupportsComplex] - | Iterable[Iterable[ExpressionOrSupportsComplex]], + | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., rng: Generator | None = ..., **kwargs: Any, ) -> Statevec: ... @@ -1366,7 +1375,7 @@ def simulate_pattern( | DensityMatrix | Iterable[State] | Iterable[ExpressionOrSupportsComplex] - | Iterable[Iterable[ExpressionOrSupportsComplex]], + | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., rng: Generator | None = ..., **kwargs: Any, ) -> DensityMatrix: ... @@ -1378,7 +1387,7 @@ def simulate_pattern( input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] - | Iterable[Iterable[ExpressionOrSupportsComplex]], + | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., rng: Generator | None = ..., **kwargs: Any, ) -> MBQCTensorNet: ... @@ -1386,19 +1395,19 @@ def simulate_pattern( @overload def simulate_pattern( self, - backend: str = ..., + backend: Backend[_StateT_co], input_state: Data = ..., rng: Generator | None = ..., **kwargs: Any, - ) -> Statevec | DensityMatrix | MBQCTensorNet: ... + ) -> _StateT_co: ... def simulate_pattern( self, - backend: Backend[_StateT_co] | str = "statevector", + backend: Backend[_StateT_co] | _BackendLiteral = "statevector", input_state: Data = BasicStates.PLUS, rng: Generator | None = None, **kwargs: Any, - ) -> Statevec | DensityMatrix | MBQCTensorNet: + ) -> _StateT_co | _BuiltinBackendState: """Simulate the execution of the pattern by using :class:`graphix.simulator.PatternSimulator`. Available backend: ['statevector', 'densitymatrix', 'tensornetwork'] @@ -1420,10 +1429,9 @@ def simulate_pattern( .. seealso:: :class:`graphix.simulator.PatternSimulator` """ - sim = PatternSimulator(self, backend=backend, **kwargs) + sim: PatternSimulator[_StateT_co] = PatternSimulator(self, backend=backend, **kwargs) sim.run(input_state, rng=rng) - state: Statevec | DensityMatrix | MBQCTensorNet = sim.backend.state - return state + return sim.backend.state def perform_pauli_measurements(self, leave_input: bool = False, ignore_pauli_with_deps: bool = False) -> None: """Perform Pauli measurements in the pattern using efficient stabilizer simulator. diff --git a/graphix/sim/__init__.py b/graphix/sim/__init__.py index d9c88f6d3..e4e8526b9 100644 --- a/graphix/sim/__init__.py +++ b/graphix/sim/__init__.py @@ -2,9 +2,26 @@ from __future__ import annotations +from typing import Literal + from graphix.sim.base_backend import Backend from graphix.sim.data import Data from graphix.sim.density_matrix import DensityMatrix, DensityMatrixBackend from graphix.sim.statevec import Statevec, StatevectorBackend +from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend + +_BuiltinBackendState = DensityMatrix | Statevec | MBQCTensorNet +_BuiltinBackend = DensityMatrixBackend | StatevectorBackend | TensorNetworkBackend +_BackendLiteral = Literal["statevector", "densitymatrix", "tensornetwork", "mps"] -__all__ = ["Backend", "Data", "DensityMatrix", "DensityMatrixBackend", "Statevec", "StatevectorBackend"] +__all__ = [ + "Backend", + "Data", + "DensityMatrix", + "DensityMatrixBackend", + "Statevec", + "StatevectorBackend", + "_BackendLiteral", + "_BuiltinBackend", + "_BuiltinBackendState", +] diff --git a/graphix/simulator.py b/graphix/simulator.py index 2969a77f2..9f5ae46af 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,7 +8,7 @@ import abc import warnings -from typing import TYPE_CHECKING, Literal, overload +from typing import TYPE_CHECKING, Generic, Literal, overload import numpy as np @@ -22,6 +22,7 @@ from graphix.command import BaseM, CommandKind, MeasureUpdate, N from graphix.measurements import Measurement, Outcome from graphix.sim import Backend, DensityMatrixBackend, StatevectorBackend +from graphix.sim.base_backend import _StateT_co from graphix.sim.tensornet import TensorNetworkBackend from graphix.states import BasicStates @@ -33,8 +34,7 @@ from graphix.command import BaseN from graphix.noise_models.noise_model import CommandOrNoise, NoiseModel from graphix.pattern import Pattern - from graphix.sim import Data - from graphix.sim.base_backend import _StateT_co + from graphix.sim import Data, _BackendLiteral, _BuiltinBackend class PrepareMethod(abc.ABC): @@ -204,18 +204,19 @@ def set_measure_result(self, node: int, result: Outcome) -> None: self.results[node] = result -class PatternSimulator: +class PatternSimulator(Generic[_StateT_co]): """MBQC simulator. Executes the measurement pattern. """ noise_model: NoiseModel | None + backend: Backend[_StateT_co] | _BuiltinBackend def __init__( self, pattern: Pattern, - backend: Backend[_StateT_co] | str = "statevector", + backend: Backend[_StateT_co] | _BackendLiteral = "statevector", prepare_method: PrepareMethod | None = None, measure_method: MeasureMethod | None = None, noise_model: NoiseModel | None = None, @@ -331,9 +332,9 @@ def run(self, input_state: Data = BasicStates.PLUS, rng: Generator | None = None def _initialize_backend( pattern: Pattern, backend: StatevectorBackend | Literal["statevector"], - noise_model: None, + noise_model: NoiseModel | None, branch_selector: BranchSelector | None, - graph_prep: None, + graph_prep: str | None, symbolic: bool, ) -> StatevectorBackend: ... @@ -344,7 +345,7 @@ def _initialize_backend( backend: DensityMatrixBackend | Literal["densitymatrix"], noise_model: NoiseModel | None, branch_selector: BranchSelector | None, - graph_prep: None, + graph_prep: str | None, symbolic: bool, ) -> DensityMatrixBackend: ... @@ -353,7 +354,7 @@ def _initialize_backend( def _initialize_backend( pattern: Pattern, backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], - noise_model: None, + noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool, @@ -363,22 +364,22 @@ def _initialize_backend( @overload def _initialize_backend( pattern: Pattern, - backend: Backend[_StateT_co] | str, + backend: Backend[_StateT_co], noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool, -) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: ... +) -> Backend[_StateT_co]: ... def _initialize_backend( pattern: Pattern, - backend: Backend[_StateT_co] | str, + backend: Backend[_StateT_co] | _BackendLiteral, noise_model: NoiseModel | None, branch_selector: BranchSelector | None, graph_prep: str | None, symbolic: bool, -) -> StatevectorBackend | DensityMatrixBackend | TensorNetworkBackend: +) -> _BuiltinBackend | Backend[_StateT_co]: """ Initialize the backend. @@ -401,7 +402,7 @@ def _initialize_backend( :class:`Backend` matching the appropriate backend """ - if isinstance(backend, (StatevectorBackend, DensityMatrixBackend, TensorNetworkBackend)): + if isinstance(backend, Backend): if branch_selector is not None: raise ValueError("`branch_selector` cannot be specified if `backend` is already instantiated.") if graph_prep is not None: diff --git a/tests/test_branch_selector.py b/tests/test_branch_selector.py index 215c581c7..6e97060b6 100644 --- a/tests/test_branch_selector.py +++ b/tests/test_branch_selector.py @@ -20,6 +20,7 @@ from numpy.random import Generator from graphix.measurements import Outcome + from graphix.sim import _BackendLiteral NB_ROUNDS = 100 @@ -52,7 +53,7 @@ def measure(self, qubit: int, f_expectation0: Callable[[], float], rng: Generato ), ], ) -def test_expectation_value(fx_rng: Generator, backend: str) -> None: +def test_expectation_value(fx_rng: Generator, backend: _BackendLiteral) -> None: # Pattern that measures 0 on qubit 0 with probability 1. pattern = Pattern(cmds=[N(0), M(0)]) branch_selector = CheckedBranchSelector(expected={0: 1.0}) @@ -73,7 +74,7 @@ def test_expectation_value(fx_rng: Generator, backend: str) -> None: ), ], ) -def test_random_branch_selector(fx_rng: Generator, backend: str) -> None: +def test_random_branch_selector(fx_rng: Generator, backend: _BackendLiteral) -> None: branch_selector = RandomBranchSelector() pattern = Pattern(cmds=[N(0), M(0)]) for _ in range(NB_ROUNDS): @@ -91,7 +92,7 @@ def test_random_branch_selector(fx_rng: Generator, backend: str) -> None: "tensornetwork", ], ) -def test_random_branch_selector_without_pr_calc(backend: str) -> None: +def test_random_branch_selector_without_pr_calc(backend: _BackendLiteral) -> None: branch_selector = RandomBranchSelector(pr_calc=False) # Pattern that measures 0 on qubit 0 with probability > 0.999999999, to avoid numerical errors when exploring impossible branches. pattern = Pattern(cmds=[N(0), M(0, angle=1e-5)]) @@ -114,7 +115,7 @@ def test_random_branch_selector_without_pr_calc(backend: str) -> None: ], ) @pytest.mark.parametrize("outcome", itertools.product([0, 1], repeat=3)) -def test_fixed_branch_selector(backend: str, outcome: list[Outcome]) -> None: +def test_fixed_branch_selector(backend: _BackendLiteral, outcome: list[Outcome]) -> None: results1: dict[int, Outcome] = dict(enumerate(outcome[:-1])) results2: dict[int, Outcome] = {2: outcome[2]} branch_selector = FixedBranchSelector(results1, default=FixedBranchSelector(results2)) @@ -134,7 +135,7 @@ def test_fixed_branch_selector(backend: str, outcome: list[Outcome]) -> None: "tensornetwork", ], ) -def test_fixed_branch_selector_no_default(backend: str) -> None: +def test_fixed_branch_selector_no_default(backend: _BackendLiteral) -> None: results: dict[int, Outcome] = {} branch_selector = FixedBranchSelector(results) pattern = Pattern(cmds=[N(0), M(0, angle=1e-5)]) @@ -153,7 +154,7 @@ def test_fixed_branch_selector_no_default(backend: str) -> None: ], ) @pytest.mark.parametrize("outcome", [0, 1]) -def test_const_branch_selector(backend: str, outcome: Outcome) -> None: +def test_const_branch_selector(backend: _BackendLiteral, outcome: Outcome) -> None: branch_selector = ConstBranchSelector(outcome) pattern = Pattern(cmds=[N(0), M(0, angle=1e-5)]) for _ in range(NB_ROUNDS): diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 89424957b..50c90cc56 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -2,8 +2,7 @@ import copy import itertools -import typing -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import networkx as nx import numpy as np @@ -27,6 +26,8 @@ if TYPE_CHECKING: from collections.abc import Sequence + from graphix.sim import _BackendLiteral + def compare_backend_result_with_statevec(backend_state: object, statevec: Statevec) -> float: if isinstance(backend_state, Statevec): @@ -110,14 +111,12 @@ def test_minimize_space_with_gflow(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.filterwarnings("ignore:Simulating using densitymatrix backend with no noise.") @pytest.mark.parametrize("backend_type", ["statevector", "densitymatrix", "tensornetwork"]) - def test_empty_output_nodes( - self, backend_type: typing.Literal["statevector", "densitymatrix", "tensornetwork"] - ) -> None: + def test_empty_output_nodes(self, backend_type: _BackendLiteral) -> None: pattern = Pattern(input_nodes=[0]) pattern.add(M(node=0, angle=0.5)) def simulate_and_measure() -> int: - sim = PatternSimulator(pattern, backend_type) + sim: PatternSimulator[Any] = PatternSimulator(pattern, backend_type) sim.run() state = sim.backend.state if isinstance(state, Statevec): @@ -171,7 +170,7 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) @pytest.mark.parametrize("backend", ["statevector", "densitymatrix"]) # TODO: tensor network backend is excluded because "parallel preparation strategy does not support not-standardized pattern". - def test_pauli_measurement_random_circuit(self, fx_bg: PCG64, jumps: int, backend: str) -> None: + def test_pauli_measurement_random_circuit(self, fx_bg: PCG64, jumps: int, backend: _BackendLiteral) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 @@ -946,7 +945,7 @@ def test_pauli_measurement_end_with_measure(self) -> None: p.perform_pauli_measurements() @pytest.mark.parametrize("backend", ["statevector", "densitymatrix"]) - def test_arbitrary_inputs(self, fx_rng: Generator, nqb: int, rand_circ: Circuit, backend: str) -> None: + def test_arbitrary_inputs(self, fx_rng: Generator, nqb: int, rand_circ: Circuit, backend: _BackendLiteral) -> None: rand_angles = fx_rng.random(nqb) * 2 * np.pi rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] From aa533124b386e6713be97f052b217f506c36c8a7 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 16 Dec 2025 16:27:56 +0100 Subject: [PATCH 21/36] Final typing fix --- tests/test_pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 50c90cc56..cdc6d83fc 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -116,7 +116,7 @@ def test_empty_output_nodes(self, backend_type: _BackendLiteral) -> None: pattern.add(M(node=0, angle=0.5)) def simulate_and_measure() -> int: - sim: PatternSimulator[Any] = PatternSimulator(pattern, backend_type) + sim: PatternSimulator[object] = PatternSimulator(pattern, backend_type) sim.run() state = sim.backend.state if isinstance(state, Statevec): From cd26b4450bc6705dacb35eeb1861b54231a0ff15 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 16 Dec 2025 16:29:06 +0100 Subject: [PATCH 22/36] Remove Any import. --- tests/test_pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index cdc6d83fc..50a489574 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -2,7 +2,7 @@ import copy import itertools -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import networkx as nx import numpy as np From 9941608d18961a58f55ca18b212ddb8349a797d3 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 16 Dec 2025 16:58:42 +0100 Subject: [PATCH 23/36] Pushing again to check CI. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91226fe8..dda176c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- #396: Removed generic `BackendState` from `graphix.sim` modules. +- #396: Removed generic `BackendState` from `graphix.sim` modules and methods in `graphix.pattern` and `graphix.simulator` modules. - #374: Adapted existing method `graphix.opengraph.OpenGraph.isclose` to the new API introduced in #358. From 55febbe8311186c05b0c8919455e9509a2d96ef7 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 16 Dec 2025 17:13:05 +0100 Subject: [PATCH 24/36] Disabled qasm parser CI tests causing Graphix CI to fail. --- noxfile.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/noxfile.py b/noxfile.py index 16574550d..ccad346b7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -74,18 +74,18 @@ def tests_symbolic(session: Session) -> None: run_pytest(session, doctest_modules=True) -@nox.session(python=["3.10", "3.11", "3.12", "3.13"]) -def tests_qasm_parser(session: Session) -> None: - """Run the test suite of graphix-qasm-parser.""" - session.install(".") - install_pytest(session) - session.install("nox") # needed for `--doctest-modules` - # Use `session.cd` as a context manager to ensure that the - # working directory is restored afterward. This is important - # because Windows cannot delete a temporary directory while it - # is the working directory. - with TemporaryDirectory() as tmpdir, session.cd(tmpdir): - session.run("git", "clone", "https://github.com/TeamGraphix/graphix-qasm-parser") - with session.cd("graphix-qasm-parser"): - session.install(".") - run_pytest(session, doctest_modules=True) +# @nox.session(python=["3.10", "3.11", "3.12", "3.13"]) +# def tests_qasm_parser(session: Session) -> None: +# """Run the test suite of graphix-qasm-parser.""" +# session.install(".") +# install_pytest(session) +# session.install("nox") # needed for `--doctest-modules` +# # Use `session.cd` as a context manager to ensure that the +# # working directory is restored afterward. This is important +# # because Windows cannot delete a temporary directory while it +# # is the working directory. +# with TemporaryDirectory() as tmpdir, session.cd(tmpdir): +# session.run("git", "clone", "https://github.com/TeamGraphix/graphix-qasm-parser") +# with session.cd("graphix-qasm-parser"): +# session.install(".") +# run_pytest(session, doctest_modules=True) From 3de41aa256d15526bd1c3d248275755efa6ef819 Mon Sep 17 00:00:00 2001 From: emlynsg Date: Tue, 6 Jan 2026 11:28:48 +0900 Subject: [PATCH 25/36] Added qasm test back in. --- noxfile.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/noxfile.py b/noxfile.py index ccad346b7..16574550d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -74,18 +74,18 @@ def tests_symbolic(session: Session) -> None: run_pytest(session, doctest_modules=True) -# @nox.session(python=["3.10", "3.11", "3.12", "3.13"]) -# def tests_qasm_parser(session: Session) -> None: -# """Run the test suite of graphix-qasm-parser.""" -# session.install(".") -# install_pytest(session) -# session.install("nox") # needed for `--doctest-modules` -# # Use `session.cd` as a context manager to ensure that the -# # working directory is restored afterward. This is important -# # because Windows cannot delete a temporary directory while it -# # is the working directory. -# with TemporaryDirectory() as tmpdir, session.cd(tmpdir): -# session.run("git", "clone", "https://github.com/TeamGraphix/graphix-qasm-parser") -# with session.cd("graphix-qasm-parser"): -# session.install(".") -# run_pytest(session, doctest_modules=True) +@nox.session(python=["3.10", "3.11", "3.12", "3.13"]) +def tests_qasm_parser(session: Session) -> None: + """Run the test suite of graphix-qasm-parser.""" + session.install(".") + install_pytest(session) + session.install("nox") # needed for `--doctest-modules` + # Use `session.cd` as a context manager to ensure that the + # working directory is restored afterward. This is important + # because Windows cannot delete a temporary directory while it + # is the working directory. + with TemporaryDirectory() as tmpdir, session.cd(tmpdir): + session.run("git", "clone", "https://github.com/TeamGraphix/graphix-qasm-parser") + with session.cd("graphix-qasm-parser"): + session.install(".") + run_pytest(session, doctest_modules=True) From a9f4889c3583df2618fa6d4af851634ad607a6e6 Mon Sep 17 00:00:00 2001 From: emlynsg Date: Tue, 6 Jan 2026 13:45:13 +0900 Subject: [PATCH 26/36] Fixed merge from master. --- graphix/simulator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphix/simulator.py b/graphix/simulator.py index 63e6e8b24..48c25fa85 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,9 +8,8 @@ import abc import warnings -from typing import TYPE_CHECKING, Generic, Literal, overload from math import pi -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, Generic, Literal, overload # assert_never introduced in Python 3.11 # override introduced in Python 3.12 From 90d0185cc3e4537f5ec936148f14ff2fbd8394ac Mon Sep 17 00:00:00 2001 From: emlynsg Date: Tue, 6 Jan 2026 13:50:17 +0900 Subject: [PATCH 27/36] Fixed merge from master. --- graphix/sim/base_backend.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index f2683f966..23855daa1 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -29,6 +29,7 @@ from numpy.random import Generator from graphix import command + from graphix.fundamentals import Plane from graphix.measurements import Measurement, Outcome from graphix.noise_models.noise_model import Noise from graphix.parameter import ExpressionOrComplex, ExpressionOrFloat From 2bd504293015a3fc6b100b55ba27fc8f9feb82ff Mon Sep 17 00:00:00 2001 From: emlynsg Date: Wed, 7 Jan 2026 12:20:04 +0900 Subject: [PATCH 28/36] fixing added code --- graphix/sim/base_backend.py | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index 23855daa1..29c5bf846 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -505,39 +505,6 @@ def _op_mat_from_result( return op_mat_complex -def perform_measure( - qubit_node: int, - qubit_loc: int, - plane: Plane, - angle: ExpressionOrFloat, - state: DenseState, - branch_selector: BranchSelector, - rng: Generator | None = None, - symbolic: bool = False, -) -> Outcome: - """Perform measurement of a qubit.""" - vec = plane.polar(angle) - # op_mat0 may contain the matrix operator associated with the outcome 0, - # but the value is computed lazily, i.e., only if needed. - op_mat0 = None - - def get_op_mat0() -> Matrix: - nonlocal op_mat0 - if op_mat0 is None: - op_mat0 = _op_mat_from_result(vec, 0, symbolic=symbolic) - return op_mat0 - - def f_expectation0() -> float: - exp_val = state.expectation_single(get_op_mat0(), qubit_loc) - assert math.isclose(exp_val.imag, 0, abs_tol=1e-10) - return exp_val.real - - result = branch_selector.measure(qubit_node, f_expectation0, rng) - op_mat = _op_mat_from_result(vec, 1, symbolic=symbolic) if result else get_op_mat0() - state.evolve_single(op_mat, qubit_loc) - return result - - @dataclass(frozen=True) class Backend(Generic[_StateT_co]): """ From d98c98d8d7ff0af1f8a8e8e2871f17a179214c5a Mon Sep 17 00:00:00 2001 From: emlynsg Date: Wed, 7 Jan 2026 15:36:45 +0900 Subject: [PATCH 29/36] fixing CI --- graphix/sim/base_backend.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index 29c5bf846..e6c55d595 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -29,7 +29,6 @@ from numpy.random import Generator from graphix import command - from graphix.fundamentals import Plane from graphix.measurements import Measurement, Outcome from graphix.noise_models.noise_model import Noise from graphix.parameter import ExpressionOrComplex, ExpressionOrFloat From 1a6f15c714f9860d500c51ddde02c2d3e8868cc4 Mon Sep 17 00:00:00 2001 From: emlynsg Date: Wed, 7 Jan 2026 15:42:18 +0900 Subject: [PATCH 30/36] fixing imports --- graphix/pattern.py | 3 ++- graphix/simulator.py | 1 - tests/test_pattern.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 04414078d..756a19dae 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -38,7 +38,8 @@ from numpy.random import Generator - from graphix.parameter import ExpressionOrFloat, ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter + from graphix.flow.core import CausalFlow, GFlow + from graphix.parameter import ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter from graphix.sim import ( Backend, Data, diff --git a/graphix/simulator.py b/graphix/simulator.py index e25371237..7874a260b 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -8,7 +8,6 @@ import abc import warnings -from math import pi from typing import TYPE_CHECKING, Generic, Literal, overload # assert_never introduced in Python 3.11 diff --git a/tests/test_pattern.py b/tests/test_pattern.py index da262d2b1..099ecb081 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -2,7 +2,6 @@ import copy import itertools -import typing from typing import TYPE_CHECKING, NamedTuple import networkx as nx @@ -16,7 +15,7 @@ from graphix.flow.exceptions import ( FlowError, ) -from graphix.fundamentals import ANGLE_PI, Plane +from graphix.fundamentals import ANGLE_PI, Angle, Plane from graphix.measurements import Measurement, Outcome, PauliMeasurement from graphix.opengraph import OpenGraph from graphix.pattern import Pattern, RunnabilityError, RunnabilityErrorReason, shift_outcomes From 982db24e09c14b0895e04b9b8e29b981e1667844 Mon Sep 17 00:00:00 2001 From: emlynsg Date: Wed, 7 Jan 2026 15:55:24 +0900 Subject: [PATCH 31/36] fixing typing in tests --- tests/test_pattern.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 099ecb081..3a49409b0 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -2,7 +2,7 @@ import copy import itertools -from typing import TYPE_CHECKING, NamedTuple +from typing import TYPE_CHECKING, Literal, NamedTuple import networkx as nx import numpy as np @@ -1216,7 +1216,7 @@ def test_pauli_measurement_end_with_measure(self) -> None: p.perform_pauli_measurements() @pytest.mark.parametrize("backend", ["statevector", "densitymatrix"]) - def test_arbitrary_inputs(self, fx_rng: Generator, nqb: int, rand_circ: Circuit, backend: str) -> None: + def test_arbitrary_inputs(self, fx_rng: Generator, nqb: int, rand_circ: Circuit, backend: _BackendLiteral) -> None: rand_angles = fx_rng.random(nqb) * 2 * ANGLE_PI rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] From 4118f5d95bd951798b32ebf1a58dcb2b53c0164b Mon Sep 17 00:00:00 2001 From: emlynsg Date: Wed, 7 Jan 2026 15:56:43 +0900 Subject: [PATCH 32/36] fixing typing in tests --- tests/test_pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 3a49409b0..74468f255 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -2,7 +2,7 @@ import copy import itertools -from typing import TYPE_CHECKING, Literal, NamedTuple +from typing import TYPE_CHECKING, NamedTuple import networkx as nx import numpy as np From 24ee5327c8fa34be98ecd3437d09bd4c679a7982 Mon Sep 17 00:00:00 2001 From: emlynsg Date: Thu, 8 Jan 2026 11:39:32 +0900 Subject: [PATCH 33/36] fixed test typing --- tests/test_pattern.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 74468f255..07e235766 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -33,7 +33,9 @@ from graphix.sim import _BackendLiteral -def compare_backend_result_with_statevec(backend_state: object, statevec: Statevec) -> float: +def compare_backend_result_with_statevec( + backend_state: Statevec | DensityMatrix | MBQCTensorNet, statevec: Statevec +) -> float: if isinstance(backend_state, Statevec): return float(np.abs(np.dot(backend_state.flatten().conjugate(), statevec.flatten()))) if isinstance(backend_state, DensityMatrix): @@ -122,7 +124,7 @@ def test_empty_output_nodes(self, backend_type: _BackendLiteral) -> None: pattern.add(M(node=0, angle=0.5)) def simulate_and_measure() -> int: - sim: PatternSimulator[object] = PatternSimulator(pattern, backend_type) + sim: PatternSimulator[Statevec | DensityMatrix | MBQCTensorNet] = PatternSimulator(pattern, backend_type) sim.run() state = sim.backend.state if isinstance(state, Statevec): From 89fc88d876fb10a853097842c4f08f03e02bbc4e Mon Sep 17 00:00:00 2001 From: emlynsg Date: Thu, 8 Jan 2026 11:57:08 +0900 Subject: [PATCH 34/36] fixed test typing --- tests/test_pattern.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 07e235766..7e6b528c9 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -33,9 +33,7 @@ from graphix.sim import _BackendLiteral -def compare_backend_result_with_statevec( - backend_state: Statevec | DensityMatrix | MBQCTensorNet, statevec: Statevec -) -> float: +def compare_backend_result_with_statevec(backend_state: Statevec | DensityMatrix, statevec: Statevec) -> float: if isinstance(backend_state, Statevec): return float(np.abs(np.dot(backend_state.flatten().conjugate(), statevec.flatten()))) if isinstance(backend_state, DensityMatrix): From 2f46277eaf02b2d8eedc1dc91cd04455fdd4f6bf Mon Sep 17 00:00:00 2001 From: emlynsg Date: Thu, 8 Jan 2026 12:01:10 +0900 Subject: [PATCH 35/36] fixed test typing --- tests/test_pattern.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 7e6b528c9..13b3c9d58 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -188,7 +188,7 @@ def test_pauli_measurement_random_circuit(self, fx_bg: PCG64, jumps: int, backen pattern.perform_pauli_measurements() pattern.minimize_space() state = circuit.simulate_statevector().statevec - state_mbqc = pattern.simulate_pattern(backend, rng=rng) + state_mbqc: Statevec | DensityMatrix = pattern.simulate_pattern(backend, rng=rng) assert compare_backend_result_with_statevec(state_mbqc, state) == pytest.approx(1) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -1221,7 +1221,7 @@ def test_arbitrary_inputs(self, fx_rng: Generator, nqb: int, rand_circ: Circuit, rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] randpattern = rand_circ.transpile().pattern - out = randpattern.simulate_pattern(backend=backend, input_state=states, rng=fx_rng) + out: Statevec | DensityMatrix = randpattern.simulate_pattern(backend=backend, input_state=states, rng=fx_rng) out_circ = rand_circ.simulate_statevector(input_state=states).statevec assert compare_backend_result_with_statevec(out, out_circ) == pytest.approx(1) From 2b1feebf2324e7b775771496bb70fb26de5dc3d2 Mon Sep 17 00:00:00 2001 From: emlynsg Date: Thu, 8 Jan 2026 12:17:08 +0900 Subject: [PATCH 36/36] fixed test typing --- tests/test_pattern.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 13b3c9d58..ce47409ad 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -2,7 +2,7 @@ import copy import itertools -from typing import TYPE_CHECKING, NamedTuple +from typing import TYPE_CHECKING, Literal, NamedTuple import networkx as nx import numpy as np @@ -176,7 +176,9 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: @pytest.mark.parametrize("jumps", range(1, 11)) @pytest.mark.parametrize("backend", ["statevector", "densitymatrix"]) # TODO: tensor network backend is excluded because "parallel preparation strategy does not support not-standardized pattern". - def test_pauli_measurement_random_circuit(self, fx_bg: PCG64, jumps: int, backend: _BackendLiteral) -> None: + def test_pauli_measurement_random_circuit( + self, fx_bg: PCG64, jumps: int, backend: Literal["statevector", "densitymatrix"] + ) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 depth = 3 @@ -1216,7 +1218,9 @@ def test_pauli_measurement_end_with_measure(self) -> None: p.perform_pauli_measurements() @pytest.mark.parametrize("backend", ["statevector", "densitymatrix"]) - def test_arbitrary_inputs(self, fx_rng: Generator, nqb: int, rand_circ: Circuit, backend: _BackendLiteral) -> None: + def test_arbitrary_inputs( + self, fx_rng: Generator, nqb: int, rand_circ: Circuit, backend: Literal["statevector", "densitymatrix"] + ) -> None: rand_angles = fx_rng.random(nqb) * 2 * ANGLE_PI rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)]