Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
f6411bc
Enable type-checking for simulator and backends
thierry-martinez Jul 6, 2025
09fbd24
Replace cast with safe astype coercions
thierry-martinez Jul 18, 2025
4ce7a27
Merge branch 'master' into typed_simulator
thierry-martinez Jul 18, 2025
e0e1a7c
Add coercion from `bool` to `Outcome`
thierry-martinez Jul 22, 2025
558d013
Do not export `StateT_co`
thierry-martinez Jul 22, 2025
e1fa51e
Use iterator unpack and comment about not using comprehension
thierry-martinez Jul 22, 2025
7ba231f
Remove useless list coercion
thierry-martinez Jul 22, 2025
a7acac2
Use `Mapping` instead of `dict`
thierry-martinez Jul 22, 2025
2ca05aa
Remove deprecated `ptrace`
thierry-martinez Jul 22, 2025
e131f5f
Replace `.shape[0].bit_length()` by `.size`
thierry-martinez Jul 22, 2025
84d8ab1
Remove useless annotation on `list_of_sv`
thierry-martinez Jul 22, 2025
8aff7bf
Add missing return type annotation
thierry-martinez Jul 22, 2025
dc0e255
Use list comprehension instead of `list(map(.))`
thierry-martinez Jul 22, 2025
c88f456
Remove useless dynamic type check
thierry-martinez Jul 22, 2025
a454fd4
Comparison with `np.object_`
thierry-martinez Jul 22, 2025
5adb8ba
Revert to `BaseM`
thierry-martinez Jul 22, 2025
f7e8d48
Merge branch 'master' into typed_simulator
thierry-martinez Jul 22, 2025
dbf70f4
No need to use `abs`
thierry-martinez Jul 22, 2025
38a24a4
Rename `FullStateBackend` into `DenseStateBackend`
thierry-martinez Jul 22, 2025
e458e6e
Comment for `apply_channel` default implementation
thierry-martinez Jul 22, 2025
19b3b9b
Add a comment for the `Backend` interface
thierry-martinez Jul 22, 2025
85a6131
Remove wrapper for eig
thierry-martinez Jul 22, 2025
80580c8
Change tensordot axes for tuples
thierry-martinez Jul 23, 2025
e8f5a69
Add `check_expression_or_complex`
thierry-martinez Jul 23, 2025
cdde47e
Add `check_expression_or_float`
thierry-martinez Jul 23, 2025
7b54360
Explain add_qubit
thierry-martinez Jul 23, 2025
c8e67be
Use listcomp
thierry-martinez Jul 23, 2025
57dbb30
Use % 2
thierry-martinez Jul 23, 2025
a711ea7
Replace print by warning
thierry-martinez Jul 23, 2025
a77e1ec
Add type-stub for quimb
thierry-martinez Jul 23, 2025
8524152
Add missing include field for setuptools
thierry-martinez Jul 23, 2025
be848eb
Remove unused functions
thierry-martinez Jul 23, 2025
4e33978
Add missing type annotation
thierry-martinez Jul 23, 2025
81c62cc
Add _AbstractTensorNetworkBackend to initialize fields properly
thierry-martinez Jul 23, 2025
d88ca87
Make `_StateT_co` and `_DenseStateT_co` private
thierry-martinez Jul 23, 2025
8d3397f
Merge branch 'master' into typed_simulator
thierry-martinez Jul 23, 2025
259ff7c
Merge branch 'master' into typed_simulator
thierry-martinez Jul 24, 2025
c535d4e
Replace with noqa
thierry-martinez Jul 24, 2025
f30da16
Replace with noqa
thierry-martinez Jul 24, 2025
17b69ee
Merge branch 'master' into typed_simulator
thierry-martinez Jul 24, 2025
06630e6
Add type annotations for mypy
thierry-martinez Jul 24, 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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- #277: Methods for pretty-printing `Pattern`: `to_ascii`,
`to_unicode`, `to_latex`.

