From adce1bc7d5bf27b293b100e2f068c6f02776d860 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 30 Jan 2026 15:28:53 +0100 Subject: [PATCH 1/4] removed flag --- graphix/pattern.py | 60 ++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index b636c97ec..81b1023bc 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -42,7 +42,11 @@ from numpy.random import Generator from graphix.flow.core import CausalFlow, GFlow, XZCorrections - from graphix.parameter import ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter + from graphix.parameter import ( + ExpressionOrSupportsComplex, + ExpressionOrSupportsFloat, + Parameter, + ) from graphix.sim import ( Backend, Data, @@ -113,7 +117,6 @@ def __init__( else: self.__input_nodes = list(input_nodes) # input nodes (list() makes our own copy of the list) self.__n_node = len(self.__input_nodes) # total number of nodes in the graph state - self._pauli_preprocessed = False # flag for `measure_pauli` preprocessing completion self.__seq = [] # output nodes are initially a copy input nodes, since none are measured yet @@ -135,7 +138,6 @@ def add(self, cmd: Command) -> None: cmd : :class:`graphix.command.Command` MBQC command. """ - self._pauli_preprocessed = False if cmd.kind == CommandKind.N: self.__n_node += 1 self.__output_nodes.append(cmd.node) @@ -149,7 +151,6 @@ def extend(self, *cmds: Command | Iterable[Command]) -> None: :param cmds: sequences of commands """ - self._pauli_preprocessed = False for item in cmds: if isinstance(item, Iterable): for cmd in item: @@ -159,7 +160,6 @@ def extend(self, *cmds: Command | Iterable[Command]) -> None: def clear(self) -> None: """Clear the sequence of pattern commands.""" - self._pauli_preprocessed = False self.__n_node = len(self.__input_nodes) self.__seq = [] self.__output_nodes = list(self.__input_nodes) @@ -171,7 +171,6 @@ def replace(self, cmds: list[Command], input_nodes: list[int] | None = None) -> :param input_nodes: optional, list of input qubits (by default, keep the same input nodes as before) """ - self._pauli_preprocessed = False if input_nodes is not None: self.__input_nodes = list(input_nodes) self.clear() @@ -217,7 +216,6 @@ def compose( - Input (and, respectively, output) nodes in the returned pattern have the order of the pattern `self` followed by those of the pattern `other`. Merged nodes are removed. - If `preserve_mapping = True` and :math:`|M_1| = |I_2| = |O_2|`, then the outputs of the returned pattern are the outputs of pattern `self`, where the nth merged output is replaced by the output of pattern `other` corresponding to its nth input instead. """ - self._pauli_preprocessed = False nodes_p1 = self.extract_nodes() | self.results.keys() # Results contain preprocessed Pauli nodes nodes_p2 = other.extract_nodes() | other.results.keys() @@ -382,19 +380,28 @@ def __eq__(self, other: object) -> bool: ) def to_ascii( - self, left_to_right: bool = False, limit: int = 40, target: Container[command.CommandKind] | None = None + self, + left_to_right: bool = False, + limit: int = 40, + target: Container[command.CommandKind] | None = None, ) -> str: """Return the ASCII string representation of the pattern.""" return pattern_to_str(self, OutputFormat.ASCII, left_to_right, limit, target) def to_latex( - self, left_to_right: bool = False, limit: int = 40, target: Container[command.CommandKind] | None = None + self, + left_to_right: bool = False, + limit: int = 40, + target: Container[command.CommandKind] | None = None, ) -> str: """Return a string containing the LaTeX representation of the pattern.""" return pattern_to_str(self, OutputFormat.LaTeX, left_to_right, limit, target) def to_unicode( - self, left_to_right: bool = False, limit: int = 40, target: Container[command.CommandKind] | None = None + self, + left_to_right: bool = False, + limit: int = 40, + target: Container[command.CommandKind] | None = None, ) -> str: """Return the Unicode string representation of the pattern.""" return pattern_to_str(self, OutputFormat.Unicode, left_to_right, limit, target) @@ -855,7 +862,10 @@ def extract_signals(self) -> dict[int, set[int]]: if cmd.kind == CommandKind.M: extracted_signal = extract_signal(cmd.plane, cmd.s_domain, cmd.t_domain) if extracted_signal.signal: - self.__seq.insert(pos + 1, command.S(node=cmd.node, domain=extracted_signal.signal)) + self.__seq.insert( + pos + 1, + command.S(node=cmd.node, domain=extracted_signal.signal), + ) cmd.s_domain = extracted_signal.s_domain cmd.t_domain = extracted_signal.t_domain pos += 1 @@ -1315,13 +1325,12 @@ def minimize_space(self) -> None: if not self.is_standard(): self.standardize() meas_order = None - if not self._pauli_preprocessed: - try: - cf = self.extract_causal_flow() - except FlowError: - meas_order = None - else: - meas_order = list(itertools.chain(*reversed(cf.partial_order_layers[1:]))) + try: + cf = self.extract_causal_flow() + except FlowError: + meas_order = None + else: + meas_order = list(itertools.chain(*reversed(cf.partial_order_layers[1:]))) if meas_order is None: meas_order = self._measurement_order_space() self._reorder_pattern(self.sort_measurement_commands(meas_order)) @@ -1335,7 +1344,8 @@ def _reorder_pattern(self, meas_commands: list[command.M]) -> None: list of measurement ('M') commands """ new = dataclasses.replace( - optimization.StandardizedPattern.from_pattern(self), m_list=tuple(meas_commands) + optimization.StandardizedPattern.from_pattern(self), + m_list=tuple(meas_commands), ).to_space_optimal_pattern() self.__seq = new.__seq @@ -1549,7 +1559,11 @@ def draw_graph( filename=filename, ) - def to_qasm3(self, filename: Path | str, input_state: dict[int, State] | State = BasicStates.PLUS) -> None: + def to_qasm3( + self, + filename: Path | str, + input_state: dict[int, State] | State = BasicStates.PLUS, + ) -> None: """Export measurement pattern to OpenQASM 3.0 file. See :func:`graphix.qasm3_exporter.pattern_to_qasm3`. @@ -1600,7 +1614,6 @@ def copy(self) -> Pattern: result.__input_nodes = self.__input_nodes.copy() result.__output_nodes = self.__output_nodes.copy() result.__n_node = self.__n_node - result._pauli_preprocessed = self._pauli_preprocessed result.results = self.results.copy() return result @@ -1813,11 +1826,12 @@ def measure_pauli(pattern: Pattern, *, copy: bool = False, ignore_pauli_with_dep pat.reorder_output_nodes(standardized_pattern.output_nodes) assert pat.n_node == len(graph_state.nodes) pat.results = results - pat._pauli_preprocessed = True return pat -def pauli_nodes(pattern: optimization.StandardizedPattern) -> tuple[list[tuple[command.M, PauliMeasurement]], set[int]]: +def pauli_nodes( + pattern: optimization.StandardizedPattern, +) -> tuple[list[tuple[command.M, PauliMeasurement]], set[int]]: """Return the list of measurement commands that are in Pauli bases and that are not dependent on any non-Pauli measurements. Parameters From 3b4c628a30a783b6c43e7a2a31d4460c6e8989b4 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Fri, 30 Jan 2026 16:02:36 +0100 Subject: [PATCH 2/4] final changes --- graphix/pattern.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 81b1023bc..92dcbeb8a 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1493,7 +1493,7 @@ def perform_pauli_measurements(self, ignore_pauli_with_deps: bool = False) -> No """ if self.input_nodes: raise ValueError("Remove inputs with `self.remove_input_nodes()` before performing Pauli presimulation.") - measure_pauli(self, copy=False, ignore_pauli_with_deps=ignore_pauli_with_deps) + self.__dict__.update(measure_pauli(self, ignore_pauli_with_deps=ignore_pauli_with_deps).__dict__) def draw_graph( self, @@ -1722,7 +1722,7 @@ def __str__(self) -> str: assert_never(self.reason) -def measure_pauli(pattern: Pattern, *, copy: bool = False, ignore_pauli_with_deps: bool = False) -> Pattern: +def measure_pauli(pattern: Pattern, *, ignore_pauli_with_deps: bool = False) -> Pattern: """Perform Pauli measurement of a pattern by fast graph state simulator. Uses the decorated-graph method implemented in graphix.graphsim to perform the measurements in Pauli bases, and then sort remaining nodes back into @@ -1733,9 +1733,6 @@ def measure_pauli(pattern: Pattern, *, copy: bool = False, ignore_pauli_with_dep Parameters ---------- pattern : graphix.pattern.Pattern object - copy : bool - True: changes will be applied to new copied object and will be returned - False: changes will be applied to the supplied Pattern object ignore_pauli_with_deps : bool Optional (*False* by default). If *True*, Pauli measurements with domains depending on other measures are preserved as-is in the pattern. @@ -1751,14 +1748,14 @@ def measure_pauli(pattern: Pattern, *, copy: bool = False, ignore_pauli_with_dep .. seealso:: :class:`graphix.pattern.Pattern.remove_input_nodes` .. seealso:: :class:`graphix.graphsim.GraphState` """ - pat = Pattern() if copy else pattern + pat = Pattern() standardized_pattern = optimization.StandardizedPattern.from_pattern(pattern) if not ignore_pauli_with_deps: standardized_pattern = standardized_pattern.perform_pauli_pushing() output_nodes = set(pattern.output_nodes) graph = standardized_pattern.extract_graph() graph_state = GraphState(nodes=graph.nodes, edges=graph.edges, vops=standardized_pattern.c_dict) - results: dict[int, Outcome] = pat.results + results: dict[int, Outcome] = pattern.results to_measure, non_pauli_meas = pauli_nodes(standardized_pattern) if not to_measure: return pattern From d5796d3e558424174bf22f58cfaa18bc952d5bb0 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 2 Feb 2026 11:27:38 +0100 Subject: [PATCH 3/4] fixed unnecessary formatting --- graphix/pattern.py | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 92dcbeb8a..53994d524 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -47,12 +47,7 @@ ExpressionOrSupportsFloat, Parameter, ) - from graphix.sim import ( - Backend, - Data, - DensityMatrixBackend, - StatevectorBackend, - ) + from graphix.sim import Backend, Data, DensityMatrixBackend, StatevectorBackend from graphix.sim.base_backend import _StateT_co from graphix.sim.tensornet import TensorNetworkBackend from graphix.simulator import _BackendLiteral @@ -380,28 +375,19 @@ def __eq__(self, other: object) -> bool: ) def to_ascii( - self, - left_to_right: bool = False, - limit: int = 40, - target: Container[command.CommandKind] | None = None, + self, left_to_right: bool = False, limit: int = 40, target: Container[command.CommandKind] | None = None ) -> str: """Return the ASCII string representation of the pattern.""" return pattern_to_str(self, OutputFormat.ASCII, left_to_right, limit, target) def to_latex( - self, - left_to_right: bool = False, - limit: int = 40, - target: Container[command.CommandKind] | None = None, + self, left_to_right: bool = False, limit: int = 40, target: Container[command.CommandKind] | None = None ) -> str: """Return a string containing the LaTeX representation of the pattern.""" return pattern_to_str(self, OutputFormat.LaTeX, left_to_right, limit, target) def to_unicode( - self, - left_to_right: bool = False, - limit: int = 40, - target: Container[command.CommandKind] | None = None, + self, left_to_right: bool = False, limit: int = 40, target: Container[command.CommandKind] | None = None ) -> str: """Return the Unicode string representation of the pattern.""" return pattern_to_str(self, OutputFormat.Unicode, left_to_right, limit, target) @@ -862,10 +848,7 @@ def extract_signals(self) -> dict[int, set[int]]: if cmd.kind == CommandKind.M: extracted_signal = extract_signal(cmd.plane, cmd.s_domain, cmd.t_domain) if extracted_signal.signal: - self.__seq.insert( - pos + 1, - command.S(node=cmd.node, domain=extracted_signal.signal), - ) + self.__seq.insert(pos + 1, command.S(node=cmd.node, domain=extracted_signal.signal)) cmd.s_domain = extracted_signal.s_domain cmd.t_domain = extracted_signal.t_domain pos += 1 @@ -1344,8 +1327,7 @@ def _reorder_pattern(self, meas_commands: list[command.M]) -> None: list of measurement ('M') commands """ new = dataclasses.replace( - optimization.StandardizedPattern.from_pattern(self), - m_list=tuple(meas_commands), + optimization.StandardizedPattern.from_pattern(self), m_list=tuple(meas_commands) ).to_space_optimal_pattern() self.__seq = new.__seq @@ -1559,11 +1541,7 @@ def draw_graph( filename=filename, ) - def to_qasm3( - self, - filename: Path | str, - input_state: dict[int, State] | State = BasicStates.PLUS, - ) -> None: + def to_qasm3(self, filename: Path | str, input_state: dict[int, State] | State = BasicStates.PLUS) -> None: """Export measurement pattern to OpenQASM 3.0 file. See :func:`graphix.qasm3_exporter.pattern_to_qasm3`. @@ -1826,9 +1804,7 @@ def measure_pauli(pattern: Pattern, *, ignore_pauli_with_deps: bool = False) -> return pat -def pauli_nodes( - pattern: optimization.StandardizedPattern, -) -> tuple[list[tuple[command.M, PauliMeasurement]], set[int]]: +def pauli_nodes(pattern: optimization.StandardizedPattern) -> tuple[list[tuple[command.M, PauliMeasurement]], set[int]]: """Return the list of measurement commands that are in Pauli bases and that are not dependent on any non-Pauli measurements. Parameters From 69b3bfc1011d82d46c362d959f25ec36785dc706 Mon Sep 17 00:00:00 2001 From: Emlyn Graham Date: Mon, 2 Feb 2026 11:29:45 +0100 Subject: [PATCH 4/4] fixed unnecessary formatting --- graphix/pattern.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/graphix/pattern.py b/graphix/pattern.py index 53994d524..edb212d8f 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -42,11 +42,7 @@ from numpy.random import Generator from graphix.flow.core import CausalFlow, GFlow, XZCorrections - from graphix.parameter import ( - ExpressionOrSupportsComplex, - ExpressionOrSupportsFloat, - Parameter, - ) + from graphix.parameter import ExpressionOrSupportsComplex, ExpressionOrSupportsFloat, Parameter from graphix.sim import Backend, Data, DensityMatrixBackend, StatevectorBackend from graphix.sim.base_backend import _StateT_co from graphix.sim.tensornet import TensorNetworkBackend