From a41f7d61fd578b3b7cc7bd65579cb37d024cf9b3 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 17 Apr 2026 10:58:18 +0200 Subject: [PATCH 01/63] first push --- graphix/instruction.py | 16 +- graphix/transpiler.py | 525 ++++++++++++++++++++++++++++--------- tests/test_fundamentals.py | 34 +-- tests/test_parameter.py | 2 +- tests/test_pauli.py | 16 +- 5 files changed, 448 insertions(+), 145 deletions(-) diff --git a/graphix/instruction.py b/graphix/instruction.py index d3cadab5f..d289eabd1 100644 --- a/graphix/instruction.py +++ b/graphix/instruction.py @@ -49,6 +49,7 @@ class InstructionKind(Enum): X = enum.auto() Y = enum.auto() Z = enum.auto() + J = enum.auto() I = enum.auto() M = enum.auto() RX = enum.auto() @@ -290,5 +291,18 @@ def visit(self, visitor: InstructionVisitor) -> RZ: return RZ(visitor.visit_qubit(self.target), visitor.visit_angle(self.angle)) -InstructionWithoutRZZ = CCX | CNOT | SWAP | CZ | H | S | X | Y | Z | I | M | RX | RY | RZ +@dataclass(repr=False) +class J(_KindChecker, BaseInstruction): + """J circuit instruction.""" + + target: int + angle: ParameterizedAngle = field(metadata={"repr": repr_angle}) + kind: ClassVar[Literal[InstructionKind.J]] = field(default=InstructionKind.J, init=False) + + @override + def visit(self, visitor: InstructionVisitor) -> J: + return J(visitor.visit_qubit(self.target), visitor.visit_angle(self.angle)) + + +InstructionWithoutRZZ = CCX | CNOT | SWAP | CZ | H | S | X | Y | Z | I | M | RX | RY | RZ | J Instruction = InstructionWithoutRZZ | RZZ diff --git a/graphix/transpiler.py b/graphix/transpiler.py index b4219a736..03ab63cc2 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -9,6 +9,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, SupportsFloat +import networkx as nx + # assert_never introduced in Python 3.11 # override introduced in Python 3.12 from typing_extensions import assert_never, override @@ -16,21 +18,24 @@ from graphix import command, instruction, parameter from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.command import E, M, N, X, Z +from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers from graphix.fundamentals import ANGLE_PI, Axis -from graphix.instruction import Instruction, InstructionKind, InstructionVisitor, InstructionWithoutRZZ -from graphix.measurements import Measurement, PauliMeasurement +from graphix.instruction import Instruction, InstructionKind, InstructionVisitor +from graphix.measurements import BlochMeasurement, Measurement, PauliMeasurement +from graphix.opengraph import OpenGraph from graphix.ops import Ops -from graphix.pattern import Pattern +from graphix.optimization import StandardizedPattern from graphix.sim.statevec import Statevec, StatevectorBackend if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence + from collections.abc import Callable, Iterable, Mapping, Sequence from numpy.random import Generator from graphix.command import Command from graphix.fundamentals import ParameterizedAngle from graphix.parameter import ExpressionOrFloat, Parameter + from graphix.pattern import Pattern from graphix.sim import Data from graphix.sim.base_backend import Matrix @@ -143,6 +148,8 @@ def add(self, instr: Instruction) -> None: self.ry(instr.target, instr.angle) case InstructionKind.RZ: self.rz(instr.target, instr.angle) + case InstructionKind.J: + self.j(instr.target, instr.angle) case _: assert_never(instr.kind) @@ -294,6 +301,19 @@ def rz(self, qubit: int, angle: ParameterizedAngle) -> None: assert qubit in self.active_qubits self.instruction.append(instruction.RZ(target=qubit, angle=angle)) + def j(self, qubit: int, angle: ParameterizedAngle) -> None: + """Apply a J rotation gate. + + Parameters + ---------- + qubit : int + target qubit + angle : ParameterizedAngle + rotation angle in units of π + """ + assert qubit in self.active_qubits + self.instruction.append(instruction.J(target=qubit, angle=angle)) + def r(self, qubit: int, axis: Axis, angle: ParameterizedAngle) -> None: """Apply a rotation gate on the given axis. @@ -388,116 +408,80 @@ def m(self, qubit: int, axis: Axis) -> None: self.active_qubits.remove(qubit) def transpile(self) -> TranspileResult: - """Transpile the circuit to a pattern. + """Transpile a circuit via J-∧z decomposition to a pattern. + + Parameters + ---------- + self: the circuit to transpile. Returns ------- - result : :class:`TranspileResult` object + the result of the transpilation: a pattern. + + Raises + ------ + IllformedCircuitError: if the pattern is ill-formed (operation on already measured node) + CircuitWithMeasurementError: if the circuit contains measurements. + """ - n_node = self.width - out: list[int | None] = list(range(self.width)) - pattern = Pattern(input_nodes=list(range(self.width))) - classical_outputs = [] - for instr in _transpile_rzz(self.instruction): - match instr.kind: - case instruction.InstructionKind.CZ: - target0 = _check_target(out, instr.targets[0]) - target1 = _check_target(out, instr.targets[1]) - seq = self._cz_command(target0, target1) - pattern.extend(seq) - case instruction.InstructionKind.CNOT: - ancilla = [n_node, n_node + 1] - control = _check_target(out, instr.control) - target = _check_target(out, instr.target) - out[instr.control], out[instr.target], seq = self._cnot_command(control, target, ancilla) - pattern.extend(seq) - n_node += 2 - case instruction.InstructionKind.SWAP: - target0 = _check_target(out, instr.targets[0]) - target1 = _check_target(out, instr.targets[1]) - out[instr.targets[0]], out[instr.targets[1]] = ( - target1, - target0, - ) - case instruction.InstructionKind.I: - pass - case instruction.InstructionKind.H: - single_ancilla = n_node - target = _check_target(out, instr.target) - out[instr.target], seq = self._h_command(target, single_ancilla) - pattern.extend(seq) - n_node += 1 - case instruction.InstructionKind.S: - ancilla = [n_node, n_node + 1] - target = _check_target(out, instr.target) - out[instr.target], seq = self._s_command(target, ancilla) - pattern.extend(seq) - n_node += 2 - case instruction.InstructionKind.X: - ancilla = [n_node, n_node + 1] - target = _check_target(out, instr.target) - out[instr.target], seq = self._x_command(target, ancilla) - pattern.extend(seq) - n_node += 2 - case instruction.InstructionKind.Y: - ancilla = [n_node, n_node + 1, n_node + 2, n_node + 3] - target = _check_target(out, instr.target) - out[instr.target], seq = self._y_command(target, ancilla) - pattern.extend(seq) - n_node += 4 - case instruction.InstructionKind.Z: - ancilla = [n_node, n_node + 1] - target = _check_target(out, instr.target) - out[instr.target], seq = self._z_command(target, ancilla) - pattern.extend(seq) - n_node += 2 - case instruction.InstructionKind.RX: - ancilla = [n_node, n_node + 1] - target = _check_target(out, instr.target) - out[instr.target], seq = self._rx_command(target, ancilla, instr.angle) - pattern.extend(seq) - n_node += 2 - case instruction.InstructionKind.RY: - ancilla = [n_node, n_node + 1, n_node + 2, n_node + 3] - target = _check_target(out, instr.target) - out[instr.target], seq = self._ry_command(target, ancilla, instr.angle) - pattern.extend(seq) - n_node += 4 - case instruction.InstructionKind.RZ: - ancilla = [n_node, n_node + 1] - target = _check_target(out, instr.target) - out[instr.target], seq = self._rz_command(target, ancilla, instr.angle) - pattern.extend(seq) - n_node += 2 - case instruction.InstructionKind.CCX: - ancilla = [n_node + i for i in range(18)] - control0 = _check_target(out, instr.controls[0]) - control1 = _check_target(out, instr.controls[1]) - target = _check_target(out, instr.target) - ( - out[instr.controls[0]], - out[instr.controls[1]], - out[instr.target], - seq, - ) = self._ccx_command( - control0, - control1, - target, - ancilla, - ) - pattern.extend(seq) - n_node += 18 - case instruction.InstructionKind.M: - target = _check_target(out, instr.target) - seq = self._m_command(target, instr.axis) - pattern.extend(seq) - classical_outputs.append(target) - out[instr.target] = None - case _: - assert_never(instr.kind) - output_nodes = [node for node in out if node is not None] - pattern.reorder_output_nodes(output_nodes) - return TranspileResult(pattern, tuple(classical_outputs)) + indices: list[int | None] = list(range(self.width)) + n_nodes = self.width + measurements: dict[int, BlochMeasurement] = {} + classical_outputs: dict[int, command.M] = {} + inputs = list(range(n_nodes)) + graph: nx.Graph[int] = nx.Graph() # CHANGE TO OPEN GRAPH NOT NX + graph.add_nodes_from(inputs) + x_corrections: dict[int, set[int]] = {} + for instr in self.instruction: + if instr.kind == InstructionKind.M: + target = indices[instr.target] + if target is None: + raise IllformedCircuitError + classical_outputs[target] = command.M(target, PauliMeasurement(instr.axis)) + indices[instr.target] = None + continue + for instr_jcz in instruction_to_jcz(instr): + if instr_jcz.kind == InstructionKind.J: + target = indices[instr_jcz.target] + if target is None: + raise IllformedCircuitError + graph.add_edge(target, n_nodes) # Also adds nodes + measurements[target] = Measurement.XY(normalize_angle(-instr_jcz.angle)) + indices[instr_jcz.target] = n_nodes + x_corrections[target] = {n_nodes} # X correction on ancilla + n_nodes += 1 + continue + if instr_jcz.kind == InstructionKind.CZ: + t0, t1 = instr_jcz.targets + i0, i1 = indices[t0], indices[t1] + if i0 is None or i1 is None: + raise IllformedCircuitError + # If edge exists, remove it; else, add it + if graph.has_edge(i0, i1): + graph.remove_edge(i0, i1) + else: + graph.add_edge(i0, i1) + continue + assert_never(instr_jcz.kind) + outputs = [i for i in indices if i is not None] + outputs.extend(classical_outputs.keys()) + og = OpenGraph( + graph=graph, + input_nodes=inputs, + output_nodes=outputs, + measurements=measurements, + ) + z_corrections: dict[int, set[int]] = {} + for node, correctors in x_corrections.items(): + (corrector,) = correctors + z_targets = set(graph.neighbors(corrector)) - {node} + if z_targets: + z_corrections[node] = z_targets + partial_order_layers = _corrections_to_partial_order_layers(og, x_corrections, z_corrections) + f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) + pattern = StandardizedPattern.from_pattern(f.to_corrections().to_pattern()).to_space_optimal_pattern() + pattern.extend(classical_outputs.values()) + return TranspileResult(pattern, tuple(classical_outputs.keys())) @classmethod def _cnot_command( @@ -1085,14 +1069,287 @@ def transpile_measurements_to_z_axis(self) -> Circuit: return circuit -def _transpile_rzz(instructions: Iterable[Instruction]) -> Iterator[InstructionWithoutRZZ]: - for instr in instructions: - if instr.kind == InstructionKind.RZZ: - yield instruction.CNOT(control=instr.control, target=instr.target) - yield instruction.RZ(target=instr.target, angle=instr.angle) - yield instruction.CNOT(control=instr.control, target=instr.target) - else: - yield instr +def decompose_rzz(instr: instruction.RZZ) -> list[instruction.CNOT | instruction.RZ]: + """Return a decomposition of RZZ(α) gate as CNOT(control, target)·Rz(target, α)·CNOT(control, target). + + Parameters + ---------- + instr: the RZZ instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [ + instruction.CNOT(target=instr.target, control=instr.control), + instruction.RZ(instr.target, instr.angle), + instruction.CNOT(target=instr.target, control=instr.control), + ] + + +def decompose_ccx( + instr: instruction.CCX, +) -> list[instruction.H | instruction.CNOT | instruction.RZ]: + """Return a decomposition of the CCX gate into H, CNOT, T and T-dagger gates. + + This decomposition of the Toffoli gate can be found in + Michael A. Nielsen and Isaac L. Chuang, + Quantum Computation and Quantum Information, + Cambridge University Press, 2000 + (p. 182 in the 10th Anniversary Edition). + + Parameters + ---------- + instr: the CCX instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [ + instruction.H(instr.target), + instruction.CNOT(control=instr.controls[1], target=instr.target), + instruction.RZ(instr.target, -ANGLE_PI / 4), + instruction.CNOT(control=instr.controls[0], target=instr.target), + instruction.RZ(instr.target, ANGLE_PI / 4), + instruction.CNOT(control=instr.controls[1], target=instr.target), + instruction.RZ(instr.target, -ANGLE_PI / 4), + instruction.CNOT(control=instr.controls[0], target=instr.target), + instruction.RZ(instr.controls[1], -ANGLE_PI / 4), + instruction.RZ(instr.target, ANGLE_PI / 4), + instruction.CNOT(control=instr.controls[0], target=instr.controls[1]), + instruction.H(instr.target), + instruction.RZ(instr.controls[1], -ANGLE_PI / 4), + instruction.CNOT(control=instr.controls[0], target=instr.controls[1]), + instruction.RZ(instr.controls[0], ANGLE_PI / 4), + instruction.RZ(instr.controls[1], ANGLE_PI / 2), + ] + + +def decompose_cnot(instr: instruction.CNOT) -> list[instruction.H | instruction.CZ]: + """Return a decomposition of the CNOT gate as H·∧z·H. + + Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. + + Parameters + ---------- + instr: the CNOT instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [ + instruction.H(instr.target), + instruction.CZ((instr.control, instr.target)), + instruction.H(instr.target), + ] + + +def decompose_swap(instr: instruction.SWAP) -> list[instruction.CNOT]: + """Return a decomposition of the SWAP gate as CNOT(0, 1)·CNOT(1, 0)·CNOT(0, 1). + + Michael A. Nielsen and Isaac L. Chuang, + Quantum Computation and Quantum Information, + Cambridge University Press, 2000 + (p. 23 in the 10th Anniversary Edition). + + Parameters + ---------- + instr: the SWAP instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [ + instruction.CNOT(control=instr.targets[0], target=instr.targets[1]), + instruction.CNOT(control=instr.targets[1], target=instr.targets[0]), + instruction.CNOT(control=instr.targets[0], target=instr.targets[1]), + ] + + +def decompose_y(instr: instruction.Y) -> list[instruction.X | instruction.Z]: + """Return a decomposition of the Y gate as X·Z. + + Parameters + ---------- + instr: the Y instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return list(reversed([instruction.X(instr.target), instruction.Z(instr.target)])) + + +def decompose_rx(instr: instruction.RX) -> list[instruction.J]: + """Return a J decomposition of the RX gate. + + The Rx(α) gate is decomposed into J(α)·H (that is to say, J(α)·J(0)). + Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. + + Parameters + ---------- + instr: the RX instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [instruction.J(target=instr.target, angle=angle) for angle in reversed((instr.angle, 0))] + + +def decompose_ry(instr: instruction.RY) -> list[instruction.J]: + """Return a J decomposition of the RY gate. + + The Ry(α) gate is decomposed into J(0)·J(π/2)·J(α)·J(-π/2). + Vincent Danos, Elham Kashefi, Prakash Panangaden, Robust and parsimonious realisations of unitaries in the one-way + model, 2004. + + Parameters + ---------- + instr: the RY instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [ + instruction.J(target=instr.target, angle=angle) + for angle in reversed((0, ANGLE_PI / 2, instr.angle, -ANGLE_PI / 2)) + ] + + +def decompose_rz(instr: instruction.RZ) -> list[instruction.J]: + """Return a J decomposition of the RZ gate. + + The Rz(α) gate is decomposed into H·J(α) (that is to say, J(0)·J(α)). + Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. + + Parameters + ---------- + instr: the RZ instruction to decompose. + + Returns + ------- + the decomposition. + + """ + return [instruction.J(target=instr.target, angle=angle) for angle in reversed((0, instr.angle))] + + +def instruction_to_jcz(instr: Instruction) -> Sequence[instruction.J | instruction.CZ]: + """Return a J-∧z decomposition of the instruction. + + Parameters + ---------- + instr: the instruction to decompose. + + Returns + ------- + the decomposition. + + """ + # Use == for mypy + if instr.kind == InstructionKind.J: + return [instr] + if instr.kind == InstructionKind.CZ: + return [instr] + if instr.kind == InstructionKind.I: + return [] + if instr.kind == InstructionKind.H: + return [instruction.J(instr.target, 0)] + if instr.kind == InstructionKind.S: + return instruction_to_jcz(instruction.RZ(instr.target, ANGLE_PI / 2)) + if instr.kind == InstructionKind.X: + return instruction_to_jcz(instruction.RX(instr.target, ANGLE_PI)) + if instr.kind == InstructionKind.Y: + return instruction_list_to_jcz(decompose_y(instr)) + if instr.kind == InstructionKind.Z: + return instruction_to_jcz(instruction.RZ(instr.target, ANGLE_PI)) + if instr.kind == InstructionKind.RX: + return decompose_rx(instr) + if instr.kind == InstructionKind.RY: + return decompose_ry(instr) + if instr.kind == InstructionKind.RZ: + return decompose_rz(instr) + if instr.kind == InstructionKind.CCX: + return instruction_list_to_jcz(decompose_ccx(instr)) + if instr.kind == InstructionKind.RZZ: + return instruction_list_to_jcz(decompose_rzz(instr)) + if instr.kind == InstructionKind.CNOT: + return instruction_list_to_jcz(decompose_cnot(instr)) + if instr.kind == InstructionKind.SWAP: + return instruction_list_to_jcz(decompose_swap(instr)) + if instr.kind == InstructionKind.M: + raise ValueError("Measurement instructions cannot be decomposed into J and CZ gates.") + assert_never(instr.kind) + + +def instruction_list_to_jcz( + instrs: Iterable[Instruction], +) -> list[instruction.J | instruction.CZ]: + """Return a J-∧z decomposition of the sequence of instructions. + + Parameters + ---------- + instrs: the instruction sequence to decompose. + + Returns + ------- + the decomposition. + + """ + return [jcz_instr for instr in instrs for jcz_instr in instruction_to_jcz(instr)] + + +def j_commands(current_node: int, next_node: int, angle: ParameterizedAngle) -> list[command.Command]: + """Return the MBQC pattern commands for a J gate. + + Parameters + ---------- + current_node: the current node. + next_node: the next node. + angle: the angle of the J gate. + + Returns + ------- + the MBQC pattern commands for a J gate as a list + + """ + return [ + command.N(node=next_node), + command.E(nodes=(current_node, next_node)), + command.M(current_node, Measurement.XY(angle)), + command.X(node=next_node, domain={current_node}), + ] + + +def normalize_angle(angle: ParameterizedAngle) -> ParameterizedAngle: + r"""Return an equivalent angle in range :math:`[0, 2 \cdot \pi)` if ``angle`` is instantiated. + + Parameters + ---------- + angle: ParameterizedAngle + An angle. + + Returns + ------- + ParameterizedAngle + An equivalent angle in range :math:`[0, 2 \cdot \pi)` if ``angle`` is instantiated. + If ``angle`` is parameterized, ``angle`` is returned unchanged. + """ + if isinstance(angle, float): + return angle % (2 * ANGLE_PI) + return angle @dataclass(frozen=True) @@ -1152,3 +1409,35 @@ def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: if instr.kind == InstructionKind.M: visitor.qubits[instr.target] = None return TranspileSwapsResult(new_circuit, tuple(visitor.qubits)) + + +class IllformedCircuitError(Exception): + """Raised if the circuit is ill-formed.""" + + def __init__(self) -> None: + """Build the exception.""" + super().__init__("Ill-formed pattern") + + +class CircuitWithMeasurementError(Exception): + """Raised if the circuit contains measurements.""" + + def __init__(self) -> None: + """Build the exception.""" + super().__init__("Circuits containing measurements are not supported by the transpiler.") + + +class InternalInstructionError(Exception): + """Raised if the circuit contains internal _XC or _ZC instructions.""" + + def __init__(self, instr: instruction.Instruction) -> None: + """Build the exception.""" + super().__init__(f"Internal instruction: {instr}") + + +class MeasurementsNoPreproceddedError(Exception): + """Raised if the circuit contains measurements that have not been preprocessed before the transpile step.""" + + def __init__(self) -> None: + """Build the exception.""" + super().__init__("Circuits containing measurements were incorrectly passed to the transpiler.") diff --git a/tests/test_fundamentals.py b/tests/test_fundamentals.py index 48adbbc94..2460fad29 100644 --- a/tests/test_fundamentals.py +++ b/tests/test_fundamentals.py @@ -50,32 +50,32 @@ def test_mul_int(self) -> None: def test_mul_float(self) -> None: left = Sign.PLUS * 1.0 assert isinstance(left, float) - assert left == float(Sign.PLUS) # noqa: RUF069 + assert left == float(Sign.PLUS) right = 1.0 * Sign.PLUS assert isinstance(right, float) - assert right == float(Sign.PLUS) # noqa: RUF069 + assert right == float(Sign.PLUS) left = Sign.MINUS * 1.0 assert isinstance(left, float) - assert left == float(Sign.MINUS) # noqa: RUF069 + assert left == float(Sign.MINUS) right = 1.0 * Sign.MINUS assert isinstance(right, float) - assert right == float(Sign.MINUS) # noqa: RUF069 + assert right == float(Sign.MINUS) def test_mul_complex(self) -> None: left = Sign.PLUS * complex(1) assert isinstance(left, complex) - assert left == complex(Sign.PLUS) # noqa: RUF069 + assert left == complex(Sign.PLUS) right = complex(1) * Sign.PLUS assert isinstance(right, complex) - assert right == complex(Sign.PLUS) # noqa: RUF069 + assert right == complex(Sign.PLUS) left = Sign.MINUS * complex(1) assert isinstance(left, complex) - assert left == complex(Sign.MINUS) # noqa: RUF069 + assert left == complex(Sign.MINUS) right = complex(1) * Sign.MINUS assert isinstance(right, complex) - assert right == complex(Sign.MINUS) # noqa: RUF069 + assert right == complex(Sign.MINUS) def test_int(self) -> None: # Necessary to justify `type: ignore` @@ -103,10 +103,10 @@ def test_properties(self, sign: Sign, is_imag: bool) -> None: assert ComplexUnit.from_properties(sign=sign, is_imag=is_imag).is_imag == is_imag def test_complex(self) -> None: - assert complex(ComplexUnit.ONE) == 1 # noqa: RUF069 - assert complex(ComplexUnit.J) == 1j # noqa: RUF069 - assert complex(ComplexUnit.MINUS_ONE) == -1 # noqa: RUF069 - assert complex(ComplexUnit.MINUS_J) == -1j # noqa: RUF069 + assert complex(ComplexUnit.ONE) == 1 + assert complex(ComplexUnit.J) == 1j + assert complex(ComplexUnit.MINUS_ONE) == -1 + assert complex(ComplexUnit.MINUS_J) == -1j def test_str(self) -> None: assert str(ComplexUnit.ONE) == "1" @@ -116,15 +116,15 @@ def test_str(self) -> None: @pytest.mark.parametrize(("lhs", "rhs"), itertools.product(ComplexUnit, ComplexUnit)) def test_mul_self(self, lhs: ComplexUnit, rhs: ComplexUnit) -> None: - assert complex(lhs * rhs) == complex(lhs) * complex(rhs) # noqa: RUF069 + assert complex(lhs * rhs) == complex(lhs) * complex(rhs) def test_mul_number(self) -> None: assert ComplexUnit.ONE * 1 == ComplexUnit.ONE assert 1 * ComplexUnit.ONE == ComplexUnit.ONE - assert ComplexUnit.ONE * 1.0 == ComplexUnit.ONE # noqa: RUF069 - assert 1.0 * ComplexUnit.ONE == ComplexUnit.ONE # noqa: RUF069 - assert ComplexUnit.ONE * complex(1) == ComplexUnit.ONE # noqa: RUF069 - assert complex(1) * ComplexUnit.ONE == ComplexUnit.ONE # noqa: RUF069 + assert ComplexUnit.ONE * 1.0 == ComplexUnit.ONE + assert 1.0 * ComplexUnit.ONE == ComplexUnit.ONE + assert ComplexUnit.ONE * complex(1) == ComplexUnit.ONE + assert complex(1) * ComplexUnit.ONE == ComplexUnit.ONE def test_neg(self) -> None: assert -ComplexUnit.ONE == ComplexUnit.MINUS_ONE diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 1f2dfcf93..a9bf521d7 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -25,7 +25,7 @@ def test_pattern_affine_operations() -> None: assert alpha + 1 + 1 == alpha + 2 assert alpha + alpha == 2 * alpha assert alpha - alpha == 0 - assert alpha / 2 == 0.5 * alpha # noqa: RUF069 + assert alpha / 2 == 0.5 * alpha assert -alpha + alpha == 0 beta = Placeholder("beta") with pytest.raises(PlaceholderOperationError): diff --git a/tests/test_pauli.py b/tests/test_pauli.py index 670690880..b867cef7a 100644 --- a/tests/test_pauli.py +++ b/tests/test_pauli.py @@ -71,21 +71,21 @@ def test_iterate_false(self) -> None: cmp = list(Pauli.iterate(symbol_only=False)) assert len(cmp) == 16 assert cmp[0] == Pauli.I - assert cmp[1] == 1j * Pauli.I # noqa: RUF069 + assert cmp[1] == 1j * Pauli.I assert cmp[2] == -1 * Pauli.I - assert cmp[3] == -1j * Pauli.I # noqa: RUF069 + assert cmp[3] == -1j * Pauli.I assert cmp[4] == Pauli.X - assert cmp[5] == 1j * Pauli.X # noqa: RUF069 + assert cmp[5] == 1j * Pauli.X assert cmp[6] == -1 * Pauli.X - assert cmp[7] == -1j * Pauli.X # noqa: RUF069 + assert cmp[7] == -1j * Pauli.X assert cmp[8] == Pauli.Y - assert cmp[9] == 1j * Pauli.Y # noqa: RUF069 + assert cmp[9] == 1j * Pauli.Y assert cmp[10] == -1 * Pauli.Y - assert cmp[11] == -1j * Pauli.Y # noqa: RUF069 + assert cmp[11] == -1j * Pauli.Y assert cmp[12] == Pauli.Z - assert cmp[13] == 1j * Pauli.Z # noqa: RUF069 + assert cmp[13] == 1j * Pauli.Z assert cmp[14] == -1 * Pauli.Z - assert cmp[15] == -1j * Pauli.Z # noqa: RUF069 + assert cmp[15] == -1j * Pauli.Z def test_iter_meta(self) -> None: it = Pauli.iterate(symbol_only=False) From cab11715befbbec79e877ccd856e1ec6bd0f7f34 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 17 Apr 2026 14:50:04 +0200 Subject: [PATCH 02/63] adding J to ops and qasm --- graphix/ops.py | 29 +++++++++++++++++++++++++++++ graphix/qasm3_exporter.py | 6 ++++++ graphix/transpiler.py | 2 ++ 3 files changed, 37 insertions(+) diff --git a/graphix/ops.py b/graphix/ops.py index 075273da7..381a212df 100644 --- a/graphix/ops.py +++ b/graphix/ops.py @@ -167,6 +167,35 @@ def rz(theta: ParameterizedAngle) -> npt.NDArray[np.complex128] | npt.NDArray[np """ return Ops._cast_array([[exp(-1j * angle_to_rad(theta) / 2), 0], [0, exp(1j * angle_to_rad(theta) / 2)]], theta) + @overload + @staticmethod + def j(theta: Angle) -> npt.NDArray[np.complex128]: ... + + @overload + @staticmethod + def j(theta: Expression) -> npt.NDArray[np.object_]: ... + + @staticmethod + def j(theta: ParameterizedAngle) -> npt.NDArray[np.complex128] | npt.NDArray[np.object_]: + """J operation. + + Parameters + ---------- + theta : Angle | Expression + rotation angle in units of π + + Returns + ------- + operator : 2*2 np.asarray + """ + return Ops._cast_array( + [ + [1 / np.sqrt(2), (1 / np.sqrt(2)) * exp(1j * angle_to_rad(theta))], + [1 / np.sqrt(2), (-1 / np.sqrt(2)) * exp(1j * angle_to_rad(theta))], + ], + theta, + ) + @overload @staticmethod def rzz(theta: Angle) -> npt.NDArray[np.complex128]: ... diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 4f91e6ab7..5e725fbd5 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -84,6 +84,12 @@ def instruction_to_qasm3(instruction: Instruction) -> str: return qasm3_gate_call( instruction.kind.name.lower(), args=[angle], operands=[qasm3_qubit(instruction.target)] ) + case InstructionKind.J: + angle = angle_to_qasm3(instruction.angle) + target = qasm3_qubit(instruction.target) + p_gate = qasm3_gate_call("p", args=[angle], operands=[target]) + h_gate = qasm3_gate_call("h", operands=[target]) + return f"{p_gate};\n {h_gate}" case InstructionKind.H | InstructionKind.S | InstructionKind.X | InstructionKind.Y | InstructionKind.Z: return qasm3_gate_call(instruction.kind.name.lower(), [qasm3_qubit(instruction.target)]) case InstructionKind.I: diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 03ab63cc2..b6bdf1ed4 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -999,6 +999,8 @@ def evolve(op: Matrix, qargs: Iterable[int]) -> None: evolve_single(Ops.ry(instr.angle), instr.target) case instruction.InstructionKind.RZ: evolve_single(Ops.rz(instr.angle), instr.target) + case instruction.InstructionKind.J: + evolve_single(Ops.j(instr.angle), instr.target) case instruction.InstructionKind.RZZ: evolve(Ops.rzz(instr.angle), [instr.control, instr.target]) case instruction.InstructionKind.CCX: From 03993d7b53dd45a64acb0c60080cf573e4cdc817 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 17 Apr 2026 18:21:46 +0200 Subject: [PATCH 03/63] fixing tests --- graphix/pattern.py | 2 +- graphix/random_objects.py | 3 ++- tests/test_opengraph.py | 3 ++- tests/test_optimization.py | 4 ++++ tests/test_pattern.py | 17 +++++++++++++---- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index d0b37b17d..8f8085d53 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -541,7 +541,7 @@ def shift_signals(self, method: str = "direct") -> dict[int, set[int]]: ---------- method : str, optional 'direct' shift_signals is executed on a conventional Pattern sequence. - 'mc' shift_signals is done using the original algorithm on the measurement calculus paper. + 'mc' shift_signals is done using the original algorithm in the measurement calculus paper. Returns ------- diff --git a/graphix/random_objects.py b/graphix/random_objects.py index d5c89f788..8ae3893a7 100644 --- a/graphix/random_objects.py +++ b/graphix/random_objects.py @@ -418,6 +418,7 @@ def rand_circuit( functools.partial(circuit.ry, angle=ANGLE_PI / 4), functools.partial(circuit.rz, angle=-ANGLE_PI / 4), functools.partial(circuit.rx, angle=-ANGLE_PI / 4), + functools.partial(circuit.j, angle=-ANGLE_PI / 4), circuit.h, circuit.s, circuit.x, @@ -437,7 +438,7 @@ def rand_circuit( if use_ccx: for j, k, l in _gentriplet(nqubits, 2, rng): circuit.ccx(j, k, l) - for j, k in _genpair(nqubits, 4, rng): + for j, k in _genpair(nqubits, 2, rng): circuit.swap(j, k) for j in range(nqubits): ind = rng.integers(len(gate_choice)) diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 1fd2cac83..3dfcd61d1 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -17,6 +17,7 @@ from graphix.fundamentals import ANGLE_PI, Axis, Plane from graphix.measurements import Measurement from graphix.opengraph import OpenGraph, OpenGraphError +from graphix.optimization import StandardizedPattern from graphix.parameter import Placeholder from graphix.pattern import Pattern from graphix.random_objects import rand_circuit @@ -942,7 +943,7 @@ def test_from_to_pattern(self, fx_rng: Generator) -> None: depth = 2 circuit = rand_circuit(n_qubits, depth, fx_rng) pattern_ref = circuit.transpile().pattern - pattern = pattern_ref.extract_opengraph().to_pattern() + pattern = StandardizedPattern.from_pattern(pattern_ref.extract_opengraph().to_pattern()).to_space_optimal_pattern() for plane in {Plane.XY, Plane.XZ, Plane.YZ}: alpha = 2 * ANGLE_PI * fx_rng.random() diff --git a/tests/test_optimization.py b/tests/test_optimization.py index cb891a936..809ee7c33 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -63,7 +63,9 @@ def test_incorporate_pauli_results(fx_bg: PCG64, jumps: int) -> None: pattern.shift_signals() pattern.remove_input_nodes() pattern.perform_pauli_measurements() + pattern = StandardizedPattern.from_pattern(pattern).to_space_optimal_pattern() pattern2 = incorporate_pauli_results(pattern) + pattern2 = StandardizedPattern.from_pattern(pattern2).to_space_optimal_pattern() state = pattern.simulate_pattern(rng=rng) state2 = pattern2.simulate_pattern(rng=rng) assert state.isclose(state2) @@ -97,7 +99,9 @@ def test_remove_useless_domains(fx_bg: PCG64, jumps: int) -> None: pattern.shift_signals() pattern.remove_input_nodes() pattern.perform_pauli_measurements() + pattern = StandardizedPattern.from_pattern(pattern).to_space_optimal_pattern() pattern2 = remove_useless_domains(pattern) + pattern2 = StandardizedPattern.from_pattern(pattern2).to_space_optimal_pattern() state = pattern.simulate_pattern(rng=rng) state2 = pattern2.simulate_pattern(rng=rng) assert state.isclose(state2) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 67acbda83..6dd963975 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -19,6 +19,7 @@ from graphix.fundamentals import ANGLE_PI, Angle, Plane from graphix.measurements import BlochMeasurement, Measurement, Outcome, PauliMeasurement from graphix.opengraph import OpenGraph +from graphix.optimization import StandardizedPattern from graphix.pattern import Pattern, PatternError, RunnabilityError, RunnabilityErrorReason, shift_outcomes from graphix.random_objects import rand_circuit, rand_gate from graphix.sim.density_matrix import DensityMatrix @@ -69,7 +70,9 @@ def test_standardize(self, fx_rng: Generator) -> None: depth = 1 circuit = rand_circuit(nqubits, depth, fx_rng) pattern = circuit.transpile().pattern - + pattern = pattern.infer_pauli_measurements() + pattern.remove_input_nodes() + pattern.perform_pauli_measurements() pattern.standardize() assert pattern.is_standard() state = circuit.simulate_statevector().statevec @@ -214,6 +217,7 @@ def test_shift_signals(self, fx_bg: PCG64, jumps: int) -> None: pattern.standardize() pattern.shift_signals(method="mc") assert pattern.is_standard() + pattern = StandardizedPattern.from_pattern(pattern).to_space_optimal_pattern() state = circuit.simulate_statevector().statevec state_mbqc = pattern.simulate_pattern(rng=rng) assert state_mbqc.isclose(state) @@ -233,6 +237,7 @@ def test_pauli_measurement_random_circuit( pattern.standardize() pattern.shift_signals(method="mc") pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.minimize_space() state = circuit.simulate_statevector().statevec @@ -252,6 +257,7 @@ def test_pauli_measurement_random_circuit_all_paulis( pattern.standardize() pattern.shift_signals(method="mc") pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements(ignore_pauli_with_deps=ignore_pauli_with_deps) assert ignore_pauli_with_deps or not any( cmd.measurement.try_to_pauli() is not None for cmd in pattern if cmd.kind == CommandKind.M @@ -288,10 +294,11 @@ def test_pauli_measurement(self) -> None: pattern.standardize() pattern.shift_signals(method="mc") pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() isolated_nodes = pattern.extract_isolated_nodes() - # 42-node is the isolated and output node. - isolated_nodes_ref = {42} + # 48-node is the isolated and output node. + isolated_nodes_ref = {48} assert isolated_nodes == isolated_nodes_ref def test_pauli_measurement_error(self, fx_rng: Generator) -> None: @@ -332,9 +339,10 @@ def test_pauli_measured_against_nonmeasured(self, fx_bg: PCG64, jumps: int, igno depth = 2 circuit = rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern - pattern.standardize() + pattern.minimize_space() pattern1 = copy.deepcopy(pattern) pattern1.remove_input_nodes() + pattern1 = pattern1.infer_pauli_measurements() pattern1.perform_pauli_measurements(ignore_pauli_with_deps=ignore_pauli_with_deps) state = pattern.simulate_pattern(rng=rng) state1 = pattern1.simulate_pattern(rng=rng) @@ -348,6 +356,7 @@ def test_pauli_repeated_measurement(self, fx_bg: PCG64, jumps: int) -> None: circuit = rand_circuit(nqubits, depth, rng, use_ccx=False) pattern = circuit.transpile().pattern pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() assert not pattern.results pattern.perform_pauli_measurements() assert pattern.results From b8e72425565bfd9540f50ac3a0bfe18140e9c594 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 10:35:11 +0200 Subject: [PATCH 04/63] fixing tests --- tests/test_pattern.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 6dd963975..fae037450 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -486,6 +486,7 @@ def test_pauli_measurement_then_standardize(self, fx_bg: PCG64, jumps: int) -> N circuit = rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.standardize() pattern.minimize_space() From 5490b9de94edf963a726f22754a9cc407823646f Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 16:48:20 +0200 Subject: [PATCH 05/63] fixing QASM with J gate and other tests --- graphix/qasm3_exporter.py | 7 +- graphix/random_objects.py | 5 +- graphix/transpiler.py | 4 +- tests/test_fundamentals.py | 34 +- tests/test_opengraph.py | 4 +- tests/test_parameter.py | 2 +- tests/test_pattern.py | 13 +- tests/test_pauli.py | 16 +- tests/test_qasm3_exporter.py | 1 + .../test_qasm3_exporter_to_graphix_parser.py | 5 +- tests/test_qasm3_exporter_to_qiskit.py | 3 +- tests/test_transpiler.py | 331 +++++++++--------- 12 files changed, 215 insertions(+), 210 deletions(-) diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 5e725fbd5..b3e204a12 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -42,6 +42,7 @@ def circuit_to_qasm3_lines(circuit: Circuit) -> Iterator[str]: """ yield "OPENQASM 3;" yield 'include "stdgates.inc";' + yield "" yield f"qubit[{circuit.width}] q;" if any(instr.kind == InstructionKind.M for instr in circuit.instruction): yield f"bit[{circuit.width}] b;" @@ -85,11 +86,7 @@ def instruction_to_qasm3(instruction: Instruction) -> str: instruction.kind.name.lower(), args=[angle], operands=[qasm3_qubit(instruction.target)] ) case InstructionKind.J: - angle = angle_to_qasm3(instruction.angle) - target = qasm3_qubit(instruction.target) - p_gate = qasm3_gate_call("p", args=[angle], operands=[target]) - h_gate = qasm3_gate_call("h", operands=[target]) - return f"{p_gate};\n {h_gate}" + raise ValueError("OpenQASM3 does not support J(alpha) instructions.") case InstructionKind.H | InstructionKind.S | InstructionKind.X | InstructionKind.Y | InstructionKind.Z: return qasm3_gate_call(instruction.kind.name.lower(), [qasm3_qubit(instruction.target)]) case InstructionKind.I: diff --git a/graphix/random_objects.py b/graphix/random_objects.py index 8ae3893a7..c966c4ae3 100644 --- a/graphix/random_objects.py +++ b/graphix/random_objects.py @@ -377,6 +377,7 @@ def rand_circuit( depth: int, rng: Generator | None = None, *, + use_j: bool = True, use_cz: bool = True, use_rzz: bool = False, use_ccx: bool = False, @@ -393,6 +394,8 @@ def rand_circuit( Number of alternating entangling and single-qubit layers. rng : numpy.random.Generator, optional Random number generator. A default generator is created if ``None``. + use_j : bool, optional + If ``True`` add J gates in each layer (default: ``False``). use_cz : bool, optional If ``True`` add CZ gates in each layer (default: ``True``). use_rzz : bool, optional @@ -418,13 +421,13 @@ def rand_circuit( functools.partial(circuit.ry, angle=ANGLE_PI / 4), functools.partial(circuit.rz, angle=-ANGLE_PI / 4), functools.partial(circuit.rx, angle=-ANGLE_PI / 4), - functools.partial(circuit.j, angle=-ANGLE_PI / 4), circuit.h, circuit.s, circuit.x, circuit.z, circuit.y, *parametric_gate_choice, + *((functools.partial(circuit.j, angle=ANGLE_PI / 4),) if use_j else ()), ) for _ in range(depth): for j, k in _genpair(nqubits, 2, rng): diff --git a/graphix/transpiler.py b/graphix/transpiler.py index b6bdf1ed4..8ad7cf40b 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -21,7 +21,7 @@ from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers from graphix.fundamentals import ANGLE_PI, Axis from graphix.instruction import Instruction, InstructionKind, InstructionVisitor -from graphix.measurements import BlochMeasurement, Measurement, PauliMeasurement +from graphix.measurements import BlochMeasurement, Measurement, Outcome, PauliMeasurement from graphix.opengraph import OpenGraph from graphix.ops import Ops from graphix.optimization import StandardizedPattern @@ -959,7 +959,7 @@ def simulate_statevector( else: backend.add_nodes(range(self.width), input_state) - classical_measures = [] + classical_measures: list[Outcome] = [] for i in range(len(self.instruction)): instr = self.instruction[i] diff --git a/tests/test_fundamentals.py b/tests/test_fundamentals.py index 2460fad29..48adbbc94 100644 --- a/tests/test_fundamentals.py +++ b/tests/test_fundamentals.py @@ -50,32 +50,32 @@ def test_mul_int(self) -> None: def test_mul_float(self) -> None: left = Sign.PLUS * 1.0 assert isinstance(left, float) - assert left == float(Sign.PLUS) + assert left == float(Sign.PLUS) # noqa: RUF069 right = 1.0 * Sign.PLUS assert isinstance(right, float) - assert right == float(Sign.PLUS) + assert right == float(Sign.PLUS) # noqa: RUF069 left = Sign.MINUS * 1.0 assert isinstance(left, float) - assert left == float(Sign.MINUS) + assert left == float(Sign.MINUS) # noqa: RUF069 right = 1.0 * Sign.MINUS assert isinstance(right, float) - assert right == float(Sign.MINUS) + assert right == float(Sign.MINUS) # noqa: RUF069 def test_mul_complex(self) -> None: left = Sign.PLUS * complex(1) assert isinstance(left, complex) - assert left == complex(Sign.PLUS) + assert left == complex(Sign.PLUS) # noqa: RUF069 right = complex(1) * Sign.PLUS assert isinstance(right, complex) - assert right == complex(Sign.PLUS) + assert right == complex(Sign.PLUS) # noqa: RUF069 left = Sign.MINUS * complex(1) assert isinstance(left, complex) - assert left == complex(Sign.MINUS) + assert left == complex(Sign.MINUS) # noqa: RUF069 right = complex(1) * Sign.MINUS assert isinstance(right, complex) - assert right == complex(Sign.MINUS) + assert right == complex(Sign.MINUS) # noqa: RUF069 def test_int(self) -> None: # Necessary to justify `type: ignore` @@ -103,10 +103,10 @@ def test_properties(self, sign: Sign, is_imag: bool) -> None: assert ComplexUnit.from_properties(sign=sign, is_imag=is_imag).is_imag == is_imag def test_complex(self) -> None: - assert complex(ComplexUnit.ONE) == 1 - assert complex(ComplexUnit.J) == 1j - assert complex(ComplexUnit.MINUS_ONE) == -1 - assert complex(ComplexUnit.MINUS_J) == -1j + assert complex(ComplexUnit.ONE) == 1 # noqa: RUF069 + assert complex(ComplexUnit.J) == 1j # noqa: RUF069 + assert complex(ComplexUnit.MINUS_ONE) == -1 # noqa: RUF069 + assert complex(ComplexUnit.MINUS_J) == -1j # noqa: RUF069 def test_str(self) -> None: assert str(ComplexUnit.ONE) == "1" @@ -116,15 +116,15 @@ def test_str(self) -> None: @pytest.mark.parametrize(("lhs", "rhs"), itertools.product(ComplexUnit, ComplexUnit)) def test_mul_self(self, lhs: ComplexUnit, rhs: ComplexUnit) -> None: - assert complex(lhs * rhs) == complex(lhs) * complex(rhs) + assert complex(lhs * rhs) == complex(lhs) * complex(rhs) # noqa: RUF069 def test_mul_number(self) -> None: assert ComplexUnit.ONE * 1 == ComplexUnit.ONE assert 1 * ComplexUnit.ONE == ComplexUnit.ONE - assert ComplexUnit.ONE * 1.0 == ComplexUnit.ONE - assert 1.0 * ComplexUnit.ONE == ComplexUnit.ONE - assert ComplexUnit.ONE * complex(1) == ComplexUnit.ONE - assert complex(1) * ComplexUnit.ONE == ComplexUnit.ONE + assert ComplexUnit.ONE * 1.0 == ComplexUnit.ONE # noqa: RUF069 + assert 1.0 * ComplexUnit.ONE == ComplexUnit.ONE # noqa: RUF069 + assert ComplexUnit.ONE * complex(1) == ComplexUnit.ONE # noqa: RUF069 + assert complex(1) * ComplexUnit.ONE == ComplexUnit.ONE # noqa: RUF069 def test_neg(self) -> None: assert -ComplexUnit.ONE == ComplexUnit.MINUS_ONE diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 3dfcd61d1..58354aed9 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -943,7 +943,9 @@ def test_from_to_pattern(self, fx_rng: Generator) -> None: depth = 2 circuit = rand_circuit(n_qubits, depth, fx_rng) pattern_ref = circuit.transpile().pattern - pattern = StandardizedPattern.from_pattern(pattern_ref.extract_opengraph().to_pattern()).to_space_optimal_pattern() + pattern = StandardizedPattern.from_pattern( + pattern_ref.extract_opengraph().to_pattern() + ).to_space_optimal_pattern() for plane in {Plane.XY, Plane.XZ, Plane.YZ}: alpha = 2 * ANGLE_PI * fx_rng.random() diff --git a/tests/test_parameter.py b/tests/test_parameter.py index a9bf521d7..1f2dfcf93 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -25,7 +25,7 @@ def test_pattern_affine_operations() -> None: assert alpha + 1 + 1 == alpha + 2 assert alpha + alpha == 2 * alpha assert alpha - alpha == 0 - assert alpha / 2 == 0.5 * alpha + assert alpha / 2 == 0.5 * alpha # noqa: RUF069 assert -alpha + alpha == 0 beta = Placeholder("beta") with pytest.raises(PlaceholderOperationError): diff --git a/tests/test_pattern.py b/tests/test_pattern.py index fae037450..68556efa5 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -714,6 +714,7 @@ def test_compose_5(self, fx_rng: Generator) -> None: p2 = circuit_2.transpile().pattern # inputs: [0] p, _ = p1.compose(p2, mapping={0: 1, 1: 2, 2: 3}) + p = StandardizedPattern.from_pattern(p).to_space_optimal_pattern() circuit_12 = Circuit(1) circuit_12.h(0) @@ -752,6 +753,7 @@ def test_compose_7(self, fx_rng: Generator) -> None: circuit_1.rz(0, alpha) p1 = circuit_1.transpile().pattern p1.remove_input_nodes() + p1 = p1.infer_pauli_measurements() p1.perform_pauli_measurements() circuit_2 = Circuit(1) @@ -879,10 +881,12 @@ def test_extract_partial_order_layers_results(self) -> None: c.rz(0, 0.2) p = c.transpile().pattern p.remove_input_nodes() + p = p.infer_pauli_measurements() p.perform_pauli_measurements() assert p.extract_partial_order_layers() == (frozenset({2}), frozenset({0})) p = Pattern(cmds=[N(0), N(1), N(2), M(0), E((1, 2)), X(1, {0}), M(2, Measurement.XY(0.3))]) + p = p.infer_pauli_measurements() p.perform_pauli_measurements() assert p.extract_partial_order_layers() == (frozenset({1}), frozenset({2})) @@ -1119,9 +1123,12 @@ def test_extract_xzc_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: xzc.check_well_formed() p_test = xzc.to_pattern() - for p in [p_ref, p_test]: - p.remove_input_nodes() - p.perform_pauli_measurements() + p_ref.remove_input_nodes() + p_test.remove_input_nodes() + p_ref = p_ref.infer_pauli_measurements() + p_test = p_test.infer_pauli_measurements() + p_ref.perform_pauli_measurements() + p_test.perform_pauli_measurements() s_ref = p_ref.simulate_pattern(rng=rng) s_test = p_test.simulate_pattern(rng=rng) diff --git a/tests/test_pauli.py b/tests/test_pauli.py index b867cef7a..670690880 100644 --- a/tests/test_pauli.py +++ b/tests/test_pauli.py @@ -71,21 +71,21 @@ def test_iterate_false(self) -> None: cmp = list(Pauli.iterate(symbol_only=False)) assert len(cmp) == 16 assert cmp[0] == Pauli.I - assert cmp[1] == 1j * Pauli.I + assert cmp[1] == 1j * Pauli.I # noqa: RUF069 assert cmp[2] == -1 * Pauli.I - assert cmp[3] == -1j * Pauli.I + assert cmp[3] == -1j * Pauli.I # noqa: RUF069 assert cmp[4] == Pauli.X - assert cmp[5] == 1j * Pauli.X + assert cmp[5] == 1j * Pauli.X # noqa: RUF069 assert cmp[6] == -1 * Pauli.X - assert cmp[7] == -1j * Pauli.X + assert cmp[7] == -1j * Pauli.X # noqa: RUF069 assert cmp[8] == Pauli.Y - assert cmp[9] == 1j * Pauli.Y + assert cmp[9] == 1j * Pauli.Y # noqa: RUF069 assert cmp[10] == -1 * Pauli.Y - assert cmp[11] == -1j * Pauli.Y + assert cmp[11] == -1j * Pauli.Y # noqa: RUF069 assert cmp[12] == Pauli.Z - assert cmp[13] == 1j * Pauli.Z + assert cmp[13] == 1j * Pauli.Z # noqa: RUF069 assert cmp[14] == -1 * Pauli.Z - assert cmp[15] == -1j * Pauli.Z + assert cmp[15] == -1j * Pauli.Z # noqa: RUF069 def test_iter_meta(self) -> None: it = Pauli.iterate(symbol_only=False) diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index f9751d0de..b23965f1f 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -56,6 +56,7 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: circuit = rand_circuit(nqubits, depth, rng=rng) pattern = circuit.transpile().pattern pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.minimize_space() _qasm3 = pattern_to_qasm3(pattern) diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index 0a3dc58ed..d0199a707 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -42,7 +42,7 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: nqubits = 5 depth = 4 # See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5 - check_round_trip(rand_circuit(nqubits, depth, rng, use_cz=False)) + check_round_trip(rand_circuit(nqubits, depth, rng, use_j=False, use_cz=True)) @pytest.mark.parametrize( @@ -52,8 +52,7 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: instruction.RZZ(target=0, control=1, angle=ANGLE_PI / 4), instruction.CNOT(target=0, control=1), instruction.SWAP(targets=(0, 1)), - # See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5 - # instruction.CZ(targets=(0, 1)), + instruction.CZ(targets=(0, 1)), instruction.H(target=0), instruction.S(target=0), instruction.X(target=0), diff --git a/tests/test_qasm3_exporter_to_qiskit.py b/tests/test_qasm3_exporter_to_qiskit.py index a857daa7d..0b541380e 100644 --- a/tests/test_qasm3_exporter_to_qiskit.py +++ b/tests/test_qasm3_exporter_to_qiskit.py @@ -117,9 +117,10 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 5 - circuit = rand_circuit(nqubits, depth, rng=rng) + circuit = rand_circuit(nqubits, depth, rng=rng, use_j=False) pattern = circuit.transpile().pattern pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.minimize_space() diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 69fef1f1b..3c2022f03 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -7,12 +7,13 @@ from numpy.random import PCG64, Generator from graphix import instruction -from graphix.branch_selector import ConstBranchSelector +from graphix.branch_selector import ConstBranchSelector, FixedBranchSelector from graphix.fundamentals import ANGLE_PI, Axis, Sign from graphix.instruction import I, InstructionKind from graphix.random_objects import rand_circuit, rand_gate, rand_state_vector +from graphix.simulator import DefaultMeasureMethod from graphix.states import BasicStates -from graphix.transpiler import Circuit, transpile_swaps +from graphix.transpiler import Circuit, decompose_ccx, transpile_swaps from tests.test_branch_selector import CheckedBranchSelector if TYPE_CHECKING: @@ -39,111 +40,37 @@ lambda rng: instruction.RX(0, rng.random() * 2 * ANGLE_PI), lambda rng: instruction.RY(0, rng.random() * 2 * ANGLE_PI), lambda rng: instruction.RZ(0, rng.random() * 2 * ANGLE_PI), + lambda rng: instruction.J(0, rng.random() * 2 * ANGLE_PI), ] class TestTranspilerUnitGates: - def test_cz(self, fx_rng: Generator) -> None: - circuit = Circuit(2) - circuit.cz(0, 1) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_cnot(self, fx_rng: Generator) -> None: - circuit = Circuit(2) - circuit.cnot(0, 1) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_hadamard(self, fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.h(0) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_s(self, fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.s(0) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_x(self, fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.x(0) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_y(self, fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.y(0) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_z(self, fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.z(0) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_rx(self, fx_rng: Generator) -> None: - theta = fx_rng.uniform() * 2 * ANGLE_PI - circuit = Circuit(1) - circuit.rx(0, theta) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_ry(self, fx_rng: Generator) -> None: - theta = fx_rng.uniform() * 2 * ANGLE_PI - circuit = Circuit(1) - circuit.ry(0, theta) - pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) - - def test_rz(self, fx_rng: Generator) -> None: - theta = fx_rng.uniform() * 2 * ANGLE_PI - circuit = Circuit(1) - circuit.rz(0, theta) + @pytest.mark.parametrize("instruction", INSTRUCTION_TEST_CASES) + def test_instruction_flow(self, fx_rng: Generator, instruction: InstructionTestCase) -> None: + circuit = Circuit(3, instr=[instruction(fx_rng)]) pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) - assert state_mbqc.isclose(state) + flow = pattern.to_bloch().extract_causal_flow() + flow.check_well_formed() - def test_i(self, fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.i(0) + @pytest.mark.parametrize("jumps", range(1, 11)) + @pytest.mark.parametrize("instruction", INSTRUCTION_TEST_CASES) + def test_instructions(self, fx_bg: PCG64, jumps: int, instruction: InstructionTestCase) -> None: + rng = Generator(fx_bg.jumped(jumps)) + circuit = Circuit(3, instr=[instruction(rng)]) pattern = circuit.transpile().pattern - state = circuit.simulate_statevector(rng=fx_rng).statevec - state_mbqc = pattern.simulate_pattern(rng=fx_rng) + input_state = rand_state_vector(3, rng=rng) + state = circuit.simulate_statevector(input_state=input_state).statevec + state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) assert state_mbqc.isclose(state) - @pytest.mark.parametrize("jumps", range(1, 11)) - def test_ccx(self, fx_bg: PCG64, jumps: int) -> None: - rng = Generator(fx_bg.jumped(jumps)) - nqubits = 4 - depth = 6 - circuit = rand_circuit(nqubits, depth, rng, use_ccx=True) + def test_simple(self) -> None: + rng = np.random.default_rng(420) + circuit = Circuit(3, instr=[instruction.CCX(0, (1, 2))]) pattern = circuit.transpile().pattern pattern.minimize_space() - state = circuit.simulate_statevector(rng=rng).statevec - state_mbqc = pattern.simulate_pattern(rng=rng) + input_state = rand_state_vector(3, rng=rng) + state = circuit.simulate_statevector(input_state=input_state).statevec + state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) assert state_mbqc.isclose(state) def test_transpiled(self, fx_rng: Generator) -> None: @@ -171,6 +98,21 @@ def test_measure(self, fx_bg: PCG64, jumps: int, axis: Axis, outcome: Outcome) - state_mbqc = pattern.simulate_pattern(rng=rng, input_state=input_state, branch_selector=branch_selector) assert state_mbqc.isclose(state) + @pytest.mark.parametrize("jumps", range(1, 11)) + @pytest.mark.parametrize("axis", [Axis.X, Axis.Y, Axis.Z]) + @pytest.mark.parametrize("outcome", [0, 1]) + def test_measure_early(self, fx_bg: PCG64, jumps: int, axis: Axis, outcome: Outcome) -> None: + rng = Generator(fx_bg.jumped(jumps)) + circuit = Circuit(3) + circuit.m(0, axis) + circuit.cnot(1, 2) + input_state = rand_state_vector(3, rng=rng) + branch_selector = ConstBranchSelector(outcome) + state = circuit.simulate_statevector(rng=rng, input_state=input_state, branch_selector=branch_selector).statevec + pattern = circuit.transpile().pattern + state_mbqc = pattern.simulate_pattern(rng=rng, input_state=input_state, branch_selector=branch_selector) + assert state_mbqc.isclose(state) + @pytest.mark.parametrize("input_axis", [Axis.X, Axis.Y, Axis.Z]) @pytest.mark.parametrize("input_sign", [Sign.PLUS, Sign.MINUS]) @pytest.mark.parametrize("measurement_axis", [Axis.X, Axis.Y, Axis.Z]) @@ -213,6 +155,131 @@ def test_transpile_measurements_to_z_axis(self, fx_bg: PCG64, jumps: int, axis: ).statevec assert state_z.isclose(state) + @pytest.mark.parametrize("jumps", range(1, 11)) + def test_transpile_swaps(self, fx_bg: PCG64, jumps: int) -> None: + rng = Generator(fx_bg.jumped(jumps)) + nqubits = 4 + depth = 6 + circuit = rand_circuit(nqubits, depth, rng, use_ccx=True, use_rzz=True) + assert any(instr.kind == InstructionKind.SWAP for instr in circuit.instruction) + transpiled_swaps = transpile_swaps(circuit) + circuit2 = transpiled_swaps.circuit + assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) + state = circuit.simulate_statevector(rng=rng).statevec + state2 = circuit2.simulate_statevector(rng=rng).statevec + qubits: list[int] = [] + for qubit in transpiled_swaps.qubits: + assert qubit is not None + qubits.append(qubit) + state2.psi = np.transpose(state2.psi, qubits) + assert state.isclose(state2) + + @pytest.mark.parametrize("jumps", range(1, 11)) + @pytest.mark.parametrize("axis", [Axis.X, Axis.Y, Axis.Z]) + @pytest.mark.parametrize("outcome", [0, 1]) + def test_transpile_swaps_with_measurements(self, fx_bg: PCG64, jumps: int, axis: Axis, outcome: Outcome) -> None: + rng = Generator(fx_bg.jumped(jumps)) + circuit = Circuit(3) + circuit.swap(0, 1) + circuit.swap(0, 2) + circuit.cnot(1, 2) + circuit.m(1, axis) + circuit.i(0) + transpiled_swaps = transpile_swaps(circuit) + circuit2 = transpiled_swaps.circuit + assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) + assert I(2) in circuit2.instruction + input_state = rand_state_vector(3, rng=rng) + branch_selector = ConstBranchSelector(outcome) + state = circuit.simulate_statevector(rng=rng, input_state=input_state, branch_selector=branch_selector).statevec + state2 = circuit2.simulate_statevector( + rng=rng, input_state=input_state, branch_selector=branch_selector + ).statevec + assert transpiled_swaps.qubits == (2, None, 1) + state2.swap((0, 1)) + assert state.isclose(state2) + + def test_cz_ccx(self) -> None: + """Test case reported in issue #2. + + https://github.com/qat-inria/graphix-jcz-transpiler/issues/2 + """ + circuit = Circuit(width=3) + circuit.cz(2, 0) + circuit.ccx(0, 1, 2) + ref_state = circuit.simulate_statevector().statevec + pattern = circuit.transpile().pattern + state = pattern.simulate_pattern() + assert state.isclose(ref_state) + + def test_ccx_decomposition(self) -> None: + circuit = Circuit(width=3) + circuit.cz(2, 0) + circuit.ccx(0, 1, 2) + circuit2 = Circuit(width=3) + circuit2.cz(2, 0) + circuit2.extend(decompose_ccx(instruction.CCX(controls=(0, 1), target=2))) + state = circuit.simulate_statevector().statevec + state2 = circuit2.simulate_statevector().statevec + assert state.isclose(state2) + + def test_cnot_cz(self) -> None: + """Test regression about output node reordering.""" + circuit = Circuit(width=3, instr=[instruction.CNOT(0, 1), instruction.CZ((0, 1))]) + state = circuit.simulate_statevector().statevec + pattern = circuit.transpile().pattern + state_mbqc = pattern.simulate_pattern() + assert state.isclose(state_mbqc) + + @pytest.mark.parametrize("jumps", range(1, 6)) + @pytest.mark.parametrize("axes", [[Axis.X, Axis.Y], [Axis.X, Axis.Y, Axis.Z]]) + def test_classical_outputs_consistency(self, fx_bg: PCG64, jumps: int, axes: list[Axis]) -> None: + """Check that `classical_outputs` are in the same order as `classical_measures`.""" + rng = Generator(fx_bg.jumped(jumps)) + n = len(axes) + width = n + 1 + circuit = Circuit(width) + for q in range(n): + circuit.cnot(q, q + 1) + for q, axis in enumerate(axes): + circuit.m(q, axis) + + transpile_result = circuit.transpile() + pattern = transpile_result.pattern + expected_outcomes: list[Outcome] = [1 if q % 2 else 0 for q in range(n)] + results_circuit: dict[int, Outcome] = dict(zip(range(n), expected_outcomes, strict=False)) + m_outcomes = dict(zip(transpile_result.classical_outputs, expected_outcomes, strict=False)) + non_output_nodes = pattern.extract_nodes() - set(pattern.output_nodes) + results_pattern: dict[int, Outcome] = {node: m_outcomes.get(node, 0) for node in non_output_nodes} + input_state = rand_state_vector(width, rng=rng) + measure_method = DefaultMeasureMethod() + circuit_result = circuit.simulate_statevector( + rng=rng, + input_state=input_state, + branch_selector=FixedBranchSelector(results=results_circuit), + ) + pattern.simulate_pattern( + rng=rng, + input_state=input_state, + branch_selector=FixedBranchSelector(results=results_pattern), + measure_method=measure_method, + ) + assert len(transpile_result.classical_outputs) == len(circuit_result.classical_measures) + pattern_measures = [measure_method.results[node] for node in transpile_result.classical_outputs] + assert pattern_measures == list(circuit_result.classical_measures) + assert pattern_measures == expected_outcomes + + def test_classical_outputs_empty(self) -> None: + """Circuits with no M instructions produce empty classical_outputs.""" + circuit = Circuit(2) + circuit.cnot(0, 1) + circuit.h(0) + result = circuit.transpile() + assert len(result.classical_outputs) == 0 + assert len(circuit.simulate_statevector().classical_measures) == 0 + + +class TestCircuits: def test_add_extend(self) -> None: circuit = Circuit(3) circuit.ccx(0, 1, 2) @@ -232,75 +299,3 @@ def test_add_extend(self) -> None: circuit.rz(1, 0.5) circuit2 = Circuit(3, instr=circuit.instruction) assert circuit.instruction == circuit2.instruction - - @pytest.mark.parametrize("instruction", INSTRUCTION_TEST_CASES) - def test_instruction_flow(self, fx_rng: Generator, instruction: InstructionTestCase) -> None: - circuit = Circuit(3, instr=[instruction(fx_rng)]) - pattern = circuit.transpile().pattern - flow = pattern.to_bloch().extract_causal_flow() - flow.check_well_formed() - - @pytest.mark.parametrize("jumps", range(1, 11)) - @pytest.mark.parametrize("instruction", INSTRUCTION_TEST_CASES) - def test_instructions(self, fx_bg: PCG64, jumps: int, instruction: InstructionTestCase) -> None: - rng = Generator(fx_bg.jumped(jumps)) - circuit = Circuit(3, instr=[instruction(rng)]) - pattern = circuit.transpile().pattern - input_state = rand_state_vector(3, rng=rng) - state = circuit.simulate_statevector(input_state=input_state).statevec - state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) - assert state_mbqc.isclose(state) - - def test_simple(self) -> None: - rng = np.random.default_rng(420) - circuit = Circuit(3, instr=[instruction.CCX(0, (1, 2))]) - pattern = circuit.transpile().pattern - pattern.minimize_space() - input_state = rand_state_vector(3, rng=rng) - state = circuit.simulate_statevector(input_state=input_state).statevec - state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) - assert state_mbqc.isclose(state) - - -@pytest.mark.parametrize("jumps", range(1, 11)) -def test_transpile_swaps(fx_bg: PCG64, jumps: int) -> None: - rng = Generator(fx_bg.jumped(jumps)) - nqubits = 4 - depth = 6 - circuit = rand_circuit(nqubits, depth, rng, use_ccx=True, use_rzz=True) - assert any(instr.kind == InstructionKind.SWAP for instr in circuit.instruction) - transpiled_swaps = transpile_swaps(circuit) - circuit2 = transpiled_swaps.circuit - assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) - state = circuit.simulate_statevector(rng=rng).statevec - state2 = circuit2.simulate_statevector(rng=rng).statevec - qubits: list[int] = [] - for qubit in transpiled_swaps.qubits: - assert qubit is not None - qubits.append(qubit) - state2.psi = np.transpose(state2.psi, qubits) - assert state.isclose(state2) - - -@pytest.mark.parametrize("jumps", range(1, 11)) -@pytest.mark.parametrize("axis", [Axis.X, Axis.Y, Axis.Z]) -@pytest.mark.parametrize("outcome", [0, 1]) -def test_transpile_swaps_with_measurements(fx_bg: PCG64, jumps: int, axis: Axis, outcome: Outcome) -> None: - rng = Generator(fx_bg.jumped(jumps)) - circuit = Circuit(3) - circuit.swap(0, 1) - circuit.swap(0, 2) - circuit.cnot(1, 2) - circuit.m(1, axis) - circuit.i(0) - transpiled_swaps = transpile_swaps(circuit) - circuit2 = transpiled_swaps.circuit - assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) - assert I(2) in circuit2.instruction - input_state = rand_state_vector(3, rng=rng) - branch_selector = ConstBranchSelector(outcome) - state = circuit.simulate_statevector(rng=rng, input_state=input_state, branch_selector=branch_selector).statevec - state2 = circuit2.simulate_statevector(rng=rng, input_state=input_state, branch_selector=branch_selector).statevec - assert transpiled_swaps.qubits == (2, None, 1) - state2.swap((0, 1)) - assert state.isclose(state2) From a14e2dc495adfbda4ca8da2817a29ffdfc23abe6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:03:45 +0200 Subject: [PATCH 06/63] Bump the python-packages group with 2 updates (#486) * Bump the python-packages group with 2 updates Updates the requirements on [ruff](https://github.com/astral-sh/ruff) and [qiskit](https://github.com/Qiskit/qiskit) to permit the latest version. Updates `ruff` from 0.15.10 to 0.15.11 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.10...0.15.11) Updates `qiskit` to 2.4.0 - [Release notes](https://github.com/Qiskit/qiskit/releases) - [Changelog](https://github.com/Qiskit/qiskit/blob/main/docs/release_notes.rst) - [Commits](https://github.com/Qiskit/qiskit/compare/1.0.0...2.4.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.11 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: python-packages - dependency-name: qiskit dependency-version: 2.4.0 dependency-type: direct:development dependency-group: python-packages ... Signed-off-by: dependabot[bot] * reverting bump to qiskit max version --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Emlyn Graham --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index efcab81a2..b33b83389 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ mypy==1.20.1 pre-commit # for language-agnostic hooks pyright -ruff==0.15.10 +ruff==0.15.11 # Stubs types-networkx==3.6.1.20260408 From 13365fe84f40f1ab3e62268a8adaf0e5b7c5a125 Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Mon, 20 Apr 2026 15:41:20 +0200 Subject: [PATCH 07/63] Instruct Dependabot to ignore Qiskit 2.4 (#487) Mypy currently fails when type-checking code against the latest Qiskit release (2.4.0; see #485 and python/mypy#21263). While #485 added an upperbound `qiskit<2.4` to `requirements-dev.txt`, it did not configure Dependabot to respect this limit, causing PR reverts (see commit bd9c491 in #486). This commit adds the necessary configuration to prevent those updates. --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6e8e05358..e1469ca79 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,8 @@ updates: python-packages: patterns: - "*" + ignore: + - dependency-name: "qiskit" + # There is a bug with Mypy and Qiskit 2.4.0 + # See https://github.com/python/mypy/issues/21263 + versions: [ ">=2.4" ] From 4b154d154168328e9dcb278efbf1d60da874cc6b Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 17:06:01 +0200 Subject: [PATCH 08/63] update CHANGELOG --- CHANGELOG.md | 8 ++++++++ graphix/qasm3_exporter.py | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f31b3d5a..de2442912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- #484: J & CZ transpilation. + - Replaced `Circuit.transpile()` with a new approach based on J & CZ decomposition. + - Added `instruction.J` class. + ## [0.3.5] - 2026-03-26 ### Added diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index b3e204a12..0a5e9e00e 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -42,7 +42,6 @@ def circuit_to_qasm3_lines(circuit: Circuit) -> Iterator[str]: """ yield "OPENQASM 3;" yield 'include "stdgates.inc";' - yield "" yield f"qubit[{circuit.width}] q;" if any(instr.kind == InstructionKind.M for instr in circuit.instruction): yield f"bit[{circuit.width}] b;" @@ -72,7 +71,23 @@ def angle_to_qasm3(angle: ParameterizedAngle) -> str: def instruction_to_qasm3(instruction: Instruction) -> str: - """Get the OpenQASM3 representation of a single circuit instruction.""" + """Get the OpenQASM3 representation of a single circuit instruction. + + Parameters + ---------- + instruction : Instruction + The instruction to convert. + + Returns + ------- + str + The OpenQASM3 representation of the instruction. + + Raises + ------ + ValueError + If the instruction is not supported by OpenQASM3. + """ match instruction.kind: case InstructionKind.M: if instruction.axis != Axis.Z: From 6c52f8041df1ddaac7b4f32b83d87d02bcbc3610 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 17:20:51 +0200 Subject: [PATCH 09/63] fixing vis tests --- tests/test_visualization.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_visualization.py b/tests/test_visualization.py index d260a84ee..bd6c99208 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -124,6 +124,7 @@ def example_hadamard() -> Pattern: def example_local_clifford() -> Pattern: pattern = example_hadamard() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() return pattern @@ -239,6 +240,7 @@ def test_draw_graph_reference(flow_and_not_pauli_presimulate: bool) -> Figure: pattern = pattern.to_bloch() else: pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.standardize() pattern.draw_graph( From dc14f1bfaeb2ad49c76e9d1d6a79f6ed0b6c53d8 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 17:34:47 +0200 Subject: [PATCH 10/63] fixing docstring examples --- graphix/pattern.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 8f8085d53..ea754c3e7 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -398,16 +398,16 @@ def to_ascii( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_ascii() - 'Z(16,{11}) Z(18,{13}) Z(20,{7}) X(16,{15}) X(18,{14}) X(20,{19}) {17}[M(19)]{7} {10}[M(15,-pi/4)]{11} {17,12}[M(14)]{13} {17,9}[M(11)]{10} {0,1,5,10,13}[M(7,-pi/4)]{17} {1}[M(13,-7pi/4)]{12} {8}[M(10,-7pi/4)]{9} {17}[M(12)]{1} {6}[M(9)]{8} {8,3}[M(1,-pi/4)] {5}[M(8,-pi/4)]{6} {17,4}[M(6)]{5} [M(17)]{0} {3}[M(5,-7pi/4)]{4} M(0) {2}[M(4)]{3} [M(3)]{2} M(2) E(19,20) E(15,16) E(14,18) E(11,15) E(7,19) E(7,14) E(7,11) E(13,14) E(10,11) E(12,13) E(12,7) E(9,10) E(1,12) E(1,9) E(8,9)...(27 more commands)' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31,0)]{28} E(31,32) N(32) [M(29,0)]{0} E(29,30) N(30) {26}[M(28,3pi/2)]{27} E(28,31) N(31) {17}[M(21,0)]{20} E(21,24) N(24) {25}[M(27,0)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20,0)]{17} E(20,21) N(21) {23}[M(26,0)]{25} E(26,27) N(27) {22}[M(25,0)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25)...(63 more commands)' >>> pattern.to_ascii(left_to_right=True) - 'N(3) N(4) N(5) N(6) N(7) N(8) N(9) N(10) N(11) N(12) N(13) N(14) N(15) N(16) N(17) N(18) N(19) N(20) E(2,3) E(3,4) E(4,5) E(4,1) E(0,17) E(5,6) E(17,7) E(6,8) E(6,7) E(8,9) E(1,9) E(1,12) E(9,10) E(12,7) E(12,13) E(10,11) E(13,14) E(7,11) E(7,14) E(7,19) E(11,15)...(27 more commands)' + 'N(3) E(2,3) M(2,0) N(4) E(3,4) [M(3,0)]{2} E(4,1) N(5) E(4,5) {2}[M(4,0)]{3} N(6) E(5,6) {3}[M(5,pi/4)]{4} N(7) E(6,7) {4}[M(6,0)]{5} N(8) E(7,8) {5}[M(7,0)]{6} E(8,0) N(9) E(8,9) {6}[M(8,0)]{7} N(10) E(9,10) {7}[M(9,7pi/4)]{8} N(11) E(10,11) {8}[M(10,0)]{9} N(12) E(11,12) {9}[M(11,0)]{10} N(18) E(1,18) E(1,12) {11,3}[M(1,pi/4)] N(13) E(12,13) {10}[M(12,0)]{11}...(63 more commands)' >>> pattern.to_ascii(limit=None) - 'Z(16,{11}) Z(18,{13}) Z(20,{7}) X(16,{15}) X(18,{14}) X(20,{19}) {17}[M(19)]{7} {10}[M(15,-pi/4)]{11} {17,12}[M(14)]{13} {17,9}[M(11)]{10} {0,1,5,10,13}[M(7,-pi/4)]{17} {1}[M(13,-7pi/4)]{12} {8}[M(10,-7pi/4)]{9} {17}[M(12)]{1} {6}[M(9)]{8} {8,3}[M(1,-pi/4)] {5}[M(8,-pi/4)]{6} {17,4}[M(6)]{5} [M(17)]{0} {3}[M(5,-7pi/4)]{4} M(0) {2}[M(4)]{3} [M(3)]{2} M(2) E(19,20) E(15,16) E(14,18) E(11,15) E(7,19) E(7,14) E(7,11) E(13,14) E(10,11) E(12,13) E(12,7) E(9,10) E(1,12) E(1,9) E(8,9) E(6,7) E(6,8) E(17,7) E(5,6) E(0,17) E(4,1) E(4,5) E(3,4) E(2,3) N(20) N(19) N(18) N(17) N(16) N(15) N(14) N(13) N(12) N(11) N(10) N(9) N(8) N(7) N(6) N(5) N(4) N(3)' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31,0)]{28} E(31,32) N(32) [M(29,0)]{0} E(29,30) N(30) {26}[M(28,3pi/2)]{27} E(28,31) N(31) {17}[M(21,0)]{20} E(21,24) N(24) {25}[M(27,0)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20,0)]{17} E(20,21) N(21) {23}[M(26,0)]{25} E(26,27) N(27) {22}[M(25,0)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25) N(25) {14}[M(16,0)]{15} E(16,0) E(16,17) N(17) {13}[M(15,0)]{14} E(15,16) N(16) {18}[M(22,0)]{19} E(22,23) N(23) E(22,0) {12}[M(14,0)]{13} E(14,15) N(15) {1}[M(19,0)]{18} E(19,22) N(22) {11}[M(13,pi/4)]{12} E(13,14) N(14) [M(18,0)]{1} E(18,19) N(19) {10}[M(12,0)]{11} E(12,13) N(13) {11,3}[M(1,pi/4)] E(1,12) E(1,18) N(18) {9}[M(11,0)]{10} E(11,12) N(12) {8}[M(10,0)]{9} E(10,11) N(11) {7}[M(9,7pi/4)]{8} E(9,10) N(10) {6}[M(8,0)]{7} E(8,9) N(9) E(8,0) {5}[M(7,0)]{6} E(7,8) N(8) {4}[M(6,0)]{5} E(6,7) N(7) {3}[M(5,pi/4)]{4} E(5,6) N(6) {2}[M(4,0)]{3} E(4,5) N(5) E(4,1) [M(3,0)]{2} E(3,4) N(4) M(2,0) E(2,3) N(3)' >>> from graphix.command import CommandKind >>> pattern.to_ascii(target={CommandKind.M}) - '{17}[M(19)]{7} {10}[M(15,-pi/4)]{11} {17,12}[M(14)]{13} {17,9}[M(11)]{10} {0,1,5,10,13}[M(7,-pi/4)]{17} {1}[M(13,-7pi/4)]{12} {8}[M(10,-7pi/4)]{9} {17}[M(12)]{1} {6}[M(9)]{8} {8,3}[M(1,-pi/4)] {5}[M(8,-pi/4)]{6} {17,4}[M(6)]{5} [M(17)]{0} {3}[M(5,-7pi/4)]{4} M(0) {2}[M(4)]{3} [M(3)]{2} M(2)' + '{27}[M(31,0)]{28} [M(29,0)]{0} {26}[M(28,3pi/2)]{27} {17}[M(21,0)]{20} {25}[M(27,0)]{26} {26,19,15,7}[M(0,7pi/4)] {16}[M(20,0)]{17} {23}[M(26,0)]{25} {22}[M(25,0)]{23} {15}[M(17,7pi/4)]{16} {19}[M(23,pi/4)]{22} {14}[M(16,0)]{15} {13}[M(15,0)]{14} {18}[M(22,0)]{19} {12}[M(14,0)]{13} {1}[M(19,0)]{18} {11}[M(13,pi/4)]{12} [M(18,0)]{1} {10}[M(12,0)]{11} {11,3}[M(1,pi/4)] {9}[M(11,0)]{10} {8}[M(10,0)]{9} {7}[M(9,7pi/4)]{8} {6}[M(8,0)]{7} {5}[M(7,0)]{6} {4}[M(6,0)]{5} {3}[M(5,pi/4)]{4} {2}[M(4,0)]{3} [M(3,0)]{2} M(2,0)' >>> pattern.to_ascii(target={CommandKind.X, CommandKind.Z}) - 'Z(16,{11}) Z(18,{13}) Z(20,{7}) X(16,{15}) X(18,{14}) X(20,{19})' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0})' """ return pattern_to_str(self, OutputFormat.ASCII, left_to_right, limit, target) @@ -436,13 +436,14 @@ def to_latex( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_latex() - '\\(Z_{16}^{11}\\,Z_{18}^{13}\\,Z_{20}^{7}\\,X_{16}^{15}\\,X_{18}^{14}\\,X_{20}^{19}\\,{}_{17}[M_{19}]^{7}\\,{}_{10}[M_{15}^{-\\frac{\\pi}{4}}]^{11}\\,{}_{17,12}[M_{14}]^{13}\\,{}_{17,9}[M_{11}]^{10}\\,{}_{0,1,5,10,13}[M_{7}^{-\\frac{\\pi}{4}}]^{17}\\,{}_{1}[M_{13}^{-\\frac{7\\pi}{4}}]^{12}\\,{}_{8}[M_{10}^{-\\frac{7\\pi}{4}}]^{9}\\,{}_{17}[M_{12}]^{1}\\,{}_{6}[M_{9}]^{8}\\,{}_{8,3}[M_{1}^{-\\frac{\\pi}{4}}]\\,{}_{5}[M_{8}^{-\\frac{\\pi}{4}}]^{6}\\,{}_{17,4}[M_{6}]^{5}\\,[M_{17}]^{0}\\,{}_{3}[M_{5}^{-\\frac{7\\pi}{4}}]^{4}\\,M_{0}\\,{}_{2}[M_{4}]^{3}\\,[M_{3}]^{2}\\,M_{2}\\,E_{19,20}\\,E_{15,16}\\,E_{14,18}\\,E_{11,15}\\,E_{7,19}\\,E_{7,14}\\,E_{7,11}\\,E_{13,14}\\,E_{10,11}\\,E_{12,13}\\,E_{12,7}\\,E_{9,10}\\,E_{1,12}\\,E_{1,9}\\,E_{8,9}\\)...(27 more commands)' + '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\,{}_{27}[M_{31}^{0}]^{28}\\,E_{31,32}\\,N_{32}\\,[M_{29}^{0}]^{0}\\,E_{29,30}\\,N_{30}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,E_{28,31}\\,N_{31}\\,{}_{17}[M_{21}^{0}]^{20}\\,E_{21,24}\\,N_{24}\\,{}_{25}[M_{27}^{0}]^{26}\\,E_{27,28}\\,N_{28}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,E_{0,27}\\,E_{0,29}\\,N_{29}\\,{}_{16}[M_{20}^{0}]^{17}\\,E_{20,21}\\,N_{21}\\,{}_{23}[M_{26}^{0}]^{25}\\,E_{26,27}\\,N_{27}\\,{}_{22}[M_{25}^{0}]^{23}\\,E_{25,26}\\,N_{26}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,E_{17,20}\\,N_{20}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,E_{23,25}\\)...(63 more commands)' >>> pattern.to_latex(left_to_right=True) - '\\(N_{3}\\,N_{4}\\,N_{5}\\,N_{6}\\,N_{7}\\,N_{8}\\,N_{9}\\,N_{10}\\,N_{11}\\,N_{12}\\,N_{13}\\,N_{14}\\,N_{15}\\,N_{16}\\,N_{17}\\,N_{18}\\,N_{19}\\,N_{20}\\,E_{2,3}\\,E_{3,4}\\,E_{4,5}\\,E_{4,1}\\,E_{0,17}\\,E_{5,6}\\,E_{17,7}\\,E_{6,8}\\,E_{6,7}\\,E_{8,9}\\,E_{1,9}\\,E_{1,12}\\,E_{9,10}\\,E_{12,7}\\,E_{12,13}\\,E_{10,11}\\,E_{13,14}\\,E_{7,11}\\,E_{7,14}\\,E_{7,19}\\,E_{11,15}\\)...(27 more commands)' + '\\(N_{3}\\,E_{2,3}\\,M_{2}^{0}\\,N_{4}\\,E_{3,4}\\,[M_{3}^{0}]^{2}\\,E_{4,1}\\,N_{5}\\,E_{4,5}\\,{}_{2}[M_{4}^{0}]^{3}\\,N_{6}\\,E_{5,6}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,N_{7}\\,E_{6,7}\\,{}_{4}[M_{6}^{0}]^{5}\\,N_{8}\\,E_{7,8}\\,{}_{5}[M_{7}^{0}]^{6}\\,E_{8,0}\\,N_{9}\\,E_{8,9}\\,{}_{6}[M_{8}^{0}]^{7}\\,N_{10}\\,E_{9,10}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,N_{11}\\,E_{10,11}\\,{}_{8}[M_{10}^{0}]^{9}\\,N_{12}\\,E_{11,12}\\,{}_{9}[M_{11}^{0}]^{10}\\,N_{18}\\,E_{1,18}\\,E_{1,12}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,N_{13}\\,E_{12,13}\\,{}_{10}[M_{12}^{0}]^{11}\\)...(63 more commands)' + >>> from graphix.command import CommandKind >>> pattern.to_latex(target={CommandKind.M}) - '\\({}_{17}[M_{19}]^{7}\\,{}_{10}[M_{15}^{-\\frac{\\pi}{4}}]^{11}\\,{}_{17,12}[M_{14}]^{13}\\,{}_{17,9}[M_{11}]^{10}\\,{}_{0,1,5,10,13}[M_{7}^{-\\frac{\\pi}{4}}]^{17}\\,{}_{1}[M_{13}^{-\\frac{7\\pi}{4}}]^{12}\\,{}_{8}[M_{10}^{-\\frac{7\\pi}{4}}]^{9}\\,{}_{17}[M_{12}]^{1}\\,{}_{6}[M_{9}]^{8}\\,{}_{8,3}[M_{1}^{-\\frac{\\pi}{4}}]\\,{}_{5}[M_{8}^{-\\frac{\\pi}{4}}]^{6}\\,{}_{17,4}[M_{6}]^{5}\\,[M_{17}]^{0}\\,{}_{3}[M_{5}^{-\\frac{7\\pi}{4}}]^{4}\\,M_{0}\\,{}_{2}[M_{4}]^{3}\\,[M_{3}]^{2}\\,M_{2}\\)' + '\\({}_{27}[M_{31}^{0}]^{28}\\,[M_{29}^{0}]^{0}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,{}_{17}[M_{21}^{0}]^{20}\\,{}_{25}[M_{27}^{0}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}^{0}]^{17}\\,{}_{23}[M_{26}^{0}]^{25}\\,{}_{22}[M_{25}^{0}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}^{0}]^{15}\\,{}_{13}[M_{15}^{0}]^{14}\\,{}_{18}[M_{22}^{0}]^{19}\\,{}_{12}[M_{14}^{0}]^{13}\\,{}_{1}[M_{19}^{0}]^{18}\\,{}_{11}[M_{13}^{\frac{\\pi}{4}}]^{12}\\,[M_{18}^{0}]^{1}\\,{}_{10}[M_{12}^{0}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}^{0}]^{10}\\,{}_{8}[M_{10}^{0}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\pi}{4}}]^{8}\\,{}_{6}[M_{8}^{0}]^{7}\\,{}_{5}[M_{7}^{0}]^{6}\\,{}_{4}[M_{6}^{0}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}^{0}]^{3}\\,[M_{3}^{0}]^{2}\\,M_{2}^{0}\\)' >>> pattern.to_latex(target={CommandKind.X, CommandKind.Z}) - '\\(Z_{16}^{11}\\,Z_{18}^{13}\\,Z_{20}^{7}\\,X_{16}^{15}\\,X_{18}^{14}\\,X_{20}^{19}\\)' + '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\)' """ return pattern_to_str(self, OutputFormat.LaTeX, left_to_right, limit, target) @@ -471,13 +472,14 @@ def to_unicode( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_unicode() - 'Z₁₆¹¹ Z₁₈¹³ Z₂₀⁷ X₁₆¹⁵ X₁₈¹⁴ X₂₀¹⁹ ₁₇[M₁₉]⁷ ₁₀[M₁₅(-π/4)]¹¹ ₁₇₊₁₂[M₁₄]¹³ ₁₇₊₉[M₁₁]¹⁰ ₀₊₁₊₅₊₁₀₊₁₃[M₇(-π/4)]¹⁷ ₁[M₁₃(-7π/4)]¹² ₈[M₁₀(-7π/4)]⁹ ₁₇[M₁₂]¹ ₆[M₉]⁸ ₈₊₃[M₁(-π/4)] ₅[M₈(-π/4)]⁶ ₁₇₊₄[M₆]⁵ [M₁₇]⁰ ₃[M₅(-7π/4)]⁴ M₀ ₂[M₄]³ [M₃]² M₂ E₁₉₋₂₀ E₁₅₋₁₆ E₁₄₋₁₈ E₁₁₋₁₅ E₇₋₁₉ E₇₋₁₄ E₇₋₁₁ E₁₃₋₁₄ E₁₀₋₁₁ E₁₂₋₁₃ E₁₂₋₇ E₉₋₁₀ E₁₋₁₂ E₁₋₉ E₈₋₉...(27 more commands)' + 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰ ₂₇[M₃₁(0)]²⁸ E₃₁₋₃₂ N₃₂ [M₂₉(0)]⁰ E₂₉₋₃₀ N₃₀ ₂₆[M₂₈(3π/2)]²⁷ E₂₈₋₃₁ N₃₁ ₁₇[M₂₁(0)]²⁰ E₂₁₋₂₄ N₂₄ ₂₅[M₂₇(0)]²⁶ E₂₇₋₂₈ N₂₈ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] E₀₋₂₇ E₀₋₂₉ N₂₉ ₁₆[M₂₀(0)]¹⁷ E₂₀₋₂₁ N₂₁ ₂₃[M₂₆(0)]²⁵ E₂₆₋₂₇ N₂₇ ₂₂[M₂₅(0)]²³ E₂₅₋₂₆ N₂₆ ₁₅[M₁₇(7π/4)]¹⁶ E₁₇₋₂₀ N₂₀ ₁₉[M₂₃(π/4)]²² E₂₃₋₂₅...(63 more commands)' >>> pattern.to_unicode(left_to_right=True) - 'N₃ N₄ N₅ N₆ N₇ N₈ N₉ N₁₀ N₁₁ N₁₂ N₁₃ N₁₄ N₁₅ N₁₆ N₁₇ N₁₈ N₁₉ N₂₀ E₂₋₃ E₃₋₄ E₄₋₅ E₄₋₁ E₀₋₁₇ E₅₋₆ E₁₇₋₇ E₆₋₈ E₆₋₇ E₈₋₉ E₁₋₉ E₁₋₁₂ E₉₋₁₀ E₁₂₋₇ E₁₂₋₁₃ E₁₀₋₁₁ E₁₃₋₁₄ E₇₋₁₁ E₇₋₁₄ E₇₋₁₉ E₁₁₋₁₅...(27 more commands)' + 'N₃ E₂₋₃ M₂(0) N₄ E₃₋₄ [M₃(0)]² E₄₋₁ N₅ E₄₋₅ ₂[M₄(0)]³ N₆ E₅₋₆ ₃[M₅(π/4)]⁴ N₇ E₆₋₇ ₄[M₆(0)]⁵ N₈ E₇₋₈ ₅[M₇(0)]⁶ E₈₋₀ N₉ E₈₋₉ ₆[M₈(0)]⁷ N₁₀ E₉₋₁₀ ₇[M₉(7π/4)]⁸ N₁₁ E₁₀₋₁₁ ₈[M₁₀(0)]⁹ N₁₂ E₁₁₋₁₂ ₉[M₁₁(0)]¹⁰ N₁₈ E₁₋₁₈ E₁₋₁₂ ₁₁₊₃[M₁(π/4)] N₁₃ E₁₂₋₁₃ ₁₀[M₁₂(0)]¹¹...(63 more commands)' + >>> from graphix.command import CommandKind >>> pattern.to_unicode(target={CommandKind.M}) - '₁₇[M₁₉]⁷ ₁₀[M₁₅(-π/4)]¹¹ ₁₇₊₁₂[M₁₄]¹³ ₁₇₊₉[M₁₁]¹⁰ ₀₊₁₊₅₊₁₀₊₁₃[M₇(-π/4)]¹⁷ ₁[M₁₃(-7π/4)]¹² ₈[M₁₀(-7π/4)]⁹ ₁₇[M₁₂]¹ ₆[M₉]⁸ ₈₊₃[M₁(-π/4)] ₅[M₈(-π/4)]⁶ ₁₇₊₄[M₆]⁵ [M₁₇]⁰ ₃[M₅(-7π/4)]⁴ M₀ ₂[M₄]³ [M₃]² M₂' + '₂₇[M₃₁(0)]²⁸ [M₂₉(0)]⁰ ₂₆[M₂₈(3π/2)]²⁷ ₁₇[M₂₁(0)]²⁰ ₂₅[M₂₇(0)]²⁶ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] ₁₆[M₂₀(0)]¹⁷ ₂₃[M₂₆(0)]²⁵ ₂₂[M₂₅(0)]²³ ₁₅[M₁₇(7π/4)]¹⁶ ₁₉[M₂₃(π/4)]²² ₁₄[M₁₆(0)]¹⁵ ₁₃[M₁₅(0)]¹⁴ ₁₈[M₂₂(0)]¹⁹ ₁₂[M₁₄(0)]¹³ ₁[M₁₉(0)]¹⁸ ₁₁[M₁₃(π/4)]¹² [M₁₈(0)]¹ ₁₀[M₁₂(0)]¹¹ ₁₁₊₃[M₁(π/4)] ₉[M₁₁(0)]¹⁰ ₈[M₁₀(0)]⁹ ₇[M₉(7π/4)]⁸ ₆[M₈(0)]⁷ ₅[M₇(0)]⁶ ₄[M₆(0)]⁵ ₃[M₅(π/4)]⁴ ₂[M₄(0)]³ [M₃(0)]² M₂(0)' >>> pattern.to_unicode(target={CommandKind.X, CommandKind.Z}) - 'Z₁₆¹¹ Z₁₈¹³ Z₂₀⁷ X₁₆¹⁵ X₁₈¹⁴ X₂₀¹⁹' + 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰' """ return pattern_to_str(self, OutputFormat.Unicode, left_to_right, limit, target) From 4aa512b863aeed2e71fbaa7e6bf506a224b5750b Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 17:44:14 +0200 Subject: [PATCH 11/63] fixing examples --- examples/mbqc_vqe.py | 1 + examples/tn_simulation.py | 2 ++ examples/visualization.py | 1 + 3 files changed, 4 insertions(+) diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index fce62723a..f74c8da91 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -93,6 +93,7 @@ def build_mbqc_pattern(self, params: Iterable[ParameterizedAngle]) -> Pattern: pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() # Infer Pauli measurements to determine measurement planes pattern.perform_pauli_measurements() # Perform Pauli measurements return pattern diff --git a/examples/tn_simulation.py b/examples/tn_simulation.py index 8de62c998..3213479e6 100644 --- a/examples/tn_simulation.py +++ b/examples/tn_simulation.py @@ -85,6 +85,7 @@ def ansatz( # %% # Optimizing by performing Pauli measurements in the pattern using efficient stabilizer simulator. pattern.remove_input_nodes() +pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() # %% @@ -203,6 +204,7 @@ def cost( pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() mbqc_tn = pattern.simulate_pattern(backend="tensornetwork", graph_prep="parallel") exp_val: float = 0 diff --git a/examples/visualization.py b/examples/visualization.py index 2c08fbebf..8f81e345c 100644 --- a/examples/visualization.py +++ b/examples/visualization.py @@ -38,6 +38,7 @@ # %% # next, show the gflow: pattern.remove_input_nodes() +pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.draw_graph(flow_from_pattern=False, show_measurement_planes=True, node_distance=(1, 0.6)) From 759da438e82920a177dd95a868e82658c536e5d4 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 17:54:18 +0200 Subject: [PATCH 12/63] testing doc build fix --- .github/workflows/doc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 725e7c1dc..79a0c697e 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -36,7 +36,7 @@ jobs: run: pip install -e ".[dev,doc]" - name: Make docs - run: sphinx-build -W docs/source docs/build -j auto + run: sphinx-build -W docs/source docs/build -j 2 lint-text: runs-on: ubuntu-latest From c0a5053439068810975e14c2a0289099dd28775c Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 20 Apr 2026 17:58:53 +0200 Subject: [PATCH 13/63] fixing failing docs CI --- .github/workflows/doc.yml | 2 +- examples/mbqc_vqe.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 79a0c697e..8efe9d7eb 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -36,7 +36,7 @@ jobs: run: pip install -e ".[dev,doc]" - name: Make docs - run: sphinx-build -W docs/source docs/build -j 2 + run: sphinx-build -W docs/source docs/build -j 1 lint-text: runs-on: ubuntu-latest diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index f74c8da91..82b7f846b 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -95,6 +95,7 @@ def build_mbqc_pattern(self, params: Iterable[ParameterizedAngle]) -> Pattern: pattern.remove_input_nodes() pattern = pattern.infer_pauli_measurements() # Infer Pauli measurements to determine measurement planes pattern.perform_pauli_measurements() # Perform Pauli measurements + pattern.minimize_space() return pattern # %% From 513687fb6fac0bdffac3de35494478d641346e41 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 21 Apr 2026 14:37:49 +0200 Subject: [PATCH 14/63] fixing docs --- docs/source/generator.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/generator.rst b/docs/source/generator.rst index 1a9915952..7fb8488bb 100644 --- a/docs/source/generator.rst +++ b/docs/source/generator.rst @@ -16,6 +16,8 @@ Pattern Generation .. automethod:: simulate_statevector + .. automethod:: cz + .. automethod:: cnot .. automethod:: h @@ -34,6 +36,8 @@ Pattern Generation .. automethod:: rz + .. automethod:: j + .. automethod:: ccx .. automethod:: m From 90f455ad4afc23b070e1e361818a41a6a7777863 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 21 Apr 2026 14:57:48 +0200 Subject: [PATCH 15/63] fixing reverse deps failures and pattern latex docstring --- graphix/pattern.py | 2 +- graphix/transpiler.py | 445 +----------------------------------------- 2 files changed, 3 insertions(+), 444 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index ea754c3e7..3164b0a79 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -441,7 +441,7 @@ def to_latex( '\\(N_{3}\\,E_{2,3}\\,M_{2}^{0}\\,N_{4}\\,E_{3,4}\\,[M_{3}^{0}]^{2}\\,E_{4,1}\\,N_{5}\\,E_{4,5}\\,{}_{2}[M_{4}^{0}]^{3}\\,N_{6}\\,E_{5,6}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,N_{7}\\,E_{6,7}\\,{}_{4}[M_{6}^{0}]^{5}\\,N_{8}\\,E_{7,8}\\,{}_{5}[M_{7}^{0}]^{6}\\,E_{8,0}\\,N_{9}\\,E_{8,9}\\,{}_{6}[M_{8}^{0}]^{7}\\,N_{10}\\,E_{9,10}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,N_{11}\\,E_{10,11}\\,{}_{8}[M_{10}^{0}]^{9}\\,N_{12}\\,E_{11,12}\\,{}_{9}[M_{11}^{0}]^{10}\\,N_{18}\\,E_{1,18}\\,E_{1,12}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,N_{13}\\,E_{12,13}\\,{}_{10}[M_{12}^{0}]^{11}\\)...(63 more commands)' >>> from graphix.command import CommandKind >>> pattern.to_latex(target={CommandKind.M}) - '\\({}_{27}[M_{31}^{0}]^{28}\\,[M_{29}^{0}]^{0}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,{}_{17}[M_{21}^{0}]^{20}\\,{}_{25}[M_{27}^{0}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}^{0}]^{17}\\,{}_{23}[M_{26}^{0}]^{25}\\,{}_{22}[M_{25}^{0}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}^{0}]^{15}\\,{}_{13}[M_{15}^{0}]^{14}\\,{}_{18}[M_{22}^{0}]^{19}\\,{}_{12}[M_{14}^{0}]^{13}\\,{}_{1}[M_{19}^{0}]^{18}\\,{}_{11}[M_{13}^{\frac{\\pi}{4}}]^{12}\\,[M_{18}^{0}]^{1}\\,{}_{10}[M_{12}^{0}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}^{0}]^{10}\\,{}_{8}[M_{10}^{0}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\pi}{4}}]^{8}\\,{}_{6}[M_{8}^{0}]^{7}\\,{}_{5}[M_{7}^{0}]^{6}\\,{}_{4}[M_{6}^{0}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}^{0}]^{3}\\,[M_{3}^{0}]^{2}\\,M_{2}^{0}\\)' + '\\({}_{27}[M_{31}^{0}]^{28}\\,[M_{29}^{0}]^{0}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,{}_{17}[M_{21}^{0}]^{20}\\,{}_{25}[M_{27}^{0}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}^{0}]^{17}\\,{}_{23}[M_{26}^{0}]^{25}\\,{}_{22}[M_{25}^{0}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}^{0}]^{15}\\,{}_{13}[M_{15}^{0}]^{14}\\,{}_{18}[M_{22}^{0}]^{19}\\,{}_{12}[M_{14}^{0}]^{13}\\,{}_{1}[M_{19}^{0}]^{18}\\,{}_{11}[M_{13}^{\\frac{\\pi}{4}}]^{12}\\,[M_{18}^{0}]^{1}\\,{}_{10}[M_{12}^{0}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}^{0}]^{10}\\,{}_{8}[M_{10}^{0}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,{}_{6}[M_{8}^{0}]^{7}\\,{}_{5}[M_{7}^{0}]^{6}\\,{}_{4}[M_{6}^{0}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}^{0}]^{3}\\,[M_{3}^{0}]^{2}\\,M_{2}^{0}\\)' >>> pattern.to_latex(target={CommandKind.X, CommandKind.Z}) '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\)' """ diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 8ad7cf40b..a7dc25b46 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -429,7 +429,7 @@ def transpile(self) -> TranspileResult: measurements: dict[int, BlochMeasurement] = {} classical_outputs: dict[int, command.M] = {} inputs = list(range(n_nodes)) - graph: nx.Graph[int] = nx.Graph() # CHANGE TO OPEN GRAPH NOT NX + graph: nx.Graph[int] = nx.Graph() # In future change to OpenGraph not nx graph.add_nodes_from(inputs) x_corrections: dict[int, set[int]] = {} for instr in self.instruction: @@ -481,450 +481,9 @@ def transpile(self) -> TranspileResult: f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) pattern = StandardizedPattern.from_pattern(f.to_corrections().to_pattern()).to_space_optimal_pattern() pattern.extend(classical_outputs.values()) + pattern = pattern.infer_pauli_measurements() return TranspileResult(pattern, tuple(classical_outputs.keys())) - @classmethod - def _cnot_command( - cls, control_node: int, target_node: int, ancilla: Sequence[int] - ) -> tuple[int, int, list[command.Command]]: - """MBQC commands for CNOT gate. - - Parameters - ---------- - control_node : int - control node on graph - target : int - target node on graph - ancilla : list of two ints - ancilla node indices to be added to graph - - Returns - ------- - control_out : int - control node on graph after the gate - target_out : int - target node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 2 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] - seq.extend( - ( - E(nodes=(target_node, ancilla[0])), - E(nodes=(control_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - M(node=target_node), - M(node=ancilla[0], s_domain={target_node}), - X(node=ancilla[1], domain={ancilla[0]}), - Z(node=ancilla[1], domain={target_node}), - Z(node=control_node, domain={target_node}), - ) - ) - return control_node, ancilla[1], seq - - @classmethod - def _cz_command(cls, target_1: int, target_2: int) -> list[Command]: - """MBQC commands for CZ gate. - - Parameters - ---------- - target_1 : int - target node on graph - target_2 : int - other target node on graph - - Returns - ------- - commands : list - list of MBQC commands - """ - return [E(nodes=(target_1, target_2))] - - @classmethod - def _m_command(cls, input_node: int, axis: Axis) -> list[Command]: - """MBQC commands for measuring qubit. - - Parameters - ---------- - input_node : int - target node on graph - axis : Axis - measurement basis - - Returns - ------- - commands : list - list of MBQC commands - """ - # `measurement.angle` and `M.angle` are both expressed in units of π. - return [M(input_node, PauliMeasurement(axis))] - - @classmethod - def _h_command(cls, input_node: int, ancilla: int) -> tuple[int, list[Command]]: - """MBQC commands for Hadamard gate. - - Parameters - ---------- - input_node : int - target node on graph - ancilla : int - ancilla node index to be added - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - seq: list[Command] = [N(node=ancilla)] - seq.extend((E(nodes=(input_node, ancilla)), M(node=input_node), X(node=ancilla, domain={input_node}))) - return ancilla, seq - - @classmethod - def _s_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: - """MBQC commands for S gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of two ints - ancilla node indices to be added to graph - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 2 - seq: list[Command] = [N(node=ancilla[0]), command.N(node=ancilla[1])] - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - M(input_node, -Measurement.Y), - M(node=ancilla[0], s_domain={input_node}), - X(node=ancilla[1], domain={ancilla[0]}), - Z(node=ancilla[1], domain={input_node}), - ) - ) - return ancilla[1], seq - - @classmethod - def _x_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: - """MBQC commands for Pauli X gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of two ints - ancilla node indices to be added to graph - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 2 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - M(node=input_node), - M(ancilla[0], -Measurement.X, s_domain={input_node}), - X(node=ancilla[1], domain={ancilla[0]}), - Z(node=ancilla[1], domain={input_node}), - ) - ) - return ancilla[1], seq - - @classmethod - def _y_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: - """MBQC commands for Pauli Y gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of four ints - ancilla node indices to be added to graph - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 4 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] - seq.extend([N(node=ancilla[2]), N(node=ancilla[3])]) - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - E(nodes=(ancilla[1], ancilla[2])), - E(nodes=(ancilla[2], ancilla[3])), - M(input_node, Measurement.Y), - M(ancilla[0], -Measurement.X, s_domain={input_node}), - M(ancilla[1], -Measurement.Y, s_domain={ancilla[0]}, t_domain={input_node}), - M(node=ancilla[2], s_domain={ancilla[1]}, t_domain={ancilla[0]}), - X(node=ancilla[3], domain={ancilla[2]}), - Z(node=ancilla[3], domain={ancilla[1]}), - ) - ) - return ancilla[3], seq - - @classmethod - def _z_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: - """MBQC commands for Pauli Z gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of two ints - ancilla node indices to be added to graph - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 2 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - M(input_node, -Measurement.X), - M(node=ancilla[0], s_domain={input_node}), - X(node=ancilla[1], domain={ancilla[0]}), - Z(node=ancilla[1], domain={input_node}), - ) - ) - return ancilla[1], seq - - @classmethod - def _rx_command( - cls, input_node: int, ancilla: Sequence[int], angle: ParameterizedAngle - ) -> tuple[int, list[command.Command]]: - """MBQC commands for X rotation gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of two ints - ancilla node indices to be added to graph - angle : ParameterizedAngle - measurement angle in units of π - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 2 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - M(node=input_node), - M(ancilla[0], Measurement.XY(-angle), s_domain={input_node}), - X(node=ancilla[1], domain={ancilla[0]}), - Z(node=ancilla[1], domain={input_node}), - ) - ) - return ancilla[1], seq - - @classmethod - def _ry_command( - cls, input_node: int, ancilla: Sequence[int], angle: ParameterizedAngle - ) -> tuple[int, list[command.Command]]: - """MBQC commands for Y rotation gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of four ints - ancilla node indices to be added to graph - angle : ParameterizedAngle - rotation angle in units of π - - Returns - ------- - out_node : int - control node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 4 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] - seq.extend([N(node=ancilla[2]), N(node=ancilla[3])]) - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - E(nodes=(ancilla[1], ancilla[2])), - E(nodes=(ancilla[2], ancilla[3])), - M(input_node, Measurement.Y), - M(ancilla[0], Measurement.XY(-angle), s_domain={input_node}), - M(ancilla[1], -Measurement.Y, s_domain={ancilla[0]}, t_domain={input_node}), - M(node=ancilla[2], s_domain={ancilla[1]}, t_domain={ancilla[0]}), - X(node=ancilla[3], domain={ancilla[2]}), - Z(node=ancilla[3], domain={ancilla[1]}), - ) - ) - return ancilla[3], seq - - @classmethod - def _rz_command( - cls, input_node: int, ancilla: Sequence[int], angle: ParameterizedAngle - ) -> tuple[int, list[command.Command]]: - """MBQC commands for Z rotation gate. - - Parameters - ---------- - input_node : int - input node index - ancilla : list of two ints - ancilla node indices to be added to graph - angle : ParameterizedAngle - measurement angle in units of π - - Returns - ------- - out_node : int - node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 2 - seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] # assign new qubit labels - seq.extend( - ( - E(nodes=(input_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - M(input_node, Measurement.XY(-angle)), - M(node=ancilla[0], s_domain={input_node}), - X(node=ancilla[1], domain={ancilla[0]}), - Z(node=ancilla[1], domain={input_node}), - ) - ) - return ancilla[1], seq - - @classmethod - def _ccx_command( - cls, - control_node1: int, - control_node2: int, - target_node: int, - ancilla: Sequence[int], - ) -> tuple[int, int, int, list[command.Command]]: - """MBQC commands for CCX gate. - - Parameters - ---------- - control_node1 : int - first control node on graph - control_node2 : int - second control node on graph - target_node : int - target node on graph - ancilla : list of int - ancilla node indices to be added to graph - - Returns - ------- - control_out1 : int - first control node on graph after the gate - control_out2 : int - second control node on graph after the gate - target_out : int - target node on graph after the gate - commands : list - list of MBQC commands - """ - assert len(ancilla) == 18 - seq: list[Command] = [N(node=ancilla[i]) for i in range(18)] # assign new qubit labels - seq.extend( - ( - E(nodes=(target_node, ancilla[0])), - E(nodes=(ancilla[0], ancilla[1])), - E(nodes=(ancilla[1], ancilla[2])), - E(nodes=(ancilla[1], control_node2)), - E(nodes=(control_node1, ancilla[14])), - E(nodes=(ancilla[2], ancilla[3])), - E(nodes=(ancilla[14], ancilla[4])), - E(nodes=(ancilla[3], ancilla[5])), - E(nodes=(ancilla[3], ancilla[4])), - E(nodes=(ancilla[5], ancilla[6])), - E(nodes=(control_node2, ancilla[6])), - E(nodes=(control_node2, ancilla[9])), - E(nodes=(ancilla[6], ancilla[7])), - E(nodes=(ancilla[9], ancilla[4])), - E(nodes=(ancilla[9], ancilla[10])), - E(nodes=(ancilla[7], ancilla[8])), - E(nodes=(ancilla[10], ancilla[11])), - E(nodes=(ancilla[4], ancilla[8])), - E(nodes=(ancilla[4], ancilla[11])), - E(nodes=(ancilla[4], ancilla[16])), - E(nodes=(ancilla[8], ancilla[12])), - E(nodes=(ancilla[11], ancilla[15])), - E(nodes=(ancilla[12], ancilla[13])), - E(nodes=(ancilla[16], ancilla[17])), - M(node=target_node), - M(node=ancilla[0], s_domain={target_node}), - M(node=ancilla[1], s_domain={ancilla[0]}, t_domain={target_node}), - M(node=control_node1), - M(ancilla[2], Measurement.XY(-7 * ANGLE_PI / 4), s_domain={ancilla[1]}, t_domain={ancilla[0]}), - M(node=ancilla[14], s_domain={control_node1}), - M(node=ancilla[3], s_domain={ancilla[2]}, t_domain={ancilla[1], ancilla[14]}), - M(ancilla[5], Measurement.XY(-ANGLE_PI / 4), s_domain={ancilla[3]}, t_domain={ancilla[2]}), - M(control_node2, Measurement.XY(-ANGLE_PI / 4), t_domain={ancilla[5], ancilla[0]}), - M(node=ancilla[6], s_domain={ancilla[5]}, t_domain={ancilla[3]}), - M(node=ancilla[9], s_domain={control_node2}, t_domain={ancilla[14]}), - M(ancilla[7], Measurement.XY(-7 * ANGLE_PI / 4), s_domain={ancilla[6]}, t_domain={ancilla[5]}), - M(ancilla[10], Measurement.XY(-7 * ANGLE_PI / 4), s_domain={ancilla[9]}, t_domain={control_node2}), - M( - ancilla[4], - Measurement.XY(-ANGLE_PI / 4), - s_domain={ancilla[14]}, - t_domain={control_node1, control_node2, ancilla[2], ancilla[7], ancilla[10]}, - ), - M(node=ancilla[8], s_domain={ancilla[7]}, t_domain={ancilla[14], ancilla[6]}), - M(node=ancilla[11], s_domain={ancilla[10]}, t_domain={ancilla[9], ancilla[14]}), - M(ancilla[12], Measurement.XY(-ANGLE_PI / 4), s_domain={ancilla[8]}, t_domain={ancilla[7]}), - M( - node=ancilla[16], - s_domain={ancilla[4]}, - t_domain={ancilla[14]}, - ), - X(node=ancilla[17], domain={ancilla[16]}), - X(node=ancilla[15], domain={ancilla[11]}), - X(node=ancilla[13], domain={ancilla[12]}), - Z(node=ancilla[17], domain={ancilla[4]}), - Z(node=ancilla[15], domain={ancilla[10]}), - Z(node=ancilla[13], domain={ancilla[8]}), - ) - ) - return ancilla[17], ancilla[15], ancilla[13], seq - def simulate_statevector( self, input_state: Data | None = None, From a9fc97baa04034e319ad8a12a1b029a7ef5eb4b1 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 21 Apr 2026 18:14:50 +0200 Subject: [PATCH 16/63] fix lint --- graphix/transpiler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index a7dc25b46..751fbde24 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -17,7 +17,6 @@ from graphix import command, instruction, parameter from graphix.branch_selector import BranchSelector, RandomBranchSelector -from graphix.command import E, M, N, X, Z from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers from graphix.fundamentals import ANGLE_PI, Axis from graphix.instruction import Instruction, InstructionKind, InstructionVisitor @@ -32,7 +31,6 @@ from numpy.random import Generator - from graphix.command import Command from graphix.fundamentals import ParameterizedAngle from graphix.parameter import ExpressionOrFloat, Parameter from graphix.pattern import Pattern From d145d848a0714f07a5266c409a4f364a348b23ff Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 22 Apr 2026 11:48:33 +0200 Subject: [PATCH 17/63] fixing docstrings --- graphix/pattern.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 7ff35cd77..e56d39dab 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -410,14 +410,14 @@ def to_ascii( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_ascii() - 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31,0)]{28} E(31,32) N(32) [M(29,0)]{0} E(29,30) N(30) {26}[M(28,3pi/2)]{27} E(28,31) N(31) {17}[M(21,0)]{20} E(21,24) N(24) {25}[M(27,0)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20,0)]{17} E(20,21) N(21) {23}[M(26,0)]{25} E(26,27) N(27) {22}[M(25,0)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25)...(63 more commands)' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31)]{28} E(31,32) N(32) [M(29)]{0} E(29,30) N(30) {26}[M(28,-Y)]{27} E(28,31) N(31) {17}[M(21)]{20} E(21,24) N(24) {25}[M(27)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20)]{17} E(20,21) N(21) {23}[M(26)]{25} E(26,27) N(27) {22}[M(25)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25)...(63 more commands)' >>> pattern.to_ascii(left_to_right=True) - 'N(3) E(2,3) M(2,0) N(4) E(3,4) [M(3,0)]{2} E(4,1) N(5) E(4,5) {2}[M(4,0)]{3} N(6) E(5,6) {3}[M(5,pi/4)]{4} N(7) E(6,7) {4}[M(6,0)]{5} N(8) E(7,8) {5}[M(7,0)]{6} E(8,0) N(9) E(8,9) {6}[M(8,0)]{7} N(10) E(9,10) {7}[M(9,7pi/4)]{8} N(11) E(10,11) {8}[M(10,0)]{9} N(12) E(11,12) {9}[M(11,0)]{10} N(18) E(1,18) E(1,12) {11,3}[M(1,pi/4)] N(13) E(12,13) {10}[M(12,0)]{11}...(63 more commands)' + 'N(3) E(2,3) M(2) N(4) E(3,4) [M(3)]{2} E(4,1) N(5) E(4,5) {2}[M(4)]{3} N(6) E(5,6) {3}[M(5,pi/4)]{4} N(7) E(6,7) {4}[M(6)]{5} N(8) E(7,8) {5}[M(7)]{6} E(8,0) N(9) E(8,9) {6}[M(8)]{7} N(10) E(9,10) {7}[M(9,7pi/4)]{8} N(11) E(10,11) {8}[M(10)]{9} N(12) E(11,12) {9}[M(11)]{10} N(18) E(1,18) E(1,12) {11,3}[M(1,pi/4)] N(13) E(12,13) {10}[M(12)]{11}...(63 more commands)' >>> pattern.to_ascii(limit=None) - 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31,0)]{28} E(31,32) N(32) [M(29,0)]{0} E(29,30) N(30) {26}[M(28,3pi/2)]{27} E(28,31) N(31) {17}[M(21,0)]{20} E(21,24) N(24) {25}[M(27,0)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20,0)]{17} E(20,21) N(21) {23}[M(26,0)]{25} E(26,27) N(27) {22}[M(25,0)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25) N(25) {14}[M(16,0)]{15} E(16,0) E(16,17) N(17) {13}[M(15,0)]{14} E(15,16) N(16) {18}[M(22,0)]{19} E(22,23) N(23) E(22,0) {12}[M(14,0)]{13} E(14,15) N(15) {1}[M(19,0)]{18} E(19,22) N(22) {11}[M(13,pi/4)]{12} E(13,14) N(14) [M(18,0)]{1} E(18,19) N(19) {10}[M(12,0)]{11} E(12,13) N(13) {11,3}[M(1,pi/4)] E(1,12) E(1,18) N(18) {9}[M(11,0)]{10} E(11,12) N(12) {8}[M(10,0)]{9} E(10,11) N(11) {7}[M(9,7pi/4)]{8} E(9,10) N(10) {6}[M(8,0)]{7} E(8,9) N(9) E(8,0) {5}[M(7,0)]{6} E(7,8) N(8) {4}[M(6,0)]{5} E(6,7) N(7) {3}[M(5,pi/4)]{4} E(5,6) N(6) {2}[M(4,0)]{3} E(4,5) N(5) E(4,1) [M(3,0)]{2} E(3,4) N(4) M(2,0) E(2,3) N(3)' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31)]{28} E(31,32) N(32) [M(29)]{0} E(29,30) N(30) {26}[M(28,-Y)]{27} E(28,31) N(31) {17}[M(21)]{20} E(21,24) N(24) {25}[M(27)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20)]{17} E(20,21) N(21) {23}[M(26)]{25} E(26,27) N(27) {22}[M(25)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25) N(25) {14}[M(16)]{15} E(16,0) E(16,17) N(17) {13}[M(15)]{14} E(15,16) N(16) {18}[M(22)]{19} E(22,23) N(23) E(22,0) {12}[M(14)]{13} E(14,15) N(15) {1}[M(19)]{18} E(19,22) N(22) {11}[M(13,pi/4)]{12} E(13,14) N(14) [M(18)]{1} E(18,19) N(19) {10}[M(12)]{11} E(12,13) N(13) {11,3}[M(1,pi/4)] E(1,12) E(1,18) N(18) {9}[M(11)]{10} E(11,12) N(12) {8}[M(10)]{9} E(10,11) N(11) {7}[M(9,7pi/4)]{8} E(9,10) N(10) {6}[M(8)]{7} E(8,9) N(9) E(8,0) {5}[M(7)]{6} E(7,8) N(8) {4}[M(6)]{5} E(6,7) N(7) {3}[M(5,pi/4)]{4} E(5,6) N(6) {2}[M(4)]{3} E(4,5) N(5) E(4,1) [M(3)]{2} E(3,4) N(4) M(2) E(2,3) N(3)' >>> from graphix.command import CommandKind >>> pattern.to_ascii(target={CommandKind.M}) - '{27}[M(31,0)]{28} [M(29,0)]{0} {26}[M(28,3pi/2)]{27} {17}[M(21,0)]{20} {25}[M(27,0)]{26} {26,19,15,7}[M(0,7pi/4)] {16}[M(20,0)]{17} {23}[M(26,0)]{25} {22}[M(25,0)]{23} {15}[M(17,7pi/4)]{16} {19}[M(23,pi/4)]{22} {14}[M(16,0)]{15} {13}[M(15,0)]{14} {18}[M(22,0)]{19} {12}[M(14,0)]{13} {1}[M(19,0)]{18} {11}[M(13,pi/4)]{12} [M(18,0)]{1} {10}[M(12,0)]{11} {11,3}[M(1,pi/4)] {9}[M(11,0)]{10} {8}[M(10,0)]{9} {7}[M(9,7pi/4)]{8} {6}[M(8,0)]{7} {5}[M(7,0)]{6} {4}[M(6,0)]{5} {3}[M(5,pi/4)]{4} {2}[M(4,0)]{3} [M(3,0)]{2} M(2,0)' + '{27}[M(31)]{28} [M(29)]{0} {26}[M(28,-Y)]{27} {17}[M(21)]{20} {25}[M(27)]{26} {26,19,15,7}[M(0,7pi/4)] {16}[M(20)]{17} {23}[M(26)]{25} {22}[M(25)]{23} {15}[M(17,7pi/4)]{16} {19}[M(23,pi/4)]{22} {14}[M(16)]{15} {13}[M(15)]{14} {18}[M(22)]{19} {12}[M(14)]{13} {1}[M(19)]{18} {11}[M(13,pi/4)]{12} [M(18)]{1} {10}[M(12)]{11} {11,3}[M(1,pi/4)] {9}[M(11)]{10} {8}[M(10)]{9} {7}[M(9,7pi/4)]{8} {6}[M(8)]{7} {5}[M(7)]{6} {4}[M(6)]{5} {3}[M(5,pi/4)]{4} {2}[M(4)]{3} [M(3)]{2} M(2)' >>> pattern.to_ascii(target={CommandKind.X, CommandKind.Z}) 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0})' """ @@ -448,12 +448,12 @@ def to_latex( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_latex() - '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\,{}_{27}[M_{31}^{0}]^{28}\\,E_{31,32}\\,N_{32}\\,[M_{29}^{0}]^{0}\\,E_{29,30}\\,N_{30}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,E_{28,31}\\,N_{31}\\,{}_{17}[M_{21}^{0}]^{20}\\,E_{21,24}\\,N_{24}\\,{}_{25}[M_{27}^{0}]^{26}\\,E_{27,28}\\,N_{28}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,E_{0,27}\\,E_{0,29}\\,N_{29}\\,{}_{16}[M_{20}^{0}]^{17}\\,E_{20,21}\\,N_{21}\\,{}_{23}[M_{26}^{0}]^{25}\\,E_{26,27}\\,N_{27}\\,{}_{22}[M_{25}^{0}]^{23}\\,E_{25,26}\\,N_{26}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,E_{17,20}\\,N_{20}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,E_{23,25}\\)...(63 more commands)' + '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\,{}_{27}[M_{31}]^{28}\\,E_{31,32}\\,N_{32}\\,[M_{29}]^{0}\\,E_{29,30}\\,N_{30}\\,{}_{26}[M_{28}^{-Y}]^{27}\\,E_{28,31}\\,N_{31}\\,{}_{17}[M_{21}]^{20}\\,E_{21,24}\\,N_{24}\\,{}_{25}[M_{27}]^{26}\\,E_{27,28}\\,N_{28}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,E_{0,27}\\,E_{0,29}\\,N_{29}\\,{}_{16}[M_{20}]^{17}\\,E_{20,21}\\,N_{21}\\,{}_{23}[M_{26}]^{25}\\,E_{26,27}\\,N_{27}\\,{}_{22}[M_{25}]^{23}\\,E_{25,26}\\,N_{26}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,E_{17,20}\\,N_{20}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,E_{23,25}\\)...(63 more commands)' >>> pattern.to_latex(left_to_right=True) - '\\(N_{3}\\,E_{2,3}\\,M_{2}^{0}\\,N_{4}\\,E_{3,4}\\,[M_{3}^{0}]^{2}\\,E_{4,1}\\,N_{5}\\,E_{4,5}\\,{}_{2}[M_{4}^{0}]^{3}\\,N_{6}\\,E_{5,6}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,N_{7}\\,E_{6,7}\\,{}_{4}[M_{6}^{0}]^{5}\\,N_{8}\\,E_{7,8}\\,{}_{5}[M_{7}^{0}]^{6}\\,E_{8,0}\\,N_{9}\\,E_{8,9}\\,{}_{6}[M_{8}^{0}]^{7}\\,N_{10}\\,E_{9,10}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,N_{11}\\,E_{10,11}\\,{}_{8}[M_{10}^{0}]^{9}\\,N_{12}\\,E_{11,12}\\,{}_{9}[M_{11}^{0}]^{10}\\,N_{18}\\,E_{1,18}\\,E_{1,12}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,N_{13}\\,E_{12,13}\\,{}_{10}[M_{12}^{0}]^{11}\\)...(63 more commands)' + '\\(N_{3}\\,E_{2,3}\\,M_{2}\\,N_{4}\\,E_{3,4}\\,[M_{3}]^{2}\\,E_{4,1}\\,N_{5}\\,E_{4,5}\\,{}_{2}[M_{4}]^{3}\\,N_{6}\\,E_{5,6}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,N_{7}\\,E_{6,7}\\,{}_{4}[M_{6}]^{5}\\,N_{8}\\,E_{7,8}\\,{}_{5}[M_{7}]^{6}\\,E_{8,0}\\,N_{9}\\,E_{8,9}\\,{}_{6}[M_{8}]^{7}\\,N_{10}\\,E_{9,10}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,N_{11}\\,E_{10,11}\\,{}_{8}[M_{10}]^{9}\\,N_{12}\\,E_{11,12}\\,{}_{9}[M_{11}]^{10}\\,N_{18}\\,E_{1,18}\\,E_{1,12}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,N_{13}\\,E_{12,13}\\,{}_{10}[M_{12}]^{11}\\)...(63 more commands)' >>> from graphix.command import CommandKind >>> pattern.to_latex(target={CommandKind.M}) - '\\({}_{27}[M_{31}^{0}]^{28}\\,[M_{29}^{0}]^{0}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,{}_{17}[M_{21}^{0}]^{20}\\,{}_{25}[M_{27}^{0}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}^{0}]^{17}\\,{}_{23}[M_{26}^{0}]^{25}\\,{}_{22}[M_{25}^{0}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}^{0}]^{15}\\,{}_{13}[M_{15}^{0}]^{14}\\,{}_{18}[M_{22}^{0}]^{19}\\,{}_{12}[M_{14}^{0}]^{13}\\,{}_{1}[M_{19}^{0}]^{18}\\,{}_{11}[M_{13}^{\\frac{\\pi}{4}}]^{12}\\,[M_{18}^{0}]^{1}\\,{}_{10}[M_{12}^{0}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}^{0}]^{10}\\,{}_{8}[M_{10}^{0}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,{}_{6}[M_{8}^{0}]^{7}\\,{}_{5}[M_{7}^{0}]^{6}\\,{}_{4}[M_{6}^{0}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}^{0}]^{3}\\,[M_{3}^{0}]^{2}\\,M_{2}^{0}\\)' + '\\({}_{27}[M_{31}]^{28}\\,[M_{29}]^{0}\\,{}_{26}[M_{28}^{-Y}]^{27}\\,{}_{17}[M_{21}]^{20}\\,{}_{25}[M_{27}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}]^{17}\\,{}_{23}[M_{26}]^{25}\\,{}_{22}[M_{25}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}]^{15}\\,{}_{13}[M_{15}]^{14}\\,{}_{18}[M_{22}]^{19}\\,{}_{12}[M_{14}]^{13}\\,{}_{1}[M_{19}]^{18}\\,{}_{11}[M_{13}^{\\frac{\\pi}{4}}]^{12}\\,[M_{18}]^{1}\\,{}_{10}[M_{12}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}]^{10}\\,{}_{8}[M_{10}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,{}_{6}[M_{8}]^{7}\\,{}_{5}[M_{7}]^{6}\\,{}_{4}[M_{6}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}]^{3}\\,[M_{3}]^{2}\\,M_{2}\\)' >>> pattern.to_latex(target={CommandKind.X, CommandKind.Z}) '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\)' """ @@ -484,12 +484,12 @@ def to_unicode( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_unicode() - 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰ ₂₇[M₃₁(0)]²⁸ E₃₁₋₃₂ N₃₂ [M₂₉(0)]⁰ E₂₉₋₃₀ N₃₀ ₂₆[M₂₈(3π/2)]²⁷ E₂₈₋₃₁ N₃₁ ₁₇[M₂₁(0)]²⁰ E₂₁₋₂₄ N₂₄ ₂₅[M₂₇(0)]²⁶ E₂₇₋₂₈ N₂₈ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] E₀₋₂₇ E₀₋₂₉ N₂₉ ₁₆[M₂₀(0)]¹⁷ E₂₀₋₂₁ N₂₁ ₂₃[M₂₆(0)]²⁵ E₂₆₋₂₇ N₂₇ ₂₂[M₂₅(0)]²³ E₂₅₋₂₆ N₂₆ ₁₅[M₁₇(7π/4)]¹⁶ E₁₇₋₂₀ N₂₀ ₁₉[M₂₃(π/4)]²² E₂₃₋₂₅...(63 more commands)' + 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰ ₂₇[M₃₁]²⁸ E₃₁₋₃₂ N₃₂ [M₂₉]⁰ E₂₉₋₃₀ N₃₀ ₂₆[M₂₈(-Y)]²⁷ E₂₈₋₃₁ N₃₁ ₁₇[M₂₁]²⁰ E₂₁₋₂₄ N₂₄ ₂₅[M₂₇]²⁶ E₂₇₋₂₈ N₂₈ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] E₀₋₂₇ E₀₋₂₉ N₂₉ ₁₆[M₂₀]¹⁷ E₂₀₋₂₁ N₂₁ ₂₃[M₂₆]²⁵ E₂₆₋₂₇ N₂₇ ₂₂[M₂₅]²³ E₂₅₋₂₆ N₂₆ ₁₅[M₁₇(7π/4)]¹⁶ E₁₇₋₂₀ N₂₀ ₁₉[M₂₃(π/4)]²² E₂₃₋₂₅...(63 more commands)' >>> pattern.to_unicode(left_to_right=True) - 'N₃ E₂₋₃ M₂(0) N₄ E₃₋₄ [M₃(0)]² E₄₋₁ N₅ E₄₋₅ ₂[M₄(0)]³ N₆ E₅₋₆ ₃[M₅(π/4)]⁴ N₇ E₆₋₇ ₄[M₆(0)]⁵ N₈ E₇₋₈ ₅[M₇(0)]⁶ E₈₋₀ N₉ E₈₋₉ ₆[M₈(0)]⁷ N₁₀ E₉₋₁₀ ₇[M₉(7π/4)]⁸ N₁₁ E₁₀₋₁₁ ₈[M₁₀(0)]⁹ N₁₂ E₁₁₋₁₂ ₉[M₁₁(0)]¹⁰ N₁₈ E₁₋₁₈ E₁₋₁₂ ₁₁₊₃[M₁(π/4)] N₁₃ E₁₂₋₁₃ ₁₀[M₁₂(0)]¹¹...(63 more commands)' + 'N₃ E₂₋₃ M₂ N₄ E₃₋₄ [M₃]² E₄₋₁ N₅ E₄₋₅ ₂[M₄]³ N₆ E₅₋₆ ₃[M₅(π/4)]⁴ N₇ E₆₋₇ ₄[M₆]⁵ N₈ E₇₋₈ ₅[M₇]⁶ E₈₋₀ N₉ E₈₋₉ ₆[M₈]⁷ N₁₀ E₉₋₁₀ ₇[M₉(7π/4)]⁸ N₁₁ E₁₀₋₁₁ ₈[M₁₀]⁹ N₁₂ E₁₁₋₁₂ ₉[M₁₁]¹⁰ N₁₈ E₁₋₁₈ E₁₋₁₂ ₁₁₊₃[M₁(π/4)] N₁₃ E₁₂₋₁₃ ₁₀[M₁₂]¹¹...(63 more commands)' >>> from graphix.command import CommandKind >>> pattern.to_unicode(target={CommandKind.M}) - '₂₇[M₃₁(0)]²⁸ [M₂₉(0)]⁰ ₂₆[M₂₈(3π/2)]²⁷ ₁₇[M₂₁(0)]²⁰ ₂₅[M₂₇(0)]²⁶ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] ₁₆[M₂₀(0)]¹⁷ ₂₃[M₂₆(0)]²⁵ ₂₂[M₂₅(0)]²³ ₁₅[M₁₇(7π/4)]¹⁶ ₁₉[M₂₃(π/4)]²² ₁₄[M₁₆(0)]¹⁵ ₁₃[M₁₅(0)]¹⁴ ₁₈[M₂₂(0)]¹⁹ ₁₂[M₁₄(0)]¹³ ₁[M₁₉(0)]¹⁸ ₁₁[M₁₃(π/4)]¹² [M₁₈(0)]¹ ₁₀[M₁₂(0)]¹¹ ₁₁₊₃[M₁(π/4)] ₉[M₁₁(0)]¹⁰ ₈[M₁₀(0)]⁹ ₇[M₉(7π/4)]⁸ ₆[M₈(0)]⁷ ₅[M₇(0)]⁶ ₄[M₆(0)]⁵ ₃[M₅(π/4)]⁴ ₂[M₄(0)]³ [M₃(0)]² M₂(0)' + '₂₇[M₃₁]²⁸ [M₂₉]⁰ ₂₆[M₂₈(-Y)]²⁷ ₁₇[M₂₁]²⁰ ₂₅[M₂₇]²⁶ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] ₁₆[M₂₀]¹⁷ ₂₃[M₂₆]²⁵ ₂₂[M₂₅]²³ ₁₅[M₁₇(7π/4)]¹⁶ ₁₉[M₂₃(π/4)]²² ₁₄[M₁₆]¹⁵ ₁₃[M₁₅]¹⁴ ₁₈[M₂₂]¹⁹ ₁₂[M₁₄]¹³ ₁[M₁₉]¹⁸ ₁₁[M₁₃(π/4)]¹² [M₁₈]¹ ₁₀[M₁₂]¹¹ ₁₁₊₃[M₁(π/4)] ₉[M₁₁]¹⁰ ₈[M₁₀]⁹ ₇[M₉(7π/4)]⁸ ₆[M₈]⁷ ₅[M₇]⁶ ₄[M₆]⁵ ₃[M₅(π/4)]⁴ ₂[M₄]³ [M₃]² M₂' >>> pattern.to_unicode(target={CommandKind.X, CommandKind.Z}) 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰' """ From 5a826f8c80106c7d4525df0e74dfe4776d0fc76a Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 24 Apr 2026 14:37:43 +0200 Subject: [PATCH 18/63] reverting Pauli inference --- graphix/transpiler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 751fbde24..7dd918dd5 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -479,7 +479,6 @@ def transpile(self) -> TranspileResult: f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) pattern = StandardizedPattern.from_pattern(f.to_corrections().to_pattern()).to_space_optimal_pattern() pattern.extend(classical_outputs.values()) - pattern = pattern.infer_pauli_measurements() return TranspileResult(pattern, tuple(classical_outputs.keys())) def simulate_statevector( From 3871d8564facc7a918abeb5d34ac1ac2764b0737 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 24 Apr 2026 15:26:53 +0200 Subject: [PATCH 19/63] fixing Veriphix reverse dep --- noxfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 3418a52db..510b7bf68 100644 --- a/noxfile.py +++ b/noxfile.py @@ -114,7 +114,8 @@ class ReverseDependency: ), ReverseDependency("https://github.com/TeamGraphix/graphix-qasm-parser", branch="fix_angles"), ReverseDependency( - "https://github.com/qat-inria/veriphix", + "https://github.com/emlynsg/veriphix", + branch="jcz-test-fix", doctest_modules=False, install_target=".[dev]", ), From 63082607ffef890cbe1f9832c7ddf39f6c997575 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 24 Apr 2026 15:42:41 +0200 Subject: [PATCH 20/63] fixing vqe example --- examples/mbqc_vqe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index 82b7f846b..de9dd8b0a 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -33,6 +33,7 @@ from graphix import Circuit from graphix.parameter import Placeholder from graphix.simulator import PatternSimulator +from graphix.transpiler import transpile_swaps if TYPE_CHECKING: from collections.abc import Iterable @@ -89,6 +90,7 @@ def __init__(self, n_qubits: int, hamiltonian: npt.NDArray[np.float64]): # Function to build the MBQC pattern def build_mbqc_pattern(self, params: Iterable[ParameterizedAngle]) -> Pattern: circuit = build_vqe_circuit(self.n_qubits, params) + circuit = transpile_swaps(circuit).circuit pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() From 287859ed85610ef0180804e5f4abc01002bcc9bf Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 24 Apr 2026 16:09:23 +0200 Subject: [PATCH 21/63] fixing docstrings --- graphix/pattern.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 226c71c28..6c13f9c18 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -411,14 +411,14 @@ def to_ascii( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_ascii() - 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31)]{28} E(31,32) N(32) [M(29)]{0} E(29,30) N(30) {26}[M(28,-Y)]{27} E(28,31) N(31) {17}[M(21)]{20} E(21,24) N(24) {25}[M(27)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20)]{17} E(20,21) N(21) {23}[M(26)]{25} E(26,27) N(27) {22}[M(25)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25)...(63 more commands)' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31,0)]{28} E(31,32) N(32) [M(29,0)]{0} E(29,30) N(30) {26}[M(28,3pi/2)]{27} E(28,31) N(31) {17}[M(21,0)]{20} E(21,24) N(24) {25}[M(27,0)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20,0)]{17} E(20,21) N(21) {23}[M(26,0)]{25} E(26,27) N(27) {22}[M(25,0)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25)...(63 more commands)' >>> pattern.to_ascii(left_to_right=True) - 'N(3) E(2,3) M(2) N(4) E(3,4) [M(3)]{2} E(4,1) N(5) E(4,5) {2}[M(4)]{3} N(6) E(5,6) {3}[M(5,pi/4)]{4} N(7) E(6,7) {4}[M(6)]{5} N(8) E(7,8) {5}[M(7)]{6} E(8,0) N(9) E(8,9) {6}[M(8)]{7} N(10) E(9,10) {7}[M(9,7pi/4)]{8} N(11) E(10,11) {8}[M(10)]{9} N(12) E(11,12) {9}[M(11)]{10} N(18) E(1,18) E(1,12) {11,3}[M(1,pi/4)] N(13) E(12,13) {10}[M(12)]{11}...(63 more commands)' + 'N(3) E(2,3) M(2,0) N(4) E(3,4) [M(3,0)]{2} E(4,1) N(5) E(4,5) {2}[M(4,0)]{3} N(6) E(5,6) {3}[M(5,pi/4)]{4} N(7) E(6,7) {4}[M(6,0)]{5} N(8) E(7,8) {5}[M(7,0)]{6} E(8,0) N(9) E(8,9) {6}[M(8,0)]{7} N(10) E(9,10) {7}[M(9,7pi/4)]{8} N(11) E(10,11) {8}[M(10,0)]{9} N(12) E(11,12) {9}[M(11,0)]{10} N(18) E(1,18) E(1,12) {11,3}[M(1,pi/4)] N(13) E(12,13) {10}[M(12,0)]{11}...(63 more commands)' >>> pattern.to_ascii(limit=None) - 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31)]{28} E(31,32) N(32) [M(29)]{0} E(29,30) N(30) {26}[M(28,-Y)]{27} E(28,31) N(31) {17}[M(21)]{20} E(21,24) N(24) {25}[M(27)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20)]{17} E(20,21) N(21) {23}[M(26)]{25} E(26,27) N(27) {22}[M(25)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25) N(25) {14}[M(16)]{15} E(16,0) E(16,17) N(17) {13}[M(15)]{14} E(15,16) N(16) {18}[M(22)]{19} E(22,23) N(23) E(22,0) {12}[M(14)]{13} E(14,15) N(15) {1}[M(19)]{18} E(19,22) N(22) {11}[M(13,pi/4)]{12} E(13,14) N(14) [M(18)]{1} E(18,19) N(19) {10}[M(12)]{11} E(12,13) N(13) {11,3}[M(1,pi/4)] E(1,12) E(1,18) N(18) {9}[M(11)]{10} E(11,12) N(12) {8}[M(10)]{9} E(10,11) N(11) {7}[M(9,7pi/4)]{8} E(9,10) N(10) {6}[M(8)]{7} E(8,9) N(9) E(8,0) {5}[M(7)]{6} E(7,8) N(8) {4}[M(6)]{5} E(6,7) N(7) {3}[M(5,pi/4)]{4} E(5,6) N(6) {2}[M(4)]{3} E(4,5) N(5) E(4,1) [M(3)]{2} E(3,4) N(4) M(2) E(2,3) N(3)' + 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0}) {27}[M(31,0)]{28} E(31,32) N(32) [M(29,0)]{0} E(29,30) N(30) {26}[M(28,3pi/2)]{27} E(28,31) N(31) {17}[M(21,0)]{20} E(21,24) N(24) {25}[M(27,0)]{26} E(27,28) N(28) {26,19,15,7}[M(0,7pi/4)] E(0,27) E(0,29) N(29) {16}[M(20,0)]{17} E(20,21) N(21) {23}[M(26,0)]{25} E(26,27) N(27) {22}[M(25,0)]{23} E(25,26) N(26) {15}[M(17,7pi/4)]{16} E(17,20) N(20) {19}[M(23,pi/4)]{22} E(23,25) N(25) {14}[M(16,0)]{15} E(16,0) E(16,17) N(17) {13}[M(15,0)]{14} E(15,16) N(16) {18}[M(22,0)]{19} E(22,23) N(23) E(22,0) {12}[M(14,0)]{13} E(14,15) N(15) {1}[M(19,0)]{18} E(19,22) N(22) {11}[M(13,pi/4)]{12} E(13,14) N(14) [M(18,0)]{1} E(18,19) N(19) {10}[M(12,0)]{11} E(12,13) N(13) {11,3}[M(1,pi/4)] E(1,12) E(1,18) N(18) {9}[M(11,0)]{10} E(11,12) N(12) {8}[M(10,0)]{9} E(10,11) N(11) {7}[M(9,7pi/4)]{8} E(9,10) N(10) {6}[M(8,0)]{7} E(8,9) N(9) E(8,0) {5}[M(7,0)]{6} E(7,8) N(8) {4}[M(6,0)]{5} E(6,7) N(7) {3}[M(5,pi/4)]{4} E(5,6) N(6) {2}[M(4,0)]{3} E(4,5) N(5) E(4,1) [M(3,0)]{2} E(3,4) N(4) M(2,0) E(2,3) N(3)' >>> from graphix.command import CommandKind >>> pattern.to_ascii(target={CommandKind.M}) - '{27}[M(31)]{28} [M(29)]{0} {26}[M(28,-Y)]{27} {17}[M(21)]{20} {25}[M(27)]{26} {26,19,15,7}[M(0,7pi/4)] {16}[M(20)]{17} {23}[M(26)]{25} {22}[M(25)]{23} {15}[M(17,7pi/4)]{16} {19}[M(23,pi/4)]{22} {14}[M(16)]{15} {13}[M(15)]{14} {18}[M(22)]{19} {12}[M(14)]{13} {1}[M(19)]{18} {11}[M(13,pi/4)]{12} [M(18)]{1} {10}[M(12)]{11} {11,3}[M(1,pi/4)] {9}[M(11)]{10} {8}[M(10)]{9} {7}[M(9,7pi/4)]{8} {6}[M(8)]{7} {5}[M(7)]{6} {4}[M(6)]{5} {3}[M(5,pi/4)]{4} {2}[M(4)]{3} [M(3)]{2} M(2)' + '{27}[M(31,0)]{28} [M(29,0)]{0} {26}[M(28,3pi/2)]{27} {17}[M(21,0)]{20} {25}[M(27,0)]{26} {26,19,15,7}[M(0,7pi/4)] {16}[M(20,0)]{17} {23}[M(26,0)]{25} {22}[M(25,0)]{23} {15}[M(17,7pi/4)]{16} {19}[M(23,pi/4)]{22} {14}[M(16,0)]{15} {13}[M(15,0)]{14} {18}[M(22,0)]{19} {12}[M(14,0)]{13} {1}[M(19,0)]{18} {11}[M(13,pi/4)]{12} [M(18,0)]{1} {10}[M(12,0)]{11} {11,3}[M(1,pi/4)] {9}[M(11,0)]{10} {8}[M(10,0)]{9} {7}[M(9,7pi/4)]{8} {6}[M(8,0)]{7} {5}[M(7,0)]{6} {4}[M(6,0)]{5} {3}[M(5,pi/4)]{4} {2}[M(4,0)]{3} [M(3,0)]{2} M(2,0)' >>> pattern.to_ascii(target={CommandKind.X, CommandKind.Z}) 'X(24,{21}) Z(24,{20}) X(32,{31}) Z(32,{28}) X(30,{29}) Z(30,{0})' """ @@ -449,12 +449,12 @@ def to_latex( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_latex() - '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\,{}_{27}[M_{31}]^{28}\\,E_{31,32}\\,N_{32}\\,[M_{29}]^{0}\\,E_{29,30}\\,N_{30}\\,{}_{26}[M_{28}^{-Y}]^{27}\\,E_{28,31}\\,N_{31}\\,{}_{17}[M_{21}]^{20}\\,E_{21,24}\\,N_{24}\\,{}_{25}[M_{27}]^{26}\\,E_{27,28}\\,N_{28}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,E_{0,27}\\,E_{0,29}\\,N_{29}\\,{}_{16}[M_{20}]^{17}\\,E_{20,21}\\,N_{21}\\,{}_{23}[M_{26}]^{25}\\,E_{26,27}\\,N_{27}\\,{}_{22}[M_{25}]^{23}\\,E_{25,26}\\,N_{26}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,E_{17,20}\\,N_{20}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,E_{23,25}\\)...(63 more commands)' + '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\,{}_{27}[M_{31}^{0}]^{28}\\,E_{31,32}\\,N_{32}\\,[M_{29}^{0}]^{0}\\,E_{29,30}\\,N_{30}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,E_{28,31}\\,N_{31}\\,{}_{17}[M_{21}^{0}]^{20}\\,E_{21,24}\\,N_{24}\\,{}_{25}[M_{27}^{0}]^{26}\\,E_{27,28}\\,N_{28}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,E_{0,27}\\,E_{0,29}\\,N_{29}\\,{}_{16}[M_{20}^{0}]^{17}\\,E_{20,21}\\,N_{21}\\,{}_{23}[M_{26}^{0}]^{25}\\,E_{26,27}\\,N_{27}\\,{}_{22}[M_{25}^{0}]^{23}\\,E_{25,26}\\,N_{26}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,E_{17,20}\\,N_{20}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,E_{23,25}\\)...(63 more commands)' >>> pattern.to_latex(left_to_right=True) - '\\(N_{3}\\,E_{2,3}\\,M_{2}\\,N_{4}\\,E_{3,4}\\,[M_{3}]^{2}\\,E_{4,1}\\,N_{5}\\,E_{4,5}\\,{}_{2}[M_{4}]^{3}\\,N_{6}\\,E_{5,6}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,N_{7}\\,E_{6,7}\\,{}_{4}[M_{6}]^{5}\\,N_{8}\\,E_{7,8}\\,{}_{5}[M_{7}]^{6}\\,E_{8,0}\\,N_{9}\\,E_{8,9}\\,{}_{6}[M_{8}]^{7}\\,N_{10}\\,E_{9,10}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,N_{11}\\,E_{10,11}\\,{}_{8}[M_{10}]^{9}\\,N_{12}\\,E_{11,12}\\,{}_{9}[M_{11}]^{10}\\,N_{18}\\,E_{1,18}\\,E_{1,12}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,N_{13}\\,E_{12,13}\\,{}_{10}[M_{12}]^{11}\\)...(63 more commands)' + '\\(N_{3}\\,E_{2,3}\\,M_{2}^{0}\\,N_{4}\\,E_{3,4}\\,[M_{3}^{0}]^{2}\\,E_{4,1}\\,N_{5}\\,E_{4,5}\\,{}_{2}[M_{4}^{0}]^{3}\\,N_{6}\\,E_{5,6}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,N_{7}\\,E_{6,7}\\,{}_{4}[M_{6}^{0}]^{5}\\,N_{8}\\,E_{7,8}\\,{}_{5}[M_{7}^{0}]^{6}\\,E_{8,0}\\,N_{9}\\,E_{8,9}\\,{}_{6}[M_{8}^{0}]^{7}\\,N_{10}\\,E_{9,10}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,N_{11}\\,E_{10,11}\\,{}_{8}[M_{10}^{0}]^{9}\\,N_{12}\\,E_{11,12}\\,{}_{9}[M_{11}^{0}]^{10}\\,N_{18}\\,E_{1,18}\\,E_{1,12}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,N_{13}\\,E_{12,13}\\,{}_{10}[M_{12}^{0}]^{11}\\)...(63 more commands)' >>> from graphix.command import CommandKind >>> pattern.to_latex(target={CommandKind.M}) - '\\({}_{27}[M_{31}]^{28}\\,[M_{29}]^{0}\\,{}_{26}[M_{28}^{-Y}]^{27}\\,{}_{17}[M_{21}]^{20}\\,{}_{25}[M_{27}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}]^{17}\\,{}_{23}[M_{26}]^{25}\\,{}_{22}[M_{25}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}]^{15}\\,{}_{13}[M_{15}]^{14}\\,{}_{18}[M_{22}]^{19}\\,{}_{12}[M_{14}]^{13}\\,{}_{1}[M_{19}]^{18}\\,{}_{11}[M_{13}^{\\frac{\\pi}{4}}]^{12}\\,[M_{18}]^{1}\\,{}_{10}[M_{12}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}]^{10}\\,{}_{8}[M_{10}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,{}_{6}[M_{8}]^{7}\\,{}_{5}[M_{7}]^{6}\\,{}_{4}[M_{6}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}]^{3}\\,[M_{3}]^{2}\\,M_{2}\\)' + '\\({}_{27}[M_{31}^{0}]^{28}\\,[M_{29}^{0}]^{0}\\,{}_{26}[M_{28}^{\\frac{3\\pi}{2}}]^{27}\\,{}_{17}[M_{21}^{0}]^{20}\\,{}_{25}[M_{27}^{0}]^{26}\\,{}_{26,19,15,7}[M_{0}^{\\frac{7\\pi}{4}}]\\,{}_{16}[M_{20}^{0}]^{17}\\,{}_{23}[M_{26}^{0}]^{25}\\,{}_{22}[M_{25}^{0}]^{23}\\,{}_{15}[M_{17}^{\\frac{7\\pi}{4}}]^{16}\\,{}_{19}[M_{23}^{\\frac{\\pi}{4}}]^{22}\\,{}_{14}[M_{16}^{0}]^{15}\\,{}_{13}[M_{15}^{0}]^{14}\\,{}_{18}[M_{22}^{0}]^{19}\\,{}_{12}[M_{14}^{0}]^{13}\\,{}_{1}[M_{19}^{0}]^{18}\\,{}_{11}[M_{13}^{\\frac{\\pi}{4}}]^{12}\\,[M_{18}^{0}]^{1}\\,{}_{10}[M_{12}^{0}]^{11}\\,{}_{11,3}[M_{1}^{\\frac{\\pi}{4}}]\\,{}_{9}[M_{11}^{0}]^{10}\\,{}_{8}[M_{10}^{0}]^{9}\\,{}_{7}[M_{9}^{\\frac{7\\pi}{4}}]^{8}\\,{}_{6}[M_{8}^{0}]^{7}\\,{}_{5}[M_{7}^{0}]^{6}\\,{}_{4}[M_{6}^{0}]^{5}\\,{}_{3}[M_{5}^{\\frac{\\pi}{4}}]^{4}\\,{}_{2}[M_{4}^{0}]^{3}\\,[M_{3}^{0}]^{2}\\,M_{2}^{0}\\)' >>> pattern.to_latex(target={CommandKind.X, CommandKind.Z}) '\\(X_{24}^{21}\\,Z_{24}^{20}\\,X_{32}^{31}\\,Z_{32}^{28}\\,X_{30}^{29}\\,Z_{30}^{0}\\)' """ @@ -485,12 +485,12 @@ def to_unicode( >>> circuit.ccx(0, 1, 2) >>> pattern = circuit.transpile().pattern >>> pattern.to_unicode() - 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰ ₂₇[M₃₁]²⁸ E₃₁₋₃₂ N₃₂ [M₂₉]⁰ E₂₉₋₃₀ N₃₀ ₂₆[M₂₈(-Y)]²⁷ E₂₈₋₃₁ N₃₁ ₁₇[M₂₁]²⁰ E₂₁₋₂₄ N₂₄ ₂₅[M₂₇]²⁶ E₂₇₋₂₈ N₂₈ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] E₀₋₂₇ E₀₋₂₉ N₂₉ ₁₆[M₂₀]¹⁷ E₂₀₋₂₁ N₂₁ ₂₃[M₂₆]²⁵ E₂₆₋₂₇ N₂₇ ₂₂[M₂₅]²³ E₂₅₋₂₆ N₂₆ ₁₅[M₁₇(7π/4)]¹⁶ E₁₇₋₂₀ N₂₀ ₁₉[M₂₃(π/4)]²² E₂₃₋₂₅...(63 more commands)' + 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰ ₂₇[M₃₁(0)]²⁸ E₃₁₋₃₂ N₃₂ [M₂₉(0)]⁰ E₂₉₋₃₀ N₃₀ ₂₆[M₂₈(3π/2)]²⁷ E₂₈₋₃₁ N₃₁ ₁₇[M₂₁(0)]²⁰ E₂₁₋₂₄ N₂₄ ₂₅[M₂₇(0)]²⁶ E₂₇₋₂₈ N₂₈ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] E₀₋₂₇ E₀₋₂₉ N₂₉ ₁₆[M₂₀(0)]¹⁷ E₂₀₋₂₁ N₂₁ ₂₃[M₂₆(0)]²⁵ E₂₆₋₂₇ N₂₇ ₂₂[M₂₅(0)]²³ E₂₅₋₂₆ N₂₆ ₁₅[M₁₇(7π/4)]¹⁶ E₁₇₋₂₀ N₂₀ ₁₉[M₂₃(π/4)]²² E₂₃₋₂₅...(63 more commands)' >>> pattern.to_unicode(left_to_right=True) - 'N₃ E₂₋₃ M₂ N₄ E₃₋₄ [M₃]² E₄₋₁ N₅ E₄₋₅ ₂[M₄]³ N₆ E₅₋₆ ₃[M₅(π/4)]⁴ N₇ E₆₋₇ ₄[M₆]⁵ N₈ E₇₋₈ ₅[M₇]⁶ E₈₋₀ N₉ E₈₋₉ ₆[M₈]⁷ N₁₀ E₉₋₁₀ ₇[M₉(7π/4)]⁸ N₁₁ E₁₀₋₁₁ ₈[M₁₀]⁹ N₁₂ E₁₁₋₁₂ ₉[M₁₁]¹⁰ N₁₈ E₁₋₁₈ E₁₋₁₂ ₁₁₊₃[M₁(π/4)] N₁₃ E₁₂₋₁₃ ₁₀[M₁₂]¹¹...(63 more commands)' + 'N₃ E₂₋₃ M₂(0) N₄ E₃₋₄ [M₃(0)]² E₄₋₁ N₅ E₄₋₅ ₂[M₄(0)]³ N₆ E₅₋₆ ₃[M₅(π/4)]⁴ N₇ E₆₋₇ ₄[M₆(0)]⁵ N₈ E₇₋₈ ₅[M₇(0)]⁶ E₈₋₀ N₉ E₈₋₉ ₆[M₈(0)]⁷ N₁₀ E₉₋₁₀ ₇[M₉(7π/4)]⁸ N₁₁ E₁₀₋₁₁ ₈[M₁₀(0)]⁹ N₁₂ E₁₁₋₁₂ ₉[M₁₁(0)]¹⁰ N₁₈ E₁₋₁₈ E₁₋₁₂ ₁₁₊₃[M₁(π/4)] N₁₃ E₁₂₋₁₃ ₁₀[M₁₂(0)]¹¹...(63 more commands)' >>> from graphix.command import CommandKind >>> pattern.to_unicode(target={CommandKind.M}) - '₂₇[M₃₁]²⁸ [M₂₉]⁰ ₂₆[M₂₈(-Y)]²⁷ ₁₇[M₂₁]²⁰ ₂₅[M₂₇]²⁶ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] ₁₆[M₂₀]¹⁷ ₂₃[M₂₆]²⁵ ₂₂[M₂₅]²³ ₁₅[M₁₇(7π/4)]¹⁶ ₁₉[M₂₃(π/4)]²² ₁₄[M₁₆]¹⁵ ₁₃[M₁₅]¹⁴ ₁₈[M₂₂]¹⁹ ₁₂[M₁₄]¹³ ₁[M₁₉]¹⁸ ₁₁[M₁₃(π/4)]¹² [M₁₈]¹ ₁₀[M₁₂]¹¹ ₁₁₊₃[M₁(π/4)] ₉[M₁₁]¹⁰ ₈[M₁₀]⁹ ₇[M₉(7π/4)]⁸ ₆[M₈]⁷ ₅[M₇]⁶ ₄[M₆]⁵ ₃[M₅(π/4)]⁴ ₂[M₄]³ [M₃]² M₂' + '₂₇[M₃₁(0)]²⁸ [M₂₉(0)]⁰ ₂₆[M₂₈(3π/2)]²⁷ ₁₇[M₂₁(0)]²⁰ ₂₅[M₂₇(0)]²⁶ ₂₆₊₁₉₊₁₅₊₇[M₀(7π/4)] ₁₆[M₂₀(0)]¹⁷ ₂₃[M₂₆(0)]²⁵ ₂₂[M₂₅(0)]²³ ₁₅[M₁₇(7π/4)]¹⁶ ₁₉[M₂₃(π/4)]²² ₁₄[M₁₆(0)]¹⁵ ₁₃[M₁₅(0)]¹⁴ ₁₈[M₂₂(0)]¹⁹ ₁₂[M₁₄(0)]¹³ ₁[M₁₉(0)]¹⁸ ₁₁[M₁₃(π/4)]¹² [M₁₈(0)]¹ ₁₀[M₁₂(0)]¹¹ ₁₁₊₃[M₁(π/4)] ₉[M₁₁(0)]¹⁰ ₈[M₁₀(0)]⁹ ₇[M₉(7π/4)]⁸ ₆[M₈(0)]⁷ ₅[M₇(0)]⁶ ₄[M₆(0)]⁵ ₃[M₅(π/4)]⁴ ₂[M₄(0)]³ [M₃(0)]² M₂(0)' >>> pattern.to_unicode(target={CommandKind.X, CommandKind.Z}) 'X₂₄²¹ Z₂₄²⁰ X₃₂³¹ Z₃₂²⁸ X₃₀²⁹ Z₃₀⁰' """ From 49e3d7e514e482490c9bbacfa4fdd1c908f54817 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 24 Apr 2026 18:23:11 +0200 Subject: [PATCH 22/63] fixing reverse deps --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 510b7bf68..5294e36f5 100644 --- a/noxfile.py +++ b/noxfile.py @@ -108,7 +108,7 @@ class ReverseDependency: @nox.parametrize( "package", [ - ReverseDependency("https://github.com/thierry-martinez/graphix-stim-backend"), + ReverseDependency("https://github.com/emlynsg/graphix-stim-backend", branch="jcz"), ReverseDependency( "https://github.com/TeamGraphix/graphix-symbolic", ), From 005afb3e831da03288729d912d3206bb66378028 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 11:57:12 +0200 Subject: [PATCH 23/63] adding object inference to backend kron --- graphix/sim/base_backend.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index ef1977b3c..53495da08 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -105,6 +105,11 @@ def kron(a: Matrix, b: Matrix) -> Matrix: b_o = b.astype(np.object_, copy=False) return np.kron(a_o, b_o) + if np.object_ in (a.dtype, b.dtype): + a_o = a.astype(np.object_, copy=False) + b_o = b.astype(np.object_, copy=False) + return np.kron(a.astype(np.object_, copy=False), b.astype(np.object_, copy=False)) + raise TypeError("Operands should have the same type.") From 921f5d6e33af5ba9ee3ee1c6d64eaa0a071b1323 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 13:03:46 +0200 Subject: [PATCH 24/63] fixing reverse deps, adding type inference to add nodes --- graphix/sim/base_backend.py | 5 ----- graphix/sim/density_matrix.py | 2 ++ graphix/sim/statevec.py | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index 53495da08..ef1977b3c 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -105,11 +105,6 @@ def kron(a: Matrix, b: Matrix) -> Matrix: b_o = b.astype(np.object_, copy=False) return np.kron(a_o, b_o) - if np.object_ in (a.dtype, b.dtype): - a_o = a.astype(np.object_, copy=False) - b_o = b.astype(np.object_, copy=False) - return np.kron(a.astype(np.object_, copy=False), b.astype(np.object_, copy=False)) - raise TypeError("Operands should have the same type.") diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index cb5570e2a..2b2263cd4 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -143,6 +143,8 @@ def add_nodes(self, nqubit: int, data: Data) -> None: Previously existing nodes remain unchanged. """ dm_to_add = DensityMatrix(nqubit=nqubit, data=data) + if self.rho.dtype == np.object_ and dm_to_add.rho.dtype != np.object_: + dm_to_add.rho = dm_to_add.rho.astype(np.object_, copy=False) self.tensor(dm_to_add) @override diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 1f7dd23e6..f5c5aa483 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -171,6 +171,8 @@ def add_nodes(self, nqubit: int, data: Data) -> None: Previously existing nodes remain unchanged. """ sv_to_add = Statevec(nqubit=nqubit, data=data) + if self.psi.dtype == np.object_ and sv_to_add.psi.dtype != np.object_: + sv_to_add.psi = sv_to_add.psi.astype(np.object_, copy=False) self.tensor(sv_to_add) @override From 4f3244de47811390fe806c1f485715c08bf8e24e Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 13:10:01 +0200 Subject: [PATCH 25/63] editing docstring --- graphix/sim/density_matrix.py | 1 + graphix/sim/statevec.py | 1 + 2 files changed, 2 insertions(+) diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index 2b2263cd4..d38bed2e2 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -137,6 +137,7 @@ def add_nodes(self, nqubit: int, data: Data) -> None: - A multi-qubit state vector of dimension :math:`2^n` initializes the new nodes jointly. - A density matrix must have shape :math:`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 ----- diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index f5c5aa483..6b9d19cfd 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -165,6 +165,7 @@ def add_nodes(self, nqubit: int, data: Data) -> None: - A single-qubit state vector will be broadcast to all nodes. - A multi-qubit state vector of dimension :math:`2^n`, where :math:`n = \mathrm{len}(nodes)`, initializes the new nodes jointly. + - The type of nodes to be added is inferred from the type of the existing ``Statevec``. Notes ----- From c5ffc6f57e9933db821f300b40e6bc69e5a211fa Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 16:23:41 +0200 Subject: [PATCH 26/63] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcfdc8808..ef29d087a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - #484: J & CZ transpilation. - - Replaced `Circuit.transpile()` with a new approach based on J & CZ decomposition. + - Replaced `Circuit.transpile()` with a new approach based decomposing circuits into J & CZ gates. - Added `instruction.J` class. - #479: Added new methods `OpenGraph.draw`, `PauliFlow.draw` and `XZCorrections.draw`. From a244f0fe2e683ea2ebbe89682a2657b3562653ae Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 16:29:59 +0200 Subject: [PATCH 27/63] fixing docs build --- docs/source/conf.py | 2 +- examples/mbqc_vqe.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3f25f64cd..5eda68360 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -90,7 +90,7 @@ def setup(app: Sphinx) -> None: "gallery_dirs": ["gallery"], "filename_pattern": "/", "thumbnail_size": (800, 550), - "parallel": True, + "parallel": False, } suppress_warnings = ["config.cache"] diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index de9dd8b0a..b7997a19d 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -160,7 +160,7 @@ def cost_function(params: Iterable[float]) -> float: # %% # Perform the optimization using COBYLA def compute() -> OptimizeResult: - return minimize(cost_function, initial_params, method="COBYLA", options={"maxiter": 100}) + return minimize(cost_function, initial_params, method="COBYLA", options={"maxiter": 40}) result = compute() From 2b3bd8b6d381e5aa24777f636129231b79d51876 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 16:31:02 +0200 Subject: [PATCH 28/63] fixing docs build --- examples/mbqc_vqe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index b7997a19d..41875509e 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -177,9 +177,9 @@ def compute() -> OptimizeResult: # Compare performances between using parameterized circuits (with placeholders) or not mbqc_vqe = MBQCVQEWithPlaceholders(n_qubits, hamiltonian) -time_with_placeholders = timeit(compute, number=2) +time_with_placeholders = timeit(compute, number=1) print(f"Time with placeholders: {time_with_placeholders}") mbqc_vqe = MBQCVQE(n_qubits, hamiltonian) -time_without_placeholders = timeit(compute, number=2) +time_without_placeholders = timeit(compute, number=1) print(f"Time without placeholders: {time_without_placeholders}") From 7d3aa5ffc411bcfa2d91692de237420b3cf1b903 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 27 Apr 2026 16:40:55 +0200 Subject: [PATCH 29/63] fixing docs build --- examples/ghz_with_tn.py | 5 +++-- examples/mbqc_vqe.py | 2 +- examples/qaoa.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/ghz_with_tn.py b/examples/ghz_with_tn.py index 3e73e8364..d02949d81 100644 --- a/examples/ghz_with_tn.py +++ b/examples/ghz_with_tn.py @@ -15,8 +15,9 @@ import networkx as nx from graphix import Circuit +from graphix.transpiler import transpile_swaps -n = 100 +n = 50 print(f"{n}-qubit GHZ state generation") circuit = Circuit(n) @@ -32,7 +33,7 @@ # %% # Transpile into pattern -pattern = circuit.transpile().pattern +pattern = transpile_swaps(circuit).circuit.transpile().pattern pattern.standardize() graph = pattern.extract_graph() diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index 41875509e..eece69ed6 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -160,7 +160,7 @@ def cost_function(params: Iterable[float]) -> float: # %% # Perform the optimization using COBYLA def compute() -> OptimizeResult: - return minimize(cost_function, initial_params, method="COBYLA", options={"maxiter": 40}) + return minimize(cost_function, initial_params, method="COBYLA", options={"maxiter": 20}) result = compute() diff --git a/examples/qaoa.py b/examples/qaoa.py index 0e57f3b66..f81036d84 100644 --- a/examples/qaoa.py +++ b/examples/qaoa.py @@ -41,6 +41,7 @@ # perform Pauli measurements and plot the new (minimal) graph to perform the same quantum computation pattern.remove_input_nodes() +pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.draw(flow_from_pattern=False) From 17b9d5c18a7ed3f08f350d1bb5a396d253a89ac1 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 28 Apr 2026 10:24:13 +0200 Subject: [PATCH 30/63] fixing docs --- examples/deutsch_jozsa.py | 1 + examples/qft_with_tn.py | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/deutsch_jozsa.py b/examples/deutsch_jozsa.py index e5f82d056..7603d54cc 100644 --- a/examples/deutsch_jozsa.py +++ b/examples/deutsch_jozsa.py @@ -74,6 +74,7 @@ # Now we preprocess all Pauli measurements, which requires that we move inputs to N commands pattern.remove_input_nodes() +pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() print( pattern.to_ascii( diff --git a/examples/qft_with_tn.py b/examples/qft_with_tn.py index eb92a6b3e..06ac29913 100644 --- a/examples/qft_with_tn.py +++ b/examples/qft_with_tn.py @@ -67,6 +67,7 @@ def qft(circuit: Circuit, n: int) -> None: # Using efficient graph state simulator `graphix.graphsim`, we can classically preprocess Pauli measurements. # We are currently improving the speed of this process by using rust-based graph manipulation backend. pattern.remove_input_nodes() +pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() From fe0e22cdadaca463b3d88ef1fe20c5b7f57a8f9f Mon Sep 17 00:00:00 2001 From: Emlyn <42484330+emlynsg@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:27:00 +0200 Subject: [PATCH 31/63] Update docs/source/generator.rst Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/source/generator.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/source/generator.rst b/docs/source/generator.rst index 7fb8488bb..2a5fb5569 100644 --- a/docs/source/generator.rst +++ b/docs/source/generator.rst @@ -17,7 +17,6 @@ Pattern Generation .. automethod:: simulate_statevector .. automethod:: cz - .. automethod:: cnot .. automethod:: h From f378c8596816afeffd476bb1ea7cda6faa7aa979 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 28 Apr 2026 18:20:59 +0200 Subject: [PATCH 32/63] implementing fixes --- docs/source/conf.py | 2 +- graphix/qasm3_exporter.py | 28 +++++++++++- graphix/sim/density_matrix.py | 4 +- graphix/sim/statevec.py | 4 +- graphix/transpiler.py | 44 ++++++++++++++----- tests/test_qasm3_exporter.py | 8 ++++ .../test_qasm3_exporter_to_graphix_parser.py | 10 ++++- 7 files changed, 80 insertions(+), 20 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5eda68360..3f25f64cd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -90,7 +90,7 @@ def setup(app: Sphinx) -> None: "gallery_dirs": ["gallery"], "filename_pattern": "/", "thumbnail_size": (800, 550), - "parallel": False, + "parallel": True, } suppress_warnings = ["config.cache"] diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 0a5e9e00e..cf4d84151 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -3,10 +3,11 @@ from __future__ import annotations from typing import TYPE_CHECKING - +import warnings # assert_never added in Python 3.11 from typing_extensions import assert_never +from graphix import instruction from graphix._version import version from graphix.command import CommandKind from graphix.fundamentals import Axis, ParameterizedAngle, Plane @@ -40,6 +41,7 @@ def circuit_to_qasm3_lines(circuit: Circuit) -> Iterator[str]: Iterator[str] The OpenQASM 3.0 lines that represent the circuit. """ + circuit = _decompose_j_gates(circuit) yield "OPENQASM 3;" yield 'include "stdgates.inc";' yield f"qubit[{circuit.width}] q;" @@ -101,7 +103,9 @@ def instruction_to_qasm3(instruction: Instruction) -> str: instruction.kind.name.lower(), args=[angle], operands=[qasm3_qubit(instruction.target)] ) case InstructionKind.J: - raise ValueError("OpenQASM3 does not support J(alpha) instructions.") + raise ValueError( + "J gate should have been removed by `_decompose_j_gates`." + ) case InstructionKind.H | InstructionKind.S | InstructionKind.X | InstructionKind.Y | InstructionKind.Z: return qasm3_gate_call(instruction.kind.name.lower(), [qasm3_qubit(instruction.target)]) case InstructionKind.I: @@ -269,3 +273,23 @@ def domain_to_qasm3_lines(domain: Iterable[int], cmd: str) -> Iterator[str]: yield f"if ({condition}) {{\n" yield f" {cmd};\n" yield "}\n" + + +def _decompose_j_gates(circuit: Circuit) -> Circuit: + """Decompose J(alpha) into RZ(alpha) then H, up to global phase. + """ + if not any(instr.kind == InstructionKind.J for instr in circuit.instruction): + return circuit + warnings.warn( + "J gates decomposed as RZ * H for QASM3 export.", + stacklevel=3, + ) + new_circuit = Circuit(circuit.width) + for instr in circuit.instruction: + if instr.kind == InstructionKind.J: + # circuit time order: RZ first, H second (J = H * RZ) + new_circuit.add(instruction.RZ(target=instr.target, angle=instr.angle)) + new_circuit.add(instruction.H(target=instr.target)) + else: + new_circuit.add(instr) + return new_circuit \ No newline at end of file diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index d38bed2e2..dd37403b7 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -144,8 +144,6 @@ def add_nodes(self, nqubit: int, data: Data) -> None: Previously existing nodes remain unchanged. """ dm_to_add = DensityMatrix(nqubit=nqubit, data=data) - if self.rho.dtype == np.object_ and dm_to_add.rho.dtype != np.object_: - dm_to_add.rho = dm_to_add.rho.astype(np.object_, copy=False) self.tensor(dm_to_add) @override @@ -262,6 +260,8 @@ def tensor(self, other: DensityMatrix) -> None: """ if not isinstance(other, DensityMatrix): other = DensityMatrix(other) + if self.rho.dtype == np.object_ and other.rho.dtype != np.object_: + other.rho = other.rho.astype(np.object_, copy=False) self.rho = kron(self.rho, other.rho) def cnot(self, edge: tuple[int, int]) -> None: diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 6b9d19cfd..010618d58 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -172,8 +172,6 @@ def add_nodes(self, nqubit: int, data: Data) -> None: Previously existing nodes remain unchanged. """ sv_to_add = Statevec(nqubit=nqubit, data=data) - if self.psi.dtype == np.object_ and sv_to_add.psi.dtype != np.object_: - sv_to_add.psi = sv_to_add.psi.astype(np.object_, copy=False) self.tensor(sv_to_add) @override @@ -304,6 +302,8 @@ def tensor(self, other: Statevec) -> None: psi_other = other.psi.flatten() total_num = len(self.dims()) + len(other.dims()) + if self.psi.dtype == np.object_ and other.psi.dtype != np.object_: + other.psi = other.psi.astype(np.object_, copy=False) self.psi = kron(psi_self, psi_other).reshape((2,) * total_num) def cnot(self, qubits: tuple[int, int]) -> None: diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 7dd918dd5..2f1400892 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -7,7 +7,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, SupportsFloat +from typing import TYPE_CHECKING, SupportsFloat, Generic, TypeVar import networkx as nx @@ -37,18 +37,31 @@ from graphix.sim import Data from graphix.sim.base_backend import Matrix +_R = TypeVar("_R", bound="Pattern | CausalFlow[BlochMeasurement]") +_CO = TypeVar("_CO", bound="tuple[int, ...] | dict[int, command.M]") @dataclass -class TranspileResult: +class TranspileResult(Generic[_R, _CO]): """ The result of a transpilation. - pattern : :class:`graphix.pattern.Pattern` object - classical_outputs : tuple[int,...], index of nodes measured with *M* gates + pattern : :class:`graphix.pattern.Pattern` or :class:`graphix.flow.core.CausalFlow` object + classical_outputs : tuple[int, ...] | dict[int, command.M], index of nodes measured with *M* gates, with associated M commands as dictionary. """ + result: _R + classical_outputs: _CO - pattern: Pattern - classical_outputs: tuple[int, ...] + @property + def pattern(self) -> Pattern: + if not isinstance(self.result, Pattern): + raise AttributeError("result is not a Pattern; use `.flow` or `.result`") + return self.result + + @property + def flow(self) -> CausalFlow[BlochMeasurement]: + if not isinstance(self.result, CausalFlow): + raise AttributeError("result is not a CausalFlow; use `.pattern` or `.result`") + return self.result @dataclass @@ -405,8 +418,8 @@ def m(self, qubit: int, axis: Axis) -> None: self.instruction.append(instruction.M(target=qubit, axis=axis)) self.active_qubits.remove(qubit) - def transpile(self) -> TranspileResult: - """Transpile a circuit via J-∧z decomposition to a pattern. + def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]]: + """Transpile a circuit via J-∧z decomposition to a causal flow. Parameters ---------- @@ -414,7 +427,7 @@ def transpile(self) -> TranspileResult: Returns ------- - the result of the transpilation: a pattern. + the result of the transpilation: a causal flow and classical outputs. Raises ------ @@ -477,9 +490,10 @@ def transpile(self) -> TranspileResult: z_corrections[node] = z_targets partial_order_layers = _corrections_to_partial_order_layers(og, x_corrections, z_corrections) f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) - pattern = StandardizedPattern.from_pattern(f.to_corrections().to_pattern()).to_space_optimal_pattern() - pattern.extend(classical_outputs.values()) - return TranspileResult(pattern, tuple(classical_outputs.keys())) + return TranspileResult(f, classical_outputs) + + def transpile(self) -> TranspileResult[Pattern, tuple[int, ...]]: + return _transpile_cflow_to_pattern(self.transpile_to_cflow()) def simulate_statevector( self, @@ -969,6 +983,12 @@ def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: return TranspileSwapsResult(new_circuit, tuple(visitor.qubits)) +def _transpile_cflow_to_pattern(tr: TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]]) -> TranspileResult[Pattern, tuple[int, ...]]: + pattern = StandardizedPattern.from_pattern(tr.flow.to_corrections().to_pattern()).to_space_optimal_pattern() + pattern.extend(tr.classical_outputs.values()) + return TranspileResult(pattern, tuple(tr.classical_outputs.keys())) + + class IllformedCircuitError(Exception): """Raised if the circuit is ill-formed.""" diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index b23965f1f..608d92a1b 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -60,3 +60,11 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: pattern.perform_pauli_measurements() pattern.minimize_space() _qasm3 = pattern_to_qasm3(pattern) + + +def test_j_gate_to_qasm3() -> None: + """Check that J gates export properly.""" + circuit = Circuit(1, instr=[instruction.J(target=0, angle=0)]) + qasm = circuit_to_qasm3(circuit) + with pytest.warns(UserWarning, match="J gates decomposed as RZ * H for QASM3 export."): + warnings.warn("J gates decomposed as RZ * H for QASM3 export.", UserWarning) \ No newline at end of file diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index d0199a707..ae5416ad6 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -9,7 +9,7 @@ from graphix import Circuit, instruction from graphix.fundamentals import ANGLE_PI -from graphix.qasm3_exporter import circuit_to_qasm3 +from graphix.qasm3_exporter import circuit_to_qasm3, _decompose_j_gates from graphix.random_objects import rand_circuit if TYPE_CHECKING: @@ -66,3 +66,11 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: ) def test_instruction_to_qasm3(instruction: Instruction) -> None: check_round_trip(Circuit(3, instr=[instruction])) + +def test_j_to_qasm3() -> None: + circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)] + check_circuit = _decompose_j_gates(circuit) + qasm = circuit_to_qasm3(circuit) + parser = OpenQASMParser() + parsed_circuit = parser.parse_str(qasm) + assert parsed_circuit.instruction == circuit.instruction From 38ff734c6e0eab38bfd2645e8a6637538a582ee0 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Tue, 28 Apr 2026 18:22:16 +0200 Subject: [PATCH 33/63] updated CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef29d087a..c64d28eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #484: J & CZ transpilation. - Replaced `Circuit.transpile()` with a new approach based decomposing circuits into J & CZ gates. + - Added `Circuit.transpile_to_cflow()` to produce `CausalFlow` using the same decomposition. - Added `instruction.J` class. + - #479: Added new methods `OpenGraph.draw`, `PauliFlow.draw` and `XZCorrections.draw`. - #454, #481: New space minimization API that allows users to select or define custom heuristics. From 5e5521238c012db96dea3e4299fcc2f26cfcc4f9 Mon Sep 17 00:00:00 2001 From: Emlyn <42484330+emlynsg@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:34:04 +0200 Subject: [PATCH 34/63] Update CHANGELOG.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c64d28eab..bc93781ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replaced `Circuit.transpile()` with a new approach based decomposing circuits into J & CZ gates. - Added `Circuit.transpile_to_cflow()` to produce `CausalFlow` using the same decomposition. - Added `instruction.J` class. - - #479: Added new methods `OpenGraph.draw`, `PauliFlow.draw` and `XZCorrections.draw`. - #454, #481: New space minimization API that allows users to select or define custom heuristics. From 26db8a2fa6d6dbfe8b6750b25d52a8342ee09b33 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 29 Apr 2026 09:55:07 +0200 Subject: [PATCH 35/63] trying to fix qasm3 --- graphix/__init__.py | 2 +- graphix/qasm3_exporter.py | 15 +++++++------- graphix/transpiler.py | 38 ++++++++++++++++++++++++++++++------ tests/conftest.py | 4 ++-- tests/test_qasm3_exporter.py | 6 ++++-- 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/graphix/__init__.py b/graphix/__init__.py index cb8aedb61..1ea010024 100644 --- a/graphix/__init__.py +++ b/graphix/__init__.py @@ -3,8 +3,8 @@ from __future__ import annotations from graphix.graphsim import GraphState +from graphix.transpiler import Circuit # isort: skip # must be imported out of order from graphix.pattern import Pattern from graphix.sim.statevec import Statevec -from graphix.transpiler import Circuit __all__ = ["Circuit", "GraphState", "Pattern", "Statevec"] diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index cf4d84151..797fbb85e 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -2,8 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING import warnings +from typing import TYPE_CHECKING + # assert_never added in Python 3.11 from typing_extensions import assert_never @@ -14,11 +15,12 @@ from graphix.instruction import Instruction, InstructionKind from graphix.pretty_print import OutputFormat, angle_to_str from graphix.states import BasicStates, State +from graphix.transpiler import Circuit if TYPE_CHECKING: from collections.abc import Iterable, Iterator - from graphix import Circuit, Pattern + from graphix import Pattern from graphix.command import Command @@ -103,9 +105,7 @@ def instruction_to_qasm3(instruction: Instruction) -> str: instruction.kind.name.lower(), args=[angle], operands=[qasm3_qubit(instruction.target)] ) case InstructionKind.J: - raise ValueError( - "J gate should have been removed by `_decompose_j_gates`." - ) + raise ValueError("J gate should have been removed by `_decompose_j_gates`.") case InstructionKind.H | InstructionKind.S | InstructionKind.X | InstructionKind.Y | InstructionKind.Z: return qasm3_gate_call(instruction.kind.name.lower(), [qasm3_qubit(instruction.target)]) case InstructionKind.I: @@ -276,8 +276,7 @@ def domain_to_qasm3_lines(domain: Iterable[int], cmd: str) -> Iterator[str]: def _decompose_j_gates(circuit: Circuit) -> Circuit: - """Decompose J(alpha) into RZ(alpha) then H, up to global phase. - """ + """Decompose J(alpha) into RZ(alpha) then H, up to global phase.""" if not any(instr.kind == InstructionKind.J for instr in circuit.instruction): return circuit warnings.warn( @@ -292,4 +291,4 @@ def _decompose_j_gates(circuit: Circuit) -> Circuit: new_circuit.add(instruction.H(target=instr.target)) else: new_circuit.add(instr) - return new_circuit \ No newline at end of file + return new_circuit diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 2f1400892..179fa637e 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -7,7 +7,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, SupportsFloat, Generic, TypeVar +from typing import TYPE_CHECKING, Generic, SupportsFloat, TypeVar import networkx as nx @@ -15,7 +15,7 @@ # override introduced in Python 3.12 from typing_extensions import assert_never, override -from graphix import command, instruction, parameter +from graphix import Pattern, command, instruction, parameter from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers from graphix.fundamentals import ANGLE_PI, Axis @@ -33,13 +33,13 @@ from graphix.fundamentals import ParameterizedAngle from graphix.parameter import ExpressionOrFloat, Parameter - from graphix.pattern import Pattern from graphix.sim import Data from graphix.sim.base_backend import Matrix _R = TypeVar("_R", bound="Pattern | CausalFlow[BlochMeasurement]") _CO = TypeVar("_CO", bound="tuple[int, ...] | dict[int, command.M]") + @dataclass class TranspileResult(Generic[_R, _CO]): """ @@ -47,20 +47,34 @@ class TranspileResult(Generic[_R, _CO]): pattern : :class:`graphix.pattern.Pattern` or :class:`graphix.flow.core.CausalFlow` object classical_outputs : tuple[int, ...] | dict[int, command.M], index of nodes measured with *M* gates, with associated M commands as dictionary. + """ + result: _R classical_outputs: _CO @property def pattern(self) -> Pattern: + """Return pattern from TranspileResult if any. + + Raises + ------ + TypeError: if the TranspileResult stores a flow instead.\ + """ if not isinstance(self.result, Pattern): - raise AttributeError("result is not a Pattern; use `.flow` or `.result`") + raise TypeError("result is not a Pattern; use `.flow` or `.result`") return self.result @property def flow(self) -> CausalFlow[BlochMeasurement]: + """Return causal flow from TranspileResult if any. + + Raises + ------ + TypeError: if the TranspileResult stores a pattern instead. + """ if not isinstance(self.result, CausalFlow): - raise AttributeError("result is not a CausalFlow; use `.pattern` or `.result`") + raise TypeError("result is not a CausalFlow; use `.pattern` or `.result`") return self.result @@ -493,6 +507,16 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di return TranspileResult(f, classical_outputs) def transpile(self) -> TranspileResult[Pattern, tuple[int, ...]]: + """Transpile a circuit via J-∧z decomposition to a pattern. + + Parameters + ---------- + self: the circuit to transpile. + + Returns + ------- + the result of the transpilation: a pattern and classical outputs. + """ return _transpile_cflow_to_pattern(self.transpile_to_cflow()) def simulate_statevector( @@ -983,7 +1007,9 @@ def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: return TranspileSwapsResult(new_circuit, tuple(visitor.qubits)) -def _transpile_cflow_to_pattern(tr: TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]]) -> TranspileResult[Pattern, tuple[int, ...]]: +def _transpile_cflow_to_pattern( + tr: TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]], +) -> TranspileResult[Pattern, tuple[int, ...]]: pattern = StandardizedPattern.from_pattern(tr.flow.to_corrections().to_pattern()).to_space_optimal_pattern() pattern.extend(tr.classical_outputs.values()) return TranspileResult(pattern, tuple(tr.classical_outputs.keys())) diff --git a/tests/conftest.py b/tests/conftest.py index 61852ef85..2512cf4a5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,11 +14,11 @@ import pytest from numpy.random import PCG64, Generator +from graphix import Circuit from graphix.random_objects import rand_circuit -from graphix.transpiler import Circuit if TYPE_CHECKING: - from graphix.pattern import Pattern + from graphix import Pattern SEED = 25 DEPTH = 1 diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index 608d92a1b..22c9f5a56 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -8,6 +8,8 @@ from __future__ import annotations +import warnings + import pytest from numpy.random import PCG64, Generator @@ -65,6 +67,6 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: def test_j_gate_to_qasm3() -> None: """Check that J gates export properly.""" circuit = Circuit(1, instr=[instruction.J(target=0, angle=0)]) - qasm = circuit_to_qasm3(circuit) with pytest.warns(UserWarning, match="J gates decomposed as RZ * H for QASM3 export."): - warnings.warn("J gates decomposed as RZ * H for QASM3 export.", UserWarning) \ No newline at end of file + circuit_to_qasm3(circuit) + warnings.warn("J gates decomposed as RZ * H for QASM3 export.", UserWarning, stacklevel=2) From dff39b8b875c0f694bca360e97214c00f57f8d7d Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 29 Apr 2026 10:26:16 +0200 Subject: [PATCH 36/63] fixing qasm --- graphix/__init__.py | 2 +- graphix/qasm3_exporter.py | 5 +++-- graphix/transpiler.py | 8 +++++--- tests/conftest.py | 6 +++--- tests/test_qasm3_exporter.py | 7 ++----- tests/test_qasm3_exporter_to_graphix_parser.py | 7 ++++--- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/graphix/__init__.py b/graphix/__init__.py index 1ea010024..cb8aedb61 100644 --- a/graphix/__init__.py +++ b/graphix/__init__.py @@ -3,8 +3,8 @@ from __future__ import annotations from graphix.graphsim import GraphState -from graphix.transpiler import Circuit # isort: skip # must be imported out of order from graphix.pattern import Pattern from graphix.sim.statevec import Statevec +from graphix.transpiler import Circuit __all__ = ["Circuit", "GraphState", "Pattern", "Statevec"] diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 797fbb85e..0e47b483f 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -8,6 +8,7 @@ # assert_never added in Python 3.11 from typing_extensions import assert_never +import graphix.transpiler from graphix import instruction from graphix._version import version from graphix.command import CommandKind @@ -15,13 +16,13 @@ from graphix.instruction import Instruction, InstructionKind from graphix.pretty_print import OutputFormat, angle_to_str from graphix.states import BasicStates, State -from graphix.transpiler import Circuit if TYPE_CHECKING: from collections.abc import Iterable, Iterator from graphix import Pattern from graphix.command import Command + from graphix.transpiler import Circuit def circuit_to_qasm3(circuit: Circuit) -> str: @@ -283,7 +284,7 @@ def _decompose_j_gates(circuit: Circuit) -> Circuit: "J gates decomposed as RZ * H for QASM3 export.", stacklevel=3, ) - new_circuit = Circuit(circuit.width) + new_circuit = graphix.transpiler.Circuit(circuit.width) for instr in circuit.instruction: if instr.kind == InstructionKind.J: # circuit time order: RZ first, H second (J = H * RZ) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 179fa637e..bd7e7b8cf 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -15,7 +15,8 @@ # override introduced in Python 3.12 from typing_extensions import assert_never, override -from graphix import Pattern, command, instruction, parameter +import graphix.pattern +from graphix import command, instruction, parameter from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers from graphix.fundamentals import ANGLE_PI, Axis @@ -33,6 +34,7 @@ from graphix.fundamentals import ParameterizedAngle from graphix.parameter import ExpressionOrFloat, Parameter + from graphix.pattern import Pattern from graphix.sim import Data from graphix.sim.base_backend import Matrix @@ -59,9 +61,9 @@ def pattern(self) -> Pattern: Raises ------ - TypeError: if the TranspileResult stores a flow instead.\ + TypeError: if the TranspileResult stores a flow instead. """ - if not isinstance(self.result, Pattern): + if not isinstance(self.result, graphix.pattern.Pattern): raise TypeError("result is not a Pattern; use `.flow` or `.result`") return self.result diff --git a/tests/conftest.py b/tests/conftest.py index 2512cf4a5..1e34e0f02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,11 +14,11 @@ import pytest from numpy.random import PCG64, Generator -from graphix import Circuit +import graphix.transpiler from graphix.random_objects import rand_circuit if TYPE_CHECKING: - from graphix import Pattern + from graphix import Circuit, Pattern SEED = 25 DEPTH = 1 @@ -36,7 +36,7 @@ def fx_bg() -> PCG64: @pytest.fixture def hadamardpattern() -> Pattern: - circ = Circuit(1) + circ = graphix.transpiler.Circuit(1) circ.h(0) return circ.transpile().pattern diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index 22c9f5a56..fb551bfeb 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -8,8 +8,6 @@ from __future__ import annotations -import warnings - import pytest from numpy.random import PCG64, Generator @@ -66,7 +64,6 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: def test_j_gate_to_qasm3() -> None: """Check that J gates export properly.""" - circuit = Circuit(1, instr=[instruction.J(target=0, angle=0)]) - with pytest.warns(UserWarning, match="J gates decomposed as RZ * H for QASM3 export."): + circuit = Circuit(1, instr=[instruction.J(target=0, angle=0.0)]) + with pytest.warns(UserWarning, match=r"J gates decomposed as RZ \* H for QASM3 export\."): circuit_to_qasm3(circuit) - warnings.warn("J gates decomposed as RZ * H for QASM3 export.", UserWarning, stacklevel=2) diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index ae5416ad6..77dc0f1e6 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -9,7 +9,7 @@ from graphix import Circuit, instruction from graphix.fundamentals import ANGLE_PI -from graphix.qasm3_exporter import circuit_to_qasm3, _decompose_j_gates +from graphix.qasm3_exporter import _decompose_j_gates, circuit_to_qasm3 from graphix.random_objects import rand_circuit if TYPE_CHECKING: @@ -67,10 +67,11 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: def test_instruction_to_qasm3(instruction: Instruction) -> None: check_round_trip(Circuit(3, instr=[instruction])) + def test_j_to_qasm3() -> None: - circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)] + circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)]) check_circuit = _decompose_j_gates(circuit) qasm = circuit_to_qasm3(circuit) parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) - assert parsed_circuit.instruction == circuit.instruction + assert parsed_circuit.instruction == check_circuit.instruction From 20146558afd87df62da9d07405975d068eb4d8b3 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 29 Apr 2026 13:46:07 +0200 Subject: [PATCH 37/63] fixing kron for statevec --- graphix/sim/statevec.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 010618d58..efb24bfe1 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -302,8 +302,8 @@ def tensor(self, other: Statevec) -> None: psi_other = other.psi.flatten() total_num = len(self.dims()) + len(other.dims()) - if self.psi.dtype == np.object_ and other.psi.dtype != np.object_: - other.psi = other.psi.astype(np.object_, copy=False) + if psi_self.dtype == np.object_ and psi_other.dtype != np.object_: + psi_other = psi_other.astype(np.object_, copy=False) self.psi = kron(psi_self, psi_other).reshape((2,) * total_num) def cnot(self, qubits: tuple[int, int]) -> None: From 101bd87a621493cc4bc4d172c8f46ce931fc85ae Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Wed, 29 Apr 2026 16:34:04 +0200 Subject: [PATCH 38/63] fixing other qasm3 exporter tests --- docs/source/generator.rst | 1 + graphix/transpiler.py | 26 ------------------- .../test_qasm3_exporter_to_graphix_parser.py | 5 ++-- tests/test_qasm3_exporter_to_qiskit.py | 2 +- 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/docs/source/generator.rst b/docs/source/generator.rst index 2a5fb5569..f9fac960f 100644 --- a/docs/source/generator.rst +++ b/docs/source/generator.rst @@ -17,6 +17,7 @@ Pattern Generation .. automethod:: simulate_statevector .. automethod:: cz + .. automethod:: cnot .. automethod:: h diff --git a/graphix/transpiler.py b/graphix/transpiler.py index bd7e7b8cf..cf3a48789 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -448,8 +448,6 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di Raises ------ IllformedCircuitError: if the pattern is ill-formed (operation on already measured node) - CircuitWithMeasurementError: if the circuit contains measurements. - """ indices: list[int | None] = list(range(self.width)) n_nodes = self.width @@ -1023,27 +1021,3 @@ class IllformedCircuitError(Exception): def __init__(self) -> None: """Build the exception.""" super().__init__("Ill-formed pattern") - - -class CircuitWithMeasurementError(Exception): - """Raised if the circuit contains measurements.""" - - def __init__(self) -> None: - """Build the exception.""" - super().__init__("Circuits containing measurements are not supported by the transpiler.") - - -class InternalInstructionError(Exception): - """Raised if the circuit contains internal _XC or _ZC instructions.""" - - def __init__(self, instr: instruction.Instruction) -> None: - """Build the exception.""" - super().__init__(f"Internal instruction: {instr}") - - -class MeasurementsNoPreproceddedError(Exception): - """Raised if the circuit contains measurements that have not been preprocessed before the transpile step.""" - - def __init__(self) -> None: - """Build the exception.""" - super().__init__("Circuits containing measurements were incorrectly passed to the transpiler.") diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index 77dc0f1e6..29329afba 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -31,9 +31,10 @@ def check_round_trip(circuit: Circuit) -> None: qasm = circuit_to_qasm3(circuit) + check_circuit = _decompose_j_gates(circuit) parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) - assert parsed_circuit.instruction == circuit.instruction + assert parsed_circuit.instruction == check_circuit.instruction @pytest.mark.parametrize("jumps", range(1, 11)) @@ -42,7 +43,7 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: nqubits = 5 depth = 4 # See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5 - check_round_trip(rand_circuit(nqubits, depth, rng, use_j=False, use_cz=True)) + check_round_trip(rand_circuit(nqubits, depth, rng, use_j=True, use_cz=True)) @pytest.mark.parametrize( diff --git a/tests/test_qasm3_exporter_to_qiskit.py b/tests/test_qasm3_exporter_to_qiskit.py index 0b541380e..03273fe35 100644 --- a/tests/test_qasm3_exporter_to_qiskit.py +++ b/tests/test_qasm3_exporter_to_qiskit.py @@ -117,7 +117,7 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 5 depth = 5 - circuit = rand_circuit(nqubits, depth, rng=rng, use_j=False) + circuit = rand_circuit(nqubits, depth, rng=rng, use_j=True) pattern = circuit.transpile().pattern pattern.remove_input_nodes() pattern = pattern.infer_pauli_measurements() From 3b0672763d9b582f74a68605768fbf8b5f340b54 Mon Sep 17 00:00:00 2001 From: Emlyn <42484330+emlynsg@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:02:17 +0200 Subject: [PATCH 39/63] Update graphix/transpiler.py Co-authored-by: matulni --- graphix/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index cf3a48789..664366d4e 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -47,7 +47,7 @@ class TranspileResult(Generic[_R, _CO]): """ The result of a transpilation. - pattern : :class:`graphix.pattern.Pattern` or :class:`graphix.flow.core.CausalFlow` object + result : :class:`graphix.pattern.Pattern` or :class:`graphix.flow.core.CausalFlow` object classical_outputs : tuple[int, ...] | dict[int, command.M], index of nodes measured with *M* gates, with associated M commands as dictionary. """ From febb6aea3148dcb0c381461dbf733cc6523e3af4 Mon Sep 17 00:00:00 2001 From: Emlyn <42484330+emlynsg@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:03:49 +0200 Subject: [PATCH 40/63] Update graphix/transpiler.py Co-authored-by: matulni --- graphix/transpiler.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 664366d4e..52f51a8d7 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -56,15 +56,8 @@ class TranspileResult(Generic[_R, _CO]): classical_outputs: _CO @property - def pattern(self) -> Pattern: - """Return pattern from TranspileResult if any. - - Raises - ------ - TypeError: if the TranspileResult stores a flow instead. - """ - if not isinstance(self.result, graphix.pattern.Pattern): - raise TypeError("result is not a Pattern; use `.flow` or `.result`") + def pattern(self: TranspileResult[Pattern, _CO]) -> Pattern: + """Return pattern from `TranspileResult` if any.""" return self.result @property From 6e1e845dbc6f265eace421cc577b6e2c31c8195d Mon Sep 17 00:00:00 2001 From: Emlyn <42484330+emlynsg@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:04:05 +0200 Subject: [PATCH 41/63] Update graphix/transpiler.py Co-authored-by: matulni --- graphix/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 52f51a8d7..1b1e851ae 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -447,7 +447,7 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di measurements: dict[int, BlochMeasurement] = {} classical_outputs: dict[int, command.M] = {} inputs = list(range(n_nodes)) - graph: nx.Graph[int] = nx.Graph() # In future change to OpenGraph not nx + graph: nx.Graph[int] = nx.Graph() graph.add_nodes_from(inputs) x_corrections: dict[int, set[int]] = {} for instr in self.instruction: From 5f13bad5a53aa94d309a2ec60dd94e763e1d474b Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Thu, 30 Apr 2026 16:19:48 +0200 Subject: [PATCH 42/63] changes and fixes from Mateo comments --- graphix/qasm3_exporter.py | 25 +- graphix/transpiler.py | 280 ++++++++---------- tests/test_optimization.py | 3 + tests/test_parameter.py | 1 + tests/test_pattern.py | 8 + tests/test_pretty_print.py | 2 +- tests/test_qasm3_exporter.py | 7 - .../test_qasm3_exporter_to_graphix_parser.py | 11 +- tests/test_tnsim.py | 3 + tests/test_transpiler.py | 38 ++- 10 files changed, 165 insertions(+), 213 deletions(-) diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 0e47b483f..e610f1ace 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -2,14 +2,11 @@ from __future__ import annotations -import warnings from typing import TYPE_CHECKING # assert_never added in Python 3.11 from typing_extensions import assert_never -import graphix.transpiler -from graphix import instruction from graphix._version import version from graphix.command import CommandKind from graphix.fundamentals import Axis, ParameterizedAngle, Plane @@ -44,7 +41,8 @@ def circuit_to_qasm3_lines(circuit: Circuit) -> Iterator[str]: Iterator[str] The OpenQASM 3.0 lines that represent the circuit. """ - circuit = _decompose_j_gates(circuit) + if any(instr.kind == InstructionKind.J for instr in circuit.instruction): + raise ValueError("J gates must be decomposed before QASM3 export using `Circuit.transpile_j_to_rzh`.") yield "OPENQASM 3;" yield 'include "stdgates.inc";' yield f"qubit[{circuit.width}] q;" @@ -274,22 +272,3 @@ def domain_to_qasm3_lines(domain: Iterable[int], cmd: str) -> Iterator[str]: yield f"if ({condition}) {{\n" yield f" {cmd};\n" yield "}\n" - - -def _decompose_j_gates(circuit: Circuit) -> Circuit: - """Decompose J(alpha) into RZ(alpha) then H, up to global phase.""" - if not any(instr.kind == InstructionKind.J for instr in circuit.instruction): - return circuit - warnings.warn( - "J gates decomposed as RZ * H for QASM3 export.", - stacklevel=3, - ) - new_circuit = graphix.transpiler.Circuit(circuit.width) - for instr in circuit.instruction: - if instr.kind == InstructionKind.J: - # circuit time order: RZ first, H second (J = H * RZ) - new_circuit.add(instruction.RZ(target=instr.target, angle=instr.angle)) - new_circuit.add(instruction.H(target=instr.target)) - else: - new_circuit.add(instr) - return new_circuit diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 1b1e851ae..272572710 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -15,7 +15,6 @@ # override introduced in Python 3.12 from typing_extensions import assert_never, override -import graphix.pattern from graphix import command, instruction, parameter from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers @@ -28,7 +27,7 @@ from graphix.sim.statevec import Statevec, StatevectorBackend if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Mapping, Sequence + from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence from numpy.random import Generator @@ -61,15 +60,8 @@ def pattern(self: TranspileResult[Pattern, _CO]) -> Pattern: return self.result @property - def flow(self) -> CausalFlow[BlochMeasurement]: - """Return causal flow from TranspileResult if any. - - Raises - ------ - TypeError: if the TranspileResult stores a pattern instead. - """ - if not isinstance(self.result, CausalFlow): - raise TypeError("result is not a CausalFlow; use `.pattern` or `.result`") + def flow(self: TranspileResult[CausalFlow[BlochMeasurement], _CO]) -> CausalFlow[BlochMeasurement]: + """Return causal flow from TranspileResult if any.""" return self.result @@ -450,27 +442,27 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di graph: nx.Graph[int] = nx.Graph() graph.add_nodes_from(inputs) x_corrections: dict[int, set[int]] = {} - for instr in self.instruction: - if instr.kind == InstructionKind.M: - target = indices[instr.target] - if target is None: - raise IllformedCircuitError - classical_outputs[target] = command.M(target, PauliMeasurement(instr.axis)) - indices[instr.target] = None - continue - for instr_jcz in instruction_to_jcz(instr): - if instr_jcz.kind == InstructionKind.J: - target = indices[instr_jcz.target] + for instr in instructions_to_jcz(self.instruction): + match instr.kind: + case InstructionKind.M: + target = indices[instr.target] + if target is None: + raise IllformedCircuitError + classical_outputs[target] = command.M(target, PauliMeasurement(instr.axis)) + indices[instr.target] = None + continue + case InstructionKind.J: + target = indices[instr.target] if target is None: raise IllformedCircuitError graph.add_edge(target, n_nodes) # Also adds nodes - measurements[target] = Measurement.XY(normalize_angle(-instr_jcz.angle)) - indices[instr_jcz.target] = n_nodes + measurements[target] = Measurement.XY(normalize_angle(-instr.angle)) + indices[instr.target] = n_nodes x_corrections[target] = {n_nodes} # X correction on ancilla n_nodes += 1 continue - if instr_jcz.kind == InstructionKind.CZ: - t0, t1 = instr_jcz.targets + case InstructionKind.CZ: + t0, t1 = instr.targets i0, i1 = indices[t0], indices[t1] if i0 is None or i1 is None: raise IllformedCircuitError @@ -480,9 +472,10 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di else: graph.add_edge(i0, i1) continue - assert_never(instr_jcz.kind) + case _: + assert_never(instr.kind) outputs = [i for i in indices if i is not None] - outputs.extend(classical_outputs.keys()) + outputs.extend(classical_outputs.keys()) # Necessary for flow-finding step og = OpenGraph( graph=graph, input_nodes=inputs, @@ -657,9 +650,21 @@ def transpile_measurements_to_z_axis(self) -> Circuit: circuit.add(instr) return circuit + def transpile_j_to_rzh(self) -> Circuit: + """Return an equivalent circuit where all J gates have been replaced with RZ and H gates.""" + new_circuit = Circuit(self.width) + for instr in self.instruction: + match instr.kind: + case InstructionKind.J: + new_circuit.add(instruction.RZ(target=instr.target, angle=instr.angle)) + new_circuit.add(instruction.H(target=instr.target)) + case _: + new_circuit.add(instr) + return new_circuit + -def decompose_rzz(instr: instruction.RZZ) -> list[instruction.CNOT | instruction.RZ]: - """Return a decomposition of RZZ(α) gate as CNOT(control, target)·Rz(target, α)·CNOT(control, target). +def decompose_rzz(instr: instruction.RZZ) -> Iterator[instruction.CNOT | instruction.RZ]: + """Yield a decomposition of RZZ(α) gate as CNOT(control, target)·Rz(target, α)·CNOT(control, target). Parameters ---------- @@ -670,17 +675,15 @@ def decompose_rzz(instr: instruction.RZZ) -> list[instruction.CNOT | instruction the decomposition. """ - return [ - instruction.CNOT(target=instr.target, control=instr.control), - instruction.RZ(instr.target, instr.angle), - instruction.CNOT(target=instr.target, control=instr.control), - ] + yield instruction.CNOT(target=instr.target, control=instr.control) + yield instruction.RZ(instr.target, instr.angle) + yield instruction.CNOT(target=instr.target, control=instr.control) def decompose_ccx( instr: instruction.CCX, -) -> list[instruction.H | instruction.CNOT | instruction.RZ]: - """Return a decomposition of the CCX gate into H, CNOT, T and T-dagger gates. +) -> Iterator[instruction.H | instruction.CNOT | instruction.RZ]: + """Yield a decomposition of the CCX gate into H, CNOT, T and T-dagger gates. This decomposition of the Toffoli gate can be found in Michael A. Nielsen and Isaac L. Chuang, @@ -697,28 +700,27 @@ def decompose_ccx( the decomposition. """ - return [ - instruction.H(instr.target), - instruction.CNOT(control=instr.controls[1], target=instr.target), - instruction.RZ(instr.target, -ANGLE_PI / 4), - instruction.CNOT(control=instr.controls[0], target=instr.target), - instruction.RZ(instr.target, ANGLE_PI / 4), - instruction.CNOT(control=instr.controls[1], target=instr.target), - instruction.RZ(instr.target, -ANGLE_PI / 4), - instruction.CNOT(control=instr.controls[0], target=instr.target), - instruction.RZ(instr.controls[1], -ANGLE_PI / 4), - instruction.RZ(instr.target, ANGLE_PI / 4), - instruction.CNOT(control=instr.controls[0], target=instr.controls[1]), - instruction.H(instr.target), - instruction.RZ(instr.controls[1], -ANGLE_PI / 4), - instruction.CNOT(control=instr.controls[0], target=instr.controls[1]), - instruction.RZ(instr.controls[0], ANGLE_PI / 4), - instruction.RZ(instr.controls[1], ANGLE_PI / 2), - ] - - -def decompose_cnot(instr: instruction.CNOT) -> list[instruction.H | instruction.CZ]: - """Return a decomposition of the CNOT gate as H·∧z·H. + c0, c1, t = instr.controls[0], instr.controls[1], instr.target + yield instruction.H(t) + yield instruction.CNOT(control=c1, target=t) + yield instruction.RZ(t, -ANGLE_PI / 4) + yield instruction.CNOT(control=c0, target=t) + yield instruction.RZ(t, ANGLE_PI / 4) + yield instruction.CNOT(control=c1, target=t) + yield instruction.RZ(t, -ANGLE_PI / 4) + yield instruction.CNOT(control=c0, target=t) + yield instruction.RZ(c1, -ANGLE_PI / 4) + yield instruction.RZ(t, ANGLE_PI / 4) + yield instruction.CNOT(control=c0, target=c1) + yield instruction.H(t) + yield instruction.RZ(c1, -ANGLE_PI / 4) + yield instruction.CNOT(control=c0, target=c1) + yield instruction.RZ(c0, ANGLE_PI / 4) + yield instruction.RZ(c1, ANGLE_PI / 2) + + +def decompose_cnot(instr: instruction.CNOT) -> Iterator[instruction.H | instruction.CZ]: + """Yield a decomposition of the CNOT gate as H·∧z·H. Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. @@ -731,15 +733,13 @@ def decompose_cnot(instr: instruction.CNOT) -> list[instruction.H | instruction. the decomposition. """ - return [ - instruction.H(instr.target), - instruction.CZ((instr.control, instr.target)), - instruction.H(instr.target), - ] + yield instruction.H(instr.target) + yield instruction.CZ((instr.control, instr.target)) + yield instruction.H(instr.target) -def decompose_swap(instr: instruction.SWAP) -> list[instruction.CNOT]: - """Return a decomposition of the SWAP gate as CNOT(0, 1)·CNOT(1, 0)·CNOT(0, 1). +def decompose_swap(instr: instruction.SWAP) -> Iterator[instruction.CNOT]: + """Yield a decomposition of the SWAP gate as CNOT(0, 1)·CNOT(1, 0)·CNOT(0, 1). Michael A. Nielsen and Isaac L. Chuang, Quantum Computation and Quantum Information, @@ -755,14 +755,12 @@ def decompose_swap(instr: instruction.SWAP) -> list[instruction.CNOT]: the decomposition. """ - return [ - instruction.CNOT(control=instr.targets[0], target=instr.targets[1]), - instruction.CNOT(control=instr.targets[1], target=instr.targets[0]), - instruction.CNOT(control=instr.targets[0], target=instr.targets[1]), - ] + yield instruction.CNOT(control=instr.targets[0], target=instr.targets[1]) + yield instruction.CNOT(control=instr.targets[1], target=instr.targets[0]) + yield instruction.CNOT(control=instr.targets[0], target=instr.targets[1]) -def decompose_y(instr: instruction.Y) -> list[instruction.X | instruction.Z]: +def decompose_y(instr: instruction.Y) -> Iterator[instruction.X | instruction.Z]: """Return a decomposition of the Y gate as X·Z. Parameters @@ -774,11 +772,12 @@ def decompose_y(instr: instruction.Y) -> list[instruction.X | instruction.Z]: the decomposition. """ - return list(reversed([instruction.X(instr.target), instruction.Z(instr.target)])) + yield instruction.Z(instr.target) + yield instruction.X(instr.target) -def decompose_rx(instr: instruction.RX) -> list[instruction.J]: - """Return a J decomposition of the RX gate. +def decompose_rx(instr: instruction.RX) -> Iterator[instruction.J]: + """Yield a J decomposition of the RX gate. The Rx(α) gate is decomposed into J(α)·H (that is to say, J(α)·J(0)). Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. @@ -792,11 +791,12 @@ def decompose_rx(instr: instruction.RX) -> list[instruction.J]: the decomposition. """ - return [instruction.J(target=instr.target, angle=angle) for angle in reversed((instr.angle, 0))] + yield instruction.J(instr.target, 0) + yield instruction.J(instr.target, instr.angle) -def decompose_ry(instr: instruction.RY) -> list[instruction.J]: - """Return a J decomposition of the RY gate. +def decompose_ry(instr: instruction.RY) -> Iterator[instruction.J]: + """Yield a J decomposition of the RY gate. The Ry(α) gate is decomposed into J(0)·J(π/2)·J(α)·J(-π/2). Vincent Danos, Elham Kashefi, Prakash Panangaden, Robust and parsimonious realisations of unitaries in the one-way @@ -811,14 +811,14 @@ def decompose_ry(instr: instruction.RY) -> list[instruction.J]: the decomposition. """ - return [ - instruction.J(target=instr.target, angle=angle) - for angle in reversed((0, ANGLE_PI / 2, instr.angle, -ANGLE_PI / 2)) - ] + yield instruction.J(target=instr.target, angle=-ANGLE_PI / 2) + yield instruction.J(target=instr.target, angle=instr.angle) + yield instruction.J(target=instr.target, angle=ANGLE_PI / 2) + yield instruction.J(target=instr.target, angle=0) -def decompose_rz(instr: instruction.RZ) -> list[instruction.J]: - """Return a J decomposition of the RZ gate. +def decompose_rz(instr: instruction.RZ) -> Iterator[instruction.J]: + """Yield a J decomposition of the RZ gate. The Rz(α) gate is decomposed into H·J(α) (that is to say, J(0)·J(α)). Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. @@ -832,11 +832,12 @@ def decompose_rz(instr: instruction.RZ) -> list[instruction.J]: the decomposition. """ - return [instruction.J(target=instr.target, angle=angle) for angle in reversed((0, instr.angle))] + yield instruction.J(target=instr.target, angle=instr.angle) + yield instruction.J(target=instr.target, angle=0) -def instruction_to_jcz(instr: Instruction) -> Sequence[instruction.J | instruction.CZ]: - """Return a J-∧z decomposition of the instruction. +def instructions_to_jcz(instrs: Iterable[Instruction]) -> Iterator[instruction.J | instruction.CZ | instruction.M]: + """Yield a J-∧z decomposition of the instruction. Parameters ---------- @@ -847,79 +848,38 @@ def instruction_to_jcz(instr: Instruction) -> Sequence[instruction.J | instructi the decomposition. """ - # Use == for mypy - if instr.kind == InstructionKind.J: - return [instr] - if instr.kind == InstructionKind.CZ: - return [instr] - if instr.kind == InstructionKind.I: - return [] - if instr.kind == InstructionKind.H: - return [instruction.J(instr.target, 0)] - if instr.kind == InstructionKind.S: - return instruction_to_jcz(instruction.RZ(instr.target, ANGLE_PI / 2)) - if instr.kind == InstructionKind.X: - return instruction_to_jcz(instruction.RX(instr.target, ANGLE_PI)) - if instr.kind == InstructionKind.Y: - return instruction_list_to_jcz(decompose_y(instr)) - if instr.kind == InstructionKind.Z: - return instruction_to_jcz(instruction.RZ(instr.target, ANGLE_PI)) - if instr.kind == InstructionKind.RX: - return decompose_rx(instr) - if instr.kind == InstructionKind.RY: - return decompose_ry(instr) - if instr.kind == InstructionKind.RZ: - return decompose_rz(instr) - if instr.kind == InstructionKind.CCX: - return instruction_list_to_jcz(decompose_ccx(instr)) - if instr.kind == InstructionKind.RZZ: - return instruction_list_to_jcz(decompose_rzz(instr)) - if instr.kind == InstructionKind.CNOT: - return instruction_list_to_jcz(decompose_cnot(instr)) - if instr.kind == InstructionKind.SWAP: - return instruction_list_to_jcz(decompose_swap(instr)) - if instr.kind == InstructionKind.M: - raise ValueError("Measurement instructions cannot be decomposed into J and CZ gates.") - assert_never(instr.kind) - - -def instruction_list_to_jcz( - instrs: Iterable[Instruction], -) -> list[instruction.J | instruction.CZ]: - """Return a J-∧z decomposition of the sequence of instructions. - - Parameters - ---------- - instrs: the instruction sequence to decompose. - - Returns - ------- - the decomposition. - - """ - return [jcz_instr for instr in instrs for jcz_instr in instruction_to_jcz(instr)] - - -def j_commands(current_node: int, next_node: int, angle: ParameterizedAngle) -> list[command.Command]: - """Return the MBQC pattern commands for a J gate. - - Parameters - ---------- - current_node: the current node. - next_node: the next node. - angle: the angle of the J gate. - - Returns - ------- - the MBQC pattern commands for a J gate as a list - - """ - return [ - command.N(node=next_node), - command.E(nodes=(current_node, next_node)), - command.M(current_node, Measurement.XY(angle)), - command.X(node=next_node, domain={current_node}), - ] + for instr in instrs: + match instr.kind: + case InstructionKind.J | InstructionKind.CZ | InstructionKind.M: + yield instr + case InstructionKind.I: + return + case InstructionKind.H: + yield instruction.J(instr.target, 0) + case InstructionKind.S: + yield from decompose_rz(instruction.RZ(instr.target, ANGLE_PI / 2)) + case InstructionKind.X: + yield from decompose_rx(instruction.RX(instr.target, ANGLE_PI)) + case InstructionKind.Y: + yield from instructions_to_jcz(decompose_y(instr)) + case InstructionKind.Z: + yield from decompose_rz(instruction.RZ(instr.target, ANGLE_PI)) + case InstructionKind.RX: + yield from decompose_rx(instr) + case InstructionKind.RY: + yield from decompose_ry(instr) + case InstructionKind.RZ: + yield from decompose_rz(instr) + case InstructionKind.CCX: + yield from instructions_to_jcz(decompose_ccx(instr)) + case InstructionKind.RZZ: + yield from instructions_to_jcz(decompose_rzz(instr)) + case InstructionKind.CNOT: + yield from instructions_to_jcz(decompose_cnot(instr)) + case InstructionKind.SWAP: + yield from instructions_to_jcz(decompose_swap(instr)) + case _: + assert_never(instr.kind) def normalize_angle(angle: ParameterizedAngle) -> ParameterizedAngle: diff --git a/tests/test_optimization.py b/tests/test_optimization.py index 809ee7c33..7664d8655 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -62,6 +62,7 @@ def test_incorporate_pauli_results(fx_bg: PCG64, jumps: int) -> None: pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern = StandardizedPattern.from_pattern(pattern).to_space_optimal_pattern() pattern2 = incorporate_pauli_results(pattern) @@ -82,6 +83,7 @@ def test_flow_after_pauli_preprocessing(fx_bg: PCG64, jumps: int) -> None: pattern.shift_signals() # pattern.move_pauli_measurements_to_the_front() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern2 = incorporate_pauli_results(pattern) gflow = pattern2.extract_gflow() @@ -98,6 +100,7 @@ def test_remove_useless_domains(fx_bg: PCG64, jumps: int) -> None: pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern = StandardizedPattern.from_pattern(pattern).to_space_optimal_pattern() pattern2 = remove_useless_domains(pattern) diff --git a/tests/test_parameter.py b/tests/test_parameter.py index e8d3014ff..3ba84ef1a 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -171,6 +171,7 @@ def test_random_circuit_with_parameters(fx_bg: PCG64, jumps: int, use_xreplace: pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.minimize_space() assignment: dict[Parameter, float] = {alpha: rng.uniform(high=2), beta: rng.uniform(high=2)} diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 68556efa5..afe358059 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -157,6 +157,7 @@ def test_minimize_space_with_gflow(self, fx_bg: PCG64, jumps: int) -> None: pattern.standardize() pattern.shift_signals(method="mc") pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() pattern.minimize_space() state = circuit.simulate_statevector().statevec @@ -379,9 +380,12 @@ def test_pauli_repeated_measurement_compose(self, fx_bg: PCG64, jumps: int) -> N pattern1.remove_input_nodes() assert not pattern.results assert not pattern1.results + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() + pattern1 = pattern1.infer_pauli_measurements() pattern1.perform_pauli_measurements() composed_pattern.remove_input_nodes() + composed_pattern = composed_pattern.infer_pauli_measurements() composed_pattern.perform_pauli_measurements() assert abs(len(composed_pattern.results) - len(pattern.results) - len(pattern1.results)) <= 2 @@ -1010,6 +1014,8 @@ def test_extract_causal_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None p_ref.remove_input_nodes() p_test.remove_input_nodes() + p_ref = p_ref.infer_pauli_measurements() + p_test = p_test.infer_pauli_measurements() p_ref.perform_pauli_measurements() p_test.perform_pauli_measurements() @@ -1029,6 +1035,8 @@ def test_extract_gflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: p_ref.remove_input_nodes() p_test.remove_input_nodes() + p_ref = p_ref.infer_pauli_measurements() + p_test = p_test.infer_pauli_measurements() p_ref.perform_pauli_measurements() p_test.perform_pauli_measurements() diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 28dedce55..6bf690d2b 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -119,7 +119,7 @@ def test_flow_pretty_print_random( flow_extractor: Callable[[OpenGraph[Measurement]], PauliFlow[Measurement]], ) -> None: rng = Generator(fx_bg.jumped(jumps)) - rand_og = rand_circuit(5, 5, rng=rng).transpile().pattern.extract_opengraph() + rand_og = rand_circuit(5, 5, rng=rng).transpile().pattern.infer_pauli_measurements().extract_opengraph() flow = flow_extractor(rand_og) flow.to_ascii() diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index fb551bfeb..b23965f1f 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -60,10 +60,3 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: pattern.perform_pauli_measurements() pattern.minimize_space() _qasm3 = pattern_to_qasm3(pattern) - - -def test_j_gate_to_qasm3() -> None: - """Check that J gates export properly.""" - circuit = Circuit(1, instr=[instruction.J(target=0, angle=0.0)]) - with pytest.warns(UserWarning, match=r"J gates decomposed as RZ \* H for QASM3 export\."): - circuit_to_qasm3(circuit) diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index 29329afba..ec8461c9a 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -9,7 +9,7 @@ from graphix import Circuit, instruction from graphix.fundamentals import ANGLE_PI -from graphix.qasm3_exporter import _decompose_j_gates, circuit_to_qasm3 +from graphix.qasm3_exporter import circuit_to_qasm3 from graphix.random_objects import rand_circuit if TYPE_CHECKING: @@ -31,7 +31,7 @@ def check_round_trip(circuit: Circuit) -> None: qasm = circuit_to_qasm3(circuit) - check_circuit = _decompose_j_gates(circuit) + check_circuit = circuit.transpile_j_to_rzh() parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) assert parsed_circuit.instruction == check_circuit.instruction @@ -43,7 +43,7 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: nqubits = 5 depth = 4 # See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5 - check_round_trip(rand_circuit(nqubits, depth, rng, use_j=True, use_cz=True)) + check_round_trip(rand_circuit(nqubits, depth, rng, use_j=True, use_cz=True).transpile_j_to_rzh()) @pytest.mark.parametrize( @@ -70,9 +70,8 @@ def test_instruction_to_qasm3(instruction: Instruction) -> None: def test_j_to_qasm3() -> None: - circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)]) - check_circuit = _decompose_j_gates(circuit) + circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)]).transpile_j_to_rzh() qasm = circuit_to_qasm3(circuit) parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) - assert parsed_circuit.instruction == check_circuit.instruction + assert parsed_circuit.instruction == circuit.instruction diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index 15a83b971..aa4ba3101 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -332,6 +332,7 @@ def test_with_graphtrans(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> N pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() state = circuit.simulate_statevector().statevec tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", rng=fx_rng) @@ -351,6 +352,7 @@ def test_with_graphtrans_sequential(self, fx_bg: PCG64, jumps: int, fx_rng: Gene pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() state = circuit.simulate_statevector().statevec tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", graph_prep="sequential", rng=fx_rng) @@ -400,6 +402,7 @@ def test_evolve(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None: pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() + pattern = pattern.infer_pauli_measurements() pattern.perform_pauli_measurements() state = circuit.simulate_statevector().statevec tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", rng=fx_rng) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 3c2022f03..8339a6a93 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -49,6 +49,7 @@ class TestTranspilerUnitGates: def test_instruction_flow(self, fx_rng: Generator, instruction: InstructionTestCase) -> None: circuit = Circuit(3, instr=[instruction(fx_rng)]) pattern = circuit.transpile().pattern + circuit.transpile_to_cflow().flow.check_well_formed() flow = pattern.to_bloch().extract_causal_flow() flow.check_well_formed() @@ -63,16 +64,6 @@ def test_instructions(self, fx_bg: PCG64, jumps: int, instruction: InstructionTe state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) assert state_mbqc.isclose(state) - def test_simple(self) -> None: - rng = np.random.default_rng(420) - circuit = Circuit(3, instr=[instruction.CCX(0, (1, 2))]) - pattern = circuit.transpile().pattern - pattern.minimize_space() - input_state = rand_state_vector(3, rng=rng) - state = circuit.simulate_statevector(input_state=input_state).statevec - state_mbqc = pattern.simulate_pattern(input_state=input_state, rng=rng) - assert state_mbqc.isclose(state) - def test_transpiled(self, fx_rng: Generator) -> None: nqubits = 2 depth = 1 @@ -174,6 +165,21 @@ def test_transpile_swaps(self, fx_bg: PCG64, jumps: int) -> None: state2.psi = np.transpose(state2.psi, qubits) assert state.isclose(state2) + @pytest.mark.parametrize("jumps", range(1, 11)) + def test_transpile_j_to_rzh(self, fx_bg: PCG64, jumps: int) -> None: + rng = Generator(fx_bg.jumped(jumps)) + nqubits = 3 + depth = 2 + circuit = rand_circuit(nqubits, depth, rng, use_j=True, use_ccx=True, use_rzz=True) + circuit.j(0, 0.5) # Ensure that there is at least one J instruction + assert any(instr.kind == InstructionKind.J for instr in circuit.instruction) + circuit2 = circuit.transpile_j_to_rzh() + assert not any(instr.kind == InstructionKind.J for instr in circuit2.instruction) + state = circuit.simulate_statevector(rng=rng).statevec + state2 = circuit2.simulate_statevector(rng=rng).statevec + print(state.fidelity(state2)) + assert state.fidelity(state2) == pytest.approx(1) + @pytest.mark.parametrize("jumps", range(1, 11)) @pytest.mark.parametrize("axis", [Axis.X, Axis.Y, Axis.Z]) @pytest.mark.parametrize("outcome", [0, 1]) @@ -199,7 +205,7 @@ def test_transpile_swaps_with_measurements(self, fx_bg: PCG64, jumps: int, axis: state2.swap((0, 1)) assert state.isclose(state2) - def test_cz_ccx(self) -> None: + def test_cz_ccx(self, fx_rng: Generator) -> None: """Test case reported in issue #2. https://github.com/qat-inria/graphix-jcz-transpiler/issues/2 @@ -207,9 +213,9 @@ def test_cz_ccx(self) -> None: circuit = Circuit(width=3) circuit.cz(2, 0) circuit.ccx(0, 1, 2) - ref_state = circuit.simulate_statevector().statevec + ref_state = circuit.simulate_statevector(rng=fx_rng).statevec pattern = circuit.transpile().pattern - state = pattern.simulate_pattern() + state = pattern.simulate_pattern(rng=fx_rng) assert state.isclose(ref_state) def test_ccx_decomposition(self) -> None: @@ -223,12 +229,12 @@ def test_ccx_decomposition(self) -> None: state2 = circuit2.simulate_statevector().statevec assert state.isclose(state2) - def test_cnot_cz(self) -> None: + def test_cnot_cz(self, fx_rng: Generator) -> None: """Test regression about output node reordering.""" circuit = Circuit(width=3, instr=[instruction.CNOT(0, 1), instruction.CZ((0, 1))]) - state = circuit.simulate_statevector().statevec + state = circuit.simulate_statevector(rng=fx_rng).statevec pattern = circuit.transpile().pattern - state_mbqc = pattern.simulate_pattern() + state_mbqc = pattern.simulate_pattern(rng=fx_rng) assert state.isclose(state_mbqc) @pytest.mark.parametrize("jumps", range(1, 6)) From 7f70084138cc759e41d5377553fdae0eef8374b7 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Thu, 30 Apr 2026 16:38:34 +0200 Subject: [PATCH 43/63] added bool transpile to qasm export functions --- CHANGELOG.md | 3 +++ graphix/qasm3_exporter.py | 8 ++++++-- tests/test_qasm3_exporter_to_graphix_parser.py | 11 +++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6976e3d9e..68daa70e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replaced `Circuit.transpile()` with a new approach based decomposing circuits into J & CZ gates. - Added `Circuit.transpile_to_cflow()` to produce `CausalFlow` using the same decomposition. - Added `instruction.J` class. + - Added `Circuit.transpile_j_to_rzh()` method to prepare circuits with J gates for export to OpenQASM. - #476 Introduced new methods `OpenGraph.extract_circuit`, `CliffordMap.to_tableau` and new function `graphix.circ_ext.compilation.cm_berg_pass`. Circuit extraction can be done natively in Graphix. @@ -20,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- #484: Added `transpile` argument to `qasm3_exporter.circuit_to_qasm3` and `qasm3_exporter.circuit_to_qasm3_lines`, which defaults to true and applies `Circuit.transpile_j_to_rzh` and `Circuit.transpile_measurements_to_z_axis` methods. + - #479: Added new methods `OpenGraph.draw`, `PauliFlow.draw` and `XZCorrections.draw`. - #454, #481: New space minimization API that allows users to select or define custom heuristics. diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index e610f1ace..5ed1d2527 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -22,7 +22,7 @@ from graphix.transpiler import Circuit -def circuit_to_qasm3(circuit: Circuit) -> str: +def circuit_to_qasm3(circuit: Circuit, transpile: bool = True) -> str: """Export circuit instructions to OpenQASM 3.0 representation. Returns @@ -30,10 +30,12 @@ def circuit_to_qasm3(circuit: Circuit) -> str: str The OpenQASM 3.0 string representation of the circuit. """ + if transpile: + circuit = circuit.transpile_j_to_rzh().transpile_measurements_to_z_axis() return "\n".join(circuit_to_qasm3_lines(circuit)) -def circuit_to_qasm3_lines(circuit: Circuit) -> Iterator[str]: +def circuit_to_qasm3_lines(circuit: Circuit, transpile: bool = True) -> Iterator[str]: """Export circuit instructions to line-by-line OpenQASM 3.0 representation. Returns @@ -41,6 +43,8 @@ def circuit_to_qasm3_lines(circuit: Circuit) -> Iterator[str]: Iterator[str] The OpenQASM 3.0 lines that represent the circuit. """ + if transpile: + circuit = circuit.transpile_j_to_rzh().transpile_measurements_to_z_axis() if any(instr.kind == InstructionKind.J for instr in circuit.instruction): raise ValueError("J gates must be decomposed before QASM3 export using `Circuit.transpile_j_to_rzh`.") yield "OPENQASM 3;" diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index ec8461c9a..0fda75241 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -31,10 +31,9 @@ def check_round_trip(circuit: Circuit) -> None: qasm = circuit_to_qasm3(circuit) - check_circuit = circuit.transpile_j_to_rzh() parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) - assert parsed_circuit.instruction == check_circuit.instruction + assert parsed_circuit.instruction == circuit.instruction @pytest.mark.parametrize("jumps", range(1, 11)) @@ -43,7 +42,7 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: nqubits = 5 depth = 4 # See https://github.com/TeamGraphix/graphix-qasm-parser/pull/5 - check_round_trip(rand_circuit(nqubits, depth, rng, use_j=True, use_cz=True).transpile_j_to_rzh()) + check_round_trip(rand_circuit(nqubits, depth, rng, use_j=True, use_cz=True)) @pytest.mark.parametrize( @@ -63,6 +62,7 @@ def test_circuit_to_qasm3(fx_bg: PCG64, jumps: int) -> None: instruction.RX(target=0, angle=ANGLE_PI / 4), instruction.RY(target=0, angle=ANGLE_PI / 4), instruction.RZ(target=0, angle=ANGLE_PI / 4), + instruction.J(target=0, angle=ANGLE_PI / 4), ], ) def test_instruction_to_qasm3(instruction: Instruction) -> None: @@ -70,8 +70,11 @@ def test_instruction_to_qasm3(instruction: Instruction) -> None: def test_j_to_qasm3() -> None: - circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)]).transpile_j_to_rzh() + circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)]) qasm = circuit_to_qasm3(circuit) parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) assert parsed_circuit.instruction == circuit.instruction + with pytest.raises(ValueError): + circuit_to_qasm3(circuit, transpile=False) + From 490003f32934b750311d398fed00cdaff16604f4 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Thu, 30 Apr 2026 16:42:07 +0200 Subject: [PATCH 44/63] fix ruff --- tests/test_qasm3_exporter_to_graphix_parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index 0fda75241..a30ca7fc1 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -77,4 +77,3 @@ def test_j_to_qasm3() -> None: assert parsed_circuit.instruction == circuit.instruction with pytest.raises(ValueError): circuit_to_qasm3(circuit, transpile=False) - From 95c51678a976cfd2c19b2aec7582552eabc4279d Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Thu, 30 Apr 2026 17:04:01 +0200 Subject: [PATCH 45/63] fixing tests with transpile bool flag --- graphix/qasm3_exporter.py | 2 +- tests/test_qasm3_exporter_to_graphix_parser.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 5ed1d2527..cc711b8be 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -32,7 +32,7 @@ def circuit_to_qasm3(circuit: Circuit, transpile: bool = True) -> str: """ if transpile: circuit = circuit.transpile_j_to_rzh().transpile_measurements_to_z_axis() - return "\n".join(circuit_to_qasm3_lines(circuit)) + return "\n".join(circuit_to_qasm3_lines(circuit, transpile)) def circuit_to_qasm3_lines(circuit: Circuit, transpile: bool = True) -> Iterator[str]: diff --git a/tests/test_qasm3_exporter_to_graphix_parser.py b/tests/test_qasm3_exporter_to_graphix_parser.py index a30ca7fc1..53360b5c4 100644 --- a/tests/test_qasm3_exporter_to_graphix_parser.py +++ b/tests/test_qasm3_exporter_to_graphix_parser.py @@ -31,9 +31,10 @@ def check_round_trip(circuit: Circuit) -> None: qasm = circuit_to_qasm3(circuit) + check_circuit = circuit.transpile_j_to_rzh() parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) - assert parsed_circuit.instruction == circuit.instruction + assert parsed_circuit.instruction == check_circuit.instruction @pytest.mark.parametrize("jumps", range(1, 11)) @@ -74,6 +75,10 @@ def test_j_to_qasm3() -> None: qasm = circuit_to_qasm3(circuit) parser = OpenQASMParser() parsed_circuit = parser.parse_str(qasm) - assert parsed_circuit.instruction == circuit.instruction + assert parsed_circuit.instruction == circuit.transpile_j_to_rzh().instruction + + +def test_j_to_qasm3_failure() -> None: + circuit = Circuit(3, instr=[instruction.J(target=0, angle=ANGLE_PI / 4)]) with pytest.raises(ValueError): circuit_to_qasm3(circuit, transpile=False) From 6711e3a630a41f62c12530e5ea727eefec1f6eb6 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Thu, 30 Apr 2026 17:50:37 +0200 Subject: [PATCH 46/63] fixing merge conflicts --- graphix/instruction.py | 3 ++- graphix/transpiler.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/graphix/instruction.py b/graphix/instruction.py index e95c7cd25..86be7e347 100644 --- a/graphix/instruction.py +++ b/graphix/instruction.py @@ -327,6 +327,7 @@ class InstructionWithoutRZZ: RX: TypeAlias = RX RY: TypeAlias = RY RZ: TypeAlias = RZ + J: TypeAlias = J def __init__(self) -> None: raise TypeError("InstructionWithoutRZZ is a namespace, not a class.") @@ -348,5 +349,5 @@ def __init__(self) -> None: if TYPE_CHECKING: - InstructionTypeWithoutRZZ = CCX | CNOT | SWAP | CZ | H | S | X | Y | Z | I | M | RX | RY | RZ + InstructionTypeWithoutRZZ = CCX | CNOT | SWAP | CZ | H | S | X | Y | Z | I | M | RX | RY | RZ | J InstructionType = InstructionTypeWithoutRZZ | RZZ diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 84878243b..d3f40fab3 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -19,7 +19,7 @@ from graphix.branch_selector import BranchSelector, RandomBranchSelector from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers from graphix.fundamentals import ANGLE_PI, Axis -from graphix.instruction import Instruction, InstructionKind, InstructionVisitor +from graphix.instruction import InstructionKind, InstructionVisitor from graphix.measurements import BlochMeasurement, Measurement, Outcome, PauliMeasurement from graphix.opengraph import OpenGraph from graphix.ops import Ops @@ -31,9 +31,8 @@ from numpy.random import Generator - from graphix.command import CommandType from graphix.fundamentals import ParameterizedAngle - from graphix.instruction import InstructionType, InstructionTypeWithoutRZZ + from graphix.instruction import InstructionType from graphix.parameter import ExpressionOrFloat, Parameter from graphix.pattern import Pattern from graphix.sim import Data From ac1cc84f57a243e5b1d5e6dd81251037586dd128 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Mon, 8 Jun 2026 23:18:43 +0200 Subject: [PATCH 47/63] Integrate SWAP transpilation --- examples/ghz_with_tn.py | 3 +- examples/mbqc_vqe.py | 2 - graphix/transpiler.py | 108 ++++++++++++++++++++---- noxfile.py | 2 +- tests/test_optimization.py | 1 + tests/test_pattern.py | 1 + tests/test_remove_pauli_measurements.py | 1 + tests/test_transpiler.py | 15 ++-- uv.lock | 20 +++-- 9 files changed, 117 insertions(+), 36 deletions(-) diff --git a/examples/ghz_with_tn.py b/examples/ghz_with_tn.py index d02949d81..2834c4e0e 100644 --- a/examples/ghz_with_tn.py +++ b/examples/ghz_with_tn.py @@ -15,7 +15,6 @@ import networkx as nx from graphix import Circuit -from graphix.transpiler import transpile_swaps n = 50 print(f"{n}-qubit GHZ state generation") @@ -33,7 +32,7 @@ # %% # Transpile into pattern -pattern = transpile_swaps(circuit).circuit.transpile().pattern +pattern = circuit.transpile().pattern pattern.standardize() graph = pattern.extract_graph() diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index 7e2ff20b6..6f991bb20 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -33,7 +33,6 @@ from graphix import Circuit from graphix.parameter import Placeholder from graphix.simulator import PatternSimulator -from graphix.transpiler import transpile_swaps if TYPE_CHECKING: from collections.abc import Iterable @@ -90,7 +89,6 @@ def __init__(self, n_qubits: int, hamiltonian: npt.NDArray[np.float64]): # Function to build the MBQC pattern def build_mbqc_pattern(self, params: Iterable[ParameterizedAngle]) -> Pattern: circuit = build_vqe_circuit(self.n_qubits, params) - circuit = transpile_swaps(circuit).circuit pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() diff --git a/graphix/transpiler.py b/graphix/transpiler.py index d3f40fab3..26aa9c6bc 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -6,10 +6,13 @@ from __future__ import annotations +import enum from dataclasses import dataclass +from enum import Enum from typing import TYPE_CHECKING, Generic, SupportsFloat, TypeVar import networkx as nx +import numpy as np # assert_never introduced in Python 3.11 # override introduced in Python 3.12 @@ -28,9 +31,12 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence + from typing import Any + import numpy.typing as npt from numpy.random import Generator + from graphix.command import Node from graphix.fundamentals import ParameterizedAngle from graphix.instruction import InstructionType from graphix.parameter import ExpressionOrFloat, Parameter @@ -38,6 +44,8 @@ from graphix.sim import Data from graphix.sim.base_backend import Matrix + _ScalarT = TypeVar("_ScalarT", bound=np.generic[Any]) + _R = TypeVar("_R", bound="Pattern | CausalFlow[BlochMeasurement]") _CO = TypeVar("_CO", bound="tuple[int, ...] | dict[int, command.M]") @@ -493,7 +501,7 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) return TranspileResult(f, classical_outputs) - def transpile(self) -> TranspileResult[Pattern, tuple[int, ...]]: + def transpile(self, transpile_swaps: bool = True) -> TranspileResult[Pattern, tuple[int, ...]]: """Transpile a circuit via J-∧z decomposition to a pattern. Parameters @@ -504,7 +512,13 @@ def transpile(self) -> TranspileResult[Pattern, tuple[int, ...]]: ------- the result of the transpilation: a pattern and classical outputs. """ - return _transpile_cflow_to_pattern(self.transpile_to_cflow()) + if not transpile_swaps: + return _transpile_cflow_to_pattern(self.transpile_to_cflow()) + swap = _transpile_swaps(self) + result = _transpile_cflow_to_pattern(swap.circuit.transpile_to_cflow()) + result.pattern.reorder_output_nodes(swap.swap_output_nodes(result.pattern.output_nodes)) + classical_outputs = swap.swap_classical_outputs(result.classical_outputs) + return TranspileResult(result.pattern, classical_outputs) def simulate_statevector( self, @@ -909,24 +923,76 @@ class TranspileSwapsResult: circuit: Circuit """Circuit without SWAP gates.""" - qubits: tuple[int | None, ...] + outputs: tuple[OutputIndex, ...] """ Tuple which has the same width as the circuit and which for every qubit of the original circuit provides the index of the - corresponding qubit in the output of the returned - circuit. Measured qubits are mapped to ``None`` in this tuple. + corresponding qubit in the output of the swapped circuit + (either measured or not). + """ + + def extract_outputs(self, kind: OutputKind) -> tuple[int, ...]: + """Return the sequence of outputs of the given kind.""" + return tuple(output.index for output in self.outputs if output.kind == kind) + + def extract_output_node_indices(self) -> tuple[int, ...]: + """Return for each output node, sorted in the order of the original circuit, the index of the corresponding output node in the order of the swapped circuit.""" + reduced_index = {} + reduced_counter = 0 + for index, output in enumerate(self.outputs): + if output.kind == OutputKind.Qubit: + reduced_index[index] = reduced_counter + reduced_counter += 1 + return tuple(reduced_index[index] for index in self.extract_outputs(OutputKind.Qubit)) + + def swap_statevec(self, statevec: npt.NDArray[_ScalarT]) -> npt.NDArray[_ScalarT]: + """Reorder the elements of a statevector obtained from a swapped circuit to restore the qubit ordering of the original circuit.""" + return np.transpose(statevec, self.extract_output_node_indices()) + + def swap_output_nodes(self, output_nodes: Sequence[Node]) -> tuple[Node, ...]: + """Reorder the output nodes of a pattern obtained from a swapped circuit to restore the qubit ordering of the original circuit.""" + return tuple(output_nodes[index] for index in self.extract_output_node_indices()) + + def swap_classical_outputs(self, classical_outputs: Sequence[Node]) -> tuple[int, ...]: + """Reorder the classical outpus of a pattern obtained from a swapped circuit to restore the output ordering of the original circuit.""" + return tuple(classical_outputs[index] for index in self.extract_outputs(OutputKind.Bit)) + + +class OutputKind(Enum): + """Specify whether a qubit is measured or not.""" + + Qubit = enum.auto() + Bit = enum.auto() + + +@dataclass(frozen=True) +class OutputIndex: + """Index of a swapped qubit. + + If the qubit is measured, ``kind`` equals to `OutputKind.Bit` and + ``index`` is the index of the measurement. + + If the qubit is not measured, ``kind`` equals to `OutputKind.qubit` + and ``index`` is the index of the qubit in the swapped circuit. """ + kind: OutputKind + index: int + class _TranspileSwapVisitor(InstructionVisitor): - qubits: list[int | None] + outputs: list[OutputIndex] def __init__(self, width: int) -> None: - self.qubits = list(range(width)) + self.outputs = [OutputIndex(OutputKind.Qubit, index) for index in range(width)] @override def visit_qubit(self, qubit: int) -> int: - return _check_target(self.qubits, qubit) + target = self.outputs[qubit] + if target.kind == OutputKind.Bit: + msg = f"Qubit {qubit} has already been measured." + raise ValueError(msg) + return target.index def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: @@ -941,24 +1007,32 @@ def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: ------- TranspileSwapsResult The field ``circuit`` contains an equivalent circuit without - SWAP gates. - The field ``qubits`` contains a tuple which has the same width - as the circuit and which for every qubit of the original - circuit provides the index of the corresponding qubit in the - output of the returned circuit. Measured qubits are mapped to - ``None`` in this tuple. + SWAP gates. The field ``outputs`` contains a tuple which has + the same width as the circuit. For every qubit of the original + circuit, either the qubit is not measured, and ``outputs`` + provides the index of the corresponding qubit in the output of + the returned circuit; or the qubit has been measured, and + ``outputs`` provides the index of the measurement. """ new_circuit = Circuit(circuit.width) visitor = _TranspileSwapVisitor(circuit.width) + measurement_index = 0 for instr in circuit.instruction: if instr.kind == InstructionKind.SWAP: u, v = instr.targets - visitor.qubits[u], visitor.qubits[v] = visitor.qubits[v], visitor.qubits[u] + # We apply the visitor to check that the qubits have not been measured. + visitor.visit_qubit(u) + visitor.visit_qubit(v) + visitor.outputs[u], visitor.outputs[v] = visitor.outputs[v], visitor.outputs[u] else: new_circuit.add(instr.visit(visitor)) if instr.kind == InstructionKind.M: - visitor.qubits[instr.target] = None - return TranspileSwapsResult(new_circuit, tuple(visitor.qubits)) + visitor.outputs[instr.target] = OutputIndex(OutputKind.Bit, measurement_index) + measurement_index += 1 + return TranspileSwapsResult(new_circuit, tuple(visitor.outputs)) + + +_transpile_swaps = transpile_swaps def _transpile_cflow_to_pattern( diff --git a/noxfile.py b/noxfile.py index 67ba00006..e2ff43b64 100644 --- a/noxfile.py +++ b/noxfile.py @@ -117,7 +117,7 @@ class ReverseDependency: "https://github.com/thierry-martinez/veriphix", doctest_modules=False, install_target=".[dev]", - branch="fix/graphix_498_remove_pauli", + branch="fix_reorder_and_refresh", ), ReverseDependency("https://github.com/TeamGraphix/graphix-ibmq", doctest_modules=False), ReverseDependency("https://github.com/qat-inria/graphix-stim-compiler"), diff --git a/tests/test_optimization.py b/tests/test_optimization.py index c0af39277..9d2e1305d 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -57,6 +57,7 @@ def test_standardize_clifford_entanglement(fx_rng: Generator) -> None: assert state_p.isclose(state_ref) +@pytest.mark.parametrize("jumps", range(1, 11)) def test_flow_after_pauli_preprocessing(fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) nqubits = 3 diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 1b407cf90..5c6696b12 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -675,6 +675,7 @@ def test_compose_7(self, fx_rng: Generator) -> None: circuit_1.h(0) circuit_1.rz(0, alpha) p1 = circuit_1.transpile().pattern + p1.remove_input_nodes() p1 = p1.infer_pauli_measurements() p1.remove_pauli_measurements() diff --git a/tests/test_remove_pauli_measurements.py b/tests/test_remove_pauli_measurements.py index b4729c488..10870ab36 100644 --- a/tests/test_remove_pauli_measurements.py +++ b/tests/test_remove_pauli_measurements.py @@ -257,6 +257,7 @@ def test_pattern_remove_pauli_measurements() -> None: circuit = Circuit(2) circuit.cnot(0, 1) pattern = circuit.transpile().pattern + pattern = pattern.infer_pauli_measurements() pattern2 = pattern.remove_pauli_measurements(copy=True) assert all_bloch_measurement_or_input_node( pattern2.input_nodes, (cmd for cmd in pattern2 if isinstance(cmd, Command.M)) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 061fbf2b4..75f62da0b 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -13,7 +13,7 @@ from graphix.random_objects import rand_circuit, rand_gate, rand_state_vector from graphix.simulator import DefaultMeasureMethod from graphix.states import BasicStates -from graphix.transpiler import Circuit, decompose_ccx, transpile_swaps +from graphix.transpiler import Circuit, OutputIndex, OutputKind, decompose_ccx, transpile_swaps from tests.test_branch_selector import CheckedBranchSelector if TYPE_CHECKING: @@ -158,11 +158,7 @@ def test_transpile_swaps(self, fx_bg: PCG64, jumps: int) -> None: assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) state = circuit.simulate_statevector(rng=rng).statevec state2 = circuit2.simulate_statevector(rng=rng).statevec - qubits: list[int] = [] - for qubit in transpiled_swaps.qubits: - assert qubit is not None - qubits.append(qubit) - state2.psi = np.transpose(state2.psi, qubits) + state2.psi = transpiled_swaps.swap_statevec(state2.psi) assert state.isclose(state2) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -177,7 +173,6 @@ def test_transpile_j_to_rzh(self, fx_bg: PCG64, jumps: int) -> None: assert not any(instr.kind == InstructionKind.J for instr in circuit2.instruction) state = circuit.simulate_statevector(rng=rng).statevec state2 = circuit2.simulate_statevector(rng=rng).statevec - print(state.fidelity(state2)) assert state.fidelity(state2) == pytest.approx(1) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -201,7 +196,11 @@ def test_transpile_swaps_with_measurements(self, fx_bg: PCG64, jumps: int, axis: state2 = circuit2.simulate_statevector( rng=rng, input_state=input_state, branch_selector=branch_selector ).statevec - assert transpiled_swaps.qubits == (2, None, 1) + assert transpiled_swaps.outputs == ( + OutputIndex(OutputKind.Qubit, 2), + OutputIndex(OutputKind.Bit, 0), + OutputIndex(OutputKind.Qubit, 1), + ) state2.swap((0, 1)) assert state.isclose(state2) diff --git a/uv.lock b/uv.lock index 5147219c2..e7a8fc1a8 100644 --- a/uv.lock +++ b/uv.lock @@ -918,6 +918,7 @@ wheels = [ name = "graphix" source = { editable = "." } dependencies = [ + { name = "idna" }, { name = "matplotlib" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -929,6 +930,7 @@ dependencies = [ { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sympy" }, { name = "typing-extensions" }, + { name = "urllib3" }, ] [package.optional-dependencies] @@ -966,18 +968,22 @@ extra = [ { name = "pyzx" }, ] typing = [ + { name = "mypy" }, { name = "numba", marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "types-setuptools" }, ] [package.metadata] requires-dist = [ { name = "furo", marker = "extra == 'doc'" }, + { name = "idna", specifier = ">=3.15" }, { name = "joblib", marker = "extra == 'doc'" }, { name = "matplotlib" }, { name = "matplotlib", marker = "extra == 'doc'" }, { name = "mypy", marker = "extra == 'dev'", specifier = "==2.1.0" }, + { name = "mypy", marker = "extra == 'typing'", specifier = ">=2.1.0" }, { name = "networkx" }, { name = "nox", marker = "extra == 'dev'", specifier = "==2026.4.10" }, { name = "numba", marker = "python_full_version >= '3.11' and extra == 'typing'", specifier = "==0.65.1" }, @@ -1006,7 +1012,9 @@ requires-dist = [ { name = "types-networkx", marker = "extra == 'dev'", specifier = "==3.6.1.20260518" }, { name = "types-psutil", marker = "extra == 'dev'" }, { name = "types-setuptools", marker = "extra == 'dev'" }, + { name = "types-setuptools", marker = "extra == 'typing'", specifier = ">=82.0.0.20260518" }, { name = "typing-extensions" }, + { name = "urllib3", specifier = ">=2.7.0" }, ] provides-extras = ["dev", "doc", "extra", "typing"] @@ -1030,11 +1038,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" }, ] [[package]] @@ -3195,11 +3203,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] From f2f2da443cdf15a3d147531081b2008e1b944308 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Tue, 9 Jun 2026 08:05:00 +0200 Subject: [PATCH 48/63] Pin graphix-stim-backend --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index e2ff43b64..498cc9d84 100644 --- a/noxfile.py +++ b/noxfile.py @@ -110,7 +110,7 @@ class ReverseDependency: @nox.parametrize( "package", [ - ReverseDependency("https://github.com/thierry-martinez/graphix-stim-backend"), + ReverseDependency("https://github.com/thierry-martinez/graphix-stim-backend", branch="fix/add_jcz"), ReverseDependency("https://github.com/TeamGraphix/graphix-symbolic"), ReverseDependency("https://github.com/TeamGraphix/graphix-qasm-parser"), ReverseDependency( From c3e5cfc3bfb6f9b8105f5106b075d86a7654f1f2 Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Thu, 11 Jun 2026 22:48:42 +0200 Subject: [PATCH 49/63] Update graphix/transpiler.py Co-authored-by: matulni --- graphix/transpiler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 33b0ef18f..17f1bbaf2 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -501,8 +501,7 @@ def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], di ) z_corrections: dict[int, set[int]] = {} for node, correctors in x_corrections.items(): - (corrector,) = correctors - z_targets = set(graph.neighbors(corrector)) - {node} + z_targets = og.neighbors(correctors) - {node} if z_targets: z_corrections[node] = z_targets partial_order_layers = _corrections_to_partial_order_layers(og, x_corrections, z_corrections) From 094c159a703fda9e9d5f90dca1e6ecfdf973ef71 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Thu, 11 Jun 2026 22:51:06 +0200 Subject: [PATCH 50/63] Rename `transpile_to_causal_flow` --- CHANGELOG.md | 2 +- graphix/transpiler.py | 6 +++--- tests/test_transpiler.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 743070bf5..e21c4fdb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #484: J & CZ transpilation. - Replaced `Circuit.transpile()` with a new approach based decomposing circuits into J & CZ gates. - - Added `Circuit.transpile_to_cflow()` to produce `CausalFlow` using the same decomposition. + - Added `Circuit.transpile_to_causal_flow()` to produce `CausalFlow` using the same decomposition. - Added `instruction.J` class. - Added `Circuit.transpile_j_to_rzh()` method to prepare circuits with J gates for export to OpenQASM. - Added `transpile` argument to `qasm3_exporter.circuit_to_qasm3` and `qasm3_exporter.circuit_to_qasm3_lines`, which defaults to true and applies `Circuit.transpile_j_to_rzh` and `Circuit.transpile_measurements_to_z_axis` methods. diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 17f1bbaf2..5efbc05ce 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -436,7 +436,7 @@ def m(self, qubit: int, axis: Axis) -> None: self.instruction.append(instruction.M(target=qubit, axis=axis)) self.active_qubits.remove(qubit) - def transpile_to_cflow(self) -> TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]]: + def transpile_to_causal_flow(self) -> TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]]: """Transpile a circuit via J-∧z decomposition to a causal flow. Parameters @@ -520,9 +520,9 @@ def transpile(self, transpile_swaps: bool = True) -> TranspileResult[Pattern, tu the result of the transpilation: a pattern and classical outputs. """ if not transpile_swaps: - return _transpile_cflow_to_pattern(self.transpile_to_cflow()) + return _transpile_cflow_to_pattern(self.transpile_to_causal_flow()) swap = _transpile_swaps(self) - result = _transpile_cflow_to_pattern(swap.circuit.transpile_to_cflow()) + result = _transpile_cflow_to_pattern(swap.circuit.transpile_to_causal_flow()) result.pattern.reorder_output_nodes(swap.swap_output_nodes(result.pattern.output_nodes)) classical_outputs = swap.swap_classical_outputs(result.classical_outputs) return TranspileResult(result.pattern, classical_outputs) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 062d3c403..0c9311058 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -53,7 +53,7 @@ class TestTranspilerUnitGates: def test_instruction_flow(self, fx_rng: Generator, instruction: InstructionTestCase) -> None: circuit = Circuit(3, instr=[instruction(fx_rng)]) pattern = circuit.transpile().pattern - circuit.transpile_to_cflow().flow.check_well_formed() + circuit.transpile_to_causal_flow().flow.check_well_formed() flow = pattern.to_bloch().extract_causal_flow() flow.check_well_formed() From e067d7ac65888de4055fcd691354162c9e3af93f Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Thu, 11 Jun 2026 22:54:20 +0200 Subject: [PATCH 51/63] Update graphix/transpiler.py Co-authored-by: matulni --- graphix/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 5efbc05ce..e1250513d 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -1091,7 +1091,7 @@ class IllformedCircuitError(Exception): def __init__(self) -> None: """Build the exception.""" - super().__init__("Ill-formed pattern") + super().__init__("Ill-formed circuit") @overload From 3c9abb4a84292ba6013e15c19946cc66adb96caa Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 13 Jun 2026 08:16:35 +0200 Subject: [PATCH 52/63] Don't check for J gates before QASM3 transpilation --- graphix/qasm3_exporter.py | 42 ++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/graphix/qasm3_exporter.py b/graphix/qasm3_exporter.py index 93ac0ba8c..19d152f64 100644 --- a/graphix/qasm3_exporter.py +++ b/graphix/qasm3_exporter.py @@ -22,31 +22,47 @@ from graphix.instruction import InstructionType -def circuit_to_qasm3(circuit: Circuit, transpile: bool = True) -> str: - """Export circuit instructions to OpenQASM 3.0 representation. +def circuit_to_qasm3(circuit: Circuit, *, transpile: bool = True) -> str: + """Export circuit instructions to an OpenQASM 3.0 string. + + Parameters + ---------- + circuit: Circuit + The quantum circuit to be exported. + + transpile: bool, optional + If ``True`` (the default), the circuit is first transpiled so that any J gates + are replaced by equivalent RZ and H gates. + If ``False``, a ``ValueError`` is raised when the circuit contains a J gate. Returns ------- str - The OpenQASM 3.0 string representation of the circuit. + The OpenQASM 3.0 representation of the supplied circuit. """ - if transpile: - circuit = circuit.transpile_j_to_rzh().transpile_measurements_to_z_axis() - return "\n".join(circuit_to_qasm3_lines(circuit, transpile)) + return "\n".join(circuit_to_qasm3_lines(circuit, transpile=transpile)) -def circuit_to_qasm3_lines(circuit: Circuit, transpile: bool = True) -> Iterator[str]: +def circuit_to_qasm3_lines(circuit: Circuit, *, transpile: bool = True) -> Iterator[str]: """Export circuit instructions to line-by-line OpenQASM 3.0 representation. + Parameters + ---------- + circuit: Circuit + The quantum circuit to be exported. + + transpile: bool, optional + If ``True`` (the default), the circuit is first transpiled so that any J gates + are replaced by equivalent RZ and H gates. + If ``False``, a ``ValueError`` is raised when the circuit contains a J gate. + Returns ------- Iterator[str] - The OpenQASM 3.0 lines that represent the circuit. + An iterator over the OpenQASM 3.0 lines that represent the circuit. """ if transpile: circuit = circuit.transpile_j_to_rzh().transpile_measurements_to_z_axis() - if any(instr.kind == InstructionKind.J for instr in circuit.instruction): - raise ValueError("J gates must be decomposed before QASM3 export using `Circuit.transpile_j_to_rzh`.") yield "OPENQASM 3;" yield 'include "stdgates.inc";' yield f"qubit[{circuit.width}] q;" @@ -99,7 +115,7 @@ def instruction_to_qasm3(instruction: InstructionType) -> str: case InstructionKind.M: if instruction.axis != Axis.Z: raise ValueError( - "OpenQASM3 only supports measurements on Z axis. Use `Circuit.transpile_measurements_to_z_axis` to rewrite measurements on X and Y axes." + "OpenQASM3 only supports measurements on Z axis. Use `Circuit.transpile_measurements_to_z_axis` to rewrite measurements on X and Y axes, or setting `transpile=True`." ) return f"b[{instruction.target}] = measure q[{instruction.target}]" case InstructionKind.RX | InstructionKind.RY | InstructionKind.RZ: @@ -108,7 +124,9 @@ def instruction_to_qasm3(instruction: InstructionType) -> str: instruction.kind.name.lower(), args=[angle], operands=[qasm3_qubit(instruction.target)] ) case InstructionKind.J: - raise ValueError("J gate should have been removed by `_decompose_j_gates`.") + raise ValueError( + "J gates must be decomposed before QASM3 export using `Circuit.transpile_j_to_rzh`, or setting `transpile=True`." + ) case InstructionKind.H | InstructionKind.S | InstructionKind.X | InstructionKind.Y | InstructionKind.Z: return qasm3_gate_call(instruction.kind.name.lower(), [qasm3_qubit(instruction.target)]) case InstructionKind.I: From 50b0d9cfc034cc2aefdeaef0320e19f7433f5014 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 13 Jun 2026 08:18:30 +0200 Subject: [PATCH 53/63] Add `permute` to `DenseState` to replace `sort_qubits` and `swap_statevec` --- graphix/sim/base_backend.py | 25 ++++++++++++++----------- graphix/sim/density_matrix.py | 9 +++++++++ graphix/sim/statevec.py | 4 ++++ graphix/transpiler.py | 6 ------ tests/test_density_matrix.py | 15 +++++++++++++++ tests/test_statevec.py | 29 ++++++++++++++++++++++++++++- tests/test_transpiler.py | 4 ++-- 7 files changed, 72 insertions(+), 20 deletions(-) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index ef1977b3c..855e2df6b 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -443,6 +443,17 @@ def swap(self, qubits: tuple[int, int]) -> None: (control, target) qubit indices """ + @abstractmethod + def permute(self, permutation: Sequence[int]) -> None: + """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. + """ + def apply_noise(self, qubits: Sequence[int], noise: Noise) -> None: # noqa: ARG002 """Apply noise. @@ -659,7 +670,7 @@ def entangle_nodes(self, edge: tuple[int, int]) -> None: """ @abstractmethod - def finalize(self, output_nodes: Iterable[int]) -> None: + def finalize(self, output_nodes: Sequence[int]) -> None: """To be run at the end of pattern simulation to convey the order of output nodes.""" @abstractmethod @@ -818,18 +829,10 @@ def apply_clifford(self, node: int, clifford: Clifford) -> None: loc = self.node_index.index(node) self.state.evolve_single(clifford.matrix, loc) - def sort_qubits(self, output_nodes: Iterable[int]) -> None: - """Sort the qubit order in internal statevector.""" - for i, ind in enumerate(output_nodes): - if self.node_index.index(ind) != i: - move_from = self.node_index.index(ind) - self.state.swap((i, move_from)) - self.node_index.swap(i, move_from) - @override - def finalize(self, output_nodes: Iterable[int]) -> None: + def finalize(self, output_nodes: Sequence[int]) -> None: """To be run at the end of pattern simulation.""" - self.sort_qubits(output_nodes) + self.state.permute([self.node_index.index(node) for node in output_nodes]) @property def nqubit(self) -> int: diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index dd37403b7..06b484123 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -285,6 +285,15 @@ def swap(self, qubits: tuple[int, int]) -> None: """ self.evolve(SWAP_TENSOR.reshape(4, 4), qubits) + @override + def permute(self, permutation: Sequence[int]) -> None: + tensor_shape = [2] * (2 * self.nqubit) + perm_cols = [i + self.nqubit for i in permutation] + full_permutation = [*permutation, *perm_cols] + rho_tensor = self.rho.reshape(tensor_shape) + rho_permuted_tensor = np.transpose(rho_tensor, axes=full_permutation) + self.rho = rho_permuted_tensor.reshape((2**self.nqubit, 2**self.nqubit)) + def entangle(self, edge: tuple[int, int]) -> None: """Connect graph nodes. diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index efb24bfe1..7ac17a739 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -333,6 +333,10 @@ def swap(self, qubits: tuple[int, int]) -> None: # sort back axes self.psi = np.moveaxis(psi, (0, 1), qubits) + @override + def permute(self, permutation: Sequence[int]) -> None: + self.psi = np.transpose(self.psi, permutation) + def normalize(self) -> None: """Normalize the state in-place.""" # Note that the following calls to `astype` are guaranteed to diff --git a/graphix/transpiler.py b/graphix/transpiler.py index e1250513d..e42091f1e 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING, Generic, SupportsFloat, TypeVar, overload import networkx as nx -import numpy as np # assert_never introduced in Python 3.11 # override introduced in Python 3.12 @@ -987,11 +986,6 @@ def extract_output_node_indices(self) -> tuple[int, ...]: reduced_counter += 1 return tuple(reduced_index[index] for index in self.extract_outputs(OutputKind.Qubit)) - def swap_statevec(self, statevec: Statevec) -> Statevec: - """Reorder the elements of a statevector obtained from a swapped circuit to restore the qubit ordering of the original circuit.""" - psi = np.transpose(statevec.psi, self.extract_output_node_indices()) - return Statevec(psi.flatten(), statevec.nqubit) - def swap_output_nodes(self, output_nodes: Sequence[Node]) -> tuple[Node, ...]: """Reorder the output nodes of a pattern obtained from a swapped circuit to restore the qubit ordering of the original circuit.""" return tuple(output_nodes[index] for index in self.extract_output_node_indices()) diff --git a/tests/test_density_matrix.py b/tests/test_density_matrix.py index 7c67909c5..ad6f3016e 100644 --- a/tests/test_density_matrix.py +++ b/tests/test_density_matrix.py @@ -2,6 +2,7 @@ import copy import functools +import itertools import random from typing import TYPE_CHECKING @@ -15,13 +16,17 @@ from graphix.channels import KrausChannel, dephasing_channel, depolarising_channel from graphix.fundamentals import ANGLE_PI, Plane from graphix.ops import Ops +from graphix.random_objects import rand_state_vector from graphix.sim.density_matrix import DensityMatrix, DensityMatrixBackend from graphix.sim.statevec import CNOT_TENSOR, CZ_TENSOR, SWAP_TENSOR, Statevec from graphix.simulator import DefaultMeasureMethod from graphix.states import BasicStates, PlanarState from graphix.transpiler import Circuit +from tests.test_statevec import permute_with_swap if TYPE_CHECKING: + from collections.abc import Sequence + from numpy.random import Generator from graphix.measurements import Outcome @@ -929,3 +934,13 @@ def test_measure(self, outcome: Outcome) -> None: else np.kron(np.array([[1, 0], [0, 0]]), np.ones((2, 2)) / 2) ) assert np.allclose(backend.state.rho, expected_matrix) + + +@pytest.mark.parametrize("permutation", itertools.permutations(range(3))) +def test_permute(fx_rng: Generator, permutation: Sequence[int]) -> None: + nqubits = len(permutation) + dm = DensityMatrix(rand_state_vector(nqubits, fx_rng)) + dm_ref = copy.copy(dm) + dm.permute(permutation) + permute_with_swap(dm_ref, permutation) + assert np.array_equal(dm.rho, dm_ref.rho) diff --git a/tests/test_statevec.py b/tests/test_statevec.py index e7ce8d925..92448df14 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -1,6 +1,8 @@ from __future__ import annotations +import copy import functools +import itertools from typing import TYPE_CHECKING import numpy as np @@ -8,15 +10,19 @@ from graphix.fundamentals import ANGLE_PI, Plane from graphix.pattern import Pattern +from graphix.random_objects import rand_state_vector +from graphix.sim.base_backend import NodeIndex from graphix.sim.statevec import Statevec, _norm_numeric from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: - from collections.abc import Mapping + from collections.abc import Mapping, Sequence from typing import Literal from numpy.random import Generator + from graphix.sim.base_backend import DenseState + _ENCODING = Literal["LSB", "MSB"] @@ -243,3 +249,24 @@ def test_normalize() -> None: statevec = Statevec(nqubit=1, data=BasicStates.PLUS) statevec.remove_qubit(0) assert _norm_numeric(statevec.psi.astype(np.complex128, copy=False)) == 1 + + +def permute_with_swap(dense_state: DenseState, permutation: Sequence[int]) -> None: + nqubits = len(permutation) + node_index = NodeIndex() + node_index.extend(range(nqubits)) + for i, ind in enumerate(permutation): + if node_index.index(ind) != i: + move_from = node_index.index(ind) + dense_state.swap((i, move_from)) + node_index.swap(i, move_from) + + +@pytest.mark.parametrize("permutation", itertools.permutations(range(3))) +def test_permute(fx_rng: Generator, permutation: Sequence[int]) -> None: + nqubits = len(permutation) + statevec = Statevec(rand_state_vector(nqubits, fx_rng)) + statevec_ref = copy.copy(statevec) + statevec.permute(permutation) + permute_with_swap(statevec_ref, permutation) + assert np.array_equal(statevec.psi, statevec_ref.psi) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 0c9311058..eaf579c72 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -172,7 +172,7 @@ def test_transpile_swaps(self, fx_bg: PCG64, jumps: int) -> None: assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) state = circuit.simulate_statevector(rng=rng).statevec state2 = circuit2.simulate_statevector(rng=rng).statevec - state2 = transpiled_swaps.swap_statevec(state2) + state2.permute(transpiled_swaps.extract_output_node_indices()) assert state.isclose(state2) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -372,7 +372,7 @@ def test_transpile_swaps(fx_bg: PCG64, jumps: int) -> None: assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) state = circuit.simulate_statevector(rng=rng).statevec state2 = circuit2.simulate_statevector(rng=rng).statevec - state2 = transpiled_swaps.swap_statevec(state2) + state2.permute(transpiled_swaps.extract_output_node_indices()) assert state.isclose(state2) From f13174282f6053e226efdf36e2f4f3bbfd64e31d Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 13 Jun 2026 08:19:09 +0200 Subject: [PATCH 54/63] Remove duplicated `infer_pauli_measurements` --- tests/test_pattern.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index e90e746dc..f672f1ce6 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -1048,8 +1048,6 @@ def test_extract_xzc_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: xzc.check_well_formed() p_test = xzc.to_pattern() - p_test = p_test.infer_pauli_measurements() - p_ref = p_ref.infer_pauli_measurements() p_ref.remove_pauli_measurements() p_test = p_test.infer_pauli_measurements() From dfa8c671520b4848a8d6b5e7deca2a6755137e6c Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 13 Jun 2026 08:19:43 +0200 Subject: [PATCH 55/63] Split `TranspileResult` into `TranspiledFlow` and `TranspiledPattern` --- graphix/transpiler.py | 64 ++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index e42091f1e..7c26ca448 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -51,32 +51,31 @@ _DenseStateT = TypeVar("_DenseStateT", bound="DenseState") -_R = TypeVar("_R", bound="Pattern | CausalFlow[BlochMeasurement]") -_CO = TypeVar("_CO", bound="tuple[int, ...] | dict[int, command.M]") +@dataclass(frozen=True, slots=True) +class TranspiledPattern: + """A transpiled pattern.""" -@dataclass -class TranspileResult(Generic[_R, _CO]): - """ - The result of a transpilation. + pattern: Pattern - result : :class:`graphix.pattern.Pattern` or :class:`graphix.flow.core.CausalFlow` object - classical_outputs : tuple[int, ...] | dict[int, command.M], index of nodes measured with *M* gates, with associated M commands as dictionary. + classical_outputs: tuple[Node, ...] + """Nodes measured with circuit measurements, in the order of the gates.""" - """ - result: _R - classical_outputs: _CO +@dataclass(frozen=True, slots=True) +class TranspiledFlow: + """A transpiled causal flow.""" + + flow: CausalFlow[BlochMeasurement] - @property - def pattern(self: TranspileResult[Pattern, _CO]) -> Pattern: - """Return pattern from `TranspileResult` if any.""" - return self.result + classical_outputs: dict[int, command.M] + """M commands for nodes measured with circuit measurements.""" - @property - def flow(self: TranspileResult[CausalFlow[BlochMeasurement], _CO]) -> CausalFlow[BlochMeasurement]: - """Return causal flow from TranspileResult if any.""" - return self.result + def to_pattern(self) -> TranspiledPattern: + """Return the transpiled pattern.""" + pattern = StandardizedPattern.from_pattern(self.flow.to_corrections().to_pattern()).to_space_optimal_pattern() + pattern.extend(self.classical_outputs.values()) + return TranspiledPattern(pattern, tuple(self.classical_outputs.keys())) @dataclass(frozen=True) @@ -435,7 +434,7 @@ def m(self, qubit: int, axis: Axis) -> None: self.instruction.append(instruction.M(target=qubit, axis=axis)) self.active_qubits.remove(qubit) - def transpile_to_causal_flow(self) -> TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]]: + def transpile_to_causal_flow(self) -> TranspiledFlow: """Transpile a circuit via J-∧z decomposition to a causal flow. Parameters @@ -505,26 +504,29 @@ def transpile_to_causal_flow(self) -> TranspileResult[CausalFlow[BlochMeasuremen z_corrections[node] = z_targets partial_order_layers = _corrections_to_partial_order_layers(og, x_corrections, z_corrections) f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) - return TranspileResult(f, classical_outputs) + return TranspiledFlow(f, classical_outputs) - def transpile(self, transpile_swaps: bool = True) -> TranspileResult[Pattern, tuple[int, ...]]: + def transpile(self, *, transpile_swaps: bool = True) -> TranspiledPattern: """Transpile a circuit via J-∧z decomposition to a pattern. Parameters ---------- - self: the circuit to transpile. + 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. + TranspiledPattern + The result of the transpilation: a pattern and classical outputs. """ if not transpile_swaps: - return _transpile_cflow_to_pattern(self.transpile_to_causal_flow()) + return self.transpile_to_causal_flow().to_pattern() swap = _transpile_swaps(self) - result = _transpile_cflow_to_pattern(swap.circuit.transpile_to_causal_flow()) + result = swap.circuit.transpile_to_causal_flow().to_pattern() result.pattern.reorder_output_nodes(swap.swap_output_nodes(result.pattern.output_nodes)) classical_outputs = swap.swap_classical_outputs(result.classical_outputs) - return TranspileResult(result.pattern, classical_outputs) + return TranspiledPattern(result.pattern, classical_outputs) @overload def simulate_statevector( @@ -1072,14 +1074,6 @@ def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: _transpile_swaps = transpile_swaps -def _transpile_cflow_to_pattern( - tr: TranspileResult[CausalFlow[BlochMeasurement], dict[int, command.M]], -) -> TranspileResult[Pattern, tuple[int, ...]]: - pattern = StandardizedPattern.from_pattern(tr.flow.to_corrections().to_pattern()).to_space_optimal_pattern() - pattern.extend(tr.classical_outputs.values()) - return TranspileResult(pattern, tuple(tr.classical_outputs.keys())) - - class IllformedCircuitError(Exception): """Raised if the circuit is ill-formed.""" From 8ebf3dc831d3294c3a535830afd18ef158bdc599 Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Wed, 10 Jun 2026 14:04:57 +0200 Subject: [PATCH 56/63] Take `enable-cache` and `python-version` into account when setting up `uv` in CI (#516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `setup-uv` composite action ignored the `enable-cache` and `python-version` inputs. Cache was still enabled by default, which is the intended behaviour, but the system Python version was used instead of the one specified. As a result, type‑checking and coverage ran with Python 3.12 rather than the requested Python 3.14. This commit fixes the `setup-uv` composite action so that it respects both the `enable-cache` and `python-version` inputs. The use of Python 3.12 for type-checking was noticed during the review of #512 (see comment https://github.com/TeamGraphix/graphix/pull/512#discussion_r3302942519). In the type-checking workflow, `python-version` was set to 3.13 (and was ignored) with an outdated comment about Qiskit/qiskit-aer#2378, which is now closed (see comment https://github.com/thierry-martinez/graphix-pyzx/pull/1#discussion_r3309462656). The `python-version` field has been updated to 3.14. --- .github/actions/setup-uv/action.yml | 20 ++++++++++++++++++-- .github/workflows/typecheck.yml | 6 ++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-uv/action.yml b/.github/actions/setup-uv/action.yml index 309716e3d..078bccb5a 100644 --- a/.github/actions/setup-uv/action.yml +++ b/.github/actions/setup-uv/action.yml @@ -1,6 +1,14 @@ name: Set up uv description: Install the pinned version of uv and cache its package cache - +inputs: + enable-cache: + description: Enable cache + required: false + type: boolean + python-version: + description: Python version + required: false + type: string runs: using: composite steps: @@ -8,10 +16,18 @@ runs: shell: bash run: pip install -r .github/uv-requirements.txt + - name: Pin Python version ${{ inputs.python-version }} + if: ${{ inputs.python-version != '' }} + shell: bash + run: uv python pin "$PYTHON_VERSION" + env: + PYTHON_VERSION: ${{ inputs.python-version }} + - name: Cache uv packages + if: ${{ inputs.enable-cache == 'true' }} uses: actions/cache@v4 with: path: ~/.cache/uv key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} restore-keys: | - uv-${{ runner.os }}- \ No newline at end of file + uv-${{ runner.os }}- diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index a40d377cd..f80fc2f95 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -12,9 +12,7 @@ concurrency: cancel-in-progress: true env: - # We stick to Python 3.13 since qiskit-aer is not available yet for Python 3.14. - # See https://github.com/Qiskit/qiskit-aer/issues/2378. - python-version: "3.13" + python-version: "3.14" jobs: mypy-pyright: @@ -67,4 +65,4 @@ jobs: run: | cd ${{ runner.temp }} echo "from graphix import Pattern" > test_graphix_type.py - uv run --project ${{ github.workspace }} --no-sync mypy test_graphix_type.py \ No newline at end of file + uv run --project ${{ github.workspace }} --no-sync mypy test_graphix_type.py From 6ee82865e34a34989dd49cdd88845206406acea3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 10:02:34 +0200 Subject: [PATCH 57/63] Bump ruff from 0.15.15 to 0.15.16 in the python-packages group (#538) Bumps the python-packages group with 1 update: [ruff](https://github.com/astral-sh/ruff). Updates `ruff` from 0.15.15 to 0.15.16 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.15...0.15.16) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-packages ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2f2e3df3f..e47a9eed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dev = [ "mypy==2.1.0", "pre-commit", "pyright", - "ruff==0.15.15", + "ruff==0.15.16", "types-networkx==3.6.1.20260518", "types-psutil", "types-setuptools", diff --git a/uv.lock b/uv.lock index 65752fee0..586529ed2 100644 --- a/uv.lock +++ b/uv.lock @@ -997,7 +997,7 @@ requires-dist = [ { name = "qiskit-aer", marker = "extra == 'dev'" }, { name = "qiskit-qasm3-import", marker = "extra == 'dev'" }, { name = "quimb" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.15" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.16" }, { name = "scipy" }, { name = "scipy-stubs", marker = "extra == 'dev'" }, { name = "sphinx", marker = "extra == 'doc'" }, @@ -2620,27 +2620,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, - { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, - { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, - { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, - { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, - { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, - { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, - { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, - { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, - { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, +version = "0.15.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, + { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, + { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, + { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, + { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, + { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, ] [[package]] From 56976ccb66ff3cbf40d84db468f51d4c60783378 Mon Sep 17 00:00:00 2001 From: thierry-martinez Date: Sat, 13 Jun 2026 11:52:07 +0200 Subject: [PATCH 58/63] Pin `matplotlib==3.10.9` for extra `dev` dependencies (#541) * Pin `matplotlib==3.10.9` for extra `dev` dependencies This commit adds a pin to `matplotlib==3.10.9` in the `dev` extra dependencies. Matplotlib 3.11.0 introduces visual changes that break our graphical-regression tests. As we do for other packages whose new releases can break the test suite, we pin a precise version in the `dev` extra to ensure reproducible CI while keeping the package unpinned in the default dependencies so users can choose whichever version they prefer. We pin `3.10.9` instead of the newer `3.11.0` because the latter drops support for Python 3.11, which we continue to support until its official end-of-life (October 2026): https://devguide.python.org/versions/ * Restore qiskit constraint in `dev` extra * Remove `pytest --mpl` in `tests_minimal` We cannot run `pytest --mpl` in `test_minimal` (without extra), since the visualization output depends on the version of Matplotlib, and the pinning is only in the `dev` extra. --- noxfile.py | 5 +- pyproject.toml | 3 +- uv.lock | 621 ++++++++++++++++++++++++------------------------- 3 files changed, 307 insertions(+), 322 deletions(-) diff --git a/noxfile.py b/noxfile.py index 498cc9d84..886811efa 100644 --- a/noxfile.py +++ b/noxfile.py @@ -40,7 +40,10 @@ def tests_minimal(session: Session) -> None: """Run the test suite with minimal dependencies.""" session.install(".") install_pytest(session) - run_pytest(session, mpl=True) + # We cannot run `pytest --mpl` here because the visualization output + # depends on the Matplotlib version, and the pinning is only present + # in the `dev` extra. + run_pytest(session) @nox.session(python=PYTHON_VERSIONS) diff --git a/pyproject.toml b/pyproject.toml index e47a9eed3..261a140a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,9 +58,10 @@ dev = [ "pytest-cov", "pytest-mock", "pytest-mpl", - "qiskit>=1.0", + "qiskit>=1.0,<2.4", "qiskit_qasm3_import", "qiskit-aer", + "matplotlib==3.10.9", ] doc = [ "furo", diff --git a/uv.lock b/uv.lock index 586529ed2..a341d8610 100644 --- a/uv.lock +++ b/uv.lock @@ -48,40 +48,42 @@ wheels = [ [[package]] name = "ast-serialize" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/9d/912fefab0e30aee6a3af8a62bbea4a81b29afa4ba2c973d31170620a26de/ast_serialize-0.3.0.tar.gz", hash = "sha256:1bc3ca09a63a021376527c4e938deedd11d11d675ce850e6f9c7487f5889992b", size = 60689, upload-time = "2026-04-30T23:24:48.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/57/a54d4de491d6cdd7a4e4b0952cc3ca9f60dcefa7b5fb48d6d492debe1649/ast_serialize-0.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a867927df59f76a18dc1d874a0b2c079b42c58972dca637905576deb0912e14", size = 1182966, upload-time = "2026-04-30T23:23:57.376Z" }, - { url = "https://files.pythonhosted.org/packages/ee/9e/a5db014bb0f91b209236b57c429389e31290c0093532b8436d577699b2fa/ast_serialize-0.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a6fb063bf040abf8321e7b8113a0554eda445ffc508aa51287f8808886a5ae22", size = 1171316, upload-time = "2026-04-30T23:23:59.63Z" }, - { url = "https://files.pythonhosted.org/packages/15/59/fd55133e478c4326f60a11df02573bf7ccb2ac685810b50f1803d0f68053/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5075cd8482573d743586779e5f9b652a015e37d4e95132d7e5a9bc5c8f483d8f", size = 1232234, upload-time = "2026-04-30T23:24:01.168Z" }, - { url = "https://files.pythonhosted.org/packages/cc/79/0ca1d26357ecb4a697d74d00b73ef3137f24c140424125393a0de820eb09/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41560b27794f4553b0f77811e9fb325b77db4a2b39018d437e09932275306e66", size = 1233437, upload-time = "2026-04-30T23:24:03.151Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/7078ec94dd6e124b8e028ac77016a4f13c83fa1c145790f2e68f3816998b/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b967c01ca74909c5d90e0fe4393401e2cc5da5ebd9a6262a19e45ffd3757dec8", size = 1440188, upload-time = "2026-04-30T23:24:04.717Z" }, - { url = "https://files.pythonhosted.org/packages/21/16/cca7195ef55a012f8013c3442afa91d287a0a36dcf88b480b262475135b3/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:424ebb8f46cd993f7cec4009d119312d8433dd90e6b0df0499cd2c91bdcc5af9", size = 1254211, upload-time = "2026-04-30T23:24:06.18Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0f/f3d4dfae67dee6580534361a6343367d34217e7d25cff858bd1d8f03b8ed/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14b1d566b56e2ee70b11fec1de7e0b94ec7cd83717ec7d189967841a361190e", size = 1255973, upload-time = "2026-04-30T23:24:07.772Z" }, - { url = "https://files.pythonhosted.org/packages/14/41/55fbfe02c42f40fbe3e74eda167d977d555ff720ce1abfa08515236efd88/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba30b18735f047ec11103d1ab92f4789cf1fea1e0dc89b04a2f5a0632fd79de", size = 1298629, upload-time = "2026-04-30T23:24:09.4Z" }, - { url = "https://files.pythonhosted.org/packages/28/36/7d2501cacc7989fb8504aa9da2a2022a174200a59d4e6639de4367a57fdd/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ea0754cb7b0f682ebb005ffb0d18f8d17993490d9c289863cd69cacc4ab8df", size = 1408435, upload-time = "2026-04-30T23:24:11.013Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/54e3b469c3fa0bf9cd532fa643d1d33b73303f8d70beac3e366b68dd64b7/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a0c5aa1073a5ba7b2abaa4b54abe8b8d75c4d1e2d54a2ff70b0ca6222fea5728", size = 1508174, upload-time = "2026-04-30T23:24:12.635Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/9b9621865b02c60539e26d9b114a312b4fa46aa703e33e79317174bfea21/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e52650d834c1ea7791969a361de2c54c13b2fb4c519ec79445fa8b9021a147d", size = 1502354, upload-time = "2026-04-30T23:24:14.186Z" }, - { url = "https://files.pythonhosted.org/packages/34/dd/f138bc5c43b0c414fdd12eefe15677839323078b6e75301ad7f96cd26d45/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15bd6af3f136c61dae27805eb6b8f3269e85a545c4c27ffe9e530ead78d2b36d", size = 1450504, upload-time = "2026-04-30T23:24:16.076Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/97ef9e1c315601db74365955c8edd3292e3055500d6317602815dbdf08ae/ast_serialize-0.3.0-cp314-cp314t-win32.whl", hash = "sha256:d188bfe37b674b49708497683051d4b571366a668799c9b8e8a94513694969d9", size = 1058662, upload-time = "2026-04-30T23:24:17.535Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d6/e2c3483c31580fdb623f92ad38d2f856cde4b9205a3e6bd84760f3de7d82/ast_serialize-0.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5832c2fdf8f8a6cf682b4cfcf677f5eaf39b4ddbc490f5480cfccdd1e7ce8fa1", size = 1100349, upload-time = "2026-04-30T23:24:18.992Z" }, - { url = "https://files.pythonhosted.org/packages/ab/89/29abcb1fe18a429cda60c6e0bbd1d6e90499339842a2f548d7567542357e/ast_serialize-0.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:670f177188d128fb7f9f15b5ad0e1b553d22c34e3f584dcb83eb8077600437f0", size = 1072895, upload-time = "2026-04-30T23:24:20.706Z" }, - { url = "https://files.pythonhosted.org/packages/bc/93/72abad83966ed6235647c9f956417dc1e17e997696388521910e3d1fa3f4/ast_serialize-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ec2fafa5e4313cc8feed96e436ebe19ac7bc6fa41fbc2827e826c48b9e4c3a9", size = 1190024, upload-time = "2026-04-30T23:24:22.486Z" }, - { url = "https://files.pythonhosted.org/packages/85/4f/eb88584b2f0234e581762011208ca203252bf6c98e59b4769daa571f3576/ast_serialize-0.3.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef6d3c08b7b4cd29b48410338e134764a00e76d25841eb02c1084e868c888ecc", size = 1178633, upload-time = "2026-04-30T23:24:24.35Z" }, - { url = "https://files.pythonhosted.org/packages/56/51/cf1ec1ff3e616373d0dcbd5fad502e0029dc541f13ab642259762a7d127f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d841424f41b886e98044abc80769c14a956e6e5ccd5fb5b0d9f5ead72be18a4", size = 1241351, upload-time = "2026-04-30T23:24:25.987Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/68fcf50478cf1093f2d423f034ae06453122c8b415d8e21a44668eca485d/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d21453734ad39367ede5d37efe4f59f830ce1c09f432fc72a90e368f77a4a3e7", size = 1239582, upload-time = "2026-04-30T23:24:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/9d/c1/a6c9fa284eceb5fc6f21347e968445a051d7ca2c4d34e6a04314646dbcee/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5e110cdce2a347e1dd987529c88ef54d26f67848dce3eba1b3b2cc2cf085c94", size = 1448853, upload-time = "2026-04-30T23:24:29.534Z" }, - { url = "https://files.pythonhosted.org/packages/23/5f/8ad3829a09e4e8c5328a53ce7d4711d660944e3e164c5f6abcc2c8f27167/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6e23a98e57560a055f5c4b68700a0fd5ce483d2814c23140b3638c7f5d1e61", size = 1262204, upload-time = "2026-04-30T23:24:31.482Z" }, - { url = "https://files.pythonhosted.org/packages/25/13/44aa28d97f10e25247e8576b5f6b2795d4fa1a80acc88acc942c508d06f7/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c9e763d70293d65ce1e1ea8c943140c68d0953f0268c7ee0998f2e07f77dd0", size = 1266458, upload-time = "2026-04-30T23:24:33.088Z" }, - { url = "https://files.pythonhosted.org/packages/d8/58/b3a8be3777cd3744324fd5cec0d80d37cd96fc7cbb0fb010e03dff1e870f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4388a1796c228f1ce5c391426f7d21a0003ad3b47f677dbeded9bd1a85c7209f", size = 1308700, upload-time = "2026-04-30T23:24:34.657Z" }, - { url = "https://files.pythonhosted.org/packages/13/03/f8312d6b57f5471a9dc7946f22b8798a1fc296d38c25766223aacadec42c/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5283cdcc0c64c3d8b9b688dc6aaa012d9c0cf1380a7f774a6bae6a1c01b3205a", size = 1416724, upload-time = "2026-04-30T23:24:36.562Z" }, - { url = "https://files.pythonhosted.org/packages/50/5d/13fc3789a7abac00559da2e2e9f386db4612aa1f84fc53d09bf714c37545/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ef88cc5842a5d7a6ac09dc0d5fc2c98f5d276c1f076f866d55047ce886785b", size = 1515441, upload-time = "2026-04-30T23:24:38.018Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b9/7ab43fc7a23b1f970281093228f5f79bed6edeed7a3e672bde6d7a832a58/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cc14bf402bdc0978594ecce783793de2c7470cd4f5cd7eb286ca97ed8ff7cba9", size = 1510522, upload-time = "2026-04-30T23:24:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/56/ec/d75fc2b788d319f1fad77c14156896f31afdfc68af85b505e5bdebcb9592/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11eae0cf1b7b3e0678133cc2daa974ea972caf02eb4b3aa062af6fa9acd52c57", size = 1460917, upload-time = "2026-04-30T23:24:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/95/74/f99c81193a2725911e1911ae567ed27c2f2419332c7f3537366f9d238cac/ast_serialize-0.3.0-cp39-abi3-win32.whl", hash = "sha256:2db3dd99de5e6a5a11d7dda73de8750eb6e5baaf25245adf7bdcfe64b6108ae2", size = 1067804, upload-time = "2026-04-30T23:24:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/16/81/76af00c47daa151e89f98ae21fbbcb2840aaa9f5766579c4da76a3c57188/ast_serialize-0.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:a2cd125adccf7969470621905d302750cd25951f22ea430d9a25b7be031e5549", size = 1105561, upload-time = "2026-04-30T23:24:44.578Z" }, - { url = "https://files.pythonhosted.org/packages/bd/46/d3ec57ad500f598d1554bd14ce4df615960549ab2844961bc4e1f5fbd174/ast_serialize-0.3.0-cp39-abi3-win_arm64.whl", hash = "sha256:0dd00da29985f15f50dc35728b7e1e7c84507bccfea1d9914738530f1c72238a", size = 1077165, upload-time = "2026-04-30T23:24:46.377Z" }, +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, + { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, + { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, + { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, + { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, + { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, + { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, + { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, + { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, + { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, + { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, + { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, ] [[package]] @@ -104,11 +106,11 @@ wheels = [ [[package]] name = "autoray" -version = "0.8.10" +version = "0.8.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/ca/fb0890c28a7228cdb4545d2e32f4016ebb16514039ab1ec7065092db6cee/autoray-0.8.10.tar.gz", hash = "sha256:a411c87fe5c0c12120c56478ddf64172d584705e582ef86ef12f81f5769f697f", size = 1324674, upload-time = "2026-03-06T22:36:39.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/ef/3f4c83009e9825f3a8565bd71759be63de06de37768db31f9bf56a4075ce/autoray-0.8.11.tar.gz", hash = "sha256:3e994ea6813916b4e5471853f5332d69224fd67a5f057d602d20df47dcf63fe4", size = 1344555, upload-time = "2026-06-08T22:36:49.685Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/59/3044cc591035b2d2962826a9ae323e02f1dcb07a821d217ae5ccd9b94481/autoray-0.8.10-py3-none-any.whl", hash = "sha256:033f4ba37e2278ece9c50be4a42abe0d91d5c611194d90904c4e6d980300454a", size = 939985, upload-time = "2026-03-06T22:36:38.027Z" }, + { url = "https://files.pythonhosted.org/packages/9f/be/f0352174093c8beed98e85e37f9190cc7da8f26330b9853f5153a9514a0a/autoray-0.8.11-py3-none-any.whl", hash = "sha256:336d51cde75232585e965cac51e74e953d88c48405c8c199946514458ccbcfba", size = 941629, upload-time = "2026-06-08T22:36:47.75Z" }, ] [[package]] @@ -122,24 +124,24 @@ wheels = [ [[package]] name = "beautifulsoup4" -version = "4.14.3" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, ] [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.5.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, ] [[package]] @@ -444,127 +446,127 @@ wheels = [ [[package]] name = "cotengra" -version = "0.7.5" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "autoray" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/42/041a65c3a12646156ae584a474a7843da977e51453403365aec9ec54bc7c/cotengra-0.7.5.tar.gz", hash = "sha256:f3cee6c0cd29fd239a1e9868c3b719c574cc56204dbf7e658ba10f3fd93dce98", size = 2830534, upload-time = "2025-06-12T23:53:13.954Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/5b/6bf6d614ac5499417843fe9b288d4ad93b7af92b5dd1c57ebd6f8ae93942/cotengra-0.8.1.tar.gz", hash = "sha256:4a94f3288a2a7bbc4bebb82f43984695385abd3f9130558b5a5d7f89d39c7b38", size = 2491311, upload-time = "2026-06-08T18:00:48.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0e/5356269a34f7860fb506b6690e74f4d0062e0e50bf6345fb5405e294b1ec/cotengra-0.7.5-py3-none-any.whl", hash = "sha256:0d269883ee87e4a08ce6c91ca38aafbd3d56e177a3b3b54d61e3fe314fe67e8b", size = 195379, upload-time = "2025-06-12T23:53:11.971Z" }, + { url = "https://files.pythonhosted.org/packages/33/58/6c663b9f21e38c5f944eabe540050b316c61551f116dfeac344b5c61982d/cotengra-0.8.1-py3-none-any.whl", hash = "sha256:11d02e88fa23047375f2d93a19a3f3d3c8b5cb2f1754f19703f6444545b3711b", size = 254371, upload-time = "2026-06-08T18:00:46.638Z" }, ] [[package]] name = "coverage" -version = "7.13.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, - { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, - { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, - { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, - { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, - { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, - { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, - { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, - { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, - { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, - { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, - { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, - { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, - { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, - { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, - { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, - { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, - { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, - { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, - { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, - { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, - { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, - { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, - { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, - { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +version = "7.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/69/0d2ef01ff4b8fcecd4cba920d11e92fa4f96ae412441d3b56a90a258e69b/coverage-7.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf", size = 219722, upload-time = "2026-05-26T20:38:14.002Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ae/9afdeaa31b9d9ce98124b6abf8bb49119bf71aecae04f8567c189d91299f/coverage-7.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf", size = 220240, upload-time = "2026-05-26T20:38:17.424Z" }, + { url = "https://files.pythonhosted.org/packages/51/69/c998589871df7ea7dba865cc5ee32b5a3e1d47ba6c68ef91104c7c46fa5e/coverage-7.14.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d", size = 246981, upload-time = "2026-05-26T20:38:19.266Z" }, + { url = "https://files.pythonhosted.org/packages/fc/10/1c7d04c13040dac531d21b712bbe08f902e6dd9b58f5d77875c4d030f8f2/coverage-7.14.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2", size = 248812, upload-time = "2026-05-26T20:38:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/c1/65/2a38a4607ef27cadcfbcee034dba5830ae2569f90144a0f4c7dbf47d30b0/coverage-7.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47", size = 250675, upload-time = "2026-05-26T20:38:22.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a2/a446ed9752a4a59b79e0fb6cbb319f6facb2183045c0725462625e66f87e/coverage-7.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550", size = 252590, upload-time = "2026-05-26T20:38:23.63Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fd/e81fbd7ba752365546e9842b1cbdaad3d6919d2a522c590aef16a281ec5e/coverage-7.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e", size = 247691, upload-time = "2026-05-26T20:38:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/53/35/f3c26fdaae9ea937d154ca4d372e5ea0a4167ff70d36c6074ac2eacb2f83/coverage-7.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f", size = 248716, upload-time = "2026-05-26T20:38:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/2e/14/940b6c49551fd343e8507ee2b0ba7af5d0aa04ed5bf768285cb7c72a9884/coverage-7.14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1", size = 246721, upload-time = "2026-05-26T20:38:28.282Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2c/40fc0634186c28292a662dff578866b3913983d6c375a3c2a74020938719/coverage-7.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5", size = 250533, upload-time = "2026-05-26T20:38:29.753Z" }, + { url = "https://files.pythonhosted.org/packages/de/e3/2c26bf1e811f9df991ff2a9bdddebdd13ee0665d564df7d05979f9146297/coverage-7.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b", size = 246990, upload-time = "2026-05-26T20:38:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b0/060260ef56bd92363ebdce0c7095ce422b06e69aae71828efeca473ab1ca/coverage-7.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332", size = 247593, upload-time = "2026-05-26T20:38:33.065Z" }, + { url = "https://files.pythonhosted.org/packages/63/f3/501502046efeb0d6d94b5ca54941d95f1184183dd6bdb7f283985783bb4a/coverage-7.14.1-cp310-cp310-win32.whl", hash = "sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59", size = 222330, upload-time = "2026-05-26T20:38:35.36Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5d/1bf99f2c558f128faf7906817ccbdb576ba815d3b41ce2ac1719b70a3663/coverage-7.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253", size = 223261, upload-time = "2026-05-26T20:38:37.196Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, + { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, + { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, + { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, + { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, + { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, + { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, + { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, ] [package.optional-dependencies] @@ -746,11 +748,11 @@ wheels = [ [[package]] name = "decorator" -version = "5.2.1" +version = "5.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ] [[package]] @@ -777,11 +779,11 @@ wheels = [ [[package]] name = "distlib" -version = "0.4.0" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] [[package]] @@ -832,68 +834,68 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.2" +version = "3.29.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/f5/3557bf28e0f1943e4849154c821533706e6dea010f96fb6aa0b6949037d1/filelock-3.29.3.tar.gz", hash = "sha256:7fc1b3f39cf172fd8203812043c57b8a65aef9969f38b6704f628b881f761a84", size = 61956, upload-time = "2026-06-10T17:37:11.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, + { url = "https://files.pythonhosted.org/packages/81/8f/b61d427c4f49a8bdadc93f4e7e74df8a6df6f77ee6e26bf0df53d3925363/filelock-3.29.3-py3-none-any.whl", hash = "sha256:e58333029cc9b925f39aad59b1d8f0a1ad836af4e60d7217f4a4dba87461261d", size = 42324, upload-time = "2026-06-10T17:37:10.37Z" }, ] [[package]] name = "fonttools" -version = "4.62.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/532ed43808b469c807e8cb6b21358da3fe6fd51486b3a8c93db0bb5d957f/fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c", size = 2873740, upload-time = "2026-03-13T13:52:11.822Z" }, - { url = "https://files.pythonhosted.org/packages/85/e4/2318d2b430562da7227010fb2bb029d2fa54d7b46443ae8942bab224e2a0/fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a", size = 2417649, upload-time = "2026-03-13T13:52:14.605Z" }, - { url = "https://files.pythonhosted.org/packages/4c/28/40f15523b5188598018e7956899fed94eb7debec89e2dd70cb4a8df90492/fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3", size = 4935213, upload-time = "2026-03-13T13:52:17.399Z" }, - { url = "https://files.pythonhosted.org/packages/42/09/7dbe3d7023f57d9b580cfa832109d521988112fd59dddfda3fddda8218f9/fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23", size = 4892374, upload-time = "2026-03-13T13:52:20.175Z" }, - { url = "https://files.pythonhosted.org/packages/d1/2d/84509a2e32cb925371560ef5431365d8da2183c11d98e5b4b8b4e42426a5/fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d", size = 4911856, upload-time = "2026-03-13T13:52:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/a5/80/df28131379eed93d9e6e6fccd3bf6e3d077bebbfe98cc83f21bbcd83ed02/fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae", size = 5031712, upload-time = "2026-03-13T13:52:25.14Z" }, - { url = "https://files.pythonhosted.org/packages/3d/03/3c8f09aad64230cd6d921ae7a19f9603c36f70930b00459f112706f6769a/fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed", size = 1507878, upload-time = "2026-03-13T13:52:28.149Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ec/f53f626f8f3e89f4cadd8fc08f3452c8fd182c951ad5caa35efac22b29ab/fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9", size = 1556766, upload-time = "2026-03-13T13:52:30.814Z" }, - { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, - { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, - { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, - { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, ] [[package]] @@ -924,7 +926,7 @@ dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "quimb", version = "1.11.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "quimb", version = "1.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "quimb", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sympy" }, @@ -933,6 +935,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "matplotlib" }, { name = "mypy" }, { name = "nox" }, { name = "pre-commit" }, @@ -948,7 +951,7 @@ dev = [ { name = "qiskit-qasm3-import" }, { name = "ruff" }, { name = "scipy-stubs", version = "1.15.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy-stubs", version = "1.17.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy-stubs", version = "1.17.1.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "types-networkx" }, { name = "types-psutil" }, { name = "types-setuptools" }, @@ -976,6 +979,7 @@ requires-dist = [ { name = "furo", marker = "extra == 'doc'" }, { name = "joblib", marker = "extra == 'doc'" }, { name = "matplotlib" }, + { name = "matplotlib", marker = "extra == 'dev'", specifier = "==3.10.9" }, { name = "matplotlib", marker = "extra == 'doc'" }, { name = "mypy", marker = "extra == 'dev'", specifier = "==2.1.0" }, { name = "networkx" }, @@ -993,7 +997,7 @@ requires-dist = [ { name = "pytest-mock", marker = "extra == 'dev'" }, { name = "pytest-mpl", marker = "extra == 'dev'" }, { name = "pyzx", marker = "extra == 'extra'", specifier = ">=0.10.0" }, - { name = "qiskit", marker = "extra == 'dev'", specifier = ">=1.0" }, + { name = "qiskit", marker = "extra == 'dev'", specifier = ">=1.0,<2.4" }, { name = "qiskit-aer", marker = "extra == 'dev'" }, { name = "qiskit-qasm3-import", marker = "extra == 'dev'" }, { name = "quimb" }, @@ -1021,20 +1025,20 @@ wheels = [ [[package]] name = "identify" -version = "2.6.18" +version = "2.6.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] [[package]] name = "idna" -version = "3.15" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1082,51 +1086,29 @@ wheels = [ [[package]] name = "ipython" -version = "9.10.1" +version = "9.14.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ + "python_full_version >= '3.12'", "python_full_version == '3.11.*'", ] dependencies = [ - { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version == '3.11.*'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version == '3.11.*'" }, - { name = "jedi", marker = "python_full_version == '3.11.*'" }, - { name = "matplotlib-inline", marker = "python_full_version == '3.11.*'" }, - { name = "pexpect", marker = "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version == '3.11.*'" }, - { name = "pygments", marker = "python_full_version == '3.11.*'" }, - { name = "stack-data", marker = "python_full_version == '3.11.*'" }, - { name = "traitlets", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/25/daae0e764047b0a2480c7bbb25d48f4f509b5818636562eeac145d06dfee/ipython-9.10.1.tar.gz", hash = "sha256:e170e9b2a44312484415bdb750492699bf329233b03f2557a9692cce6466ada4", size = 4426663, upload-time = "2026-03-27T09:53:26.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl", hash = "sha256:82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232", size = 622827, upload-time = "2026-03-27T09:53:24.566Z" }, -] - -[[package]] -name = "ipython" -version = "9.12.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.12'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" }, - { name = "jedi", marker = "python_full_version >= '3.12'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" }, - { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" }, - { name = "pygments", marker = "python_full_version >= '3.12'" }, - { name = "stack-data", marker = "python_full_version >= '3.12'" }, - { name = "traitlets", marker = "python_full_version >= '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/73/7114f80a8f9cabdb13c27732dce24af945b2923dcab80723602f7c8bc2d8/ipython-9.12.0.tar.gz", hash = "sha256:01daa83f504b693ba523b5a407246cabde4eb4513285a3c6acaff11a66735ee4", size = 4428879, upload-time = "2026-03-27T09:42:45.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/23/3a27530575643c8bb7bfc757a28e2e7ef80092afbf59a2bc5716320b6602/ipython-9.14.1.tar.gz", hash = "sha256:f913bf74df06d458e46ced84ca506c23797590d594b236fe60b14df213291e7b", size = 4433457, upload-time = "2026-06-05T08:12:34.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl", hash = "sha256:0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d", size = 625661, upload-time = "2026-03-27T09:42:42.831Z" }, + { url = "https://files.pythonhosted.org/packages/9d/22/58818a63eaf8982b67632b1bc20585c811611b15a8da19d6012323dc76a5/ipython-9.14.1-py3-none-any.whl", hash = "sha256:5d4a9ecaa3b10e6e5f269dd0948bdb58ca9cb851899cd23e07c320d3eb11613c", size = 627770, upload-time = "2026-06-05T08:12:33.045Z" }, ] [[package]] @@ -1148,8 +1130,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "ipython", version = "9.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, - { name = "ipython", version = "9.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "ipython", version = "9.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "jupyterlab-widgets" }, { name = "traitlets" }, { name = "widgetsnbextension" }, @@ -1161,14 +1142,14 @@ wheels = [ [[package]] name = "jedi" -version = "0.19.2" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, ] [[package]] @@ -1613,14 +1594,14 @@ wheels = [ [[package]] name = "matplotlib-inline" -version = "0.2.1" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, ] [[package]] @@ -1981,7 +1962,7 @@ wheels = [ [[package]] name = "optype" -version = "0.17.0" +version = "0.17.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", @@ -1990,9 +1971,9 @@ resolution-markers = [ dependencies = [ { name = "typing-extensions", marker = "python_full_version >= '3.11' and python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/9f/3b13bab05debf685678b8af004e46b8c67c6f98ffa08eaf5d33bcf162c16/optype-0.17.0.tar.gz", hash = "sha256:31351a1e64d9eba7bf67e14deefb286e85c66458db63c67dd5e26dd72e4664e5", size = 53484, upload-time = "2026-03-08T23:03:12.594Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/86/e6f1f6f3487492dfcf3b7a2d4e2534d27af6ac05b364b276706906c34865/optype-0.17.1.tar.gz", hash = "sha256:07bfa32b795dea28fba8605a6288d36370d072f25183fb9c29b5a90f4b6f5638", size = 53572, upload-time = "2026-05-17T22:13:28.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/44/dca78187415947d1bb90b2ee2a58e47d9573528331e8dc6196996b53612a/optype-0.17.0-py3-none-any.whl", hash = "sha256:8c2d88ff13149454bcf6eb47502f80d288bc542e7238fcc412ac4d222c439397", size = 65854, upload-time = "2026-03-08T23:03:11.425Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/c6a2b043e33f0dd012486dcebe0593585588d400175d22aad42049c88321/optype-0.17.1-py3-none-any.whl", hash = "sha256:82f2508ca31cb21e53a41648482d890fe1f5c6cb153720551af41161555adaf1", size = 65954, upload-time = "2026-05-17T22:13:27.549Z" }, ] [package.optional-dependencies] @@ -2003,29 +1984,29 @@ numpy = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] name = "parso" -version = "0.8.6" +version = "0.8.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, ] [[package]] name = "pathspec" -version = "1.0.4" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] [[package]] @@ -2140,11 +2121,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.6" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, ] [[package]] @@ -2366,15 +2347,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.2.2" +version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, ] [[package]] @@ -2573,7 +2554,7 @@ wheels = [ [[package]] name = "quimb" -version = "1.13.0" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", @@ -2589,14 +2570,14 @@ dependencies = [ { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "tqdm", marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/7f/a4fa9cde1425c935c8f25d8ffa0810b922e9c49bcd43be43cf3d0a68d053/quimb-1.13.0.tar.gz", hash = "sha256:efc9aa5f32b69ada50b5950d1eb0d104100903aecbf8a6615e95fa1073aa9f68", size = 10343979, upload-time = "2026-03-20T00:56:47.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/ca/0a80995d046b079f34bd78f281ceaaa286a15b06f7afeccc43d7cb2efe1d/quimb-1.14.0.tar.gz", hash = "sha256:8c3706088ba76e281334f719b341d4630e63c36bf4fe53bbfb46325e506dcad3", size = 13882683, upload-time = "2026-05-11T00:27:17.405Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/b1/07c2f512960f0a70f741592b8f46f2edb09f5a3512487f55005b176dd604/quimb-1.13.0-py3-none-any.whl", hash = "sha256:861862ca9a03c91476ca9e013f9da6df80fa2d53783a6a6f206e8862fd87f358", size = 2016612, upload-time = "2026-03-20T00:56:45.23Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c5/2175e1a8cd38dea77353a82e130a73a97061fa13f1a9e74dd89c35488db6/quimb-1.14.0-py3-none-any.whl", hash = "sha256:aca4bef42536db220bedc845b0fd030a0ce1fe20ae288449d7a9d0e4915ae94b", size = 2025761, upload-time = "2026-05-11T00:27:15.169Z" }, ] [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -2604,9 +2585,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] [[package]] @@ -2817,18 +2798,18 @@ wheels = [ [[package]] name = "scipy-stubs" -version = "1.17.1.4" +version = "1.17.1.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", "python_full_version == '3.11.*'", ] dependencies = [ - { name = "optype", version = "0.17.0", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.11'" }, + { name = "optype", version = "0.17.1", source = { registry = "https://pypi.org/simple" }, extra = ["numpy"], marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/75/d944a11fca64aa84fbb4bfcf613b758319c6103cb30a304a0e9727009d62/scipy_stubs-1.17.1.4.tar.gz", hash = "sha256:cae00c5207aa62ceb4bcadea202d9fbbf002e958f9e4de981720436b8d5c1802", size = 396980, upload-time = "2026-04-13T11:46:54.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/30/7a2e621918d1317ab972f797161131f2635648ad5d92baf0695dd009e4f9/scipy_stubs-1.17.1.5.tar.gz", hash = "sha256:284b1dd1dd46107a614971d170030d310cd88b2ac6b483f85285ee0ff87720bd", size = 399933, upload-time = "2026-05-25T21:34:33.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/f8/334aa5a7a482ea89cb14d92f6a4d9ffa1e193e733144d4d14c7ffcb33583/scipy_stubs-1.17.1.4-py3-none-any.whl", hash = "sha256:e6e5c390fb864745bc3d5f591de81f5cb4f84403857d4f660acb5b6339956f5b", size = 604752, upload-time = "2026-04-13T11:46:53.135Z" }, + { url = "https://files.pythonhosted.org/packages/e1/26/d4bc2ba3427a623f79a6c10c8f427c7a55b56eb8b3eddc369319d97f741b/scipy_stubs-1.17.1.5-py3-none-any.whl", hash = "sha256:58ebf054a86c000c72e8982e121c4ead0d3d9ba7a6c38aa5fa71b07f96a427fd", size = 607388, upload-time = "2026-05-25T21:34:32.073Z" }, ] [[package]] @@ -2842,20 +2823,20 @@ wheels = [ [[package]] name = "snowballstemmer" -version = "3.0.1" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, ] [[package]] name = "soupsieve" -version = "2.8.3" +version = "2.8.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, ] [[package]] @@ -3050,11 +3031,11 @@ wheels = [ [[package]] name = "stevedore" -version = "5.7.0" +version = "5.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6d/90764092216fa560f6587f83bb70113a8ba510ba436c6476a2b47359057c/stevedore-5.7.0.tar.gz", hash = "sha256:31dd6fe6b3cbe921e21dcefabc9a5f1cf848cf538a1f27543721b8ca09948aa3", size = 516200, upload-time = "2026-02-20T13:27:06.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/06/36d260a695f383345ab5bbc3fd447249594ae2fa8dfd19c533d5ae23f46b/stevedore-5.7.0-py3-none-any.whl", hash = "sha256:fd25efbb32f1abb4c9e502f385f0018632baac11f9ee5d1b70f88cc5e22ad4ed", size = 54483, upload-time = "2026-02-20T13:27:05.561Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" }, ] [[package]] @@ -3134,23 +3115,23 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.3" +version = "4.68.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/05/0d5260f1f1ca784f4a4a0def9cbe6affe587f5b4025328d446c3d67765f4/tqdm-4.68.2.tar.gz", hash = "sha256:89c230e8dbc67c7615c142487111222f878c77427ea09549960f62389e258add", size = 171923, upload-time = "2026-06-09T13:26:42.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" }, ] [[package]] name = "traitlets" -version = "5.14.3" +version = "5.15.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, ] [[package]] @@ -3204,7 +3185,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.2.3" +version = "21.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3213,18 +3194,18 @@ dependencies = [ { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/8c/bdd9f89f89e4a787ac61bb2da4d884bc45e0c287ec694dfa3170dddd5cfe/virtualenv-21.2.3.tar.gz", hash = "sha256:9bb6d1414ab55ca624371e30c7719c32f183ef44da544ef8aa44a456de7ac191", size = 5844776, upload-time = "2026-04-14T01:10:36.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/50/7564c805bb8966d9771caaba8a143fa5e57c848ce4e7fdf2d55a1feb2ead/virtualenv-21.4.3.tar.gz", hash = "sha256:938ff0fd3f4e0f0d3a025f67a3d2f25e3c3aabbcd5857ea6170619138d72d141", size = 7644454, upload-time = "2026-06-11T16:47:04.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/19/bc7c4e05f42532863cf2ae7e7e847beab25835934e0410160b47eeff1e35/virtualenv-21.2.3-py3-none-any.whl", hash = "sha256:486652347ea8526d91e9807c0274583cb7ba31dd4942ff10fb5621402f0fe0d8", size = 5828329, upload-time = "2026-04-14T01:10:34.809Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8d/84b0d07c6b5f685f85ddf6c87a59d3a8a895a3dfd89e759666fabe951b94/virtualenv-21.4.3-py3-none-any.whl", hash = "sha256:75f4127d4067397c64f38579ce918fec6bf9ca2cd4f48685e82952cc3c035840", size = 7625544, upload-time = "2026-06-11T16:47:01.78Z" }, ] [[package]] name = "wcwidth" -version = "0.6.0" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" }, ] [[package]] From 5c6737b05e02bf997b6cd04959da458a6b016691 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sun, 14 Jun 2026 02:57:22 +0200 Subject: [PATCH 59/63] Handle pre-allocated qubits --- graphix/sim/base_backend.py | 6 ++++++ graphix/sim/density_matrix.py | 10 ++++++---- graphix/sim/statevec.py | 8 ++++++++ graphix/sim/tensornet.py | 6 ++++++ graphix/simulator.py | 19 +++++++++++++++++++ tests/test_density_matrix.py | 8 ++++++++ tests/test_simulator.py | 13 +++++++------ tests/test_statevec.py | 8 ++++++++ 8 files changed, 68 insertions(+), 10 deletions(-) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index 855e2df6b..e22d04e03 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -588,6 +588,11 @@ class Backend(Generic[_StateT_co]): # (specifically, as a parameter of `__init__`) since `_StateT_co` is covariant. state: _StateT_co = dataclasses.field(init=False) + @property + @abstractmethod + def nqubit(self) -> int: + """Return the number of qubits.""" + @abstractmethod def add_nodes(self, nodes: Sequence[int], data: Data = BasicStates.PLUS) -> None: r""" @@ -835,6 +840,7 @@ def finalize(self, output_nodes: Sequence[int]) -> None: self.state.permute([self.node_index.index(node) for node in output_nodes]) @property + @override def nqubit(self) -> int: """Return the number of qubits of the current state.""" return self.state.nqubit diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index 06b484123..cb2698295 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -20,7 +20,7 @@ from graphix.channels import KrausChannel from graphix.parameter import Expression, ExpressionOrFloat, ExpressionOrSupportsComplex from graphix.sim.base_backend import DenseState, DenseStateBackend, Matrix, kron, matmul, outer, tensordot, vdot -from graphix.sim.statevec import CNOT_TENSOR, CZ_TENSOR, SWAP_TENSOR, Statevec +from graphix.sim.statevec import CNOT_TENSOR, CZ_TENSOR, SWAP_TENSOR, Statevec, _check_permutation from graphix.states import BasicStates, State if TYPE_CHECKING: @@ -287,12 +287,14 @@ def swap(self, qubits: tuple[int, int]) -> None: @override def permute(self, permutation: Sequence[int]) -> None: - tensor_shape = [2] * (2 * self.nqubit) - perm_cols = [i + self.nqubit for i in permutation] + nqubit = self.nqubit + _check_permutation(permutation, nqubit) + tensor_shape = [2] * (2 * nqubit) + perm_cols = [i + nqubit for i in permutation] full_permutation = [*permutation, *perm_cols] rho_tensor = self.rho.reshape(tensor_shape) rho_permuted_tensor = np.transpose(rho_tensor, axes=full_permutation) - self.rho = rho_permuted_tensor.reshape((2**self.nqubit, 2**self.nqubit)) + self.rho = rho_permuted_tensor.reshape((2**nqubit, 2**nqubit)) def entangle(self, edge: tuple[int, int]) -> None: """Connect graph nodes. diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 7ac17a739..43c1b8cb9 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -335,6 +335,7 @@ def swap(self, qubits: tuple[int, int]) -> None: @override def permute(self, permutation: Sequence[int]) -> None: + _check_permutation(permutation, self.nqubit) self.psi = np.transpose(self.psi, permutation) def normalize(self) -> None: @@ -589,3 +590,10 @@ def _format_encoding(nqubit: int, i: int, encoding: _ENCODING) -> str: if encoding == "LSB": return output[::-1] return output + + +def _check_permutation(permutation: Sequence[int], nqubits: int) -> None: + if len(permutation) != nqubits: + raise ValueError(f"Permutation has length {len(permutation)}, but {nqubits} qubits expected.") + if set(permutation) != set(range(nqubits)): + raise ValueError(f"{permutation} is not a permutation.") diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index f9e8c56f7..f438bd0c6 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -801,6 +801,12 @@ def apply_clifford(self, node: int, clifford: Clifford) -> None: def finalize(self, output_nodes: Iterable[int]) -> None: """Do nothing.""" + @property + @override + def nqubit(self) -> int: + """Raise NotImplementedError: the current number of qubits is not well-defined in the current implementation of the TensorNetworkBackend.""" + raise NotImplementedError + def gen_str() -> str: """Generate dummy string for einsum.""" diff --git a/graphix/simulator.py b/graphix/simulator.py index e81439ebc..50410d1e2 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -407,6 +407,25 @@ def run( Stack level to use for warnings. Defaults to 1, meaning that warnings are reported at this function's call site. """ + # Check whether the backend is properly initialized. We + # disable the check for TensorNetworkBackend because its + # current behavior differs from other backends. + if not isinstance(self.backend, TensorNetworkBackend): + initial_nqubit = self.backend.nqubit + if input_state is None: + # No explicit state supplied: the backend must already contain the + # required input qubits. + input_nodes_len = len(self.pattern.input_nodes) + if initial_nqubit != input_nodes_len: + raise ValueError( + f"`input_state` is `None`: the backend is expected to have {input_nodes_len} input nodes already prepared, but {initial_nqubit} were found." + ) + # An explicit state was supplied: the backend must start with a clean + # state (no pre-allocated qubits). + elif initial_nqubit != 0: + raise ValueError( + f"`input_state` is not `None`: the backend is expected to have no pre-allocated qubits, but has {initial_nqubit} qubits." + ) if input_state is not None: self.backend.add_nodes(self.pattern.input_nodes, input_state) if self.noise_model is None: diff --git a/tests/test_density_matrix.py b/tests/test_density_matrix.py index ad6f3016e..622c5e2a7 100644 --- a/tests/test_density_matrix.py +++ b/tests/test_density_matrix.py @@ -944,3 +944,11 @@ def test_permute(fx_rng: Generator, permutation: Sequence[int]) -> None: dm.permute(permutation) permute_with_swap(dm_ref, permutation) assert np.array_equal(dm.rho, dm_ref.rho) + + +def test_permute_bad_permutation() -> None: + dm = DensityMatrix(nqubit=2) + with pytest.raises(ValueError, match="Permutation has length"): + dm.permute([0]) + with pytest.raises(ValueError, match="not a permutation"): + dm.permute([1, 2]) diff --git a/tests/test_simulator.py b/tests/test_simulator.py index e10f03101..ddf139a36 100644 --- a/tests/test_simulator.py +++ b/tests/test_simulator.py @@ -2,6 +2,8 @@ from typing import TYPE_CHECKING +import pytest + from graphix import Circuit from graphix.sim.statevec import Statevec, StatevectorBackend from graphix.states import BasicStates @@ -27,11 +29,10 @@ def test_input_state_none(fx_rng: Generator) -> None: backend.add_nodes(pattern.input_nodes, input_state) state = pattern.simulate_pattern(backend=backend, input_state=None, rng=fx_rng) assert state.isclose(Statevec(BasicStates.PLUS)) - # The backend already prepares |0>. If the simulator also prepares - # the input qubits in |+> (because we do not pass - # `input_state=None`), an additional qubit is introduced. The - # simulation therefore applies (I ⊗ H) on |0+>, resulting in |00>. + # If the backend already prepares |0>. and if we ask the simulator + # to prepare the input qubits in |+> (because we do not pass + # `input_state=None`), `ValueError` is raised. backend = StatevectorBackend() backend.add_nodes(pattern.input_nodes, input_state) - state = pattern.simulate_pattern(backend=backend, rng=fx_rng) - assert state.isclose(Statevec(BasicStates.ZERO, nqubit=2)) + with pytest.raises(ValueError, match="the backend is expected to have no pre-allocated qubits"): + pattern.simulate_pattern(backend=backend, rng=fx_rng) diff --git a/tests/test_statevec.py b/tests/test_statevec.py index 92448df14..a91e56f7c 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -270,3 +270,11 @@ def test_permute(fx_rng: Generator, permutation: Sequence[int]) -> None: statevec.permute(permutation) permute_with_swap(statevec_ref, permutation) assert np.array_equal(statevec.psi, statevec_ref.psi) + + +def test_permute_bad_permutation() -> None: + statevec = Statevec(nqubit=2) + with pytest.raises(ValueError, match="Permutation has length"): + statevec.permute([0]) + with pytest.raises(ValueError, match="not a permutation"): + statevec.permute([1, 2]) From d0a65851833565b6f57ba044ff56fef4daf39333 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Mon, 15 Jun 2026 00:14:27 +0200 Subject: [PATCH 60/63] Better coverage --- graphix/sim/density_matrix.py | 2 +- graphix/sim/statevec.py | 2 +- graphix/transpiler.py | 26 +++++----------- tests/test_qasm3_exporter.py | 13 +++++++- tests/test_simulator.py | 57 ++++++++++++++++++++++------------- tests/test_transpiler.py | 44 +++++++++++++++++---------- 6 files changed, 85 insertions(+), 59 deletions(-) diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index cb2698295..29538cc79 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -261,7 +261,7 @@ def tensor(self, other: DensityMatrix) -> None: if not isinstance(other, DensityMatrix): other = DensityMatrix(other) if self.rho.dtype == np.object_ and other.rho.dtype != np.object_: - other.rho = other.rho.astype(np.object_, copy=False) + other.rho = other.rho.astype(np.object_, copy=False) # pragma: nocover self.rho = kron(self.rho, other.rho) def cnot(self, edge: tuple[int, int]) -> None: diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 43c1b8cb9..2ee79149a 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -303,7 +303,7 @@ def tensor(self, other: Statevec) -> None: total_num = len(self.dims()) + len(other.dims()) if psi_self.dtype == np.object_ and psi_other.dtype != np.object_: - psi_other = psi_other.astype(np.object_, copy=False) + psi_other = psi_other.astype(np.object_, copy=False) # pragma: nocover self.psi = kron(psi_self, psi_other).reshape((2,) * total_num) def cnot(self, qubits: tuple[int, int]) -> None: diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 7c26ca448..82cd3198a 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -444,10 +444,6 @@ def transpile_to_causal_flow(self) -> TranspiledFlow: Returns ------- the result of the transpilation: a causal flow and classical outputs. - - Raises - ------ - IllformedCircuitError: if the pattern is ill-formed (operation on already measured node) """ indices: list[int | None] = list(range(self.width)) n_nodes = self.width @@ -461,15 +457,15 @@ def transpile_to_causal_flow(self) -> TranspiledFlow: match instr.kind: case InstructionKind.M: target = indices[instr.target] - if target is None: - raise IllformedCircuitError + if target is None: # pragma: no cover + raise RuntimeError("Ill-formed circuit") classical_outputs[target] = command.M(target, PauliMeasurement(instr.axis)) indices[instr.target] = None continue case InstructionKind.J: target = indices[instr.target] - if target is None: - raise IllformedCircuitError + if target is None: # pragma: no cover + raise RuntimeError("Ill-formed circuit") graph.add_edge(target, n_nodes) # Also adds nodes measurements[target] = Measurement.XY(normalize_angle(-instr.angle)) indices[instr.target] = n_nodes @@ -479,15 +475,15 @@ def transpile_to_causal_flow(self) -> TranspiledFlow: case InstructionKind.CZ: t0, t1 = instr.targets i0, i1 = indices[t0], indices[t1] - if i0 is None or i1 is None: - raise IllformedCircuitError + if i0 is None or i1 is None: # pragma: no cover + raise RuntimeError("Ill-formed circuit") # If edge exists, remove it; else, add it if graph.has_edge(i0, i1): graph.remove_edge(i0, i1) else: graph.add_edge(i0, i1) continue - case _: + case _: # pragma: no cover assert_never(instr.kind) outputs = [i for i in indices if i is not None] outputs.extend(classical_outputs.keys()) # Necessary for flow-finding step @@ -1074,14 +1070,6 @@ def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: _transpile_swaps = transpile_swaps -class IllformedCircuitError(Exception): - """Raised if the circuit is ill-formed.""" - - def __init__(self) -> None: - """Build the exception.""" - super().__init__("Ill-formed circuit") - - @overload def _initialize_backend( backend: StatevectorBackend | Literal["statevector"], diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index 5d02a0e7d..651335e9c 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -58,5 +58,16 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: pattern = pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.minimize_space() - print(pattern) _qasm3 = pattern_to_qasm3(pattern) + + +def test_to_qasm3_failures() -> None: + circuit = Circuit(2) + circuit.m(0, Axis.X) + with pytest.raises(ValueError, match="OpenQASM3 only supports measurements on Z axis"): + circuit_to_qasm3(circuit, transpile=False) + circuit = circuit.transpile_measurements_to_z_axis() + circuit.j(1, 0.25) + with pytest.raises(ValueError, match="J gates must be decomposed before QASM3 export"): + circuit_to_qasm3(circuit, transpile=False) + _qasm3 = circuit_to_qasm3(circuit) diff --git a/tests/test_simulator.py b/tests/test_simulator.py index ddf139a36..866a4e60c 100644 --- a/tests/test_simulator.py +++ b/tests/test_simulator.py @@ -4,35 +4,50 @@ import pytest -from graphix import Circuit -from graphix.sim.statevec import Statevec, StatevectorBackend -from graphix.states import BasicStates +from graphix import BasicStates, Pattern, Statevec, StatevectorBackend if TYPE_CHECKING: from numpy.random import Generator -def test_input_state_none(fx_rng: Generator) -> None: - circuit = Circuit(1) - circuit.h(0) - pattern = circuit.transpile().pattern - # By default, the initial state is |+>, therefore H|+> = |0>. - state = pattern.simulate_pattern(rng=fx_rng) +def test_no_explicit_input_state(hadamardpattern: Pattern, fx_rng: Generator) -> None: + # No explicit input state: the default initial state is |+⟩. + # H|+⟩ = |0⟩, so we expect the final state to be |0⟩. + state = hadamardpattern.simulate_pattern(rng=fx_rng) assert state.isclose(Statevec(BasicStates.ZERO)) - # With the initial state |0>, we obtain H|0> = |+>. - input_state = BasicStates.ZERO - state = pattern.simulate_pattern(input_state=input_state, rng=fx_rng) + + +def test_explicit_input_state_zero(hadamardpattern: Pattern, fx_rng: Generator) -> None: + # Provide an explicit input state |0⟩. + # H|0⟩ = |+⟩, so the final state should be |+⟩. + state = hadamardpattern.simulate_pattern(input_state=BasicStates.ZERO, rng=fx_rng) assert state.isclose(Statevec(BasicStates.PLUS)) - # With the initial state |0> prepared in the backend, we obtain - # H|0> = |+>. + + +def test_backend_prepared_zero(hadamardpattern: Pattern, fx_rng: Generator) -> None: + # Prepare the initial state in a backend and pass `input_state=None`. + # The backend already contains |0⟩ on its input nodes, + # therefore H|0⟩ = |+⟩. backend = StatevectorBackend() - backend.add_nodes(pattern.input_nodes, input_state) - state = pattern.simulate_pattern(backend=backend, input_state=None, rng=fx_rng) + backend.add_nodes(hadamardpattern.input_nodes, BasicStates.ZERO) + state = hadamardpattern.simulate_pattern(backend=backend, input_state=None, rng=fx_rng) assert state.isclose(Statevec(BasicStates.PLUS)) - # If the backend already prepares |0>. and if we ask the simulator - # to prepare the input qubits in |+> (because we do not pass - # `input_state=None`), `ValueError` is raised. + + +def test_no_prepared_qubits_and_input_state_none(hadamardpattern: Pattern, fx_rng: Generator) -> None: + # No prepared qubits in the backend and `input_state=None`. + # This is ambiguous, so a ValueError must be raised. + backend = StatevectorBackend() + with pytest.raises(ValueError, match="the backend is expected to have 1 input nodes already prepared"): + hadamardpattern.simulate_pattern(backend=backend, input_state=None, rng=fx_rng) + + +def test_prepared_qubits_and_input_state(hadamardpattern: Pattern, fx_rng: Generator) -> None: + # Backend already contains a state (|0⟩) **and** we ask the + # simulator to prepare its own input state (by omitting `input_state`). + # This would lead to double-allocation of qubits, so a ValueError is + # raised. backend = StatevectorBackend() - backend.add_nodes(pattern.input_nodes, input_state) + backend.add_nodes(hadamardpattern.input_nodes, BasicStates.ZERO) with pytest.raises(ValueError, match="the backend is expected to have no pre-allocated qubits"): - pattern.simulate_pattern(backend=backend, rng=fx_rng) + hadamardpattern.simulate_pattern(backend=backend, rng=fx_rng) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index eaf579c72..f3ae5f181 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -12,7 +12,7 @@ from graphix.instruction import I, InstructionKind from graphix.random_objects import rand_circuit, rand_gate, rand_state_vector from graphix.sim.density_matrix import DensityMatrix -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevec, StatevectorBackend from graphix.simulator import DefaultMeasureMethod from graphix.states import BasicStates from graphix.transpiler import Circuit, OutputIndex, OutputKind, decompose_ccx, transpile_swaps @@ -160,21 +160,6 @@ def test_transpile_measurements_to_z_axis(self, fx_bg: PCG64, jumps: int, axis: ).statevec assert state_z.isclose(state) - @pytest.mark.parametrize("jumps", range(1, 11)) - def test_transpile_swaps(self, fx_bg: PCG64, jumps: int) -> None: - rng = Generator(fx_bg.jumped(jumps)) - nqubits = 4 - depth = 6 - circuit = rand_circuit(nqubits, depth, rng, use_ccx=True, use_rzz=True) - assert any(instr.kind == InstructionKind.SWAP for instr in circuit.instruction) - transpiled_swaps = transpile_swaps(circuit) - circuit2 = transpiled_swaps.circuit - assert not any(instr.kind == InstructionKind.SWAP for instr in circuit2.instruction) - state = circuit.simulate_statevector(rng=rng).statevec - state2 = circuit2.simulate_statevector(rng=rng).statevec - state2.permute(transpiled_swaps.extract_output_node_indices()) - assert state.isclose(state2) - @pytest.mark.parametrize("jumps", range(1, 11)) def test_transpile_j_to_rzh(self, fx_bg: PCG64, jumps: int) -> None: rng = Generator(fx_bg.jumped(jumps)) @@ -402,3 +387,30 @@ def test_transpile_swaps_with_measurements(fx_bg: PCG64, jumps: int, axis: Axis, ) state2.swap((0, 1)) assert state.isclose(state2) + + +def test_transpile_double_cz() -> None: + circuit = Circuit(2) + circuit.cz(0, 1) + circuit.cz(1, 0) + cf = circuit.transpile_to_causal_flow() + assert len(cf.flow.og.graph.edges) == 0 + + +def test_transpile_swaps_vs_no_transpile_swaps() -> None: + circuit = Circuit(2) + circuit.rx(0, 0.25) + circuit.ry(0, 0.25) + circuit.cz(0, 1) + circuit.swap(0, 1) + pattern_without_swap = circuit.transpile().pattern + pattern_with_swap = circuit.transpile(transpile_swaps=False).pattern + state_without_swap = pattern_without_swap.simulate_pattern() + state_with_swap = pattern_with_swap.simulate_pattern() + assert state_without_swap.isclose(state_with_swap) + + +def test_backend_branch_selector() -> None: + circ = Circuit(1) + with pytest.raises(ValueError, match="already instantiated"): + circ.simulate_statevector(backend=StatevectorBackend(), branch_selector=ConstBranchSelector(0)) From 9f4197294363542ae5690fd4282fc17c4e2f40a0 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Mon, 15 Jun 2026 08:22:06 +0200 Subject: [PATCH 61/63] Fix coverage --- graphix/transpiler.py | 11 +++++------ pyproject.toml | 4 +++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/graphix/transpiler.py b/graphix/transpiler.py index 82cd3198a..aa49d76bb 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -457,14 +457,14 @@ def transpile_to_causal_flow(self) -> TranspiledFlow: match instr.kind: case InstructionKind.M: target = indices[instr.target] - if target is None: # pragma: no cover + if target is None: raise RuntimeError("Ill-formed circuit") classical_outputs[target] = command.M(target, PauliMeasurement(instr.axis)) indices[instr.target] = None continue case InstructionKind.J: target = indices[instr.target] - if target is None: # pragma: no cover + if target is None: raise RuntimeError("Ill-formed circuit") graph.add_edge(target, n_nodes) # Also adds nodes measurements[target] = Measurement.XY(normalize_angle(-instr.angle)) @@ -475,7 +475,7 @@ def transpile_to_causal_flow(self) -> TranspiledFlow: case InstructionKind.CZ: t0, t1 = instr.targets i0, i1 = indices[t0], indices[t1] - if i0 is None or i1 is None: # pragma: no cover + if i0 is None or i1 is None: raise RuntimeError("Ill-formed circuit") # If edge exists, remove it; else, add it if graph.has_edge(i0, i1): @@ -483,7 +483,7 @@ def transpile_to_causal_flow(self) -> TranspiledFlow: else: graph.add_edge(i0, i1) continue - case _: # pragma: no cover + case _: assert_never(instr.kind) outputs = [i for i in indices if i is not None] outputs.extend(classical_outputs.keys()) # Necessary for flow-finding step @@ -1025,8 +1025,7 @@ def __init__(self, width: int) -> None: def visit_qubit(self, qubit: int) -> int: target = self.outputs[qubit] if target.kind == OutputKind.Bit: - msg = f"Qubit {qubit} has already been measured." - raise ValueError(msg) + raise RuntimeError(f"Qubit {qubit} has already been measured.") return target.index diff --git a/pyproject.toml b/pyproject.toml index 261a140a3..c5c3fcc41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -195,8 +195,10 @@ extraPaths = ["./stubs"] [tool.coverage.report] exclude_also = [ "if TYPE_CHECKING:", - "raise NotImplementedError\\(.*\\)", + "case _:", + "raise NotImplementedError(\\(.*\\))?", "return NotImplemented", + "raise RuntimeError(\\(.*\\))", "typing_extensions.assert_never\\(.*\\)", "assert_never\\(.*\\)", "@abc.abstractmethod", From 0ee2f8d46aefc8eb23e716e0fa520f948228005b Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Mon, 15 Jun 2026 08:26:37 +0200 Subject: [PATCH 62/63] Update CHANGELOG and doc --- CHANGELOG.md | 1 + docs/source/generator.rst | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e21c4fdb0..747b234e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `instruction.J` class. - Added `Circuit.transpile_j_to_rzh()` method to prepare circuits with J gates for export to OpenQASM. - Added `transpile` argument to `qasm3_exporter.circuit_to_qasm3` and `qasm3_exporter.circuit_to_qasm3_lines`, which defaults to true and applies `Circuit.transpile_j_to_rzh` and `Circuit.transpile_measurements_to_z_axis` methods. + - The transpiler now returns `TranspiledPattern` or `TranspiledFlow`, instead of `TranspileResult`. - #490: Introduced new `Instruction` and `Command` namespace classes for instruction and command instantiation. diff --git a/docs/source/generator.rst b/docs/source/generator.rst index f9fac960f..97918ae39 100644 --- a/docs/source/generator.rst +++ b/docs/source/generator.rst @@ -42,6 +42,8 @@ Pattern Generation .. automethod:: m -.. autoclass:: TranspileResult +.. autoclass:: TranspiledPattern + +.. autoclass:: TranspiledFlow .. autoclass:: SimulateResult From 1dcb37b306a45e87eae5384bfb3e57d372b601c7 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Tue, 16 Jun 2026 08:20:37 +0200 Subject: [PATCH 63/63] Fix `NodeIndex` after `permute` --- graphix/sim/base_backend.py | 28 ++++++++++++++++++++++++++-- tests/test_simulator.py | 7 +++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index e22d04e03..5dea50ce1 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -230,8 +230,7 @@ class NodeIndex: def __init__(self) -> None: """Initialize an empty mapping between nodes and qubit indices.""" - self.__dict = {} - self.__list = [] + self.clear() def __getitem__(self, index: int) -> int: """Return the qubit node associated with the specified index. @@ -316,6 +315,11 @@ def swap(self, i: int, j: int) -> None: self.__dict[node_i] = j self.__dict[node_j] = i + def clear(self) -> None: + """Delete all the elements, restoring the index to its initial state.""" + self.__dict = {} + self.__list = [] + class NoiseNotSupportedError(Exception): """Exception raised when `apply_channel` is called on a backend that does not support noise.""" @@ -838,6 +842,26 @@ def apply_clifford(self, node: int, clifford: Clifford) -> None: def finalize(self, output_nodes: Sequence[int]) -> None: """To be run at the end of pattern simulation.""" self.state.permute([self.node_index.index(node) for node in output_nodes]) + self.node_index.clear() + self.node_index.extend(output_nodes) + + def sort_qubits(self, output_nodes: Iterable[int]) -> None: + """Sort the qubit order in internal statevector.""" + for i, ind in enumerate(output_nodes): + if self.node_index.index(ind) != i: + move_from = self.node_index.index(ind) + self.state.swap((i, move_from)) + self.node_index.swap(i, move_from) + + # @override + # def finalize(self, output_nodes: Iterable[int]) -> None: + # """To be run at the end of pattern simulation.""" + # from copy import copy + # duplicate = copy(self) + # duplicate.sort_qubits(output_nodes) + # self.state.permute([self.node_index.index(node) for node in output_nodes]) + # if not self.state.isclose(duplicate.state): + # assert False, output_nodes @property @override diff --git a/tests/test_simulator.py b/tests/test_simulator.py index 866a4e60c..6c9ecbb81 100644 --- a/tests/test_simulator.py +++ b/tests/test_simulator.py @@ -51,3 +51,10 @@ def test_prepared_qubits_and_input_state(hadamardpattern: Pattern, fx_rng: Gener backend.add_nodes(hadamardpattern.input_nodes, BasicStates.ZERO) with pytest.raises(ValueError, match="the backend is expected to have no pre-allocated qubits"): hadamardpattern.simulate_pattern(backend=backend, rng=fx_rng) + + +def test_node_index_after_finalize() -> None: + pattern = Pattern(input_nodes=[0, 1], output_nodes=[1, 0]) + backend = StatevectorBackend() + pattern.simulate_pattern(backend=backend) + assert list(backend.node_index) == [1, 0]