graphix.sim package

Submodules

Module contents

Simulation backends.

class Backend[source]

Bases: Generic[_StateT_co]

Abstract base class for all quantum backends.

A backend is responsible for managing a quantum system, including the set of active qubits (nodes), their initialization, evolution, and measurement. It defines the interface through which high-level quantum programs interact with the underlying simulation or hardware model.

Concrete subclasses implement specific state representations and simulation strategies, such as dense state vectors, density matrices, or tensor networks.

Responsibilities of a backend typically include: - Managing a dynamic set of qubits (nodes) and their state - Applying quantum gates or operations - Performing measurements and returning classical outcomes - Tracking and exposing the underlying quantum state

Examples of concrete subclasses include: - StatevectorBackend (pure states via state vectors) - DensityMatrixBackend (mixed states via density matrices) - TensorNetworkBackend (compressed states via tensor networks)

Parameters:

state (BackendState) – internal state of the backend: instance of Statevector, DensityMatrix, or MBQCTensorNet.

See also

BackendState, :class:`DenseStateBackend, StatevectorBackend, DensityMatrixBackend, TensorNetworkBackend

Notes

This class is abstract and should not be instantiated directly.

The class hierarchy of states mirrors the class hierarchy of backends: - DenseStateBackend and TensorNetworkBackend are subclasses of Backend,

and DenseState and MBQCTensorNet are subclasses of BackendState.

The type variable _StateT_co specifies the type of the state field, so that subclasses provide a precise type for this field: - StatevectorBackend is a subtype of Backend[Statevector]. - DensityMatrixBackend is a subtype of Backend[DensityMatrix]. - TensorNetworkBackend is a subtype of Backend[MBQCTensorNet].

The type variables _StateT_co and _DenseStateT_co are declared as covariant. That is, Backend[T1] is a subtype of Backend[T2] if T1 is a subtype of T2. This means that StatevectorBackend, DensityMatrixBackend, and TensorNetworkBackend are all subtypes of Backend[BackendState]. This covariance is sound because backends are frozen dataclasses; thus, the type of state cannot be changed after instantiation.

The interface expected from a backend includes the following methods: - add_nodes: which executes N commands. - apply_channel: used for noisy simulations.

The class Backend provides a default implementation that raises NoiseNotSupportedError, indicating that the backend does not support noise. Backends that support noise (e.g., DensityMatrixBackend) override this method to implement the effect of noise.

state: _StateT_co
abstract property nqubit: int

Return the number of qubits.

abstractmethod add_nodes(nodes: Sequence[int], data: Data = graphix.states.PlanarState(Plane.XY, 0)) → None[source]

Add new nodes (qubits) to the backend and initialize them in a specified state.

Parameters:
  • nodes (Sequence[int]) – A list of node indices to add to the backend. These indices can be any integer values but must be fresh: each index must be distinct from all previously added nodes.

  • data (Data, optional) –

    The state in which to initialize the newly added nodes. The supported forms of state specification depend on the backend implementation.

    All backends must support the basic predefined states in BasicStates.

    • If a single basic state is provided, all new nodes are initialized in that state.

    • If a list of basic states is provided, it must match the length of nodes, and each node is initialized with its corresponding state.

    Some backends support other forms of state specification.

    • StatevectorBackend supports arbitrary state vectors:
      • A single-qubit state vector will be broadcast to all nodes.

      • A multi-qubit state vector of dimension \(2^n\), where \(n = \mathrm{len}(nodes)\), initializes the new nodes jointly.

    • DensityMatrixBackend supports both state vectors and density matrices:
      • State vectors are handled as in StatevectorBackend, and converted to density matrices.

      • A density matrix must have shape \(2^n \times 2^n\), where \(n = \mathrm{len}(nodes)\), and is used to jointly initialize the new nodes.

Notes

Previously existing nodes remain unchanged.

apply_noise(cmd: ApplyNoise) → None[source]

Apply noise.

The default implementation of this method raises NoiseNotSupportedError, indicating that the backend does not support noise. Backends that support noise (e.g., DensityMatrixBackend) override this method to implement the effect of noise.

Note: the simulator is responsible for checking that the measurement outcomes match the domain condition before calling this method.

Parameters:
  • nodes (sequence of ints.) – Target qubits

  • noise (Noise) – Noise to apply

abstractmethod apply_clifford(node: int, clifford: Clifford) → None[source]

Apply single-qubit Clifford gate, specified by vop index specified in graphix.clifford.CLIFFORD.

abstractmethod correct_byproduct(cmd: command.X | command.Z) → None[source]

Byproduct correction correct for the X or Z byproduct operators, by applying the X or Z gate.

Note: the simulator is responsible for checking that the measurement outcomes match the domain condition before calling this method.

abstractmethod entangle_nodes(edge: tuple[int, int]) → None[source]

Apply CZ gate to two connected nodes.

Parameters:

edge (tuple (i, j)) – a pair of node indices

abstractmethod finalize(output_nodes: Sequence[int]) → None[source]

To be run at the end of pattern simulation to convey the order of output nodes.

abstractmethod measure(node: int, measurement: Measurement, rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Perform measurement of a node and trace out the qubit.

Parameters:
  • node (int)

  • measurement (Measurement)

  • rng (Generator, optional) – Random-number generator for measurements. This generator is used only in case of random branch selection (see RandomBranchSelector).

__init__() → None
class DensityMatrix(data: Data = graphix.states.PlanarState(Plane.XY, 0), nqubit: int | None = None)[source]

Bases: DenseState, InplaceParameterizable

DensityMatrix object.

__init__(data: Data = graphix.states.PlanarState(Plane.XY, 0), nqubit: int | None = None) → None[source]

Initialize density matrix objects.

The behaviour builds on the one of graphix.statevec.Statevector. data can be: - a single graphix.states.State (classical description of a quantum state) - an iterable of graphix.states.State objects - an iterable of iterable of scalars (A 2**n x 2**n numerical density matrix) - a graphix.statevec.DensityMatrix object - a graphix.statevec.Statevector object

If nqubit is not provided, the number of qubit is inferred from data and checked for consistency. If only one graphix.states.State is provided and nqubit is a valid integer, initialize the statevector in the tensor product state. If both nqubit and data are provided, consistency of the dimensions is checked. If a graphix.statevec.Statevector or graphix.statevec.DensityMatrix is passed, returns a copy.

Parameters:
  • data (Data) – input data to prepare the state. Can be a classical description or a numerical input, defaults to graphix.states.BasicStates.PLUS

  • nqubit (int, optional) – number of qubits to prepare, defaults to None

rho: NDArray[object_ | complex128]
property nqubit: int

Return the number of qubits.

draw(output: OutputFormat = OutputFormat.Unicode, *, max_denominator: int = 1000, atol: float = 1e-09, rtol: float = 0.0, precision: int = 4) → str[source]

Return a pretty-printed matrix representation of the density matrix.

Each entry is rendered with graphix.pretty_print.complex_to_str(), so common values appear as exact expressions (e.g. 1/2) rather than floating-point numbers.

Parameters:
  • output (OutputFormat, optional) – Desired formatting style. Defaults to OutputFormat.Unicode.

  • max_denominator (int, optional) – Maximum denominator used by the entry recognition (default: 1000).

  • atol (float, optional) – Absolute tolerance for the recognition heuristics (default: 1e-9).

  • rtol (float, optional) – Relative tolerance for the recognition heuristics (default: 0.0).

  • precision (int, optional) – Number of significant digits to use for entries that fall back to a decimal representation (default: 4).

Returns:

The formatted density matrix.

Return type:

str

add_nodes(nqubit: int, data: Data) → None[source]

Add nodes (qubits) to the density matrix and initialize them in a specified state.

Parameters:
  • nqubit (int) – The number of qubits to add to the density matrix.

  • data (Data, optional) –

    The state in which to initialize the newly added nodes.

    • If a single basic state is provided, all new nodes are initialized in that state.

    • If a list of basic states is provided, it must match the length of nodes, and each node is initialized with its corresponding state.

    • A single-qubit state vector will be broadcast to all nodes.

    • A multi-qubit state vector of dimension \(2^n\) initializes the new nodes jointly.

    • A density matrix must have shape \(2^n \times 2^n\), and is used to jointly initialize the new nodes.

    • The type of nodes to be added is inferred from the type of the existing DensityMatrix.

Notes

Previously existing nodes remain unchanged.

evolve_single(op: NDArray[object_ | complex128], qubit: int) → None[source]

Single-qubit operation.

Parameters:
  • op (np.ndarray) – 2*2 matrix.

  • qubit (int) – Index of qubit to apply operator.

evolve(op: Matrix, qubits: Sequence[int]) → None[source]

Multi-qubit operation.

Parameters:
  • op (np.array) – 2^n*2^n matrix

  • qubits (list of ints) – target qubits’ indexes

expectation_single(op: NDArray[object_ | complex128], qubit: int) → complex[source]

Return the expectation value of single-qubit operator.

Parameters:
  • op (np.array) – 2*2 Hermite operator

  • qubit (int) – Index of qubit on which to apply operator.

Returns:

complex

Return type:

expectation value (real for hermitian ops!).

dims() → tuple[int, ...][source]

Return the dimensions of the density matrix.

tensor(other: DensityMatrix) → None[source]

Tensor product state with other density matrix.

Results in self \(\otimes\) other.

Parameters:

other – DensityMatrix object to be tensored with self.

cnot(edge: tuple[int, int]) → None[source]

Apply CNOT gate to density matrix.

Parameters:

edge ((int, int) or [int, int]) – Edge to apply CNOT gate.

swap(qubits: tuple[int, int]) → None[source]

Swap qubits.

Parameters:

qubits ((int, int)) – (control, target) qubits indices.

permute(permutation: Sequence[int]) → None[source]

Reorder the qubits.

Parameters:

permutation (Sequence[int]) – The permutation to apply. For each position in the resulting order, the value gives the index of the qubit in the original ordering.

entangle(qubits: tuple[int, int]) → None[source]

Connect graph nodes.

Parameters:

qubits ((int, int) or [int, int]) – (control, target) qubit indices.

normalize() → None[source]

Normalize density matrix.

remove_qubit(qubit: int) → None[source]

Remove a qubit.

ptrace(qargs: Collection[int] | int) → None[source]

Partial trace.

Parameters:

qargs (list of ints or int) – Indices of qubit to trace out.

fidelity(statevec: Statevector) → Expression | float[source]

Calculate the fidelity against reference statevector.

Parameters:

statevec (numpy array) – statevector (flattened numpy array) to compare with

flatten() → NDArray[object_ | complex128][source]

Return flattened density matrix.

apply_channel(channel: KrausChannel, qargs: Sequence[int]) → None[source]

Apply a channel to a density matrix.

:param : :type : rho: density matrix. :param channel: KrausChannel to be applied to the density matrix :type channel: graphix.channel.KrausChannel object :param qargs: :type qargs: target qubit indices

Return type:

nothing

Raises:
  • ValueError – If the final density matrix is not normalized after application of the channel. This shouldn’t happen since graphix.channel.KrausChannel objects are normalized by construction.

  • .... –

apply_noise(qubits: Sequence[int], noise: Noise) → None[source]

Apply noise.

Parameters:
  • qubits (sequence of ints.) – Target qubits

  • noise (Noise) – Noise to apply

replace_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat, *, copy: bool = False) → DensityMatrix[source]

Substitute all occurrences of the given variable in measurement angles by the given value.

Parameters:
  • variable (Parameter) – The symbolic expression to be replaced within the measurement angles.

  • substitute (ExpressionOrSupportsFloat) – The value or symbolic expression to substitute in place of variable.

  • copy (bool, optional) – If True, the current object remains unchanged and a new object is returned. The default is False, meaning that changes are performed in place.

Returns:

The object with the updated measurement parameters. Equal to self if copy is False.

Return type:

Self

replace_parameters(assignment: Mapping[Parameter, ExpressionOrSupportsFloat], *, copy: bool = False) → DensityMatrix[source]

Substitute all occurrences of the given keys in measurement angles by the given values in parallel.

Parameters:
  • assignment (Mapping[Parameter, ExpressionOrSupportsFloat]) – A dictionary-like mapping where keys are the Parameter objects to be replaced and values are the new expressions or numerical values.

  • copy (bool, optional) – If True, the current object remains unchanged and a new object is returned. The default is False, meaning that changes are performed in place.

Returns:

The object with the updated measurement parameters. Equal to self if copy is False.

Return type:

Self

class DensityMatrixBackend(*, node_index: NodeIndex = <factory>, branch_selector: BranchSelector = <factory>, symbolic: bool = False)[source]

Bases: DenseStateBackend[DensityMatrix]

MBQC simulator with density matrix method.

__init__(*, node_index: NodeIndex = <factory>, branch_selector: BranchSelector = <factory>, symbolic: bool = False) → None
state: DensityMatrix
class MBQCTensorNet(branch_selector: BranchSelector, graph_nodes: Iterable[int] | None = None, graph_edges: Iterable[tuple[int, int]] | None = None, default_output_nodes: Iterable[int] | None = None, ts: list[TensorNetwork] | TensorNetwork | None = None, virtual: bool = False)[source]

Bases: TensorNetwork

Tensor Network Simulator interface for MBQC patterns, using quimb.tensor.core.TensorNetwork.

__init__(branch_selector: BranchSelector, graph_nodes: Iterable[int] | None = None, graph_edges: Iterable[tuple[int, int]] | None = None, default_output_nodes: Iterable[int] | None = None, ts: list[TensorNetwork] | TensorNetwork | None = None, virtual: bool = False) → None[source]

Initialize MBQCTensorNet.

Parameters:
  • (optional) (ts) – node indices of the graph state.

  • (optional) – edge indices of the graph state.

  • default_output_nodes (list of int) – output node indices at the end of MBQC operations, if known in advance.

  • (optional) – optional initial state.

open_tensor(index: int | str) → NDArray[complex128][source]

Get tensor specified by node index. The tensor has a dangling edge.

Parameters:

index (str) – node index

Returns:

Specified tensor

Return type:

numpy.ndarray

add_qubit(index: int, state: str | NDArray[complex128] = 'plus') → None[source]

Add a single qubit to the network.

Parameters:
  • index (int) – index of the new qubit.

  • (optional) (state) – initial state of the new qubit. “plus”, “minus”, “zero”, “one”, “iplus”, “iminus”, or 1*2 np.ndarray (arbitrary state).

evolve_single(index: int, arr: NDArray[complex128], label: str = 'U') → None[source]

Apply single-qubit operator to a qubit with the given index.

Parameters:
  • index (int) – qubit index.

  • arr (2*2 numpy.ndarray) – single-qubit operator.

  • (optional) (label) – label for the gate.

add_qubits(indices: Sequence[int], states: PrepareState | Iterable[PrepareState] = 'plus') → None[source]

Add qubits to the network.

Parameters:
  • indices (iterator of int) – indices of the new qubits.

  • (optional) (states) – initial state or list of initial states of the new qubits.

measure_single(index: int, basis: str | npt.NDArray[np.complex128] = 'Z', bypass_probability_calculation: bool = True, outcome: Outcome | None = None, rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Measure a node in specified basis. Note this does not perform the partial trace.

Parameters:
  • index (int) – index of the node to be measured.

  • basis (str or np.ndarray) – default “Z”. measurement basis, “Z” or “X” or “Y” for Pauli basis measurements. 1*2 numpy.ndarray for arbitrary measurement bases.

  • bypass_probability_calculation (bool) – default True. if True, skip the calculation of the probability of the measurement result and use equal probability for each result. if False, calculate the probability of the measurement result from the state.

  • outcome (int (0 or 1)) – User-chosen measurement result, giving the outcome of (-1)^{outcome}.

Returns:

measurement result.

Return type:

int

prepare_graph_state(nodes: Iterable[int], edges: Iterable[tuple[int, int]]) → None[source]

Prepare the graph state without directly applying CZ gates.

Parameters:
  • nodes (iterator of int) – set of the nodes

  • edges (iterator of tuple) – set of the edges

  • seealso: (..) – __init__():

basis_coefficient(basis: int | str, normalize: bool = True, indices: Sequence[int] | None = None) → complex[source]

Calculate the coefficient of a given computational basis.

Parameters:
  • basis (int or str) – computational basis expressed in binary (str) or integer, e.g. 101 or 5.

  • (optional) (indices) – if True, normalize the coefficient by the norm of the entire state.

  • (optional) – target qubit indices to compute the coefficients, default is the MBQC output nodes (self.default_output_nodes).

Returns:

coef – coefficient

Return type:

complex

basis_amplitude(basis: str | int) → float[source]

Calculate the probability amplitude of the specified computational basis state.

Parameters:

basis (int or str) – computational basis expressed in binary (str) or integer, e.g. 101 or 5.

Returns:

the probability amplitude of the specified state.

Return type:

float

to_statevector(indices: Sequence[int] | None = None) → npt.NDArray[np.complex128][source]

Retrieve the statevector from the tensornetwork.

This method tends to be slow however we plan to parallelize this.

Parameters:

(optional) (indices) – target qubit indices. Default is the MBQC output nodes (self.default_output_nodes).

Returns:

statevector

Return type:

numpy.ndarray

flatten() → NDArray[complex128][source]

Return flattened statevector.

norm(optimize: str | PathOptimizer | None = None) → float[source]

Calculate the norm of the state.

Returns:

norm of the state

Return type:

float

expectation_value(op: npt.NDArray[np.complex128], qubit_indices: Sequence[int], output_node_indices: Iterable[int] | None = None, optimize: str | PathOptimizer | None = None) → float[source]

Calculate expectation value of the given operator.

Parameters:
  • op (numpy.ndarray) – single- or multi-qubit Hermitian operator

  • qubit_indices (list of int) – Applied positions of logical qubits.

  • (optional) (output_node_indices) – Indices of nodes in the entire TN, that remain unmeasured after MBQC operations. Default is the output nodes specified in measurement pattern (self.default_output_nodes).

Returns:

Expectation value

Return type:

float

evolve(operator: NDArray[complex128], qubit_indices: list[int], decompose: bool = True) → None[source]

Apply an arbitrary operator to the state.

Parameters:
  • operator (numpy.ndarray) – operator.

  • qubit_indices (list of int) – Applied positions of logical qubits.

  • decompose (bool, optional) – default True whether a given operator will be decomposed or not. If True, operator is decomposed into Matrix Product Operator(MPO)

copy(virtual: bool = False, deep: bool = False) → MBQCTensorNet[source]

Return the copy of this object.

Parameters:

deep (bool, optional) – Defaults to False. Whether to copy the underlying data as well.

Returns:

duplicated object

Return type:

TensorNetworkBackend

class Statevector(data: Data = graphix.states.PlanarState(Plane.XY, 0), nqubit: int | None = None, max_qubits: int | None = None)[source]

Bases: DenseState

Statevector object.

_psi

Complex-valued 1-dimensional array representing the quantum statevector. Only the first 2**nqubit complex values have meaning.

Type:

npt.NDArray[np.complex128]

_nqubit

Number of active qubits at any given time.

Type:

int

_max_qubits

Maximum Hilbert space size allowed for internal computations. It determines the size of self._psi. For circuit simulations, it corresponds to the number of qubits, while for pattern simulations it corresponds to the pattern’s maximum space. The method Statevector.ensure_capacity() allows to increase this number.

Type:

int

Notes

The internal representation of the quantum state is guaranteed to be normalized after initialization, and it is assumed to remain normalized thereafter.

Using evolve_single(), expectation_single(), evolve(), or expectation_value() with non-unitary operators does not preserve the norm of the statevector and may lead to unexpected behavior.

In pattern simulation, node measurements call evolve_single() with a projector (a non-unitary operator). However, the measured qubit is immediately removed via remove_qubit(), which restores the unit norm of the internal quantum state. See graphix.sim.base_backend.DenseStateBackend.measure() for additional details.

__init__(data: Data = graphix.states.PlanarState(Plane.XY, 0), nqubit: int | None = None, max_qubits: int | None = None) → None[source]

Initialize a statevector object.

data can be: - a single graphix.states.State (classical description of a quantum state) - an iterable of graphix.states.State objects - an iterable of scalars (a \(2^n\) numerical statevector) - a single graphix.sim.statevec.Statevector

If nqubit is not provided, it is inferred from data. If max_qubits is not provided, it is set to match the inferred max_qubits. If only one graphix.states.State is provided and nqubit is a valid integer, the statevector is initialized in the tensor product state. If a class:graphix.sim.statevec.Statevector is provided, a copy is returned. Consistency between provided nqubit, max_qubits and data is checked.

Parameters:
  • data (Data, optional) – Input data to prepare the state. Can be a classical description or a numerical input, defaults to graphix.states.BasicStates.PLUS.

  • nqubit (int | None, optional) – Number of qubits to prepare. If None (default), it’s inferred from data.

  • max_qubits (int | None, optional.) – Maximum Hilbert space size for array preallocation. If None (default), it’s set equal to nqubit.

Raises:

ValueError – If nqubit, max_qubits or data are not consistent with each other.

property psi: NDArray[complex128]

View of the meaningful elements in self._psi.

These are the first 2**self.nqubit elements.

property nqubit: int

Number of qubits.

property max_qubits: int

Preallocated number of qubits.

ensure_capacity(required_qubits: int) → None[source]

Extend the state vector if the required qubit capacity exceeds the current one.

It copies the full state vector if required_qubits > self.max_qubits, otherwise does nothing.

Parameters:

required_qubits (int) – Minimum number of qubits the state vector must support. If expansion is needed, self._psi is extended to size 2**required_qubits.

flatten() → NDArray[object_ | complex128][source]

Return flattened state vector.

A view of only the first 2**self.nqubit elements of self._psi is returned.

add_nodes(nqubit: int, data: Data) → None[source]

Add nodes (qubits) to the state vector and initialize them in a specified state.

Previously existing nodes remain unchanged.

Parameters:
  • nqubit (int) – The number of qubits to add to the state vector.

  • data (Data) –

    The state in which to initialize the newly added nodes.

    • If a single basic state is provided, all new nodes are initialized in that state.

    • If a list of basic states is provided, it must match the length of nodes, and each node is initialized with its corresponding state.

    • A single-qubit state vector will be broadcast to all nodes.

    • A multi-qubit state vector of dimension \(2^n\), where \(n = \mathrm{len}(nodes)\), initializes the new nodes jointly.

Notes

This method can extend the size of self._psi for convenience, but this requires allocating a full new array.

entangle(qubits: tuple[int, int]) → None[source]

Apply a CZ gate on two qubits.

Parameters:

qubits (tuple[int, int]) – (control, target) qubit indices.

evolve_single(op: NDArray[object_ | complex128], qubit: int) → None[source]

Apply a single-qubit operator.

Parameters:
  • op (npt.NDArray[np.complex128]) – Complex-valued matrix of shape \((2, 2)\) representing the operator to apply.

  • qubit (int) – Target qubit index.

expectation_single(op: NDArray[object_ | complex128], qubit: int) → complex[source]

Return the expectation value of a single-qubit operator.

Parameters:
  • op (npt.NDArray[np.complex128]) – Complex-valued matrix of shape \((2, 2)\) representing the operator to measure.

  • qubit (int) – Target qubit index.

Returns:

Expectation value.

Return type:

complex

Notes

This method assumes that quantum state represented by self.psi is normalized. See the class docstring for details.

evolve(op: Matrix, qubits: Sequence[int]) → None[source]

Apply a multi-qubit operator.

Parameters:
  • op (npt.NDArray[np.complex128]) – Complex-valued matrix of shape \((2^n, 2^n)\) representing the operator to apply.

  • qubits (Sequence[int]) – Target qubit indices.

Notes

This method is a fallback for circuit simulation and it’s not required for pattern simulation. It does not have an efficient JIT-compiled implementation.

expectation_value(op: Matrix, qubits: Sequence[int]) → complex[source]

Return the expectation value of a multi-qubit operator.

Parameters:
  • op (npt.NDArray[np.complex128]) – Complex-valued matrix of shape \((2^n, 2^n)\) representing the operator to measure.

  • qubits (Sequence[int]) – Target qubit indices.

Notes

This method assumes that quantum state represented by self.psi is normalized. See the class docstring for details.

remove_qubit(qubit: int) → None[source]

Remove a separable qubit from the system and assemble the statevector of the remaining qubits.

This method is deprecated. See self.project_qubit().

Parameters:

qubit (int) – Target qubit index.

project_qubit(op: NDArray[object_ | complex128], qubit: int) → None[source]

Project out a qubit from the system and assemble the statevector of the remaining qubits.

This method evolves the statevector with op and removes qubit. It assumes that after the application of op, qubit is a separable qubit.

Let \(P \ket{\psi} = \sum c_i \ket{i}\) be the statevector after the application of the projector \(P\) (op), with sum taken over \(i \in [ 0 \dots 00,\ 0\dots 01,\ \dots,\ 1 \dots 11 ]\), this method returns

\[\begin{split}\ket{\psi}' =& c_{0 \dots 0_{\mathrm{k-1}}0_{\mathrm{k}}0_{\mathrm{k+1}} \dots 00} \ket{0 \dots 0_{\mathrm{k-1}}0_{\mathrm{k+1}} \dots 00} \\ & + c_{0 \dots 0_{\mathrm{k-1}}0_{\mathrm{k}}0_{\mathrm{k+1}} \dots 01} \ket{0 \dots 0_{\mathrm{k-1}}0_{\mathrm{k+1}} \dots 01} \\ & + c_{0 \dots 0_{\mathrm{k-1}}0_{\mathrm{k}}0_{\mathrm{k+1}} \dots 10} \ket{0 \dots 0_{\mathrm{k-1}}0_{\mathrm{k+1}} \dots 10} \\ & + \dots \\ & + c_{1 \dots 1_{\mathrm{k-1}}0_{\mathrm{k}}1_{\mathrm{k+1}} \dots 11} \ket{1 \dots 1_{\mathrm{k-1}}1_{\mathrm{k+1}} \dots 11},\end{split}\]

(after normalization) for \(k =\) qubit. If the \(k\) th qubit is in the \(\ket{1}\) state, all the amplitudes above will be zero. In that case the returned state will be the one above with \(0_{\mathrm{k}}\) replaced with \(1_{\mathrm{k}}\).

Warning

This method assumes that op is a projector, and therefore that qubit is a separable qubit after applying op. It is implemented as a significantly faster alternative for partial trace to be used after single-qubit measurements. Separability is not checked.

Parameters:
  • op (npt.NDArray[np.complex128]) – Complex-valued matrix of shape \((2, 2)\) representing the projector to apply.

  • qubit (int) – Target qubit index.

Notes

This method combines evolve_single() and remove_qubit().

First, the relevant entries of self.psi are partitioned into two disjoint subvectors. The partition depends on the value of q. For a three-qubit statevector [a0, a1, a2, a3, a4, a5, a6, a7]:

q = 0: sv0 = [a0, a1, a2, a3], sv1 = [a4, a5, a6, a7]
q = 1: sv0 = [a0, a1, a4, a5], sv1 = [a2, a3, a6, a7]
q = 2: sv0 = [a0, a2, a4, a6], sv1 = [a1, a3, a5, a7]

The statevector is then iterated over as in evolve_single(), updating self.psi in place while computing the norm of each partition separately.

If both resulting subvectors have nonzero norm, they are guaranteed to be identical. The subvector sv0 is normalized and written to the first 2**(nqubit - 1) entries of self.psi.

If exactly one subvector has nonzero norm, that subvector is selected. If both have zero norm, a RuntimeError is raised.

swap(qubits: tuple[int, int]) → None[source]

Apply SWAP gate between two qubits.

Parameters:

qubits (tuple[int, int]) – (control, target) qubit indices.

tensor(other: Statevector) → None[source]

Tensor product state with other qubits.

Results in self \(\otimes\) other.

Parameters:

other (graphix.sim.statevec.Statevector) – Statevector to be tensored with self.

Notes

This method is used internally by add_nodes().

permute(permutation: Sequence[int]) → None[source]

Reorder the qubits.

Parameters:

permutation (Sequence[int]) – The permutation to apply. For each position in the resulting order, the value gives the index of the qubit in the original ordering.

fidelity(other: Statevector | NDArray[object_ | complex128]) → float[source]

Calculate the fidelity against another statevector.

The fidelity is defined as \(|\langle\psi_1|\psi_2\rangle|^2\).

Parameters:

other (graphix.sim.statevec.Statevector) – Statevector to compare with.

Returns:

Fidelity between the two statevectors.

Return type:

float

isclose(other: Statevector | NDArray[object_ | complex128], *, rtol: float = 1e-09, atol: float = 0.0) → bool[source]

Check if two quantum states are equal up to global phase.

Two states are considered close if their fidelity is close to 1.

Parameters:
Returns:

True if the states are equal up to global phase.

Return type:

bool

to_dict(encoding: _ENCODING = 'MSB', *, rtol: float = 0.0, atol: float = 1e-08) → dict[str, np.object_ | np.complex128][source]

Convert the statevector to dictionary form.

This dictionary representation uses a ket-like notation where the dictionary keys are qubit strings for the basis vectors and values are the corresponding complex amplitudes. Amplitudes below a certain threshold are filtered out.

Parameters:
  • encoding (Literal["LSB", "MSB"], default="MSB") – Encoding for the basis kets. See notes for additional information.

  • rtol (float, default=0.0) – Relative tolerance used when deciding whether a coefficient should be treated as zero. Values whose magnitude is within this relative tolerance of zero are omitted from the resulting dictionary.

  • atol (float, default=1e-8) – Absolute tolerance used when deciding whether a coefficient should be treated as zero. Values whose magnitude is within this relative tolerance of zero are omitted from the resulting dictionary.

Returns:

The statevector in dictionary form.

Return type:

dict[str, complex]

Notes

The encoding determines the bit ordering convention used when mapping basis states to dictionary keys. Consider a tensor product of three qubits:

\[\]

lvertpsirangle = q_0 otimes q_1 otimes q_2.

If encoding == "MSB" the first qubit is represented in the Most Significant Bit -> q0q1q2. This is the default representation in Graphix. If encoding == "LSB" the first qubit is represented in the Least Significant Bit -> q2q1q0. This is the default representation in other software packages such as Qiskit.

Example

>>> from graphix.states import BasicStates
>>> from graphix.sim.statevec import Statevector
>>> sv = Statevector(data=[BasicStates.ZERO, BasicStates.ONE])
>>> sv.to_dict()
{'01': np.complex128(1+0j)}
>>> sv.to_dict(encoding="LSB")
{'10': np.complex128(1+0j)}
to_prob_dict(encoding: _ENCODING = 'MSB', *, rtol: float = 0.0, atol: float = 1e-08) → dict[str, np.object_ | np.float64][source]

Convert the statevector to a probability distirbution in a dictionary form.

This dictionary representation uses a ket-like notation where the dictionary keys are qubit strings for the basis vectors and values are the corresponding probabilities. Basis vector whose amplitude is below a certain threshold are filtered out.

Parameters:
  • encoding (Literal["LSB", "MSB"], default="MSB") – Encoding for the basis kets. See to_dict() for additional information.

  • rtol (float, default=0.0) – Relative tolerance used when deciding whether a coefficient should be treated as zero. Values whose magnitude is within this relative tolerance of zero are omitted from the resulting dictionary.

  • atol (float, default=1e-8) – Absolute tolerance used when deciding whether a coefficient should be treated as zero. Values whose magnitude is within this relative tolerance of zero are omitted from the resulting dictionary.

Returns:

The probability distribution associated to the statevector in dictionary form.

Return type:

dict[str, float]

See also

meth:to_dict

draw(output: OutputFormat | None = None, *, encoding: _ENCODING = 'MSB', max_denominator: int = 1000, atol: float = 1e-09, rtol: float = 0.0, precision: int = 4) → str[source]

Return a pretty-printed ket-notation representation of the statevector.

Amplitudes are rendered with graphix.pretty_print.complex_to_str(), so common values appear as exact expressions (e.g. √2/2) rather than floating-point numbers.

Parameters:
  • output (OutputFormat, optional) – Desired formatting style. Defaults to OutputFormat.Unicode.

  • encoding ({"LSB", "MSB"}, optional) – Bit-ordering convention for the basis kets (default: "MSB"). See to_dict().

  • max_denominator (int, optional) – Maximum denominator used by the amplitude recognition (default: 1000).

  • atol (float, optional) – Absolute tolerance for dropping near-zero amplitudes and for the recognition heuristics (default: 1e-9).

  • rtol (float, optional) – Relative tolerance for dropping near-zero amplitudes (default: 0.0).

  • precision (int, optional) – Number of significant digits to use for amplitudes that fall back to a decimal representation (default: 4).

Returns:

The formatted statevector.

Return type:

str

Examples

>>> from graphix.transpiler import Circuit
>>> circuit = Circuit(2)
>>> circuit.h(0)
>>> circuit.cz(0, 1)
>>> print(circuit.simulate().state.draw())
sqrt(2)/2(|00> + |01>)
class StatevectorBackend(state: Statevector = <factory>, *, node_index: NodeIndex = <factory>, branch_selector: BranchSelector = <factory>, symbolic: bool = False)[source]

Bases: DenseStateBackend[Statevector]

Numba JIT-compiled MBQC statevector backend simulator based on Ref. [1].

See also

graphix.sim.base_backend.DenseStateBackend

Base class describing available parameters and shared behavior.

Notes

By default, the backend is initialized with a 0-dimensional statevector (a scalar 1) and max_qubits = 0.

The internal state representation can be expanded using StatevectorBackend.add_nodes, but this is inefficient since it requires copying the full quantum state array.

To preallocate memory for a fixed system size, use StatevectorBackend.with_capacity().

References

[1] McGuffin, M. J., Robert J-M., and Ikeda K. “How to Write a Simulator for Quantum Circuits from Scratch: A Tutorial.”, 2025 (arXiv:2506.08142).

state: Statevector
classmethod with_capacity(max_qubits: int, state: Statevector | None = None, **kwargs: Unpack[DenseStateBackendKwargs]) → Self[source]

Initialize the backend with preallocated statevector capacity.

Parameters:
Returns:

Backend instance with capacity for up to max_qubits qubits.

Return type:

Self

__init__(state: Statevector = <factory>, *, node_index: NodeIndex = <factory>, branch_selector: BranchSelector = <factory>, symbolic: bool = False) → None
class TensorNetworkBackend(pattern: Pattern, graph_prep: str = 'auto', input_state: Data | None = None, branch_selector: BranchSelector | None = None)[source]

Bases: _AbstractTensorNetworkBackend

Tensor Network Simulator for MBQC.

Executes the measurement pattern using TN expression of graph states.

Parameters:
  • pattern (graphix.Pattern)

  • graph_prep (str) –

    ‘parallel’ :

    Faster method for preparing a graph state. The expression of a graph state can be obtained from the graph geometry. See https://journals.aps.org/pra/abstract/10.1103/PhysRevA.76.052315 for detail calculation. Note that ‘N’ and ‘E’ commands in the measurement pattern are ignored.

    ’sequential’ :

    Sequentially execute N and E commands, strictly following the measurement pattern. In this strategy, All N and E commands executed sequentially.

    ’auto’(default) :

    Automatically select a preparation strategy based on the max degree of a graph

  • input_state (preparation for input states (only BasicStates.PLUS is supported for tensor networks yet),)

  • branch_selector (graphix.branch_selector.BranchSelector, optional) – Branch selector to be used for measurements.

__init__(pattern: Pattern, graph_prep: str = 'auto', input_state: Data | None = None, branch_selector: BranchSelector | None = None) → None[source]

Construct a tensor network backend.

add_nodes(nodes: Sequence[int], data: Data = graphix.states.PlanarState(Plane.XY, 0)) → None[source]

Add new nodes (qubits) to the network and initialize them in a specified state.

Parameters:
  • nodes (Sequence[int]) – A list of node indices to add to the backend. These indices can be any integer values but must be fresh: each index must be distinct from all previously added nodes.

  • data (Data, optional) –

    The state in which to initialize the newly added nodes.

    • If a single basic state is provided, all new nodes are initialized in that state.

    • If a list of basic states is provided, it must match the length of nodes, and each node is initialized with its corresponding state.

Notes

Previously existing nodes remain unchanged.

entangle_nodes(edge: tuple[int, int]) → None[source]

Make entanglement between nodes specified by edge.

Parameters:

edge (tuple of int) – edge specifies two target nodes of the CZ gate.

measure(node: int, measurement: Measurement, rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Perform measurement of the node.

In the context of tensornetwork, performing measurement equals to applying measurement operator to the tensor. Here, directly contracted with the projected state.

Parameters:
  • node (int) – index of the node to measure

  • measurement (Measurement) – measure plane and angle

correct_byproduct(cmd: X | Z) → None[source]

Byproduct correction correct for the X or Z byproduct operators, by applying the X or Z gate.

Note: the simulator is responsible for checking that the measurement outcomes match the domain condition before calling this method.

apply_clifford(node: int, clifford: Clifford) → None[source]

Apply single-qubit Clifford gate.

Parameters:

cmd (list) – clifford command. See https://arxiv.org/pdf/2212.11975.pdf for the detail.

finalize(output_nodes: Iterable[int]) → None[source]

Do nothing.

property nqubit: int

the current number of qubits is not well-defined in the current implementation of the TensorNetworkBackend.

Type:

Raise NotImplementedError