Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_<object>` for transformations that can only return `object` or raise an exception. Equivalently, we use `.from_<object>` for constructors.
- `.to_<object>_or_none` for transformations that can return `object` or `None`. Equivalently, we use `.from_<object>_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
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/statevec.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
14 changes: 7 additions & 7 deletions docs/source/modifier.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Pattern Manipulation

.. automethod:: simulate_pattern

.. automethod:: compute_max_degree
.. automethod:: max_degree

.. automethod:: compose

Expand All @@ -50,17 +50,17 @@ Pattern Manipulation

.. automethod:: is_standard

.. automethod:: extract_graph
.. automethod:: graph

.. automethod:: extract_nodes
.. automethod:: nodes

.. automethod:: extract_causal_flow
.. automethod:: to_causalflow

.. automethod:: extract_gflow
.. automethod:: to_gflow

.. automethod:: extract_opengraph
.. automethod:: to_opengraph

.. automethod:: extract_measurement_commands
.. automethod:: measurement_commands

.. automethod:: parallelize_pattern

Expand Down
2 changes: 1 addition & 1 deletion docs/source/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/fusion_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 1 addition & 1 deletion examples/ghz_with_tn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion examples/qft_with_tn.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}")

Expand Down
2 changes: 1 addition & 1 deletion examples/tn_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}")

Expand Down
2 changes: 1 addition & 1 deletion examples/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

# %%
4 changes: 2 additions & 2 deletions graphix/_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion graphix/circ_ext/extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 15 additions & 15 deletions graphix/flow/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,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.
Expand Down Expand Up @@ -267,7 +267,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.
Expand All @@ -278,7 +278,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.

Expand Down Expand Up @@ -350,7 +350,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
Expand Down Expand Up @@ -595,7 +595,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
Expand Down Expand Up @@ -623,7 +623,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
Expand Down Expand Up @@ -974,7 +974,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
----------
Expand Down Expand Up @@ -1008,7 +1008,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
Expand All @@ -1029,10 +1029,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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1320,7 +1320,7 @@ def _corrections_to_dag(

Notes
-----
See :func:`XZCorrections.extract_dag`.
See :func:`XZCorrections.dag`.
"""
relations = (
(measured_node, corrected_node)
Expand Down Expand Up @@ -1461,7 +1461,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)
Expand Down Expand Up @@ -1570,7 +1570,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}
Expand Down
6 changes: 3 additions & 3 deletions graphix/fundamentals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
22 changes: 11 additions & 11 deletions graphix/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
"""

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
Loading
Loading