Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
59a4b49
:sparkles: Add ZXGraphState
Oct 5, 2025
d2172a9
:sparkles: Implement local_complement
Oct 5, 2025
fab5a8a
:sparkles: Implement pivot
Oct 5, 2025
5e47377
:sparkles: Add remove_clifford
Oct 5, 2025
6d02cdd
:sparkles: Implement remove_cliffords
Oct 5, 2025
0ce2db8
:sparkles: Implement convert_to_phase_gadget
Oct 5, 2025
b3a795e
:sparkles: Implement merge_yz_to_xy
Oct 5, 2025
c2ba8a5
:sparkles: Implement merge_yz_nodes
Oct 5, 2025
f89521b
:sparkles: Implement full_reduce
Oct 5, 2025
4525e5d
:art: Fix docstring
Oct 5, 2025
aeb1bf8
:art: Fix docstring
Oct 5, 2025
0b29a92
:art: Fix test
Oct 5, 2025
92843c0
:sparkles: Add a function to generate random MBQC circuit
Oct 5, 2025
3674192
:sparkles: Add demonstration for zxgraph simplification
Oct 5, 2025
d205a1d
:bug: Fix bug in generating circuit
Oct 5, 2025
d62f07b
:art: Fix type hint
Oct 5, 2025
ccc3e91
:art: Fix docstrings
Oct 5, 2025
c2a706b
:art: Fix type hint
Oct 5, 2025
0a8818b
:bulb: Add docs
Oct 5, 2025
bc70745
Merge branch 'master' into 102-add-zxgraphstate-class
Oct 17, 2025
06f0490
:bug: Fix bug after merge
Oct 17, 2025
14f76c5
:recycle: Refactor to_zx_graphstate
Oct 17, 2025
167ea75
:bug: Fix output node treatment
Oct 20, 2025
e44defb
:bug: Fix operation corresponding to lemma 4.7
Oct 20, 2025
6e97ac9
:bug: Fix handling of output node corresponding to lem. 4.7
Oct 20, 2025
c0a0649
:art: Improve readability
Oct 22, 2025
d882ed4
:recycle: Split case corresponds to lemma 4.11
Oct 22, 2025
26ba73e
:white_check_mark: Add tests for _is_noninput_with_io_nbrs
Oct 22, 2025
1fde3e9
:bug: Correct pivot definition
Oct 28, 2025
68dbbce
:sparkles: Add new case for removing cliffords
Oct 29, 2025
8207c0d
:fire: Delete unnecessary process
Nov 17, 2025
8b1eb91
:art: Fix docstring
Nov 17, 2025
47eb80d
:bug: Fix bug in preprocess
Nov 17, 2025
790aeb3
:bug: Fix expand_local_cliffords
Nov 17, 2025
92c962e
:art: Add demo to assert zxgraph simplification validity
Nov 17, 2025
f6a55e0
:art: Fix type hints
Nov 17, 2025
03a996a
Merge branch 'master' into 102-add-zxgraphstate-class
Nov 17, 2025
253d71f
:art: Fix import
Nov 17, 2025
525bd42
:bug: Fix bug in _expnad_input_local_cliffords
Nov 17, 2025
41f5fe8
:white_check_mark: Update tests for expand_local_cliffords
Nov 23, 2025
023d285
:art: Update expand_local_cliffords
Nov 23, 2025
be9a8e4
:art: Improve readability
Nov 24, 2025
6f37848
:art: Update docs for lc and pivot
Nov 24, 2025
9619ba4
:fire: Remove _swap
Nov 24, 2025
43bb480
:truck: Split gflow_wrapper
Nov 24, 2025
39af736
:art: Fix method name
Nov 24, 2025
adc9203
:white_check_mark: Add inner product test
Nov 24, 2025
35de92e
:art: Fix old test
Nov 24, 2025
c2f75b2
:art: Change update_lc_basis logic
Nov 25, 2025
204a61a
:bug: Fix bug
Nov 25, 2025
9ee7ee2
:art: Fix clifford expansion test
Nov 25, 2025
3a9b83a
:art: Fix zxgraphstate test
Nov 25, 2025
3e12fe8
:art: ruff
Nov 25, 2025
73fb584
:art: pyright
Nov 25, 2025
779d9bd
:art: ruff
Nov 25, 2025
4bfa792
:bug: Fix bug in input/output expansion
Nov 26, 2025
9f258e5
:white_check_mark: Add LocalClifford updating test in ZXGraphState layer
Nov 26, 2025
4b89957
:art: Fix import
Nov 26, 2025
65b7b7e
:sparkles: Add logically equivalent measurement basis map
Nov 27, 2025
2301bc7
:sparkles: Assure gflow existence
Nov 27, 2025
4f9ba16
:art: Fix tests
Nov 27, 2025
9fa0b72
:art: Fix
Nov 27, 2025
e6067c5
:art: Fix docs
Nov 27, 2025
2b88998
:art: Fix docstring
Nov 27, 2025
e1741f6
:bulb: Add doc settings
Nov 27, 2025
5bc194d
:art: Fix docs
Nov 27, 2025
8a31d96
:bug: Fix docs
Nov 27, 2025
69ffb37
:memo: Improve docs
Nov 27, 2025
a745331
:recycle: Move local_cliffords stuff into ZXGraphState
Dec 5, 2025
c624419
:recycle: Move local_cliffords related tests into test_zxgraphstate
Dec 5, 2025
476fc10
:art: Fix initialization
Dec 5, 2025
50a68f9
:art: ruff
Dec 5, 2025
370d3bc
:memo: Fix docs
Dec 5, 2025
6c6cab1
:art: Fix doc
Dec 5, 2025
59e4182
:art: Replace FlowLike
Dec 11, 2025
d73da3a
:fire: Remove Any
Dec 11, 2025
53d01c1
:art: Replace by assert_never
Dec 11, 2025
1acaa6e
:art: Fix string
Dec 11, 2025
24385f3
:fire: Remove debug memo
Dec 11, 2025
7959b82
:art: Ensure base has local_clifford attr in from_base_graph_state
Dec 11, 2025
e97d060
:art: Replace set creation
Dec 11, 2025
718e7ef
:art: Fix gflow_wrapper return obj
Dec 11, 2025
9ee30db
:art: Add expected_plane parameter to preserve gflow existence
Dec 12, 2025
61a63f0
:bug: Fix bug in pivot
Dec 12, 2025
9361a22
:bug: Fix bug in expand_local_cliffords
Dec 12, 2025
0cee9ba
:memo: Fix docs
Dec 12, 2025
8059c8c
:art: Fix tests
Dec 12, 2025
4998cf2
:art: Add unregister/replace method for inputs/outputs
Dec 12, 2025
475f4e7
:fire: Clean gflow_utils
Dec 12, 2025
3de2d37
:fire: Remove basis2tuple, round_clifford_angle
Dec 12, 2025
b65529d
:fire: Remove pivot_on_boundary
Dec 12, 2025
1b6f15f
:bug: Fix bug in merge_yz_to_xy and pivot
Dec 12, 2025
b3a5bc3
:art: Fix input/output node indices
Dec 12, 2025
9f50f1d
:fire: Remove old test
Dec 12, 2025
3e43e54
:fire: Remove old test
Dec 12, 2025
5be1924
:memo: Fixed docs for gflow_wrapper
Dec 12, 2025
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
12 changes: 12 additions & 0 deletions docs/source/gflow_utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Gflow Utils
===========

