diff --git a/CHANGELOG.md b/CHANGELOG.md index a588bae2f..3288604de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- #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. - #375: Adapted existing method `graphix.opengraph.OpenGraph.compose` to the new API introduced in #358. diff --git a/graphix/pattern.py b/graphix/pattern.py index 976f26fd9..756a19dae 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -14,7 +14,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, Literal, SupportsFloat, overload import networkx as nx from typing_extensions import assert_never @@ -39,11 +39,20 @@ from numpy.random import Generator from graphix.flow.core import CausalFlow, GFlow - from graphix.parameter import ExpressionOrSupportsFloat, Parameter - from graphix.sim import Backend, BackendState, Data - - -_StateT_co = TypeVar("_StateT_co", bound="BackendState", covariant=True) + from graphix.parameter import ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter + 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 class Pattern: @@ -1375,13 +1384,60 @@ def space_list(self) -> list[int]: n_list.append(nodes) return n_list + @overload + def simulate_pattern( + self, + backend: StatevectorBackend | Literal["statevector"] = "statevector", + input_state: State + | Statevec + | Iterable[State] + | Iterable[ExpressionOrSupportsComplex] + | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., + rng: Generator | None = ..., + **kwargs: Any, + ) -> Statevec: ... + + @overload + def simulate_pattern( + self, + backend: DensityMatrixBackend | Literal["densitymatrix"], + input_state: State + | DensityMatrix + | Iterable[State] + | Iterable[ExpressionOrSupportsComplex] + | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., + rng: Generator | None = ..., + **kwargs: Any, + ) -> DensityMatrix: ... + + @overload + def simulate_pattern( + self, + backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], + input_state: State + | Iterable[State] + | Iterable[ExpressionOrSupportsComplex] + | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., + rng: Generator | None = ..., + **kwargs: Any, + ) -> MBQCTensorNet: ... + + @overload + def simulate_pattern( + self, + backend: Backend[_StateT_co], + input_state: Data = ..., + rng: Generator | None = ..., + **kwargs: Any, + ) -> _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, - ) -> BackendState: + ) -> _StateT_co | _BuiltinBackendState: """Simulate the execution of the pattern by using :class:`graphix.simulator.PatternSimulator`. Available backend: ['statevector', 'densitymatrix', 'tensornetwork'] @@ -1403,7 +1459,7 @@ 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) return sim.backend.state diff --git a/graphix/sim/__init__.py b/graphix/sim/__init__.py index e54f62cab..e4e8526b9 100644 --- a/graphix/sim/__init__.py +++ b/graphix/sim/__init__.py @@ -2,9 +2,26 @@ from __future__ import annotations -from graphix.sim.base_backend import Backend, BackendState +from typing import Literal + +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 +from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend + +_BuiltinBackendState = DensityMatrix | Statevec | MBQCTensorNet +_BuiltinBackend = DensityMatrixBackend | StatevectorBackend | TensorNetworkBackend +_BackendLiteral = Literal["statevector", "densitymatrix", "tensornetwork", "mps"] -__all__ = ["Backend", "BackendState", "Data", "DensityMatrix", "Statevec"] +__all__ = [ + "Backend", + "Data", + "DensityMatrix", + "DensityMatrixBackend", + "Statevec", + "StatevectorBackend", + "_BackendLiteral", + "_BuiltinBackend", + "_BuiltinBackendState", +] diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index 4fec736d1..d1c33c02c 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -37,6 +37,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`. @@ -325,37 +327,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. @@ -373,7 +345,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. @@ -388,6 +360,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: """ @@ -527,9 +503,6 @@ def _op_mat_from_result( return op_mat_complex -_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 409954ed4..aea313569 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -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 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 604ab01ec..7874a260b 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, Literal, overload # assert_never introduced in Python 3.11 # override introduced in Python 3.12 @@ -19,9 +19,8 @@ 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.base_backend import _StateT_co from graphix.sim.tensornet import TensorNetworkBackend from graphix.states import BasicStates @@ -33,10 +32,7 @@ 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 Data, _BackendLiteral, _BuiltinBackend class PrepareMethod(abc.ABC): @@ -205,18 +201,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[BackendState] | str = "statevector", + backend: Backend[_StateT_co] | _BackendLiteral = "statevector", prepare_method: PrepareMethod | None = None, measure_method: MeasureMethod | None = None, noise_model: NoiseModel | None = None, @@ -251,43 +248,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 = _initialize_backend(pattern, backend, noise_model, branch_selector, graph_prep, symbolic) self.noise_model = noise_model self.__pattern = pattern if prepare_method is None: @@ -362,3 +323,111 @@ 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: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> StatevectorBackend: ... + + +@overload +def _initialize_backend( + pattern: Pattern, + backend: DensityMatrixBackend | Literal["densitymatrix"], + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> DensityMatrixBackend: ... + + +@overload +def _initialize_backend( + pattern: Pattern, + backend: TensorNetworkBackend | Literal["tensornetwork", "mps"], + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> TensorNetworkBackend: ... + + +@overload +def _initialize_backend( + pattern: Pattern, + backend: Backend[_StateT_co], + 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] | _BackendLiteral, + noise_model: NoiseModel | None, + branch_selector: BranchSelector | None, + graph_prep: str | None, + symbolic: bool, +) -> _BuiltinBackend | 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}.") diff --git a/tests/test_branch_selector.py b/tests/test_branch_selector.py index 883d0d67e..1f3dc6782 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 @@ -54,7 +55,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}) @@ -75,7 +76,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): @@ -93,7 +94,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)]) @@ -116,7 +117,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)) @@ -136,7 +137,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)]) @@ -155,7 +156,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 0b282cac9..ce47409ad 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, NamedTuple +from typing import TYPE_CHECKING, Literal, NamedTuple import networkx as nx import numpy as np @@ -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 @@ -31,11 +30,10 @@ if TYPE_CHECKING: from collections.abc import Sequence - from graphix.fundamentals import Angle - from graphix.sim.base_backend import BackendState + from graphix.sim import _BackendLiteral -def compare_backend_result_with_statevec(backend_state: BackendState, 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): @@ -119,14 +117,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[Statevec | DensityMatrix | MBQCTensorNet] = PatternSimulator(pattern, backend_type) sim.run() state = sim.backend.state if isinstance(state, Statevec): @@ -180,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: str) -> 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 @@ -192,7 +190,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)) @@ -1220,12 +1218,14 @@ 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: 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)] 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)