- #312: The separation between `TensorNetworkBackend` and backends
that operate on a full-state representation, such as
`StatevecBackend` and `DensityMatrixBackend`, is now clearer with
the introduction of the abstract classes `DenseStateBackend` and
`DenseState`, which derive from `Backend` and `BackendState`,
respectively. `StatevecBackend` and `DensityMatrixBackend` inherit
from `DenseStateBackend`, while `Statevec` and `DensityMatrix`
inherit from `DenseState`. Note that the class hierarchy of
`BackendState` mirrors that of `Backend`.

- #322: Added a new `optimization` module containing:

* a functional version of `standardize` that returns a standardized
Expand Down Expand Up @@ -44,6 +54,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
semantics is preserved between circuits, ZX graphs, open graphs and
patterns.

- #302, #308, #312: `Pattern`, `Circuit`, `PatternSimulator`, and
backends are now type-checked.

### Changed

- #277: The method `Pattern.print_pattern` is now deprecated.
Expand All @@ -61,6 +74,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Note: the method `perform_pauli_measurements` still places C
commands before X and Z commands.

- #312: Backend's `State` has been renamed to `BackendState` to avoid
a name conflict with the `State` class defined in `graphix.states`,
which represents the state of a single qubit.

- #312: `Backend[StateT_co]` and `DenseStateBackend[DenseStateT_co]`
are now parameterized by covariant type variables, allowing
subclasses to narrow the type of the state field to match their
specific state representation. Covariance is sound in this context
because the classes are frozen, and it ensures that
`Backend[BackendState]` is a supertype of all backend classes.

## [0.3.1] - 2025-04-21

### Added
Expand Down
6 changes: 5 additions & 1 deletion graphix/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from graphix.ops import Ops

if TYPE_CHECKING:
from collections.abc import Iterable
from collections.abc import Iterable, Iterator

_T = TypeVar("_T", bound=np.generic)

Expand Down Expand Up @@ -147,6 +147,10 @@ def __len__(self) -> int:
"""Return the number of Kraus operators."""
return len(self.__data)

def __iter__(self) -> Iterator[KrausData]:
"""Iterate over Kraus operators."""
return iter(self.__data)

@property
def nqubit(self) -> int:
"""Return the number of qubits."""
Expand Down
11 changes: 7 additions & 4 deletions graphix/graphsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@

from graphix import utils
from graphix.clifford import Clifford
from graphix.measurements import outcome
from graphix.ops import Ops
from graphix.sim.statevec import Statevec

if TYPE_CHECKING:
import functools
from collections.abc import Iterable, Mapping

from graphix.measurements import Outcome


if TYPE_CHECKING:
Graph = nx.Graph[int]
Expand Down Expand Up @@ -373,7 +376,7 @@ def equivalent_fill_node(self, node: int) -> int:
return 2
return 0

def measure_x(self, node: int, choice: int = 0) -> int:
def measure_x(self, node: int, choice: Outcome = 0) -> Outcome:
"""Perform measurement in X basis.

According to original paper, we realise X measurement by
Expand Down Expand Up @@ -406,7 +409,7 @@ def measure_x(self, node: int, choice: int = 0) -> int:
self.h(node)
return self.measure_z(node, choice=choice)

def measure_y(self, node: int, choice: int = 0) -> int:
def measure_y(self, node: int, choice: Outcome = 0) -> Outcome:
"""Perform measurement in Y basis.

According to original paper, we realise Y measurement by
Expand All @@ -431,7 +434,7 @@ def measure_y(self, node: int, choice: int = 0) -> int:
self.h(node)
return self.measure_z(node, choice=choice)

def measure_z(self, node: int, choice: int = 0) -> int:
def measure_z(self, node: int, choice: Outcome = 0) -> Outcome:
"""Perform measurement in Z basis.

To realize the simple Z measurement on undecorated graph state,
Expand All @@ -455,7 +458,7 @@ def measure_z(self, node: int, choice: int = 0) -> int:
if choice:
for i in self.neighbors(node):
self.flip_sign(i)
result = choice if not isolated else int(self.nodes[node]["sign"])
result = choice if not isolated else outcome(self.nodes[node]["sign"])
self.remove_node(node)
return result

Expand Down
11 changes: 10 additions & 1 deletion graphix/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@

import dataclasses
import math
from typing import NamedTuple, SupportsInt
from typing import Literal, NamedTuple, SupportsInt

from typing_extensions import TypeAlias # TypeAlias introduced in Python 3.10

from graphix import utils
from graphix.fundamentals import Axis, Plane, Sign

# Ruff suggests to move this import to a type-checking block, but dataclass requires it here
from graphix.parameter import ExpressionOrFloat # noqa: TC001

Outcome: TypeAlias = Literal[0, 1]


def outcome(b: bool) -> Outcome:
"""Return 1 if True, 0 if False."""
return 1 if b else 0


@dataclasses.dataclass
class Domains:
Expand Down
7 changes: 4 additions & 3 deletions graphix/noise_models/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

if TYPE_CHECKING:
from graphix.channels import KrausChannel
from graphix.measurements import Outcome
from graphix.simulator import PatternSimulator


Expand Down Expand Up @@ -66,17 +67,17 @@ def measure(self) -> KrausChannel:
...

@abc.abstractmethod
def confuse_result(self, result: bool) -> bool:
def confuse_result(self, result: Outcome) -> Outcome:
"""Return a possibly flipped measurement outcome.

Parameters
----------
result : bool
result : Outcome
Ideal measurement result.

Returns
-------
bool
Outcome
Possibly corrupted result.
"""

Expand Down
7 changes: 6 additions & 1 deletion graphix/noise_models/noiseless_noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np
import typing_extensions

from graphix.channels import KrausChannel, KrausData
from graphix.noise_models.noise_model import NoiseModel

if TYPE_CHECKING:
from graphix.measurements import Outcome


class NoiselessNoiseModel(NoiseModel):
"""Noise model that performs no operation."""
Expand Down Expand Up @@ -51,7 +56,7 @@ def measure(self) -> KrausChannel:
return KrausChannel([KrausData(1.0, np.eye(2))])

@typing_extensions.override
def confuse_result(self, result: bool) -> bool:
def confuse_result(self, result: Outcome) -> Outcome:
"""Return the unmodified measurement result.

Parameters
Expand Down
20 changes: 20 additions & 0 deletions graphix/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,26 @@ def __hash__(self) -> int:
T = TypeVar("T")


def check_expression_or_complex(value: object) -> ExpressionOrComplex:
"""Check that the given object is of type ExpressionOrComplex and return it."""
if isinstance(value, Expression):
return value
if isinstance(value, SupportsComplex):
return complex(value)
msg = f"ExpressionOrComplex expected, but {type(value)} found."
raise TypeError(msg)


def check_expression_or_float(value: object) -> ExpressionOrFloat:
"""Check that the given object is of type ExpressionOrFloat and return it."""
if isinstance(value, Expression):
return value
if isinstance(value, SupportsFloat):
return float(value)
msg = f"ExpressionOrFloat expected, but {type(value)} found."
raise TypeError(msg)


@overload
def subs(value: ExpressionOrFloat, variable: Parameter, substitute: ExpressionOrSupportsFloat) -> ExpressionOrFloat: ...

Expand Down
23 changes: 12 additions & 11 deletions graphix/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from copy import deepcopy
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, SupportsFloat
from typing import TYPE_CHECKING, SupportsFloat, TypeVar

import networkx as nx
from typing_extensions import assert_never, override
Expand All @@ -23,7 +23,7 @@
from graphix.fundamentals import Axis, Plane, Sign
from graphix.gflow import find_flow, find_gflow, get_layers
from graphix.graphsim import GraphState
from graphix.measurements import PauliMeasurement
from graphix.measurements import Outcome, PauliMeasurement
from graphix.pretty_print import OutputFormat, pattern_to_str
from graphix.simulator import PatternSimulator
from graphix.states import BasicStates
Expand All @@ -35,9 +35,10 @@
from typing import Any, Literal

