Skip to content
Merged
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
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- **QEC Stim MPP Import**: Added utilities for building `StabilizerCode` inputs from unsigned Stim `MPP` layers, including sparse Stim qubit id mapping, coordinate import, multi-layer selection, detector/logical-observable import, and the optional `graphqomb[stim]` extra. Signed products using inverted Pauli targets are rejected because stabilizer signs are not retained.
- **Stim Circuit Import**: Added `stim_file_to_pattern()`, `stim_text_to_pattern()`, and `stim_circuit_to_pattern()` for converting supported Stim circuits into GraphQOMB patterns. The importer handles Clifford unitary blocks and `TICK`-separated Pauli measurements (`M`/`MZ`, `MX`, `MY`, `MXX`, `MYY`, `MZZ`, and `MPP`), assigns single-qubit measurement bases directly to their graph nodes, terminates each qubit lifetime at its direct measurement while allowing disjoint qubits to continue, validates that same-block MPP products commute, supports Type I and Type II Y foliation, causally lowers commuting MPP groups, composes each MPP unit through a separate unmeasured output layer, and resolves detector/logical-observable records once across the full flattened circuit. Circuit-level noise and measurement-error probabilities are intentionally omitted because GraphQOMB uses an MBQC-specific noise model; reset, measurement-reset, and feedback instructions remain unsupported.
- **Stim Circuit Import**: Added `stim_file_to_pattern()`, `stim_text_to_pattern()`, and `stim_circuit_to_pattern()` for converting supported Stim circuits into GraphQOMB patterns. The importer handles initial Pauli resets (`R`/`RZ`, `RX`, and `RY`), Clifford unitary blocks, and `TICK`-separated Pauli measurements (`M`/`MZ`, `MX`, `MY`, `MXX`, `MYY`, `MZZ`, and `MPP`), assigns single-qubit measurement bases directly to their graph nodes, terminates each qubit lifetime at its direct measurement while allowing disjoint qubits to continue, validates that same-block MPP products commute, supports Type I and Type II Y foliation, keeps MPP ancilla nodes out of the X-correction flow, derives the complete Z-correction flow from the composed data flow without Pauli simplification, places gate and MPP blocks on explicit temporal Z layers while preserving the first two Stim coordinate components, aligns parallel gate outputs across different transpiled depths, relocates idle I/O nodes without adding graph nodes or edges, and resolves detector/logical-observable records once across the full flattened circuit. Circuit-level noise and measurement-error probabilities are intentionally omitted because GraphQOMB uses an MBQC-specific noise model; mid-circuit reset, measurement-reset, and feedback instructions remain unsupported.
- **Input Initialization Bases**: Added per-input positive Pauli eigenstate initialization via `GraphState.register_input(..., init_axis=...)`, with `X+`, `Y+`, and `Z+` propagated through `qompile()`, `Pattern`, `PatternSimulator`, and Stim export.

### Changed

- **Development Tooling**: Use uv as the default dependency manager for local development, CI, documentation builds, and publishing workflows.
- **Graph State API**: Replaced legacy `physical_*` graph methods/properties with standard graph-style `nodes`, `edges`, `add_node()`, `add_edge()`, `remove_node()`, `remove_edge()`, and count/query helpers.
- **Pattern Simulator**: Materialize pending output Pauli-frame corrections when returning output statevectors or explicit output measurement results.
- **Pattern Simulator**: Sample all measurements from exact Born probabilities by default, with `calc_prob=False` retaining the legacy 50/50 assumption for non-output measurements.
- **PTN Format**: Bumped exported `.ptn` files to format version 2 to record non-default input initialization bases with `.input_basis`; version 1 files remain readable and default inputs to `X+`.
- **Stim Compiler**: Input reset instructions now follow the stored initialization basis (`RX`, `RY`, or `R`) while non-input preparations remain `RX`.
- **Stim Coordinate Import**: Reject `QUBIT_COORDS` whose first two components collide across qubits, because graph nodes are placed by the XY projection and colliding projections produce coincident nodes.

### Fixed

- **Pattern Simulator**: Raise `TypeError` for unsupported commands instead of recursively redispatching them.
- **QEC Graph-State Builder**: Add the required ancilla CZ edge when shared data qubits contain an odd number of oppositely ordered stabilizer-interaction pairs, using the same `Z`-before-`Y`-before-`X` rule for Type I and Type II foliation.
- **Stim Circuit Import**: Preserve the existing data-lane endpoint and its coordinate when importing a terminal single-qubit measurement.
- **Stim Circuit Import Coordinates**: Generate consistent 3D spacetime coordinates across gate, idle, and MPP fragments instead of mixing raw 2D gate coordinates with MPP Z layers.
- **Stim Compiler**: Preserve minus-signed axis measurement bases using inverted Stim measurement targets.
- **State Vector Array Conversion**: Convert to a real NumPy dtype without warnings when every amplitude is real-valued, and reject the conversion when it would discard nonzero imaginary amplitudes.

Expand Down
18 changes: 18 additions & 0 deletions docs/source/ptn_format.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
Pattern Text Format
===================

GraphQOMB writes pattern files using format version 2. Version 2 adds optional
per-input positive Pauli eigenstate initialization:

.. code-block:: text

.version 2
.input 0:0 1:1 2:2
.input_basis 1:Y 2:Z

Each ``.input_basis`` entry has the form ``node:X``, ``node:Y``, or ``node:Z``
and must reference a node declared by ``.input``. ``X`` initialization is the
default and is omitted when serializing, so the example initializes nodes 0,
1, and 2 as ``X+``, ``Y+``, and ``Z+`` respectively.

Version 1 files remain readable. Inputs in a version 1 file, or in a version 2
file without a corresponding ``.input_basis`` entry, are initialized as
``X+``. The ``.input_basis`` directive is rejected in version 1 files.

:mod:`graphqomb.ptn_format` module
++++++++++++++++++++++++++++++++++

Expand Down
9 changes: 9 additions & 0 deletions docs/source/qec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ support, followed by an output layer; qubits without Y support retain the Type I
two-measurement-layer layout. Ancilla support edges only touch measurement
layers, and the output of one composed unit becomes the input of the next.

Both foliation variants use the local stabilizer-interaction order
``Z -> Y -> X`` on each shared data qubit. For a pair of stabilizers, the
builder adds a CZ edge between their ancillas when an odd number of shared
data-qubit pairs reverse that order: one qubit applies stabilizer ``a`` before
``b`` while the other applies ``b`` before ``a``. Equal-Pauli overlaps have no
strict order and do not contribute. The builder tracks only the parity of the
two directions and considers only stabilizer pairs that share a data qubit, so
sparse codes do not require an all-pairs stabilizer scan.

.. automodule:: graphqomb.qec.qeccode
:members:
:show-inheritance:
11 changes: 11 additions & 0 deletions docs/source/simulator.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
Statevector Simulator
=====================

``PatternSimulator`` samples measurement results from their exact Born
probabilities by default. This is required when inputs use ``Y+`` or ``Z+``
initialization, because non-output measurements are not necessarily uniformly
random.

For compatibility with the previous faster approximation, pass
``calc_prob=False``. In that mode, non-output measurements are sampled 50/50;
output measurements still use their exact probabilities. This approximation is
only appropriate when the pattern guarantees uniformly random non-output
measurements.

.. automodule:: graphqomb.simulator
:members:
:show-inheritance:
86 changes: 64 additions & 22 deletions docs/source/stim_importer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,47 @@ Install the optional Stim integration before importing this module:
uv add "graphqomb[stim]"

The circuit importer converts supported Stim circuits into GraphQOMB
measurement patterns. It accepts Clifford unitary blocks and Pauli measurement
blocks separated by ``TICK``. The supported Pauli measurement instructions are
``M``/``MZ``, ``MX``, ``MY``, ``MXX``, ``MYY``, ``MZZ``, and ``MPP``.
measurement patterns. It accepts initial Pauli resets, Clifford unitary blocks,
and Pauli measurement blocks separated by ``TICK``. The supported Pauli
measurement instructions are ``M``/``MZ``, ``MX``, ``MY``, ``MXX``, ``MYY``,
``MZZ``, and ``MPP``.

Initial reset instructions
--------------------------

