From 7a5a96625c9fa0880fc6f559ad0b28edebc8f56e Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 7 Jul 2026 13:41:42 +0200 Subject: [PATCH 01/10] Change to_ methods --- docs/source/modifier.rst | 6 +-- examples/visualization.py | 2 +- graphix/circ_ext/extraction.py | 2 +- graphix/flow/core.py | 28 +++++----- graphix/opengraph.py | 70 ++++++++++++------------- graphix/optimization.py | 8 ++- graphix/pattern.py | 48 ++++++++--------- graphix/space_minimization.py | 2 +- graphix/transpiler.py | 8 +-- tests/test_circ_extraction.py | 14 ++--- tests/test_clifford.py | 2 +- tests/test_flow_core.py | 24 ++++----- tests/test_opengraph.py | 34 ++++++------ tests/test_optimization.py | 2 +- tests/test_pattern.py | 56 ++++++++++---------- tests/test_pauli_flow_extraction.py | 64 +++++++++++----------- tests/test_pretty_print.py | 23 ++++---- tests/test_remove_pauli_measurements.py | 10 ++-- tests/test_transpiler.py | 8 +-- tests/test_visualization.py | 12 ++--- 20 files changed, 205 insertions(+), 218 deletions(-) diff --git a/docs/source/modifier.rst b/docs/source/modifier.rst index 2d3b96073..5bd6679b2 100644 --- a/docs/source/modifier.rst +++ b/docs/source/modifier.rst @@ -54,11 +54,11 @@ Pattern Manipulation .. automethod:: extract_nodes - .. automethod:: extract_causal_flow + .. automethod:: to_causalflow - .. automethod:: extract_gflow + .. automethod:: to_gflow - .. automethod:: extract_opengraph + .. automethod:: to_opengraph .. automethod:: extract_measurement_commands diff --git a/examples/visualization.py b/examples/visualization.py index d6e0e68de..205b12040 100644 --- a/examples/visualization.py +++ b/examples/visualization.py @@ -88,7 +88,7 @@ 3: Measurement.YZ(0), } og = OpenGraph(graph, inputs, outputs, measurements) -cf = og.extract_gflow() +cf = og.to_gflow() cf.draw(measurement_labels=True) # %% diff --git a/graphix/circ_ext/extraction.py b/graphix/circ_ext/extraction.py index f55c0a559..5d5fa1449 100644 --- a/graphix/circ_ext/extraction.py +++ b/graphix/circ_ext/extraction.py @@ -676,7 +676,7 @@ def clifford_x_map_from_focused_flow(flow: PauliFlow[Measurement]) -> tuple[Paul og_extended, ancillary_inputs_map = extend_input(og) # Here it's crucial to not infer Pauli measurements to avoid converting measurements inadvertently. - flow_extended = og_extended.extract_pauli_flow() + flow_extended = og_extended.to_pauliflow() # `flow_extended` is guaranteed to be focused if `flow` is focused. # This function assumes that `flow` is focused and does not check it. diff --git a/graphix/flow/core.py b/graphix/flow/core.py index c473ac605..3ea981bcd 100644 --- a/graphix/flow/core.py +++ b/graphix/flow/core.py @@ -208,7 +208,7 @@ def to_pattern( pattern.reorder_output_nodes(self.og.output_nodes) return pattern - def to_causal_flow(self: XZCorrections[_PM_co]) -> CausalFlow[_PM_co]: + def to_causalflow(self: XZCorrections[_PM_co]) -> CausalFlow[_PM_co]: r"""Extract a causal flow from XZ-corrections. This method does not invoke the flow-extraction routine on the underlying open graph. @@ -268,7 +268,7 @@ def to_gflow(self: XZCorrections[_PM_co]) -> GFlow[_PM_co]: gf.check_well_formed() # Raises a `FlowError` if the partial order and the correction function are not compatible. return gf - def to_pauli_flow(self) -> PauliFlow[_AM_co]: + def to_pauliflow(self) -> PauliFlow[_AM_co]: r"""Extract a Pauli flow from XZ-corrections. This method does not invoke the flow-extraction routine on the underlying open graph. @@ -279,7 +279,7 @@ def to_pauli_flow(self) -> PauliFlow[_AM_co]: The difficulty, compared with :meth:`to_gflow`, is that the Pauli-flow correction sets may contain *anachronical corrections*: corrections targeting nodes measured in the X or Y Pauli bases that lie in the present or the past of the corrected node. - Such corrections do not appear in the pattern, because :meth:`PauliFlow.to_corrections` + Such corrections do not appear in the pattern, because :meth:`PauliFlow.to_xzcorrections` only keeps the part of each correction set lying in the future (the ``& future`` filter). They must therefore be reconstructed rather than read off the corrections. @@ -581,7 +581,7 @@ class PauliFlow(Generic[_AM_co]): ----- - See Definition 5 in Ref. [1] for a definition of Pauli flow. - - The flow's correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only `og` and `correction_function` are necessary to initialize an `PauliFlow` instance (see :func:`PauliFlow.try_from_correction_matrix`). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow's XZ-corrections, so it is stored as an attribute. + - The flow's correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only `og` and `correction_function` are necessary to initialize an `PauliFlow` instance (see :func:`PauliFlow.from_correction_matrix_or_none`). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow's XZ-corrections, so it is stored as an attribute. - A correct flow can only exist on an open graph with output nodes, so `layers[0]` always contains a finite set of nodes. @@ -598,7 +598,7 @@ class PauliFlow(Generic[_AM_co]): _CF_PREFIX: str = "p" # Correction function prefix for printing @classmethod - def try_from_correction_matrix(cls, correction_matrix: CorrectionMatrix[_AM_co]) -> Self | None: + def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_AM_co]) -> Self | None: """Initialize a `PauliFlow` object from a matrix encoding a correction function. Parameters @@ -626,7 +626,7 @@ def try_from_correction_matrix(cls, correction_matrix: CorrectionMatrix[_AM_co]) return cls(correction_matrix.aog.og, correction_function, partial_order_layers) - def to_corrections(self) -> XZCorrections[_AM_co]: + def to_xzcorrections(self) -> XZCorrections[_AM_co]: """Compute the X and Z corrections induced by the Pauli flow encoded in `self`. Returns @@ -975,7 +975,7 @@ def extract_circuit(self: PauliFlow[Measurement]) -> ExtractionResult: ----- - This method implements the algorithm in [1]. - - Flows are guaranteed to be focused if obtained from :func:`OpenGraph.extract_pauli_flow` or :func:`OpenGraph.extract_gflow` (see [2]). + - Flows are guaranteed to be focused if obtained from :func:`OpenGraph.to_pauliflow` or :func:`OpenGraph.to_gflow` (see [2]). References ---------- @@ -1009,7 +1009,7 @@ class GFlow(PauliFlow[_PM_co], Generic[_PM_co]): @override @classmethod - def try_from_correction_matrix(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> Self | None: + def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> Self | None: """Initialize a `GFlow` object from a matrix encoding a correction function. Parameters @@ -1030,10 +1030,10 @@ def try_from_correction_matrix(cls, correction_matrix: CorrectionMatrix[_PM_co]) ---------- [1] Mitosek and Backens, 2024 (arXiv:2410.23439). """ - return super().try_from_correction_matrix(correction_matrix) + return super().from_correction_matrix_or_none(correction_matrix) @override - def to_corrections(self) -> XZCorrections[_PM_co]: + def to_xzcorrections(self) -> XZCorrections[_PM_co]: r"""Compute the XZ-corrections induced by the generalised flow encoded in `self`. Returns @@ -1187,11 +1187,11 @@ class CausalFlow(GFlow[_PM_co], Generic[_PM_co]): @override @classmethod - def try_from_correction_matrix(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> None: + def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> None: raise NotImplementedError("Initialization of a causal flow from a correction matrix is not supported.") @override - def to_corrections(self) -> XZCorrections[_PM_co]: + def to_xzcorrections(self) -> XZCorrections[_PM_co]: r"""Compute the XZ-corrections induced by the causal flow encoded in `self`. Returns @@ -1458,7 +1458,7 @@ def _solve_pauli_correction_set( ) -> set[int] | None: """Reconstruct the Pauli-flow correction set of a single ``node``. - See :meth:`XZCorrections.to_pauli_flow` for the description of the GF(2) system solved here. + See :meth:`XZCorrections.to_pauliflow` for the description of the GF(2) system solved here. """ og = xz.og nodes = set(og.graph.nodes) @@ -1567,7 +1567,7 @@ def row_at(g: int) -> list[int]: def _reconstruct_pauli_correction_function(xz: XZCorrections[AbstractMeasurement]) -> dict[int, set[int]]: """Reconstruct a Pauli-flow correction function from XZ-corrections. - See :meth:`XZCorrections.to_pauli_flow`. + See :meth:`XZCorrections.to_pauliflow`. """ og = xz.og adjacency: dict[int, set[int]] = {n: og.neighbors({n}) for n in og.graph.nodes} diff --git a/graphix/opengraph.py b/graphix/opengraph.py index 3f1cba960..3618a5679 100644 --- a/graphix/opengraph.py +++ b/graphix/opengraph.py @@ -51,7 +51,7 @@ class OpenGraph(Generic[_AM_co]): measurements : Mapping[int, _AM_co] A mapping between the non-output nodes of the open graph (``key``) and their corresponding measurement label (``value``). Measurement labels can be specified as `Measurement`, `Plane` or `Axis` instances. output_cliffords: Mapping[int, Clifford] - A mapping between output nodes of the open graph (``key``) and Clifford operators. This attribute allows to preserve the semantics of (deterministic) patterns with local-clifford commands when extracting the open graph. See :meth:`Pattern.extract_opengraph`. + A mapping between output nodes of the open graph (``key``) and Clifford operators. This attribute allows to preserve the semantics of (deterministic) patterns with local-clifford commands when extracting the open graph. See :meth:`Pattern.to_opengraph`. Notes ----- @@ -134,11 +134,11 @@ def to_pattern(self: OpenGraph[Measurement], *, stacklevel: int = 1) -> Pattern: except TypeError: flow: PauliFlow[Measurement] | None = None else: - flow = bloch_case.find_causal_flow() + flow = bloch_case.to_causalflow_or_none() if flow is None: - flow = self.find_pauli_flow(stacklevel=stacklevel + 1) + flow = self.to_pauliflow_or_none(stacklevel=stacklevel + 1) if flow is not None: - return flow.to_corrections().to_pattern() + return flow.to_xzcorrections().to_pattern() raise OpenGraphError("The open graph does not have flow. It does not support a deterministic pattern.") def draw(self, **options: Unpack[DrawKwargs]) -> None: @@ -208,7 +208,7 @@ def downcast_bloch(self: OpenGraph[Measurement]) -> OpenGraph[BlochMeasurement]: the original one, but this function narrows the static type of the measurements. The returned graph can be used with functions that require all measurements are described as Bloch - measurements, such as :func:`OpenGraph.extract_causal_flow`. + measurements, such as :func:`OpenGraph.to_causalflow`. Example ------- @@ -239,7 +239,7 @@ def infer_pauli_measurements( i.e. an integer multiple of :math:`\pi/2`. This method can be used before calling methods such as - `OpenGraph.find_pauli_flow`, that deal with Pauli measurements + `OpenGraph.to_pauliflow_or_none`, that deal with Pauli measurements in a special way. Parameters @@ -368,10 +368,10 @@ def odd_neighbors(self, nodes: Collection[int]) -> set[int]: odd_neighbors_set ^= self.neighbors([node]) return odd_neighbors_set - def extract_causal_flow(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co]: + def to_causalflow(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co]: """Try to extract a causal flow on the open graph. - This method is a wrapper over :func:`OpenGraph.find_causal_flow` with a single return type. + This method is a wrapper over :func:`OpenGraph.to_causalflow_or_none` with a single return type. This method requires all measurements to be planar: to extract the causal flow from a graph that contains Pauli measurements, @@ -389,18 +389,18 @@ def extract_causal_flow(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co]: See Also -------- - :func:`OpenGraph.find_causal_flow` + :func:`OpenGraph.to_causalflow_or_none` """ - cf = self.find_causal_flow() + cf = self.to_causalflow_or_none() if cf is None: raise OpenGraphError("The open graph does not have a causal flow.") return cf - def extract_gflow(self: OpenGraph[_PM_co]) -> GFlow[_PM_co]: + def to_gflow(self: OpenGraph[_PM_co]) -> GFlow[_PM_co]: r"""Try to extract a maximally delayed generalised flow (gflow) on the open graph. - This method is a wrapper over :func:`OpenGraph.find_gflow` with a single return type. + This method is a wrapper over :func:`OpenGraph.to_gflow_or_none` with a single return type. This method requires all measurements to be planar: to extract a gflow from a graph that contains Pauli measurements, you @@ -418,18 +418,18 @@ def extract_gflow(self: OpenGraph[_PM_co]) -> GFlow[_PM_co]: See Also -------- - :func:`OpenGraph.find_gflow` + :func:`OpenGraph.to_gflow_or_none` """ - gf = self.find_gflow() + gf = self.to_gflow_or_none() if gf is None: raise OpenGraphError("The open graph does not have a gflow.") return gf - def extract_pauli_flow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlow[_AM_co]: + def to_pauliflow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlow[_AM_co]: r"""Try to extract a maximally delayed Pauli on the open graph. - This method is a wrapper over :func:`OpenGraph.find_pauli_flow` with a single return type. + This method is a wrapper over :func:`OpenGraph.to_pauliflow_or_none` with a single return type. Parameters ---------- @@ -449,14 +449,14 @@ def extract_pauli_flow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> Pauli See Also -------- - :func:`OpenGraph.find_pauli_flow`, :func:`OpenGraph.infer_pauli_measurements` + :func:`OpenGraph.to_pauliflow_or_none`, :func:`OpenGraph.infer_pauli_measurements` """ - pf = self.find_pauli_flow(stacklevel=stacklevel + 1) + pf = self.to_pauliflow_or_none(stacklevel=stacklevel + 1) if pf is None: raise OpenGraphError("The open graph does not have a Pauli flow.") return pf - def find_causal_flow(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co] | None: + def to_causalflow_or_none(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co] | None: """Return a causal flow on the open graph if it exists. This method requires all measurements to be planar: to find @@ -470,7 +470,7 @@ def find_causal_flow(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co] | None: See Also -------- - :func:`OpenGraph.extract_causal_flow` + :func:`OpenGraph.to_causalflow` Notes ----- @@ -483,7 +483,7 @@ def find_causal_flow(self: OpenGraph[_PM_co]) -> CausalFlow[_PM_co] | None: """ return find_cflow(self) - def find_gflow(self: OpenGraph[_PM_co]) -> GFlow[_PM_co] | None: + def to_gflow_or_none(self: OpenGraph[_PM_co]) -> GFlow[_PM_co] | None: r"""Return a maximally delayed Pauli on the open graph if it exists. This method requires all measurements to be planar: to find @@ -497,11 +497,11 @@ def find_gflow(self: OpenGraph[_PM_co]) -> GFlow[_PM_co] | None: See Also -------- - :func:`OpenGraph.extract_gflow` + :func:`OpenGraph.to_gflow` Notes ----- - - The open graph instance must be of parametric type `Measurement` or `Plane` since the gflow is only defined on open graphs with planar measurements. Measurement instances with a Pauli angle (integer multiple of :math:`\pi/2`) are interpreted as `Plane` instances, in contrast with :func:`OpenGraph.find_pauli_flow`. + - The open graph instance must be of parametric type `Measurement` or `Plane` since the gflow is only defined on open graphs with planar measurements. Measurement instances with a Pauli angle (integer multiple of :math:`\pi/2`) are interpreted as `Plane` instances, in contrast with :func:`OpenGraph.to_pauliflow_or_none`. - This function implements the algorithm presented in Ref. [1] with polynomial complexity on the number of nodes, :math:`O(N^3)`. References @@ -512,11 +512,11 @@ def find_gflow(self: OpenGraph[_PM_co]) -> GFlow[_PM_co] | None: correction_matrix = compute_correction_matrix(aog) if correction_matrix is None: return None - return GFlow.try_from_correction_matrix( + return GFlow.from_correction_matrix_or_none( correction_matrix ) # The constructor returns `None` if the correction matrix is not compatible with any partial order on the open graph. - def find_pauli_flow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlow[_AM_co] | None: + def to_pauliflow_or_none(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlow[_AM_co] | None: r"""Return a maximally delayed Pauli on the open graph if it exists. Parameters @@ -532,7 +532,7 @@ def find_pauli_flow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlo See Also -------- - :func:`OpenGraph.extract_pauli_flow`, :func:`OpenGraph.infer_pauli_measurements` + :func:`OpenGraph.to_pauliflow`, :func:`OpenGraph.infer_pauli_measurements` Notes ----- @@ -551,13 +551,13 @@ def find_pauli_flow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlo >>> graph = nx.Graph([(0, 1), (1, 2)]) >>> measurements = {0: Measurement.XZ(0.5), 1: Measurement.XZ(0.5)} >>> og = OpenGraph(graph, [0], [2], measurements) - >>> og.extract_pauli_flow() + >>> og.to_pauliflow() Traceback (most recent call last): ... graphix.opengraph.OpenGraphError: The open graph does not have a Pauli flow. >>> og.infer_pauli_measurements().measurements {0: Measurement.X, 1: Measurement.X} - >>> str(og.infer_pauli_measurements().extract_pauli_flow()) + >>> str(og.infer_pauli_measurements().to_pauliflow()) 'p(0) = {1}, p(1) = {2}; {0, 1} < {2}' """ self._warn_non_inferred_pauli_measurements(stacklevel=stacklevel + 1) @@ -565,11 +565,11 @@ def find_pauli_flow(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> PauliFlo correction_matrix = compute_correction_matrix(aog) if correction_matrix is None: return None - return PauliFlow.try_from_correction_matrix( + return PauliFlow.from_correction_matrix_or_none( correction_matrix ) # The constructor returns `None` if the correction matrix is not compatible with any partial order on the open graph. - def extract_circuit( + def to_circuit( self: OpenGraph[Measurement], pexp_cp: Callable[[PauliExponentialDAG, Circuit], None] | None = None, cm_cp: Callable[[CliffordMap, Circuit], None] | None = None, @@ -637,18 +637,14 @@ def extract_circuit( ... output_nodes=(2, 5, 8), ... measurements=dict.fromkeys((0, 1, 3, 4, 6, 7), Measurement.XY(angle=0)), ... ) - >>> og.extract_circuit() + >>> og.to_circuit() Circuit(width=3, instr=[H(2), H(1), CNOT(2, 1), H(1), H(1), H(0), CNOT(2, 0), CNOT(1, 0), H(2), H(1), H(0)]) >>> # The default compilation passes do not exploit the lower depth of the Pauli flow >>> # compared the gflow. - >>> og.infer_pauli_measurements().extract_circuit() + >>> og.infer_pauli_measurements().to_circuit() Circuit(width=3, instr=[H(2), H(1), CNOT(2, 1), H(1), H(1), H(0), CNOT(2, 0), CNOT(1, 0), H(2), H(1), H(0)]) """ - return ( - self.extract_pauli_flow(stacklevel=stacklevel + 1) - .extract_circuit() - .to_circuit(pexp_cp=pexp_cp, cm_cp=cm_cp) - ) + return self.to_pauliflow(stacklevel=stacklevel + 1).extract_circuit().to_circuit(pexp_cp=pexp_cp, cm_cp=cm_cp) def compose(self, other: OpenGraph[_AM_co], mapping: Mapping[int, int]) -> tuple[OpenGraph[_AM_co], dict[int, int]]: r"""Compose two open graphs by merging subsets of nodes from ``self`` and ``other``, and relabeling the nodes of ``other`` that were not merged. diff --git a/graphix/optimization.py b/graphix/optimization.py index e3981b763..e60cb9585 100644 --- a/graphix/optimization.py +++ b/graphix/optimization.py @@ -335,7 +335,7 @@ def to_space_optimal_pattern(self) -> Pattern: """ return standardized_to_space_optimal_pattern(self) - def extract_opengraph(self) -> OpenGraph[Measurement]: + def to_opengraph(self) -> OpenGraph[Measurement]: r"""Extract the underlying resource-state open graph from the pattern. Returns @@ -415,7 +415,7 @@ def process_domain(node: Node, domain: AbstractSet[Node]) -> None: return oset, *generations[::-1] return generations[::-1] - def extract_xzcorrections(self) -> XZCorrections[Measurement]: + def to_xzcorrections(self) -> XZCorrections[Measurement]: r"""Extract the XZ-corrections from the current measurement pattern. Returns @@ -443,9 +443,7 @@ def extract_xzcorrections(self) -> XZCorrections[Measurement]: for node, domain in self.z_dict.items(): _update_corrections(node, domain, z_corr) - og = ( - self.extract_opengraph() - ) # Raises a `ValueError` if `N` commands in the pattern do not represent a |+⟩ state. + og = self.to_opengraph() # Raises a `ValueError` if `N` commands in the pattern do not represent a |+⟩ state. return XZCorrections.from_measured_nodes_mapping( og, x_corr, z_corr diff --git a/graphix/pattern.py b/graphix/pattern.py index ddfebcf3d..f0d447800 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1037,7 +1037,7 @@ def extract_partial_order_layers(self) -> tuple[frozenset[int], ...]: """ return optimization.StandardizedPattern.from_pattern(self).extract_partial_order_layers() - def extract_causal_flow(self) -> CausalFlow[BlochMeasurement]: + def to_causalflow(self) -> CausalFlow[BlochMeasurement]: r"""Extract the causal flow structure from the current measurement pattern. This method does not call the flow-extraction routine on the underlying open graph, but constructs the flow from the pattern corrections instead. @@ -1059,12 +1059,12 @@ def extract_causal_flow(self) -> CausalFlow[BlochMeasurement]: Notes ----- - - Applying the chain ``Pattern.extract_causal_flow().to_corrections().to_pattern()`` to a strongly deterministic pattern returns a new pattern implementing the same unitary transformation. This equivalence holds as long as the original pattern contains no Clifford commands, since those are discarded during open-graph extraction. + - Applying the chain ``Pattern.to_causalflow().to_xzcorrections().to_pattern()`` to a strongly deterministic pattern returns a new pattern implementing the same unitary transformation. This equivalence holds as long as the original pattern contains no Clifford commands, since those are discarded during open-graph extraction. - This method requires that all the measurements in the pattern are represented as Bloch measurements (i.e., there are no :class:`PauliMeasurement`s). Use :meth:`to_bloch()` to convert all Pauli measurements. """ - return self.extract_xzcorrections().downcast_bloch().to_causal_flow() + return self.to_xzcorrections().downcast_bloch().to_causalflow() - def extract_gflow(self) -> GFlow[BlochMeasurement]: + def to_gflow(self) -> GFlow[BlochMeasurement]: r"""Extract the generalized flow (gflow) structure from the current measurement pattern. This method does not call the flow-extraction routine on the underlying open graph, but constructs the gflow from the pattern corrections instead. @@ -1084,17 +1084,17 @@ def extract_gflow(self) -> GFlow[BlochMeasurement]: Notes ----- - The notes provided in :func:`self.extract_causal_flow` apply here as well. + The notes provided in :func:`self.to_causalflow` apply here as well. """ - return self.extract_xzcorrections().downcast_bloch().to_gflow() + return self.to_xzcorrections().downcast_bloch().to_gflow() - def extract_pauli_flow(self) -> PauliFlow[Measurement]: + def to_pauliflow(self) -> PauliFlow[Measurement]: r"""Extract the Pauli flow structure from the current measurement pattern. This method does not call the flow-extraction routine on the underlying open graph, but reconstructs the Pauli flow from the pattern corrections instead (see - :meth:`graphix.flow.core.XZCorrections.to_pauli_flow`). Contrary to - :meth:`extract_causal_flow` and :meth:`extract_gflow`, Pauli measurements are kept as + :meth:`graphix.flow.core.XZCorrections.to_pauliflow`). Contrary to + :meth:`to_causalflow` and :meth:`to_gflow`, Pauli measurements are kept as axes rather than downcast to planar Bloch measurements, so that the Pauli-basis structure is preserved. @@ -1113,11 +1113,11 @@ def extract_pauli_flow(self) -> PauliFlow[Measurement]: Notes ----- - The notes provided in :func:`self.extract_causal_flow` apply here as well. + The notes provided in :func:`self.to_causalflow` apply here as well. """ - return self.extract_xzcorrections().to_pauli_flow() + return self.to_xzcorrections().to_pauliflow() - def extract_xzcorrections(self) -> XZCorrections[Measurement]: + def to_xzcorrections(self) -> XZCorrections[Measurement]: """Extract the XZ-corrections from the current measurement pattern. Returns @@ -1134,11 +1134,11 @@ def extract_xzcorrections(self) -> XZCorrections[Measurement]: Notes ----- - To ensure that applying the chain ``Pattern.extract_xzcorrections().to_pattern()`` to a strongly deterministic pattern returns a new pattern implementing the same unitary transformation, XZ-corrections must be extracted from a standardized pattern. This requirement arises for the same reason that flow extraction also operates correctly on standardized patterns only. + To ensure that applying the chain ``Pattern.to_xzcorrections().to_pattern()`` to a strongly deterministic pattern returns a new pattern implementing the same unitary transformation, XZ-corrections must be extracted from a standardized pattern. This requirement arises for the same reason that flow extraction also operates correctly on standardized patterns only. This equivalence holds as long as the original pattern contains no Clifford commands, since those are discarded during open-graph extraction. - See docstring in :func:`optimization.StandardizedPattern.extract_gflow` for additional information. + See docstring in :func:`optimization.StandardizedPattern.to_gflow` for additional information. """ - return optimization.StandardizedPattern.from_pattern(self).extract_xzcorrections() + return optimization.StandardizedPattern.from_pattern(self).to_xzcorrections() def _measurement_order_depth(self) -> list[int]: """Obtain a measurement order which reduces the depth of a pattern. @@ -1233,7 +1233,7 @@ def extract_isolated_nodes(self) -> set[int]: graph = self.extract_graph() return {node for node, d in graph.degree if d == 0} - def extract_opengraph(self) -> OpenGraph[Measurement]: + def to_opengraph(self) -> OpenGraph[Measurement]: r"""Extract the underlying resource-state open graph from the pattern. This method standardizes the pattern first to guarantee that @@ -1245,7 +1245,7 @@ def extract_opengraph(self) -> OpenGraph[Measurement]: ------- OpenGraph[Measurement] """ - return optimization.StandardizedPattern.from_pattern(self).extract_opengraph() + return optimization.StandardizedPattern.from_pattern(self).to_opengraph() def extract_clifford(self) -> dict[int, Clifford]: """Extract Clifford commands. @@ -1542,7 +1542,7 @@ def draw( If ``flow_from_pattern==True`` but the pattern is not compatible with a gflow, an attempt to be extract the flow from the underlying open graph will be made while warning the user. """ if annotations is None: - og = self.extract_opengraph() + og = self.to_opengraph() gv = GraphVisualizer.from_opengraph(og=og, **options) else: match annotations: @@ -1551,12 +1551,12 @@ def draw( if flow_from_pattern: try: - xz_corrections = self.extract_xzcorrections().downcast_bloch() + xz_corrections = self.to_xzcorrections().downcast_bloch() except TypeError: pass else: try: - flow = xz_corrections.to_causal_flow() + flow = xz_corrections.to_causalflow() except FlowError: try: flow = xz_corrections.to_gflow() @@ -1567,15 +1567,15 @@ def draw( ) if flow is None: - og = self.extract_opengraph() + og = self.to_opengraph() try: bloch_case = og.downcast_bloch() except TypeError: pass else: - flow = bloch_case.find_causal_flow() + flow = bloch_case.to_causalflow_or_none() if flow is None: - flow = og.find_pauli_flow(stacklevel=stacklevel + 1) + flow = og.to_pauliflow_or_none(stacklevel=stacklevel + 1) if flow is None: raise PatternError( "The pattern's open graph does not have Pauli flow. Consider setting the `annotations` parameter to `None` or `DrawPatternAnnotations.XZCorrections`." @@ -1584,7 +1584,7 @@ def draw( gv = GraphVisualizer.from_flow(flow=flow, **options) case DrawPatternAnnotations.XZCorrections: - xzcorrections = self.extract_xzcorrections() + xzcorrections = self.to_xzcorrections() gv = GraphVisualizer.from_xzcorrections(xz_corr=xzcorrections, **options) gv.visualize() diff --git a/graphix/space_minimization.py b/graphix/space_minimization.py index 468d5c903..c85eb292f 100644 --- a/graphix/space_minimization.py +++ b/graphix/space_minimization.py @@ -192,7 +192,7 @@ def causal_flow(pattern: StandardizedPattern) -> SpaceMinimizationHeuristicResul This minimization heuristic is optimal but requires the pattern to have a causal flow. """ try: - cf = pattern.extract_xzcorrections().downcast_bloch().to_causal_flow() + cf = pattern.to_xzcorrections().downcast_bloch().to_causalflow() except (TypeError, FlowError): return None else: diff --git a/graphix/transpiler.py b/graphix/transpiler.py index aa49d76bb..d1fbae532 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -73,7 +73,7 @@ class TranspiledFlow: def to_pattern(self) -> TranspiledPattern: """Return the transpiled pattern.""" - pattern = StandardizedPattern.from_pattern(self.flow.to_corrections().to_pattern()).to_space_optimal_pattern() + pattern = StandardizedPattern.from_pattern(self.flow.to_xzcorrections().to_pattern()).to_space_optimal_pattern() pattern.extend(self.classical_outputs.values()) return TranspiledPattern(pattern, tuple(self.classical_outputs.keys())) @@ -434,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) -> TranspiledFlow: + def transpile_to_causalflow(self) -> TranspiledFlow: """Transpile a circuit via J-∧z decomposition to a causal flow. Parameters @@ -517,9 +517,9 @@ def transpile(self, *, transpile_swaps: bool = True) -> TranspiledPattern: The result of the transpilation: a pattern and classical outputs. """ if not transpile_swaps: - return self.transpile_to_causal_flow().to_pattern() + return self.transpile_to_causalflow().to_pattern() swap = _transpile_swaps(self) - result = swap.circuit.transpile_to_causal_flow().to_pattern() + result = swap.circuit.transpile_to_causalflow().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 TranspiledPattern(result.pattern, classical_outputs) diff --git a/tests/test_circ_extraction.py b/tests/test_circ_extraction.py index cdd6e1549..52bfb5191 100644 --- a/tests/test_circ_extraction.py +++ b/tests/test_circ_extraction.py @@ -446,7 +446,7 @@ def test_extract_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: circuit_ref = rand_circuit(nqubits, depth, rng, use_ccx=False) pattern = circuit_ref.transpile().pattern - circuit = pattern.extract_opengraph().extract_circuit() + circuit = pattern.to_opengraph().to_circuit() s_ref = circuit.simulate_statevector(rng=rng).statevec s_test = circuit_ref.simulate_statevector(rng=rng).statevec @@ -524,7 +524,7 @@ def test_extract_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: ) def test_extract_og(self, test_case: OpenGraph[Measurement], fx_rng: Generator) -> None: pattern = test_case.to_pattern() - circuit = test_case.extract_circuit() + circuit = test_case.to_circuit() state = circuit.simulate_statevector(rng=fx_rng).statevec state_ref = pattern.simulate_pattern(rng=fx_rng) @@ -549,7 +549,7 @@ def test_extract_og_infer_pauli(self, infer_pauli: bool, fx_rng: Generator) -> N if infer_pauli: og = og.infer_pauli_measurements() - circuit = og.extract_circuit() + circuit = og.to_circuit() state = circuit.simulate_statevector(rng=fx_rng).statevec state_ref = pattern.simulate_pattern(rng=fx_rng) @@ -568,7 +568,7 @@ def test_extract_og_gflow(self, fx_rng: Generator) -> None: }, ) pattern = og.to_pattern() - circuit = og.extract_gflow().extract_circuit().to_circuit() + circuit = og.to_gflow().extract_circuit().to_circuit() state = circuit.simulate_statevector(rng=fx_rng).statevec state_ref = pattern.simulate_pattern(rng=fx_rng) @@ -591,13 +591,13 @@ def test_parametric_angles(self, test_case: float, fx_rng: Generator) -> None: ) # Substitute parameter at the level of the extracted circuit - qc1 = og.extract_circuit() + qc1 = og.to_circuit() s1 = qc1.subs(alpha, alpha_val).simulate_statevector(rng=fx_rng).statevec # Substitute parameter at the level of the open graph object # Calling `infer_pauli_measurements` is not necessary for the test to pass # (and it should not be), but it suppresses the warnings. - qc2 = og.subs(alpha, alpha_val).infer_pauli_measurements().extract_circuit() + qc2 = og.subs(alpha, alpha_val).infer_pauli_measurements().to_circuit() s2 = qc2.simulate_statevector(rng=fx_rng).statevec assert s1.isclose(s2) @@ -649,5 +649,5 @@ def test_extend_input() -> None: assert og_ext.isclose(og_ref) assert ancillary_inputs_map == {1: 8, 2: 7} - flow = og_ext.infer_pauli_measurements().extract_pauli_flow() + flow = og_ext.infer_pauli_measurements().to_pauliflow() assert flow.is_focused() diff --git a/tests/test_clifford.py b/tests/test_clifford.py index 06bd981bd..983843696 100644 --- a/tests/test_clifford.py +++ b/tests/test_clifford.py @@ -100,7 +100,7 @@ def test_try_from_matrix_ng(self, fx_rng: Generator) -> None: @pytest.mark.parametrize("c", Clifford) def test_to_pattern(self, fx_rng: Generator, c: Clifford) -> None: og = c.to_opengraph() - og.to_bloch().extract_causal_flow() + og.to_bloch().to_causalflow() pattern = og.to_pattern() pattern_ref = Pattern(input_nodes=[0], cmds=[Command.C(0, c)]) input_state = rand_state_vector(nqubits=1, rng=fx_rng) diff --git a/tests/test_flow_core.py b/tests/test_flow_core.py index 8e722bd93..d5fa30341 100644 --- a/tests/test_flow_core.py +++ b/tests/test_flow_core.py @@ -391,10 +391,10 @@ class TestFlowPatternConversion: """ @pytest.mark.parametrize("test_case", prepare_test_xzcorrections()) - def test_flow_to_corrections(self, test_case: XZCorrectionsTestCase) -> None: + def test_flow_to_xzcorrections(self, test_case: XZCorrectionsTestCase) -> None: flow = test_case.flow flow.check_well_formed() - corrections = flow.to_corrections() + corrections = flow.to_xzcorrections() corrections.check_well_formed() assert corrections.z_corrections == test_case.z_corr assert corrections.x_corrections == test_case.x_corr @@ -402,7 +402,7 @@ def test_flow_to_corrections(self, test_case: XZCorrectionsTestCase) -> None: @pytest.mark.parametrize("test_case", prepare_test_xzcorrections()) def test_corrections_to_pattern(self, test_case: XZCorrectionsTestCase, fx_rng: Generator) -> None: if test_case.pattern is not None: - pattern = test_case.flow.to_corrections().to_pattern() # type: ignore[misc] + pattern = test_case.flow.to_xzcorrections().to_pattern() # type: ignore[misc] n_shots = 2 for plane in {Plane.XY, Plane.XZ, Plane.YZ}: @@ -427,7 +427,7 @@ def test_subs(self) -> None: output_nodes=[1], measurements={0: Measurement.XY(alpha)}, ) - flow = og.extract_pauli_flow() + flow = og.to_pauliflow() og_ref = OpenGraph( graph=nx.Graph([(0, 1)]), @@ -435,7 +435,7 @@ def test_subs(self) -> None: output_nodes=[1], measurements={0: Measurement.XY(value)}, ) - flow_ref = og_ref.extract_pauli_flow() + flow_ref = og_ref.to_pauliflow() flow_test = flow.subs(alpha, value) @@ -455,7 +455,7 @@ def test_xreplace(self) -> None: output_nodes=[2], measurements={node: Measurement.XY(angle) for node, angle in enumerate(parametric_angles)}, ) - flow = og.extract_pauli_flow() + flow = og.to_pauliflow() og_ref = OpenGraph( graph=nx.Graph([(0, 1), (1, 2)]), @@ -463,7 +463,7 @@ def test_xreplace(self) -> None: output_nodes=[2], measurements={node: Measurement.XY(value) for node in range(2)}, ) - flow_ref = og_ref.extract_pauli_flow() + flow_ref = og_ref.to_pauliflow() flow_test = flow.xreplace(dict.fromkeys(parametric_angles, value)) @@ -498,7 +498,7 @@ class TestXZCorrections: # See `:func: generate_causal_flow_0` def test_order_0(self) -> None: - corrections = generate_causal_flow_0().to_corrections() + corrections = generate_causal_flow_0().to_xzcorrections() assert corrections.generate_total_measurement_order() == [0, 1, 2] assert corrections.is_compatible([0, 1, 2]) # Correct order @@ -706,7 +706,7 @@ def test_subs(self) -> None: output_nodes=[1], measurements={0: Measurement.XY(alpha)}, ) - xzcorr = og.extract_causal_flow().to_corrections() + xzcorr = og.to_causalflow().to_xzcorrections() og_ref = OpenGraph( graph=nx.Graph([(0, 1)]), @@ -714,7 +714,7 @@ def test_subs(self) -> None: output_nodes=[1], measurements={0: Measurement.XY(value)}, ) - xzcorr_ref = og_ref.extract_causal_flow().to_corrections() + xzcorr_ref = og_ref.to_causalflow().to_xzcorrections() xzcorr_test = xzcorr.subs(alpha, value) @@ -735,7 +735,7 @@ def test_xreplace(self) -> None: output_nodes=[2], measurements={node: Measurement.XY(angle) for node, angle in enumerate(parametric_angles)}, ) - xzcorr = og.extract_causal_flow().to_corrections() + xzcorr = og.to_causalflow().to_xzcorrections() og_ref = OpenGraph( graph=nx.Graph([(0, 1), (1, 2)]), @@ -743,7 +743,7 @@ def test_xreplace(self) -> None: output_nodes=[2], measurements={node: Measurement.XY(value) for node in range(2)}, ) - xzcorr_ref = og_ref.extract_causal_flow().to_corrections() + xzcorr_ref = og_ref.to_causalflow().to_xzcorrections() xzcorr_test = xzcorr.xreplace(dict.fromkeys(parametric_angles, value)) diff --git a/tests/test_opengraph.py b/tests/test_opengraph.py index 2e62d4eb0..e59d4be2b 100644 --- a/tests/test_opengraph.py +++ b/tests/test_opengraph.py @@ -1039,39 +1039,39 @@ def test_cflow(self, test_case: OpenGraphFlowTestCase, fx_rng: Generator) -> Non og = test_case.og.to_bloch() if test_case.has_cflow: - cf = og.extract_causal_flow() + cf = og.to_causalflow() cf.check_well_formed() - pattern = cf.to_corrections().to_pattern() + pattern = cf.to_xzcorrections().to_pattern() assert check_determinism(pattern, fx_rng) else: with pytest.raises(OpenGraphError, match=r"The open graph does not have a causal flow."): - og.extract_causal_flow() + og.to_causalflow() @pytest.mark.parametrize("test_case", OPEN_GRAPH_FLOW_TEST_CASES) def test_gflow(self, test_case: OpenGraphFlowTestCase, fx_rng: Generator) -> None: og = test_case.og.to_bloch() if test_case.has_gflow: - gf = og.extract_gflow() + gf = og.to_gflow() gf.check_well_formed() - pattern = gf.to_corrections().to_pattern() + pattern = gf.to_xzcorrections().to_pattern() assert check_determinism(pattern, fx_rng) else: with pytest.raises(OpenGraphError, match=r"The open graph does not have a gflow."): - og.extract_gflow() + og.to_gflow() @pytest.mark.parametrize("test_case", OPEN_GRAPH_FLOW_TEST_CASES) def test_pflow(self, test_case: OpenGraphFlowTestCase, fx_rng: Generator) -> None: og = test_case.og if test_case.has_pflow: - pf = og.infer_pauli_measurements().extract_pauli_flow() + pf = og.infer_pauli_measurements().to_pauliflow() pf.check_well_formed() - pattern = pf.to_corrections().to_pattern() + pattern = pf.to_xzcorrections().to_pattern() assert check_determinism(pattern, fx_rng) else: with pytest.raises(OpenGraphError, match=r"The open graph does not have a Pauli flow."): - og.extract_pauli_flow() + og.to_pauliflow() def test_large_linear_graph(self) -> None: r"""Test causal-flow extraction algorithm on large linear open graphs. @@ -1094,7 +1094,7 @@ def test_large_linear_graph(self) -> None: c_ref = {i: frozenset({i + 1}) for i in og.measurements} pol_ref = tuple(frozenset({i}) for i in reversed(range(n_nodes))) - flow = og.extract_causal_flow() + flow = og.to_causalflow() assert flow.correction_function == c_ref assert flow.partial_order_layers == pol_ref @@ -1102,19 +1102,19 @@ def test_large_linear_graph(self) -> None: def test_gflow_focused(self, test_case: OpenGraphFlowTestCase) -> None: """Test that the algebraic flow-finding algorithm generated focused gflows.""" if test_case.has_gflow: - gf = test_case.og.to_bloch().extract_gflow() + gf = test_case.og.to_bloch().to_gflow() assert gf.is_focused() @pytest.mark.parametrize("test_case", OPEN_GRAPH_FLOW_TEST_CASES) def test_pflow_focused(self, test_case: OpenGraphFlowTestCase) -> None: """Test that the algebraic flow-finding algorithm generated focused Pauli flows.""" if test_case.has_pflow: - pf = test_case.og.infer_pauli_measurements().extract_pauli_flow() + pf = test_case.og.infer_pauli_measurements().to_pauliflow() assert pf.is_focused() def test_double_entanglement(self) -> None: pattern = Pattern(input_nodes=[0, 1], cmds=[E((0, 1)), E((0, 1))]) - pattern2 = pattern.extract_opengraph().to_pattern() + pattern2 = pattern.to_opengraph().to_pattern() state = pattern.simulate_pattern() state2 = pattern2.simulate_pattern() assert state.isclose(state2) @@ -1124,9 +1124,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 = StandardizedPattern.from_pattern( - pattern_ref.extract_opengraph().to_pattern() - ).to_space_optimal_pattern() + pattern = StandardizedPattern.from_pattern(pattern_ref.to_opengraph().to_pattern()).to_space_optimal_pattern() for plane in {Plane.XY, Plane.XZ, Plane.YZ}: alpha = 2 * ANGLE_PI * fx_rng.random() @@ -1276,8 +1274,8 @@ def test_compose_clifford(self, fx_rng: Generator) -> None: mapping = {0: 0} pc, _ = p1.compose(p2, mapping) - og1 = p1.extract_opengraph() - og2 = p2.extract_opengraph() + og1 = p1.to_opengraph() + og2 = p2.to_opengraph() og_c, _ = og1.compose(og2, mapping) pc_test = og_c.to_pattern() diff --git a/tests/test_optimization.py b/tests/test_optimization.py index 5c386d8d3..bf33fe281 100644 --- a/tests/test_optimization.py +++ b/tests/test_optimization.py @@ -70,7 +70,7 @@ def test_flow_after_pauli_preprocessing(fx_bg: PCG64, jumps: int) -> None: pattern.remove_pauli_measurements() # We should convert to Bloch measurement the remaining Pauli # measurements on input nodes. - gflow = pattern.to_bloch().extract_gflow() + gflow = pattern.to_bloch().to_gflow() gflow.check_well_formed() diff --git a/tests/test_pattern.py b/tests/test_pattern.py index ac125d74f..3d83749eb 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -922,14 +922,14 @@ class PatternFlowTestCase(NamedTuple): # Extract causal flow from random circuits @pytest.mark.parametrize("jumps", range(1, 11)) - def test_extract_causal_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: + def test_to_causalflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: """Tests the round trip Pattern -> XZCorrections -> CausalFlow -> XZCorrections -> Pattern.""" rng = Generator(fx_bg.jumped(jumps)) nqubits = 2 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_test = p_ref.to_bloch().to_causalflow().to_xzcorrections().to_pattern().infer_pauli_measurements() p_ref = p_ref.infer_pauli_measurements() p_test = p_test.infer_pauli_measurements() @@ -942,14 +942,14 @@ def test_extract_causal_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None # Extract gflow from random circuits @pytest.mark.parametrize("jumps", range(1, 11)) - def test_extract_gflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: + def test_to_gflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: """Tests the round trip Pattern -> XZCorrections -> GFlow -> XZCorrections -> Pattern.""" rng = Generator(fx_bg.jumped(jumps)) nqubits = 2 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_test = p_ref.to_bloch().to_gflow().to_xzcorrections().to_pattern().infer_pauli_measurements() p_ref = p_ref.infer_pauli_measurements() p_test = p_test.infer_pauli_measurements() @@ -962,14 +962,14 @@ def test_extract_gflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: # Extract Pauli flow from random circuits @pytest.mark.parametrize("jumps", range(1, 11)) - def test_extract_pauli_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: + def test_to_pauliflow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: """Tests the round trip Pattern -> XZCorrections -> PauliFlow -> XZCorrections -> Pattern.""" rng = Generator(fx_bg.jumped(jumps)) nqubits = 2 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_test = p_ref.to_bloch().to_pauliflow().to_xzcorrections().to_pattern().infer_pauli_measurements() p_ref.remove_pauli_measurements() p_test.remove_pauli_measurements() @@ -979,36 +979,36 @@ def test_extract_pauli_flow_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: assert s_ref.isclose(s_test) @pytest.mark.parametrize("test_case", PATTERN_FLOW_TEST_CASES) - def test_extract_causal_flow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: + def test_to_causalflow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: if test_case.has_cflow: alpha = 2 * np.pi * fx_rng.random() s_ref = test_case.pattern.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - p_test = test_case.pattern.to_bloch().extract_causal_flow().to_corrections().to_pattern() + p_test = test_case.pattern.to_bloch().to_causalflow().to_xzcorrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) assert s_ref.isclose(s_test) else: with pytest.raises(FlowError): - test_case.pattern.extract_causal_flow() + test_case.pattern.to_causalflow() @pytest.mark.parametrize("test_case", PATTERN_FLOW_TEST_CASES) - def test_extract_gflow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: + def test_to_gflow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: """Tests the round trip Pattern -> XZCorrections -> GFlow -> XZCorrections -> Pattern.""" if test_case.has_gflow: alpha = 2 * np.pi * fx_rng.random() s_ref = test_case.pattern.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - p_test = test_case.pattern.to_bloch().extract_gflow().to_corrections().to_pattern() + p_test = test_case.pattern.to_bloch().to_gflow().to_xzcorrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) assert s_ref.isclose(s_test) else: with pytest.raises(FlowError): - test_case.pattern.extract_gflow() + test_case.pattern.to_gflow() @pytest.mark.parametrize("test_case", PATTERN_FLOW_TEST_CASES) - def test_extract_pauli_flow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: + def test_to_pauliflow(self, fx_rng: Generator, test_case: PatternFlowTestCase) -> None: """Tests the round trip Pattern -> XZCorrections -> PauliFlow -> XZCorrections -> Pattern.""" # A gflow always induces a Pauli flow, so every gflow case must round-trip via the Pauli # flow. Cases without a gflow are skipped: a Pauli flow is strictly more general and may @@ -1018,7 +1018,7 @@ def test_extract_pauli_flow(self, fx_rng: Generator, test_case: PatternFlowTestC alpha = 2 * np.pi * fx_rng.random() s_ref = test_case.pattern.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - p_test = test_case.pattern.to_bloch().extract_pauli_flow().to_corrections().to_pattern() + p_test = test_case.pattern.to_bloch().to_pauliflow().to_xzcorrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) assert s_ref.isclose(s_test) @@ -1038,16 +1038,16 @@ def test_extract_cflow_og(self, fx_rng: Generator) -> None: 4: Measurement.XY(0.4), }, ) - p_ref = og.extract_causal_flow().to_corrections().to_pattern() + p_ref = og.to_causalflow().to_xzcorrections().to_pattern() s_ref = p_ref.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - p_test = p_ref.extract_causal_flow().to_corrections().to_pattern() + p_test = p_ref.to_causalflow().to_xzcorrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) assert s_ref.isclose(s_test) # From open graph - def test_extract_gflow_og(self, fx_rng: Generator) -> None: + def test_to_gflow_og(self, fx_rng: Generator) -> None: alpha = 2 * np.pi * fx_rng.random() og = OpenGraph( @@ -1062,10 +1062,10 @@ def test_extract_gflow_og(self, fx_rng: Generator) -> None: }, ) - p_ref = og.extract_gflow().to_corrections().to_pattern() + p_ref = og.to_gflow().to_xzcorrections().to_pattern() s_ref = p_ref.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) - p_test = p_ref.extract_gflow().to_corrections().to_pattern() + p_test = p_ref.to_gflow().to_xzcorrections().to_pattern() s_test = p_test.simulate_pattern(input_state=PlanarState(Plane.XZ, alpha), rng=fx_rng) assert s_ref.isclose(s_test) @@ -1078,7 +1078,7 @@ def test_extract_xzc_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 - xzc = p_ref.extract_xzcorrections() + xzc = p_ref.to_xzcorrections() xzc.check_well_formed() p_test = xzc.to_pattern() @@ -1093,7 +1093,7 @@ def test_extract_xzc_rnd_circuit(self, fx_bg: PCG64, jumps: int) -> None: def test_extract_xzc_empty_domains(self) -> None: p = Pattern(input_nodes=[0], cmds=[N(1), E((0, 1))]) - xzc = p.extract_xzcorrections() + xzc = p.to_xzcorrections() assert xzc.x_corrections == {} assert xzc.z_corrections == {} assert xzc.partial_order_layers == (frozenset({0, 1}),) @@ -1104,9 +1104,9 @@ def test_extract_xzc_easy_example(self) -> None: cmds=[M(0), M(1), M(2, s_domain={0}, t_domain={1}), X(3, domain={2}), M(3), Z(4, domain={3})], ) - xzc = pattern.extract_xzcorrections() + xzc = pattern.to_xzcorrections() xzc_ref = XZCorrections.from_measured_nodes_mapping( - pattern.extract_opengraph(), x_corrections={0: {2}, 2: {3}}, z_corrections={1: {2}, 3: {4}} + pattern.to_opengraph(), x_corrections={0: {2}, 2: {3}}, z_corrections={1: {2}, 3: {4}} ) assert xzc.og.isclose(xzc_ref.og) assert xzc.x_corrections == xzc_ref.x_corrections @@ -1155,11 +1155,11 @@ def test_reindex(self) -> None: assert list(pattern) == list(pattern_reindexed) assert pattern.output_nodes == pattern_reindexed.output_nodes - def test_extract_opengraph_standardization(self) -> None: + def test_to_opengraph_standardization(self) -> None: p = Pattern(cmds=[N(0), C(0, Clifford.H), M(0, Measurement.XY(0.3))]) - og = p.extract_opengraph() + og = p.to_opengraph() p.standardize() - og_std = p.extract_opengraph() + og_std = p.to_opengraph() assert og.isclose(og_std) @@ -1192,8 +1192,8 @@ def test_extract_opengraph_standardization(self) -> None: ), ], ) - def test_extract_opengraph_roundtrip(self, pattern: Pattern, fx_rng: Generator) -> None: - pattern_test = pattern.extract_opengraph().to_pattern() + def test_to_opengraph_roundtrip(self, pattern: Pattern, fx_rng: Generator) -> None: + pattern_test = pattern.to_opengraph().to_pattern() sv = pattern.simulate_pattern(rng=fx_rng) sv_test = pattern_test.simulate_pattern(rng=fx_rng) diff --git a/tests/test_pauli_flow_extraction.py b/tests/test_pauli_flow_extraction.py index 80095050a..fd5e8af62 100644 --- a/tests/test_pauli_flow_extraction.py +++ b/tests/test_pauli_flow_extraction.py @@ -3,7 +3,7 @@ Correctness criterion --------------------- A reconstructed Pauli flow ``pf`` generates the original pattern if and only if -``pf.check_well_formed()`` succeeds *and* ``pf.to_corrections()`` reproduces the pattern's +``pf.check_well_formed()`` succeeds *and* ``pf.to_xzcorrections()`` reproduces the pattern's X- and Z-corrections exactly. The latter "round-trip" property is the decisive check: it guarantees that the flow generates *this* pattern (and not merely some Pauli flow of the underlying open graph, which need not be unique). The tests below verify this on the three @@ -38,18 +38,18 @@ def _norm(corrections: Mapping[int, AbstractSet[int]]) -> dict[int, frozenset[in def _assert_round_trip(pattern: Pattern) -> None: - xz = pattern.extract_xzcorrections() - pf = xz.to_pauli_flow() - # `to_pauli_flow` no longer runs `check_well_formed` in production; the + xz = pattern.to_xzcorrections() + pf = xz.to_pauliflow() + # `to_pauliflow` no longer runs `check_well_formed` in production; the # well-formedness is asserted here, in the test-suite, instead. assert pf.is_well_formed() - rt = pf.to_corrections() + rt = pf.to_xzcorrections() assert _norm(rt.x_corrections) == _norm(xz.x_corrections) assert _norm(rt.z_corrections) == _norm(xz.z_corrections) def _correction_function(pattern: Pattern) -> dict[int, set[int]]: - pf = pattern.extract_pauli_flow() + pf = pattern.to_pauliflow() return {k: set(v) for k, v in pf.correction_function.items()} @@ -82,19 +82,19 @@ def _pauli_pattern() -> Pattern: ) # fmt: skip -def test_extract_pauli_flow_causal_example() -> None: +def test_to_pauliflow_causal_example() -> None: pattern = _causal_pattern() assert _correction_function(pattern) == {0: {1}} _assert_round_trip(pattern) -def test_extract_pauli_flow_gflow_example() -> None: +def test_to_pauliflow_gflow_example() -> None: pattern = _gflow_pattern() assert _correction_function(pattern) == {0: {2, 3}, 1: {1, 2}} _assert_round_trip(pattern) -def test_extract_pauli_flow_pauli_example() -> None: +def test_to_pauliflow_pauli_example() -> None: # The flow must include the anachronical correction (node 1 in p(0)) that does not # appear in the pattern, in order to satisfy the X-axis proposition (P7). pattern = _pauli_pattern() @@ -102,7 +102,7 @@ def test_extract_pauli_flow_pauli_example() -> None: _assert_round_trip(pattern) -def test_extract_pauli_flow_pauli_opengraph() -> None: +def test_to_pauliflow_pauli_opengraph() -> None: og = OpenGraph( graph=nx.Graph([(0, 2), (2, 4), (3, 4), (4, 6), (1, 4), (1, 6), (2, 3), (3, 5), (2, 6), (3, 6)]), input_nodes=[0], @@ -118,7 +118,7 @@ def test_extract_pauli_flow_pauli_opengraph() -> None: _assert_round_trip(og.to_pattern()) -def test_extract_pauli_flow_output_zcorrection() -> None: +def test_to_pauliflow_output_zcorrection() -> None: # Regression: a Z-correction whose future target is an *output* node imposes a real GF(2) # equation in the reconstruction -- it cannot be dropped (e.g. by skipping future nodes that are # not measured in a non-Pauli plane) without silently breaking the Z-correction round-trip. @@ -149,7 +149,7 @@ def test_extract_pauli_flow_output_zcorrection() -> None: @pytest.mark.parametrize("seed", range(400)) -def test_extract_pauli_flow_randomized_round_trip(seed: int) -> None: +def test_to_pauliflow_randomized_round_trip(seed: int) -> None: # For each seed, draw a random open graph. Seeds with no edges, or whose open graph does not # admit a Pauli flow (so `to_pattern` raises `OpenGraphError`), are skipped; the rest are # converted to a pattern and round-tripped. Passing the seed as a parameter keeps each case @@ -176,20 +176,20 @@ def test_extract_pauli_flow_randomized_round_trip(seed: int) -> None: _assert_round_trip(pattern) -def test_extract_pauli_flow_pins_the_pattern_specific_flow() -> None: +def test_to_pauliflow_pins_the_pattern_specific_flow() -> None: r"""Reconstruction must return the Pauli flow implemented by *this* pattern, not just any Pauli flow of the underlying open graph. A Pauli flow on an open graph is not unique when Pauli-measured nodes admit several distinct - anachronical-correction patterns. ``OpenGraph.find_pauli_flow`` returns *some* maximally + anachronical-correction patterns. ``OpenGraph.to_pauliflow_or_none`` returns *some* maximally delayed Pauli flow (chosen by the underlying algorithm); a trivial implementation of - ``XZCorrections.to_pauli_flow`` that delegates to it -- and ignores the XZ-corrections of + ``XZCorrections.to_pauliflow`` that delegates to it -- and ignores the XZ-corrections of the pattern entirely -- would therefore pass every existing round-trip test that happens to feed it patterns whose flow already coincides with that algorithmic choice. This test pins down a small open graph that admits two well-formed Pauli flows whose - ``to_corrections()`` outputs differ, builds the XZ-corrections of the *non-default* one, + ``to_xzcorrections()`` outputs differ, builds the XZ-corrections of the *non-default* one, and asserts that the reconstruction returns the chosen flow (and not the one - ``find_pauli_flow`` would have returned on the bare open graph). + ``to_pauliflow_or_none`` would have returned on the bare open graph). """ og: OpenGraph[Measurement] = OpenGraph( graph=nx.Graph([(0, 1), (1, 2), (2, 3)]), @@ -206,35 +206,35 @@ def test_extract_pauli_flow_pins_the_pattern_specific_flow() -> None: assert pf_with_anachronical.is_well_formed() assert pf_with_future.is_well_formed() - xz_with_anachronical = pf_with_anachronical.to_corrections() - xz_with_future = pf_with_future.to_corrections() + xz_with_anachronical = pf_with_anachronical.to_xzcorrections() + xz_with_future = pf_with_future.to_xzcorrections() # The corrections genuinely differ: the future-style flow X-corrects node 3 from # node 0, the anachronical-style flow does not. assert _norm(xz_with_anachronical.x_corrections) != _norm(xz_with_future.x_corrections) - # `find_pauli_flow` is allowed to return either valid flow (or another); what matters - # is that the trivial implementation `pf = self.og.find_pauli_flow()` is independent of + # `to_pauliflow_or_none` is allowed to return either valid flow (or another); what matters + # is that the trivial implementation `pf = self.og.to_pauliflow_or_none()` is independent of # the XZ-corrections we feed in -- so it cannot get both round-trips right. - trivial_choice = og.find_pauli_flow() + trivial_choice = og.to_pauliflow_or_none() assert trivial_choice is not None trivial_cf = {k: set(v) for k, v in trivial_choice.correction_function.items()} # The real reconstruction must use the XZ-corrections to disambiguate. - rebuilt_anachronical = xz_with_anachronical.to_pauli_flow() - rebuilt_future = xz_with_future.to_pauli_flow() + rebuilt_anachronical = xz_with_anachronical.to_pauliflow() + rebuilt_future = xz_with_future.to_pauliflow() assert dict(rebuilt_anachronical.correction_function) == {0: {1}, 1: {2}, 2: {3}} assert dict(rebuilt_future.correction_function) == {0: {1, 3}, 1: {2}, 2: {3}} # And the round-trip on each must still recover the original XZ-corrections exactly. - rt_anachronical = rebuilt_anachronical.to_corrections() - rt_future = rebuilt_future.to_corrections() + rt_anachronical = rebuilt_anachronical.to_xzcorrections() + rt_future = rebuilt_future.to_xzcorrections() assert _norm(rt_anachronical.x_corrections) == _norm(xz_with_anachronical.x_corrections) assert _norm(rt_anachronical.z_corrections) == _norm(xz_with_anachronical.z_corrections) assert _norm(rt_future.x_corrections) == _norm(xz_with_future.x_corrections) assert _norm(rt_future.z_corrections) == _norm(xz_with_future.z_corrections) # Discriminator assertion: at least one of the two reconstructions must disagree with - # the trivial ``find_pauli_flow`` choice (the two flows differ from each other, so the + # the trivial ``to_pauliflow_or_none`` choice (the two flows differ from each other, so the # trivial impl -- which returns the same flow regardless -- cannot match both). assert ( dict(rebuilt_anachronical.correction_function) != trivial_cf @@ -242,12 +242,12 @@ def test_extract_pauli_flow_pins_the_pattern_specific_flow() -> None: ) -def test_to_pauli_flow_empty_pattern() -> None: +def test_to_pauliflow_empty_pattern() -> None: # Regression for the production manifestation of #531: an empty pattern has a trivial - # Pauli flow, so `to_pauli_flow` must not raise. The well-formedness sanity check is no + # Pauli flow, so `to_pauliflow` must not raise. The well-formedness sanity check is no # longer run systematically in production (it lives in the test-suite); `check_well_formed`'s # own behaviour on an empty partial order is tracked separately in #531. - pf = Pattern().extract_xzcorrections().to_pauli_flow() + pf = Pattern().to_xzcorrections().to_pauliflow() assert dict(pf.correction_function) == {} @@ -265,7 +265,7 @@ def test_to_pauli_flow_empty_pattern() -> None: ], ids=["z-input", "xz-input", "yz-input", "isolated-xy"], ) -def test_to_pauli_flow_raises_when_no_flow_exists( +def test_to_pauliflow_raises_when_no_flow_exists( measurements: dict[int, Measurement], inputs: list[int], outputs: list[int], @@ -277,7 +277,7 @@ def test_to_pauli_flow_raises_when_no_flow_exists( graph.add_nodes_from(extra_nodes) og = OpenGraph(graph=graph, input_nodes=inputs, output_nodes=outputs, measurements=measurements) with pytest.raises(FlowGenericError) as exc_info: - XZCorrections(og, {}, {}, layers).to_pauli_flow() + XZCorrections(og, {}, {}, layers).to_pauliflow() assert exc_info.value.reason == FlowGenericErrorReason.NoPauliFlow # The rendered message names the failure so it is actionable in a traceback. assert "No Pauli flow" in str(exc_info.value) diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index da3e47848..eb34e98cc 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -114,9 +114,9 @@ def test_pattern_pretty_print_random(fx_bg: PCG64, jumps: int, output: OutputFor @pytest.mark.parametrize( "flow_extractor", [ - lambda og: OpenGraph.extract_causal_flow(og.to_bloch()), - lambda og: OpenGraph.extract_gflow(og.to_bloch()), - OpenGraph.extract_pauli_flow, + lambda og: OpenGraph.to_causalflow(og.to_bloch()), + lambda og: OpenGraph.to_gflow(og.to_bloch()), + OpenGraph.to_pauliflow, ], ) def test_flow_pretty_print_random( @@ -125,7 +125,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.infer_pauli_measurements().extract_opengraph() + rand_og = rand_circuit(5, 5, rng=rng).transpile().pattern.infer_pauli_measurements().to_opengraph() flow = flow_extractor(rand_og) flow.to_ascii() @@ -140,12 +140,7 @@ def test_xzcorr_pretty_print_random( ) -> None: rng = Generator(fx_bg.jumped(jumps)) xzcorr = ( - rand_circuit(5, 5, rng=rng) - .transpile() - .pattern.extract_opengraph() - .to_bloch() - .extract_causal_flow() - .to_corrections() + rand_circuit(5, 5, rng=rng).transpile().pattern.to_opengraph().to_bloch().to_causalflow().to_xzcorrections() ) xzcorr.to_ascii() @@ -168,7 +163,7 @@ def example_og() -> OpenGraph[Measurement]: def test_cflow_str() -> None: - flow = example_og().to_bloch().extract_causal_flow() + flow = example_og().to_bloch().to_causalflow() assert str(flow) == "c(3) = {5}, c(4) = {6}, c(1) = {3}, c(2) = {4}; {1, 2} < {3, 4} < {5, 6}" @@ -190,19 +185,19 @@ def test_cflow_str() -> None: def test_gflow_str() -> None: - flow = example_og().to_bloch().extract_gflow() + flow = example_og().to_bloch().to_gflow() assert str(flow) == "g(1) = {3, 6}, g(2) = {4, 5}, g(3) = {5}, g(4) = {6}; {1, 2} < {3, 4} < {5, 6}" def test_pflow_str() -> None: - flow = example_og().extract_pauli_flow() + flow = example_og().to_pauliflow() assert str(flow) == "p(1) = {3, 6}, p(2) = {4, 5}, p(3) = {5}, p(4) = {6}; {1, 2} < {3, 4} < {5, 6}" def test_xzcorr_str() -> None: - flow = example_og().to_bloch().extract_causal_flow().to_corrections() + flow = example_og().to_bloch().to_causalflow().to_xzcorrections() assert ( str(flow) diff --git a/tests/test_remove_pauli_measurements.py b/tests/test_remove_pauli_measurements.py index 10870ab36..105c97149 100644 --- a/tests/test_remove_pauli_measurements.py +++ b/tests/test_remove_pauli_measurements.py @@ -80,7 +80,7 @@ def test_local_complement(fx_rng: Generator, measured_set: AbstractSet[int]) -> remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.local_complement(0) standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() - og2 = standardized_pattern2.extract_opengraph() + og2 = standardized_pattern2.to_opengraph() expected_graph: Graph = nx.Graph( [ (0, 1), @@ -91,7 +91,7 @@ def test_local_complement(fx_rng: Generator, measured_set: AbstractSet[int]) -> ) assert nx.utils.graphs_equal(og2.graph, expected_graph) pattern2 = standardized_pattern2.to_pattern() - assert pattern2.extract_gflow() + assert pattern2.to_gflow() check_pattern_equivalence(pattern, pattern2, rng=fx_rng) @@ -104,7 +104,7 @@ def test_pivot_edge(fx_rng: Generator, measured_set: AbstractSet[int]) -> None: remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.pivot_edge(0, 1) standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() - og2 = standardized_pattern2.extract_opengraph() + og2 = standardized_pattern2.to_opengraph() expected_graph: Graph = nx.Graph( [ (0, 1), @@ -127,7 +127,7 @@ def test_pivot_edge(fx_rng: Generator, measured_set: AbstractSet[int]) -> None: assert nx.utils.graphs_equal(og2.graph, expected_graph) assert og2.output_nodes == tuple(0 if node == 1 else 1 if node == 0 else node for node in og.output_nodes) pattern2 = standardized_pattern2.to_pattern() - assert pattern2.extract_gflow() + assert pattern2.to_gflow() check_pattern_equivalence(pattern, pattern2, rng=fx_rng) @@ -188,7 +188,7 @@ def check_pattern(pattern: Pattern, rng: Generator) -> None: assert all_bloch_measurement_or_input_node(standardized_pattern2.input_nodes, standardized_pattern2.m_list) # Check that the pattern has a gflow - standardized_pattern2.extract_xzcorrections().to_bloch().to_gflow() + standardized_pattern2.to_xzcorrections().to_bloch().to_gflow() pattern2 = standardized_pattern2.to_pattern() check_pattern_equivalence(pattern, pattern2, rng=rng) diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index f3ae5f181..6c3c8f121 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -53,8 +53,8 @@ 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_causal_flow().flow.check_well_formed() - flow = pattern.to_bloch().extract_causal_flow() + circuit.transpile_to_causalflow().flow.check_well_formed() + flow = pattern.to_bloch().to_causalflow() flow.check_well_formed() @pytest.mark.parametrize("jumps", range(1, 11)) @@ -308,7 +308,7 @@ def test_add_extend(self) -> None: 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 = pattern.to_bloch().to_causalflow() flow.check_well_formed() @pytest.mark.parametrize("jumps", range(1, 11)) @@ -393,7 +393,7 @@ def test_transpile_double_cz() -> None: circuit = Circuit(2) circuit.cz(0, 1) circuit.cz(1, 0) - cf = circuit.transpile_to_causal_flow() + cf = circuit.transpile_to_causalflow() assert len(cf.flow.og.graph.edges) == 0 diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 598082bc8..9ff7f4b9f 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -90,10 +90,10 @@ def example_pflow(rng: Generator) -> Pattern: og = OpenGraph(graph=graph, input_nodes=inputs, output_nodes=outputs, measurements=measurements) try: - og.to_bloch().extract_gflow() + og.to_bloch().to_gflow() pytest.fail("example graph shouldn't have gflow") except OpenGraphError: - og.extract_pauli_flow() # example graph has Pauli flow + og.to_pauliflow() # example graph has Pauli flow pattern = og.to_pattern() pattern.standardize() @@ -213,7 +213,7 @@ def test_og_draw() -> Figure: @pytest.mark.mpl_image_compare def test_causal_flow_draw() -> Figure: og = example_og() - og.downcast_bloch().extract_causal_flow().draw(legend=False) + og.downcast_bloch().to_causalflow().draw(legend=False) return plt.gcf() @@ -221,7 +221,7 @@ def test_causal_flow_draw() -> Figure: @pytest.mark.mpl_image_compare def test_gflow_draw() -> Figure: og = example_og() - og.downcast_bloch().extract_gflow().draw(legend=False) + og.downcast_bloch().to_gflow().draw(legend=False) return plt.gcf() @@ -229,7 +229,7 @@ def test_gflow_draw() -> Figure: @pytest.mark.mpl_image_compare def test_pauli_flow_draw() -> Figure: og = example_og() - og.infer_pauli_measurements().extract_pauli_flow().draw(legend=False) + og.infer_pauli_measurements().to_pauliflow().draw(legend=False) return plt.gcf() @@ -237,7 +237,7 @@ def test_pauli_flow_draw() -> Figure: @pytest.mark.mpl_image_compare def test_xzcorr_draw() -> Figure: og = example_og() - og.downcast_bloch().extract_causal_flow().to_corrections().draw(legend=False) + og.downcast_bloch().to_causalflow().to_xzcorrections().draw(legend=False) return plt.gcf() From b636dea347e921d50af0fde64b7e4177728b079a Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 7 Jul 2026 13:59:56 +0200 Subject: [PATCH 02/10] Rename accesors --- benchmarks/statevec.py | 2 +- docs/source/modifier.rst | 8 +++---- docs/source/tutorial.rst | 2 +- examples/fusion_extraction.py | 2 +- examples/ghz_with_tn.py | 2 +- examples/qft_with_tn.py | 2 +- examples/tn_simulation.py | 2 +- graphix/_linalg.py | 4 ++-- graphix/flow/core.py | 4 ++-- graphix/optimization.py | 8 +++---- graphix/pattern.py | 36 ++++++++++++++-------------- graphix/remove_pauli_measurements.py | 2 +- graphix/sim/tensornet.py | 6 ++--- graphix/space_minimization.py | 6 ++--- tests/test_flow_core.py | 8 +++---- tests/test_linalg.py | 4 ++-- tests/test_pattern.py | 26 ++++++++++---------- tests/test_space_minimization.py | 2 +- tests/test_transpiler.py | 2 +- 19 files changed, 64 insertions(+), 64 deletions(-) diff --git a/benchmarks/statevec.py b/benchmarks/statevec.py index 73e1c37c6..7a29b376d 100644 --- a/benchmarks/statevec.py +++ b/benchmarks/statevec.py @@ -85,7 +85,7 @@ def simple_random_circuit(nqubit, depth): pattern = circuit.transpile() pattern.standardize() pattern.minimize_space() - nqubit = len(pattern.extract_nodes()) + nqubit = len(pattern.nodes()) start = perf_counter() pattern.simulate_pattern(max_qubit_num=30) end = perf_counter() diff --git a/docs/source/modifier.rst b/docs/source/modifier.rst index 5bd6679b2..7a4fff5ce 100644 --- a/docs/source/modifier.rst +++ b/docs/source/modifier.rst @@ -24,7 +24,7 @@ Pattern Manipulation .. automethod:: simulate_pattern - .. automethod:: compute_max_degree + .. automethod:: max_degree .. automethod:: compose @@ -50,9 +50,9 @@ Pattern Manipulation .. automethod:: is_standard - .. automethod:: extract_graph + .. automethod:: graph - .. automethod:: extract_nodes + .. automethod:: nodes .. automethod:: to_causalflow @@ -60,7 +60,7 @@ Pattern Manipulation .. automethod:: to_opengraph - .. automethod:: extract_measurement_commands + .. automethod:: measurement_commands .. automethod:: parallelize_pattern diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index a828719e7..5e7a44227 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -157,7 +157,7 @@ This reveals the graph structure of the resource state which we can inspect: .. code-block:: python import networkx as nx - graph = pattern.extract_graph() + graph = pattern.graph() pos = {0: (0, 0), 1: (0, -0.5), 2: (1, 0), 3: (4, 0), 4: (1, -0.5), 5: (2, -0.5), 6: (3, -0.5), 7: (4, -0.5)} graph_params = {'node_size': 240, 'node_color': 'w', 'edgecolors': 'k', 'with_labels': True} nx.draw(graph, pos=pos, **graph_params) diff --git a/examples/fusion_extraction.py b/examples/fusion_extraction.py index abc0a1746..7a450e42c 100644 --- a/examples/fusion_extraction.py +++ b/examples/fusion_extraction.py @@ -26,7 +26,7 @@ # %% # Here we say we want a graph state with 9 nodes and 12 edges. -# We can obtain resource graph for a measurement pattern by using :code:`pattern.extract_graph()`. +# We can obtain resource graph for a measurement pattern by using :code:`pattern.graph()`. gs = Graph() nodes = [0, 1, 2, 3, 4, 5, 6, 7, 8] edges = [(0, 1), (1, 2), (2, 3), (3, 0), (3, 4), (0, 5), (4, 5), (5, 6), (6, 7), (7, 0), (7, 8), (8, 1)] diff --git a/examples/ghz_with_tn.py b/examples/ghz_with_tn.py index 2834c4e0e..a734b54ab 100644 --- a/examples/ghz_with_tn.py +++ b/examples/ghz_with_tn.py @@ -35,7 +35,7 @@ pattern = circuit.transpile().pattern pattern.standardize() -graph = pattern.extract_graph() +graph = pattern.graph() print(f"Number of nodes: {len(graph.nodes)}") print(f"Number of edges: {len(graph.edges)}") pos = nx.spring_layout(graph) diff --git a/examples/qft_with_tn.py b/examples/qft_with_tn.py index 5cb991c2a..af8f6a43d 100644 --- a/examples/qft_with_tn.py +++ b/examples/qft_with_tn.py @@ -59,7 +59,7 @@ def qft(circuit: Circuit, n: int) -> None: pattern = circuit.transpile().pattern pattern.standardize() pattern.shift_signals() -graph = pattern.extract_graph() +graph = pattern.graph() print(f"Number of nodes: {len(graph.nodes)}") print(f"Number of edges: {len(graph.edges)}") diff --git a/examples/tn_simulation.py b/examples/tn_simulation.py index 84a5e8b19..873ca1e14 100644 --- a/examples/tn_simulation.py +++ b/examples/tn_simulation.py @@ -78,7 +78,7 @@ def ansatz( # %% # Print some properties of the graph. -graph = pattern.extract_graph() +graph = pattern.graph() print(f"Number of nodes: {len(graph.nodes)}") print(f"Number of edges: {len(graph.edges)}") diff --git a/graphix/_linalg.py b/graphix/_linalg.py index 094560693..11f509bb8 100644 --- a/graphix/_linalg.py +++ b/graphix/_linalg.py @@ -64,7 +64,7 @@ def mat_mul(self, other: MatGF2 | npt.NDArray[np.uint8]) -> MatGF2: return MatGF2(_mat_mul_jit(np.ascontiguousarray(self), np.ascontiguousarray(other)), copy=False) - def compute_rank(self) -> np.intp: + def rank(self) -> np.intp: """Get the rank of the matrix. Returns @@ -100,7 +100,7 @@ def right_inverse(self) -> MatGF2 | None: red = aug.row_reduction(ncols=n, copy=False) # Reduced row echelon form # Check that rank of right block is equal to the number of rows. - # We don't use `MatGF2.compute_rank()` to avoid row-reducing twice. + # We don't use `MatGF2.rank()` to avoid row-reducing twice. if m != np.count_nonzero(red[:, :n].any(axis=1)): return None rinv = np.zeros((n, m), dtype=np.uint8).view(MatGF2) diff --git a/graphix/flow/core.py b/graphix/flow/core.py index 3ea981bcd..40f92f276 100644 --- a/graphix/flow/core.py +++ b/graphix/flow/core.py @@ -351,7 +351,7 @@ def generate_total_measurement_order(self) -> TotalOrder: assert set(total_order) == set(self.og.graph.nodes) - set(self.og.output_nodes) return total_order - def extract_dag(self) -> nx.DiGraph[int]: + def dag(self) -> nx.DiGraph[int]: """Extract the directed graph induced by the XZ-corrections. Returns @@ -1319,7 +1319,7 @@ def _corrections_to_dag( Notes ----- - See :func:`XZCorrections.extract_dag`. + See :func:`XZCorrections.dag`. """ relations = ( (measured_node, corrected_node) diff --git a/graphix/optimization.py b/graphix/optimization.py index e60cb9585..f85205389 100644 --- a/graphix/optimization.py +++ b/graphix/optimization.py @@ -221,7 +221,7 @@ def from_pattern(cls, pattern: Pattern) -> Self: c_dict[cmd.node] = cmd.clifford @ c_dict.get(cmd.node, Clifford.I) return cls(pattern.input_nodes, pattern.output_nodes, n_list, e_set, m_list, z_dict, x_dict, c_dict) - def extract_graph(self) -> nx.Graph[int]: + def graph(self) -> nx.Graph[int]: """Return the graph state from the command sequence, extracted from 'N' and 'E' commands. Returns @@ -353,9 +353,9 @@ def to_opengraph(self) -> OpenGraph[Measurement]: f"Open graph construction in flow extraction requires N commands to represent a |+⟩ state. Error found in {n}." ) measurements = {m.node: m.measurement for m in self.m_list} - return OpenGraph(self.extract_graph(), self.input_nodes, self.output_nodes, measurements, self.c_dict) + return OpenGraph(self.graph(), self.input_nodes, self.output_nodes, measurements, self.c_dict) - def extract_partial_order_layers(self) -> tuple[frozenset[int], ...]: + def partial_order_layers(self) -> tuple[frozenset[int], ...]: """Extract the measurement order of the pattern in the form of layers. This method builds a directed acyclical graph (DAG) from the pattern and then performs a topological sort. @@ -617,7 +617,7 @@ def remove_local_clifford_commands(pattern: Pattern) -> Pattern: """ from graphix.pattern import Pattern # noqa: PLC0415 - nodes = pattern.extract_nodes() + nodes = pattern.nodes() if not nodes: return pattern max_node = max(nodes) diff --git a/graphix/pattern.py b/graphix/pattern.py index f0d447800..500718488 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -208,7 +208,7 @@ def node_mapping( freshly generated by the function; they do not apply to the nodes already present in ``mapping``. """ - nodes = self.extract_nodes() + nodes = self.nodes() if mapping is None: result = {} else: @@ -325,8 +325,8 @@ 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. """ - nodes_p1 = self.extract_nodes() # Results contain preprocessed Pauli nodes - nodes_p2 = other.extract_nodes() + nodes_p1 = self.nodes() # Results contain preprocessed Pauli nodes + nodes_p2 = other.nodes() if not mapping.keys() <= nodes_p2: raise PatternError("Keys of `mapping` must correspond to the nodes of `other`.") @@ -645,7 +645,7 @@ def shift_signals(self, method: str = "direct") -> dict[int, set[int]]: case "direct": return self.shift_signals_direct() case "mc": - signal_dict = self.extract_signals() + signal_dict = self.signals() target = self._find_op_to_be_moved(CommandKind.S, rev=True) while target is not None: if target == len(self.__seq) - 1: @@ -996,7 +996,7 @@ def _move_e_after_n(self) -> None: self._commute_with_preceding(target) target -= 1 - def extract_signals(self) -> dict[int, set[int]]: + def signals(self) -> dict[int, set[int]]: """Extract 't' domain of measurement commands, turn them into signal 'S' commands and add to the command sequence. This is used for shift_signals() method. @@ -1016,7 +1016,7 @@ def extract_signals(self) -> dict[int, set[int]]: pos += 1 return signal_dict - def extract_partial_order_layers(self) -> tuple[frozenset[int], ...]: + def partial_order_layers(self) -> tuple[frozenset[int], ...]: """Extract the measurement order of the pattern in the form of layers. This method standardizes the pattern, builds a directed acyclical graph (DAG) from measurement and correction domains, and then performs a topological sort. @@ -1033,9 +1033,9 @@ def extract_partial_order_layers(self) -> tuple[frozenset[int], ...]: Notes ----- - - This function wraps :func:`optimization.StandardizedPattern.extract_partial_order_layers`, and the returned object is described in the notes of this method. + - This function wraps :func:`optimization.StandardizedPattern.partial_order_layers`, and the returned object is described in the notes of this method. """ - return optimization.StandardizedPattern.from_pattern(self).extract_partial_order_layers() + return optimization.StandardizedPattern.from_pattern(self).partial_order_layers() def to_causalflow(self) -> CausalFlow[BlochMeasurement]: r"""Extract the causal flow structure from the current measurement pattern. @@ -1148,7 +1148,7 @@ def _measurement_order_depth(self) -> list[int]: list[int] optimal measurement order for parallel computing """ - partial_order_layers = self.extract_partial_order_layers() + partial_order_layers = self.partial_order_layers() return list(itertools.chain(*reversed(partial_order_layers[1:]))) def sort_measurement_commands(self, meas_order: list[int]) -> list[command.M]: @@ -1164,10 +1164,10 @@ def sort_measurement_commands(self, meas_order: list[int]) -> list[command.M]: meas_cmds: list of command sorted measurement commands """ - meas_dict = self.extract_measurement_commands() + meas_dict = self.measurement_commands() return [meas_dict[i] for i in meas_order] - def extract_measurement_commands(self) -> dict[int, command.M]: + def measurement_commands(self) -> dict[int, command.M]: """Return a dictionary mapping nodes to measurement commands. Returns @@ -1177,7 +1177,7 @@ def extract_measurement_commands(self) -> dict[int, command.M]: """ return {cmd.node: cmd for cmd in self if cmd.kind == CommandKind.M} - def compute_max_degree(self) -> int: + def max_degree(self) -> int: """Get max degree of a pattern. Returns @@ -1185,7 +1185,7 @@ def compute_max_degree(self) -> int: max_degree : int max degree of a pattern """ - graph = self.extract_graph() + graph = self.graph() degree = graph.degree() assert isinstance(degree, nx.classes.reportviews.DiDegreeView) degrees = dict(degree).values() @@ -1193,7 +1193,7 @@ def compute_max_degree(self) -> int: return 0 return int(max(degrees)) - def extract_graph(self) -> nx.Graph[int]: + def graph(self) -> nx.Graph[int]: """Return the graph state from the command sequence, extracted from ``N`` and ``E`` commands. Returns @@ -1214,7 +1214,7 @@ def extract_graph(self) -> nx.Graph[int]: graph.add_edge(u, v) return graph - def extract_nodes(self) -> set[int]: + def nodes(self) -> set[int]: """Return the set of nodes of the pattern.""" nodes = set(self.input_nodes) for cmd in self.__seq: @@ -1222,7 +1222,7 @@ def extract_nodes(self) -> set[int]: nodes.add(cmd.node) return nodes - def extract_isolated_nodes(self) -> set[int]: + def isolated_nodes(self) -> set[int]: """Get isolated nodes. Returns @@ -1230,7 +1230,7 @@ def extract_isolated_nodes(self) -> set[int]: isolated_nodes : set[int] set of the isolated nodes """ - graph = self.extract_graph() + graph = self.graph() return {node for node, d in graph.degree if d == 0} def to_opengraph(self) -> OpenGraph[Measurement]: @@ -1247,7 +1247,7 @@ def to_opengraph(self) -> OpenGraph[Measurement]: """ return optimization.StandardizedPattern.from_pattern(self).to_opengraph() - def extract_clifford(self) -> dict[int, Clifford]: + def clifford_commands(self) -> dict[int, Clifford]: """Extract Clifford commands. Returns diff --git a/graphix/remove_pauli_measurements.py b/graphix/remove_pauli_measurements.py index 5ea7b957e..fd06ebc0a 100644 --- a/graphix/remove_pauli_measurements.py +++ b/graphix/remove_pauli_measurements.py @@ -256,7 +256,7 @@ class _RemovePauliMeasurements: def __init__(self, cut: PauliPushingCut) -> None: self.cut = cut - self.graph = cut.original_pattern.extract_graph() + self.graph = cut.original_pattern.graph() self.node_specs = {node: _NodeSpec(node) for node in self.graph.nodes()} for node, domain in cut.original_pattern.x_dict.items(): self.node_specs[node].domains.s_domain = _expand_domain(cut.shifted_domains, domain) diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index f438bd0c6..df893fb77 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -639,7 +639,7 @@ def __init__( stacklevel=1, ) case "auto": - max_degree = pattern.compute_max_degree() + max_degree = pattern.max_degree() # "parallel" does not support non standard pattern graph_prep = "sequential" if max_degree > 5 or not pattern.is_standard() else "parallel" case _: @@ -648,7 +648,7 @@ def __init__( if graph_prep == "parallel": if not pattern.is_standard(): raise ValueError("parallel preparation strategy does not support not-standardized pattern") - graph = pattern.extract_graph() + graph = pattern.graph() state = MBQCTensorNet( graph_nodes=graph.nodes, graph_edges=graph.edges, @@ -659,7 +659,7 @@ def __init__( else: # graph_prep == "sequential": state = MBQCTensorNet(default_output_nodes=pattern.output_nodes, branch_selector=branch_selector) decomposed_cz = _decompose_cz() - isolated_nodes = pattern.extract_isolated_nodes() + isolated_nodes = pattern.isolated_nodes() super().__init__( state, pattern, diff --git a/graphix/space_minimization.py b/graphix/space_minimization.py index c85eb292f..35e7bdd17 100644 --- a/graphix/space_minimization.py +++ b/graphix/space_minimization.py @@ -66,7 +66,7 @@ def standardized_pattern_max_space(pattern: StandardizedPattern) -> int: pattern execution. """ initialized = set(pattern.input_nodes) - graph = pattern.extract_graph() + graph = pattern.graph() num_active = len(pattern.input_nodes) max_active = num_active @@ -113,7 +113,7 @@ def standardized_to_space_optimal_pattern(pattern: StandardizedPattern) -> Patte initialized = set(pattern.input_nodes) done: set[Node] = set() n_dict = {n.node: n for n in pattern.n_list} - graph = pattern.extract_graph() + graph = pattern.graph() def ensure_active(node: Node) -> None: """Initialize node in pattern if it has not been initialized before.""" @@ -224,7 +224,7 @@ def greedy_degree(pattern: StandardizedPattern) -> SpaceMinimizationHeuristicRes max space in some situations. """ - graph = pattern.extract_graph() + graph = pattern.graph() nodes = set(graph.nodes) not_measured = nodes - set(pattern.output_nodes) dependency = _extract_dependency(pattern) diff --git a/tests/test_flow_core.py b/tests/test_flow_core.py index d5fa30341..fa6dd97d7 100644 --- a/tests/test_flow_core.py +++ b/tests/test_flow_core.py @@ -506,7 +506,7 @@ def test_order_0(self) -> None: assert not corrections.is_compatible([1, 2]) # Incomplete order assert not corrections.is_compatible([0, 1, 2, 3]) # Contains outputs - assert nx.utils.graphs_equal(corrections.extract_dag(), nx.DiGraph([(0, 1), (0, 2), (1, 2), (2, 3), (1, 3)])) + assert nx.utils.graphs_equal(corrections.dag(), nx.DiGraph([(0, 1), (0, 2), (1, 2), (2, 3), (1, 3)])) # See `:func: generate_causal_flow_1` def test_order_1(self) -> None: @@ -532,7 +532,7 @@ def test_order_1(self) -> None: assert not corrections.is_compatible([0, 1, 2, 3, 4, 5]) # Contains outputs assert nx.utils.graphs_equal( - corrections.extract_dag(), nx.DiGraph([(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 5), (2, 4), (3, 5)]) + corrections.dag(), nx.DiGraph([(0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 5), (2, 4), (3, 5)]) ) # Incomplete corrections @@ -555,7 +555,7 @@ def test_order_2(self) -> None: assert not corrections.is_compatible([0, 0, 1]) # Duplicates assert not corrections.is_compatible([1, 0, 2, 3]) # Contains outputs - assert nx.utils.graphs_equal(corrections.extract_dag(), nx.DiGraph([(1, 0)])) + assert nx.utils.graphs_equal(corrections.dag(), nx.DiGraph([(1, 0)])) # OG without outputs def test_order_3(self) -> None: @@ -576,7 +576,7 @@ def test_order_3(self) -> None: assert not corrections.is_compatible([2, 0, 1]) # Wrong order assert not corrections.is_compatible([0, 1]) # Incomplete order assert corrections.generate_total_measurement_order() in ([0, 1, 2], [0, 2, 1]) - assert nx.utils.graphs_equal(corrections.extract_dag(), nx.DiGraph([(0, 1), (0, 2)])) + assert nx.utils.graphs_equal(corrections.dag(), nx.DiGraph([(0, 1), (0, 2)])) # Only output nodes def test_from_measured_nodes_mapping_0(self) -> None: diff --git a/tests/test_linalg.py b/tests/test_linalg.py index a1ad1ce2f..b642c02d8 100644 --- a/tests/test_linalg.py +++ b/tests/test_linalg.py @@ -180,10 +180,10 @@ def verify_elimination(mat: MatGF2, mat_red: MatGF2, n_cols_red: int, full_reduc class TestLinAlg: @pytest.mark.parametrize("test_case", prepare_test_matrix()) - def test_compute_rank(self, test_case: LinalgTestCase) -> None: + def test_rank(self, test_case: LinalgTestCase) -> None: mat = test_case.matrix rank = test_case.rank - assert mat.compute_rank() == rank + assert mat.rank() == rank @pytest.mark.parametrize("test_case", prepare_test_matrix()) def test_right_inverse(self, benchmark: BenchmarkFixture, test_case: LinalgTestCase) -> None: diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 3d83749eb..809206e84 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -292,7 +292,7 @@ def test_pauli_measurement(self) -> None: pattern.shift_signals(method="mc") pattern = pattern.infer_pauli_measurements() pattern_opt = pattern.remove_pauli_measurements(copy=True) - isolated_nodes = pattern_opt.extract_isolated_nodes() + isolated_nodes = pattern_opt.isolated_nodes() assert isolated_nodes == set() pattern.minimize_space() pattern_opt.minimize_space() @@ -315,7 +315,7 @@ def test_pauli_measured_against_nonmeasured(self, fx_bg: PCG64, jumps: int) -> N state1 = pattern1.simulate_pattern(rng=rng) assert state.isclose(state1) - def test_extract_measurement_commands(self) -> None: + def test_measurement_commands(self) -> None: preset_meas_plane = [ Plane.XY, Plane.XY, @@ -342,7 +342,7 @@ def test_extract_measurement_commands(self) -> None: 7: M(7, Measurement.YZ(0)), 8: M(8, Measurement.XZ(0.5)), } - meas = pattern.extract_measurement_commands() + meas = pattern.measurement_commands() assert meas == ref_meas @pytest.mark.parametrize("plane", Plane) @@ -754,7 +754,7 @@ def test_check_runnability_failures(self) -> None: pattern = Pattern(cmds=[N(0), M(0, s_domain={0})]) with pytest.raises(RunnabilityError) as exc_info: - pattern.extract_partial_order_layers() + pattern.partial_order_layers() assert exc_info.value.node == 0 assert exc_info.value.reason == RunnabilityErrorReason.DomainSelfLoop @@ -770,8 +770,8 @@ def test_check_runnability_failures(self) -> None: assert exc_info.value.node == 1 assert exc_info.value.reason == RunnabilityErrorReason.NotYetMeasured - def test_compute_max_degree_empty_pattern(self) -> None: - assert Pattern().compute_max_degree() == 0 + def test_max_degree_empty_pattern(self) -> None: + assert Pattern().max_degree() == 0 @pytest.mark.parametrize( "test_case", @@ -796,21 +796,21 @@ def test_compute_max_degree_empty_pattern(self) -> None: ), # double edge in DAG ], ) - def test_extract_partial_order_layers(self, test_case: tuple[Pattern, tuple[frozenset[int], ...]]) -> None: - assert test_case[0].extract_partial_order_layers() == test_case[1] + def test_partial_order_layers(self, test_case: tuple[Pattern, tuple[frozenset[int], ...]]) -> None: + assert test_case[0].partial_order_layers() == test_case[1] - def test_extract_partial_order_layers_results(self) -> None: + def test_partial_order_layers_results(self) -> None: c = Circuit(1) c.rz(0, 0.2) p = c.transpile().pattern p = p.infer_pauli_measurements() p.remove_pauli_measurements() - assert p.extract_partial_order_layers() == (frozenset({1}), frozenset({0})) + assert p.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.remove_pauli_measurements() - assert p.extract_partial_order_layers() == (frozenset({1}), frozenset({2})) + assert p.partial_order_layers() == (frozenset({1}), frozenset({2})) class PatternFlowTestCase(NamedTuple): pattern: Pattern @@ -1237,7 +1237,7 @@ def test_no_gate(self) -> None: pattern = circuit.transpile().pattern assert len(list(iter(pattern))) == 0 - def test_extract_graph(self) -> None: + def test_graph(self) -> None: n = 3 g = nx.complete_graph(n) circuit = Circuit(n) @@ -1249,7 +1249,7 @@ def test_extract_graph(self) -> None: circuit.rx(v, ANGLE_PI / 9) pattern = circuit.transpile().pattern - graph = pattern.extract_graph() + graph = pattern.graph() graph_ref: nx.Graph[int] = nx.Graph() graph_ref.add_nodes_from(range(27)) diff --git a/tests/test_space_minimization.py b/tests/test_space_minimization.py index 88099ef6b..9e99104ec 100644 --- a/tests/test_space_minimization.py +++ b/tests/test_space_minimization.py @@ -77,7 +77,7 @@ def test_minimization_by_degree_edge_ordering() -> None: ] ) # Verify Networkx node ordering behaves as expected - graph = p.extract_graph() + graph = p.graph() assert set(graph.edges()) == {(0, 2), (1, 2)} assert set(graph.edges(2)) == {(2, 0), (2, 1)} diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index 6c3c8f121..d5048eb6d 100644 --- a/tests/test_transpiler.py +++ b/tests/test_transpiler.py @@ -253,7 +253,7 @@ def test_classical_outputs_consistency(self, fx_bg: PCG64, jumps: int, axes: lis 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) + non_output_nodes = pattern.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() From a544b0b99ee7314a9084d2d828a00207e26d15d3 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 7 Jul 2026 14:09:00 +0200 Subject: [PATCH 03/10] Up nox --- noxfile.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/noxfile.py b/noxfile.py index ab29a86c6..4dd431d22 100644 --- a/noxfile.py +++ b/noxfile.py @@ -95,16 +95,17 @@ class ReverseDependency: @nox.parametrize( "package", [ - ReverseDependency("https://github.com/TeamGraphix/graphix-stim-backend"), + ReverseDependency("https://github.com/matulni/graphix-stim-backend", branch="rename_methods"), ReverseDependency("https://github.com/TeamGraphix/graphix-symbolic"), ReverseDependency("https://github.com/TeamGraphix/graphix-qasm-parser"), - ReverseDependency("https://github.com/TeamGraphix/graphix-ibmq", doctest_modules=False), - ReverseDependency("https://github.com/TeamGraphix/graphix-stim-compiler"), - ReverseDependency("https://github.com/TeamGraphix/graphix-pyzx"), + ReverseDependency("https://github.com/matulni/graphix-ibmq", doctest_modules=False, branch="rename_methods"), + ReverseDependency("https://github.com/matulni/graphix-stim-compiler", branch="rename_methods"), + ReverseDependency("https://github.com/matulni/graphix-pyzx", branch="rename_methods"), ReverseDependency( - "https://github.com/qat-inria/veriphix", + "https://github.com/matulni/veriphix", doctest_modules=False, install_target=".[dev]", + branch="rename_methods", ), ReverseDependency("https://github.com/matulni/graphix-mqtbench", branch="minimal"), ], From f35a717795ee18826eb05d92f886436901ee72a2 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 7 Jul 2026 14:29:21 +0200 Subject: [PATCH 04/10] Mod try_from --- graphix/fundamentals.py | 6 +++--- graphix/measurements.py | 22 +++++++++++----------- graphix/opengraph.py | 2 +- graphix/optimization.py | 2 +- graphix/pattern.py | 2 +- graphix/pauli.py | 2 +- tests/test_fundamentals.py | 12 ++++++------ tests/test_measurements.py | 2 +- tests/test_pattern.py | 2 +- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/graphix/fundamentals.py b/graphix/fundamentals.py index 809482acf..617cdd872 100644 --- a/graphix/fundamentals.py +++ b/graphix/fundamentals.py @@ -166,7 +166,7 @@ class ComplexUnit(EnumReprMixin, Enum): MINUS_J = 3 @staticmethod - def try_from( + def from_or_none( value: ComplexUnit | SupportsComplexCtor, rel_tol: float = 1e-09, abs_tol: float = 0.0 ) -> ComplexUnit | None: """Return the ComplexUnit instance if the value is compatible, None otherwise. @@ -234,7 +234,7 @@ def __mul__(self, other: ComplexUnit | SupportsComplexCtor) -> ComplexUnit: if isinstance( other, (SupportsComplex, SupportsFloat, SupportsIndex, complex), - ) and (other_ := ComplexUnit.try_from(other)): + ) and (other_ := ComplexUnit.from_or_none(other)): return self.__mul__(other_) return NotImplemented @@ -329,7 +329,7 @@ def clifford(self, clifford_gate: Clifford) -> Self: -Measurement.Y >>> for pauli in PauliMeasurement: ... for clifford in Clifford: - ... assert pauli.to_bloch().clifford(clifford).try_to_pauli() == pauli.clifford(clifford) + ... assert pauli.to_bloch().clifford(clifford).to_pauli_or_none() == pauli.clifford(clifford) >>> Measurement.Y.clifford(Clifford.H).to_bloch() Measurement.XY(1.5) >>> Measurement.Y.to_bloch().clifford(Clifford.H) diff --git a/graphix/measurements.py b/graphix/measurements.py index 55307b9d4..63b297724 100644 --- a/graphix/measurements.py +++ b/graphix/measurements.py @@ -96,7 +96,7 @@ def to_bloch(self) -> BlochMeasurement: For instance, >>> from graphix.measurements import Measurement - >>> Measurement.XY(0.5).try_to_pauli() == Measurement.YZ(0.5).try_to_pauli() == Measurement.Y + >>> Measurement.XY(0.5).to_pauli_or_none() == Measurement.YZ(0.5).to_pauli_or_none() == Measurement.Y True @@ -129,7 +129,7 @@ def downcast_bloch(self) -> BlochMeasurement: """ @abstractmethod - def try_to_pauli(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement | None: + def to_pauli_or_none(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement | None: """Return the measurement description as a Pauli measurement if possible, or ``None`` otherwise. Parameters @@ -158,17 +158,17 @@ def try_to_pauli(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMea Examples -------- >>> from graphix.measurements import Measurement - >>> Measurement.XY(0.5).try_to_pauli() + >>> Measurement.XY(0.5).to_pauli_or_none() Measurement.Y - >>> Measurement.Y.try_to_pauli() + >>> Measurement.Y.to_pauli_or_none() Measurement.Y - >>> Measurement.XY(0.25).try_to_pauli() is None + >>> Measurement.XY(0.25).to_pauli_or_none() is None True >>> from graphix.parameter import Placeholder >>> alpha = Placeholder("alpha") - >>> Measurement.XY(alpha).try_to_pauli() is None + >>> Measurement.XY(alpha).to_pauli_or_none() is None True - >>> Measurement.XY(alpha).subs(alpha, 0.5).try_to_pauli() + >>> Measurement.XY(alpha).subs(alpha, 0.5).to_pauli_or_none() Measurement.Y """ @@ -250,7 +250,7 @@ def downcast_bloch(self) -> BlochMeasurement: return self @override - def try_to_pauli(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement | None: + def to_pauli_or_none(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement | None: if not isinstance(self.angle, (int, float)): return None angle_double = 2 * self.angle @@ -264,7 +264,7 @@ def try_to_pauli(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMea @override def to_pauli_or_bloch(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement | BlochMeasurement: - pm = self.try_to_pauli(rel_tol=rel_tol, abs_tol=abs_tol) + pm = self.to_pauli_or_none(rel_tol=rel_tol, abs_tol=abs_tol) return self if pm is None else pm @override @@ -418,7 +418,7 @@ def to_pauli(self) -> Pauli: """Return the Pauli gate. This method returns an instance of :class:`Pauli` and should - not be confused with :meth:`try_to_pauli`, which overrides the + not be confused with :meth:`to_pauli_or_none`, which overrides the method from :class:`Measurement`, and returns ``self``. Examples @@ -453,7 +453,7 @@ def downcast_bloch(self) -> BlochMeasurement: raise TypeError("Bloch measurement expected, but Pauli measurement was found.") @override - def try_to_pauli(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement: + def to_pauli_or_none(self, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> PauliMeasurement: """Return ``self`` (overridden from :class:`Measurement`).""" return self diff --git a/graphix/opengraph.py b/graphix/opengraph.py index 3618a5679..64b210af4 100644 --- a/graphix/opengraph.py +++ b/graphix/opengraph.py @@ -826,7 +826,7 @@ def xreplace(self: OpenGraph[_M], assignment: Mapping[Parameter, ExpressionOrSup def _warn_non_inferred_pauli_measurements(self, stacklevel: int) -> None: for m in self.measurements.values(): - if isinstance(m, BlochMeasurement) and m.try_to_pauli() is not None: + if isinstance(m, BlochMeasurement) and m.to_pauli_or_none() is not None: warn("Open graph with non-inferred Pauli measurements.", stacklevel=stacklevel + 1) return diff --git a/graphix/optimization.py b/graphix/optimization.py index f85205389..e84eb46fd 100644 --- a/graphix/optimization.py +++ b/graphix/optimization.py @@ -480,7 +480,7 @@ def to_bloch(self) -> StandardizedPattern: def _warn_non_inferred_pauli_measurements(self, stacklevel: int) -> None: for m in self.m_list: - if isinstance(m.measurement, BlochMeasurement) and m.measurement.try_to_pauli() is not None: + if isinstance(m.measurement, BlochMeasurement) and m.measurement.to_pauli_or_none() is not None: warn("Pattern with non-inferred Pauli measurements.", stacklevel=stacklevel + 1) return diff --git a/graphix/pattern.py b/graphix/pattern.py index 500718488..7960db9ec 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1502,7 +1502,7 @@ def _warn_non_inferred_pauli_measurements(self, stacklevel: int) -> None: if ( cmd.kind == CommandKind.M and isinstance(cmd.measurement, BlochMeasurement) - and cmd.measurement.try_to_pauli() is not None + and cmd.measurement.to_pauli_or_none() is not None ): warnings.warn("Pattern with non-inferred Pauli measurements.", stacklevel=stacklevel + 1) return diff --git a/graphix/pauli.py b/graphix/pauli.py index b39230c9c..c20683786 100644 --- a/graphix/pauli.py +++ b/graphix/pauli.py @@ -140,7 +140,7 @@ def __matmul__(self, other: Pauli) -> Pauli: def __mul__(self, other: ComplexUnit | SupportsComplexCtor) -> Pauli: """Return the product of two Paulis.""" - if u := ComplexUnit.try_from(other): + if u := ComplexUnit.from_or_none(other): return dataclasses.replace(self, unit=self.unit * u) return NotImplemented diff --git a/tests/test_fundamentals.py b/tests/test_fundamentals.py index 2460fad29..ec0240256 100644 --- a/tests/test_fundamentals.py +++ b/tests/test_fundamentals.py @@ -84,12 +84,12 @@ def test_int(self) -> None: class TestComplexUnit: - def test_try_from(self) -> None: - assert ComplexUnit.try_from(ComplexUnit.ONE) == ComplexUnit.ONE - assert ComplexUnit.try_from(1) == ComplexUnit.ONE - assert ComplexUnit.try_from(1.0) == ComplexUnit.ONE - assert ComplexUnit.try_from(1.0 + 0.0j) == ComplexUnit.ONE - assert ComplexUnit.try_from(3) is None + def test_from_or_none(self) -> None: + assert ComplexUnit.from_or_none(ComplexUnit.ONE) == ComplexUnit.ONE + assert ComplexUnit.from_or_none(1) == ComplexUnit.ONE + assert ComplexUnit.from_or_none(1.0) == ComplexUnit.ONE + assert ComplexUnit.from_or_none(1.0 + 0.0j) == ComplexUnit.ONE + assert ComplexUnit.from_or_none(3) is None def test_from_properties(self) -> None: assert ComplexUnit.from_properties() == ComplexUnit.ONE diff --git a/tests/test_measurements.py b/tests/test_measurements.py index 516542987..c88824425 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -20,7 +20,7 @@ def test_isclose(self) -> None: @pytest.mark.parametrize("pauli", PauliMeasurement) def test_pauli_to_bloch(pauli: PauliMeasurement) -> None: bloch = pauli.to_bloch() - pauli_back = bloch.try_to_pauli() + pauli_back = bloch.to_pauli_or_none() assert pauli == pauli_back diff --git a/tests/test_pattern.py b/tests/test_pattern.py index 809206e84..d8c740772 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -255,7 +255,7 @@ def test_pauli_measurement_random_circuit_all_paulis(self, fx_bg: PCG64, jumps: pattern.remove_pauli_measurements() input_node_set = set(pattern.input_nodes) assert not any( - cmd.measurement.try_to_pauli() is not None + cmd.measurement.to_pauli_or_none() is not None for cmd in pattern if cmd.kind == CommandKind.M and cmd.node not in input_node_set ) From 8f046536bbc65269dbe5017d93f706303ca0cb60 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 7 Jul 2026 16:26:21 +0200 Subject: [PATCH 05/10] Up CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f40251f..b88b0dbb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,11 @@ 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. +- #557: Homogeneize namespace. See PR or commit text for detailed renaming list. Fixed the following convention: + - `.to_` for transformations that can only return `object` or raise an exception. Equivalently, we use `.from_` for constructors. + - `.to__or_none` for transformations that can return `object` or `None`. Equivalently, we use `.from__or_none` for constructors. + - accessor methods use nounds instead of verb + noun. Example: `Pattern.graph` instead of `Pattern.extract_graph`. + ## [0.3.5] - 2026-03-26 ### Added From f386b1aa32d12ed41a4ca2d00ea1acaa6dc47432 Mon Sep 17 00:00:00 2001 From: matulni Date: Tue, 7 Jul 2026 16:58:41 +0200 Subject: [PATCH 06/10] Fix docstring --- graphix/flow/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/graphix/flow/core.py b/graphix/flow/core.py index 63a0a4473..d31de9437 100644 --- a/graphix/flow/core.py +++ b/graphix/flow/core.py @@ -580,13 +580,7 @@ class PauliFlow(Generic[_AM_co]): ----- - See Definition 5 in Ref. [1] for a definition of Pauli flow. - <<<<<<< HEAD - - The flow's correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only `og` and `correction_function` are necessary to initialize an `PauliFlow` instance (see :func:`PauliFlow.from_correction_matrix_or_none`). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow's XZ-corrections, so it is stored as an attribute. - - - A correct flow can only exist on an open graph with output nodes, so `layers[0]` always contains a finite set of nodes. - ======= - The flow's correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only ``og`` and ``correction_function`` are necessary to initialize an ``PauliFlow`` instance (see :func:`PauliFlow.try_from_correction_matrix`). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow's XZ-corrections, so it is stored as an attribute. - >>>>>>> master References ---------- From 4a0a9f8da44867fd12342b52e3d562322c699287 Mon Sep 17 00:00:00 2001 From: matulni Date: Mon, 13 Jul 2026 13:36:28 +0200 Subject: [PATCH 07/10] Add missing renaming suggested by Thierry --- docs/source/tutorial.rst | 2 +- graphix/flow/core.py | 10 ++++---- graphix/noise_models/amplitude_damping.py | 4 ++-- graphix/noise_models/depolarising.py | 4 ++-- graphix/noise_models/noise_model.py | 2 +- graphix/opengraph.py | 4 ++-- graphix/optimization.py | 6 ++--- graphix/pattern.py | 4 ++-- graphix/remove_pauli_measurements.py | 10 ++++---- graphix/sim/density_matrix.py | 2 +- tests/test_remove_pauli_measurements.py | 28 +++++++++++------------ 11 files changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 5e7a44227..85bb8233d 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -269,7 +269,7 @@ In the following example, we apply dephasing noise to qubit preparation commands return 1 @typing_extensions.override - def to_kraus_channel(self) -> KrausChannel: + def to_krauschannel(self) -> KrausChannel: return dephasing_channel(self.prob) @dataclass diff --git a/graphix/flow/core.py b/graphix/flow/core.py index d31de9437..a7d55de2a 100644 --- a/graphix/flow/core.py +++ b/graphix/flow/core.py @@ -580,7 +580,7 @@ class PauliFlow(Generic[_AM_co]): ----- - See Definition 5 in Ref. [1] for a definition of Pauli flow. - - The flow's correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only ``og`` and ``correction_function`` are necessary to initialize an ``PauliFlow`` instance (see :func:`PauliFlow.try_from_correction_matrix`). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow's XZ-corrections, so it is stored as an attribute. + - The flow's correction function defines a partial order (see Def. 2.8 and 2.9, Lemma 2.11 and Theorem 2.12 in Ref. [2]), therefore, only ``og`` and ``correction_function`` are necessary to initialize an ``PauliFlow`` instance (see :func:`PauliFlow.from_correctionmatrix_or_none`). However, flow-finding algorithms generate a partial order in a layer form, which is necessary to extract the flow's XZ-corrections, so it is stored as an attribute. References ---------- @@ -595,7 +595,7 @@ class PauliFlow(Generic[_AM_co]): _CF_PREFIX: str = "p" # Correction function prefix for printing @classmethod - def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_AM_co]) -> Self | None: + def from_correctionmatrix_or_none(cls, correction_matrix: CorrectionMatrix[_AM_co]) -> Self | None: """Initialize a ``PauliFlow`` object from a matrix encoding a correction function. Parameters @@ -1008,7 +1008,7 @@ class GFlow(PauliFlow[_PM_co], Generic[_PM_co]): @override @classmethod - def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> Self | None: + def from_correctionmatrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> Self | None: """Initialize a ``GFlow`` object from a matrix encoding a correction function. Parameters @@ -1029,7 +1029,7 @@ def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_ ---------- [1] Mitosek and Backens, 2024 (arXiv:2410.23439). """ - return super().from_correction_matrix_or_none(correction_matrix) + return super().from_correctionmatrix_or_none(correction_matrix) @override def to_xzcorrections(self) -> XZCorrections[_PM_co]: @@ -1187,7 +1187,7 @@ class CausalFlow(GFlow[_PM_co], Generic[_PM_co]): @override @classmethod - def from_correction_matrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> None: + def from_correctionmatrix_or_none(cls, correction_matrix: CorrectionMatrix[_PM_co]) -> None: raise NotImplementedError("Initialization of a causal flow from a correction matrix is not supported.") @override diff --git a/graphix/noise_models/amplitude_damping.py b/graphix/noise_models/amplitude_damping.py index ea23f9311..acbc9fb78 100644 --- a/graphix/noise_models/amplitude_damping.py +++ b/graphix/noise_models/amplitude_damping.py @@ -48,7 +48,7 @@ def nqubits(self) -> int: return 1 @typing_extensions.override - def to_kraus_channel(self) -> KrausChannel: + def to_krauschannel(self) -> KrausChannel: """Return the Kraus channel describing the noise element.""" return amplitude_damping_channel(self.prob) @@ -75,7 +75,7 @@ def nqubits(self) -> int: return 2 @typing_extensions.override - def to_kraus_channel(self) -> KrausChannel: + def to_krauschannel(self) -> KrausChannel: """Return the Kraus channel describing the noise element.""" return two_qubit_amplitude_damping_channel(self.prob) diff --git a/graphix/noise_models/depolarising.py b/graphix/noise_models/depolarising.py index 32866e4ae..a455ea587 100644 --- a/graphix/noise_models/depolarising.py +++ b/graphix/noise_models/depolarising.py @@ -44,7 +44,7 @@ def nqubits(self) -> int: return 1 @typing_extensions.override - def to_kraus_channel(self) -> KrausChannel: + def to_krauschannel(self) -> KrausChannel: """Return the Kraus channel describing the noise element.""" return depolarising_channel(self.prob) @@ -71,7 +71,7 @@ def nqubits(self) -> int: return 2 @typing_extensions.override - def to_kraus_channel(self) -> KrausChannel: + def to_krauschannel(self) -> KrausChannel: """Return the Kraus channel describing the noise element.""" return two_qubit_depolarising_channel(self.prob) diff --git a/graphix/noise_models/noise_model.py b/graphix/noise_models/noise_model.py index 339ebd282..94b2237f1 100644 --- a/graphix/noise_models/noise_model.py +++ b/graphix/noise_models/noise_model.py @@ -38,7 +38,7 @@ def nqubits(self) -> int: """Return the number of qubits targetted by the noise.""" @abstractmethod - def to_kraus_channel(self) -> KrausChannel: + def to_krauschannel(self) -> KrausChannel: """Return the Kraus channel describing the noise.""" diff --git a/graphix/opengraph.py b/graphix/opengraph.py index 64b210af4..b0be7e5c5 100644 --- a/graphix/opengraph.py +++ b/graphix/opengraph.py @@ -512,7 +512,7 @@ def to_gflow_or_none(self: OpenGraph[_PM_co]) -> GFlow[_PM_co] | None: correction_matrix = compute_correction_matrix(aog) if correction_matrix is None: return None - return GFlow.from_correction_matrix_or_none( + return GFlow.from_correctionmatrix_or_none( correction_matrix ) # The constructor returns `None` if the correction matrix is not compatible with any partial order on the open graph. @@ -565,7 +565,7 @@ def to_pauliflow_or_none(self: OpenGraph[_AM_co], *, stacklevel: int = 1) -> Pau correction_matrix = compute_correction_matrix(aog) if correction_matrix is None: return None - return PauliFlow.from_correction_matrix_or_none( + return PauliFlow.from_correctionmatrix_or_none( correction_matrix ) # The constructor returns `None` if the correction matrix is not compatible with any partial order on the open graph. diff --git a/graphix/optimization.py b/graphix/optimization.py index 50ad6c3d1..a571218a8 100644 --- a/graphix/optimization.py +++ b/graphix/optimization.py @@ -243,7 +243,7 @@ def perform_pauli_pushing( If you need to recover the cut between Pauli measurements and non-Pauli measurements or the shifted signal, you can use - :meth:`~graphix.remove_pauli_measurements.PauliPushingCut.from_standardized_pattern` instead. + :meth:`~graphix.remove_pauli_measurements.PauliPushingCut.from_standardizedpattern` instead. Parameters ---------- @@ -262,9 +262,9 @@ def perform_pauli_pushing( """ from graphix.remove_pauli_measurements import PauliPushingCut # noqa: PLC0415 - return PauliPushingCut.from_standardized_pattern( + return PauliPushingCut.from_standardizedpattern( self, leave_nodes, stacklevel=stacklevel + 1 - ).to_standardized_pattern() + ).to_standardizedpattern() def max_space(self) -> int: """Compute the maximum number of nodes that must be present in the graph (graph space) during the execution of the space-optimal pattern for the given measurement order. diff --git a/graphix/pattern.py b/graphix/pattern.py index 7960db9ec..fe164171f 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1784,7 +1784,7 @@ def perform_pauli_pushing( If you need to recover the cut between Pauli measurements and non-Pauli measurements or the shifted signal, you can use - :meth:`~graphix.remove_pauli_measurements.PauliPushingCut.from_standardized_pattern` instead. + :meth:`~graphix.remove_pauli_measurements.PauliPushingCut.from_standardizedpattern` instead. Parameters ---------- @@ -1854,7 +1854,7 @@ def remove_pauli_measurements( from graphix.remove_pauli_measurements import PauliPushingCut, remove_pauli_measurements # noqa: PLC0415 standardized_pattern = optimization.StandardizedPattern.from_pattern(self) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern, stacklevel=stacklevel + 1) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern, stacklevel=stacklevel + 1) standardized_pattern = remove_pauli_measurements(cut, stacklevel=stacklevel + 1) pattern = standardized_pattern.to_pattern() if standardize else standardized_pattern.to_space_optimal_pattern() if copy: diff --git a/graphix/remove_pauli_measurements.py b/graphix/remove_pauli_measurements.py index fd06ebc0a..71decacb7 100644 --- a/graphix/remove_pauli_measurements.py +++ b/graphix/remove_pauli_measurements.py @@ -73,7 +73,7 @@ def measurements(self) -> tuple[Command.M, ...]: return self.pauli_measurements + self.non_pauli_measurements @classmethod - def from_standardized_pattern( + def from_standardizedpattern( cls, pattern: StandardizedPattern, leave_nodes: AbstractSet[Node] | None = None, *, stacklevel: int = 1 ) -> PauliPushingCut: """Move Pauli measurements before the other measurements and return the cut between Pauli measurements and non-Pauli measurements. @@ -159,7 +159,7 @@ def from_standardized_pattern( pauli_measurements.append(Command.M(node=cmd.node, measurement=cmd.measurement)) return cls(pattern, tuple(pauli_measurements), tuple(non_pauli_measurements), shifted_domains) - def to_standardized_pattern(self) -> StandardizedPattern: + def to_standardizedpattern(self) -> StandardizedPattern: """Return the standardized pattern where all Pauli measurements have been pushed.""" return StandardizedPattern( self.original_pattern.input_nodes, @@ -219,7 +219,7 @@ class _RemovePauliMeasurements: This class is instantiated from a Pauli-pushing cut and can be converted back to a standardized pattern with the method - :meth:`to_standardized_pattern`. The public methods preserve the + :meth:`to_standardizedpattern`. The public methods preserve the pattern semantics as invariant, such that an equivalent standardized pattern can be obtained at any stage of the process. """ @@ -512,7 +512,7 @@ def _create_new_m(self, original_m: Command.M) -> Command.M | None: new_m.t_domain = _map_domain(self.node_map, new_m.t_domain) return new_m - def to_standardized_pattern(self) -> StandardizedPattern: + def to_standardizedpattern(self) -> StandardizedPattern: # Prepare only nodes that have not been removed (i.e., that still appear in `node_specs`) n_list = tuple(cmd_n for cmd_n in self.cut.original_pattern.n_list if cmd_n.node in self.node_specs) output_nodes = tuple(self.node_map[node] for node in self.cut.original_pattern.output_nodes) @@ -621,4 +621,4 @@ def remove_pauli_measurements(cut: PauliPushingCut, *, stacklevel: int = 1) -> S ): break process.remove_isolated_internal_nodes(stacklevel=stacklevel + 1) - return process.to_standardized_pattern() + return process.to_standardizedpattern() diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index 6aff2c05a..fe243c65b 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -455,7 +455,7 @@ def apply_noise(self, qubits: Sequence[int], noise: Noise) -> None: noise : Noise Noise to apply """ - channel = noise.to_kraus_channel() + channel = noise.to_krauschannel() self.apply_channel(channel, qubits) def subs(self, variable: Parameter, substitute: ExpressionOrSupportsFloat) -> DensityMatrix: diff --git a/tests/test_remove_pauli_measurements.py b/tests/test_remove_pauli_measurements.py index 105c97149..6ea1eea1e 100644 --- a/tests/test_remove_pauli_measurements.py +++ b/tests/test_remove_pauli_measurements.py @@ -76,10 +76,10 @@ def test_local_complement(fx_rng: Generator, measured_set: AbstractSet[int]) -> og = opengraph_lemma_2_31({node: Measurement.XY(0.25) for node in measured_set}) pattern = og.to_pattern() standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.local_complement(0) - standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() + standardized_pattern2 = remove_pauli_measurements.to_standardizedpattern() og2 = standardized_pattern2.to_opengraph() expected_graph: Graph = nx.Graph( [ @@ -100,10 +100,10 @@ def test_pivot_edge(fx_rng: Generator, measured_set: AbstractSet[int]) -> None: og = opengraph_lemma_2_32({node: Measurement.XY(0.25) for node in measured_set}) pattern = og.to_pattern() standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.pivot_edge(0, 1) - standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() + standardized_pattern2 = remove_pauli_measurements.to_standardizedpattern() og2 = standardized_pattern2.to_opengraph() expected_graph: Graph = nx.Graph( [ @@ -137,10 +137,10 @@ def test_remove_z(fx_rng: Generator, node: Node, sign: Sign) -> None: og = opengraph_lemma_2_32({node: PauliMeasurement(Axis.Z, sign)}) pattern = og.to_pattern() standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.remove_z(node, sign) - standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() + standardized_pattern2 = remove_pauli_measurements.to_standardizedpattern() pattern2 = standardized_pattern2.to_pattern() check_pattern_equivalence(pattern, pattern2, rng=fx_rng) @@ -151,10 +151,10 @@ def test_remove_y(fx_rng: Generator, node: Node, sign: Sign) -> None: og = opengraph_lemma_2_32({node: PauliMeasurement(Axis.Y, sign)}) pattern = og.to_pattern() standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.remove_y(node, sign) - standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() + standardized_pattern2 = remove_pauli_measurements.to_standardizedpattern() pattern2 = standardized_pattern2.to_pattern() check_pattern_equivalence(pattern, pattern2, rng=fx_rng) @@ -164,10 +164,10 @@ def test_remove_x_with_internal_neighbor(fx_rng: Generator, sign: Sign) -> None: og = opengraph_lemma_2_32({0: PauliMeasurement(Axis.X, sign)}) pattern = og.to_pattern() standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) remove_pauli_measurements = _RemovePauliMeasurements(cut) remove_pauli_measurements.remove_x_with_internal_neighbor(0, 1, sign) - standardized_pattern2 = remove_pauli_measurements.to_standardized_pattern() + standardized_pattern2 = remove_pauli_measurements.to_standardizedpattern() pattern2 = standardized_pattern2.to_pattern() check_pattern_equivalence(pattern, pattern2, rng=fx_rng) @@ -182,7 +182,7 @@ def all_bloch_measurement_or_input_node(input_nodes: Iterable[Node], measurement def check_pattern(pattern: Pattern, rng: Generator) -> None: standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) standardized_pattern2 = remove_pauli_measurements(cut) assert all_bloch_measurement_or_input_node(standardized_pattern2.input_nodes, standardized_pattern2.m_list) @@ -225,7 +225,7 @@ def test_step_4() -> None: og = OpenGraph(graph, input_nodes=(0,), output_nodes=(2,), measurements=measurements) pattern = og.to_pattern() standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) standardized_pattern2 = remove_pauli_measurements(cut) assert len(standardized_pattern2.m_list) == 1 @@ -238,7 +238,7 @@ def test_step_4_no_flow() -> None: # in the `try_pivot_x_with_output_node` method. pattern = Pattern(input_nodes=(0,), output_nodes=(0,), cmds=[Command.N(1), Command.E((0, 1)), Command.M(1)]) standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) standardized_pattern2 = remove_pauli_measurements(cut) assert len(standardized_pattern2.m_list) == 1 @@ -318,7 +318,7 @@ def test_try_pivot_x_with_output_node_after_pivot() -> None: ] ) standardized_pattern = StandardizedPattern.from_pattern(pattern) - cut = PauliPushingCut.from_standardized_pattern(standardized_pattern) + cut = PauliPushingCut.from_standardizedpattern(standardized_pattern) process = _RemovePauliMeasurements(cut) process.remove_x_with_internal_neighbor(0, 1, Sign.PLUS) # Fail if pivot is applied to the original node From 8644f3bd2ea97650808d060883cbefb0426cfc6e Mon Sep 17 00:00:00 2001 From: matulni Date: Mon, 13 Jul 2026 13:44:45 +0200 Subject: [PATCH 08/10] Rename Statevec into Statevector --- docs/source/intro.rst | 2 +- docs/source/lc-mbqc.rst | 2 +- docs/source/simulator.rst | 2 +- docs/source/tutorial.rst | 2 +- examples/rotation.py | 6 +-- graphix/__init__.py | 4 +- graphix/graphsim.py | 6 +-- graphix/pattern.py | 8 +-- graphix/pretty_print.py | 10 ++-- graphix/sim/__init__.py | 4 +- graphix/sim/base_backend.py | 28 +++++------ graphix/sim/data.py | 4 +- graphix/sim/density_matrix.py | 10 ++-- graphix/sim/statevec.py | 44 ++++++++--------- graphix/simulator.py | 6 +-- graphix/states.py | 2 +- graphix/transpiler.py | 8 +-- tests/test_density_matrix.py | 22 ++++----- tests/test_graphsim.py | 6 +-- tests/test_parameter.py | 6 +-- tests/test_pattern.py | 12 ++--- tests/test_pretty_print.py | 27 +++++----- tests/test_simulator.py | 8 +-- tests/test_statevec.py | 90 +++++++++++++++++----------------- tests/test_statevec_backend.py | 22 ++++----- tests/test_tnsim.py | 4 +- tests/test_transpiler.py | 4 +- 27 files changed, 175 insertions(+), 174 deletions(-) diff --git a/docs/source/intro.rst b/docs/source/intro.rst index 3fa3686c8..49e40f15a 100644 --- a/docs/source/intro.rst +++ b/docs/source/intro.rst @@ -133,7 +133,7 @@ Note that the input state has *teleported* to qubits 6 and 7 after the computati og = OpenGraph(nx.Graph([0, 1]), output_nodes=[0, 1]) >>> print(og.to_pattern().simulate_pattern()) - Statevec object with statevector [[ 0.5+0.j 0.5+0.j] + Statevector object with statevector [[ 0.5+0.j 0.5+0.j] [ 0.5+0.j -0.5+0.j]] and length (2, 2). diff --git a/docs/source/lc-mbqc.rst b/docs/source/lc-mbqc.rst index 4bd8a7118..687bfb874 100644 --- a/docs/source/lc-mbqc.rst +++ b/docs/source/lc-mbqc.rst @@ -32,7 +32,7 @@ For example, let us try the transformation rules in Elliot *et al.* [#el]_ with :alt: Pauli measurement of graph -We can perform this with :class:`~graphix.graphsim.GraphState` class and then compare to the :class:`~graphix.sim.statevec.Statevec` full statevector simulation: +We can perform this with :class:`~graphix.graphsim.GraphState` class and then compare to the :class:`~graphix.sim.statevec.Statevector` full statevector simulation: .. code-block:: python diff --git a/docs/source/simulator.rst b/docs/source/simulator.rst index 821a1be48..6fd2cb13b 100644 --- a/docs/source/simulator.rst +++ b/docs/source/simulator.rst @@ -35,7 +35,7 @@ Statevector .. autoclass:: StatevectorBackend :members: -.. autoclass:: Statevec +.. autoclass:: Statevector :members: Density Matrix diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 85bb8233d..ed0ed0925 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -54,7 +54,7 @@ We can use the :class:`~graphix.simulator.PatternSimulator` to classically simul Alternatively, we can simply call :meth:`~graphix.pattern.Pattern.simulate_pattern` of :class:`~graphix.pattern.Pattern` object to do it in one line: >>> print(pattern.simulate_pattern(backend='statevector')) -Statevec object with statevector [1.+0.j 0.+0.j] and length (2,). +Statevector object with statevector [1.+0.j 0.+0.j] and length (2,). Note again that we started with :math:`|+\rangle` state so the answer is correct. diff --git a/examples/rotation.py b/examples/rotation.py index 0de287855..812fa8c53 100644 --- a/examples/rotation.py +++ b/examples/rotation.py @@ -18,14 +18,14 @@ import numpy as np -from graphix import Circuit, Statevec +from graphix import Circuit, Statevector from graphix.ops import Ops from graphix.states import BasicStates rng = np.random.default_rng() # %% -# Here, :class:`~graphix.sim.statevec.Statevec` is our simple statevector simulator class. +# Here, :class:`~graphix.sim.statevec.Statevector` is our simple statevector simulator class. # Next, let us define the problem with a standard quantum circuit. # Note that in graphix all qubits starts in ``|+>`` states. For this example, # we use Hadamard gate (:meth:`graphix.transpiler.Circuit.h`) to start with ``|0>`` states instead. @@ -74,7 +74,7 @@ # %% # Let us compare with statevector simulation of the original circuit: -state = Statevec(nqubit=2, data=BasicStates.ZERO) # starts with |0> states +state = Statevector(nqubit=2, data=BasicStates.ZERO) # starts with |0> states state.evolve_single(Ops.rx(theta[0]), 0) state.evolve_single(Ops.rx(theta[1]), 1) print("overlap of states: ", np.abs(np.dot(state.psi.flatten().conjugate(), out_state.psi.flatten()))) diff --git a/graphix/__init__.py b/graphix/__init__.py index d3d28a818..69a95cba4 100644 --- a/graphix/__init__.py +++ b/graphix/__init__.py @@ -20,7 +20,7 @@ from graphix.pattern import DrawPatternAnnotations, Pattern from graphix.pauli import Pauli from graphix.pretty_print import OutputFormat -from graphix.sim import DensityMatrix, DensityMatrixBackend, Statevec, StatevectorBackend +from graphix.sim import DensityMatrix, DensityMatrixBackend, Statevector, StatevectorBackend from graphix.space_minimization import SpaceMinimizationHeuristics from graphix.states import BasicStates, PlanarState from graphix.transpiler import Circuit @@ -64,7 +64,7 @@ "Sign", "SpaceMinimizationHeuristics", "StandardizedPattern", - "Statevec", + "Statevector", "StatevectorBackend", "XZCorrections", "__version__", diff --git a/graphix/graphsim.py b/graphix/graphsim.py index 7aaf3cf2d..8e9129248 100644 --- a/graphix/graphsim.py +++ b/graphix/graphsim.py @@ -11,7 +11,7 @@ from graphix.clifford import Clifford from graphix.measurements import outcome from graphix.ops import Ops -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector if TYPE_CHECKING: import functools @@ -491,11 +491,11 @@ def draw(self, fill_color: str = "C0") -> None: g.add_edges_from(edges) nx.draw(g, labels=labels, node_color=colors, edgecolors="k") - def to_statevector(self) -> Statevec: + def to_statevector(self) -> Statevector: """Convert the graph state into a state vector.""" node_list = list(self.nodes) nqubit = len(self.nodes) - gstate = Statevec(nqubit=nqubit) + gstate = Statevector(nqubit=nqubit) # map graph node indices into 0 - (nqubit-1) for qubit indexing in statevec imapping = {node_list[i]: i for i in range(nqubit)} mapping = [node_list[i] for i in range(nqubit)] diff --git a/graphix/pattern.py b/graphix/pattern.py index fe164171f..1b3527b9e 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -28,7 +28,7 @@ from graphix.measurements import BlochMeasurement, Measurement, Outcome, toggle_outcome from graphix.pretty_print import OutputFormat, pattern_to_str from graphix.qasm3_exporter import pattern_to_qasm3_lines -from graphix.sim import DensityMatrix, MBQCTensorNet, Statevec +from graphix.sim import DensityMatrix, MBQCTensorNet, Statevector from graphix.simulator import PatternSimulator from graphix.space_minimization import pattern_max_space from graphix.states import BasicStates @@ -59,7 +59,7 @@ K = TypeVar("K") V = TypeVar("V") -_BuiltinBackendState = DensityMatrix | Statevec | MBQCTensorNet +_BuiltinBackendState = DensityMatrix | Statevector | MBQCTensorNet class DrawPatternAnnotations(Enum): @@ -1402,14 +1402,14 @@ def simulate_pattern( self, backend: StatevectorBackend | Literal["statevector"] = "statevector", input_state: State - | Statevec + | Statevector | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]] | None = ..., rng: Generator | None = ..., **kwargs: Any, - ) -> Statevec: ... + ) -> Statevector: ... @overload def simulate_pattern( diff --git a/graphix/pretty_print.py b/graphix/pretty_print.py index 4303903fe..557cd8e80 100644 --- a/graphix/pretty_print.py +++ b/graphix/pretty_print.py @@ -27,7 +27,7 @@ from graphix.fundamentals import Angle from graphix.pattern import Pattern from graphix.sim.density_matrix import DensityMatrix - from graphix.sim.statevec import _ENCODING, Statevec + from graphix.sim.statevec import _ENCODING, Statevector class OutputFormat(Enum): @@ -817,7 +817,7 @@ def _factor_uniform_magnitude( def statevec_to_str( - statevec: Statevec, + statevec: Statevector, output: OutputFormat, *, encoding: _ENCODING = "MSB", @@ -828,18 +828,18 @@ def statevec_to_str( ) -> str: r"""Return a ket-notation string representation of a statevector. - Amplitudes close to zero are omitted (see :meth:`graphix.sim.statevec.Statevec.to_dict`) + Amplitudes close to zero are omitted (see :meth:`graphix.sim.statevec.Statevector.to_dict`) and the remaining ones are pretty-printed with :func:`complex_to_str`. Parameters ---------- - statevec : Statevec + statevec : Statevector The statevector to format. output : OutputFormat Desired formatting style (``ASCII``, ``LaTeX`` or ``Unicode``). encoding : {"LSB", "MSB"}, optional Bit-ordering convention for the basis kets (default: ``"MSB"``). - See :meth:`graphix.sim.statevec.Statevec.to_dict`. + See :meth:`graphix.sim.statevec.Statevector.to_dict`. max_denominator : int, optional Maximum denominator used by the amplitude recognition (default: ``1000``). atol : float, optional diff --git a/graphix/sim/__init__.py b/graphix/sim/__init__.py index b83deca6a..81def8c71 100644 --- a/graphix/sim/__init__.py +++ b/graphix/sim/__init__.py @@ -5,7 +5,7 @@ from graphix.sim.base_backend import Backend from graphix.sim.data import Data from graphix.sim.density_matrix import DensityMatrix, DensityMatrixBackend -from graphix.sim.statevec import Statevec, StatevectorBackend +from graphix.sim.statevec import Statevector, StatevectorBackend from graphix.sim.tensornet import MBQCTensorNet, TensorNetworkBackend __all__ = [ @@ -14,7 +14,7 @@ "DensityMatrix", "DensityMatrixBackend", "MBQCTensorNet", - "Statevec", + "Statevector", "StatevectorBackend", "TensorNetworkBackend", ] diff --git a/graphix/sim/base_backend.py b/graphix/sim/base_backend.py index 5bdd12ab1..a6cdaf10c 100644 --- a/graphix/sim/base_backend.py +++ b/graphix/sim/base_backend.py @@ -338,14 +338,14 @@ class DenseState(ABC): This includes both state vectors (for pure states) and density matrices (for mixed states). - This class serves as a common parent for :class:`Statevec` and :class:`DensityMatrix`, which + This class serves as a common parent for :class:`Statevector` and :class:`DensityMatrix`, which implement the concrete representations of dense quantum states. It is used in simulation backends that operate using standard linear algebra on the full - state, such as :class:`StatevecBackend` and :class:`DensityMatrixBackend`. + state, such as :class:`StatevectorBackend` and :class:`DensityMatrixBackend`. See Also -------- - :class:`Statevec`, :class:`DensityMatrix` + :class:`Statevector`, :class:`DensityMatrix` Notes ----- @@ -536,18 +536,18 @@ class Backend(Generic[_StateT_co]): - Tracking and exposing the underlying quantum state Examples of concrete subclasses include: - - `StatevecBackend` (pure states via state vectors) + - `StatevectorBackend` (pure states via state vectors) - `DensityMatrixBackend` (mixed states via density matrices) - `TensorNetworkBackend` (compressed states via tensor networks) Parameters ---------- state : BackendState - internal state of the backend: instance of :class:`Statevec`, :class:`DensityMatrix`, or :class:`MBQCTensorNet`. + internal state of the backend: instance of :class:`Statevector`, :class:`DensityMatrix`, or :class:`MBQCTensorNet`. See Also -------- - :class:`BackendState`, :`class:`DenseStateBackend`, :class:`StatevecBackend`, :class:`DensityMatrixBackend`, :class:`TensorNetworkBackend` + :class:`BackendState`, :`class:`DenseStateBackend`, :class:`StatevectorBackend`, :class:`DensityMatrixBackend`, :class:`TensorNetworkBackend` Notes ----- @@ -556,18 +556,18 @@ class Backend(Generic[_StateT_co]): The class hierarchy of states mirrors the class hierarchy of backends: - `DenseStateBackend` and `TensorNetworkBackend` are subclasses of `Backend`, and `DenseState` and `MBQCTensorNet` are subclasses of `BackendState`. - - `StatevecBackend` and `DensityMatrixBackend` are subclasses of `DenseStateBackend`, - and `Statevec` and `DensityMatrix` are subclasses of `DenseState`. + - `StatevectorBackend` and `DensityMatrixBackend` are subclasses of `DenseStateBackend`, + and `Statevector` and `DensityMatrix` are subclasses of `DenseState`. The type variable `_StateT_co` specifies the type of the ``state`` field, so that subclasses provide a precise type for this field: - - `StatevecBackend` is a subtype of ``Backend[Statevec]``. + - `StatevectorBackend` is a subtype of ``Backend[Statevector]``. - `DensityMatrixBackend` is a subtype of ``Backend[DensityMatrix]``. - `TensorNetworkBackend` is a subtype of ``Backend[MBQCTensorNet]``. The type variables `_StateT_co` and `_DenseStateT_co` are declared as covariant. That is, ``Backend[T1]`` is a subtype of ``Backend[T2]`` if ``T1`` is a subtype of ``T2``. - This means that `StatevecBackend`, `DensityMatrixBackend`, and `TensorNetworkBackend` are + This means that `StatevectorBackend`, `DensityMatrixBackend`, and `TensorNetworkBackend` are all subtypes of ``Backend[BackendState]``. This covariance is sound because backends are frozen dataclasses; thus, the type of ``state`` cannot be changed after instantiation. @@ -621,13 +621,13 @@ def add_nodes(self, nodes: Sequence[int], data: Data = BasicStates.PLUS) -> None Some backends support other forms of state specification. - - ``StatevecBackend`` supports arbitrary state vectors: + - ``StatevectorBackend`` supports arbitrary state vectors: - 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. - ``DensityMatrixBackend`` supports both state vectors and density matrices: - - State vectors are handled as in ``StatevecBackend``, and converted to + - State vectors are handled as in ``StatevectorBackend``, and converted to density matrices. - A density matrix must have shape :math:`2^n \times 2^n`, where :math:`n = \mathrm{len}(nodes)`, and is used to jointly initialize the new nodes. @@ -710,7 +710,7 @@ class DenseStateBackend(Backend[_DenseStateT_co], Generic[_DenseStateT_co]): This class defines common functionality for backends that store the entire quantum state as a dense array—either as a state vector (pure state) or a density matrix (mixed state)—and perform quantum operations using standard linear algebra. It is - designed to be the shared base class of `StatevecBackend` and `DensityMatrixBackend`. + designed to be the shared base class of `StatevectorBackend` and `DensityMatrixBackend`. In contrast to :class:`TensorNetworkBackend`, which uses structured and compressed representations (e.g., matrix product states) to scale to larger systems, @@ -731,7 +731,7 @@ class DenseStateBackend(Backend[_DenseStateT_co], Generic[_DenseStateT_co]): See Also -------- - :class:`StatevecBackend`, :class:`DensityMatrixBackend`, :class:`TensorNetworkBackend` + :class:`StatevectorBackend`, :class:`DensityMatrixBackend`, :class:`TensorNetworkBackend` """ node_index: NodeIndex = dataclasses.field(default_factory=NodeIndex) diff --git a/graphix/sim/data.py b/graphix/sim/data.py index 4d87e67c0..d77f8de88 100644 --- a/graphix/sim/data.py +++ b/graphix/sim/data.py @@ -14,13 +14,13 @@ from graphix.parameter import ExpressionOrSupportsComplex from graphix.sim.density_matrix import DensityMatrix -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector from graphix.states import State Data: TypeAlias = ( State | DensityMatrix - | Statevec + | Statevector | Iterable[State] | Iterable[ExpressionOrSupportsComplex] | Iterable[Iterable[ExpressionOrSupportsComplex]] diff --git a/graphix/sim/density_matrix.py b/graphix/sim/density_matrix.py index fe243c65b..1e64bbb25 100644 --- a/graphix/sim/density_matrix.py +++ b/graphix/sim/density_matrix.py @@ -21,7 +21,7 @@ from graphix.parameter import Expression, ExpressionOrFloat, ExpressionOrSupportsComplex from graphix.pretty_print import OutputFormat, density_matrix_to_str 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, _check_permutation +from graphix.sim.statevec import CNOT_TENSOR, CZ_TENSOR, SWAP_TENSOR, Statevector, _check_permutation from graphix.states import BasicStates, State if TYPE_CHECKING: @@ -45,7 +45,7 @@ def __init__( ) -> None: """Initialize density matrix objects. - The behaviour builds on the one of *graphix.statevec.Statevec*. + The behaviour builds on the one of *graphix.statevec.Statevector*. `data` can be: - a single :class:`graphix.states.State` (classical description of a quantum state) - an iterable of :class:`graphix.states.State` objects @@ -57,7 +57,7 @@ def __init__( If only one :class:`graphix.states.State` is provided and nqubit is a valid integer, initialize the statevector in the tensor product state. If both `nqubit` and `data` are provided, consistency of the dimensions is checked. - If a *graphix.statevec.Statevec* or *graphix.statevec.DensityMatrix* is passed, returns a copy. + If a *graphix.statevec.Statevector* or *graphix.statevec.DensityMatrix* is passed, returns a copy. :param data: input data to prepare the state. Can be a classical description or a numerical input, defaults to graphix.states.BasicStates.PLUS @@ -101,7 +101,7 @@ def cast_row( if not lv.is_psd(self.rho): raise ValueError("Density matrix must be positive semi-definite.") return - statevec = Statevec(data, nqubit) + statevec = Statevector(data, nqubit) # NOTE this works since np.outer flattens the inputs! self.rho = outer(statevec.psi, statevec.psi.conj()) @@ -389,7 +389,7 @@ def ptrace(self, qargs: Collection[int] | int) -> None: self.rho = rho_res.reshape((2**nqubit_after, 2**nqubit_after)) - def fidelity(self, statevec: Statevec) -> ExpressionOrFloat: + def fidelity(self, statevec: Statevector) -> ExpressionOrFloat: """Calculate the fidelity against reference statevector. Parameters diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index cb45e1eb5..3e78ea290 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -45,7 +45,7 @@ ) -class Statevec(DenseState): +class Statevector(DenseState): """Statevector object.""" psi: Matrix @@ -61,13 +61,13 @@ def __init__( - a single :class:`graphix.states.State` (classical description of a quantum state) - an iterable of :class:`graphix.states.State` objects - an iterable of scalars (A 2**n numerical statevector) - - a *graphix.statevec.Statevec* object + - a *graphix.statevec.Statevector* object If *nqubit* is not provided, the number of qubit is inferred from *data* and checked for consistency. If only one :class:`graphix.states.State` is provided and nqubit is a valid integer, initialize the statevector in the tensor product state. If both *nqubit* and *data* are provided, consistency of the dimensions is checked. - If a *graphix.statevec.Statevec* is passed, returns a copy. + If a *graphix.statevec.Statevector* is passed, returns a copy. Parameters ---------- @@ -79,7 +79,7 @@ def __init__( if nqubit is not None and nqubit < 0: raise ValueError("nqubit must be a non-negative integer.") - if isinstance(data, Statevec): + if isinstance(data, Statevector): # assert nqubit is None or len(state.flatten()) == 2**nqubit if nqubit is not None and len(data.flatten()) != 2**nqubit: raise ValueError( @@ -145,7 +145,7 @@ def state_to_statevector( def __str__(self) -> str: """Return a string description.""" - return f"Statevec object with statevector {self.psi} and length {self.dims()}." + return f"Statevector object with statevector {self.psi} and length {self.dims()}." @override def add_nodes(self, nqubit: int, data: Data) -> None: @@ -166,13 +166,13 @@ 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``. + - The type of nodes to be added is inferred from the type of the existing ``Statevector``. Notes ----- Previously existing nodes remain unchanged. """ - sv_to_add = Statevec(nqubit=nqubit, data=data) + sv_to_add = Statevector(nqubit=nqubit, data=data) self.tensor(sv_to_add) @override @@ -289,14 +289,14 @@ def entangle(self, edge: tuple[int, int]) -> None: # sort back axes self.psi = np.moveaxis(psi, (0, 1), edge) - def tensor(self, other: Statevec) -> None: + def tensor(self, other: Statevector) -> None: r"""Tensor product state with other qubits. Results in self :math:`\otimes` other. Parameters ---------- - other : :class:`graphix.sim.statevec.Statevec` + other : :class:`graphix.sim.statevec.Statevector` statevector to be tensored with self """ psi_self = self.psi.flatten() @@ -401,14 +401,14 @@ def expectation_value(self, op: Matrix, qargs: Sequence[int]) -> complex: st1.evolve(op, qargs) return complex(np.dot(st2.psi.flatten().conjugate(), st1.psi.flatten())) - def fidelity(self, other: Statevec) -> float: + def fidelity(self, other: Statevector) -> float: r"""Calculate the fidelity against another statevector. The fidelity is defined as :math:`|\langle\psi_1|\psi_2\rangle|^2`. Parameters ---------- - other : :class:`Statevec` + other : :class:`Statevector` statevector to compare with Returns @@ -419,14 +419,14 @@ def fidelity(self, other: Statevec) -> float: inner = np.dot(self.flatten().conjugate(), other.flatten()) return float(np.abs(inner) ** 2) - def isclose(self, other: Statevec, *, rtol: float = 1e-09, atol: float = 0.0) -> bool: + def isclose(self, other: Statevector, *, rtol: float = 1e-09, atol: float = 0.0) -> bool: """Check if two quantum states are equal up to global phase. Two states are considered close if their fidelity is close to 1. Parameters ---------- - other : :class:`Statevec` + other : :class:`Statevector` statevector to compare with rtol : float relative tolerance for :func:`math.isclose` @@ -486,8 +486,8 @@ def to_dict( Example ------- >>> from graphix.states import BasicStates - >>> from graphix.sim.statevec import Statevec - >>> sv = Statevec(data=[BasicStates.ZERO, BasicStates.ONE]) + >>> from graphix.sim.statevec import Statevector + >>> sv = Statevector(data=[BasicStates.ZERO, BasicStates.ONE]) >>> sv.to_dict() {'01': np.complex128(1+0j)} >>> sv.to_dict(encoding="LSB") @@ -601,24 +601,24 @@ def _to_dict_map( return {_format_encoding(self.nqubit, i, encoding): amp for i, amp in zip(i_vals, amp_vals, strict=True)} - def subs(self, variable: Parameter, substitute: ExpressionOrSupportsFloat) -> Statevec: + def subs(self, variable: Parameter, substitute: ExpressionOrSupportsFloat) -> Statevector: """Return a copy of the state vector where all occurrences of the given variable in measurement angles are substituted by the given value.""" - result = Statevec() + result = Statevector() result.psi = np.vectorize(lambda value: parameter.subs(value, variable, substitute))(self.psi) return result - def xreplace(self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) -> Statevec: + def xreplace(self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat]) -> Statevector: """Return a copy of the state vector where all occurrences of the given keys in measurement angles are substituted by the given values in parallel.""" - result = Statevec() + result = Statevector() result.psi = np.vectorize(lambda value: parameter.xreplace(value, assignment))(self.psi) return result @dataclass(frozen=True) -class StatevectorBackend(DenseStateBackend[Statevec]): +class StatevectorBackend(DenseStateBackend[Statevector]): """MBQC simulator with statevector method.""" - state: Statevec = dataclasses.field(init=False, default_factory=lambda: Statevec(nqubit=0)) + state: Statevector = dataclasses.field(init=False, default_factory=lambda: Statevector(nqubit=0)) def _norm_symbolic(psi: npt.NDArray[np.object_]) -> ExpressionOrFloat: @@ -643,7 +643,7 @@ def _norm(psi: Matrix) -> ExpressionOrFloat: def _format_encoding(nqubit: int, i: int, encoding: _ENCODING) -> str: - """Format the i-th basis vector as a ket. See :meth:`Statevec.to_dict` for additional details.""" + """Format the i-th basis vector as a ket. See :meth:`Statevector.to_dict` for additional details.""" display_width = nqubit output = f"{i:0{display_width}b}" if encoding == "LSB": diff --git a/graphix/simulator.py b/graphix/simulator.py index 50410d1e2..ddadb10ac 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -36,7 +36,7 @@ from graphix.measurements import Measurement, Outcome from graphix.noise_models.noise_model import CommandOrNoise, NoiseModel from graphix.pattern import Pattern - from graphix.sim import Data, DensityMatrix, MBQCTensorNet, Statevec + from graphix.sim import Data, DensityMatrix, MBQCTensorNet, Statevector logger = logging.getLogger(__name__) @@ -44,7 +44,7 @@ _BackendLiteral = Literal["statevector", "densitymatrix", "tensornetwork", "mps"] if TYPE_CHECKING: - _BuiltinBackendState = DensityMatrix | MBQCTensorNet | Statevec + _BuiltinBackendState = DensityMatrix | MBQCTensorNet | Statevector _StateT = TypeVar("_StateT") @@ -264,7 +264,7 @@ class PatternSimulator(Generic[_StateT_co]): @overload def __init__( - self: PatternSimulator[Statevec], + self: PatternSimulator[Statevector], pattern: Pattern, backend: Literal["statevector"] = ..., prepare_method: PrepareMethod | None = None, diff --git a/graphix/states.py b/graphix/states.py index f8404aaa0..380b418ac 100644 --- a/graphix/states.py +++ b/graphix/states.py @@ -39,7 +39,7 @@ class PlanarState(State): """Light object used to instantiate backends. doesn't cover all possible states but this is - covered in :class:`graphix.sim.statevec.Statevec` + covered in :class:`graphix.sim.statevec.Statevector` and :class:`graphix.sim.densitymatrix.DensityMatrix` constructors. diff --git a/graphix/transpiler.py b/graphix/transpiler.py index d1fbae532..f10b80e3e 100644 --- a/graphix/transpiler.py +++ b/graphix/transpiler.py @@ -29,7 +29,7 @@ from graphix.pattern import Pattern from graphix.sim.base_backend import DenseStateBackend from graphix.sim.density_matrix import DensityMatrixBackend -from graphix.sim.statevec import Statevec, StatevectorBackend +from graphix.sim.statevec import Statevector, StatevectorBackend if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence @@ -533,7 +533,7 @@ def simulate_statevector( rng: Generator | None = None, *, stacklevel: int = 1, - ) -> SimulateResult[Statevec]: ... + ) -> SimulateResult[Statevector]: ... @overload def simulate_statevector( @@ -565,8 +565,8 @@ def simulate_statevector( rng: Generator | None = None, *, stacklevel: int = 1, - ) -> SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevec | DensityMatrix]: - # `SimulateResult` is not covariant in `_DenseStateT` so `SimulateResult[_DenseStateT]` is not a subtype of `SimulateResult[_DenseStateT | Statevec | DensityMatrix]` + ) -> SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevector | DensityMatrix]: + # `SimulateResult` is not covariant in `_DenseStateT` so `SimulateResult[_DenseStateT]` is not a subtype of `SimulateResult[_DenseStateT | Statevector | DensityMatrix]` r"""Simulate the gate sequence with a backend and input state of choice. By default, this method uses the statevector backend and initializes the register to :math:`|+\rangle^{\otimes n}`. diff --git a/tests/test_density_matrix.py b/tests/test_density_matrix.py index 3ab168861..9bef20d7a 100644 --- a/tests/test_density_matrix.py +++ b/tests/test_density_matrix.py @@ -24,7 +24,7 @@ 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.sim.statevec import CNOT_TENSOR, CZ_TENSOR, SWAP_TENSOR, Statevector from graphix.simulator import DefaultMeasureMethod from graphix.states import BasicStates, PlanarState from graphix.transpiler import Circuit @@ -107,15 +107,15 @@ def test_init_with_data_success(self, fx_rng: Generator) -> None: _data = randobj.rand_herm(2**n, fx_rng) def test_init_with_state_sucess(self, fx_rng: Generator) -> None: - # both "numerical" statevec and Statevec object - # relies on Statevec constructor validation + # both "numerical" statevec and Statevector object + # relies on Statevector constructor validation nqb = int(fx_rng.integers(2, 5)) print(f"nqb is {nqb}") rand_angles = fx_rng.random(nqb) * 2 * ANGLE_PI rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] - vec = Statevec(data=states) + vec = Statevector(data=states) # flattens input! expected_dm = np.outer( vec.psi.astype(np.complex128, copy=False), vec.psi.conj().astype(np.complex128, copy=False) @@ -139,20 +139,20 @@ def test_init_with_state_fail(self, fx_rng: Generator) -> None: _dm = DensityMatrix(nqubit=3, data=states) def test_init_with_statevec_sucess(self, fx_rng: Generator) -> None: - # both "numerical" statevec and Statevec object - # relies on Statevec constructor validation + # both "numerical" statevec and Statevector object + # relies on Statevector constructor validation nqb = int(fx_rng.integers(2, 5)) rand_angles = fx_rng.random(nqb) * 2 * ANGLE_PI rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] - vec = Statevec(data=states) + vec = Statevector(data=states) # flattens input! expected_dm = np.outer( vec.psi.astype(np.complex128, copy=False), vec.psi.conj().astype(np.complex128, copy=False) ) - # input with a Statevec object + # input with a Statevector object dm = DensityMatrix(data=vec) assert dm.dims() == (2**nqb, 2**nqb) assert np.allclose(dm.rho, expected_dm) @@ -160,7 +160,7 @@ def test_init_with_statevec_sucess(self, fx_rng: Generator) -> None: sv_list = [state.to_statevector() for state in states] sv = functools.reduce(np.kron, sv_list) # type: ignore[arg-type] - # input with a statevector DATA (not Statevec object) + # input with a statevector DATA (not Statevector object) dm2 = DensityMatrix(data=sv) print("dims", dm.dims()) @@ -176,7 +176,7 @@ def test_init_with_densitymatrix_sucess(self, fx_rng: Generator) -> None: rand_planes = fx_rng.choice(np.array(Plane), nqb) print("planes", rand_planes) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] - vec = Statevec(data=states) + vec = Statevector(data=states) expected_dm = np.outer( vec.psi.astype(np.complex128, copy=False), vec.psi.conj().astype(np.complex128, copy=False) ) @@ -212,7 +212,7 @@ def test_evolve_single_success(self, fx_rng: Generator) -> None: n = 10 for i in range(n): - sv = Statevec(nqubit=n) + sv = Statevector(nqubit=n) sv.evolve_single(op, i) expected_density_matrix = np.outer( sv.psi.astype(np.complex128, copy=False), sv.psi.conj().astype(np.complex128, copy=False) diff --git a/tests/test_graphsim.py b/tests/test_graphsim.py index 180c38929..75bfbe77f 100644 --- a/tests/test_graphsim.py +++ b/tests/test_graphsim.py @@ -10,16 +10,16 @@ from graphix.fundamentals import ANGLE_PI, Plane, angle_to_rad from graphix.graphsim import GraphState from graphix.ops import Ops -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector if TYPE_CHECKING: from graphix.fundamentals import Angle -def graph_state_to_statevec(g: GraphState) -> Statevec: +def graph_state_to_statevec(g: GraphState) -> Statevector: node_list = list(g.nodes) nqubit = len(g.nodes) - gstate = Statevec(nqubit=nqubit) + gstate = Statevector(nqubit=nqubit) imapping = {node_list[i]: i for i in range(nqubit)} mapping = [node_list[i] for i in range(nqubit)] for i, j in g.edges: diff --git a/tests/test_parameter.py b/tests/test_parameter.py index 85d0e7ecf..a7e9ca73f 100644 --- a/tests/test_parameter.py +++ b/tests/test_parameter.py @@ -12,7 +12,7 @@ from graphix.pattern import DrawPatternAnnotations, Pattern from graphix.random_objects import rand_circuit from graphix.sim.density_matrix import DensityMatrix -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector if TYPE_CHECKING: from numpy.random import PCG64, Generator @@ -134,14 +134,14 @@ def test_parallel_substitution_with_zero() -> None: def test_statevec_subs() -> None: alpha = Placeholder("alpha") - statevec = Statevec([alpha]) + statevec = Statevector([alpha]) assert np.allclose(statevec.subs(alpha, 1).psi, np.array([1])) def test_statevec_xreplace() -> None: alpha = Placeholder("alpha") beta = Placeholder("beta") - statevec = Statevec([alpha, beta]) + statevec = Statevector([alpha, beta]) assert np.allclose(statevec.xreplace({alpha: 1, beta: 2}).psi, np.array([1, 2])) diff --git a/tests/test_pattern.py b/tests/test_pattern.py index d726fd9d7..b8cb40606 100644 --- a/tests/test_pattern.py +++ b/tests/test_pattern.py @@ -23,7 +23,7 @@ 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 -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector from graphix.sim.tensornet import MBQCTensorNet from graphix.simulator import PatternSimulator from graphix.states import PlanarState @@ -36,8 +36,8 @@ from graphix.simulator import _BackendLiteral -def compare_backend_result_with_statevec(backend_state: Statevec | DensityMatrix, statevec: Statevec) -> float: - if isinstance(backend_state, Statevec): +def compare_backend_result_with_statevec(backend_state: Statevector | DensityMatrix, statevec: Statevector) -> float: + if isinstance(backend_state, Statevector): return float(backend_state.fidelity(statevec)) if isinstance(backend_state, DensityMatrix): return float(np.abs(np.dot(backend_state.rho.flatten().conjugate(), DensityMatrix(statevec).rho.flatten()))) @@ -172,7 +172,7 @@ def simulate_and_measure() -> int: sim = PatternSimulator(pattern, backend_type) sim.run(rng=fx_rng) state = sim.backend.state - if isinstance(state, Statevec): + if isinstance(state, Statevector): assert state.dims() == () elif isinstance(state, DensityMatrix): assert state.dims() == (1, 1) @@ -239,7 +239,7 @@ def test_pauli_measurement_random_circuit( pattern.remove_pauli_measurements() pattern.minimize_space() state = circuit.simulate_statevector().statevec - state_mbqc: Statevec | DensityMatrix = pattern.simulate_pattern(backend, rng=rng) + state_mbqc: Statevector | DensityMatrix = pattern.simulate_pattern(backend, rng=rng) assert compare_backend_result_with_statevec(state_mbqc, state) == pytest.approx(1) @pytest.mark.parametrize("jumps", range(1, 11)) @@ -1393,7 +1393,7 @@ def test_arbitrary_inputs( rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] randpattern = rand_circ.transpile().pattern - out: Statevec | DensityMatrix = randpattern.simulate_pattern(backend=backend, input_state=states, rng=fx_rng) + out: Statevector | DensityMatrix = randpattern.simulate_pattern(backend=backend, input_state=states, rng=fx_rng) out_circ = rand_circ.simulate_statevector(input_state=states).statevec assert compare_backend_result_with_statevec(out, out_circ) == pytest.approx(1) diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index eb34e98cc..265035370 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -19,7 +19,7 @@ from graphix.pretty_print import OutputFormat, _factor_uniform_magnitude, complex_to_str, pattern_to_str from graphix.random_objects import rand_circuit from graphix.sim.density_matrix import DensityMatrix -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector from graphix.states import BasicStates from graphix.transpiler import Circuit @@ -276,7 +276,7 @@ def test_complex_to_str_values(value: object, expected: Mapping[OutputFormat, st def test_statevec_draw() -> None: - bell = Statevec([2**-0.5, 0, 0, 2**-0.5]) + bell = Statevector([2**-0.5, 0, 0, 2**-0.5]) # A magnitude shared by every amplitude is factored out. assert bell.draw(OutputFormat.Unicode) == "√2/2(|00⟩ + |11⟩)" assert bell.draw(OutputFormat.ASCII) == "sqrt(2)/2(|00> + |11>)" @@ -285,7 +285,7 @@ def test_statevec_draw() -> None: def test_statevec_draw_single_basis_state() -> None: - state = Statevec(data=[BasicStates.ZERO, BasicStates.ONE]) + state = Statevector(data=[BasicStates.ZERO, BasicStates.ONE]) assert state.draw(OutputFormat.Unicode) == "|01⟩" # LaTeX ket notation for a bare basis state. assert state.draw(OutputFormat.LaTeX) == r"\ket{01}" @@ -301,15 +301,15 @@ def test_density_matrix_draw() -> None: def test_statevec_draw_negative_and_parenthesized() -> None: # The shared 1/2 magnitude is factored out, with the signs kept inside the parentheses. - neg = Statevec([0.5, -0.5, 0.5, 0.5]) + neg = Statevector([0.5, -0.5, 0.5, 0.5]) assert neg.draw(OutputFormat.Unicode) == "1/2(|00⟩ - |01⟩ + |10⟩ + |11⟩)" # A compound (cartesian) amplitude is parenthesized before the ket. Build from a # numpy array so the amplitudes are ``numpy.complex128`` (Python's ``complex`` only # gained ``__complex__`` in 3.11, so a bare ``complex`` is rejected on 3.10). - binomial = Statevec(np.array([0.5 + 0.25j, (1 - abs(0.5 + 0.25j) ** 2) ** 0.5])) + binomial = Statevector(np.array([0.5 + 0.25j, (1 - abs(0.5 + 0.25j) ** 2) ** 0.5])) assert binomial.draw(OutputFormat.Unicode) == "(1/2 + 1/4i)|0⟩ + √11/4|1⟩" # A unit negative amplitude collapses to a bare `-|ket⟩`. - assert Statevec([-1.0, 0.0]).draw(OutputFormat.Unicode) == "-|0⟩" + assert Statevector([-1.0, 0.0]).draw(OutputFormat.Unicode) == "-|0⟩" def test_complex_to_str_precision_is_configurable() -> None: @@ -338,11 +338,12 @@ def test_density_matrix_draw_rtol() -> None: def test_statevec_draw_factor_relative_phase() -> None: # A modulus shared up to a relative phase is still factored, with the phase kept # inside the parentheses. - assert Statevec(data=BasicStates.PLUS).draw(OutputFormat.Unicode) == "√2/2(|0⟩ + |1⟩)" - assert Statevec(data=BasicStates.PLUS_I).draw(OutputFormat.Unicode) == "√2/2(|0⟩ + i|1⟩)" - assert Statevec(data=BasicStates.MINUS_I).draw(OutputFormat.Unicode) == "√2/2(|0⟩ - i|1⟩)" + assert Statevector(data=BasicStates.PLUS).draw(OutputFormat.Unicode) == "√2/2(|0⟩ + |1⟩)" + assert Statevector(data=BasicStates.PLUS_I).draw(OutputFormat.Unicode) == "√2/2(|0⟩ + i|1⟩)" + assert Statevector(data=BasicStates.MINUS_I).draw(OutputFormat.Unicode) == "√2/2(|0⟩ - i|1⟩)" assert ( - Statevec(data=BasicStates.PLUS_I).draw(OutputFormat.LaTeX) == r"\frac{\sqrt{2}}{2}(\ket{0} + \mathrm{i}\ket{1})" + Statevector(data=BasicStates.PLUS_I).draw(OutputFormat.LaTeX) + == r"\frac{\sqrt{2}}{2}(\ket{0} + \mathrm{i}\ket{1})" ) @@ -394,7 +395,7 @@ def test_draw_max_denominator() -> None: assert dm.draw(OutputFormat.Unicode, max_denominator=1) == "[ 0.5 0.5 ]\n[ 0.5 0.5 ]" # Same effect on the statevector draw: √2/2 needs max_denominator >= 2 for the # squared form 1/2 to be representable; below that it falls back to 0.7071. - sv = Statevec([2**-0.5, 2**-0.5]) + sv = Statevector([2**-0.5, 2**-0.5]) assert sv.draw(OutputFormat.Unicode) == "√2/2(|0⟩ + |1⟩)" assert sv.draw(OutputFormat.Unicode, max_denominator=2) == "√2/2(|0⟩ + |1⟩)" assert sv.draw(OutputFormat.Unicode, max_denominator=1) == "0.7071(|0⟩ + |1⟩)" @@ -413,7 +414,7 @@ def test_complex_to_str_edge_cases() -> None: def test_statevec_draw_all_amplitudes_dropped() -> None: # A tolerance large enough to drop every amplitude prints the statevector as "0". - bell = Statevec([2**-0.5, 0, 0, 2**-0.5]) + bell = Statevector([2**-0.5, 0, 0, 2**-0.5]) assert bell.draw(OutputFormat.Unicode, atol=1.0) == "0" @@ -421,5 +422,5 @@ def test_statevec_draw_non_uniform_not_factored() -> None: # Amplitudes with different magnitudes are not factored; each term keeps its coefficient. # The negative second amplitude also exercises the ` - ` separator in the term-by-term # fallback (the factoring path handles the uniform-magnitude negative case separately). - state = Statevec([math.sqrt(3) / 2, -0.5]) + state = Statevector([math.sqrt(3) / 2, -0.5]) assert state.draw(OutputFormat.Unicode) == "√3/2|0⟩ - 1/2|1⟩" diff --git a/tests/test_simulator.py b/tests/test_simulator.py index 6c9ecbb81..2430c1434 100644 --- a/tests/test_simulator.py +++ b/tests/test_simulator.py @@ -4,7 +4,7 @@ import pytest -from graphix import BasicStates, Pattern, Statevec, StatevectorBackend +from graphix import BasicStates, Pattern, Statevector, StatevectorBackend if TYPE_CHECKING: from numpy.random import Generator @@ -14,14 +14,14 @@ def test_no_explicit_input_state(hadamardpattern: Pattern, fx_rng: Generator) -> # 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)) + assert state.isclose(Statevector(BasicStates.ZERO)) 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)) + assert state.isclose(Statevector(BasicStates.PLUS)) def test_backend_prepared_zero(hadamardpattern: Pattern, fx_rng: Generator) -> None: @@ -31,7 +31,7 @@ def test_backend_prepared_zero(hadamardpattern: Pattern, fx_rng: Generator) -> N backend = StatevectorBackend() 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)) + assert state.isclose(Statevector(BasicStates.PLUS)) def test_no_prepared_qubits_and_input_state_none(hadamardpattern: Pattern, fx_rng: Generator) -> None: diff --git a/tests/test_statevec.py b/tests/test_statevec.py index a91e56f7c..7aa6d680b 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -12,7 +12,7 @@ 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.sim.statevec import Statevector, _norm_numeric from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: @@ -26,38 +26,38 @@ _ENCODING = Literal["LSB", "MSB"] -class TestStatevec: - """Test for Statevec class. Particularly new constructor.""" +class TestStatevector: + """Test for Statevector class. Particularly new constructor.""" # test initializing one qubit in plus state def test_default_success(self) -> None: - vec = Statevec(nqubit=1) + vec = Statevector(nqubit=1) assert np.allclose(vec.psi, np.array([1, 1] / np.sqrt(2))) assert len(vec.dims()) == 1 def test_basicstates_success(self) -> None: # minus - vec = Statevec(nqubit=1, data=BasicStates.MINUS) + vec = Statevector(nqubit=1, data=BasicStates.MINUS) assert np.allclose(vec.psi, np.array([1, -1] / np.sqrt(2))) assert len(vec.dims()) == 1 # zero - vec = Statevec(nqubit=1, data=BasicStates.ZERO) + vec = Statevector(nqubit=1, data=BasicStates.ZERO) assert np.allclose(vec.psi, np.array([1, 0]), rtol=0, atol=1e-15) assert len(vec.dims()) == 1 # one - vec = Statevec(nqubit=1, data=BasicStates.ONE) + vec = Statevector(nqubit=1, data=BasicStates.ONE) assert np.allclose(vec.psi, np.array([0, 1]), rtol=0, atol=1e-15) assert len(vec.dims()) == 1 # plus_i - vec = Statevec(nqubit=1, data=BasicStates.PLUS_I) + vec = Statevector(nqubit=1, data=BasicStates.PLUS_I) assert np.allclose(vec.psi, np.array([1, 1j] / np.sqrt(2))) assert len(vec.dims()) == 1 # minus_i - vec = Statevec(nqubit=1, data=BasicStates.MINUS_I) + vec = Statevector(nqubit=1, data=BasicStates.MINUS_I) assert np.allclose(vec.psi, np.array([1, -1j] / np.sqrt(2))) assert len(vec.dims()) == 1 @@ -65,11 +65,11 @@ def test_basicstates_success(self) -> None: def test_default_tensor_success(self, fx_rng: Generator) -> None: nqb = int(fx_rng.integers(2, 5)) print(f"nqb is {nqb}") - vec = Statevec(nqubit=nqb) + vec = Statevector(nqubit=nqb) assert np.allclose(vec.psi, np.ones((2,) * nqb) / (np.sqrt(2)) ** nqb) assert len(vec.dims()) == nqb - vec = Statevec(nqubit=nqb, data=BasicStates.MINUS_I) + vec = Statevector(nqubit=nqb, data=BasicStates.MINUS_I) sv_list = [BasicStates.MINUS_I.to_statevector() for _ in range(nqb)] sv = functools.reduce(lambda a, b: np.kron(a, b).astype(np.complex128, copy=False), sv_list) assert np.allclose(vec.psi, sv.reshape((2,) * nqb)) @@ -79,7 +79,7 @@ def test_default_tensor_success(self, fx_rng: Generator) -> None: rand_angle = fx_rng.random() * 2 * ANGLE_PI rand_plane = fx_rng.choice(np.array(Plane)) state = PlanarState(rand_plane, rand_angle) - vec = Statevec(nqubit=nqb, data=state) + vec = Statevector(nqubit=nqb, data=state) sv_list = [state.to_statevector() for _ in range(nqb)] sv = functools.reduce(lambda a, b: np.kron(a, b).astype(np.complex128, copy=False), sv_list) assert np.allclose(vec.psi, sv.reshape((2,) * nqb)) @@ -89,7 +89,7 @@ def test_default_tensor_success(self, fx_rng: Generator) -> None: rand_angles = fx_rng.random(nqb) * 2 * ANGLE_PI rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] - vec = Statevec(nqubit=nqb, data=states) + vec = Statevector(nqubit=nqb, data=states) sv_list = [state.to_statevector() for state in states] sv = functools.reduce(lambda a, b: np.kron(a, b).astype(np.complex128, copy=False), sv_list) assert np.allclose(vec.psi, sv.reshape((2,) * nqb)) @@ -100,7 +100,7 @@ def test_data_success(self, fx_rng: Generator) -> None: length = 2**nqb rand_vec = fx_rng.random(length) + 1j * fx_rng.random(length) rand_vec /= np.sqrt(np.sum(np.abs(rand_vec) ** 2)) - vec = Statevec(data=rand_vec) + vec = Statevector(data=rand_vec) assert np.allclose(vec.psi, rand_vec.reshape((2,) * nqb)) assert len(vec.dims()) == nqb @@ -110,7 +110,7 @@ def test_data_dim_fail(self, fx_rng: Generator) -> None: rand_vec = fx_rng.random(length) + 1j * fx_rng.random(length) rand_vec /= np.sqrt(np.sum(np.abs(rand_vec) ** 2)) with pytest.raises(ValueError): - _vec = Statevec(data=rand_vec) + _vec = Statevector(data=rand_vec) # fail: with less qubit than number of qubits inferred from a correct state vect def test_data_dim_fail_mismatch(self, fx_rng: Generator) -> None: @@ -118,7 +118,7 @@ def test_data_dim_fail_mismatch(self, fx_rng: Generator) -> None: rand_vec = fx_rng.random(2**nqb) + 1j * fx_rng.random(2**nqb) rand_vec /= np.sqrt(np.sum(np.abs(rand_vec) ** 2)) with pytest.raises(ValueError): - _vec = Statevec(nqubit=2, data=rand_vec) + _vec = Statevector(nqubit=2, data=rand_vec) # fail: not normalized def test_data_norm_fail(self, fx_rng: Generator) -> None: @@ -126,21 +126,21 @@ def test_data_norm_fail(self, fx_rng: Generator) -> None: length = 2**nqb rand_vec = fx_rng.random(length) + 1j * fx_rng.random(length) with pytest.raises(ValueError): - _vec = Statevec(data=rand_vec) + _vec = Statevector(data=rand_vec) def test_defaults_to_one(self) -> None: - vec = Statevec() + vec = Statevector() assert len(vec.dims()) == 1 - # try copying Statevec input + # try copying Statevector input def test_copy_success(self, fx_rng: Generator) -> None: nqb = fx_rng.integers(2, 5) length = 2**nqb rand_vec = fx_rng.random(length) + 1j * fx_rng.random(length) rand_vec /= np.sqrt(np.sum(np.abs(rand_vec) ** 2)) - test_vec = Statevec(data=rand_vec) + test_vec = Statevector(data=rand_vec) # try to copy it - vec = Statevec(data=test_vec) + vec = Statevector(data=test_vec) assert np.allclose(vec.psi, test_vec.psi) assert len(vec.dims()) == len(test_vec.dims()) @@ -151,14 +151,14 @@ def test_copy_fail(self, fx_rng: Generator) -> None: length = 1 << nqb rand_vec = fx_rng.random(length) + 1j * fx_rng.random(length) rand_vec /= np.sqrt(np.sum(np.abs(rand_vec) ** 2)) - test_vec = Statevec(data=rand_vec) + test_vec = Statevector(data=rand_vec) with pytest.raises(ValueError): - _vec = Statevec(nqubit=length - 1, data=test_vec) + _vec = Statevector(nqubit=length - 1, data=test_vec) def test_nqubits(self) -> None: for i in [1, 2, 5]: - sv = Statevec(nqubit=i) + sv = Statevector(nqubit=i) assert sv.nqubit == i def test_nqubits_pattern(self) -> None: @@ -169,23 +169,23 @@ def test_nqubits_pattern(self) -> None: class TestFidelityIsclose: def test_fidelity_same_state(self) -> None: - state = Statevec(data=BasicStates.PLUS) + state = Statevector(data=BasicStates.PLUS) assert state.fidelity(state) == pytest.approx(1) def test_fidelity_orthogonal(self) -> None: - zero = Statevec(data=BasicStates.ZERO) - one = Statevec(data=BasicStates.ONE) + zero = Statevector(data=BasicStates.ZERO) + one = Statevector(data=BasicStates.ONE) assert zero.fidelity(one) == pytest.approx(0) def test_fidelity_known_value(self) -> None: # F(|0>, |+>) = 0.5 - zero = Statevec(data=BasicStates.ZERO) - plus = Statevec(data=BasicStates.PLUS) + zero = Statevector(data=BasicStates.ZERO) + plus = Statevector(data=BasicStates.PLUS) assert zero.fidelity(plus) == pytest.approx(0.5) def test_fidelity_global_phase(self) -> None: - plus = Statevec(data=BasicStates.PLUS) - plus_rotated = Statevec(data=np.array([1, 1]) / np.sqrt(2) * 1j) + plus = Statevector(data=BasicStates.PLUS) + plus_rotated = Statevector(data=np.array([1, 1]) / np.sqrt(2) * 1j) assert plus.fidelity(plus_rotated) == pytest.approx(1) def test_fidelity_symmetry(self, fx_rng: Generator) -> None: @@ -194,27 +194,27 @@ def test_fidelity_symmetry(self, fx_rng: Generator) -> None: vec_a /= np.sqrt(np.sum(np.abs(vec_a) ** 2)) vec_b = fx_rng.random(length) + 1j * fx_rng.random(length) vec_b /= np.sqrt(np.sum(np.abs(vec_b) ** 2)) - a = Statevec(data=vec_a) - b = Statevec(data=vec_b) + a = Statevector(data=vec_a) + b = Statevector(data=vec_b) assert a.fidelity(b) == pytest.approx(b.fidelity(a)) def test_isclose_same_state(self) -> None: - state = Statevec(data=BasicStates.PLUS) + state = Statevector(data=BasicStates.PLUS) assert state.isclose(state) def test_isclose_orthogonal(self) -> None: - zero = Statevec(data=BasicStates.ZERO) - one = Statevec(data=BasicStates.ONE) + zero = Statevector(data=BasicStates.ZERO) + one = Statevector(data=BasicStates.ONE) assert not zero.isclose(one) def test_isclose_global_phase(self) -> None: - plus = Statevec(data=BasicStates.PLUS) - rotated = Statevec(data=np.array([1, 1]) / np.sqrt(2) * np.exp(1j * 0.7)) + plus = Statevector(data=BasicStates.PLUS) + rotated = Statevector(data=np.array([1, 1]) / np.sqrt(2) * np.exp(1j * 0.7)) assert plus.isclose(rotated) def test_isclose_tolerance(self) -> None: - zero = Statevec(data=BasicStates.ZERO) - almost = Statevec(data=np.array([np.sqrt(1 - 1e-8), np.sqrt(1e-8)])) + zero = Statevector(data=BasicStates.ZERO) + almost = Statevector(data=np.array([np.sqrt(1 - 1e-8), np.sqrt(1e-8)])) assert not zero.isclose(almost) assert zero.isclose(almost, atol=1e-6) @@ -226,7 +226,7 @@ def test_isclose_tolerance(self) -> None: ], ) def test_to_dict(self, encoding: _ENCODING, dict_ref: Mapping[str, float]) -> None: - sv = Statevec(data=[BasicStates.ZERO, BasicStates.PLUS, BasicStates.MINUS]) + sv = Statevector(data=[BasicStates.ZERO, BasicStates.PLUS, BasicStates.MINUS]) for ket, amp in sv.to_dict(encoding=encoding).items(): assert np.isclose(dict_ref[ket], amp.real) assert np.isclose(0, amp.imag) @@ -239,14 +239,14 @@ def test_to_dict(self, encoding: _ENCODING, dict_ref: Mapping[str, float]) -> No ], ) def test_to_prob_dict(self, encoding: _ENCODING, dict_ref: Mapping[str, float]) -> None: - sv = Statevec(data=[BasicStates.ONE, BasicStates.PLUS, BasicStates.MINUS]) + sv = Statevector(data=[BasicStates.ONE, BasicStates.PLUS, BasicStates.MINUS]) for ket, amp2 in sv.to_prob_dict(encoding=encoding).items(): assert np.isclose(dict_ref[ket], amp2.real) assert np.isclose(0, amp2.imag) def test_normalize() -> None: - statevec = Statevec(nqubit=1, data=BasicStates.PLUS) + statevec = Statevector(nqubit=1, data=BasicStates.PLUS) statevec.remove_qubit(0) assert _norm_numeric(statevec.psi.astype(np.complex128, copy=False)) == 1 @@ -265,7 +265,7 @@ def permute_with_swap(dense_state: DenseState, permutation: Sequence[int]) -> No @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 = Statevector(rand_state_vector(nqubits, fx_rng)) statevec_ref = copy.copy(statevec) statevec.permute(permutation) permute_with_swap(statevec_ref, permutation) @@ -273,7 +273,7 @@ def test_permute(fx_rng: Generator, permutation: Sequence[int]) -> None: def test_permute_bad_permutation() -> None: - statevec = Statevec(nqubit=2) + statevec = Statevector(nqubit=2) with pytest.raises(ValueError, match="Permutation has length"): statevec.permute([0]) with pytest.raises(ValueError, match="not a permutation"): diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index acab2c527..ac0b56dc9 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -9,7 +9,7 @@ from graphix.fundamentals import ANGLE_PI, Plane from graphix.measurements import Measurement from graphix.pauli import Pauli -from graphix.sim.statevec import Statevec, StatevectorBackend +from graphix.sim.statevec import Statevector, StatevectorBackend from graphix.states import BasicStates, PlanarState if TYPE_CHECKING: @@ -18,7 +18,7 @@ from graphix import Pattern -class TestStatevec: +class TestStatevector: @pytest.mark.parametrize( "state", [BasicStates.PLUS, BasicStates.ZERO, BasicStates.ONE, BasicStates.PLUS_I, BasicStates.MINUS_I] ) @@ -28,37 +28,37 @@ def test_measurement_into_each_xyz_basis(self, state: PlanarState) -> None: # for measurement into |-> returns [[0, 0], ..., [0, 0]] (whose norm is zero) statevector = state.to_statevector() m_op = np.outer(statevector, statevector.T.conjugate()) - sv = Statevec(nqubit=n) + sv = Statevector(nqubit=n) sv.evolve(m_op.astype(np.complex128, copy=False), [k]) sv.remove_qubit(k) - sv2 = Statevec(nqubit=n - 1) + sv2 = Statevector(nqubit=n - 1) assert sv.isclose(sv2) def test_measurement_into_minus_state(self) -> None: n = 3 k = 0 m_op = np.outer(BasicStates.MINUS.to_statevector(), BasicStates.MINUS.to_statevector().T.conjugate()) - sv = Statevec(nqubit=n) + sv = Statevector(nqubit=n) sv.evolve(m_op.astype(np.complex128, copy=False), [k]) with pytest.raises(AssertionError): sv.remove_qubit(k) -class TestStatevecNew: +class TestStatevectorNew: # test initialization only def test_init_success(self, hadamardpattern: Pattern, fx_rng: Generator) -> None: # plus state (default) backend = StatevectorBackend() backend.add_nodes(hadamardpattern.input_nodes) - vec = Statevec(nqubit=1) + vec = Statevector(nqubit=1) assert np.allclose(vec.psi, backend.state.psi) assert len(backend.state.dims()) == 1 # minus state backend = StatevectorBackend() backend.add_nodes(hadamardpattern.input_nodes, data=BasicStates.MINUS) - vec = Statevec(nqubit=1, data=BasicStates.MINUS) + vec = Statevector(nqubit=1, data=BasicStates.MINUS) assert np.allclose(vec.psi, backend.state.psi) assert len(backend.state.dims()) == 1 @@ -68,12 +68,12 @@ def test_init_success(self, hadamardpattern: Pattern, fx_rng: Generator) -> None state = PlanarState(rand_plane, rand_angle) backend = StatevectorBackend() backend.add_nodes(hadamardpattern.input_nodes, data=state) - vec = Statevec(nqubit=1, data=state) + vec = Statevector(nqubit=1, data=state) assert np.allclose(vec.psi, backend.state.psi) # assert backend.state.nqubit == 1 assert len(backend.state.dims()) == 1 - # data input and Statevec input + # data input and Statevector input def test_init_fail(self, hadamardpattern: Pattern, fx_rng: Generator) -> None: rand_angle = fx_rng.random(2) * 2 * ANGLE_PI @@ -87,7 +87,7 @@ def test_init_fail(self, hadamardpattern: Pattern, fx_rng: Generator) -> None: def test_clifford(self) -> None: for clifford in Clifford: state = BasicStates.PLUS - vec = Statevec(nqubit=1, data=state) + vec = Statevector(nqubit=1, data=state) backend = StatevectorBackend() backend.add_nodes(nodes=[0], data=state) # Applies clifford gate "Z" diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index 2bb76a21b..af755ed2f 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -13,7 +13,7 @@ from graphix.fundamentals import ANGLE_PI from graphix.ops import Ops from graphix.random_objects import rand_circuit -from graphix.sim.statevec import Statevec +from graphix.sim.statevec import Statevector from graphix.sim.tensornet import MBQCTensorNet, gen_str from graphix.states import BasicStates from graphix.transpiler import Circuit @@ -389,7 +389,7 @@ def test_to_statevector(self, fx_bg: PCG64, nqubits: int, jumps: int, fx_rng: Ge tn = pattern.simulate_pattern("tensornetwork", rng=fx_rng) statevec_tn = tn.to_statevector() - assert Statevec(data=statevec_tn).isclose(statevec_ref) + assert Statevector(data=statevec_tn).isclose(statevec_ref) @pytest.mark.parametrize("jumps", range(1, 11)) def test_evolve(self, fx_bg: PCG64, jumps: int, fx_rng: Generator) -> None: diff --git a/tests/test_transpiler.py b/tests/test_transpiler.py index d5048eb6d..588338883 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, StatevectorBackend +from graphix.sim.statevec import Statevector, StatevectorBackend from graphix.simulator import DefaultMeasureMethod from graphix.states import BasicStates from graphix.transpiler import Circuit, OutputIndex, OutputKind, decompose_ccx, transpile_swaps @@ -98,7 +98,7 @@ def test_measure( state_mbqc = pattern.simulate_pattern( rng=rng, input_state=input_state, branch_selector=branch_selector, backend=backend ) - if isinstance(state_mbqc, Statevec) and isinstance(state, Statevec): + if isinstance(state_mbqc, Statevector) and isinstance(state, Statevector): assert state_mbqc.isclose(state) elif isinstance(state_mbqc, DensityMatrix) and isinstance(state, DensityMatrix): assert np.allclose(state_mbqc.rho, state.rho) From 141eb2fd87161f5ee94e8103008dbd40dff59633 Mon Sep 17 00:00:00 2001 From: matulni Date: Mon, 13 Jul 2026 13:56:14 +0200 Subject: [PATCH 09/10] Rename Statevec into Statevector --- graphix/sim/statevec.py | 2 +- graphix/sim/tensornet.py | 36 +++++++++++++++++----------------- graphix/states.py | 12 +++++++----- tests/test_density_matrix.py | 4 ++-- tests/test_pauli.py | 2 +- tests/test_statevec.py | 6 +++--- tests/test_statevec_backend.py | 6 ++++-- tests/test_tnsim.py | 2 +- 8 files changed, 37 insertions(+), 33 deletions(-) diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index 3e78ea290..c4401debb 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -119,7 +119,7 @@ def state_to_statevector( ) -> npt.NDArray[np.complex128]: if not isinstance(s, states.State): raise TypeError("Data should be an homogeneous sequence of states.") - return s.to_statevector() + return s.to_statevector_numpy() list_of_sv = [state_to_statevector(s) for s in input_list] diff --git a/graphix/sim/tensornet.py b/graphix/sim/tensornet.py index df893fb77..d505d505e 100644 --- a/graphix/sim/tensornet.py +++ b/graphix/sim/tensornet.py @@ -113,17 +113,17 @@ def add_qubit(self, index: int, state: PrepareState = "plus") -> None: tag = str(index) match state: case "plus": - vec = BasicStates.PLUS.to_statevector() + vec = BasicStates.PLUS.to_statevector_numpy() case "minus": - vec = BasicStates.MINUS.to_statevector() + vec = BasicStates.MINUS.to_statevector_numpy() case "zero": - vec = BasicStates.ZERO.to_statevector() + vec = BasicStates.ZERO.to_statevector_numpy() case "one": - vec = BasicStates.ONE.to_statevector() + vec = BasicStates.ONE.to_statevector_numpy() case "iplus": - vec = BasicStates.PLUS_I.to_statevector() + vec = BasicStates.PLUS_I.to_statevector_numpy() case "iminus": - vec = BasicStates.MINUS_I.to_statevector() + vec = BasicStates.MINUS_I.to_statevector_numpy() case _: if isinstance(state, str): raise TypeError(f"Unknown state: {state}") @@ -239,17 +239,17 @@ def measure_single( raise Warning("Measurement outcome is chosen but the basis state was given.") proj_vec = basis elif basis == "Z" and result == 0: - proj_vec = BasicStates.ZERO.to_statevector() + proj_vec = BasicStates.ZERO.to_statevector_numpy() elif basis == "Z" and result == 1: - proj_vec = BasicStates.ONE.to_statevector() + proj_vec = BasicStates.ONE.to_statevector_numpy() elif basis == "X" and result == 0: - proj_vec = BasicStates.PLUS.to_statevector() + proj_vec = BasicStates.PLUS.to_statevector_numpy() elif basis == "X" and result == 1: - proj_vec = BasicStates.MINUS.to_statevector() + proj_vec = BasicStates.MINUS.to_statevector_numpy() elif basis == "Y" and result == 0: - proj_vec = BasicStates.PLUS_I.to_statevector() + proj_vec = BasicStates.PLUS_I.to_statevector_numpy() elif basis == "Y" and result == 1: - proj_vec = BasicStates.MINUS_I.to_statevector() + proj_vec = BasicStates.MINUS_I.to_statevector_numpy() else: raise ValueError("Invalid measurement basis.") else: @@ -296,16 +296,16 @@ def prepare_graph_state(self, nodes: Iterable[int], edges: Iterable[tuple[int, i if node not in ind_dict: ind = gen_str() self._dangling[str(node)] = ind - self.add_tensor(Tensor(BasicStates.PLUS.to_statevector(), [ind], [str(node), "Open"])) + self.add_tensor(Tensor(BasicStates.PLUS.to_statevector_numpy(), [ind], [str(node), "Open"])) continue dim_tensor = len(vec_dict[node]) tensor = np.array( [ outer_product( - [BasicStates.VEC[0 + 2 * vec_dict[node][i]].to_statevector() for i in range(dim_tensor)] + [BasicStates.VEC[0 + 2 * vec_dict[node][i]].to_statevector_numpy() for i in range(dim_tensor)] ), outer_product( - [BasicStates.VEC[1 + 2 * vec_dict[node][i]].to_statevector() for i in range(dim_tensor)] + [BasicStates.VEC[1 + 2 * vec_dict[node][i]].to_statevector_numpy() for i in range(dim_tensor)] ), ] ) * 2 ** (dim_tensor / 4 - 1.0 / 2) @@ -345,10 +345,10 @@ def basis_coefficient( node = str(indices[i]) exp = len(indices) - i - 1 if (basis // 2**exp) == 1: - state_out = BasicStates.ONE.to_statevector() # project onto |1> + state_out = BasicStates.ONE.to_statevector_numpy() # project onto |1> basis -= 2**exp else: - state_out = BasicStates.ZERO.to_statevector() # project onto |0> + state_out = BasicStates.ZERO.to_statevector_numpy() # project onto |0> tensor = Tensor(state_out, [tn._dangling[node]], [node, f"qubit {i}", "Close"]) # retag old_ind = tn._dangling[node] @@ -773,7 +773,7 @@ def measure( bloch = measurement.to_bloch() if isinstance(bloch.angle, Expression): raise TypeError("Parameterized pattern unsupported.") - vec = PlanarState(bloch.plane, bloch.angle).to_statevector() + vec = PlanarState(bloch.plane, bloch.angle).to_statevector_numpy() if result: vec = Ops.from_axis(bloch.plane.orth) @ vec proj_vec = vec * buffer diff --git a/graphix/states.py b/graphix/states.py index 380b418ac..5c759a0b6 100644 --- a/graphix/states.py +++ b/graphix/states.py @@ -20,18 +20,20 @@ class State(ABC): """Abstract base class for single qubit states objects. Only requirement for concrete classes is to have - a to_statevector() method that returns the statevector + a to_statevector_numpy() method that returns the statevector representation of the state """ @abc.abstractmethod - def to_statevector(self) -> npt.NDArray[np.complex128]: + def to_statevector_numpy(self) -> npt.NDArray[np.complex128]: """Return the state vector.""" - def to_densitymatrix(self) -> npt.NDArray[np.complex128]: + def to_densitymatrix_numpy(self) -> npt.NDArray[np.complex128]: """Return the density matrix.""" # return DM in 2**n x 2**n dim (2x2 here) - return np.outer(self.to_statevector(), self.to_statevector().conj()).astype(np.complex128, copy=False) + return np.outer(self.to_statevector_numpy(), self.to_statevector_numpy().conj()).astype( + np.complex128, copy=False + ) @dataclasses.dataclass @@ -62,7 +64,7 @@ def __str__(self) -> str: """Return a string description of the planar state.""" return f"PlanarState object defined in plane {self.plane} with angle {self.angle}." - def to_statevector(self) -> npt.NDArray[np.complex128]: + def to_statevector_numpy(self) -> npt.NDArray[np.complex128]: """Return the state vector.""" angle_rad = angle_to_rad(self.angle) match self.plane: diff --git a/tests/test_density_matrix.py b/tests/test_density_matrix.py index 9bef20d7a..ac3c95b61 100644 --- a/tests/test_density_matrix.py +++ b/tests/test_density_matrix.py @@ -157,7 +157,7 @@ def test_init_with_statevec_sucess(self, fx_rng: Generator) -> None: assert dm.dims() == (2**nqb, 2**nqb) assert np.allclose(dm.rho, expected_dm) - sv_list = [state.to_statevector() for state in states] + sv_list = [state.to_statevector_numpy() for state in states] sv = functools.reduce(np.kron, sv_list) # type: ignore[arg-type] # input with a statevector DATA (not Statevector object) @@ -182,7 +182,7 @@ def test_init_with_densitymatrix_sucess(self, fx_rng: Generator) -> None: ) # input with a huge density matrix - dm_list = [state.to_densitymatrix() for state in states] + dm_list = [state.to_densitymatrix_numpy() for state in states] num_dm = functools.reduce(np.kron, dm_list) # type: ignore[arg-type] dm = DensityMatrix(data=num_dm) diff --git a/tests/test_pauli.py b/tests/test_pauli.py index b867cef7a..bc4b57633 100644 --- a/tests/test_pauli.py +++ b/tests/test_pauli.py @@ -98,7 +98,7 @@ def test_iter_meta(self) -> None: @pytest.mark.parametrize(("p", "b"), itertools.product(Pauli.iterate(symbol_only=True), [0, 1])) def test_eigenstate(self, p: Pauli, b: int) -> None: ev = float(Sign.plus_if(b == 0)) if p != Pauli.I else 1 - evec = p.eigenstate(b).to_statevector() + evec = p.eigenstate(b).to_statevector_numpy() assert np.allclose(p.matrix @ evec, ev * evec) def test_eigenstate_invalid(self) -> None: diff --git a/tests/test_statevec.py b/tests/test_statevec.py index 7aa6d680b..3298e9304 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -70,7 +70,7 @@ def test_default_tensor_success(self, fx_rng: Generator) -> None: assert len(vec.dims()) == nqb vec = Statevector(nqubit=nqb, data=BasicStates.MINUS_I) - sv_list = [BasicStates.MINUS_I.to_statevector() for _ in range(nqb)] + sv_list = [BasicStates.MINUS_I.to_statevector_numpy() for _ in range(nqb)] sv = functools.reduce(lambda a, b: np.kron(a, b).astype(np.complex128, copy=False), sv_list) assert np.allclose(vec.psi, sv.reshape((2,) * nqb)) assert len(vec.dims()) == nqb @@ -80,7 +80,7 @@ def test_default_tensor_success(self, fx_rng: Generator) -> None: rand_plane = fx_rng.choice(np.array(Plane)) state = PlanarState(rand_plane, rand_angle) vec = Statevector(nqubit=nqb, data=state) - sv_list = [state.to_statevector() for _ in range(nqb)] + sv_list = [state.to_statevector_numpy() for _ in range(nqb)] sv = functools.reduce(lambda a, b: np.kron(a, b).astype(np.complex128, copy=False), sv_list) assert np.allclose(vec.psi, sv.reshape((2,) * nqb)) assert len(vec.dims()) == nqb @@ -90,7 +90,7 @@ def test_default_tensor_success(self, fx_rng: Generator) -> None: rand_planes = fx_rng.choice(np.array(Plane), nqb) states = [PlanarState(plane=i, angle=j) for i, j in zip(rand_planes, rand_angles, strict=True)] vec = Statevector(nqubit=nqb, data=states) - sv_list = [state.to_statevector() for state in states] + sv_list = [state.to_statevector_numpy() for state in states] sv = functools.reduce(lambda a, b: np.kron(a, b).astype(np.complex128, copy=False), sv_list) assert np.allclose(vec.psi, sv.reshape((2,) * nqb)) assert len(vec.dims()) == nqb diff --git a/tests/test_statevec_backend.py b/tests/test_statevec_backend.py index ac0b56dc9..1ed3f41ea 100644 --- a/tests/test_statevec_backend.py +++ b/tests/test_statevec_backend.py @@ -26,7 +26,7 @@ def test_measurement_into_each_xyz_basis(self, state: PlanarState) -> None: n = 3 k = 0 # for measurement into |-> returns [[0, 0], ..., [0, 0]] (whose norm is zero) - statevector = state.to_statevector() + statevector = state.to_statevector_numpy() m_op = np.outer(statevector, statevector.T.conjugate()) sv = Statevector(nqubit=n) sv.evolve(m_op.astype(np.complex128, copy=False), [k]) @@ -38,7 +38,9 @@ def test_measurement_into_each_xyz_basis(self, state: PlanarState) -> None: def test_measurement_into_minus_state(self) -> None: n = 3 k = 0 - m_op = np.outer(BasicStates.MINUS.to_statevector(), BasicStates.MINUS.to_statevector().T.conjugate()) + m_op = np.outer( + BasicStates.MINUS.to_statevector_numpy(), BasicStates.MINUS.to_statevector_numpy().T.conjugate() + ) sv = Statevector(nqubit=n) sv.evolve(m_op.astype(np.complex128, copy=False), [k]) with pytest.raises(AssertionError): diff --git a/tests/test_tnsim.py b/tests/test_tnsim.py index af755ed2f..c006048d2 100644 --- a/tests/test_tnsim.py +++ b/tests/test_tnsim.py @@ -25,7 +25,7 @@ def random_op(sites: int, rng: Generator) -> npt.NDArray[np.complex128]: CZ = Ops.CZ -plus = BasicStates.PLUS.to_statevector() +plus = BasicStates.PLUS.to_statevector_numpy() class TestTN: From e6a915246accd7bb6da2984af8f8ff71a97529f4 Mon Sep 17 00:00:00 2001 From: matulni Date: Mon, 13 Jul 2026 14:14:00 +0200 Subject: [PATCH 10/10] Up rev dep in mqtbench --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 4dd431d22..da7602def 100644 --- a/noxfile.py +++ b/noxfile.py @@ -107,7 +107,7 @@ class ReverseDependency: install_target=".[dev]", branch="rename_methods", ), - ReverseDependency("https://github.com/matulni/graphix-mqtbench", branch="minimal"), + ReverseDependency("https://github.com/matulni/graphix-mqtbench", branch="rename_methods"), ], ) def tests_reverse_dependencies(session: Session, package: ReverseDependency) -> None: