Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
ed8b097
add is_hermitian
masa10-f Jun 26, 2025
b1f2674
add flip and conjugate method in MeasBasis
masa10-f Jun 26, 2025
cccca8b
implement simulator_backend
masa10-f Jun 26, 2025
61085bb
fix docstring for sphinx build
masa10-f Jun 26, 2025
c2ba75b
implement statevec
masa10-f Jun 26, 2025
c548a35
resove numpy error
masa10-f Jun 26, 2025
91100e3
fix pyright error
masa10-f Jun 26, 2025
b25aa86
update sphinx docs
masa10-f Jun 26, 2025
55438dc
add unittest for statevec
masa10-f Jun 26, 2025
e19e123
safe type casting
masa10-f Jun 26, 2025
2050db7
use abc.abstractmethod
masa10-f Jun 26, 2025
bfded20
fix mypy error
masa10-f Jun 26, 2025
941422e
fix sphinx build error
masa10-f Jun 26, 2025
50df50c
fix test
masa10-f Jun 26, 2025
e6f7812
update docs of matrix
masa10-f Jun 26, 2025
9e8acd7
use ArrayLike in the input of StateVector.__init__()
masa10-f Jun 27, 2025
2f4d149
remove internal _num_qubits
masa10-f Jun 27, 2025
2087a0a
rename to copy
masa10-f Jun 27, 2025
4d207ad
improve the performance of `evolve`
masa10-f Jun 27, 2025
4535f93
update test for evolve method
masa10-f Jun 27, 2025
777af77
update test comment
masa10-f Jun 27, 2025
f22b1a8
avoide unnecessary copy in measure
masa10-f Jun 27, 2025
7404c0f
optimize add_node
masa10-f Jun 27, 2025
c280092
fix interface of entangle
masa10-f Jun 27, 2025
0963f8a
make tensor_product a staticmethod
masa10-f Jun 27, 2025
5a60625
remove is_isolated
masa10-f Jun 27, 2025
39ff091
improve the efficiency of expectation
masa10-f Jun 27, 2025
3fd559a
fix mypy errors
masa10-f Jun 27, 2025
9d73962
manage qubit order for performance
masa10-f Jun 27, 2025
bbcf2d5
add test for reorder operations
masa10-f Jun 27, 2025
b50dd2b
add more test
masa10-f Jun 27, 2025
a116d57
fix the interface of StateVector
masa10-f Jun 27, 2025
5a5f812
possess the state as a tensor object inside the class
masa10-f Jun 27, 2025
2876673
format
masa10-f Jun 27, 2025
33ed8f3
fix conjugate of Axis.Y
masa10-f Jun 27, 2025
edc6ab3
add missing import
masa10-f Jun 29, 2025
ab06500
fix StateVector.__init__
masa10-f Jun 29, 2025
d376646
fix copy method
masa10-f Jun 29, 2025
94e2df5
fix docstring
masa10-f Jun 29, 2025
a74ddcc
Merge branch 'master' into statevec
masa10-f Jul 14, 2025
f647f2f
use ndim
masa10-f Jul 15, 2025
b3cc661
return tensor instead of flatten vector
masa10-f Jul 15, 2025
118e4f9
remove property decorator from state
masa10-f Jul 15, 2025
ff77316
improve copy method
masa10-f Jul 15, 2025
94a5e8e
use math.sqrt
masa10-f Jul 15, 2025
c8ccdf7
fix copy control
masa10-f Jul 15, 2025
36f54bc
update test
masa10-f Jul 15, 2025
e226a84
fix ruff error
masa10-f Jul 15, 2025
e3773e1
Merge branch 'master' into statevec
masa10-f Jul 15, 2025
eca516b
implement QubitIndexManager
masa10-f Jul 15, 2025
d01e907
update statevec
masa10-f Jul 15, 2025
2a2f408
update docstring
masa10-f Jul 15, 2025
6e3dd06
fix mypy errors
masa10-f Jul 15, 2025
28f0ea1
add unittest for QubitIndexManager
masa10-f Jul 15, 2025
5efe303
use default instead of if branch
masa10-f Jul 17, 2025
d920b16
improve match func
masa10-f Jul 17, 2025
f0f9aae
improve performance of recovery_permutation
masa10-f Jul 17, 2025
53d8aee
rename
masa10-f Jul 17, 2025
80fe758
implement internal_to_external
masa10-f Jul 17, 2025
77599e0
enforce sequential index
masa10-f Jul 17, 2025
1927105
add num_qubits property
masa10-f Jul 17, 2025
f22dee3
cast into set
masa10-f Jul 17, 2025
8db89fd
use ravel instead of flatten
masa10-f Jul 17, 2025
1efc02a
remove redundant ravel
masa10-f Jul 17, 2025
e259fe1
modify the type of copy argument
masa10-f Jul 18, 2025
7acb6f6
use bit_count to calculate num_qubits
masa10-f Jul 18, 2025
7dd89c0
use tensordot
masa10-f Jul 18, 2025
b3a877a
fix pyright error
masa10-f Jul 18, 2025
5bd9c19
avoid astype
masa10-f Jul 18, 2025
440a356
use bit_length instead of bit_count
masa10-f Jul 18, 2025
2303878
try to fix type error
masa10-f Jul 18, 2025
855b133
Merge branch 'statevec' of github.com:TeamGraphix/graphix-zx into sta…
masa10-f Jul 18, 2025
c3e7fc8
patch for size=0 case
masa10-f Jul 18, 2025
a41cb64
allow __array__ and __asarray__
masa10-f Jul 18, 2025
97d536f
implement __array__ and __asarray__
masa10-f Jul 18, 2025
d348b97
remove redundant tuple casting
masa10-f Jul 18, 2025
cf04fdf
adopt the suggested change
masa10-f Jul 18, 2025
1552fec
specify dtype
masa10-f Jul 18, 2025
540318a
remove __asarray__
masa10-f Jul 21, 2025
c9a7e3e
add copy argument
masa10-f Jul 21, 2025
d72594b
add test for __array__
masa10-f Jul 22, 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
2 changes: 2 additions & 0 deletions docs/source/matrix.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Functions
---------

.. autofunction:: graphix_zx.matrix.is_unitary

.. autofunction:: graphix_zx.matrix.is_hermitian
2 changes: 2 additions & 0 deletions docs/source/references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Module reference
:maxdepth: 2

common
simulator_backend
statevec
euler
matrix
graphstate
Expand Down
7 changes: 7 additions & 0 deletions docs/source/simulator_backend.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Simulator Backend
=================

.. automodule:: graphix_zx.simulator_backend
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/source/statevec.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
State Vector
============

.. automodule:: graphix_zx.statevec
:members:
:undoc-members:
:show-inheritance:
56 changes: 51 additions & 5 deletions graphix_zx/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ def angle(self) -> float:
"""Return the measurement angle."""
raise NotImplementedError

@abc.abstractmethod
def flip(self) -> MeasBasis:
"""Flip the measurement basis."""
raise NotImplementedError

@abc.abstractmethod
def conjugate(self) -> MeasBasis:
"""Return the conjugate of the measurement basis."""
raise NotImplementedError

@abc.abstractmethod
def vector(self) -> NDArray[np.complex128]:
"""Return the measurement basis vector."""
Expand Down Expand Up @@ -108,16 +118,17 @@ def angle(self) -> float:
return self.__angle

@typing_extensions.override
def vector(self) -> NDArray[np.complex128]:
r"""Return the measurement basis vector.
def flip(self) -> PlannerMeasBasis:
"""Flip the measurement basis.

Returns
-------
`numpy.typing.NDArray`\[`numpy.complex128`\]
measurement basis vector
`PlannerMeasBasis`
flipped PlannerMeasBasis
"""
return meas_basis(self.plane, self.angle)
return PlannerMeasBasis(self.plane, self.angle + np.pi)

@typing_extensions.override
def conjugate(self) -> PlannerMeasBasis:
"""Return the conjugate of the PlannerMeasBasis object.

Expand All @@ -142,6 +153,17 @@ def conjugate(self) -> PlannerMeasBasis:
return PlannerMeasBasis(Plane.XZ, self.angle)
typing_extensions.assert_never(self.plane)

@typing_extensions.override
def vector(self) -> NDArray[np.complex128]:
r"""Return the measurement basis vector.

Returns
-------
`numpy.typing.NDArray`\[`numpy.complex128`\]
measurement basis vector
"""
return meas_basis(self.plane, self.angle)


class AxisMeasBasis(MeasBasis):
"""Class to represent an axis measurement basis.
Expand Down Expand Up @@ -212,6 +234,30 @@ def angle(self) -> float:
angle = 0 if self.sign == Sign.PLUS else np.pi
return angle

@typing_extensions.override
def flip(self) -> AxisMeasBasis:
"""Flip the measurement basis.

Returns
-------
`AxisMeasBasis`
flipped AxisMeasBasis
"""
return AxisMeasBasis(self.axis, Sign.MINUS if self.sign == Sign.PLUS else Sign.PLUS)

@typing_extensions.override
def conjugate(self) -> AxisMeasBasis:
"""Return the conjugate of the AxisMeasBasis object.

Returns
-------
`AxisMeasBasis`
conjugate AxisMeasBasis
"""
if self.axis == Axis.Y:
return AxisMeasBasis(Axis.Y, Sign.MINUS if self.sign == Sign.PLUS else Sign.PLUS)
return AxisMeasBasis(self.axis, self.sign)

@typing_extensions.override
def vector(self) -> NDArray[np.complex128]:
r"""Return the measurement basis vector.
Expand Down
17 changes: 17 additions & 0 deletions graphix_zx/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This module provides:

- `is_unitary`: check if a matrix is unitary.
- `is_hermitian`: check if a matrix is Hermitian.
"""

from __future__ import annotations
Expand Down Expand Up @@ -33,3 +34,19 @@ def is_unitary(mat: NDArray[T]) -> bool:
if mat.shape[0] != mat.shape[1]:
return False
return np.allclose(np.eye(mat.shape[0]), mat @ mat.T.conj())


def is_hermitian(mat: NDArray[T]) -> bool:
r"""Check if a matrix is Hermitian.

Parameters
----------
mat : `numpy.typing.NDArray`\[T\]
matrix to check

Returns
-------
`bool`
`True` if Hermitian, `False` otherwise
"""
return np.allclose(mat, mat.T.conj())
205 changes: 205 additions & 0 deletions graphix_zx/simulator_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
"""The base class for simulator backends.

This module provides:

- `QubitIndexManager`: Manages the mapping of external qubit indices to internal indices
- `BaseSimulatorBackend`: Abstract base class for simulator backends.

"""

from __future__ import annotations

import abc
import itertools
import typing
from abc import ABC
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from collections.abc import Sequence

import numpy as np
from numpy.typing import NDArray

from graphix_zx.common import MeasBasis


class QubitIndexManager:
"""Manages the mapping of external qubit indices to internal indices."""

def __init__(self, num_qubits: int) -> None:
"""Initialize the QubitIndexManager with a list of initial indices."""
self.__indices = list(range(num_qubits))

@property
def num_qubits(self) -> int:
"""Get the number of qubits managed by this manager.

Returns
-------
`int`
The number of qubits.
"""
return len(self.__indices)

def add_qubits(self, num_qubits: int) -> None:
"""Add a specified number of qubits to the index manager.

Parameters
----------
num_qubits : `int`
The number of qubits to add.
"""
current_max = max(self.__indices, default=-1)
self.__indices.extend(range(current_max + 1, current_max + 1 + num_qubits))

def remove_qubit(self, qubit: int) -> None:
r"""Remove specified qubit from the index manager.

Parameters
----------
qubit : `int`
The qubit to remove.
"""
self.__indices = [q if q < qubit else q - 1 for q in self.__indices if q != qubit]

def match(self, order: Sequence[int]) -> bool:
r"""Check if the current indices match the given order.