Leading reset instructions determine the positive Pauli eigenstate used for an
input: ``R``/``RZ`` initializes ``Z+``, ``RX`` initializes ``X+``, and ``RY``
initializes ``Y+``. A reset is initial when its target qubit has not previously
participated in a unitary or measurement operation. Operations on other qubits
do not prevent a later initial reset. Repeated leading resets are accepted, and
the last reset on each qubit determines its initialization state.

Stim canonicalizes the Z-axis aliases internally, so the importer accepts both
``M`` and ``MZ`` for Z measurement and both ``R`` and ``RZ`` for Z reset. Stim
export uses ``MZ`` for Z measurement and ``R`` for Z reset.

Mid-circuit resets are rejected because GraphQOMB patterns do not currently
represent multiple lifetimes for one logical qubit. Combined measurement-reset
instructions (``MR``/``MRZ``, ``MRX``, and ``MRY``) remain unsupported.

Single-qubit measurements assign an ``AxisMeasBasis`` directly to the measured
graph node. They do not create an ``MPP`` extraction or an ancillary parity
measurement node. Inverted single-qubit measurement targets select the minus
sign of that node's basis. A direct single-qubit measurement terminates that
qubit's lifetime: a later quantum operation on the same qubit is rejected,
while operations on other qubits may continue. Reset and qubit reuse are not
supported.
data-lane endpoint without replacing that node or its coordinate. They do not
create an ``MPP`` extraction or an ancillary parity measurement node. Inverted
single-qubit measurement targets select the minus sign of that node's basis. A
direct single-qubit measurement terminates that qubit's lifetime: a later
quantum operation on the same qubit is rejected, while operations on other
qubits may continue. A measured qubit cannot begin a new lifetime later in the
circuit.

The first two components of ``QUBIT_COORDS`` are used as the fixed spatial
``(x, y)`` position of each data lane. The importer supplies the temporal ``z``
component. Every unitary ``TICK`` block is transpiled before placement. Its
input layer starts at the preceding block's output ``z``, and all of its output
nodes share the maximum transpiled depth of the block. A shorter data-wire
chain is spread across that same interval. A live qubit with no operation in
the block remains a single input/output node and is relocated directly to the
common output layer; this adds no graph node or edge and does not change the
circuit semantics.

Two-qubit measurements are parity measurements and are lowered to equivalent
unsigned ``MPP`` products. Inverted targets in ``MXX``, ``MYY``, ``MZZ``, and
Expand All @@ -27,14 +57,29 @@ corresponding parity offset.

All ``MPP`` instructions within one ``TICK`` block are represented by one
combined extraction and are validated to commute. Anticommuting products in
the same block are rejected. The importer uses one compact
stabilizer-measurement unit when its correction flow is causal. If the compact
unit has a cyclic Pauli flow, the commuting products are lowered to equivalent
sequential units instead. Non-commuting measurements must be separated by
``TICK`` in the source circuit. Each unit has a distinct unmeasured output
layer, which is composed with the next unitary or measurement fragment by qubit
index. Pass ``y_foliation=YFoliation.TYPE_II`` to any of the three import entry
points to use the three-layer Y-measurement construction; Type I is the default.
the same block are rejected. Within a combined block, local stabilizer
interactions are ordered ``Z -> Y -> X`` on each shared data qubit. If an odd
number of shared-data-qubit pairs reverse the order of the same two
stabilizers, the graph-state builder adds the required CZ edge between their
ancillas. This rule is applied automatically for both Type I and Type II
foliation.

Only data-wire nodes contribute to the X-correction flow; MPP ancilla nodes do
not produce X corrections. After composing all graph fragments, the importer
derives the Z-correction flow from the odd neighborhood of the complete
X-correction flow. Both correction maps are passed directly to ``qompile()``
without Pauli simplification or an importer-specific fallback. Non-commuting
measurements must be separated by ``TICK`` in the source circuit. Each unit has
a distinct unmeasured output layer, which is composed with the next unitary or
measurement fragment by qubit index. Pass
``y_foliation=YFoliation.TYPE_II`` to any of the three import entry points to use
the three-layer Y-measurement construction; Type I is the default.