:mod:`graphqomb.gflow_utils` module
+++++++++++++++++++++++++++++++++++

.. automodule:: graphqomb.gflow_utils

Functions
---------

.. autofunction:: graphqomb.gflow_utils.gflow_wrapper
8 changes: 0 additions & 8 deletions docs/source/graphstate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,3 @@ Functions
.. autofunction:: graphqomb.graphstate.compose
.. autofunction:: graphqomb.graphstate.bipartite_edges
.. autofunction:: graphqomb.graphstate.odd_neighbors

Auxiliary Classes
------------------
.. autoclass:: graphqomb.graphstate.LocalCliffordExpansion
:members:

.. autoclass:: graphqomb.graphstate.ExpansionMaps
:members:
2 changes: 2 additions & 0 deletions docs/source/references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ Module reference
random_objects
feedforward
focus_flow
gflow_utils
command
pattern
pauli_frame
qompiler
scheduler
stim_compiler
visualizer
zxgraphstate
32 changes: 32 additions & 0 deletions docs/source/zxgraphstate.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
ZXGraphState
============

:mod:`graphqomb.zxgraphstate` module
+++++++++++++++++++++++++++++++++++++

.. automodule:: graphqomb.zxgraphstate

ZX Graph State
--------------

.. autoclass:: graphqomb.zxgraphstate.ZXGraphState
:members:
:show-inheritance:
:member-order: bysource

Functions
---------

.. autofunction:: graphqomb.zxgraphstate.complete_graph_edges

Auxiliary Classes
------------------

.. autoclass:: graphqomb.zxgraphstate.InputLocalCliffordExpansion
:members:

.. autoclass:: graphqomb.zxgraphstate.OutputLocalCliffordExpansion
:members:

.. autoclass:: graphqomb.zxgraphstate.ExpansionMaps
:members:
136 changes: 136 additions & 0 deletions examples/zxgraph_simplification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""
Basic example of simplifying a ZX-diagram.
==========================================

By using the full_reduce method,
we can remove all the internal Clifford nodes and some non-Clifford nodes from the graph state,
which generates a simpler ZX-diagram.
This example is a simple demonstration of the simplification process.

Note that as a result of the simplification, local Clifford operations are applied to the input/output nodes.
"""

# %%

from __future__ import annotations

from copy import deepcopy

import numpy as np

from graphqomb.gflow_utils import gflow_wrapper
from graphqomb.qompiler import qompile
from graphqomb.random_objects import generate_random_flow_graph
from graphqomb.simulator import PatternSimulator, SimulatorBackend
from graphqomb.visualizer import visualize
from graphqomb.zxgraphstate import ZXGraphState

# %%
# Prepare an initial random graph state with flow
graph, flow = generate_random_flow_graph(width=3, depth=4, edge_p=0.5)
zx_graph, _ = ZXGraphState.from_base_graph_state(graph)
visualize(zx_graph)

# %%
# We can compile the graph state into a measurement pattern, simulate it, and get the resulting statevector.
pattern = qompile(zx_graph, flow)
sim = PatternSimulator(pattern, backend=SimulatorBackend.StateVector)
sim.simulate()
statevec_original = sim.state


# %%
def print_boundary_lcs(zxgraph: ZXGraphState) -> None:
lc_map = zxgraph.local_cliffords
for node in zxgraph.input_node_indices | zxgraph.output_node_indices:
# check lc on input and output nodes
lc = lc_map.get(node, None)
if lc is not None:
if node in zxgraph.input_node_indices:
print(f"Input node {node} has local Clifford: alpha={lc.alpha}, beta={lc.beta}, gamma={lc.gamma}")
else:
print(f"Output node {node} has local Clifford: alpha={lc.alpha}, beta={lc.beta}, gamma={lc.gamma}")
else:
print(f"Node {node} has no local Clifford.")


def print_meas_bses(graph: ZXGraphState) -> None:
print("node | plane | angle (/pi)")
for node in graph.input_node_indices:
print(f"{node} (input)", graph.meas_bases[node].plane, graph.meas_bases[node].angle / np.pi)
for node in graph.physical_nodes - set(graph.input_node_indices) - set(graph.output_node_indices):
print(node, graph.meas_bases[node].plane, graph.meas_bases[node].angle / np.pi)
for node in graph.output_node_indices:
print(f"{node} (output)", "-", "-")


# %%
print_boundary_lcs(zx_graph)

# %%
# Initial graph state before simplification
print_meas_bses(zx_graph)


# %%
# Simplify the graph state by full_reduce method
zx_graph_smp = deepcopy(zx_graph)
zx_graph_smp.full_reduce()

# %%
# Simplified graph state after full_reduce.
visualize(zx_graph_smp)
print_meas_bses(zx_graph_smp)
print_boundary_lcs(zx_graph_smp)


# %%
# Let us compare the graph state before and after simplification.
# We simulate the pattern obtained from the simplified graph state.
# Note that we need to call the `expand_local_cliffords` method before generating the pattern to get the gflow.

zx_graph_smp.expand_local_cliffords()
print("input_node_indices: ", set(zx_graph_smp.input_node_indices))
print("output_node_indices: ", set(zx_graph_smp.output_node_indices))
print("local_cliffords: ", zx_graph_smp.local_cliffords)

print_meas_bses(zx_graph_smp)
visualize(zx_graph_smp)
print_boundary_lcs(zx_graph_smp)

# %%
# Now we can obtain the gflow for the simplified graph state.
# Then, we compile the simplified graph state into a measurement pattern,
# simulate it, and get the resulting statevector.
gflow_smp = gflow_wrapper(zx_graph_smp)
pattern_smp = qompile(zx_graph_smp, gflow_smp)
sim_smp = PatternSimulator(pattern_smp, backend=SimulatorBackend.StateVector)
sim_smp.simulate()

# %%
statevec_smp = sim_smp.state
# %%
# normalization check
print("norm of original statevector:", np.linalg.norm(statevec_original.state()))
print("norm of simplified statevector:", np.linalg.norm(statevec_smp.state()))

# %%
# Finally, we compare the expectation values of random observables before and after simplification.
rng = np.random.default_rng()
for i in range(len(zx_graph.input_node_indices)):
rand_mat = rng.random((2, 2)) + 1j * rng.random((2, 2))
rand_mat += rand_mat.T.conj()
exp = statevec_original.expectation(rand_mat, [i])
exp_cr = statevec_smp.expectation(rand_mat, [i])
print("Expectation values for rand_mat\n===============================")
print("rand_mat: \n", rand_mat)
print("Original: \t\t", exp)
print("After simplification: \t", exp_cr)

print("norm: ", np.linalg.norm(statevec_original.state()), np.linalg.norm(statevec_smp.state()))
print("data shape: ", statevec_original.state().shape, statevec_smp.state().shape)
psi_org = statevec_original.state()
psi_smp = statevec_smp.state()
print("inner product: ", np.abs(np.vdot(psi_org, psi_smp)))

# %%
75 changes: 53 additions & 22 deletions graphqomb/euler.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,41 +213,70 @@ def conjugate(self) -> LocalClifford:
return LocalClifford(-self.gamma, -self.beta, -self.alpha)


def meas_basis_info(vector: NDArray[np.complex128]) -> tuple[Plane, float]:
def _meas_basis_candidates(vector: NDArray[np.complex128]) -> list[tuple[Plane, float]]:
r"""Return candidate measurement planes and angles corresponding to a vector.