Parameters
----------
order : `collections.abc.Sequence`\[`int`\]
A sequence of indices to compare against the current indices.

Returns
-------
`bool`
True if the current indices match the given order, False otherwise.
"""
return all(lhs == rhs for lhs, rhs in itertools.zip_longest(self.__indices, order, fillvalue=None))

def reorder(self, permutation: Sequence[int]) -> None:
r"""Reorder the indices based on a given permutation.

if permutation is [2, 0, 1], then
# [q0, q1, q2] -> [q1, q2, q0]

Parameters
----------
permutation : `collections.abc.Sequence`\[`int`\]
A sequence of indices that defines the new order of the indices.

Raises
------
ValueError
If the length of the permutation does not match the number of indices.
"""
if len(permutation) != len(self.__indices):
msg = "Permutation length must match the number of indices."
raise ValueError(msg)
self.__indices = [self.__indices[i] for i in permutation]

def inverse_permutation(self) -> list[int]:
r"""Get the permutation that would recover the original order of indices.

Returns
-------
`list`\[`int`\]
A sequence of indices that maps the current order back to the original order.
"""
inverse_perm = [0] * len(self.__indices)
for i, index in enumerate(self.__indices):
inverse_perm[index] = i
return inverse_perm

@typing.overload
def external_to_internal(self, external_qubits: int) -> int: ...

@typing.overload
def external_to_internal(self, external_qubits: Sequence[int]) -> tuple[int, ...]: ...

def external_to_internal(self, external_qubits: int | Sequence[int]) -> int | tuple[int, ...]:
r"""Convert external qubit indices to internal indices.

Parameters
----------
external_qubits : `int` | `collections.abc.Sequence`\[`int`\]
A sequence of external qubit indices.

Returns
-------
`int` | `tuple`\[`int`, ...\]
A list of internal qubit indices corresponding to the external ones.
"""
if isinstance(external_qubits, int):
return self.__indices[external_qubits]
return tuple(self.__indices[q] for q in external_qubits)

@typing.overload
def internal_to_external(self, internal_qubits: int) -> int: ...

@typing.overload
def internal_to_external(self, internal_qubits: Sequence[int]) -> tuple[int, ...]: ...

def internal_to_external(self, internal_qubits: int | Sequence[int]) -> int | tuple[int, ...]:
r"""Convert internal qubit indices to external indices.

Parameters
----------
internal_qubits : `int` | `collections.abc.Sequence`\[`int`\]
A sequence of internal qubit indices.

Returns
-------
`int` | `tuple`\[`int`, ...\]
A list of external qubit indices corresponding to the internal ones.
"""
inverse_perm = self.inverse_permutation()
if isinstance(internal_qubits, int):
return inverse_perm[internal_qubits]
return tuple(inverse_perm[q] for q in internal_qubits)


# backend for all simulator backends
class BaseSimulatorBackend(ABC):
"""Base class for simulator backends."""

@property
@abc.abstractmethod
def num_qubits(self) -> int:
"""Get the number of qubits in the state.

Returns
-------
`int`
The number of qubits in the state.
"""
raise NotImplementedError

@abc.abstractmethod
def evolve(self, operator: NDArray[np.complex128], qubits: int | Sequence[int]) -> None:
r"""Evolve the state by applying an operator to a subset of qubits.

Parameters
----------
operator : `numpy.typing.NDArray`\[`numpy.complex128`\]
The operator to apply.
qubits : `int` | `collections.abc.Sequence`\[`int`\]
The qubits to apply the operator to.
"""
raise NotImplementedError

@abc.abstractmethod
def measure(self, qubit: int, meas_basis: MeasBasis, result: int) -> None:
"""Measure a qubit in a given measurement basis.

Parameters
----------
qubit : `int`
The qubit to measure.
meas_basis : `MeasBasis`
The measurement basis to use.
result : `int`
The measurement result.
"""
raise NotImplementedError
Loading
Loading