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
2 changes: 1 addition & 1 deletion docs/source/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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).


Expand Down
2 changes: 1 addition & 1 deletion docs/source/lc-mbqc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

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/simulator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Statevector
.. autoclass:: StatevectorBackend
:members:

.. autoclass:: Statevec
.. autoclass:: Statevector
:members:

Density Matrix
Expand Down
6 changes: 3 additions & 3 deletions docs/source/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down 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 Expand Up @@ -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
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
6 changes: 3 additions & 3 deletions examples/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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())))
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/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -64,7 +64,7 @@
"Sign",
"SpaceMinimizationHeuristics",
"StandardizedPattern",
"Statevec",
"Statevector",
"StatevectorBackend",
"XZCorrections",
"__version__",
Expand Down
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
32 changes: 16 additions & 16 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 @@ -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
----------
Expand All @@ -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_correctionmatrix_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_correctionmatrix_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_correctionmatrix_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_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
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
Loading
Loading