Parameters
----------
vector : `numpy.typing.NDArray`\[`numpy.complex128`\]
1 qubit state vector

Returns
-------
`list`\[`tuple`\[`Plane`, `float`\]\]
candidate measurement planes and angles
"""
theta, phi = bloch_sphere_coordinates(vector)
candidates: list[tuple[Plane, float]] = []
if is_clifford_angle(phi):
# YZ or XZ plane
if is_close_angle(2 * phi, 0): # 0 or pi
xz_theta = -theta if is_close_angle(phi, math.pi) else theta
candidates.append((Plane.XZ, xz_theta))
if is_close_angle(phi, 0) and is_close_angle(2 * theta, 0):
candidates.append((Plane.YZ, theta))
if is_close_angle(2 * (phi - math.pi / 2), 0):
yz_theta = -theta if is_close_angle(phi, 3 * math.pi / 2) else theta
candidates.append((Plane.YZ, yz_theta))
if is_clifford_angle(theta) and not is_clifford_angle(theta / 2):
# XY plane
phi = phi + math.pi if is_close_angle(theta, 3 * math.pi / 2) else phi
candidates.append((Plane.XY, phi))

return candidates


def meas_basis_info(vector: NDArray[np.complex128], expected_plane: Plane | None = None) -> tuple[Plane, float]:
r"""Return the measurement plane and angle corresponding to a vector.

Parameters
----------
vector : `numpy.typing.NDArray`\[`numpy.complex128`\]
1 qubit state vector
expected_plane : `Plane` | `None`, optional
expected measurement plane to preserve gflow existence, by default None

Returns
-------
`tuple`\[`Plane`, `float`]
`tuple`\[`Plane`, `float`\]
measurement plane and angle

