Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
83cdae7
wip
matulni Jan 15, 2026
abc36df
Add tests and docs
matulni Jan 15, 2026
3341830
Merge branch 'master' into circuit-extraction
matulni Jan 15, 2026
5961716
Change in compilation.py
matulni Feb 13, 2026
fc73e9a
Merge branch 'master' into circuit-extraction
matulni Feb 16, 2026
5097719
Update mentions to old get_ methods
matulni Feb 16, 2026
3362ca6
Up docs
matulni Feb 16, 2026
43d3472
Merge branch 'master' into circuit-extraction
matulni Feb 17, 2026
4a30a62
Make consistent with new measurement API
matulni Feb 17, 2026
9806151
Merge branch 'master' into circuit-extraction
matulni Feb 18, 2026
bc69330
Update tests with isclose
matulni Feb 18, 2026
f24aae6
various fixes
matulni Feb 18, 2026
5a49b55
Fix pyright
matulni Feb 18, 2026
d84da7d
Merge branch 'master' into circuit-extraction
matulni Feb 23, 2026
f99c285
Add suggestions Thierry
matulni Feb 26, 2026
8dd3ebc
Replace bool by Sign in PauliString
matulni Feb 26, 2026
ffefd8c
Restore ruff noqa
matulni Feb 26, 2026
95fd393
Replace sorting by filtering in add_pexp (Thierry suggestion)
matulni Feb 26, 2026
575a9ac
Simplify API for compilation passes (Thierry's suggestion)
matulni Feb 26, 2026
d6a1464
Lift indexing into extraction phase
thierry-martinez Feb 26, 2026
e971fde
Update tests
thierry-martinez Feb 26, 2026
5903cf8
Add mods remap
matulni Feb 27, 2026
fd5b48e
Up docs
matulni Feb 27, 2026
c959197
wip
matulni Mar 2, 2026
4e928fd
Add test checking order of qubits. Add remap method to CliffordMap
matulni Mar 2, 2026
57a22a7
Merge branch 'master' into circuit-extraction
matulni Mar 2, 2026
568d4f9
Up CHANGELOG
matulni Mar 2, 2026
c971b73
Remap input nodes of clifford map
matulni Mar 2, 2026
3a6a302
Import Self from typing_extensions
matulni Mar 2, 2026
7e5e450
Turn static classes into functions and define default passes
thierry-martinez Mar 3, 2026
cbb5e3e
Up docs and tests
matulni Mar 3, 2026
8e8679d
Merge branch 'master' into circuit-extraction
matulni Mar 3, 2026
c5238c1
Fix ruff
matulni Mar 3, 2026
715b027
Merge branch 'master' into circuit-extraction
matulni Mar 5, 2026
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- #450: `Circuit.visit` and `BaseInstruction.visit` performs simple replacements on circuits and instructions, given an `InstructionVisitor`.

- #445: Circuit extraction.
- Added `PauliFlow.is_focused` to verify if a Pauli flow is focused and `PauliFlow.pauli_strings` associating a Pauli string to the every corrected node.
- Added `PauliFlow.extract_circuit`
- Added new module `graphix.circ_ext.extraction` to extract circuits from Pauli flows with the following new classes:
- `ExtractionResult`
- `PauliString`
- `PauliExponential`
- `PauliExponentialDAG`
- `CliffordMap`
- Added new module `graphix.circ_ext.compilation` to perform the transformation `ExtractionResult` -> `Graphix circuit` with the following new functions:
- `er_to_circuit`
- `pexp_ladder_pass`


### Fixed

- #429
Expand Down
1 change: 1 addition & 0 deletions graphix/circ_ext/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Utilities for circuit extraction and compilation."""
188 changes: 188 additions & 0 deletions graphix/circ_ext/compilation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
"""Compilation passes to transform the result of the circuit extraction algorithm into a quantum circuit."""

from __future__ import annotations

from itertools import chain, pairwise
from typing import TYPE_CHECKING

from graphix.fundamentals import ANGLE_PI
from graphix.sim.base_backend import NodeIndex
from graphix.transpiler import Circuit

if TYPE_CHECKING:
from collections.abc import Callable

from graphix.circ_ext.extraction import CliffordMap, ExtractionResult, PauliExponential, PauliExponentialDAG


def er_to_circuit(
er: ExtractionResult,
pexp_cp: Callable[[PauliExponentialDAG, Circuit], None] | None = None,
cm_cp: Callable[[CliffordMap, Circuit], None] | None = None,
) -> Circuit:
"""Convert a circuit extraction result into a quantum circuit representation.

This method synthesizes a circuit by sequentially applying the Clifford map and the Pauli exponential DAG (Directed Acyclic Graph) in the extraction result. It performs a validation check to ensure that the output nodes of both components are identical and it maps the output node numbers to qubit indices.

Parameters
----------
er : ExtractionResult
The result of the extraction process, containing both the ``clifford_map`` and the ``pexp_dag``.
pexp_cp: Callable[[PauliExponentialDAG, Circuit], None] | None
Compilation pass to synthetize a Pauli exponential DAG. If ``None`` (default), :func:`pexp_ladder_pass` is employed.
cm_cp: Callable[[PauliExponentialDAG, Circuit], None] | None
Compilation pass to synthetize a Clifford map. If ``None`` (default), a `ValueError` is raised since there is still no default pass for Clifford map integrated in Graphix.

Returns
-------
Circuit
A quantum circuit that combines the Clifford map operations followed by the Pauli exponential operations.

Raises
------
ValueError
If the output nodes of ``er.pexp_dag`` and ``er.clifford_map`` do not match, indicating an incompatible extraction result.
"""
if list(er.pexp_dag.output_nodes) != list(er.clifford_map.output_nodes):
raise ValueError(
"The Pauli Exponential DAG and the Clifford Map in the Extraction Result are incompatible since they have different output nodes."
)
if pexp_cp is None:
pexp_cp = pexp_ladder_pass

if cm_cp is None:
raise ValueError(
"Clifford-map pass is missing: there is still no default pass for Clifford map integrated in Graphix. You may use graphix-stim-compiler plugin."
)

n_qubits = len(er.pexp_dag.output_nodes)
circuit = Circuit(n_qubits)
outputs_mapping = NodeIndex()
outputs_mapping.extend(er.pexp_dag.output_nodes)

inputs_mapping = NodeIndex()
inputs_mapping.extend(er.clifford_map.input_nodes)

cm_cp(er.clifford_map.remap(inputs_mapping.index, outputs_mapping.index), circuit)
pexp_cp(er.pexp_dag.remap(outputs_mapping.index), circuit)
return circuit


def pexp_ladder_pass(pexp_dag: PauliExponentialDAG, circuit: Circuit) -> None:
r"""Add a Pauli exponential DAG to a circuit by using a ladder decomposition.

The input circuit is modified in-place. This function assumes that the Pauli exponential DAG has been remap, i.e., its Pauli strings are defined on qubit indices instead of output nodes. See :meth:`PauliString.remap` for additional information.

Parameters
----------
pexp_dag: PauliExponentialDAG
The Pauli exponential rotation to be added to the circuit. Its Pauli strings are assumed to be defined on qubit indices.
circuit : Circuit
The circuit to which the operation is added. The input circuit is assumed to be compatible with ``pexp_dag.output_nodes``.

Notes
-----
Pauli exponentials in the DAG are compiled sequentially following an arbitrary total order compatible with the DAG. Each Pauli exponential is decomposed into a sequence of basis changes, CNOT gates, and a single :math:`R_Z` rotation:

.. math::

R_Z(\phi) = \exp \left(-i \frac{\phi}{2} Z \right),

with effective angle :math:`\phi = -2\alpha`, where :math:`\alpha` is the angle encoded in `self.angle`. Basis changes map :math:`X` and :math:`Y` operators to the :math:`Z` basis before entangling the qubits in a CNOT ladder.

Gate set: H, CNOT, RZ, RY

See https://quantumcomputing.stackexchange.com/questions/5567/circuit-construction-for-hamiltonian-simulation/11373#11373 for additional information.
"""

def add_pexp(pexp: PauliExponential, circuit: Circuit) -> None:
r"""Add the Pauli exponential unitary to a quantum circuit.

This function modifies the input circuit in-place.

Parameters
----------
circuit : Circuit
The quantum circuit to which the Pauli exponential is added.

Notes
-----
It is assumed that the ``x``, ``y``, and ``z`` node sets of the Pauli string in the exponential are well-formed, i.e., contain valid qubit indices and are pairwise disjoint.
"""
if pexp.angle == 0: # No rotation
return

# We assume that nodes in the Pauli strings have been mapped to qubits.
modified_qubits = [
qubit
for qubit in range(circuit.width)
if qubit in pexp.pauli_string.x_nodes | pexp.pauli_string.y_nodes | pexp.pauli_string.z_nodes
]
angle = -2 * pexp.angle * pexp.pauli_string.sign

if len(modified_qubits) == 0: # Identity
return

q0 = modified_qubits[0]

if len(modified_qubits) == 1:
if q0 in pexp.pauli_string.x_nodes:
circuit.rx(q0, angle)
elif q0 in pexp.pauli_string.y_nodes:
circuit.ry(q0, angle)
else:
circuit.rz(q0, angle)
return

add_basis_change(pexp, q0, circuit)

for q1, q2 in pairwise(modified_qubits):
add_basis_change(pexp, q2, circuit)
circuit.cnot(control=q1, target=q2)

circuit.rz(modified_qubits[-1], angle)

for q2, q1 in pairwise(modified_qubits[::-1]):
circuit.cnot(control=q1, target=q2)
add_basis_change(pexp, q2, circuit)

add_basis_change(pexp, modified_qubits[0], circuit)

def add_basis_change(pexp: PauliExponential, qubit: int, circuit: Circuit) -> None:
"""Apply an X or a Y basis change to a given qubit if required by the Pauli string.

This function modifies the input circuit in-place.

Parameters
----------
pexp : PauliExponential
The Pauli exponential under consideration.
qubit : int
The qubit on which the basis-change operation is performed.
circuit : Circuit
The quantum circuit to which the basis change is added.
"""
if qubit in pexp.pauli_string.x_nodes:
circuit.h(qubit)
elif qubit in pexp.pauli_string.y_nodes:
add_hy(qubit, circuit)

def add_hy(qubit: int, circuit: Circuit) -> None:
"""Add a pi rotation around the z + y axis.

This function modifies the input circuit in-place.

Parameters
----------
qubit : int
The qubit on which the basis-change operation is performed.
circuit : Circuit
The quantum circuit to which the basis change is added.
"""
circuit.rz(qubit, ANGLE_PI / 2)
circuit.ry(qubit, ANGLE_PI / 2)
circuit.rz(qubit, ANGLE_PI / 2)

for node in chain(*reversed(pexp_dag.partial_order_layers[1:])):
pexp = pexp_dag.pauli_exponentials[node]
add_pexp(pexp, circuit)
Loading