From 4c0e0015bee6635f186a30b6e52c9b54e028b930 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 18 Jul 2026 12:05:07 +0200 Subject: [PATCH 1/3] Add in-place variants for `infer_pauli_measurements`, `to_bloch`,... Method `Pattern.infer_pauli_measurements` now operates in place by default, with an optional `copy` parameter. If `copy=True`, a modified copy is returned instead. Added the following methods to `Pattern`. By default, they modify the pattern in place, with an optional `copy` parameter. If `copy=True`, a modified copy is returned instead. - `apply`, an in-place variant of `map`, - `blochify`, an in-place variant of `to_bloch`, - `substitute`, an in-place variant of `subs`, - `replace_parameters`, an in-place variant of `xreplace`. This is a step toward the cleaner simulation API proposed in #493. It also makes the examples in the Graphix paper cleaner. --- CHANGELOG.md | 8 + examples/deutsch_jozsa.py | 2 +- examples/mbqc_vqe.py | 2 +- examples/qaoa.py | 2 +- examples/qft_with_tn.py | 2 +- examples/tn_simulation.py | 4 +- examples/visualization.py | 2 +- graphix/pattern.py | 186 +++++++++++++++++++++--- tests/test_optimization.py | 6 +- tests/test_parameter.py | 2 +- tests/test_pattern.py | 40 ++--- tests/test_qasm3_exporter.py | 2 +- tests/test_qasm3_exporter_to_qiskit.py | 2 +- tests/test_remove_pauli_measurements.py | 2 +- tests/test_tnsim.py | 6 +- tests/test_visualization.py | 6 +- 16 files changed, 215 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f40251f..41b9edd21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #545: Added an amplitude damping noise model. Introduces `amplitude_damping_channel` / `two_qubit_amplitude_damping_channel`, the `AmplitudeDampingNoise` / `TwoQubitAmplitudeDampingNoise` noise elements, and `AmplitudeDampingNoiseModel`. +- #568: Added the following methods to `Pattern`. By default, they modify the pattern in place, with an optional `copy` parameter. If `copy=True`, a modified copy is returned instead. + - `apply`, an in-place variant of `map`, + - `blochify`, an in-place variant of `to_bloch`, + - `substitute`, an in-place variant of `subs`, + - `replace_parameters`, an-in place variant of `xreplace`. + ### Fixed - #454, #481: Ensure `Pattern.minimize_space` only reduces max-space and does not increase it. @@ -100,6 +106,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #512: Method `Circuit.simulate_statevector` accepts a `backend: DenseStateBackend[_DenseStateT] | Literal["statevector", "densitymatrix"]` parameter. +- #568: Method `Pattern.infer_pauli_measurements` now operates in place by default, with an optional `copy` parameter. If `copy=True`, a modified copy is returned instead. + ## [0.3.5] - 2026-03-26 ### Added diff --git a/examples/deutsch_jozsa.py b/examples/deutsch_jozsa.py index 5f4d6dee4..c9f621812 100644 --- a/examples/deutsch_jozsa.py +++ b/examples/deutsch_jozsa.py @@ -73,7 +73,7 @@ # %% # Now we preprocess all Pauli measurements, which requires that we move inputs to N commands -pattern = pattern.infer_pauli_measurements() +pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() print( pattern.to_ascii( diff --git a/examples/mbqc_vqe.py b/examples/mbqc_vqe.py index 6f991bb20..e3e44e5c7 100644 --- a/examples/mbqc_vqe.py +++ b/examples/mbqc_vqe.py @@ -92,7 +92,7 @@ def build_mbqc_pattern(self, params: Iterable[ParameterizedAngle]) -> Pattern: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.minimize_space() return pattern diff --git a/examples/qaoa.py b/examples/qaoa.py index da993fe37..51e11f46e 100644 --- a/examples/qaoa.py +++ b/examples/qaoa.py @@ -40,7 +40,7 @@ # %% # perform Pauli measurements and plot the new (minimal) graph to perform the same quantum computation -pattern = pattern.infer_pauli_measurements() +pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.draw(flow_from_pattern=False) diff --git a/examples/qft_with_tn.py b/examples/qft_with_tn.py index 5cb991c2a..2a4e90be7 100644 --- a/examples/qft_with_tn.py +++ b/examples/qft_with_tn.py @@ -67,7 +67,7 @@ def qft(circuit: Circuit, n: int) -> None: # Using graph rewriting rules, 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.infer_pauli_measurements() pattern.remove_pauli_measurements(standardize=True) diff --git a/examples/tn_simulation.py b/examples/tn_simulation.py index 84a5e8b19..6e13d40ab 100644 --- a/examples/tn_simulation.py +++ b/examples/tn_simulation.py @@ -85,7 +85,7 @@ def ansatz( # %% # Optimizing by removing Pauli measurements in the pattern. pattern.remove_input_nodes() -pattern = pattern.infer_pauli_measurements() +pattern.infer_pauli_measurements() pattern.remove_pauli_measurements(standardize=True) # %% @@ -204,7 +204,7 @@ def cost( pattern.standardize() pattern.shift_signals() pattern.remove_input_nodes() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements(standardize=True) 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 d6e0e68de..dd2a663ef 100644 --- a/examples/visualization.py +++ b/examples/visualization.py @@ -37,7 +37,7 @@ # %% # next, show the gflow: -pattern = pattern.infer_pauli_measurements() +pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.draw(flow_from_pattern=False, measurement_labels=True) diff --git a/graphix/pattern.py b/graphix/pattern.py index ddfebcf3d..156e05908 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1622,12 +1622,88 @@ def is_parameterized(self) -> bool: ) def subs(self, variable: Parameter, substitute: ExpressionOrSupportsFloat) -> Pattern: - """Return a copy of the pattern where all occurrences of the given variable in measurement angles are substituted by the given value.""" - return self.map(lambda m: m.subs(variable, substitute)) + """Return a copy of the pattern where all occurrences of the given variable in measurement angles are substituted by the given value. + + Equivalent to ``self.substitute(variable, substitute, copy=True)`` + + Parameters + ---------- + variable : Parameter + The symbolic expression to be replaced within the measurement angles. + + substitute : ExpressionOrSupportsFloat + The value or symbolic expression to substitute in place of `variable`. + + Returns + ------- + Pattern + The pattern with the updated measurement parameters. + """ + return self.substitute(variable, substitute, copy=True) + + def substitute(self, variable: Parameter, substitute: ExpressionOrSupportsFloat, *, copy: bool = False) -> Pattern: + """Substitute all occurrences of the given variable in measurement angles by the given value. + + Parameters + ---------- + variable : Parameter + The symbolic expression to be replaced within the measurement angles. + + substitute : ExpressionOrSupportsFloat + The value or symbolic expression to substitute in place of `variable`. + + copy : bool, optional + If ``True``, the current pattern remains unchanged and a + new pattern is returned. The default is ``False``, meaning + that changes are performed in place. + + Returns + ------- + Pattern + The pattern with the updated measurement parameters. + Equal to ``self`` if ``copy`` is ``False``. + """ + return self.apply(lambda m: m.subs(variable, substitute), copy=copy) def xreplace(self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) -> Pattern: - """Return a copy of the pattern where all occurrences of the given keys in measurement angles are substituted by the given values in parallel.""" - return self.map(lambda m: m.xreplace(assignment)) + """Return a copy of the pattern where all occurrences of the given keys in measurement angles are substituted by the given values in parallel. + + Equivalent to ``self.replace_parameters(assignment, copy=True)``. + + Parameters + ---------- + assignment : Mapping[Parameter, ExpressionOrSupportsFloat] + A dictionary-like mapping where keys are the `Parameter` objects to be replaced and values are the new expressions or numerical values. + + copy : bool, optional + If ``True``, the current pattern remains unchanged and a + new pattern is returned. The default is ``False``, meaning + that changes are performed in place. + + Returns + ------- + Pattern + The pattern with the updated measurement parameters. + """ + return self.replace_parameters(assignment, copy=True) + + def replace_parameters( + self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat], *, copy: bool = False + ) -> Pattern: + """Substitute all occurrences of the given keys in measurement angles by the given values in parallel. + + Parameters + ---------- + assignment : Mapping[Parameter, ExpressionOrSupportsFloat] + A dictionary-like mapping where keys are the `Parameter` objects to be replaced and values are the new expressions or numerical values. + + Returns + ------- + Pattern + The pattern with the updated measurement parameters. + Equal to ``self`` if ``copy`` is ``False``. + """ + return self.apply(lambda m: m.xreplace(assignment), copy=copy) def copy(self) -> Pattern: """Return a copy of the pattern.""" @@ -1700,9 +1776,48 @@ def check_measured(cmd: CommandType, node: int) -> None: case CommandKind.C: check_active(cmd, cmd.node) + def apply(self, f: Callable[[Measurement], Measurement], *, copy: bool = False) -> Pattern: + """Apply the function ``f` to each measurement. + + Parameters + ---------- + f: Callable[[Measurement], Measurement] + Function applied to each measurement. + copy : bool, optional + If ``True``, the current pattern remains unchanged and a + new pattern is returned. The default is ``False``, meaning + that changes are performed in place. + + Returns + ------- + Pattern + The resulting pattern. + If ``copy`` is ``False``, the result is ``self``. + + Example + ------- + >>> from graphix import Pattern, command + >>> from graphix.measurements import BlochMeasurement, Measurement + >>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.XZ(0.25))]) + >>> pattern.apply(lambda m: BlochMeasurement(m.angle + 1, m.plane)) + Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(1.25))]) + >>> pattern + Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(1.25))]) + """ + new_seq = [cmd.map(f) if cmd.kind == CommandKind.M else cmd for cmd in self] + + if copy: + new_pattern = Pattern(input_nodes=self.input_nodes, cmds=new_seq) + new_pattern.reorder_output_nodes(self.output_nodes) + return new_pattern + self.__seq = new_seq + return self + def map(self, f: Callable[[Measurement], Measurement]) -> Pattern: """Return a pattern where the function ``f`` has been applied to each measurement. + Equivalent to ``self.apply(f, copy=True)``. + Parameters ---------- f: Callable[[Measurement], Measurement] @@ -1720,20 +1835,13 @@ def map(self, f: Callable[[Measurement], Measurement]) -> Pattern: >>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.XZ(0.25))]) >>> pattern.map(lambda m: BlochMeasurement(m.angle + 1, m.plane)) Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(1.25))]) + >>> pattern + Pattern(input_nodes=[0], cmds=[M(0, Measurement.XZ(0.25))]) """ - new_pattern = Pattern(input_nodes=self.input_nodes) + return self.apply(f, copy=True) - for cmd in self: - if cmd.kind == CommandKind.M: - new_pattern.add(cmd.map(f)) - else: - new_pattern.add(cmd) - - new_pattern.reorder_output_nodes(self.output_nodes) - return new_pattern - - def infer_pauli_measurements(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> Pattern: - """Return an equivalent pattern in which Bloch measurements close to a Pauli measurement are replaced by Pauli measurements. + def infer_pauli_measurements(self, *, rel_tol: float = 1e-09, abs_tol: float = 0.0, copy: bool = False) -> Pattern: + """Replace Bloch measurements close to a Pauli measurement by Pauli measurements. Parameters ---------- @@ -1743,11 +1851,17 @@ def infer_pauli_measurements(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) abs_tol : float, optional Absolute tolerance for comparing angles, passed to :func:`math.isclose`. Default is ``0.0``. + copy : bool, optional + If ``True``, the current pattern remains unchanged and a + new pattern is returned. The default is ``False``, meaning + that changes are performed in place. Returns ------- Pattern - An equivalent pattern in which Bloch measurements close to a Pauli measurement are replaced by Pauli measurements. + A pattern in which Bloch measurements close to a Pauli + measurement are replaced by Pauli measurements. If + ``copy`` is ``False``, the result is ``self``. Example ------- @@ -1757,11 +1871,41 @@ def infer_pauli_measurements(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) >>> pattern.infer_pauli_measurements() Pattern(input_nodes=[0], cmds=[M(0, Measurement.Y)]) """ - return self.map(lambda m: m.to_pauli_or_bloch(rel_tol, abs_tol)) + return self.apply(lambda m: m.to_pauli_or_bloch(rel_tol, abs_tol), copy=copy) + + def blochify(self, *, copy: bool = False) -> Pattern: + """Represent all measurements as Bloch measurements. + + Parameters + ---------- + copy : bool, optional + If ``True``, the current pattern remains unchanged and a + new pattern is returned. The default is ``False``, meaning + that changes are performed in place. + + Returns + ------- + Pattern + A pattern in which all measurements are Bloch measurements. + If ``copy`` is ``False``, the result is ``self``. + + Example + ------- + >>> from graphix import Pattern, command + >>> from graphix.measurements import BlochMeasurement, Measurement + >>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.Y)]) + >>> pattern.blochify() + Pattern(input_nodes=[0], cmds=[M(0, Measurement.XY(0.5))]) + >>> pattern + Pattern(input_nodes=[0], cmds=[M(0, Measurement.XY(0.5))]) + """ + return self.apply(lambda m: m.to_bloch(), copy=copy) def to_bloch(self) -> Pattern: """Return an equivalent pattern in which all measurements are represented as Bloch measurements. + Equivalent to ``self.blochify(copy=True)``. + Example ------- >>> from graphix import Pattern, command @@ -1769,15 +1913,17 @@ def to_bloch(self) -> Pattern: >>> pattern = Pattern(input_nodes=[0], cmds=[command.M(0, Measurement.Y)]) >>> pattern.to_bloch() Pattern(input_nodes=[0], cmds=[M(0, Measurement.XY(0.5))]) + >>> pattern + Pattern(input_nodes=[0], cmds=[M(0, Measurement.Y)]) """ - return self.map(lambda m: m.to_bloch()) + return self.blochify(copy=True) def perform_pauli_pushing( self, + *, leave_nodes: AbstractSet[Node] | None = None, copy: bool = False, standardize: bool = False, - *, stacklevel: int = 1, ) -> Pattern: """Move Pauli measurements before the other measurements. diff --git a/tests/test_optimization.py b/tests/test_optimization.py index 5c386d8d3..e67325250 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -66,7 +66,7 @@ def test_flow_after_pauli_preprocessing(fx_bg: PCG64, jumps: int) -> None: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() # We should convert to Bloch measurement the remaining Pauli # measurements on input nodes. @@ -83,7 +83,7 @@ def test_remove_useless_domains(fx_bg: PCG64, jumps: int) -> None: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern2 = remove_useless_domains(pattern) pattern2 = StandardizedPattern.from_pattern(pattern2).to_space_optimal_pattern() @@ -143,7 +143,7 @@ def test_remove_local_clifford_commands(fx_bg: PCG64, jumps: int) -> None: depth = 4 circuit = rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() assert any(cmd.kind == CommandKind.C for cmd in pattern) new_pattern = pattern.remove_local_clifford_commands(copy=True) diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 85d0e7ecf..f5083951c 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -170,7 +170,7 @@ def test_random_circuit_with_parameters(fx_bg: PCG64, jumps: int, use_xreplace: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_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 6e16278d1..5b187ce65 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -71,7 +71,7 @@ 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.infer_pauli_measurements() pattern.standardize() assert pattern.is_standard() state = circuit.simulate_statevector().statevec @@ -155,7 +155,7 @@ def test_minimize_space_with_gflow(self, fx_bg: PCG64, jumps: int) -> None: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals(method="mc") - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.minimize_space() state = circuit.simulate_statevector().statevec @@ -235,7 +235,7 @@ def test_pauli_measurement_random_circuit( pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals(method="mc") - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.minimize_space() state = circuit.simulate_statevector().statevec @@ -251,7 +251,7 @@ def test_pauli_measurement_random_circuit_all_paulis(self, fx_bg: PCG64, jumps: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals(method="mc") - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() input_node_set = set(pattern.input_nodes) assert not any( @@ -290,7 +290,7 @@ def test_pauli_measurement(self) -> None: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals(method="mc") - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern_opt = pattern.remove_pauli_measurements(copy=True) isolated_nodes = pattern_opt.extract_isolated_nodes() assert isolated_nodes == set() @@ -309,7 +309,7 @@ def test_pauli_measured_against_nonmeasured(self, fx_bg: PCG64, jumps: int) -> N pattern = circuit.transpile().pattern pattern.minimize_space() pattern1 = copy.deepcopy(pattern) - pattern1 = pattern1.infer_pauli_measurements() + pattern1.infer_pauli_measurements() pattern1.remove_pauli_measurements() state = pattern.simulate_pattern(rng=rng) state1 = pattern1.simulate_pattern(rng=rng) @@ -415,7 +415,7 @@ def test_pauli_measurement_then_standardize(self, fx_bg: PCG64, jumps: int) -> N depth = 3 circuit = rand_circuit(nqubits, depth, rng) pattern = circuit.transpile().pattern - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.standardize() pattern.minimize_space() @@ -683,7 +683,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.infer_pauli_measurements() p1.remove_pauli_measurements() circuit_2 = Circuit(1) @@ -804,12 +804,12 @@ def test_extract_partial_order_layers_results(self) -> None: c = Circuit(1) c.rz(0, 0.2) p = c.transpile().pattern - p = p.infer_pauli_measurements() + p.infer_pauli_measurements() p.remove_pauli_measurements() assert p.extract_partial_order_layers() == (frozenset({1}), 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.infer_pauli_measurements() p.remove_pauli_measurements() assert p.extract_partial_order_layers() == (frozenset({1}), frozenset({2})) @@ -936,10 +936,10 @@ def test_extract_causal_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None depth = 2 circuit_1 = rand_circuit(nqubits, depth, rng, use_ccx=False) p_ref = circuit_1.transpile().pattern - p_test = p_ref.to_bloch().extract_causal_flow().to_corrections().to_pattern().infer_pauli_measurements() + p_ref.infer_pauli_measurements() + p_test = p_ref.to_bloch().extract_causal_flow().to_corrections().to_pattern() + p_test.infer_pauli_measurements() - p_ref = p_ref.infer_pauli_measurements() - p_test = p_test.infer_pauli_measurements() p_ref.remove_pauli_measurements() p_test.remove_pauli_measurements() @@ -956,10 +956,10 @@ def test_extract_gflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: depth = 2 circuit_1 = rand_circuit(nqubits, depth, rng, use_ccx=False) p_ref = circuit_1.transpile().pattern - p_test = p_ref.to_bloch().extract_gflow().to_corrections().to_pattern().infer_pauli_measurements() + p_ref.infer_pauli_measurements() + p_test = p_ref.to_bloch().extract_gflow().to_corrections().to_pattern() + p_test.infer_pauli_measurements() - p_ref = p_ref.infer_pauli_measurements() - p_test = p_test.infer_pauli_measurements() p_ref.remove_pauli_measurements() p_test.remove_pauli_measurements() @@ -976,7 +976,9 @@ def test_extract_pauli_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: depth = 2 circuit_1 = rand_circuit(nqubits, depth, rng, use_ccx=False) p_ref = circuit_1.transpile().pattern - p_test = p_ref.to_bloch().extract_pauli_flow().to_corrections().to_pattern().infer_pauli_measurements() + p_ref.infer_pauli_measurements() + p_test = p_ref.to_bloch().extract_pauli_flow().to_corrections().to_pattern() + p_test.infer_pauli_measurements() p_ref.remove_pauli_measurements() p_test.remove_pauli_measurements() @@ -1089,9 +1091,9 @@ def test_extract_xzc_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: xzc.check_well_formed() p_test = xzc.to_pattern() - p_ref = p_ref.infer_pauli_measurements() + p_ref.infer_pauli_measurements() p_ref.remove_pauli_measurements() - p_test = p_test.infer_pauli_measurements() + p_test.infer_pauli_measurements() p_test.remove_pauli_measurements() s_ref = p_ref.simulate_pattern(rng=rng) diff --git a/tests/test_qasm3_exporter.py b/tests/test_qasm3_exporter.py index 651335e9c..723e167c1 100644 --- a/tests/test_qasm3_exporter.py +++ b/tests/test_qasm3_exporter.py @@ -55,7 +55,7 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: depth = 5 circuit = rand_circuit(nqubits, depth, rng=rng) pattern = circuit.transpile().pattern - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.minimize_space() _qasm3 = pattern_to_qasm3(pattern) diff --git a/tests/test_qasm3_exporter_to_qiskit.py b/tests/test_qasm3_exporter_to_qiskit.py index 77c5bab76..343c73683 100644 --- a/tests/test_qasm3_exporter_to_qiskit.py +++ b/tests/test_qasm3_exporter_to_qiskit.py @@ -119,7 +119,7 @@ def test_to_qasm3_random_circuit(fx_bg: PCG64, jumps: int) -> None: depth = 5 circuit = rand_circuit(nqubits, depth, rng=rng, use_j=True) pattern = circuit.transpile().pattern - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.minimize_space() diff --git a/tests/test_remove_pauli_measurements.py b/tests/test_remove_pauli_measurements.py index 10870ab36..0c7820797 100644 --- a/tests/test_remove_pauli_measurements.py +++ b/tests/test_remove_pauli_measurements.py @@ -257,7 +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() + 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_tnsim.py b/tests/test_tnsim.py index 2bb76a21b..f80d88b2b 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -330,7 +330,7 @@ def test_with_graphtrans(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> N pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() state = circuit.simulate_statevector().statevec tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", rng=fx_rng) @@ -349,7 +349,7 @@ def test_with_graphtrans_sequential(self, fx_bg: PCG64, jumps: int, fx_rng: Gene pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() state = circuit.simulate_statevector().statevec tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", graph_prep="sequential", rng=fx_rng) @@ -398,7 +398,7 @@ def test_evolve(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() state = circuit.simulate_statevector().statevec tn_mbqc = pattern.simulate_pattern(backend="tensornetwork", rng=fx_rng) diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 598082bc8..a67e11c9f 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -158,7 +158,7 @@ def example_hadamard() -> Pattern: def example_local_clifford() -> Pattern: pattern = example_hadamard() - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() return pattern @@ -256,9 +256,9 @@ def test_draw_graph_reference(flow_and_not_pauli_presimulate: bool) -> Figure: # Pauli flow extraction from pattern is not implemented yet; # therefore, the pattern should not contain Pauli measurements # to have causal flow. - pattern = pattern.to_bloch() + pattern.blochify() else: - pattern = pattern.infer_pauli_measurements() + pattern.infer_pauli_measurements() pattern.remove_pauli_measurements() pattern.standardize() pattern.draw( From ef37b2e72e509dc80e81e993cba08b72856af022 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 18 Jul 2026 22:07:52 +0200 Subject: [PATCH 2/3] Fix docstrings of `xreplace` and `replace_parameters` --- graphix/pattern.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 156e05908..5ca4ead3e 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1675,11 +1675,6 @@ def xreplace(self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) -> assignment : Mapping[Parameter, ExpressionOrSupportsFloat] A dictionary-like mapping where keys are the `Parameter` objects to be replaced and values are the new expressions or numerical values. - copy : bool, optional - If ``True``, the current pattern remains unchanged and a - new pattern is returned. The default is ``False``, meaning - that changes are performed in place. - Returns ------- Pattern @@ -1697,6 +1692,11 @@ def replace_parameters( assignment : Mapping[Parameter, ExpressionOrSupportsFloat] A dictionary-like mapping where keys are the `Parameter` objects to be replaced and values are the new expressions or numerical values. + copy : bool, optional + If ``True``, the current pattern remains unchanged and a + new pattern is returned. The default is ``False``, meaning + that changes are performed in place. + Returns ------- Pattern From 86f69ce6fa195b3b9ea0e501d2fd911314b5e1c1 Mon Sep 17 00:00:00 2001 From: Thierry Martinez Date: Sat, 18 Jul 2026 22:30:21 +0200 Subject: [PATCH 3/3] Fix docstrings for `subs` --- graphix/opengraph.py | 2 +- graphix/pattern.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/graphix/opengraph.py b/graphix/opengraph.py index 3f1cba960..ca5a1b61e 100644 --- a/graphix/opengraph.py +++ b/graphix/opengraph.py @@ -758,7 +758,7 @@ def subs(self: OpenGraph[_M], variable: Parameter, substitute: ExpressionOrSuppo variable : Parameter The symbolic expression to be replaced within the measurement angles. substitute : ExpressionOrSupportsFloat - The value or symbolic expression to substitute in place of `variable`. + The value or symbolic expression to substitute in place of ``variable``. Returns ------- diff --git a/graphix/pattern.py b/graphix/pattern.py index 5ca4ead3e..6365d1b9a 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1632,7 +1632,7 @@ def subs(self, variable: Parameter, substitute: ExpressionOrSupportsFloat) -> Pa The symbolic expression to be replaced within the measurement angles. substitute : ExpressionOrSupportsFloat - The value or symbolic expression to substitute in place of `variable`. + The value or symbolic expression to substitute in place of ``variable``. Returns ------- @@ -1650,7 +1650,7 @@ def substitute(self, variable: Parameter, substitute: ExpressionOrSupportsFloat, The symbolic expression to be replaced within the measurement angles. substitute : ExpressionOrSupportsFloat - The value or symbolic expression to substitute in place of `variable`. + The value or symbolic expression to substitute in place of ``variable``. copy : bool, optional If ``True``, the current pattern remains unchanged and a