Raises
------
ValueError
if the vector does not lie on any of 3 planes
"""
theta, phi = bloch_sphere_coordinates(vector)
if is_clifford_angle(phi):
# YZ or XZ plane
if is_clifford_angle(phi / 2): # 0 or pi
if is_close_angle(phi, math.pi):
theta = -theta
return Plane.XZ, theta
if is_close_angle(phi, 3 * math.pi / 2):
theta = -theta
return Plane.YZ, theta
if is_clifford_angle(theta) and not is_clifford_angle(theta / 2):
# XY plane
if is_close_angle(theta, 3 * math.pi / 2):
phi += math.pi
return Plane.XY, phi
msg = "The vector does not lie on any of 3 planes"
raise ValueError(msg)
candidates = _meas_basis_candidates(vector)

if not candidates:
msg = "The vector does not lie on any of 3 planes"
raise ValueError(msg)

if expected_plane is not None:
for plane, angle in candidates:
if plane == expected_plane:
return plane, angle
return candidates[0]


# TODO(masa10-f): Algebraic backend for this computation(#023)
Expand Down Expand Up @@ -275,7 +304,7 @@ def update_lc_lc(lc1: LocalClifford, lc2: LocalClifford) -> LocalClifford:


# TODO(masa10-f): Algebraic backend for this computation(#023)
def update_lc_basis(lc: LocalClifford, basis: MeasBasis) -> PlannerMeasBasis:
def update_lc_basis(lc: LocalClifford, basis: MeasBasis, expected_plane: Plane | None = None) -> PlannerMeasBasis:
"""Update a `MeasBasis` object with an action of `LocalClifford` object.

Parameters
Expand All @@ -284,17 +313,19 @@ def update_lc_basis(lc: LocalClifford, basis: MeasBasis) -> PlannerMeasBasis:
`LocalClifford`
basis : `MeasBasis`
`MeasBasis`
expected_plane : `Plane` | `None`, optional
expected measurement plane to preserve gflow existence, by default None

Returns
-------
`PlannerMeasBasis`
updated `PlannerMeasBasis`
"""
matrix = lc.matrix()
matrix = lc.matrix().conjugate().T
vector = basis.vector()

updated_vector = np.asarray(matrix @ vector, dtype=np.complex128)
plane, angle = meas_basis_info(updated_vector)
plane, angle = meas_basis_info(updated_vector, expected_plane=expected_plane)
return PlannerMeasBasis(plane, angle)


Expand Down
68 changes: 68 additions & 0 deletions graphqomb/gflow_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Utilities for generalized flow (gflow) computation.

This module provides:

- `gflow_wrapper`: Thin adapter around ``swiflow.gflow`` so that gflow can be computed directly
from a `BaseGraphState` instance.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import networkx as nx
from swiflow import gflow
from swiflow.common import Plane as SfPlane
from typing_extensions import assert_never

from graphqomb.common import Plane

if TYPE_CHECKING:
from networkx import Graph as NxGraph

from graphqomb.graphstate import BaseGraphState


def gflow_wrapper(graphstate: BaseGraphState) -> dict[int, set[int]]:
"""Utilize ``swiflow.gflow`` to search gflow.

Parameters
----------
graphstate : `BaseGraphState`
graph state to find gflow

Returns
-------
``dict[int, set[int]]``
gflow object

Raises
------
ValueError
If no gflow is found
"""
graph: NxGraph[int] = nx.Graph()
graph.add_nodes_from(graphstate.physical_nodes)
graph.add_edges_from(graphstate.physical_edges)

bases = graphstate.meas_bases
planes = {node: bases[node].plane for node in bases}
swiflow_planes: dict[int, SfPlane] = {}
for node, plane in planes.items():
if plane == Plane.XY:
swiflow_planes[node] = SfPlane.XY
elif plane == Plane.YZ:
swiflow_planes[node] = SfPlane.YZ
elif plane == Plane.XZ:
swiflow_planes[node] = SfPlane.XZ
else:
assert_never(plane)

gflow_object = gflow.find(
graph, graphstate.input_node_indices.keys(), graphstate.output_node_indices.keys(), swiflow_planes
)
if gflow_object is None:
msg = "No flow found"
raise ValueError(msg)

return gflow_object.f
Loading