graphix package

Subpackages

Submodules

Module contents

Optimize and simulate measurement-based quantum computation.

class AmplitudeDampingNoiseModel(prepare_error_prob: float = 0.0, x_error_prob: float = 0.0, z_error_prob: float = 0.0, entanglement_error_prob: float = 0.0, measure_channel_prob: float = 0.0, measure_error_prob: float = 0.0)[source]

Bases: NoiseModel

Amplitude damping noise model.

Parameters:

NoiseModel (class) – Parent abstract class class:NoiseModel

__init__(prepare_error_prob: float = 0.0, x_error_prob: float = 0.0, z_error_prob: float = 0.0, entanglement_error_prob: float = 0.0, measure_channel_prob: float = 0.0, measure_error_prob: float = 0.0) → None[source]
input_nodes(nodes: Iterable[int], rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Return the noise to apply to input nodes.

command(cmd: CommandOrNoise, rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Return the noise to apply to the command cmd.

confuse_result(cmd: BaseM, result: Outcome, rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Assign wrong measurement result with probability measure_error_prob.

class Axis(*values)[source]

Bases: AbstractMeasurement, EnumReprMixin, Enum

Axis: X, Y or Z.

X = 1
Y = 2
Z = 3
to_plane_or_axis() → Axis[source]

Return the plane or axis of a measurement object.

Return type:

Plane | Axis

clifford(clifford_gate: Clifford) → Axis[source]

Return a new measurement command with a Clifford applied.

Parameters:

clifford_gate (Clifford) – Clifford gate to apply before the measurement.

Returns:

Equivalent measurement representing the pattern MC.

Return type:

Self

Notes

  • The return type is Self, meaning that a Clifford applied to a Bloch measurement returns a Bloch measurement, and a Clifford applied to a Pauli measurement returns a Pauli measurement.

  • The method Measurement.clifford() does not always commute with the method Measurement.to_bloch(): the underlying Pauli measurement will be the same but the Bloch representation can be on different planes.

Examples

>>> from graphix.clifford import Clifford
>>> from graphix.measurements import Measurement, PauliMeasurement
>>> Measurement.XY(0.25).clifford(Clifford.H)
Measurement.YZ(1.75)
>>> Measurement.X.clifford(Clifford.S)
-Measurement.Y
>>> for pauli in PauliMeasurement:
...     for clifford in Clifford:
...         assert pauli.to_bloch().clifford(clifford).to_pauli_or_none() == pauli.clifford(clifford)
>>> Measurement.Y.clifford(Clifford.H).to_bloch()
Measurement.XY(1.5)
>>> Measurement.Y.to_bloch().clifford(Clifford.H)
Measurement.YZ(1.5)
class BasicStates[source]

Bases: object

Basic states.

ZERO: ClassVar[PlanarState] = graphix.states.PlanarState(Plane.XZ, 0)
ONE: ClassVar[PlanarState] = graphix.states.PlanarState(Plane.XZ, 1)
PLUS: ClassVar[PlanarState] = graphix.states.PlanarState(Plane.XY, 0)
MINUS: ClassVar[PlanarState] = graphix.states.PlanarState(Plane.XY, 1)
PLUS_I: ClassVar[PlanarState] = graphix.states.PlanarState(Plane.XY, 0.5)
MINUS_I: ClassVar[PlanarState] = graphix.states.PlanarState(Plane.XY, -0.5)
VEC: ClassVar[list[PlanarState]] = [graphix.states.PlanarState(Plane.XY, 0), graphix.states.PlanarState(Plane.XY, 1), graphix.states.PlanarState(Plane.XZ, 0), graphix.states.PlanarState(Plane.XZ, 1), graphix.states.PlanarState(Plane.XY, 0.5), graphix.states.PlanarState(Plane.XY, -0.5)]
class BlochMeasurement(angle: Expression | float, plane: Plane)[source]

Bases: AbstractPlanarMeasurement, Measurement

An MBQC measurement described by an angle and a plane.

angle

The angle of the measurement in units of \(\pi\). Should be between [0, 2).

Type:

ExpressionOrFloat

plane

The measurement plane.

Type:

graphix.fundamentals.Plane

angle: Expression | float
plane: Plane
to_bloch() → BlochMeasurement[source]

Return self (overridden from Measurement).

downcast_bloch() → BlochMeasurement[source]

Return self (overridden from Measurement).

to_pauli_or_none(rel_tol: float = 1e-09, abs_tol: float = 0.0) → PauliMeasurement | None[source]

Return the measurement description as a Pauli measurement if possible, or None otherwise.

Parameters:
  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

If self is already an instance of PauliMeasurement, the function returns self. If self is an instance of BlochMeasurement, then either the measurement is close to a Pauli measurement (i.e., the angle is close to an integer multiple of π/2) and the corresponding Pauli measurement is returned, or it is not and None is returned.

Return type:

PauliMeasurement | None

Notes

A measurement with a parameterized angle is not considered as Pauli, but can become a Pauli measurement after substitution.

Examples

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0.5).to_pauli_or_none()
Measurement.Y
>>> Measurement.Y.to_pauli_or_none()
Measurement.Y
>>> Measurement.XY(0.25).to_pauli_or_none() is None
True
>>> from graphix.parameter import Placeholder
>>> alpha = Placeholder("alpha")
>>> Measurement.XY(alpha).to_pauli_or_none() is None
True
>>> Measurement.XY(alpha).with_parameter(alpha, 0.5).to_pauli_or_none()
Measurement.Y
to_pauli_or_bloch(rel_tol: float = 1e-09, abs_tol: float = 0.0) → PauliMeasurement | BlochMeasurement[source]

Return the measurement description as a Pauli measurement if possible, a Bloch measurement otherwise.

Parameters:
  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

If self is already an instance of PauliMeasurement, the function returns self. If self is an instance of BlochMeasurement, then either the measurement is close to a Pauli measurement (i.e., the angle is close to an integer multiple of π/2) and the corresponding Pauli measurement is returned, or it is not and self is returned.

Return type:

PauliMeasurement | BlochMeasurement

Examples

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0.5).to_pauli_or_bloch()
Measurement.Y
>>> Measurement.XY(0.25).to_pauli_or_bloch()
Measurement.XY(0.25)
isclose(other: AbstractMeasurement, rel_tol: float = 1e-09, abs_tol: float = 0.0) → bool[source]

Determine whether two measurements are close in angle and share the same plane.

This method compares the angle of the current measurement with that of another measurement, using math.isclose() when both angles are floats. The planes must match exactly for the measurements to be considered close. A measurement represented as BlochMeasurement is never considered to be close to a measurement represented as PauliMeasurement.

Parameters:
  • other (AbstractMeasurement) – The measurement to compare against.

  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

  • bool

  • True if both measurements lie in the same plane and their angles

  • are equal or close within the given tolerances; False otherwise.

Examples

>>> from graphix.measurements import Measurement
>>> from graphix.fundamentals import Plane
>>> Measurement.XY(0).isclose(Measurement.XY(0))
True
>>> Measurement.XY(0).isclose(Measurement.YZ(0))
False
>>> Measurement.XY(0.1).isclose(Measurement.XY(0))
False
>>> Measurement.XY(0).isclose(Measurement.X)
False
to_plane() → Plane[source]

Return the plane of a measurement object.

Return type:

Plane

clifford(clifford_gate: Clifford) → BlochMeasurement[source]

Return a new measurement command with a Clifford applied.

Parameters:

clifford_gate (Clifford) – Clifford gate to apply before the measurement.

Returns:

Equivalent measurement representing the pattern MC.

Return type:

Self

Notes

  • The return type is Self, meaning that a Clifford applied to a Bloch measurement returns a Bloch measurement, and a Clifford applied to a Pauli measurement returns a Pauli measurement.

  • The method Measurement.clifford() does not always commute with the method Measurement.to_bloch(): the underlying Pauli measurement will be the same but the Bloch representation can be on different planes.

Examples

>>> from graphix.clifford import Clifford
>>> from graphix.measurements import Measurement, PauliMeasurement
>>> Measurement.XY(0.25).clifford(Clifford.H)
Measurement.YZ(1.75)
>>> Measurement.X.clifford(Clifford.S)
-Measurement.Y
>>> for pauli in PauliMeasurement:
...     for clifford in Clifford:
...         assert pauli.to_bloch().clifford(clifford).to_pauli_or_none() == pauli.clifford(clifford)
>>> Measurement.Y.clifford(Clifford.H).to_bloch()
Measurement.XY(1.5)
>>> Measurement.Y.to_bloch().clifford(Clifford.H)
Measurement.YZ(1.5)
with_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat) → BlochMeasurement[source]

Substitute a parameter with a value or expression.

with_parameters(assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) → BlochMeasurement[source]

Perform parallel substitution of multiple parameters.

__init__(angle: Expression | float, plane: Plane) → None
class CausalFlow(og: OpenGraph[_AM_co], correction_function: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]], _CF_PREFIX: str = 'c')[source]

Bases: GFlow[_PM_co], Generic[_PM_co]

An unmutable subclass of GFlow providing a representation of a causal flow.

This class differs from its parent class in the following:
  • The extraction of XZ-corrections from the causal flow does assumes that correction sets have one element only.

  • The method CausalFlow.is_well_formed() verifies the definition of causal flow (Definition 2 in Ref. [1]).

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

classmethod from_correctionmatrix_or_none(correction_matrix: CorrectionMatrix[_PM_co]) → None[source]

Initialize a GFlow object from a matrix encoding a correction function.

Parameters:

correction_matrix (CorrectionMatrix[_PM_co]) – Algebraic representation of the correction function.

Returns:

A gflow if it exists, None otherwise.

Return type:

Self | None

Notes

This method verifies if there exists a partial measurement order on the input open graph compatible with the input correction matrix. See Lemma 3.12, and Theorem 3.1 in Ref. [1]. Failure to find a partial order implies the non-existence of a generalised flow if the correction matrix was calculated by means of Algorithms 2 and 3 in [1].

References

[1] Mitosek and Backens, 2024 (arXiv:2410.23439).

to_xzcorrections() → XZCorrections[_PM_co][source]

Compute the XZ-corrections induced by the causal flow encoded in self.

Return type:

XZCorrections[_PM_co]

Notes

This function partially implements Theorem 1 in Ref. [1]. The generated XZ-corrections can be used to obtain a robustly deterministic pattern on the underlying open graph.

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

check_well_formed() → None[source]

Verify if the causal flow is well formed.

Raises:

FlowError – if the causal flow is not well formed.

Notes

General properties of flows:
  • The domain of the correction function is \(O^c\), the non-output nodes of the open graph.

  • The image of the correction function is a subset of \(I^c\), the non-input nodes of the open graph.

  • The nodes in the partial order are the nodes in the open graph.

  • The first layer of the partial order layers is \(O\), the output nodes of the open graph. This is guaranteed because open graphs without outputs do not have flow, unless it is an empty open graph.

Specific properties of causal flows:
  • Correction sets have one element only (C0),

  • \(i \sim c(i)\) (C1),

  • \(i \prec c(i)\) (C2),

  • \(\forall k \in N_G(c(i)) \setminus \{i\}, i \prec k\) (C3),

where \(i \in O^c\), \(c\) is the correction function and \(prec\) denotes the partial order.

Causal flows are defined on open graphs with XY measurements only.

See Definition 2 in Ref. [1].

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

__init__(og: OpenGraph[_AM_co], correction_function: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]], _CF_PREFIX: str = 'c') → None
class Circuit(width: int, instr: Iterable[InstructionType] | None = None)[source]

Bases: InplaceParameterizable

Gate-to-MBQC transpiler.

Holds gate operations and translates into MBQC measurement patterns.

width

Number of logical qubits (for gate network)

Type:

int

instruction

List containing the gate sequence applied.

Type:

list

__init__(width: int, instr: Iterable[InstructionType] | None = None) → None[source]

Construct a circuit.

Parameters:
  • width (int) – number of logical qubits for the gate network

  • instr (list[InstructionType] | None) – Optional. List of initial instructions.

instruction: list[InstructionType]
add(instr: InstructionType) → None[source]

Add an instruction to the circuit.

extend(instrs: Iterable[InstructionType]) → None[source]

Add instructions to the circuit.

cnot(control: int, target: int) → None[source]

Apply a CNOT gate.

See CNOT for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

swap(qubit1: int, qubit2: int) → None[source]

Apply a SWAP gate.

See SWAP for more information.

Parameters:
  • qubit1 (int) – first qubit to be swapped

  • qubit2 (int) – second qubit to be swapped

cz(qubit1: int, qubit2: int) → None[source]

Apply a CZ gate.

See CZ for more information.

Parameters:
  • qubit1 (int) – control qubit

  • qubit2 (int) – target qubit

h(qubit: int) → None[source]

Apply a Hadamard gate.

See H for more information.

Parameters:

qubit (int) – target qubit

s(qubit: int) → None[source]

Apply an S gate.

See S for more information.

Parameters:

qubit (int) – target qubit

x(qubit: int) → None[source]

Apply a Pauli X gate.

See X for more information.

Parameters:

qubit (int) – target qubit

y(qubit: int) → None[source]

Apply a Pauli Y gate.

See Y for more information.

Parameters:

qubit (int) – target qubit

z(qubit: int) → None[source]

Apply a Pauli Z gate.

See Z for more information.

Parameters:

qubit (int) – target qubit

rx(qubit: int, angle: ParameterizedAngle) → None[source]

Apply an X rotation gate.

See RX for more information.