An MPP block starts at the preceding gate or MPP output layer and ends two
``z`` units later. Live lanes not used by that MPP block are relocated to the
same output layer without adding nodes. Consequently, a composed output and
the next active fragment input have the same ``z`` coordinate, and imported
patterns do not mix 2D spatial coordinates with 3D spacetime coordinates.

The flattened ideal circuit is analyzed once for measurement records. Records
from single-qubit measurements, pair measurements, ``MPP``, and ideal-zero
Expand All @@ -56,18 +101,15 @@ to Pauli measurements are also omitted while retaining the ideal measurement.
Heralded noise records are retained as ideal zero-valued record positions so
that later ``DETECTOR`` and ``OBSERVABLE_INCLUDE`` references remain aligned.

Reset instructions (``R``/``RZ``, ``RX``, and ``RY``) and combined
measurement-reset instructions (``MR``/``MRZ``, ``MRX``, and ``MRY``) are not
handled by this importer. Consequently, a directly measured qubit cannot begin
a new lifetime later in the circuit.

.. code-block:: python

from graphqomb.qec.qeccode import YFoliation
from graphqomb.stim_importer import stim_text_to_pattern

result = stim_text_to_pattern(
"""
RY 0
R 1 2
MX 0
MYY 1 2
DETECTOR rec[-2] rec[-1]
Expand Down
4 changes: 2 additions & 2 deletions graphqomb/euler.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def bloch_sphere_coordinates(vector: NDArray[np.complex128]) -> tuple[float, flo
Returns
-------
`tuple`\[`float`, `float`]
`tuple`\[`float`, `float`\]
Bloch sphere coordinates (:math:`\theta`, :math:`\phi`)
"""
# normalize
Expand Down Expand Up @@ -223,7 +223,7 @@ def meas_basis_info(vector: NDArray[np.complex128]) -> tuple[Plane, float]:
Returns
-------
`tuple`\[`Plane`, `float`]
`tuple`\[`Plane`, `float`\]
measurement plane and angle
Raises
Expand Down
20 changes: 11 additions & 9 deletions graphqomb/feedforward.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,15 @@ def check_dag(dag: Mapping[int, Iterable[int]]) -> None:
Raises
------
ValueError
If the flowlike object is not causal with respect to the graph state
If the graph contains a cycle
"""
for node, children in dag.items():
for child in children:
if node in dag[child]:
msg = f"Cycle detected in the graph: {node} -> {child}"
raise ValueError(msg)
inv_dag = inverse_dag_from_dag(dag)
try:
tuple(TopologicalSorter(inv_dag).static_order())
except CycleError as exc:
cycle = " -> ".join(map(str, exc.args[1]))
msg = f"Cycle detected in the graph: {cycle}"
raise ValueError(msg) from exc


def inverse_dag_from_dag(
Expand Down Expand Up @@ -223,7 +225,7 @@ def signal_shifting(

Returns
-------
`tuple`\[`dict`\[`int`, `set`\[`int`\]\], `dict`\[`int`, `set`\[`int`\]\]]
`tuple`\[`dict`\[`int`, `set`\[`int`\]\], `dict`\[`int`, `set`\[`int`\]\]\]
Updated correction maps for X and Z after signal shifting.
"""
if zflow is None:
Expand Down Expand Up @@ -266,7 +268,7 @@ def propagate_correction_map( # noqa: C901, PLR0912

Returns
-------
`tuple`\[`dict`\[`int`, `set`\[`int`\]\], `dict`\[`int`, `set`\[`int`\]\]]
`tuple`\[`dict`\[`int`, `set`\[`int`\]\], `dict`\[`int`, `set`\[`int`\]\]\]
Updated correction maps for X and Z after measurement at the target node.

Raises
Expand Down Expand Up @@ -349,7 +351,7 @@ def pauli_simplification( # noqa: C901, PLR0912

Returns
-------
`tuple`\[`dict`\[`int`, `set`\[`int`\]\], `dict`\[`int`, `set`\[`int`\]\]]
`tuple`\[`dict`\[`int`, `set`\[`int`\]\], `dict`\[`int`, `set`\[`int`\]\]\]
Updated correction maps for X and Z after simplification.
"""
if zflow is None:
Expand Down
Loading
Loading