from graphix.parameter import ExpressionOrFloat, ExpressionOrSupportsFloat, Parameter
from graphix.sim.base_backend import Backend
from graphix.sim.base_backend import State as BackendState
from graphix.sim.density_matrix import Data
from graphix.sim import Backend, BackendState, Data


_StateT_co = TypeVar("_StateT_co", bound="BackendState", covariant=True)


@dataclass(frozen=True)
Expand Down Expand Up @@ -81,7 +82,7 @@ class Pattern:
total number of nodes in the resource state
"""

results: dict[int, int]
results: dict[int, Outcome]
__seq: list[Command]

def __init__(
Expand Down Expand Up @@ -250,7 +251,7 @@ def compose(

mapped_inputs = [mapping_complete[n] for n in other.input_nodes]
mapped_outputs = [mapping_complete[n] for n in other.output_nodes]
mapped_results = {mapping_complete[n]: m for n, m in other.results.items()}
mapped_results: dict[int, Outcome] = {mapping_complete[n]: m for n, m in other.results.items()}

merged = mapping_values_set.intersection(self.__output_nodes)

Expand Down Expand Up @@ -289,7 +290,7 @@ def update_command(cmd: Command) -> Command:

seq = self.__seq + [update_command(c) for c in other]

results = {**self.results, **mapped_results}
results: dict[int, Outcome] = {**self.results, **mapped_results}
p = Pattern(input_nodes=inputs, output_nodes=outputs, cmds=seq)
p.results = results

Expand Down Expand Up @@ -1354,7 +1355,7 @@ def space_list(self) -> list[int]:
return n_list

def simulate_pattern(
self, backend: Backend | str = "statevector", input_state: Data = BasicStates.PLUS, **kwargs: Any
self, backend: Backend[_StateT_co] | str = "statevector", input_state: Data = BasicStates.PLUS, **kwargs: Any
) -> BackendState:
"""Simulate the execution of the pattern by using :class:`graphix.simulator.PatternSimulator`.

Expand Down Expand Up @@ -1644,7 +1645,7 @@ def measure_pauli(pattern: Pattern, leave_input: bool, copy: bool = False) -> Pa
nodes, edges = pattern.get_graph()
vop_init = pattern.get_vops(conj=False)
graph_state = GraphState(nodes=nodes, edges=edges, vops=vop_init)
results: dict[int, int] = {}
results: dict[int, Outcome] = {}
to_measure, non_pauli_meas = pauli_nodes(pattern, leave_input)
if not leave_input and len(list(set(pattern.input_nodes) & {i[0].node for i in to_measure})) > 0:
new_inputs = []
Expand Down Expand Up @@ -1684,7 +1685,7 @@ def measure_pauli(pattern: Pattern, leave_input: bool, copy: bool = False) -> Pa
if basis.sign == Sign.PLUS:
results[pattern_cmd.node] = measure(pattern_cmd.node, choice=0)
else:
results[pattern_cmd.node] = 1 - measure(pattern_cmd.node, choice=1)
results[pattern_cmd.node] = 0 if measure(pattern_cmd.node, choice=1) else 1

# measure (remove) isolated nodes. if they aren't Pauli measurements,
# measuring one of the results with probability of 1 should not occur as was possible above for Pauli measurements,
Expand Down
9 changes: 9 additions & 0 deletions graphix/sim/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
"""Simulation backends."""

from __future__ import annotations

from graphix.sim.base_backend import Backend, BackendState
from graphix.sim.data import Data
from graphix.sim.density_matrix import DensityMatrix
from graphix.sim.statevec import Statevec

__all__ = ["Backend", "BackendState", "Data", "DensityMatrix", "Statevec"]
Loading
Loading