Parameters:
  • qubit (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

ry(qubit: int, angle: ParameterizedAngle) → None[source]

Apply a Y rotation gate.

See RY for more information.

Parameters:
  • qubit (int) – target qubit

  • angle (ParameterizedAngle) – angle in units of π

rz(qubit: int, angle: ParameterizedAngle) → None[source]

Apply a Z rotation gate.

See RZ for more information.

Parameters:
  • qubit (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

j(qubit: int, angle: ParameterizedAngle) → None[source]

Apply a J rotation gate.

See J for more information.

Parameters:
  • qubit (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

r(qubit: int, axis: Axis, angle: ParameterizedAngle) → None[source]

Apply a rotation gate on the given axis.

Parameters:
  • qubit (int) – target qubit

  • axis (Axis) – rotation axis

  • angle (ParameterizedAngle) – rotation angle in units of π

rzz(control: int, target: int, angle: ParameterizedAngle) → None[source]

Apply a ZZ-rotation gate.

Equivalent to the sequence CNOT(control, target), Rz(target, angle), CNOT(control, target)

and realizes rotation expressed by \(e^{-i \frac{\theta}{2} Z_c Z_t}\).

See RZZ for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

ccx(control1: int, control2: int, target: int) → None[source]

Apply a CCX (Toffoli) gate.

See CCX for more information.

Parameters:
  • control1 (int) – first control qubit

  • control2 (int) – second control qubit

  • target (int) – target qubit

i(qubit: int) → None[source]

Apply an identity (teleportation) gate.

See I for more information.

Parameters:

qubit (int) – target qubit

m(qubit: int, axis: Axis) → None[source]

Measure a quantum qubit.

The measured qubit cannot be used afterwards.

See M for more information.

Parameters:
  • qubit (int) – target qubit

  • axis (Axis) – measurement basis

sdg(qubit: int) → None[source]

Apply an SDG gate.

See SDG for more information.

Parameters:

qubit (int) – target qubit

t(qubit: int) → None[source]

Apply a T gate.

See T for more information.

Parameters:

qubit (int) – target qubit

tdg(qubit: int) → None[source]

Apply a TDG gate.

See TDG for more information.

Parameters:

qubit (int) – target qubit

sx(qubit: int) → None[source]

Apply an SX gate.

See SX for more information.

Parameters:

qubit (int) – target qubit

sxdg(qubit: int) → None[source]

Apply an SXDG gate.

See SXDG for more information.

Parameters:

qubit (int) – target qubit

cy(control: int, target: int) → None[source]

Apply a Controlled-Y gate.

See CY for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

p(qubit: int, angle: ParameterizedAngle) → None[source]

Apply a Phase rotation gate.

See P for more information.

Parameters:
  • qubit (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

u(qubit: int, theta: ParameterizedAngle, phi: ParameterizedAngle, lambda_: ParameterizedAngle) → None[source]

Apply a universal single-qubit gate.

See U for more information.

Parameters:
  • qubit (int) – target qubit

  • theta (ParameterizedAngle) – rotation angle in units of π

  • phi (ParameterizedAngle) – rotation angle in units of π

  • lambda (ParameterizedAngle) – rotation angle in units of π

cj(control: int, target: int, angle: ParameterizedAngle) → None[source]

Apply a controlled-J rotation gate.

See CJ for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

cp(control: int, target: int, angle: ParameterizedAngle) → None[source]

Apply a controlled-P rotation gate.

See CP for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

crx(control: int, target: int, angle: ParameterizedAngle) → None[source]

Apply an controlled-X rotation gate.

See CRX for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

cry(control: int, target: int, angle: ParameterizedAngle) → None[source]

Apply a controlled-Y rotation gate.

See CRY for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • angle (ParameterizedAngle) – angle in units of π

crz(control: int, target: int, angle: ParameterizedAngle) → None[source]

Apply a controlled-Z rotation gate.

See CRZ for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • angle (ParameterizedAngle) – rotation angle in units of π

cr(control: int, target: int, axis: Axis, angle: ParameterizedAngle) → None[source]

Apply a controlled-rotation gate on the given axis.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • axis (Axis) – rotation axis

  • angle (ParameterizedAngle) – rotation angle in units of π

cu(control: int, target: int, theta: ParameterizedAngle, phi: ParameterizedAngle, lambda_: ParameterizedAngle, gamma: ParameterizedAngle) → None[source]

Apply a controlled-U gate.

See CU for more information.

Parameters:
  • control (int) – control qubit

  • target (int) – target qubit

  • theta (ParameterizedAngle) – rotation angle in units of π

  • phi (ParameterizedAngle) – rotation angle in units of π

  • lambda (ParameterizedAngle) – rotation angle in units of π

  • gamma (ParameterizedAngle) – rotation angle in units of π

cswap(control: int, qubit1: int, qubit2: int) → None[source]

Apply a CSWAP gate.

See CSWAP for more information.

Parameters:
  • control (int) – control qubit

  • qubit1 (int) – first qubit to be swapped

  • qubit2 (int) – second qubit to be swapped

gphase(angle: ParameterizedAngle) → None[source]

Apply a global phase.

See GPHASE for more information.

Parameters:

angle (ParameterizedAngle) – rotation angle in units of π

transpile_to_causalflow() → TranspiledFlow[source]

Transpile a circuit via J-∧z decomposition to a causal flow.

Parameters:

self (the circuit to transpile.)

Returns:

the result of the transpilation

Return type:

a causal flow and classical outputs.

transpile(*, transpile_swaps: bool = True) → TranspiledPattern[source]

Transpile a circuit via J-∧z decomposition to a pattern.

Parameters:

transpile_swaps (bool, optional) – If True (the default), SWAP gates are eliminated by switching the qubits. If False, SWAP gates are transpiled into a sequence of three CNOT gates.

Returns:

The result of the transpilation: a pattern and classical outputs.

Return type:

TranspiledPattern

simulate(backend: StatevectorBackend | Literal['statevector'] = 'statevector', input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1) → SimulateResult[Statevector][source]
simulate(backend: DensityMatrixBackend | Literal['densitymatrix'], input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1) → SimulateResult[DensityMatrix]
simulate(backend: DenseStateBackend[_DenseStateT], input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1) → SimulateResult[_DenseStateT]

Simulate the gate sequence with a backend and input state of choice.

By default, this method uses the statevector backend and initializes the register to \(|+\rangle^{\otimes n}\).

Parameters:
  • input_state (Data)

  • backend (graphix.sim.base_backend.DenseStateBackend[_DenseStateT], ‘statevector’, or ‘densitymatrix’) – Simulator backend to use. Optional, defaults to “statevector”.

  • branch_selector (graphix.branch_selector.BranchSelector) – branch selector for measures (default: RandomBranchSelector). It cannot be specified if backend is already instantiated.

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

Returns:

result – output state of the statevector simulation and results of classical measures.

Return type:

SimulateResult

visit(visitor: InstructionVisitor, *, copy: bool = False) → Circuit[source]

Apply visitor to all instructions in the circuit.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten circuit. Equal to self if copy is False.

Return type:

Self

apply_angle(f: Callable[[ParameterizedAngle], ParameterizedAngle], *, copy: bool = False) → Circuit[source]

Apply f to all angles that occur in the circuit.

Parameters:
  • f (Callable[[ParameterizedAngle], ParameterizedAngle]) – The function to apply to every angle.

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

Returns:

The rewritten circuit. Equal to self if copy is False.

Return type:

Self

is_parameterized() → bool[source]

Return True if there is at least one measurement angle that is not just an instance of SupportsFloat.

A parameterized circuit is a circuit where at least one measurement angle is an expression that is not a number, typically an instance of sympy.Expr (but we don’t force to choose sympy here).

replace_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat, *, copy: bool = False) → Circuit[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) → Circuit[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

transpile_to_qasm_gates() → Circuit[source]

Return an equivalent circuit using only the standard OpenQASM gate set.

transpile_rzz() → Circuit[source]

Return an equivalent circuit where all RZZ gates have been replaced with OpenQASM gates.

class Clifford(*values)[source]

Bases: Enum

Clifford gate.

I: Clifford = 0
X: Clifford = 1
Y: Clifford = 2
Z: Clifford = 3
S: Clifford = 4
SDG: Clifford = 5
H: Clifford = 6
property matrix: npt.NDArray[np.complex128]

Return the matrix of the Clifford gate.

static try_from_matrix(mat: npt.NDArray[Any]) → Clifford | None[source]

Find the Clifford gate from the matrix.

Return None if not found.

Notes

Global phase is ignored.

draw(output: OutputFormat | None = None) → str[source]

Return a string representation of a Clifford.

Parameters:
  • clifford (Clifford) – The statevector to format.

  • output (OutputFormat | None) – Desired formatting style (ASCII, LaTeX or Unicode).

Returns:

The formatted Clifford.

Return type:

str

property conj: Clifford

Return the conjugate of the Clifford gate.

property hsz: list[Clifford]

Return a decomposition of the Clifford gate with the gates H, S, Z.

property qasm3: tuple[str, ...]

Return a decomposition of the Clifford gate as qasm3 gates.

measure_axis(axis: Axis) → Pauli[source]

Compute C† P C with P the Pauli +axis.

measure(pauli: Pauli) → Pauli[source]

Compute C† P C.

commute_domains(domains: Domains) → Domains[source]

Commute \(X^sZ^t\) with \(C\).

Given \(X^sZ^t\), return \(X^{s'}Z^{t'}\) such that \(X^sZ^tC = CX^{s'}Z^{t'}\).

Note that applying the method to conj() computes the reverse commutation: indeed, \(C^\dag X^s Z^t = (X^s Z^t C)^\dag = (C X^{s'} Z^{t'})^\dag = X^{s'}Z^{t'}C^\dag\).

to_opengraph() → OpenGraph[PauliMeasurement][source]

Return a local-Clifford-free open graph equivalent to the Clifford gate.

class CliffordMap(x_map: Sequence[PauliString], z_map: Sequence[PauliString], input_nodes: Sequence[int], output_nodes: Sequence[int])[source]

Bases: object

Dataclass to represent a Clifford map.

A Clifford map encodes the action of a Clifford operator on Pauli generators. It describes how single-qubit Pauli operators on the input qubits are mapped, under conjugation, to Pauli strings on the output qubits.

For each input qubit \(i\), the map specifies:

\[P_i = C X_i C^\dagger\]
\[P_i = C Z_i C^\dagger\]

where the resulting operators \(P_i\) are Pauli strings over the output qubits.

The sequences input_nodes and output_nodes define the correspondence between qubit indices and node labels in the input and output spaces.

x_map

Images of the \(X\) generators. The \(i\)-th element is the Pauli string corresponding to \(C X_i C^\dagger\).

Type:

Sequence[PauliString]

z_map

Images of the \(Z\) generators. The \(i\)-th element is the Pauli string corresponding to \(C Z_i C^\dagger\).

Type:

Sequence[PauliString]

input_nodes

Sequence of inputs nodes.

Type:

Sequence[int]

output_nodes

Sequence of outputs nodes.

Type:

Sequence[int]

Notes

See Definition 3.3 in Ref. [1].

Elements of x_map and z_map are in one-to-one correspondance with input_nodes. Each Pauli string is defined over qubit indices corresponding to positions in output_nodes.

References

[1] Simmons, 2021 (arXiv:2109.05654).

x_map: Sequence[PauliString]
z_map: Sequence[PauliString]
input_nodes: Sequence[int]
output_nodes: Sequence[int]
static from_focused_flow(flow: PauliFlow[Measurement], *, stacklevel: int = 1) → CliffordMap[source]

Extract a Clifford map from a focused Pauli flow.

This routine associates two Pauli strings (one per generator of the Pauli group, X and Z) to each input node in flow.og.

Parameters:
  • flow (PauliFlow[Measurement]) – A focused Pauli flow.

  • stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Return type:

CliffordMap

Notes

See Definition 3.3 and Example C.13 in Ref. [1].

References

[1] Simmons, 2021 (arXiv:2109.05654).

to_tableau() → MatGF2[source]

Convert the CliffordMap into its binary tableau representation.

The returned tableau is a (2n, 2n + 1) binary matrix over GF(2), where n is the number of qubits. The first n rows correspond to the images of X generators, and the next n rows correspond to the images of Z generators. Columns encode the X and Z components of the resulting Pauli strings, along with a sign column.

Each PauliString in x_map and z_map is decomposed into its X/Z support: - X contributes a 1 to the X block. - Z contributes a 1 to the Z block. - Y contributes a 1 to both X and Z blocks. The sign of the Pauli string is stored in the final column (0 for Sign.PLUS and 1 for Sign.MINUS).

Returns:

A binary matrix of shape (2n, 2n + 1) representing the Clifford tableau.

Return type:

MatGF2

Raises:

NotImplementedError – If the number of input nodes differs from the number of output nodes (i.e., the map is an isometry instead of a square Clifford).

__init__(x_map: Sequence[PauliString], z_map: Sequence[PauliString], input_nodes: Sequence[int], output_nodes: Sequence[int]) → None
class Command[source]

Bases: object

Grouping of all commands for namespace exposure.

Notes

This class is not meant to be instantiated, but rather serves as a namespace for all command types. The type alias for “any command” is CommandKind.

class N(node: int, state: State = <factory>)

Bases: BaseN, _KindChecker

Preparation command.

Parameters:
  • node (int) – Index of the qubit to prepare.

  • state (State, optional) – Initial state, defaults to PLUS.

__init__(node: int, state: State = <factory>) → None
kind: ClassVar[Literal[CommandKind.N]] = 1
state: State
class M(node: int, measurement: Measurement = Measurement.X, s_domain: set[int] = <factory>, t_domain: set[int] = <factory>)

Bases: BaseM, _KindChecker

Measurement command.

Parameters:
  • node (int) – Node index of the measured qubit.

  • measurement (Measurement) – Measurement description.

  • s_domain (set[int], optional) – Domain for the X byproduct operator.

  • t_domain (set[int], optional) – Domain for the Z byproduct operator.

__init__(node: int, measurement: Measurement = Measurement.X, s_domain: set[int] = <factory>, t_domain: set[int] = <factory>) → None
clifford(clifford_gate: Clifford) → M

Return a new measurement command with a Clifford applied.

Parameters:

clifford_gate (Clifford) – Clifford gate to apply before the measurement.

Returns:

Equivalent command representing the pattern MC.

Return type:

M

kind: ClassVar[Literal[CommandKind.M]] = 2
map(f: Callable[[Measurement], Measurement]) → M

Return a measurement command where the function f has been applied to the measurement.

Parameters:

f (Callable[[Measurement], Measurement]) – Function applied to the measurement.

Returns:

The resulting command.

Return type:

M

measurement: Measurement = Measurement.X
reindex(f: Callable[[Node], Node]) → M

Return a command whose nodes have been reindexed using f.

s_domain: set[int]
t_domain: set[int]
class E(nodes: tuple[int, int])

Bases: _KindChecker, BaseCommand

Entanglement command between two qubits.

Parameters:

nodes (tuple[int, int]) – Pair of nodes to entangle.

__init__(nodes: tuple[int, int]) → None
kind: ClassVar[Literal[CommandKind.E]] = 3
reindex(f: Callable[[Node], Node]) → E

Return a command whose nodes have been reindexed using f.

nodes: tuple[int, int]
class C(node: int, clifford: Clifford)

Bases: _KindChecker, BaseCommand

Local Clifford gate command.

Parameters:
  • node (int) – Node index on which to apply the gate.

  • clifford (Clifford) – Clifford operator to apply.

__init__(node: int, clifford: Clifford) → None
kind: ClassVar[Literal[CommandKind.C]] = 4
reindex(f: Callable[[Node], Node]) → C

Return a command whose nodes have been reindexed using f.

node: int
clifford: Clifford
class X(node: int, domain: set[int] = <factory>)

Bases: _KindChecker, BaseCommand

X correction command.

Parameters:
  • node (int) – Node to correct.

  • domain (set[int], optional) – Domain for the byproduct operator.

__init__(node: int, domain: set[int] = <factory>) → None
kind: ClassVar[Literal[CommandKind.X]] = 5
reindex(f: Callable[[Node], Node]) → X

Return a command whose nodes have been reindexed using f.

node: int
domain: set[int]
class Z(node: int, domain: set[int] = <factory>)

Bases: _KindChecker, BaseCommand

Z correction command.

Parameters:
  • node (int) – Node to correct.

  • domain (set[int], optional) – Domain for the byproduct operator.

__init__(node: int, domain: set[int] = <factory>) → None
kind: ClassVar[Literal[CommandKind.Z]] = 6
reindex(f: Callable[[Node], Node]) → Z

Return a command whose nodes have been reindexed using f.

node: int
domain: set[int]
class S(node: int, domain: set[int] = <factory>)

Bases: _KindChecker, BaseCommand

S command.

Parameters:
  • node (int) – Node for the byproduct operator.

  • domain (set[int], optional) – Domain on which to apply the operator.

__init__(node: int, domain: set[int] = <factory>) → None
kind: ClassVar[Literal[CommandKind.S]] = 7
reindex(f: Callable[[Node], Node]) → S

Return a command whose nodes have been reindexed using f.

node: int
domain: set[int]
class T

Bases: _KindChecker, BaseCommand

T command.

Parameters:

None – The T command acts globally without parameters.

__init__() → None
kind: ClassVar[Literal[CommandKind.T]] = 8
reindex(f: Callable[[Node], Node]) → T

Return a command whose nodes have been reindexed using f.

__init__() → None[source]
class ConstBranchSelector(result: Literal[0, 1])[source]

Bases: BranchSelector

Branch selector with a constant measurement outcome.

The value result is returned for every qubit.

Parameters:

result (Outcome) – The fixed measurement outcome for all qubits.

result: Literal[0, 1]
measure(qubit: int, f_expectation0: Callable[[], float], rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Return the constant measurement outcome result for any qubit.

__init__(result: Literal[0, 1]) → 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 DepolarisingNoiseModel(prepare_error_prob: float = 0.0, x_error_prob: float = 0.0, z_error_prob: float = 0.0, entanglement_error_prob: float = 0.0, measure_channel_prob: float = 0.0, measure_error_prob: float = 0.0)[source]

Bases: NoiseModel

Depolarising noise model.

Parameters:

NoiseModel (class) – Parent abstract class class:NoiseModel

__init__(prepare_error_prob: float = 0.0, x_error_prob: float = 0.0, z_error_prob: float = 0.0, entanglement_error_prob: float = 0.0, measure_channel_prob: float = 0.0, measure_error_prob: float = 0.0) → None[source]
input_nodes(nodes: Iterable[int], rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Return the noise to apply to input nodes.

command(cmd: CommandOrNoise, rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Return the noise to apply to the command cmd.

confuse_result(cmd: BaseM, result: Outcome, rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Assign wrong measurement result cmd = “M”.

class DrawPatternAnnotations(*values)[source]

Bases: Enum

Enumeration to indicate the possible annotations for Pattern.draw.

Flow = 1
XZCorrections = 2
class FixedBranchSelector(results: _T, default: BranchSelector | None = None)[source]

Bases: BranchSelector, Generic[_T]

Branch selector with predefined measurement outcomes.

The mapping is fixed in results. By default, an error is raised if a qubit is measured without a predefined outcome. However, another branch selector can be specified in default to handle such cases.

Parameters:
  • results (Mapping[int, bool]) – A dictionary mapping qubits to their measurement outcomes. If a qubit is not present in this mapping, the default branch selector is used.

  • default (BranchSelector | None, optional) – Branch selector to use for qubits not present in results. If None, an error is raised when an unmapped qubit is measured. Default is None.

results: _T
default: BranchSelector | None = None
measure(qubit: int, f_expectation0: Callable[[], float], rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Return the predefined measurement outcome of qubit, if available.

If the qubit is not present in results, the default branch selector is used. If no default is provided, an error is raised.

__init__(results: _T, default: BranchSelector | None = None) → None
class GFlow(og: OpenGraph[_AM_co], correction_function: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]], _CF_PREFIX: str = 'g')[source]

Bases: PauliFlow[_PM_co], Generic[_PM_co]

An unmutable subclass of PauliFlow providing a representation of a generalised flow (gflow).

This class differs from its parent class in the following:
  • It cannot be constructed from OpenGraph[Axis] instances, since the gflow is only defined for planar measurements.

  • The extraction of XZ-corrections from the gflow does not require knowledge on the partial order.

  • The method GFlow.is_well_formed() verifies the definition of gflow (Definition 2.36 in Ref. [1]).

References

[1] Backens et al., Quantum 5, 421 (2021), doi.org/10.22331/q-2021-03-25-421

classmethod from_correctionmatrix_or_none(correction_matrix: CorrectionMatrix[_PM_co]) → Self | None[source]

Initialize a GFlow object from a matrix encoding a correction function.

Parameters:

correction_matrix (CorrectionMatrix[_PM_co]) – Algebraic representation of the correction function.

Returns:

A gflow if it exists, None otherwise.

Return type:

Self | None

Notes

This method verifies if there exists a partial measurement order on the input open graph compatible with the input correction matrix. See Lemma 3.12, and Theorem 3.1 in Ref. [1]. Failure to find a partial order implies the non-existence of a generalised flow if the correction matrix was calculated by means of Algorithms 2 and 3 in [1].

References

[1] Mitosek and Backens, 2024 (arXiv:2410.23439).

to_xzcorrections() → XZCorrections[_PM_co][source]

Compute the XZ-corrections induced by the generalised flow encoded in self.

Return type:

XZCorrections[_PM_co]

Notes

  • This function partially implements Theorem 2 in Ref. [1]. The generated XZ-corrections can be used to obtain a robustly deterministic pattern on the underlying open graph.

  • Contrary to the overridden method in the parent class, here we do not need any information on the partial order to build the corrections since a valid correction function \(g\) guarantees that both \(g(i)\setminus \{i\}\) and \(Odd(g(i))\) are in the future of \(i\).

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

check_well_formed() → None[source]

Verify if the generalised flow is well formed.

Raises:

FlowError – if the gflow is not well formed.

Notes

General properties of flows:
  • The domain of the correction function is \(O^c\), the non-output nodes of the open graph.

  • The image of the correction function is a subset of \(I^c\), the non-input nodes of the open graph.

  • The nodes in the partial order are the nodes in the open graph.

  • The first layer of the partial order layers contains the outputs of the open graph if there are any.

Specific properties of gflows:
  • If \(j \in g(i), i \neq j\), then \(i \prec j\) (G1).

  • If \(j \in Odd(g(i)), i \neq j\), then \(i \prec j\) (G2).

  • If \(\lambda(i) = XY\), then \(i \notin g(i)`\) and \(i \in Odd((g(i)))\) (G3).

  • If \(\lambda(i) = XZ\), then \(i \in g(i)`\) and \(i \in Odd((g(i)))\) (G4).

  • If \(\lambda(i) = YZ\), then \(i \in g(i)`\) and \(i \notin Odd((g(i)))\) (G5),

where \(i \in O^c\), \(g\) is the correction function, \(prec\) denotes the partial order, \(\lambda(i)\) is the measurement plane of node \(i\), and \(Odd(s)\) is the odd neighbourhood of the set \(s\) in the open graph.

See Definition 2.36 in Ref. [1].

References

[1] Backens et al., Quantum 5, 421 (2021), doi.org/10.22331/q-2021-03-25-421

node_measurement_label(node: int) → Plane[source]

Get the measurement label of a given node in the open graph.

This method interprets measurements with a Pauli angle as Plane instances, in consistence with the gflow extraction routine.

Parameters:

node (int)

Return type:

Plane

__init__(og: OpenGraph[_AM_co], correction_function: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]], _CF_PREFIX: str = 'g') → None
class GraphState(*args, backend=None, **kwargs)[source]

Bases: Graph

Graph state simulator implemented with networkx.

Performs Pauli measurements on graph states.

ref: M. Elliot, B. Eastin & C. Caves, JPhysA 43, 025301 (2010) and PRA 77, 042307 (2008)

Each node has attributes:
hollow:

True if node is hollow (has local H operator)

sign:

True if node has negative sign (local Z operator)

loop:

True if node has loop (local S operator)

__init__(nodes: Iterable[int] | None = None, edges: Iterable[tuple[int, int]] | None = None, vops: Mapping[int, Clifford] | None = None) → None[source]

Instantiate a graph simulator.

Parameters:
  • nodes (Iterable[int]) – A container of nodes

  • edges (Iterable[tuple[int, int]]) – list of tuples (i,j) for pairs to be entangled.

  • vops (Mapping[int, Clifford]) – dict of local Clifford gates with keys for node indices and Cliffords

add_nodes_from(nodes_for_adding: Iterable[int | tuple[int, MBQCGraphNode]], **attr: Any) → None[source]

Wrap networkx.Graph.add_nodes_from to initialize MBQCGraphNode attributes.

add_node(node_for_adding: int, **attr: Any) → None[source]

Wrap networkx.Graph.add_node to initialize MBQCGraphNode attributes.

local_complement(node: int) → None[source]

Perform local complementation of a graph.

apply_vops(vops: Mapping[int, Clifford]) → None[source]

Apply local Clifford operators to the graph state from a dictionary.

Parameters:

vops (Mapping[int, Clifford]) – dict containing node indices as keys and local Clifford

Return type:

None

extract_vops() → dict[int, Clifford][source]

Apply local Clifford operators to the graph state from a dictionary.

Returns:

vops – dict containing node indices as keys and local Cliffords

Return type:

dict[int, Clifford]

flip_fill(node: int) → None[source]

Flips the fill (local H) of a node.

Parameters:

node (int) – graph node to flip the fill

Return type:

None

flip_sign(node: int) → None[source]

Flip the sign (local Z) of a node.

Note that application of Z gate is different from flip_sign if there exist an edge from the node.

Parameters:

node (int) – graph node to flip the sign

Return type:

None

advance(node: int) → None[source]

Flip the loop (local S) of a node.

If the loop already exist, sign is also flipped, reflecting the relation SS=Z. Note that application of S gate is different from advance if there exist an edge from the node.

Parameters:

node (int) – graph node to advance the loop.

Return type:

None

h(node: int) → None[source]

Apply H gate to a qubit (node).

Parameters:

node (int) – graph node to apply H gate

Return type:

None

s(node: int) → None[source]

Apply S gate to a qubit (node).

Parameters:

node (int) – graph node to apply S gate

Return type:

None

z(node: int) → None[source]

Apply Z gate to a qubit (node).

Parameters:

node (int) – graph node to apply Z gate

Return type:

None

equivalent_graph_e1(node: int) → None[source]

Tranform a graph state to a different graph state representing the same stabilizer state.

This rule applies only to a node with loop.

Parameters:

node1 (int) – A graph node with a loop to apply rule E1

Return type:

None

equivalent_graph_e2(node1: int, node2: int) → None[source]

Tranform a graph state to a different graph state representing the same stabilizer state.

This rule applies only to two connected nodes without loop.

Parameters:
  • node1 (int) – connected graph nodes to apply rule E2

  • node2 (int) – connected graph nodes to apply rule E2

Return type:

None

equivalent_fill_node(node: int) → int[source]

Fill the chosen node by graph transformation rules E1 and E2.

If the selected node is hollow and isolated, it cannot be filled and warning is thrown.

Parameters:

node (int) – node to fill.

Returns:

result – if the selected node is hollow and isolated, result is 1. if filled and isolated, 2. otherwise it is 0.

Return type:

int

measure_x(node: int, choice: Outcome = 0) → Outcome[source]

Perform measurement in X basis.

According to original paper, we realise X measurement by applying H gate to the measured node before Z measurement.

Parameters:
  • node (int) – qubit index to be measured

  • choice (int, 0 or 1) – choice of measurement outcome. observe (-1)^choice

Returns:

result – measurement outcome. 0 or 1.

Return type:

int

measure_y(node: int, choice: Outcome = 0) → Outcome[source]

Perform measurement in Y basis.

According to original paper, we realise Y measurement by applying S,Z and H gate to the measured node before Z measurement.

Parameters:
  • node (int) – qubit index to be measured

  • choice (int, 0 or 1) – choice of measurement outcome. observe (-1)^choice

Returns:

result – measurement outcome. 0 or 1.

Return type:

int

measure_z(node: int, choice: Outcome = 0) → Outcome[source]

Perform measurement in Z basis.

To realize the simple Z measurement on undecorated graph state, we first fill the measured node (remove local H gate)

Parameters:
  • node (int) – qubit index to be measured

  • choice (int, 0 or 1) – choice of measurement outcome. observe (-1)^choice

Returns:

result – measurement outcome. 0 or 1.

Return type:

int

draw(fill_color: str = 'C0') → None[source]

Draw decorated graph state.

Negative nodes are indicated by negative sign of node labels.

Parameters:

fill_color (str) – optional, fill color of nodes

to_statevector() → Statevector[source]

Convert the graph state into a state vector.

isolated_nodes() → list[int][source]

Return a list of isolated nodes (nodes with no edges).

class Instruction[source]

Bases: object

Grouping of all instructions for namespace exposure.

Notes

This class is not meant to be instantiated, but rather serves as a namespace for all instructions except RZZ. The type alias for “any command” is InstructionKind.

class I(target: int)

Bases: _KindChecker, SingleTargetInstruction

I circuit instruction.

The identity gate applies the matrix \(\left[\begin{matrix}1 & 0\\\mathrm 0 & 1\end{matrix}\right]\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.I]] = 12
class X(target: int)

Bases: _KindChecker, SingleTargetInstruction

X circuit instruction.

The X gate applies the matrix \(\left[\begin{matrix}0 & 1\\1 & 0\end{matrix}\right]\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.X]] = 8
class Y(target: int)

Bases: _KindChecker, SingleTargetInstruction

Y circuit instruction.

The Y gate applies the matrix \(\left[\begin{matrix}0 & -\mathrm i\\\mathrm i & 0\end{matrix}\right]\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.Y]] = 9
class Z(target: int)

Bases: _KindChecker, SingleTargetInstruction

Z circuit instruction.

The Z gate applies the matrix \(\left[\begin{matrix}1 & 0\\\mathrm 0 & -1\end{matrix}\right]\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.Z]] = 10
class H(target: int)

Bases: _KindChecker, SingleTargetInstruction

H circuit instruction.

The Hadamard gate applies the matrix \(\frac 1 {\sqrt 2}\left[\begin{matrix}1 & 1\\1 & -1\end{matrix}\right]\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.H]] = 6
class S(target: int)

Bases: _KindChecker, SingleTargetInstruction

S circuit instruction.

The S gate applies the matrix \(\left[\begin{matrix}1 & 0\\0 & \mathrm i\end{matrix}\right]\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.S]] = 7
class SDG(target: int)

Bases: _KindChecker, SingleTargetInstruction

SDG circuit instruction.

The \(S^\dagger\) gate applies the matrix \(\left[\begin{matrix}1 & 0\\0 & - \mathrm i\end{matrix}\right]\).

We have \(S^\dagger = \mathrm e^{\mathrm i \frac \pi 4} R_Z(-\frac \pi 2)\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.SDG]] = 17
class T(target: int)

Bases: _KindChecker, SingleTargetInstruction

T circuit instruction.

The \(T\) gate applies the matrix \(\left[\begin{matrix}1 & 0\\0 & \mathrm e^{\mathrm i \frac \pi 4}\end{matrix}\right]\).

We have \(T = \mathrm e^{\mathrm i \frac \pi 8} R_Z(\frac \pi 4)\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.T]] = 18
class TDG(target: int)

Bases: _KindChecker, SingleTargetInstruction

TDG circuit instruction.

The \(T^\dagger\) gate applies the matrix \(\left[\begin{matrix}1 & 0\\0 & \mathrm e^{- \mathrm i \frac \pi 4}\end{matrix}\right]\).

We have \(T^\dagger = \mathrm e^{\mathrm i \frac \pi 8} R_Z(- \frac \pi 4)\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.TDG]] = 19
class SX(target: int)

Bases: _KindChecker, SingleTargetInstruction

SX circuit instruction.

The \(SX\) (\(\sqrt X\)) gate applies the matrix \(\frac 1 2 \left[\begin{matrix}1 + \mathrm i & 1 - \mathrm i\\1 - \mathrm i & 1 + \mathrm i\end{matrix}\right]\).

We have \(SX = \mathrm e^{\mathrm i \frac \pi 4} R_X(\frac \pi 2)\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.SX]] = 20
class SXDG(target: int)

Bases: _KindChecker, SingleTargetInstruction

SXDG circuit instruction.

The \(SX^\dagger\) (\({\sqrt X}^\dagger\)) gate applies the matrix \(\frac 1 2 \left[\begin{matrix}1 - \mathrm i & 1 + \mathrm i\\1 + \mathrm i & 1 - \mathrm i\end{matrix}\right]\).

We have \(SX^\dagger = \mathrm e^{\mathrm i \frac \pi 4} R_X(-\frac \pi 2)\).

__init__(target: int) → None
kind: ClassVar[Literal[InstructionKind.SXDG]] = 21
class J(target: int, angle: Expression | float)

Bases: _KindChecker, RotationInstruction

J circuit instruction.

The \(J(\theta)\) gate applies the matrix

\[\begin{split}\frac 1 {\sqrt 2} \left[\begin{matrix} 1 & \mathrm e^{\mathrm i \theta}\\ 1 & - \mathrm e^{\mathrm i \theta} \end{matrix}\right]\end{split}\]

We have \(J(\theta) = \mathrm e^{\mathrm i \theta/2} H \mathrm{RZ}(\theta)\).

__init__(target: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.J]] = 11
class P(target: int, angle: Expression | float)

Bases: _KindChecker, RotationInstruction

P rotation circuit instruction.

The \(P(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0\\ 0 & \mathrm e^{\mathrm i \theta} \end{matrix}\right]\end{split}\]

We have \(P(\theta) = \mathrm e^{\theta/2} R_Z(\theta)\).

__init__(target: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.P]] = 23
class RX(target: int, angle: Expression | float)

Bases: _KindChecker, RotationInstruction

X rotation circuit instruction.

The \(RX(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} \cos \frac \theta 2 & -\mathrm i \sin \frac \theta 2\\ -\mathrm i \sin \frac \theta 2 & \cos \frac \theta 2 \end{matrix}\right]\end{split}\]
__init__(target: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.RX]] = 14
class RY(target: int, angle: Expression | float)

Bases: _KindChecker, RotationInstruction

Y rotation circuit instruction.

The \(RY(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} \cos \frac \theta 2 & - \sin \frac \theta 2\\ \sin \frac \theta 2 & \cos \frac \theta 2 \end{matrix}\right]\end{split}\]
__init__(target: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.RY]] = 15
class RZ(target: int, angle: Expression | float)

Bases: _KindChecker, RotationInstruction

Z rotation circuit instruction.

The \(RZ(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} \mathrm e^{-\mathrm i \frac \theta 2} & 0\\ 0 & \mathrm e^{\mathrm i \frac \theta 2} \end{matrix}\right]\end{split}\]
__init__(target: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.RZ]] = 16
class U(target: int, theta: Expression | float, phi: Expression | float, lambda_: Expression | float)

Bases: _KindChecker, BaseInstruction

U circuit instruction.

The \(U(\theta, \phi, \lambda)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} \cos \frac \theta 2 & - \mathrm e^{\mathrm i\lambda} \sin \frac \theta 2 \\ \mathrm e^{\mathrm i \phi} \sin \frac \theta 2 & \mathrm e^{\mathrm i (\phi + \lambda)} \cos \frac \theta 2 \end{matrix}\right]\end{split}\]

It can be decomposed as

\[U(\theta, \phi, \lambda) = \mathrm e^{\mathrm i\frac{\phi + \lambda}{2}} R_Z(\phi) R_Y(\theta) R_Z(\lambda) = \mathrm e^{\mathrm i\frac{\theta}{2}} H J\left(\phi + \frac{\pi}{2}\right) J(\theta) J\left(\lambda - \frac{\pi}{2}\right)\]
__init__(target: int, theta: Expression | float, phi: Expression | float, lambda_: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.U]] = 24
visit(visitor: InstructionVisitor, *, copy: bool = False) → Self

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

target: int
theta: Expression | float
phi: Expression | float
lambda_: Expression | float
class CJ(target: int, control: int, angle: Expression | float)

Bases: _KindChecker, ControlledRotationInstruction

Controlled-J circuit instruction.

The \(CJ(\alpha)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & \frac 1 {\sqrt 2} & \frac 1 {\sqrt 2} \mathrm e^{\mathrm i \alpha}\\ 0 & 0 & \frac 1 {\sqrt 2} & - \frac 1 {\sqrt 2} \mathrm e^{\mathrm i \alpha} \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

Following Lemmas 4.3 and 5.1 of Barenco et al. (1995), we define:

\[\begin{split}\begin{aligned} A &= R_Y\left(\frac \pi 4\right),\\ B &= R_Y\left(- \frac \pi 4\right) R_Z(- \delta),\\ C &= R_Z(\delta),\\ \delta &= \frac {\alpha + \pi} 2 \end{aligned}\end{split}\]

These operators satisfy \(ABC = I\) and \(AXBXC = \mathrm e^{-\mathrm i \delta} J(\alpha)\).

Consequently, \(CJ(\alpha)\) can be decomposed as:

\[CJ(\alpha) = (P(\delta) \otimes I) \, (I \otimes A) \, CX \, (I \otimes B) \, CX \, (I \otimes C)\]
control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

angle

Rotation angle.

Type:

ParameterizedAngle

References

Barenco, A., Bennett, C. H., Cleve, R., DiVincenzo, D. P., Margolus, N., Shor, P., Sleator, T., Smolin, J. A., & Weinfurter, H. (1995). Elementary gates for quantum computation. Physical Review A, 52(5), 3457-3467. https://doi.org/10.1103/physreva.52.3457

__init__(target: int, control: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.CJ]] = 25
class CP(target: int, control: int, angle: Expression | float)

Bases: _KindChecker, ControlledRotationInstruction

Controlled-P circuit instruction.

The \(CP(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & \mathrm e^{\mathrm i \theta} \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

angle

Phase angle.

Type:

ParameterizedAngle

__init__(target: int, control: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.CP]] = 26
class CRX(target: int, control: int, angle: Expression | float)

Bases: _KindChecker, ControlledRotationInstruction

Controlled-X rotation circuit instruction.

The \(CRX(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & \cos \frac \theta 2 & -\mathrm i \sin \frac \theta 2\\ 0 & 0 & -\mathrm i \sin \frac \theta 2 & \cos \frac \theta 2 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

angle

Rotation angle.

Type:

ParameterizedAngle

__init__(target: int, control: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.CRX]] = 27
class CRY(target: int, control: int, angle: Expression | float)

Bases: _KindChecker, ControlledRotationInstruction

Controlled-Y rotation circuit instruction.

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & \cos \frac \theta 2 & - \sin \frac \theta 2\\ 0 & 0 & \sin \frac \theta 2 & \cos \frac \theta 2 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

angle

Rotation angle.

Type:

ParameterizedAngle

__init__(target: int, control: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.CRY]] = 28
class CRZ(target: int, control: int, angle: Expression | float)

Bases: _KindChecker, ControlledRotationInstruction

Controlled-Z rotation circuit instruction.

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & \mathrm e^{-\mathrm i \frac \theta 2} & 0\\ 0 & 0 & 0 & \mathrm e^{\mathrm i \frac \theta 2} \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

angle

Rotation angle.

Type:

ParameterizedAngle

__init__(target: int, control: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.CRZ]] = 29
class CU(control: int, target: int, theta: Expression | float, phi: Expression | float, lambda_: Expression | float, gamma: Expression | float)

Bases: _KindChecker, BaseInstruction

Controlled-U circuit instruction.

The \(CU(\theta, \phi, \lambda, \gamma)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & \mathrm e^{\mathrm i\gamma} \cos\left(\frac{\theta}{2}\right) & -\mathrm e^{\mathrm i(\gamma + \lambda)} \sin\left(\frac{\theta}{2}\right) \\ 0 & 0 & \mathrm e^{\mathrm i(\gamma + \phi)} \sin\left(\frac{\theta}{2}\right) & \mathrm e^{\mathrm i(\gamma + \phi + \lambda)} \cos\left(\frac{\theta}{2}\right) \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

It can be decomposed as

\[CU(\theta, \phi, \lambda, \gamma) = \left(P\left(\frac{\gamma - \theta} 2\right) \otimes I\right) CJ(0) CJ\left(\phi + \frac \pi 2\right) CJ(\theta) CJ\left(\lambda - \frac \pi 2\right)\]
control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

theta

Rotation angle around the Y axis.

Type:

ParameterizedAngle

phi

Rotation angle around the Z axis after the Y rotation.

Type:

ParameterizedAngle

lambda_

Rotation angle around the Z axis before the Y rotation.

Type:

ParameterizedAngle

gamma

Global phase angle.

Type:

ParameterizedAngle

__init__(control: int, target: int, theta: Expression | float, phi: Expression | float, lambda_: Expression | float, gamma: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.CU]] = 30
visit(visitor: InstructionVisitor, *, copy: bool = False) → Self

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

control: int
target: int
theta: Expression | float
phi: Expression | float
lambda_: Expression | float
gamma: Expression | float
class CNOT(target: int, control: int)

Bases: _KindChecker, ControlledSingleTargetInstruction

CNOT circuit instruction.

The CNOT gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

__init__(target: int, control: int) → None
kind: ClassVar[Literal[InstructionKind.CNOT]] = 3
class CY(target: int, control: int)

Bases: _KindChecker, ControlledSingleTargetInstruction

CY circuit instruction.

The CY gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & -\mathrm i\\ 0 & 0 & \mathrm i & 0 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

__init__(target: int, control: int) → None
kind: ClassVar[Literal[InstructionKind.CY]] = 22
class CZ(targets: tuple[int, int])

Bases: _KindChecker, BaseInstruction

CZ circuit instruction.

The CZ gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & -1 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order targets[0], targets[1].

targets

Index of the target qubits.

Type:

tuple[int, int]

__init__(targets: tuple[int, int]) → None
kind: ClassVar[Literal[InstructionKind.CZ]] = 5
visit(visitor: InstructionVisitor, *, copy: bool = False) → CZ

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

targets: tuple[int, int]
class CCX(target: int, controls: tuple[int, int])

Bases: _KindChecker, BaseInstruction

Toffoli circuit instruction.

The CCX gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order controls[0], controls[1], target.

controls

Index of the control qubits.

Type:

tuple[int, int]

target

Index of the target qubit.

Type:

int

__init__(target: int, controls: tuple[int, int]) → None
kind: ClassVar[Literal[InstructionKind.CCX]] = 1
visit(visitor: InstructionVisitor, *, copy: bool = False) → CCX

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

target: int
controls: tuple[int, int]
class RZZ(target: int, control: int, angle: Expression | float)

Bases: _KindChecker, BaseInstruction

RZZ circuit instruction.

The \(\mathrm{RZZ}(\theta)\) gate applies the matrix

\[\begin{split}\left[\begin{matrix} \mathrm e^{-\mathrm i \frac \theta 2} & 0 & 0 & 0\\ 0 & \mathrm e^{\mathrm i \frac \theta 2} & 0 & 0\\ 0 & 0 & \mathrm e^{\mathrm i \frac \theta 2} & 0\\ 0 & 0 & 0 & \mathrm e^{-\mathrm i \frac \theta 2} \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, target.

We have \(\mathrm{RZZ}(\theta) = \mathrm{CNOT} (I \otimes \mathrm{RZ}(\theta)) \mathrm{CNOT}\).

control

Index of the control qubit.

Type:

int

target

Index of the target qubit.

Type:

int

angle

Rotation angle.

Type:

ParameterizedAngle

__init__(target: int, control: int, angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.RZZ]] = 2
visit(visitor: InstructionVisitor, *, copy: bool = False) → RZZ

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

target: int
control: int
angle: Expression | float
class SWAP(targets: tuple[int, int])

Bases: _KindChecker, BaseInstruction

SWAP circuit instruction.

The SWAP gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0\\ 0 & 0 & 1 & 0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 1 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order targets[0], targets[1].

targets

Index of the target qubits.

Type:

tuple[int, int]

__init__(targets: tuple[int, int]) → None
kind: ClassVar[Literal[InstructionKind.SWAP]] = 4
visit(visitor: InstructionVisitor, *, copy: bool = False) → SWAP

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

targets: tuple[int, int]
class CSWAP(control: int, targets: tuple[int, int])

Bases: _KindChecker, BaseInstruction

CSWAP circuit instruction.

The CSWAP gate applies the matrix

\[\begin{split}\left[\begin{matrix} 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \end{matrix}\right]\end{split}\]

in the computational basis. The basis states use big-endian ordering, with the most significant qubit first. The qubits are numbered in the order control, targets[0], targets[1].

control

Index of the control qubit.

Type:

int

targets

Indices of the two target qubits.

Type:

tuple[int, int]

__init__(control: int, targets: tuple[int, int]) → None
kind: ClassVar[Literal[InstructionKind.CSWAP]] = 31
visit(visitor: InstructionVisitor, *, copy: bool = False) → CSWAP

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

control: int
targets: tuple[int, int]
class M(target: int, axis: Axis)

Bases: _KindChecker, BaseInstruction

M circuit instruction.

__init__(target: int, axis: Axis) → None
kind: ClassVar[Literal[InstructionKind.M]] = 13
visit(visitor: InstructionVisitor, *, copy: bool = False) → M

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

target: int
axis: Axis
class GPHASE(angle: Expression | float)

Bases: _KindChecker, BaseInstruction

GPHASE circuit instruction.

angle

Phase angle.

Type:

ParameterizedAngle

__init__(angle: Expression | float) → None
kind: ClassVar[Literal[InstructionKind.GPHASE]] = 32
visit(visitor: InstructionVisitor, *, copy: bool = False) → GPHASE

Rewrite the instruction according to the given visitor.

Parameters:
  • visitor (InstructionVisitor) – The visitor specifying the rewriting.

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

Returns:

The rewritten instruction. Equal to self if copy is False.

Return type:

Self

angle: Expression | float
__init__() → None[source]
class KrausChannel(kraus_data: Iterable[KrausData])[source]

Bases: object

Quantum channel class in the Kraus representation.

Defined by Kraus operators \(K_i\) with scalar prefactors coef) \(c_i\), where the channel act on density matrix as \(\rho' = \sum_i K_i^\dagger \rho K_i\). The data should satisfy \(\sum K_i^\dagger K_i = I\).

__init__(kraus_data: Iterable[KrausData]) → None[source]

Initialize KrausChannel given a Kraus operator.

Parameters:

kraus_data (Iterable[KrausData]) – Iterable of Kraus operator data.

Raises:

ValueError – If kraus_data is empty.

property nqubit: int

Return the number of qubits.

class Measurement[source]

Bases: AbstractMeasurement, Parameterizable

An MBQC measurement.

Base class for BlochMeasurement and PauliMeasurement.

This class contains three class variables X, Y, and Z for the positive Pauli measurements on the three axes, and three static methods XY, YZ, and XZ, each parameterized by an angle and returning a Bloch measurement on each of the three planes.

The three static methods XY, YZ, and XZ are capitalized, contrary to what PEP 8 prescribes, to remain consistent with the names of the Pauli measurements and to match the names of the planes in the Plane enum.

X: ClassVar[PauliMeasurement] = Measurement.X
Y: ClassVar[PauliMeasurement] = Measurement.Y
Z: ClassVar[PauliMeasurement] = Measurement.Z
static XY(angle: Expression | float) → BlochMeasurement[source]

Return a Bloch measurement on the XY plane.

static YZ(angle: Expression | float) → BlochMeasurement[source]

Return a Bloch measurement on the YZ plane.

static XZ(angle: Expression | float) → BlochMeasurement[source]

Return a Bloch measurement on the XZ plane.

abstractmethod to_bloch() → BlochMeasurement[source]

Return the measurement description as an angle and a plane on the Bloch sphere.

There is no unique Bloch representation for each Pauli measurement. For instance,

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0.5).to_pauli_or_none() == Measurement.YZ(0.5).to_pauli_or_none() == Measurement.Y
True

This method follows the convention illustrated below:

>>> from graphix.measurements import PauliMeasurement
>>> for pm in PauliMeasurement:
...     print(f"{pm}.to_bloch() == {pm.to_bloch()}")
+X.to_bloch() == Measurement.XY(0)
-X.to_bloch() == Measurement.XY(1)
+Y.to_bloch() == Measurement.XY(0.5)
-Y.to_bloch() == Measurement.XY(1.5)
+Z.to_bloch() == Measurement.YZ(0)
-Z.to_bloch() == Measurement.YZ(1)
abstractmethod downcast_bloch() → BlochMeasurement[source]

Return the measurement description if it is already given as an angle and a plane on the Bloch sphere; raise TypeError otherwise.

Examples

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0).downcast_bloch()
Measurement.XY(0)
>>> Measurement.X.downcast_bloch()
Traceback (most recent call last):
    ...
TypeError: Bloch measurement expected, but Pauli measurement was found.
abstractmethod to_pauli_or_none(rel_tol: float = 1e-09, abs_tol: float = 0.0) → PauliMeasurement | None[source]

Return the measurement description as a Pauli measurement if possible, or None otherwise.

Parameters:
  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

If self is already an instance of PauliMeasurement, the function returns self. If self is an instance of BlochMeasurement, then either the measurement is close to a Pauli measurement (i.e., the angle is close to an integer multiple of π/2) and the corresponding Pauli measurement is returned, or it is not and None is returned.

Return type:

PauliMeasurement | None

Notes

A measurement with a parameterized angle is not considered as Pauli, but can become a Pauli measurement after substitution.

Examples

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0.5).to_pauli_or_none()
Measurement.Y
>>> Measurement.Y.to_pauli_or_none()
Measurement.Y
>>> Measurement.XY(0.25).to_pauli_or_none() is None
True
>>> from graphix.parameter import Placeholder
>>> alpha = Placeholder("alpha")
>>> Measurement.XY(alpha).to_pauli_or_none() is None
True
>>> Measurement.XY(alpha).with_parameter(alpha, 0.5).to_pauli_or_none()
Measurement.Y
abstractmethod to_pauli_or_bloch(rel_tol: float = 1e-09, abs_tol: float = 0.0) → PauliMeasurement | BlochMeasurement[source]

Return the measurement description as a Pauli measurement if possible, a Bloch measurement otherwise.

Parameters:
  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

If self is already an instance of PauliMeasurement, the function returns self. If self is an instance of BlochMeasurement, then either the measurement is close to a Pauli measurement (i.e., the angle is close to an integer multiple of π/2) and the corresponding Pauli measurement is returned, or it is not and self is returned.

Return type:

PauliMeasurement | BlochMeasurement

Examples

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0.5).to_pauli_or_bloch()
Measurement.Y
>>> Measurement.XY(0.25).to_pauli_or_bloch()
Measurement.XY(0.25)
__init__() → None
class NoiseModel[source]

Bases: ABC

Abstract base class for all noise models.

abstractmethod input_nodes(nodes: Iterable[int], rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Return the noise to apply to input nodes.

abstractmethod command(cmd: CommandOrNoise, rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Return the noise to apply to the command cmd.

abstractmethod confuse_result(cmd: BaseM, result: Outcome, rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Return a possibly flipped measurement outcome.

Parameters:
  • result (Outcome) – Ideal measurement result.

  • cmd (BaseM) – The measurement command that produced the given outcome.

Returns:

Possibly corrupted result.

Return type:

Outcome

transpile(sequence: Iterable[CommandOrNoise], rng: Generator | None = None, *, stacklevel: int = 1) → list[CommandOrNoise][source]

Apply the noise to a sequence of commands and return the resulting sequence.

class OpenGraph(graph: nx.Graph[int], input_nodes: Sequence[int], output_nodes: Sequence[int], measurements: Mapping[int, _AM_co], output_cliffords: Mapping[int, Clifford] = <factory>)[source]

Bases: Parameterizable, Simulable[_AM_co], Generic[_AM_co]

An unmutable dataclass providing a representation of open graph states.

graph

The underlying resource-state graph. Nodes represent qubits and edges represent the application of \(CZ\) gate on the linked nodes.

Type:

networkx.Graph[int]

input_nodes

An ordered sequence of node labels corresponding to the open graph inputs.

Type:

Sequence[int]

output_nodes

An ordered sequence of node labels corresponding to the open graph outputs.

Type:

Sequence[int]

measurements

A mapping between the non-output nodes of the open graph (key) and their corresponding measurement label (value). Measurement labels can be specified as Measurement, Plane or Axis instances.

Type:

Mapping[int, _AM_co]

output_cliffords

A mapping between output nodes of the open graph (key) and Clifford operators. This attribute allows to preserve the semantics of (deterministic) patterns with local-clifford commands when extracting the open graph. See Pattern.to_opengraph().

Type:

Mapping[int, Clifford]

Notes

The inputs and outputs of OpenGraph instances in Graphix are defined as ordered sequences of node labels. This contrasts the usual definition of open graphs in the literature, where inputs and outputs are unordered sets of nodes labels. This restriction facilitates the interplay with Pattern objects, where the order of input and output nodes represents a choice of Hilbert space basis.

Example

>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>>
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {i: Measurement.XY(0.5 * i) for i in range(2)}
>>> input_nodes = [0]
>>> output_nodes = [2]
>>> og = OpenGraph(graph, input_nodes, output_nodes, measurements)
graph: nx.Graph[int]
input_nodes: Sequence[int]
output_nodes: Sequence[int]
measurements: Mapping[int, _AM_co]
output_cliffords: Mapping[int, Clifford]
to_pattern(*, stacklevel: int = 1) → Pattern[source]

Extract a deterministic pattern from an OpenGraph[Measurement] if it exists.

Parameters:

stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

A deterministic pattern on the open graph.

Return type:

Pattern

Raises:

OpenGraphError – If the open graph does not have flow.

Notes

  • The open graph instance must be of parametric type Measurement to allow for a pattern extraction, otherwise it does not contain information about the measurement angles.

  • This method proceeds by searching a flow on the open graph and converting it into a pattern as prescripted in Ref. [1_to_pattern]. It first attempts to find a causal flow because the corresponding flow-finding algorithm has lower complexity. If it fails, it attemps to find a Pauli flow because this property is more general than a generalised flow, and the corresponding flow-finding algorithms have the same complexity in the current implementation.

References

[1_to_pattern]

Browne et al., NJP 9, 250 (2007)

draw(**options: Unpack[DrawKwargs]) → None[source]

Visualize the opengraph.

Parameters:

options (Unpack[DrawKwargs]) – Options controlling graph visualization. See VisualizationOptions.

map(f: Callable[[_A], _B]) → OpenGraph[_B][source]

Apply a function to every measurement of the open graph and return the resulting open graph.

Parameters:

f (Callable[[_A], _B]) – Function applied to each measurement.

Returns:

The resulting open graph.

Return type:

OpenGraph[_B]

Notes

The types of measurements can be changed by f.

Example

>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {0: Measurement.XY(0.5), 1: Measurement.YZ(1)}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> og.map(lambda m: m.plane).measurements
{0: Plane.XY, 1: Plane.YZ}
to_bloch() → OpenGraph[BlochMeasurement][source]

Return the open graph where all measurements are converted to Bloch.

Example

>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {0: Measurement.X, 1: Measurement.Y}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> og.to_bloch().measurements
{0: Measurement.XY(0), 1: Measurement.XY(0.5)}
downcast_bloch() → OpenGraph[BlochMeasurement][source]

Return the open graph if all measurements are described as Bloch measurements; raise TypeError otherwise.

If this method succeeds, the returned graph is identical to the original one, but this function narrows the static type of the measurements. The returned graph can be used with functions that require all measurements are described as Bloch measurements, such as OpenGraph.to_causalflow().

Example

>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {0: Measurement.XY(0.5), 1: Measurement.YZ(1)}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> og.downcast_bloch().measurements
{0: Measurement.XY(0.5), 1: Measurement.YZ(1)}
>>> measurements = {0: Measurement.X, 1: Measurement.Y}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> og.downcast_bloch()
Traceback (most recent call last):
    ...
TypeError: Bloch measurement expected, but Pauli measurement was found.
infer_pauli_measurements(rel_tol: float = 1e-09, abs_tol: float = 0.0) → OpenGraph[Measurement][source]

Return an equivalent open graph in which Bloch measurements close to a Pauli measurement are replaced by Pauli measurements.

Pauli measurements are measurements with a Pauli angle, i.e. an integer multiple of \(\pi/2\).

This method can be used before calling methods such as OpenGraph.to_pauliflow_or_none, that deal with Pauli measurements in a special way.

Parameters:
  • pattern (pattern) – Source pattern.

  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

An equivalent open graph in which Bloch measurements close to a Pauli measurement are replaced by Pauli measurements.

Return type:

Pattern

Example

>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {0: Measurement.XY(0.5), 1: Measurement.YZ(1)}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> og.infer_pauli_measurements().measurements
{0: Measurement.Y, 1: -Measurement.Z}
isclose(other: OpenGraph[_AM_co], rel_tol: float = 1e-09, abs_tol: float = 0.0) → bool[source]

Check if two open graphs are equal within a given tolerance.

Parameters:
  • other (OpenGraph[_AM_co])

  • rel_tol (float) – Relative tolerance. Optional, defaults to 1e-09.

  • abs_tol (float) – Absolute tolerance. Optional, defaults to 0.0.

Returns:

True if the two open graphs are approximately equal.

Return type:

bool

Notes

This method verifies the open graphs have:
  • Truly equal underlying graphs (not up to an isomorphism).

  • Equal input and output nodes.

  • Same measurement planes or axes and approximately equal measurement angles if the open graph is of parametric type Measurement.

  • Equal output_cliffords mappings.

The static typer does not allow an isclose comparison of two open graphs with different parametric type. For a structural comparison, see OpenGraph.is_equal_structurally().

is_equal_structurally(other: OpenGraph[AbstractMeasurement]) → bool[source]

Compare the underlying structure of two open graphs.

Parameters:

other (OpenGraph[AbstractMeasurement])

Returns:

True if self and og have the same structure.

Return type:

bool

Notes

This method verifies the open graphs have:

  • Truly equal underlying graphs (not up to an isomorphism).

  • Equal input and output nodes. This assumes equal types as well, i.e., if self.input_nodes is a list and other.input_nodes is a tuple, this method will return False.

  • Equal output_cliffords mappings.

It assumes the open graphs are well formed.

The static typer allows comparing the structure of two open graphs with different parametric type.

neighbors(nodes: Collection[int]) → set[int][source]

Return the set containing the neighborhood of a set of nodes in the open graph.

Parameters:

nodes (Collection[int]) – Set of nodes whose neighborhood is to be found

Returns:

neighbors_set – Neighborhood of set nodes.

Return type:

set[int]

odd_neighbors(nodes: Collection[int]) → set[int][source]

Return the set containing the odd neighborhood of a set of nodes in the open graph.

Parameters:

nodes (Collection[int]) – Set of nodes whose odd neighborhood is to be found

Returns:

odd_neighbors_set – Odd neighborhood of set nodes.

Return type:

set[int]

to_causalflow() → CausalFlow[_PM_co][source]

Try to extract a causal flow on the open graph.

This method is a wrapper over OpenGraph.to_causalflow_or_none() with a single return type.

This method requires all measurements to be planar: to extract the causal flow from a graph that contains Pauli measurements, you can apply OpenGraph.to_bloch() first.

Returns:

A causal flow object if the open graph has causal flow.

Return type:

CausalFlow[_PM_co]

Raises:

OpenGraphError – If the open graph does not have a causal flow.

to_gflow() → GFlow[_PM_co][source]

Try to extract a maximally delayed generalised flow (gflow) on the open graph.

This method is a wrapper over OpenGraph.to_gflow_or_none() with a single return type.

This method requires all measurements to be planar: to extract a gflow from a graph that contains Pauli measurements, you can apply OpenGraph.to_bloch() first.

Returns:

A gflow object if the open graph has gflow.

Return type:

GFlow[_PM_co]

Raises:

OpenGraphError – If the open graph does not have a gflow.

to_pauliflow(*, stacklevel: int = 1) → PauliFlow[_AM_co][source]

Try to extract a maximally delayed Pauli on the open graph.

This method is a wrapper over OpenGraph.to_pauliflow_or_none() with a single return type.

Parameters:

stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

A Pauli flow object if the open graph has Pauli flow.

Return type:

PauliFlow[_AM_co]

Raises:

OpenGraphError – If the open graph does not have a Pauli flow.

to_causalflow_or_none() → CausalFlow[_PM_co] | None[source]

Return a causal flow on the open graph if it exists.

This method requires all measurements to be planar: to find the causal flow from a graph that contains Pauli measurements, you can apply OpenGraph.to_bloch() first.

Returns:

A causal flow object if the open graph has causal flow or None otherwise.

Return type:

CausalFlow[_PM_co] | None

Notes

  • The open graph instance must be of parametric type Measurement or Plane since the causal flow is only defined on open graphs with \(XY\) measurements.

  • This function implements the algorithm presented in Ref. [1] with polynomial complexity on the number of nodes, \(O(N^2)\).

References

[1] Mhalla and Perdrix, (2008), Finding Optimal Flows Efficiently, doi.org/10.1007/978-3-540-70575-8_70

to_gflow_or_none() → GFlow[_PM_co] | None[source]

Return a maximally delayed Pauli on the open graph if it exists.

This method requires all measurements to be planar: to find a gflow in a graph that contains Pauli measurements, you can apply OpenGraph.to_bloch() first.

Returns:

A gflow object if the open graph has gflow or None otherwise.

Return type:

GFlow[_PM_co] | None

Notes

  • The open graph instance must be of parametric type Measurement or Plane since the gflow is only defined on open graphs with planar measurements. Measurement instances with a Pauli angle (integer multiple of \(\pi/2\)) are interpreted as Plane instances, in contrast with OpenGraph.to_pauliflow_or_none().

  • This function implements the algorithm presented in Ref. [1] with polynomial complexity on the number of nodes, \(O(N^3)\).

References

[1] Mitosek and Backens, 2024 (arXiv:2410.23439).

to_pauliflow_or_none(*, stacklevel: int = 1) → PauliFlow[_AM_co] | None[source]

Return a maximally delayed Pauli on the open graph if it exists.

Parameters:

stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

A Pauli flow object if the open graph has Pauli flow or None otherwise.

Return type:

PauliFlow[_AM_co] | None

Notes

  • Measurements are considered to be Pauli only if they are represented as graphix.measurements.PauliMeasurement. The method OpenGraph.infer_pauli_measurements() can be called first to convert all measurements with a Pauli angle (integer multiple of \(\pi/2\)) into Pauli measurements.

  • This function implements the algorithm presented in Ref. [1] with polynomial complexity on the number of nodes, \(O(N^3)\).

References

[1] Mitosek and Backens, 2024 (arXiv:2410.23439).

Example

>>> import warnings
>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> graph = nx.Graph([(0, 1), (1, 2)])
>>> measurements = {0: Measurement.XZ(0.5), 1: Measurement.XZ(0.5)}
>>> og = OpenGraph(graph, [0], [2], measurements)
>>> with warnings.catch_warnings():
...     warnings.filterwarnings("ignore", message="Open graph with non-inferred Pauli measurements.")
...     og.to_pauliflow()
Traceback (most recent call last):
    ...
graphix.opengraph.OpenGraphError: The open graph does not have a Pauli flow.
>>> og.infer_pauli_measurements().measurements
{0: Measurement.X, 1: Measurement.X}
>>> str(og.infer_pauli_measurements().to_pauliflow())
'p(0) = {1}, p(1) = {2}; {0, 1} < {2}'
to_circuit(pexp_cp: Callable[[PauliExponentialDAG, Circuit], None] | None = None, cm_cp: Callable[[CliffordMap, Circuit], None] | None = None, *, stacklevel: int = 1) → Circuit[source]

Extract a unitary in the form of a circuit from an open graph resource state.

This method acts as a wrapper around the circuit extraction routine, simplifying its usage. It first attempts to extract the Pauli flow of the open graph, then applies the circuit extraction procedure described in Ref. [1_to_circuit], and finally compiles the resulting circuit using the provided passes. To obtain the open graph’s unitary in the form of a Pauli exponential DAG along with a Clifford transformation, as presented in Ref. [1_to_circuit], one should instead operate directly on the flow object using PauliFlow.extract_circuit().

Parameters:
Returns:

Quantum circuit represented as a set of instructions.

Return type:

Circuit

Notes

  • The open graph instance must be of parametric type Measurement to allow for a circuit extraction, otherwise it does not contain information about the measurement angles.

  • This wrapper extracts a Pauli flow rather than a gflow, as the former is more general while the underlying extraction algorithms have the same computational complexity in both cases. The resulting unitary is identical whether it is obtained from a GFlow or from a PauliFlow with inferred Pauli measurements. However, compilation passes that simultaneously diagonalize Pauli exponentials within the same layer of the Pauli exponential DAG may benefit from flows of lower depth, which is often the case for Pauli flow. The pass graphix.circ_ext.compilation.pexp_ladder_pass() does not take into account the flow’s depth.

References

[1_to_circuit] (1,2)

Simmons, 2021 (arXiv:2109.05654).

Examples

>>> import warnings
>>> import networkx as nx
>>> from graphix.opengraph import OpenGraph
>>> from graphix.measurements import Measurement
>>> og = OpenGraph(
...     graph=nx.Graph([(0, 1), (1, 2), (3, 4), (4, 5), (6, 7), (7, 8), (1, 3), (4, 6)]),
...     input_nodes=(0, 3, 6),
...     output_nodes=(2, 5, 8),
...     measurements=dict.fromkeys((0, 1, 3, 4, 6, 7), Measurement.XY(angle=0)),
... )
>>> with warnings.catch_warnings():
...     warnings.filterwarnings("ignore", message="Open graph with non-inferred Pauli measurements.")
...     og.to_circuit()
Circuit(width=3, instr=[H(2), H(1), CNOT(2, 1), H(1), H(1), H(0), CNOT(2, 0), CNOT(1, 0), H(2), H(1), H(0)])
>>> # The default compilation passes do not exploit the lower depth of the Pauli flow
>>> # compared the gflow.
>>> og.infer_pauli_measurements().to_circuit()
Circuit(width=3, instr=[H(2), H(1), CNOT(2, 1), H(1), H(1), H(0), CNOT(2, 0), CNOT(1, 0), H(2), H(1), H(0)])
compose(other: OpenGraph[_AM_co], mapping: Mapping[int, int]) → tuple[OpenGraph[_AM_co], dict[int, int]][source]

Compose two open graphs by merging subsets of nodes from self and other, and relabeling the nodes of other that were not merged.

Parameters:
  • other (OpenGraph[_AM_co]) – Open graph to be composed with self.

  • mapping (dict[int, int]) – Partial relabelling of the nodes in other, with keys and values denoting the old and new node labels, respectively.

Returns:

  • og (OpenGraph[_AM_co]) – Composed open graph.

  • mapping_complete (dict[int, int]) – Complete relabelling of the nodes in other, with keys and values denoting the old and new node label, respectively.

Notes

Let’s denote \(\{G(V_1, E_1), I_1, O_1\}\) the open graph self, \(\{G(V_2, E_2), I_2, O_2\}\) the open graph other, \(\{G(V, E), I, O\}\) the resulting open graph og and {v:u} an element of mapping.

We define \(V, U\) the set of nodes in mapping.keys() and mapping.values(), and \(M = U \cap V_1\) the set of merged nodes.

The open graph composition requires that

  • \(V \subseteq V_2\).

  • If both v and u are measured, the corresponding measurements must have the same plane and angle.

The returned open graph follows this convention:

  • \(I = (I_1 \cup I_2) \setminus M \cup (I_1 \cap I_2 \cap M)\),

  • \(O = (O_1 \cup O_2) \setminus M \cup (O_1 \cap O_2 \cap M)\),

  • If only one node of the pair {v:u} is measured, this measure is assigned to \(u \in V\) in the resulting open graph.

  • Input (and, respectively, output) nodes in the returned open graph have the order of the open graph self followed by those of the open graph other. Merged nodes are removed, except when they are input (or output) nodes in both open graphs, in which case, they appear in the order they originally had in the graph self.

  • Clifford operations on output nodes:

    • if two output nodes \(o_1\) and \(o_2\) are merged, then \(C(o_1) = C_2(o_2) \circ C_1(o_1)\);

    • if an output node \(o_1\) is merged with a measured node \(m_2\), then \(M(o_1) = M_2(m_2) \circ C_1(o_1)\);

    • if a measured node \(m_1\) is merged with an output node \(o_2\), then \(M(m_1) = M_1(m_1) \circ C_2(o_2)\).

with_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat) → OpenGraph[_M][source]

Substitute a parameter with a value or expression in all measurement angles.

Creates a new open graph where every measurement angle containing the specified variable is updated using the provided substitution. The original open graph instance remains unmodified.

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.

Returns:

A new instance of OpenGraph with the updated measurement parameters.

Return type:

OpenGraph[Measurement]

Notes

Substitution relies on object identity. You must provide the exact parameter object instance currently stored in the measurements. Passing a new object with the same name will not trigger a substitution if it is not the same instance in memory.

Examples

>>> import networkx as nx
>>> from graphix.fundamentals import Plane
>>> from graphix.measurements import Measurement
>>> from graphix.opengraph import OpenGraph
>>> from graphix.parameter import Placeholder
>>> # Initialize placeholders and open graph
>>> parametric_angles = [Placeholder(f"alpha{i}") for i in range(2)]
>>> measurements = {node: Measurement.XY(angle) for node, angle in enumerate(parametric_angles)}
>>> og = OpenGraph(
...     graph=nx.Graph([(0, 1), (1, 2)]),
...     input_nodes=[0],
...     output_nodes=[2],
...     measurements=measurements,
... )
>>> # To perform substitution, use the actual object in memory
>>> new_og = og.with_parameter(parametric_angles[0], 0.3)
>>> # Note: og.with_parameter(Placeholder("alpha0"), 0.3) would not trigger any substitution.
with_parameters(assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) → OpenGraph[_M][source]

Perform parallel substitution of multiple parameters in measurement angles.

Creates a new open graph where occurrences of parameters defined in the assignment mapping are replaced by their corresponding values. The original open graph instance remains unmodified.

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.

Returns:

A new instance of OpenGraph with the updated measurement angles.

Return type:

OpenGraph[Measurement]

Notes

The notes provided in self.subs() apply here as well.

Examples

>>> import networkx as nx
>>> from graphix.fundamentals import Plane
>>> from graphix.measurements import Measurement
>>> from graphix.opengraph import OpenGraph
>>> from graphix.parameter import Placeholder
>>> # Initialize placeholders
>>> alpha = Placeholder("alpha")
>>> beta = Placeholder("beta")
>>> measurements = {0: Measurement.XY(alpha), 1: Measurement.XY(beta)}
>>> og = OpenGraph(nx.Graph([(0, 1)]), [0], [], measurements)
>>> # Substitute multiple parameters at once
>>> subs_map = {alpha: 0.5, beta: 1.2}
>>> new_og = og.with_parameters(subs_map)
__init__(graph: nx.Graph[int], input_nodes: Sequence[int], output_nodes: Sequence[int], measurements: Mapping[int, _AM_co], output_cliffords: Mapping[int, Clifford] = <factory>) → None
class OutputFormat(*values)[source]

Bases: Enum

Enumeration of the output format for pretty-printing.

ASCII = 1
LaTeX = 2
Unicode = 3
class Pattern(input_nodes: Iterable[int] | None = None, cmds: Iterable[CommandType] | None = None, output_nodes: Iterable[int] | None = None)[source]

Bases: InplaceParameterizable, Simulable[Measurement]

MBQC pattern class.

Pattern holds a sequence of commands to operate the MBQC (Pattern.seq), and provide modification strategies to improve the structure and simulation efficiency of the pattern accoring to measurement calculus.

ref: V. Danos, E. Kashefi and P. Panangaden. J. ACM 54.2 8 (2007)

__init__(input_nodes: Iterable[int] | None = None, cmds: Iterable[CommandType] | None = None, output_nodes: Iterable[int] | None = None) → None[source]

Construct a pattern.

Parameters:
  • input_nodes (Iterable[int] | None) – Optional. List of input qubits.

  • cmds (Iterable[CommandType] | None) – Optional. List of initial commands.

  • output_nodes (Iterable[int] | None) – Optional. List of output qubits.

add(cmd: CommandType) → None[source]

Add command to the end of the pattern.

An MBQC command is an instance of graphix.command.CommandType.

Parameters:

cmd (graphix.command.CommandType) – MBQC command.

extend(*cmds: CommandType | Iterable[CommandType]) → None[source]

Add sequences of commands.

Parameters:

cmds – sequences of commands

clear() → None[source]

Clear the sequence of pattern commands.

replace(cmds: list[CommandType], input_nodes: list[int] | None = None) → None[source]

Replace pattern with a given sequence of pattern commands.

Parameters:
  • cmds – list of commands

  • input_nodes – optional, list of input qubits (by default, keep the same input nodes as before)

node_mapping(mapping: Mapping[Node, Node] | None = None, *, start: int = 0, avoids: Collection[Node] | None = None, preserves: Collection[Node] | None = None) → dict[Node, Node][source]

Compute an injective mapping of node indices.

The resulting mapping can be passed to reindex(). Fresh indices are allocated in increasing order, starting at start and skipping any values already used.

Parameters:
  • mapping (Mapping[Node, Node] | None, optional) – An initial (partial) mapping that will be extended. A ValueError is raised if the supplied mapping is not injective.

  • start (int, optional) – The starting index from which fresh indices are allocated.

  • avoids (Collection[Node] | None, optional) – Indices that must not be assigned to a node.

  • preserves (Collection[Node] | None, optional) – Nodes that should not appear in the mapping. Their index will not be changed by reindex().

Returns:

A dictionary that maps each node of self (except those listed in preserves) to a distinct integer index.

Return type:

dict[Node, Node]

Notes

The resulting dictionary is guaranteed to be an extension of mapping, i.e., all entries from mapping appear in the resulting dictionary. In particular, the constraints specified in avoids and preserves only affect nodes that are freshly generated by the function; they do not apply to the nodes already present in mapping.

reindex(mapping: Callable[[Node], Node] | Mapping[Node, Node] | None = None, *, copy: bool = False) → Pattern[source]

Return a pattern whose nodes have been re-indexed using f.

This method does not verify that f is injective. The semantic of the pattern is only preserved when f is injective. A non-injective mapping can even break the runnability of the resulting pattern.

Parameters:
  • mapping (Callable[[Node], Node] | Mapping[Node, Node] | None, optional) – A function or a mapping that translates the current node indices to new ones. Indices that are not present in the mapping are left unchanged. If f is omitted, a default mapping is computed by using node_mapping().

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

Returns:

The re-indexed pattern. Equal to self if copy is False.

Return type:

Pattern

compose(other: Pattern, mapping: Mapping[int, int], preserve_mapping: bool = False) → tuple[Pattern, dict[int, int]][source]

Compose two patterns by merging subsets of outputs from self and a subset of inputs of other, and relabeling the nodes of other that were not merged.

Parameters:
  • other (Pattern) – Pattern to be composed with self.

  • mapping (Mapping[int, int]) – Partial relabelling of the nodes in other, with keys and values denoting the old and new node labels, respectively.

  • preserve_mapping (bool) – Boolean flag controlling the ordering of the output nodes in the returned pattern.

Returns:

  • p (Pattern) – composed pattern

  • mapping_complete (dict[int, int]) – Complete relabelling of the nodes in other, with keys and values denoting the old and new node label, respectively.

Notes

Let’s denote \((I_j, O_j, V_j, S_j)\) the ordered set of inputs and outputs, the computational space and the sequence of commands of pattern \(P_j\), respectively, with \(j = 1\) for pattern self and \(j = 2\) for pattern other. Let’s denote \(P\) the resulting pattern with \((I, O, V, S)\). Let’s denote \(K, U\) the sets of keys and values of mapping, \(M_1 = O_1 \cap U\) the set of merged outputs, and \(M_2 = \{k \in I_2 \cap K | k \rightarrow v, v \in M_1 \}\) the set of merged inputs.

The pattern composition requires that

  • \(K \subseteq V_2\).

  • For a pair \((k, v) \in (K, U)\)
    • \(U \cap V_1 \setminus O_1 = \emptyset\). If \(v \in O_1\), then \(k \in I_2\), otherwise an error is raised.

    • \(v\) can always satisfy \(v \notin V_1\), thereby allowing a custom relabelling.

The returned pattern follows this convention:

  • Nodes of pattern other not specified in mapping (i.e., \(V_2 \cap K^c\)) are relabelled in ascending order.

  • The sequence of the resulting pattern is \(S = S_2 S_1\), where nodes in \(S_2\) are relabelled according to mapping.

  • \(I = I_1 \cup (I_2 \setminus M_2)\).

  • \(O = (O_1 \setminus M_1) \cup O_2\).

  • Input (and, respectively, output) nodes in the returned pattern have the order of the pattern self followed by those of the pattern other. Merged nodes are removed.

  • If preserve_mapping = True and \(|M_1| = |I_2| = |O_2|\), then the outputs of the returned pattern are the outputs of pattern self, where the nth merged output is replaced by the output of pattern other corresponding to its nth input instead.

property input_nodes: list[int]

List input nodes.

property output_nodes: list[int]

List all nodes that are either input_nodes or prepared with N commands and that have not been measured with an M command.

property n_node: int

Count of nodes that are either input_nodes or prepared with N commands.

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

Arrange the order of output_nodes.

Parameters:

output_nodes (iterable of int) – output nodes order determined by user. each index corresponds to that of logical qubits.

reorder_input_nodes(input_nodes: Iterable[int]) → None[source]

Arrange the order of input_nodes.

Parameters:

input_nodes (iterable of int) – input nodes order determined by user. each index corresponds to that of logical qubits.

to_ascii(left_to_right: bool = False, limit: int | None = 40, target: Container[command.CommandKind] | None = None) → str[source]

Return the ASCII string representation of the pattern.

Parameters:
  • left_to_right (bool, optional) – If True, the first command will appear at the beginning of the resulting string. If False (the default), the first command will appear at the end of the string.

  • limit (int | None, optional) – If set to an int (default: 40), only first limit commands are printed, and an ellipsis is added at the end to indicate that some commands have been elided. If limit=None, there is no limit on the number of printed commands.

  • target (Container[command.CommandKind], optional) – If set, only commands of kinds specified in target are printed.

Examples

>>> from graphix import Circuit
>>> circuit = Circuit(1)
>>> circuit.h(0)
>>> pattern = circuit.transpile().pattern
>>> pattern.to_ascii()
'X(1,{0}) M(0,0) E(0,1) N(1)'
>>> pattern.to_ascii(left_to_right=True)
'N(1) E(0,1) M(0,0) X(1,{0})'
>>> from graphix.command import CommandKind
>>> pattern.to_ascii(target={CommandKind.M})
'M(0,0)'
>>> pattern.to_ascii(target={CommandKind.X, CommandKind.Z})
'X(1,{0})'
>>> circuit = Circuit(3)
>>> circuit.ccx(0, 1, 2)
>>> pattern = circuit.transpile().pattern
>>> pattern.to_ascii(limit=5)
'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28})...(98 more commands)'
>>> pattern.to_ascii(limit=5, left_to_right=True)
'N(3) E(2,3) M(2,0) N(4)...(98 more commands)'
>>> pattern.to_ascii(limit=None)
'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,...E(4,1) [M(3,0)]{2} E(3,4) N(4) M(2,0) E(2,3) N(3)'
to_latex(left_to_right: bool = False, limit: int | None = 40, target: Container[command.CommandKind] | None = None) → str[source]

Return a string containing the LaTeX representation of the pattern.

Parameters:
  • left_to_right (bool, optional) – If True, the first command will appear at the beginning of the resulting string. If False (the default), the first command will appear at the end of the string.

  • limit (int | None, optional) – If set to an int (default: 40), only first limit commands are printed, and an ellipsis is added at the end to indicate that some commands have been elided. If limit=None, there is no limit on the number of printed commands.

  • target (Container[command.CommandKind], optional) – If set, only commands of kinds specified in target are printed.

Examples

>>> from graphix import Circuit
>>> circuit = Circuit(1)
>>> circuit.h(0)
>>> pattern = circuit.transpile().pattern
>>> pattern.to_latex()
'\\(X_{1}^{0}\\,M_{0}^{0}\\,E_{0,1}\\,N_{1}\\)'
>>> pattern.to_latex(left_to_right=True)
'\\(N_{1}\\,E_{0,1}\\,M_{0}^{0}\\,X_{1}^{0}\\)'
>>> from graphix.command import CommandKind
>>> pattern.to_latex(target={CommandKind.M})
'\\(M_{0}^{0}\\)'
>>> pattern.to_latex(target={CommandKind.X, CommandKind.Z})
'\\(X_{1}^{0}\\)'
to_unicode(left_to_right: bool = False, limit: int | None = 40, target: Container[command.CommandKind] | None = None) → str[source]

Return the Unicode string representation of the pattern.

Parameters:
  • left_to_right (bool, optional) – If True, the first command will appear at the beginning of the resulting string. If False (the default), the first command will appear at the end of the string.

  • limit (int | None, optional) – If set to an int (default: 40), only first limit commands are printed, and an ellipsis is added at the end to indicate that some commands have been elided. If limit=None, there is no limit on the number of printed commands.

  • target (Container[command.CommandKind], optional) – If set, only commands of kinds specified in target are printed.

Examples

>>> from graphix import Circuit
>>> circuit = Circuit(1)
>>> circuit.h(0)
>>> pattern = circuit.transpile().pattern
>>> pattern.to_unicode()
'X₁⁰ M₀(0) E₀₋₁ N₁'
>>> pattern.to_unicode(left_to_right=True)
'N₁ E₀₋₁ M₀(0) X₁⁰'
>>> from graphix.command import CommandKind
>>> pattern.to_unicode(target={CommandKind.M})
'M₀(0)'
>>> pattern.to_unicode(target={CommandKind.X, CommandKind.Z})
'X₁⁰'
standardize() → None[source]

Execute standardization of the pattern.

‘standard’ pattern is one where commands are sorted in the order of ‘N’, ‘E’, ‘M’ and then byproduct commands (‘X’ and ‘Z’) and finally Clifford commands (‘C’).

is_standard(strict: bool = False) → bool[source]

Determine whether the command sequence is standard.

Parameters:

strict (bool, optional) – If True, ensures that C commands are the last ones.

Returns:

is_standard – True if the pattern is standard

Return type:

bool

shift_signals(method: str = 'direct') → dict[int, set[int]][source]

Perform signal shifting procedure.

Extract the t-dependence of the measurement into ‘S’ commands and commute them to the end of the command sequence where it can be removed. This procedure simplifies the dependence structure of the pattern.

Ref for the original ‘mc’ method:
  1. Danos, E. Kashefi and P. Panangaden. J. ACM 54.2 8 (2007)

Parameters:

method (str, optional) – ‘direct’ shift_signals is executed on a conventional Pattern sequence. ‘mc’ shift_signals is done using the original algorithm in the measurement calculus paper.

Returns:

signal_dict – For each node, the signal that have been shifted.

Return type:

dict[int, set[int]]

shift_signals_direct() → dict[int, set[int]][source]

Perform signal shifting procedure.

signals() → dict[int, set[int]][source]

Extract ‘t’ domain of measurement commands, turn them into signal ‘S’ commands and add to the command sequence.

This is used for shift_signals() method.

partial_order_layers() → tuple[frozenset[int], ...][source]

Extract the measurement order of the pattern in the form of layers.

This method standardizes the pattern, builds a directed acyclical graph (DAG) from measurement and correction domains, and then performs a topological sort.

Returns:

Measurement partial order between the pattern’s nodes in a layer form.

Return type:

tuple[frozenset[int], …]

Raises:

RunnabilityError – If the pattern is not runnable.

Notes

to_causalflow() → CausalFlow[BlochMeasurement][source]

Extract the causal flow structure from the current measurement pattern.

This method does not call the flow-extraction routine on the underlying open graph, but constructs the flow from the pattern corrections instead.

Returns:

The causal flow associated with the current pattern.

Return type:

CausalFlow[Measurement]

Raises:
  • FlowError – If the pattern: - Contains measurements in forbidden planes (XZ or YZ), - Is empty, or - Induces a correction function and a partial order which fail the well-formedness checks for a valid causal flow.

  • ValueError – If N commands in the pattern do not represent a \(|+\rangle\) state or if the pattern corrections form closed loops.

Notes

  • Applying the chain Pattern.to_causalflow().to_xzcorrections().to_pattern() to a strongly deterministic pattern returns a new pattern implementing the same unitary transformation. This equivalence holds as long as the original pattern contains no Clifford commands, since those are discarded during open-graph extraction.

  • This method requires that all the measurements in the pattern are represented as Bloch measurements (i.e., there are no PauliMeasurement`s). Use :meth:`to_bloch() to convert all Pauli measurements.

to_gflow() → GFlow[BlochMeasurement][source]

Extract the generalized flow (gflow) structure from the current measurement pattern.

This method does not call the flow-extraction routine on the underlying open graph, but constructs the gflow from the pattern corrections instead.

Returns:

The gflow associated with the current pattern.

Return type:

GFlow[Measurement]

Raises:
  • FlowError – If the pattern is empty or if the extracted structure does not satisfy the well-formedness conditions required for a valid gflow.

  • ValueError – If N commands in the pattern do not represent a \(|+\rangle\) state or if the pattern corrections form closed loops.

Notes

The notes provided in self.to_causalflow() apply here as well.

to_pauliflow() → PauliFlow[Measurement][source]

Extract the Pauli flow structure from the current measurement pattern.

This method does not call the flow-extraction routine on the underlying open graph, but reconstructs the Pauli flow from the pattern corrections instead (see graphix.flow.core.XZCorrections.to_pauliflow()). Contrary to to_causalflow() and to_gflow(), Pauli measurements are kept as axes rather than downcast to planar Bloch measurements, so that the Pauli-basis structure is preserved.

Returns:

The Pauli flow associated with the current pattern.

Return type:

PauliFlow[Measurement]

Raises:
  • FlowError – If the pattern is empty or if no Pauli flow is compatible with the pattern corrections.

  • ValueError – If N commands in the pattern do not represent a \(|+\rangle\) state or if the pattern corrections form closed loops.

Notes

The notes provided in self.to_causalflow() apply here as well.

to_xzcorrections() → XZCorrections[Measurement][source]

Extract the XZ-corrections from the current measurement pattern.

Returns:

The XZ-corrections associated with the current pattern.

Return type:

XZCorrections[Measurement]

Raises:
  • XZCorrectionsError – If the extracted correction dictionaries are not well formed.

  • ValueError – If N commands in the pattern do not represent a \(\ket{+}\) state or if the pattern corrections form closed loops.

Notes

To ensure that applying the chain Pattern.to_xzcorrections().to_pattern() to a strongly deterministic pattern returns a new pattern implementing the same unitary transformation, XZ-corrections must be extracted from a standardized pattern. This requirement arises for the same reason that flow extraction also operates correctly on standardized patterns only. This equivalence holds as long as the original pattern contains no Clifford commands, since those are discarded during open-graph extraction. See docstring in optimization.StandardizedPattern.to_gflow() for additional information.

sort_measurement_commands(meas_order: list[int]) → list[M][source]

Convert measurement order to sequence of measurement commands.

Parameters:

meas_order (list of int) – optimal measurement order.

Returns:

meas_cmds – sorted measurement commands

Return type:

list of command

measurement_commands() → dict[int, M][source]

Return a dictionary mapping nodes to measurement commands.

Returns:

meas_dict – measurement commands indexed by nodes

Return type:

dict[int, command.M]

max_degree() → int[source]

Get max degree of a pattern.

Returns:

max_degree – max degree of a pattern

Return type:

int

nodes() → set[int][source]

Return the set of nodes of the pattern.

isolated_nodes() → set[int][source]

Return the set of isolated nodes in the pattern.

Returns:

Set of the isolated nodes

Return type:

set[int]

to_opengraph() → OpenGraph[Measurement][source]

Extract the underlying resource-state open graph from the pattern.

This method standardizes the pattern first to guarantee that Clifford commands are properly encoded in the resulting open graph. Specifically, Cliffords acting on measured nodes are absorbed into measurements, while Cliffords acting on output nodes are stored in OpenGraph.output_cliffords.

Return type:

OpenGraph[Measurement]

clifford_commands() → dict[int, Clifford][source]

Extract Clifford commands.

Returns:

vops

Return type:

dict

connected_nodes(node: int, prepared: set[int] | None = None) → list[int][source]

Find nodes that are connected to a specified node.

These nodes must be in the statevector when the specified node is measured, to ensure correct computation. If connected nodes already exist in the statevector (prepared), then they will be ignored as they do not need to be prepared again.

Parameters:
  • node (int) – node index

  • prepared (list) – list of node indices, which are to be ignored

Returns:

node_list – list of nodes that are entangled with specified node

Return type:

list

correction_commands() → list[X | Z][source]

Return the list of byproduct correction commands.

parallelize_pattern() → None[source]

Optimize the pattern to reduce the depth of the computation by gathering measurement commands that can be performed simultaneously.

This optimized pattern runs efficiently on GPUs and quantum hardwares with depth (e.g. coherence time) limitations.

minimize_space(heuristics: Iterable[SpaceMinimizationHeuristic] | None = None, *, copy: bool = False) → Pattern[source]

Return a pattern that reduces the maximal space, i.e. the number of qubits simultaneously required to execute the pattern.

See graphix.space_minimization.minimize_space() for more information about the default heuristics.

To obtain a space-optimal pattern while preserving the measurement order, use minimize_space([]). This commutes the N and E commands so that the pattern is space-optimal among patterns with the same measurement order (see StandardizedPattern.to_space_optimal_pattern()).

Parameters:
  • heuristics (Iterable[SpaceMinimizationHeuristic] | None = None) – The heuristics to try in order. By default, DEFAULT_HEURISTICS is used.

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

Returns:

The optimized pattern. Equal to self if copy is False.

Return type:

Pattern

max_space() → int[source]

Compute the maximum number of nodes that must be present in the graph (graph space) during the execution of the pattern.

For statevector simulation, this is equivalent to the maximum memory needed for classical simulation.

Returns:

n_nodes – max number of nodes present in the graph during pattern execution.

Return type:

int

space_list() → list[int][source]

Return the list of the number of nodes present in the graph (space) during each step of execution of the pattern (for N and M commands).

Returns:

N_list – time evolution of ‘space’ at each ‘N’ and ‘M’ commands of pattern.

Return type:

list

to_pattern() → Pattern[source]

Return self.

remove_input_nodes() → None[source]

Remove the input nodes from the pattern and replace them with N commands.

This removes the possibility of choosing the input state, fixing the input state to the plus state. .. seealso:: graphix.command.N

draw(*, annotations: DrawPatternAnnotations | None = DrawPatternAnnotations.Flow, flow_from_pattern: bool = True, stacklevel: int = 1, **options: Unpack[DrawKwargs]) → None[source]

Visualize the underlying graph of the pattern with annotations.

Parameters:
  • annotations (DrawPatternAnnotations | None, default=DrawPatternAnnotations.Flow) –

    Annotations to be shown.
    • DrawPatternAnnotations.Flow (default): show the pattern’s flow if it exists.

    • DrawPatternAnnotations.XZCorrections: show the pattern’s XZ-corrections.

    • None: show the underlying open graph only.

  • flow_from_pattern (bool, default=True) – If True, the command sequence of the pattern is used to derive a flow structure. If False, only the underlying open graph is used.

  • options (Unpack[DrawKwargs]) – Options controlling graph visualization. See VisualizationOptions.

  • stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Raises:

PatternError – If the underlying open graph does not have flow.

Notes

If flow_from_pattern==True but the pattern is not compatible with a causal flow, gflow, or Pauli flow, an attempt to extract the flow from the underlying open graph will be made while warning the user.

to_qasm3(filename: Path | str, input_state: dict[int, State] | State = graphix.states.PlanarState(Plane.XY, 0)) → None[source]

Export measurement pattern to OpenQASM 3.0 file.

See graphix.qasm3_exporter.pattern_to_qasm3().

Parameters:
  • filename (Path | str) – File name to export to. Example: "filename.qasm".

  • input_state (dict[int, State] | State, default BasicStates.PLUS) – The initial state for each input node. Only |0⟩ or |+⟩ states are supported.

is_parameterized() → bool[source]

Return True if there is at least one measurement angle that is not just an instance of SupportsFloat.

A parameterized pattern is a pattern where at least one measurement angle is an expression that is not a number, typically an instance of sympy.Expr (but we don’t force to choose sympy here).

replace_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat, *, copy: bool = False) → Pattern[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) → Pattern[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

copy() → Pattern[source]

Return a copy of the pattern.

check_runnability() → None[source]

Check whether the pattern is runnable.

Raises RunnabilityError exception if it is not.

Notes

The runnability check can only guarantee the runnability of MBQC+LC patterns. Patterns that make use of custom :class:BaseN and :class:BaseM commands can have additional runnability constraints that are not checked by this method. For instance, in the Veriphix implementation of VBQC, blind measurements have hidden domains that cannot be checked.

apply(f: Callable[[Measurement], Measurement], *, copy: bool = False) → Pattern[source]

Apply the function f to each measurement.

Parameters:
  • f (Callable[[Measurement], Measurement]) – Function applied to each measurement.

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

Returns:

The resulting pattern. If copy is False, the result is self.

Return type:

Pattern

Example

>>> from graphix import Pattern, command
>>> from graphix.measurements import BlochMeasurement, Measurement
>>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.XZ(0.25))])
>>> pattern.apply(lambda m: BlochMeasurement(m.angle + 1, m.plane))
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(1.25))])
>>> pattern
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(1.25))])
map(f: Callable[[Measurement], Measurement]) → Pattern[source]

Return a pattern where the function f has been applied to each measurement.

Equivalent to self.apply(f, copy=True).

Parameters:

f (Callable[[Measurement], Measurement]) – Function applied to each measurement.

Returns:

The resulting pattern.

Return type:

Pattern

Example

>>> from graphix import Pattern, command
>>> from graphix.measurements import BlochMeasurement, Measurement
>>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.XZ(0.25))])
>>> pattern.map(lambda m: BlochMeasurement(m.angle + 1, m.plane))
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(1.25))])
>>> pattern
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(0.25))])
infer_pauli_measurements(*, rel_tol: float = 1e-09, abs_tol: float = 0.0, copy: bool = False) → Pattern[source]

Replace Bloch measurements close to a Pauli measurement by Pauli measurements.

Parameters:
  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

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

Returns:

A pattern in which Bloch measurements close to a Pauli measurement are replaced by Pauli measurements. If copy is False, the result is self.

Return type:

Pattern

Example

>>> from graphix import Pattern, command
>>> from graphix.measurements import BlochMeasurement, Measurement
>>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.XY(0.5))])
>>> pattern.infer_pauli_measurements()
Pattern(input_nodes=[0], cmds=[M(0, Measurement.Y)])
blochify(*, copy: bool = False) → Pattern[source]

Represent all measurements as Bloch measurements.

Parameters:

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

Returns:

A pattern in which all measurements are Bloch measurements. If copy is False, the result is self.

Return type:

Pattern

Example

>>> from graphix import Pattern, command
>>> from graphix.measurements import BlochMeasurement, Measurement
>>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.Y)])
>>> pattern.blochify()
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XY(0.5))])
>>> pattern
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XY(0.5))])
to_bloch() → Pattern[source]

Return an equivalent pattern in which all measurements are represented as Bloch measurements.

Equivalent to self.blochify(copy=True).

Example

>>> from graphix import Pattern, command
>>> from graphix.measurements import BlochMeasurement, Measurement
>>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.Y)])
>>> pattern.to_bloch()
Pattern(input_nodes=[0], cmds=[M(0, Measurement.XY(0.5))])
>>> pattern
Pattern(input_nodes=[0], cmds=[M(0, Measurement.Y)])
perform_pauli_pushing(*, leave_nodes: Set[int] | None = None, copy: bool = False, standardize: bool = False, stacklevel: int = 1) → Pattern[source]

Move Pauli measurements before the other measurements.

If you need to recover the cut between Pauli measurements and non-Pauli measurements or the shifted signal, you can use from_standardizedpattern() instead.

Parameters:
  • leave_nodes (AbstractSet[Node], optional) – Nodes that should not be moved. This constraint only applies to Pauli nodes and has no effect on non-Pauli nodes.

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

  • standardize (bool, optional) – If True, the pattern is returned in standardized form. The default is False: the nodes are prepared on a need-by-need basis, minimizing space usage.

  • stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

The pattern in which Pauli measurements have been moved before the other measurements. If copy is False, the result is self.

Return type:

Pattern

Notes

This function relies on StandardizedPattern.perform_pauli_pushing().

remove_pauli_measurements(*, copy: bool = False, standardize: bool = False, stacklevel: int = 1) → Pattern[source]

Remove non-input Pauli measurements from the given pattern.

See remove_pauli_measurements() for more information.

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

  • standardize (bool, optional) – If True, the pattern is returned in standardized form. The default is False: the nodes are prepared on a need-by-need basis, minimizing space usage.

  • stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

The pattern in which Pauli measurements have been moved before the other measurements. If copy is False, the result is self.

Return type:

Pattern

remove_local_clifford_commands(*, copy: bool = False) → Pattern[source]

Return an equivalent pattern where local Clifford commands have been replaced by MBQC commands.

See remove_local_clifford_commands() for more information.

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

  • stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

The pattern in which local Clifford commands have been replaced by MBQC commands. If copy is False, the result is self.

Return type:

Pattern

class Pauli(symbol: IXYZ = SingletonI.I, unit: ComplexUnit = ComplexUnit.ONE)[source]

Bases: object

Pauli gate: u * {I, X, Y, Z} where u is a complex unit.

Pauli gates can be multiplied with other Pauli gates (with @), with complex units and unit constants (with *), and can be negated.

symbol: IXYZ = 1
unit: ComplexUnit = 0
I: ClassVar[Pauli] = Pauli.I
X: ClassVar[Pauli] = Pauli.X
Y: ClassVar[Pauli] = Pauli.Y
Z: ClassVar[Pauli] = Pauli.Z
static from_axis(axis: Axis) → Pauli[source]

Return the Pauli associated to the given axis.

property axis: Axis

Return the axis associated to the Pauli.

Fails if the Pauli is identity.

property matrix: npt.NDArray[np.complex128]

Return the matrix of the Pauli gate.

eigenstate(binary: int = 0) → PlanarState[source]

Return the eigenstate of the Pauli.

static iterate(symbol_only: bool = False) → Iterator[Pauli][source]

Iterate over all Pauli gates.

Parameters:
  • (bool (symbol_only)

  • optional) (Exclude the unit in the iteration. Defaults to False.)

__init__(symbol: IXYZ = SingletonI.I, unit: ComplexUnit = ComplexUnit.ONE) → None
class PauliExponential(angle: Expression | float, pauli_string: PauliString)[source]

Bases: object

Dataclass representing a Pauli exponential.

A Pauli exponential corresponds to the unitary operator

\[U(\alpha) = \exp \left(i \frac{\alpha}{2} P\right),\]

where \(\alpha\) is a real-valued angle and \(P\) is a Pauli string.

angle

The Pauli exponential angle \(\alpha\) in units of \(\pi\). When extracted from a corrected node, it corresponds to the node’s measurement divided by two.

Type:

ParameterizedAngle

pauli_string

The signed Pauli string \(P\) specifying the tensor product of Pauli operators acting on qubit indices.

Type:

PauliString

angle: Expression | float
pauli_string: PauliString
static from_measured_node(flow: PauliFlow[Measurement], node: Node) → PauliExponential[source]

Extract the Pauli exponential of a measured node and its focused correction set.

Parameters:
  • flow (PauliFlow[Measurement]) – A focused Pauli flow. The resulting Pauli string is extracted from its correction function.

  • node (int) – A measured node whose associated Pauli string is computed.

Returns:

Primary extraction string associated to the input measured nodes. The sets in the returned PauliString instance are disjoint.

Return type:

PauliExponential

Notes

See Eq. (13) and Lemma 4.4 in Ref. [1]. The phase of the Pauli string is given by Eq. (37).

References

[1] Simmons, 2021 (arXiv:2109.05654).

__init__(angle: Expression | float, pauli_string: PauliString) → None
class PauliExponentialDAG(pauli_exponentials: Mapping[int, PauliExponential], partial_order_layers: Sequence[AbstractSet[int]], output_nodes: Sequence[int])[source]

Bases: object

Dataclass to represent a multi-qubit rotation formed by a sequence of Pauli exponentials extracted from a pattern.

pauli_exponentials

Mapping between measured nodes (keys) and Pauli exponentials (values).

Type:

Mapping[int, PauliExponential]

partial_order_layers

Partial order between the Pauli exponentials in a layer form. The set layers[i] comprises the nodes in layer i. Nodes in layer i are “larger” in the partial order than nodes in layer i+1. The pattern’s output nodes are always in layer 0.

Type:

Sequence[AbstractSet[int]]

output_nodes

Output nodes on which the Pauli exponential rotation acts.

Type:

Sequence[int]

Notes

See Definition 3.3 in Ref. [1].

The Pauli strings in the Pauli exponentials are defined on qubit indices which correspond to the indices of the sequence output_nodes.

References

[1] Simmons, 2021 (arXiv:2109.05654).

pauli_exponentials: Mapping[int, PauliExponential]
partial_order_layers: Sequence[AbstractSet[int]]
output_nodes: Sequence[int]
static from_focused_flow(flow: PauliFlow[Measurement]) → PauliExponentialDAG[source]

Extract a Pauli exponential rotation from a focused Pauli flow.

This routine associates a Pauli exponential to each measured node in flow. The flow’s partial order defines a partial order between the Pauli exponentials such that Pauli exponentials in the same layer commute.

Parameters:

flow (PauliFlow[Measurement]) – A focused Pauli flow.

Return type:

PauliExponentialDAG

Notes

See Definition 3.3 and Example C.13 in Ref. [1].

References

[1] Simmons, 2021 (arXiv:2109.05654).

__init__(pauli_exponentials: Mapping[int, PauliExponential], partial_order_layers: Sequence[AbstractSet[int]], output_nodes: Sequence[int]) → None
class PauliFlow(og: OpenGraph[_AM_co], correction_function: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]], _CF_PREFIX: str = 'p')[source]

Bases: Parameterizable, Simulable[_AM_co], Generic[_AM_co]

An unmutable dataclass providing a representation of a Pauli flow.

og

The open graph with respect to which the Pauli flow is defined.

Type:

OpenGraph[_AM_co]

correction_function

Pauli flow correction function. correction_function[i] is the set of qubits correcting the measurement of qubit i.

Type:

Mapping[int, AbstractSet[int]

partial_order_layers

Partial order between the open graph’s nodes in a layer form. The set layers[i] comprises the nodes in layer i. Nodes in layer i are “larger” in the partial order than nodes in layer i+1. Output nodes are always in layer 0 if there are any.

Type:

Sequence[AbstractSet[int]]

Notes

  • See Definition 5 in Ref. [1] for a definition of Pauli flow.

  • The flow’s correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only og and correction_function are necessary to initialize an PauliFlow instance (see PauliFlow.from_correctionmatrix_or_none()). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow’s XZ-corrections, so it is stored as an attribute.

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212). [2] Mitosek and Backens, 2024 (arXiv:2410.23439).

og: OpenGraph[_AM_co]
correction_function: Mapping[int, AbstractSet[int]]
partial_order_layers: Sequence[AbstractSet[int]]
classmethod from_correctionmatrix_or_none(correction_matrix: CorrectionMatrix[_AM_co]) → Self | None[source]

Initialize a PauliFlow object from a matrix encoding a correction function.

Parameters:

correction_matrix (CorrectionMatrix[_AM_co]) – Algebraic representation of the correction function.

Returns:

A Pauli flow if it exists, None otherwise.

Return type:

Self | None

Notes

This method verifies if there exists a partial measurement order on the input open graph compatible with the input correction matrix. See Lemma 3.12, and Theorem 3.1 in Ref. [1]. Failure to find a partial order implies the non-existence of a Pauli flow if the correction matrix was calculated by means of Algorithms 2 and 3 in [1].

References

[1] Mitosek and Backens, 2024 (arXiv:2410.23439).

to_xzcorrections() → XZCorrections[_AM_co][source]

Compute the X and Z corrections induced by the Pauli flow encoded in self.

Return type:

XZCorrections[_AM_co]

Notes

This method partially implements Theorem 4 in [1]. The generated X and Z corrections can be used to obtain a robustly deterministic pattern on the underlying open graph.

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

to_pattern(total_measurement_order: TotalOrder | None = None) → Pattern[source]

Generate a pattern from this PauliFlow[Measurement] instance.

See XZCorrections.to_pattern() for further details about the total_measurement_order parameter and the exceptions that may be raised.

is_well_formed() → bool[source]

Verify if flow is well formed.

This method is a wrapper over self.check_well_formed`() catching the ``FlowError` exceptions.

Return type:

True if self is a well-formed flow, False otherwise.

check_well_formed() → None[source]

Verify if the Pauli flow is well formed.

Raises:

FlowError – if the Pauli flow is not well formed.

Notes

General properties of flows:
  • The domain of the correction function is \(O^c\), the non-output nodes of the open graph.

  • The image of the correction function is a subset of \(I^c\), the non-input nodes of the open graph.

  • The nodes in the partial order are the nodes in the open graph.

  • The first layer of the partial order layers contains the outputs of the open graph if there are any.

Specific properties of Pauli flows:
  • If \(j \in p(i), i \neq j, \lambda(j) \notin \{X, Y\}\), then \(i \prec j\) (P1).

  • If \(j \in Odd(p(i)), i \neq j, \lambda(j) \notin \{Y, Z\}\), then \(i \prec j\) (P2).

  • If \(neg i \prec j, i \neq j, \lambda(j) = Y\), then either \(j \notin p(i)\) and \(j \in Odd((p(i)))\) or \(j \in p(i)\) and \(j \notin Odd((p(i)))\) (P3).

  • If \(\lambda(i) = XY\), then \(i \notin p(i)\) and \(i \in Odd((p(i)))\) (P4).

  • If \(\lambda(i) = XZ\), then \(i \in p(i)\) and \(i \in Odd((p(i)))\) (P5).

  • If \(\lambda(i) = YZ\), then \(i \in p(i)\) and \(i \notin Odd((p(i)))\) (P6).

  • If \(\lambda(i) = X\), then \(i \in Odd((p(i)))\) (P7).

  • If \(\lambda(i) = Z\), then \(i \in p(i)\) (P8).

  • If \(\lambda(i) = Y\), then either \(i \notin p(i)\) and \(i \in Odd((p(i)))\) or \(i \in p(i)\) and \(i \notin Odd((p(i)))\) (P9),

where \(i \in O^c\), \(c\) is the correction function, \(prec\) denotes the partial order, \(\lambda(i)\) is the measurement plane or axis of node \(i\), and \(Odd(s)\) is the odd neighbourhood of the set \(s\) in the open graph.

See Definition 5 in Ref. [1] or Definition 2.4 in Ref. [2].

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212). [2] Mitosek and Backens, 2024 (arXiv:2410.23439).

node_measurement_label(node: int) → Plane | Axis[source]

Get the measurement label of a given node in the open graph.

This method interprets measurements with a Pauli angle as Axis instances, in consistence with the Pauli flow extraction routine.

Parameters:

node (int)

Return type:

Plane | Axis

to_ascii(multiline: bool = False) → str[source]

Return an ASCII string representing the flow’s correction function and partial order layers.

Parameters:

multiline (bool (optional)) – Flag to format the output. If True a new line is printed after each correction set. Defaults to False.

Return type:

str

to_latex(multiline: bool = False) → str[source]

Return a string containing the LaTeX representation of the flow’s correction function and partial order layers.

See notes in to_ascii() for additional information.

to_unicode(multiline: bool = False) → str[source]

Return a Unicode string representing the flow’s correction function and partial order layers.

See notes in to_ascii() for additional information.

draw(**options: Unpack[DrawKwargs]) → None[source]

Visualize the opengraph, correction structure and partial order.

Parameters:

options (Unpack[DrawKwargs]) – Options controlling graph visualization. See VisualizationOptions.

with_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat) → _T_PauliFlowMeasurement[source]

Substitute a parameter with a value or expression in all measurement angles of the open graph.

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.

Returns:

A new instance of the flow object with the updated measurement parameters.

Return type:

_T_PauliFlowMeasurement

Notes

See notes and examples in OpenGraph.with_parameter().

with_parameters(assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) → _T_PauliFlowMeasurement[source]

Perform parallel substitution of multiple parameters in measurement angles of the open graph.

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.

Returns:

A new instance of the flow object with the updated measurement angles.

Return type:

_T_PauliFlowMeasurement

Notes

See notes and examples in OpenGraph.with_parameters().

is_focused() → bool[source]

Verify if the input Pauli flow is focused.

Returns:

True if the input Pauli flow is focused, False otherwise.

Return type:

bool

Notes

This function verifies Definition 4.3 in Ref. [1].

References

[1] Simmons, 2021 (arXiv:2109.05654).

property extraction_pauli_strings: dict[int, PauliString][source]

Compute the extraction Pauli strings associated with each node in the correction function.

This property requires the flow to be focused.

Returns:

A dictionary where the keys are node indices (from the correction function) and the values are the computed PauliString objects.

Return type:

dict[int, PauliString]

Raises:

ValueError – If the flow is not focused (i.e., self.is_focused() is False).

Notes

This property is cached; the dictionary is computed only once upon the first access and stored for subsequent calls. See notes in PauliString.from_measured_node for additional information.

extract_circuit(*, stacklevel: int = 1) → ExtractionResult[source]

Extract a circuit from a flow.

This routine assumes that the flow self is focused (see Notes).

Parameters:

stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

Wrapper over a Pauli-exponential DAG and a Clifford map encoding the linear transformation implemented by the input flow.

Return type:

ExtractionResult

Notes

References

[1] Simmons, 2021 (arXiv:2109.05654). [2] Mitosek and Backens, 2024 (arXiv:2410.23439).

__init__(og: OpenGraph[_AM_co], correction_function: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]], _CF_PREFIX: str = 'p') → None
class PauliMeasurement(axis: Axis, sign: Sign = Sign.PLUS)[source]

Bases: Measurement

Pauli measurement.

axis: Axis
sign: Sign = 1
to_pauli() → Pauli[source]

Return the Pauli gate.

This method returns an instance of Pauli and should not be confused with to_pauli_or_none(), which overrides the method from Measurement, and returns self.

Examples

>>> from graphix.measurements import Measurement
>>> Measurement.X.to_pauli()
Pauli.X
>>> (-Measurement.Y).to_pauli()
-Pauli.Y
to_bloch() → BlochMeasurement[source]

Return the measurement description as an angle and a plane on the Bloch sphere.

There is no unique Bloch representation for each Pauli measurement. For instance,

>>> from graphix.measurements import Measurement
>>> Measurement.XY(0.5).to_pauli_or_none() == Measurement.YZ(0.5).to_pauli_or_none() == Measurement.Y
True

This method follows the convention illustrated below:

>>> from graphix.measurements import PauliMeasurement
>>> for pm in PauliMeasurement:
...     print(f"{pm}.to_bloch() == {pm.to_bloch()}")
+X.to_bloch() == Measurement.XY(0)
-X.to_bloch() == Measurement.XY(1)
+Y.to_bloch() == Measurement.XY(0.5)
-Y.to_bloch() == Measurement.XY(1.5)
+Z.to_bloch() == Measurement.YZ(0)
-Z.to_bloch() == Measurement.YZ(1)
downcast_bloch() → BlochMeasurement[source]

Raise TypeError (overridden from Measurement).

to_pauli_or_none(rel_tol: float = 1e-09, abs_tol: float = 0.0) → PauliMeasurement[source]

Return self (overridden from Measurement).

to_pauli_or_bloch(rel_tol: float = 1e-09, abs_tol: float = 0.0) → PauliMeasurement[source]

Return self (overridden from Measurement).

to_plane_or_axis() → Axis[source]

Return self.axis (overridden from Measurement).

isclose(other: AbstractMeasurement, rel_tol: float = 1e-09, abs_tol: float = 0.0) → bool[source]

Determine whether this measurement is close to another.

Subclasses should implement a notion of “closeness” between two measurements, comparing measurement-specific attributes. The default comparison for float values involves checking equality within given relative or absolute tolerances.

Parameters:
  • other (AbstractMeasurement) – The measurement to compare against.

  • rel_tol (float, optional) – Relative tolerance for determining closeness. Relevant for comparing angles in the Measurement subclass. Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for determining closeness. Relevant for comparing angles in the Measurement subclass. Default is 0.0.

Returns:

True if this measurement is considered close to other according to the subclass’s comparison rules; False otherwise.

Return type:

bool

clifford(clifford_gate: Clifford) → PauliMeasurement[source]

Return a new measurement command with a Clifford applied.

Parameters:

clifford_gate (Clifford) – Clifford gate to apply before the measurement.

Returns:

Equivalent measurement representing the pattern MC.

Return type:

Self

Notes

  • The return type is Self, meaning that a Clifford applied to a Bloch measurement returns a Bloch measurement, and a Clifford applied to a Pauli measurement returns a Pauli measurement.

  • The method Measurement.clifford() does not always commute with the method Measurement.to_bloch(): the underlying Pauli measurement will be the same but the Bloch representation can be on different planes.

Examples

>>> from graphix.clifford import Clifford
>>> from graphix.measurements import Measurement, PauliMeasurement
>>> Measurement.XY(0.25).clifford(Clifford.H)
Measurement.YZ(1.75)
>>> Measurement.X.clifford(Clifford.S)
-Measurement.Y
>>> for pauli in PauliMeasurement:
...     for clifford in Clifford:
...         assert pauli.to_bloch().clifford(clifford).to_pauli_or_none() == pauli.clifford(clifford)
>>> Measurement.Y.clifford(Clifford.H).to_bloch()
Measurement.XY(1.5)
>>> Measurement.Y.to_bloch().clifford(Clifford.H)
Measurement.YZ(1.5)
with_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat) → Self[source]

Substitute a parameter with a value or expression.

with_parameters(assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) → Self[source]

Perform parallel substitution of multiple parameters.

__init__(axis: Axis, sign: Sign = Sign.PLUS) → None
class PauliString(dim: int, axes: Mapping[int, Axis], sign: Sign = Sign.PLUS)[source]

Bases: object

Dataclass representing a Pauli string.

dim

Dimension of the Hilbert space on which the Pauli string acts.

Type:

int

axes

Mapping between qubit indices and the applied Pauli operator.

Type:

Mapping[int, Axis]

sign

Sign of the Pauli string.

Type:

Sign

Notes

The identity operators in the Pauli string are omitted in axes, but they can be inferred from the dimension of the Hilbert space dim.

dim: int
axes: Mapping[int, Axis]
sign: Sign = 1
static from_str(ps: str) → PauliString[source]

Construct a PauliString from its string representation.

The sign prefix ('+' or '-') is optional; if omitted, the sign defaults to Sign.PLUS.

Parameters:

ps (str) – String encoding of a Pauli string. Optionally starts with '+' or '-', followed by zero or more single-character Pauli operators ('X', 'Y', 'Z', or 'I').

Returns:

The PauliString instance corresponding to the input string.

Return type:

PauliString

Raises:

ValueError – If the first character is not '+' or '-' or a valid operator character, or any operator character is not one of 'X', 'Y', 'Z', 'I'.

Examples

>>> PauliString.from_str("+XYZ")
PauliString(dim=3, axes={0: Axis.X, 1: Axis.Y, 2: Axis.Z}, sign=Sign.PLUS)
>>> PauliString.from_str("-IXI")
PauliString(dim=3, axes={1: Axis.X}, sign=Sign.MINUS)
>>> PauliString.from_str("X")
PauliString(dim=1, axes={0: Axis.X}, sign=Sign.PLUS)
>>> PauliString.from_str("")
PauliString(dim=0, axes={}, sign=Sign.PLUS)
static from_tableau(tab: MatGF2) → PauliString[source]

Construct a PauliString from a one-dimensional tableau representation.

The tableau encodes a Pauli operator of the form \(\pm P_0 \otimes P_1 \otimes \cdots \otimes P_{n-1}\), where each single-qubit Pauli is stored as an (x, z) bit pair and the final element encodes the sign.

Layout of tab (length 2n + 1):

[ x_0, x_1, …, x_{n-1} | z_0, z_1, …, z_{n-1} | sign ]

Encoding conventions:

  • (x=1, z=0) → X

  • (x=0, z=1) → Z

  • (x=1, z=1) → Y

  • (x=0, z=0) → I (identity, qubit absent in axes)

  • sign = 0 → +1

  • sign = 1 → -1

Parameters:

tab (MatGF2) – A one-dimensional GF(2) array of odd length 2n + 1 representing an n-qubit Pauli operator.

Returns:

The Pauli operator encoded by tab.

Return type:

PauliString

Raises:

ValueError – If tab is not one-dimensional or len(tab) is even.

Examples

>>> tab = MatGF2(np.array([1, 1, 1]))
>>> PauliString.from_tableau(tab)
PauliString(dim=1, axes={0: Axis.Y}, sign=Sign.MINUS)
>>> tab = MatGF2(np.array([0, 0, 0, 1, 0]))
>>> PauliString.from_tableau(tab)
PauliString(dim=2, axes={1: Axis.Z}, sign=Sign.PLUS)
to_tableau() → MatGF2[source]

Serialise this PauliString into a one-dimensional tableau representation.

Produces the inverse of from_tableau(): a MatGF2 of length 2n + 1 whose layout is:

[ x_0, x_1, …, x_{n-1} | z_0, z_1, …, z_{n-1} | sign ]

Encoding conventions:

  • X → (x=1, z=0)

  • Z → (x=0, z=1)

  • Y → (x=1, z=1)

  • I → (x=0, z=0) (absent in self.axes)

  • + sign → 0

  • - sign → 1

Returns:

A one-dimensional GF(2) array of length 2 * self.dim + 1.

Return type:

MatGF2

Examples

>>> ps = PauliString.from_str("-XY")
>>> ps.to_tableau()
MatGF2([1, 1, 0, 1, 1], dtype=uint8)
__init__(dim: int, axes: Mapping[int, Axis], sign: Sign = Sign.PLUS) → None
class Placeholder(name: str)[source]

Bases: AffineExpression, Parameter

Placeholder for measurement angles.

These placeholder may appear in affine expressions. Placeholders and affine expressions may be used as angles in rotation gates of Circuit class or for the measurement angle of the measurement commands. Pattern optimizations such that standardization, signal shifting and Pauli preprocessing can be applied to patterns with placeholders.

These placeholders and affine expressions do not support arbitrary computation and are not suitable for simulation. You may use Circuit.subs() or Pattern.subs() with an actual value before the computation.

__init__(name: str) → None[source]

Create a new Placeholder object.

Parameters:

name (str) – name of the parameter, used for binding values.

property name: str

Return the name of the placeholder.

class PlanarState(plane: Plane, angle: float)[source]

Bases: State

Light object used to instantiate backends.

doesn’t cover all possible states but this is covered in graphix.sim.statevec.Statevector and graphix.sim.densitymatrix.DensityMatrix constructors.

Parameters:
  • plane (graphix.pauli.Plane) – One of the three planes (XY, XZ, YZ)

  • angle (float) – Angle (in units of π)

Returns:

State

Return type:

graphix.states.State object

plane: Plane
angle: float
to_statevector_numpy() → NDArray[complex128][source]

Return the state vector.

__init__(plane: Plane, angle: float) → None
class Plane(*values)[source]

Bases: AbstractPlanarMeasurement, EnumReprMixin, Enum

Plane: XY, YZ or XZ.

XY = 1
YZ = 2
XZ = 3
property axes: tuple[Axis, Axis]

Return the pair of axes that carry the plane.

property orth: Axis

Return the axis orthogonal to the plane.

property cos: Axis

Return the axis of the plane that conventionally carries the cos.

property sin: Axis

Return the axis of the plane that conventionally carries the sin.

polar(angle: float) → tuple[float, float, float][source]
polar(angle: Expression) → tuple[Expression, Expression, Expression]

Return the Cartesian coordinates of the point of module 1 at the given angle, following the conventional orientation for cos and sin.

static from_axes(a: Axis, b: Axis) → Plane[source]

Return the plane carried by the given axes.

to_plane_or_axis() → Plane[source]

Return the plane.

Return type:

Plane

to_plane() → Plane[source]

Return the plane.

Return type:

Plane

clifford(clifford_gate: Clifford) → Plane[source]

Return a new measurement command with a Clifford applied.

Parameters:

clifford_gate (Clifford) – Clifford gate to apply before the measurement.

Returns:

Equivalent measurement representing the pattern MC.

Return type:

Self

Notes

  • The return type is Self, meaning that a Clifford applied to a Bloch measurement returns a Bloch measurement, and a Clifford applied to a Pauli measurement returns a Pauli measurement.

  • The method Measurement.clifford() does not always commute with the method Measurement.to_bloch(): the underlying Pauli measurement will be the same but the Bloch representation can be on different planes.

Examples

>>> from graphix.clifford import Clifford
>>> from graphix.measurements import Measurement, PauliMeasurement
>>> Measurement.XY(0.25).clifford(Clifford.H)
Measurement.YZ(1.75)
>>> Measurement.X.clifford(Clifford.S)
-Measurement.Y
>>> for pauli in PauliMeasurement:
...     for clifford in Clifford:
...         assert pauli.to_bloch().clifford(clifford).to_pauli_or_none() == pauli.clifford(clifford)
>>> Measurement.Y.clifford(Clifford.H).to_bloch()
Measurement.XY(1.5)
>>> Measurement.Y.to_bloch().clifford(Clifford.H)
Measurement.YZ(1.5)
class RandomBranchSelector(pr_calc: bool = True)[source]

Bases: BranchSelector

Random branch selector.

Parameters:

pr_calc (bool, optional) – Whether to compute the probability distribution before selecting the measurement result. If False, measurements yield 0/1 with equal probability (50% each). Default is True.

pr_calc: bool = True
measure(qubit: int, f_expectation0: Callable[[], float], rng: Generator | None = None, *, stacklevel: int = 1) → Outcome[source]

Return the measurement outcome of qubit.

If pr_calc is True, the measurement outcome is determined based on the computed probability of outcome 0. Otherwise, the result is randomly chosen with a 50% chance for either outcome.

__init__(pr_calc: bool = True) → None
class Sign(*values)[source]

Bases: EnumReprMixin, Enum

Sign, plus or minus.

PLUS = 1
MINUS = -1
static plus_if(b: bool) → Sign[source]

Return + if b is True, - otherwise.

static minus_if(b: bool) → Sign[source]

Return - if b is True, + otherwise.

class SpaceMinimizationHeuristics[source]

Bases: object

Predefined heuristics.

static causal_flow(pattern: StandardizedPattern) → SpaceMinimizationHeuristicResult | None[source]

Use the causal flow layer to minimize space.

This minimization heuristic is optimal but requires the pattern to have a causal flow.

static order_unchanged(pattern: StandardizedPattern) → SpaceMinimizationHeuristicResult | None[source]

Leave the measurement order unchanged.

Since this heuristic preserves the original measurement order, it cannot return a pattern with worse max space than the original.

Usually, this heuristic is included in the list of heuristics considered to ensure that the result of space minimization cannot have a worse max space than the original pattern. This is the case by default, but the user can remove this heuristic from the list to see the effect of the other heuristics even if they don’t reduce the max space of the pattern.

static greedy_degree(pattern: StandardizedPattern) → SpaceMinimizationHeuristicResult | None[source]

Choose greedily the nodes by minimal degree.

This minimization heuristic can improve but can also worsen the max space in some situations.

class StandardizedPattern(input_nodes: Iterable[Node], output_nodes: Iterable[Node], n_list: Iterable[command.N], e_set: Iterable[Iterable[Node]], m_list: Iterable[command.M], z_dict: Mapping[Node, Iterable[Node]], x_dict: Mapping[Node, Iterable[Node]], c_dict: Mapping[Node, Clifford])[source]

Bases: _StandardizedPattern, Simulable[Measurement]

Pattern in standardized form.

Use the method to_pattern() to get the standardized pattern.

This class uses immutable data structures for its fields (tuple, frozenset and Mapping).

Instances can be generated with the constructor from any compatible data structures, and an instance can be generated directly from a pattern with the class method from_pattern().

The constructor instantiates the Mapping fields as MappingProxyType objects over fresh dictionaries, ensuring their immutability. We expose the type Mapping instead of MappingProxyType for the readability, as they provide the same interface.

input_nodes

Input nodes.

Type:

tuple[Node, …]

output_nodes

Output nodes.

Type:

tuple[Node, …]

n_list

The N commands.

Type:

tuple[command.N]

e_set

Set of edges. Each edge is a set with two elements.

Type:

frozenset[frozenset[Node]]

m_list

The M commands.

Type:

tuple[command.M]

z_dict

Mapping associating Z-domains to some nodes.

Type:

Mapping[Node, frozenset[Node]]

x_dict

Mapping associating X-domains to some nodes.

Type:

Mapping[Node, frozenset[Node]]

c_dict

Mapping associating Clifford corrections to some output nodes.

Type:

Mapping[Node, Clifford]

__init__(input_nodes: Iterable[Node], output_nodes: Iterable[Node], n_list: Iterable[command.N], e_set: Iterable[Iterable[Node]], m_list: Iterable[command.M], z_dict: Mapping[Node, Iterable[Node]], x_dict: Mapping[Node, Iterable[Node]], c_dict: Mapping[Node, Clifford]) → None[source]

Return a new StandardizedPattern with immutable data structures.

classmethod from_pattern(pattern: Pattern) → Self[source]

Compute the standardized form of the given pattern.

graph() → nx.Graph[int][source]

Return the graph state from the command sequence, extracted from ‘N’ and ‘E’ commands.

Returns:

graph_state

Return type:

nx.Graph

perform_pauli_pushing(leave_nodes: AbstractSet[Node] | None = None, *, stacklevel: int = 1) → StandardizedPattern[source]

Move Pauli measurements before the other measurements.

If you need to recover the cut between Pauli measurements and non-Pauli measurements or the shifted signal, you can use from_standardizedpattern() instead.

Parameters:
  • leave_nodes (AbstractSet[Node], optional) – Nodes that should not be moved. This constraint only applies to Pauli nodes and has no effect on non-Pauli nodes.

  • stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

The pattern in which Pauli measurements have been moved before the other measurements.

Return type:

StandardizedPattern

max_space() → int[source]

Compute the maximum number of nodes that must be present in the graph (graph space) during the execution of the space-optimal pattern for the given measurement order.

This is equivalent to to_space_optimal_pattern().max_space().

Returns:

n_nodes – Maximum number of nodes present in the graph during space-optimal pattern execution.

Return type:

int

minimize_space(heuristics: Iterable[SpaceMinimizationHeuristic] | None = None) → StandardizedPattern[source]

Return a pattern with an optimized measurement order that reduces the maximal space, i.e. the number of qubits simultaneously required to execute the pattern.

Note that standardized patterns always have a maximal space equal to the total number of nodes in the open graph, because standardization requires the entire graph to be prepared before measurement.

Space reduction is specifically realized when the optimized order is applied via to_space_optimal_pattern().

See graphix.space_minimization.minimize_space() for default heuristics.

Parameters:

heuristics (Iterable[SpaceMinimizationHeuristic] | None, default None) – The heuristics to apply sequentially. Defaults to DEFAULT_HEURISTICS.

Returns:

The optimized pattern.

Return type:

StandardizedPattern

to_pattern() → Pattern[source]

Return the standardized pattern.

to_standardizedpattern() → StandardizedPattern[source]

Return self.

to_space_optimal_pattern() → Pattern[source]

Return a pattern that is space-optimal for the given measurement order.

This method treats the measurement order as fixed, performing node preparations (N) and entanglements (E) as late as possible to minimize space usage. While the resulting pattern is guaranteed to be optimal for this specific order, the method does not explore alternative orders.

To find an alternative measurement order that further reduces space, use minimize_space().

to_opengraph() → OpenGraph[Measurement][source]

Extract the underlying resource-state open graph from the pattern.

Return type:

OpenGraph[Measurement]

Raises:

ValueError – If N commands in the pattern do not represent a \(\ket{+}\) state.

partial_order_layers() → tuple[frozenset[int], ...][source]

Extract the measurement order of the pattern in the form of layers.

This method builds a directed acyclical graph (DAG) from the pattern and then performs a topological sort.

Returns:

Measurement partial order between the pattern’s nodes in a layer form.

Return type:

tuple[frozenset[int], …]

Raises:

ValueError – If the correction domains in the pattern form closed loops. This implies that the pattern is not runnable.

Notes

The returned object follows the same conventions as the partial_order_layers attribute of PauliFlow and XZCorrections objects:
  • Nodes in the same layer can be measured simultaneously.

  • Nodes in layer i must be measured after nodes in layer i + 1.

  • All output nodes (if any) are in the first layer.

  • There cannot be any empty layers.

to_xzcorrections() → XZCorrections[Measurement][source]

Extract the XZ-corrections from the current measurement pattern.

Returns:

The XZ-corrections associated with the current pattern.

Return type:

XZCorrections[Measurement]

Raises:
  • XZCorrectionsError – If the extracted correction dictionaries are not well formed.

  • ValueError – If N commands in the pattern do not represent a \(\ket{+}\) state or if the pattern corrections form closed loops.

map(f: Callable[[Measurement], Measurement]) → StandardizedPattern[source]

Return a pattern where the function f has been applied to each measurement.

Parameters:

f (Callable[[Measurement], Measurement]) – Function applied to each measurement.

Returns:

The resulting pattern.

Return type:

StandardizedPattern

to_bloch() → StandardizedPattern[source]

Return an equivalent pattern in which all measurements are represented as Bloch measurements.

infer_pauli_measurements(*, rel_tol: float = 1e-09, abs_tol: float = 0.0) → StandardizedPattern[source]

Return a pattern where Bloch measurements close to a Pauli measurement are replaced by Pauli measurements.

Parameters:
  • rel_tol (float, optional) – Relative tolerance for comparing angles, passed to math.isclose(). Default is 1e-9.

  • abs_tol (float, optional) – Absolute tolerance for comparing angles, passed to math.isclose(). Default is 0.0.

Returns:

A pattern in which Bloch measurements close to a Pauli measurement are replaced by Pauli measurements. If copy is False, the result is self.

Return type:

StandardizedPattern

remove_pauli_measurements(*, stacklevel: int = 1) → StandardizedPattern[source]

Remove non-input Pauli measurements from the given pattern.

See remove_pauli_measurements() for more information.

Parameters:

stacklevel (int, optional) – Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function’s call site.

Returns:

The pattern in which Pauli measurements have been moved before the other measurements.

Return type:

StandardizedPattern

input_nodes
output_nodes
n_list
e_set
m_list
z_dict
x_dict
c_dict
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 XZCorrections(og: OpenGraph[_AM_co], x_corrections: Mapping[int, AbstractSet[int]], z_corrections: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]])[source]

Bases: Parameterizable, Simulable[_AM_co], Generic[_AM_co]

An unmutable dataclass providing a representation of XZ-corrections.

og

The open graph with respect to which the XZ-corrections are defined.

Type:

OpenGraph[_AM_co]

x_corrections

Mapping of X-corrections: in each (key, value) pair, key is a measured node, and value is the set of nodes on which an X-correction must be applied depending on the measurement result of key.

Type:

Mapping[int, AbstractSet[int]]

z_corrections

Mapping of Z-corrections: in each (key, value) pair, key is a measured node, and value is the set of nodes on which an Z-correction must be applied depending on the measurement result of key.

Type:

Mapping[int, AbstractSet[int]]

partial_order_layers

Partial order between the open graph’s nodes in a layer form determined by the corrections. The set layers[i] comprises the nodes in layer i. Nodes in layer i are “larger” in the partial order than nodes in layer i+1. If the open graph has output nodes, they are always in layer 0. Non-corrected, measured nodes are always in the last layer.

Type:

Sequence[AbstractSet[int]]

Notes

The XZ-corrections mappings define a partial order, therefore, only og, x_corrections and z_corrections are necessary to initialize an XZCorrections instance (see XZCorrections.from_measured_nodes_mapping()). However, XZ-corrections are often extracted from a flow whose partial order is known and can be used to construct a pattern, so it can also be passed as an argument to the dataclass constructor. The correctness of the input parameters is not verified automatically.

og: OpenGraph[_AM_co]
x_corrections: Mapping[int, AbstractSet[int]]
z_corrections: Mapping[int, AbstractSet[int]]
partial_order_layers: Sequence[AbstractSet[int]]
static from_measured_nodes_mapping(og: OpenGraph[_AM_co], x_corrections: Mapping[int, AbstractSet[int]] | None = None, z_corrections: Mapping[int, AbstractSet[int]] | None = None) → XZCorrections[_AM_co][source]

Create an XZCorrections instance from the XZ-corrections mappings.

Parameters:
  • og (OpenGraph[_AM_co]) – Open graph with respect to which the corrections are defined.

  • x_corrections (Mapping[int, AbstractSet[int]] | None) – Mapping of X-corrections: in each (key, value) pair, key is a measured node, and value is the set of nodes on which an X-correction must be applied depending on the measurement result of key.

  • z_corrections (Mapping[int, AbstractSet[int]] | None) – Mapping of X-corrections: in each (key, value) pair, key is a measured node, and value is the set of nodes on which an X-correction must be applied depending on the measurement result of key.

Return type:

XZCorrections[_AM_co]

Raises:

XZCorrectionsError – If the input dictionaries are not well formed. In well-formed correction dictionaries: - Keys are a subset of the measured nodes. - Values correspond to nodes of the open graph. - Corrections do not form closed loops.

Notes

This method computes the partial order induced by the XZ-corrections.

to_pattern(total_measurement_order: TotalOrder | None = None) → Pattern[source]

Generate a unique pattern from an instance of XZCorrections[Measurement].

Parameters:

total_measurement_order (TotalOrder | None) – Ordered sequence of all the non-output nodes in the open graph indicating the measurement order. This parameter must be compatible with the partial order induced by the XZ-corrections. Optional, defaults to None. If None an arbitrary total order compatible with self.partial_order_layers is generated.

Return type:

Pattern

Raises:

XZCorrectionsError – If the input total measurement order is not compatible with the partial order induced by the XZ-corrections.

Notes

  • The XZCorrections instance must be of parametric type Measurement to allow for a pattern extraction, otherwise the underlying open graph does not contain information about the measurement angles.

  • The resulting pattern is guaranteed to be runnable if the XZCorrections object is well formed, but does not need to be deterministic. It will be deterministic if the XZ-corrections were inferred from a flow. In this case, this routine follows the recipe in Theorems 1, 2 and 4 in Ref. [1].

References

[1] Browne et al., NJP 9, 250 (2007).

to_causalflow() → CausalFlow[_PM_co][source]

Extract a causal flow from XZ-corrections.

This method does not invoke the flow-extraction routine on the underlying open graph. Instead, it assigns the x_corrections mapping to the flow’s correction function and verifies that it is compatible with the intrinsic partial order of the XZ-corrections. If the resulting correction function is incompatible with this partial order, or the open graph contains measurements in XZ or YZ planes, a FlowError is raised.

Return type:

CausalFlow[_PM_co]

Notes

See Theorem 1 in Ref. [1].

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

to_gflow() → GFlow[_PM_co][source]

Extract a gflow from XZ-corrections.

This method does not invoke the flow-extraction routine on the underlying open graph. Instead, it assigns the x_corrections mapping to the flow’s correction function and verifies that it is compatible with the intrinsic partial order of the XZ-corrections. Nodes measured in planes XZ or YZ are assigned to their correcting set. If the resulting correction function is incompatible with this partial order FlowError is raised.

Return type:

GFlow[_PM_co]

Notes

See Theorem 2 in Ref. [1].

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

to_pauliflow() → PauliFlow[_AM_co][source]

Extract a Pauli flow from XZ-corrections.

This method does not invoke the flow-extraction routine on the underlying open graph. Instead, it reconstructs, for every measured node, a correction set whose future part matches the observed XZ-corrections and which satisfies the Pauli-flow propositions (P1-P9, see PauliFlow.check_well_formed()).

The difficulty, compared with to_gflow(), is that the Pauli-flow correction sets may contain anachronical corrections: corrections targeting nodes measured in the X or Y Pauli bases that lie in the present or the past of the corrected node. Such corrections do not appear in the pattern, because PauliFlow.to_xzcorrections() only keeps the part of each correction set lying in the future (the & future filter). They must therefore be reconstructed rather than read off the corrections.

For each measured node i this is cast as a system of linear equations over GF(2): the membership of the future nodes in the correction set is pinned by the X-corrections of i; the free variables are the anachronical (non-future, X/Y-measured) candidates and, where the proposition allows it, i itself; and the equations encode the odd-neighbourhood constraints, namely the Z-corrections on the future nodes (P-future), the vanishing of the odd neighbourhood on past non-(Y/Z) nodes (P2), the coupling on past Y-measured nodes (P3) and the local proposition on i (P4-P9). The system is reduced with graphix._linalg.MatGF2.gauss_elimination() and solved with graphix._linalg.solve_f2_linear_system().

Return type:

PauliFlow[_AM_co]

Raises:

FlowError – If no Pauli flow is compatible with the XZ-corrections (raised by _reconstruct_pauli_correction_function() when the GF(2) system has no solution for at least one measured node).

Notes

See Theorem 4 in Ref. [1] and Definition 5 therein for the Pauli-flow propositions.

References

[1] Browne et al., 2007 New J. Phys. 9 250 (arXiv:quant-ph/0702212).

to_bloch() → XZCorrections[BlochMeasurement][source]

Return the XZ-corrections where all measurements in the open graph are converted to Bloch.

See OpenGraph.to_bloch() for additional information.

downcast_bloch() → XZCorrections[BlochMeasurement][source]

Return the open graph if all measurements are described as Bloch measurements; raise TypeError otherwise.

See OpenGraph.downcast_bloch() for additional information.

generate_total_measurement_order() → Sequence[int][source]

Generate a sequence of all the non-output nodes in the open graph in an arbitrary order compatible with the intrinsic partial order of the XZ-corrections.

Return type:

TotalOrder

to_dag() → nx.DiGraph[int][source]

Extract the directed graph induced by the XZ-corrections.

Returns:

Directed graph in which an edge i -> j represents a correction applied to qubit j, conditioned on the measurement outcome of qubit i.

Return type:

nx.DiGraph[int]

Notes

  • Not all nodes of the underlying open graph are nodes of the returned directed graph, but only those involved in a correction, either as corrected qubits or belonging to a correction domain.

  • The output of this method is not guaranteed to be a directed acyclical graph (i.e., a directed graph without any loops). This is only the case if the XZCorrections object is well formed, which is verified by the method XZCorrections.is_wellformed().

is_compatible(total_measurement_order: Sequence[int]) → bool[source]

Verify if a given total measurement order is compatible with the intrisic partial order of the XZ-corrections.

Parameters:

total_measurement_order (TotalOrder) – An ordered sequence of all the non-output nodes in the open graph.

Returns:

True if total_measurement_order is compatible with self.partial_order_layers, False otherwise.

Return type:

bool

check_well_formed() → None[source]

Verify if the XZ-corrections are well formed.

Raises:

XZCorrectionsError – if the XZ-corrections are not well formed.

Notes

A correct XZCorrections instance verifies the following properties:
  • Keys of the correction dictionaries are measured nodes, i.e., a subset of \(O^c\).

  • Corrections respect the partial order.

  • The first layer of the partial order contains all the output nodes if there are any.

  • The partial order contains all the nodes (without duplicates) and it does not have empty layers.

This method assumes that the open graph is well formed.

to_ascii(multiline: bool = False) → str[source]

Return an ASCII string representing the XZCorrections’ mappings and partial order layers.

Parameters:

multiline (bool (optional)) – Flag to format the output. If True a new line is printed after each correction set. Defaults to False.

Return type:

str

to_latex(multiline: bool = False) → str[source]

Return a string containing the LaTeX representation of the XZCorrections’ mappings and partial order layers.

See notes in to_ascii() for additional information.

to_unicode(multiline: bool = False) → str[source]

Return a Unicode string representing the XZCorrections’ mappings and partial order layers.

See notes in to_ascii() for additional information.

draw(**options: Unpack[DrawKwargs]) → None[source]

Visualize the opengraph, correction structure and partial order.

Parameters:

options (Unpack[DrawKwargs]) – Options controlling graph visualization. See VisualizationOptions.

with_parameter(variable: Parameter, substitute: ExpressionOrSupportsFloat) → XZCorrections[_M][source]

Substitute a parameter with a value or expression in all measurement angles of the open graph.

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.

Returns:

A new instance of the XZCorrections object with the updated measurement parameters.

Return type:

XZCorrections[Measurement]

Notes

See notes and examples in OpenGraph.with_parameter().

with_parameters(assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) → XZCorrections[_M][source]

Perform parallel substitution of multiple parameters in measurement angles of the open graph.

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.

Returns:

A new instance of the XZCorrections object with the updated measurement angles.

Return type:

XZCorrections[Measurement]

Notes

See notes and examples in OpenGraph.with_parameters().

__init__(og: OpenGraph[_AM_co], x_corrections: Mapping[int, AbstractSet[int]], z_corrections: Mapping[int, AbstractSet[int]], partial_order_layers: Sequence[AbstractSet[int]]) → None
angle_to_rad(angle: float) → float[source]
angle_to_rad(angle: Expression) → Expression

Convert a Graphix angle to radians.

In Graphix, angles are expressed in units of π.

rad_to_angle(angle: float) → float[source]
rad_to_angle(angle: Expression) → Expression

Convert an angle expressed in radians to a Graphix angle.

In Graphix, angles are expressed in units of π.