Skip to content

166 type hinting #173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8fa8120
Happefy mypy
S-Linde Apr 29, 2024
20c5408
set mypy to strict
S-Linde Apr 29, 2024
7060b54
Add type hints for squirrel_ir
S-Linde Apr 29, 2024
76a97ce
Add type hints for instruction_library
S-Linde Apr 29, 2024
181c72d
Add type hints for commin
S-Linde Apr 29, 2024
a16d636
fix tests
S-Linde Apr 30, 2024
67100de
type hinting circuit
S-Linde Apr 30, 2024
864b745
type hinting circuit
S-Linde Apr 30, 2024
78b3244
type hinting circuit_matrix_calculator
S-Linde Apr 30, 2024
32a3928
type hint decomposer
S-Linde Apr 30, 2024
434a397
Type hints utils
S-Linde Apr 30, 2024
f940127
Type hint merger
S-Linde Apr 30, 2024
430a516
type hint exporter
S-Linde Apr 30, 2024
ef9eda4
type hint parser
S-Linde Apr 30, 2024
62e1204
Type hint common
S-Linde Apr 30, 2024
9b82522
Modernize type hints
S-Linde May 1, 2024
960d86d
Implement suggestion
S-Linde May 27, 2024
a0f46db
resolve merge conflits
S-Linde May 31, 2024
0668e95
Fix unmergerd errors
S-Linde Jun 3, 2024
4beaedc
Fix tests
S-Linde Jun 3, 2024
762dd7a
fix type hints mapper
S-Linde Jun 3, 2024
dfcecf7
fix type hints merger
S-Linde Jun 3, 2024
a25c92c
Fix type hints reindexer
S-Linde Jun 3, 2024
d349265
Fix type hints IR
S-Linde Jun 3, 2024
41d7913
Fix type hints default_gates
S-Linde Jun 3, 2024
f698a25
Fix type hints decomposer
S-Linde Jun 3, 2024
4e3013e
Fix type hints utils
S-Linde Jun 3, 2024
c30341e
Fix type hints circuit_builder
S-Linde Jun 3, 2024
2810ba0
Fix import issues
S-Linde Jun 3, 2024
fd301e6
Fix type hints exporter
S-Linde Jun 4, 2024
c55893e
fix type hints register manager
S-Linde Jun 4, 2024
2a476cd
Add type hints parser
S-Linde Jun 4, 2024
00fddda
fix type hints circuit
S-Linde Jun 4, 2024
c616c01
Add mypy to github actions
S-Linde Jun 4, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ venv/
ENV/
env.bak/
venv.bak/
venv38/

# Spyder project settings
.spyderproject
Expand Down
2 changes: 2 additions & 0 deletions opensquirrel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from opensquirrel.circuit import Circuit
from opensquirrel.circuit_builder import CircuitBuilder
from opensquirrel.default_gates import default_gate_aliases

__all__ = ["Circuit", "CircuitBuilder", "default_gate_aliases"]
25 changes: 15 additions & 10 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

from typing import Callable, Dict
from collections.abc import Callable, Mapping
from typing import TYPE_CHECKING, Any, Literal

import numpy as np
from numpy.typing import NDArray

from opensquirrel.decomposer import general_decomposer
from opensquirrel.decomposer.general_decomposer import Decomposer
Expand All @@ -13,6 +15,9 @@
from opensquirrel.mapper import IdentityMapper, Mapper
from opensquirrel.register_manager import RegisterManager

if TYPE_CHECKING:
from typing import Self


class Circuit:
"""The Circuit class is the only interface to access OpenSquirrel's features.
Expand All @@ -38,7 +43,7 @@ class Circuit:
<BLANKLINE>
"""

def __init__(self, register_manager: RegisterManager, ir: IR):
def __init__(self, register_manager: RegisterManager, ir: IR) -> None:
"""Create a circuit object from a register manager and an IR."""
self.register_manager = register_manager
self.ir = ir
Expand All @@ -56,10 +61,10 @@ def __eq__(self, other):
def from_string(
cls,
cqasm3_string: str,
gate_set: [Callable[..., Gate]] = default_gate_set,
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
measurement_set: [Callable[..., Measure]] = default_measurement_set,
):
gate_set: list[Callable[..., Gate]] = default_gate_set,
gate_aliases: Mapping[str, Callable[..., Gate]] = default_gate_aliases,
measurement_set: list[Callable[..., Measure]] = default_measurement_set,
) -> Self:
"""Create a circuit object from a cQasm3 string. All the gates in the circuit need to be defined in
the `gates` argument.

Expand Down Expand Up @@ -91,7 +96,7 @@ def qubit_register_size(self) -> int:
def qubit_register_name(self) -> str:
return self.register_manager.qubit_register_name

def merge_single_qubit_gates(self):
def merge_single_qubit_gates(self) -> None:
"""Merge all consecutive 1-qubit gates in the circuit.

Gates obtained from merging other gates become anonymous gates.
Expand All @@ -100,7 +105,7 @@ def merge_single_qubit_gates(self):

general_merger.merge_single_qubit_gates(self)

def decompose(self, decomposer: Decomposer):
def decompose(self, decomposer: Decomposer) -> None:
"""Generic decomposition pass. It applies the given decomposer function to every gate in the circuit."""
general_decomposer.decompose(self.ir, decomposer)

Expand All @@ -112,14 +117,14 @@ def map(self, mapper: Mapper) -> None:

remap_ir(self, mapper.get_mapping())

def replace(self, gate_generator: Callable[..., Gate], f):
def replace(self, gate_generator: Callable[..., Gate], f: Callable[..., list[Gate]]) -> None:
"""Manually replace occurrences of a given gate with a list of gates.
`f` is a callable that takes the arguments of the gate that is to be replaced
and returns the decomposition as a list of gates.
"""
general_decomposer.replace(self.ir, gate_generator, f)

def export(self, fmt: ExportFormat = None) -> None:
def export(self, fmt: Literal[ExportFormat.QUANTIFY_SCHEDULER] | None = None) -> Any:
if fmt == ExportFormat.QUANTIFY_SCHEDULER:
from opensquirrel.exporter import quantify_scheduler_exporter

Expand Down
60 changes: 34 additions & 26 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import inspect
from typing import Callable, Dict
from collections.abc import Callable, Mapping
from functools import partial
from typing import TYPE_CHECKING, Any

from opensquirrel.circuit import Circuit
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
Expand All @@ -10,6 +12,9 @@
from opensquirrel.ir import IR, Comment, Gate, Qubit
from opensquirrel.register_manager import RegisterManager

if TYPE_CHECKING:
from typing import Self


class CircuitBuilder(GateLibrary, MeasurementLibrary):
"""
Expand Down Expand Up @@ -41,40 +46,43 @@ class CircuitBuilder(GateLibrary, MeasurementLibrary):
def __init__(
self,
qubit_register_size: int,
gate_set: [Callable[..., Gate]] = default_gate_set,
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
measurement_set: List[Callable[..., Measure]] = default_measurement_set,
gate_set: list[Callable[..., Gate]] = default_gate_set,
gate_aliases: Mapping[str, Callable[..., Gate]] = default_gate_aliases,
measurement_set: list[Callable[..., Measure]] = default_measurement_set,
):
GateLibrary.__init__(self, gate_set, gate_aliases)
MeasurementLibrary.__init__(self, measurement_set)
self.register_manager = RegisterManager(qubit_register_size)
self.ir = IR()

def __getattr__(self, attr):
def add_comment(comment_string: str) -> CircuitBuilder:
self.ir.add_comment(Comment(comment_string))
return self
def __getattr__(self, attr: Any) -> Callable[..., Self]:
if attr == "comment":
return self._add_comment

return partial(self._add_instruction, attr)

def add_instruction(*args: tuple) -> CircuitBuilder:
if any(attr == measure.__name__ for measure in self.measurement_set):
generator_f = MeasurementLibrary.get_measurement_f(self, attr)
_check_generator_f_args(generator_f, args)
self.ir.add_measurement(generator_f(*args))
else:
generator_f = GateLibrary.get_gate_f(self, attr)
_check_generator_f_args(generator_f, args)
self.ir.add_gate(generator_f(*args))
return self
def _add_comment(self, comment_string: str) -> Self:
self.squirrel_ir.add_comment(Comment(comment_string))
return self

def _check_generator_f_args(generator_f, args):
for i, par in enumerate(inspect.signature(generator_f).parameters.values()):
if not isinstance(args[i], par.annotation):
raise TypeError(
f"Wrong argument type for instruction `{attr}`, got {type(args[i])} but expected"
f" {par.annotation}"
)
def _add_instruction(self, attr: str, *args: Any) -> Self:
if any(attr == measure.__name__ for measure in self.measurement_set):
generator_f = MeasurementLibrary.get_measurement_f(self, attr)
self._check_generator_f_args(generator_f, attr, args)
self.ir.add_measurement(generator_f(*args))
else:
generator_f = GateLibrary.get_gate_f(self, attr)
self._check_generator_f_args(generator_f, args)
self.ir.add_gate(generator_f(*args))
return self

return add_comment if attr == "comment" else add_instruction
@staticmethod
def _check_generator_f_args(generator_f, attr, args) -> None:
for i, par in enumerate(inspect.signature(generator_f).parameters.values()):
if not isinstance(args[i], par.annotation):
raise TypeError(
f"Wrong argument type for instruction `{attr}`, got {type(args[i])} but expected {par.annotation}"
)

def to_circuit(self) -> Circuit:
return Circuit(self.register_manager, self.ir)
17 changes: 11 additions & 6 deletions opensquirrel/circuit_matrix_calculator.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
import numpy as np
from numpy.typing import NDArray

from opensquirrel.circuit import Circuit
from opensquirrel.ir import IR, Comment, Gate, IRVisitor
from opensquirrel.utils.matrix_expander import get_matrix


class _CircuitMatrixCalculator(IRVisitor):
def __init__(self, qubit_register_size):
def __init__(self, qubit_register_size: int) -> None:
self.qubit_register_size = qubit_register_size
self.matrix = np.eye(1 << self.qubit_register_size, dtype=np.complex128)

def visit_gate(self, gate: Gate):
def visit_gate(self, gate: Gate) -> None:
big_matrix = get_matrix(gate, qubit_register_size=self.qubit_register_size)
self.matrix = big_matrix @ self.matrix

def visit_comment(self, comment: Comment):
def visit_comment(self, comment: Comment) -> None:
pass


def get_circuit_matrix(circuit: Circuit):
def get_circuit_matrix(circuit: Circuit) -> NDArray[np.complex128]:
"""Compute the (large) unitary matrix corresponding to the circuit.
This matrix has 4**n elements, where n is the number of qubits.
Result is stored as a numpy array of complex numbers.

This matrix has 4**n elements, where n is the number of qubits. Result is stored as a numpy array of complex
numbers.

Returns:
Matrix representation of the circuit.
"""
impl = _CircuitMatrixCalculator(circuit.qubit_register_size)

Expand Down
14 changes: 9 additions & 5 deletions opensquirrel/common.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from __future__ import annotations

import cmath
import math
from typing import Tuple
from collections.abc import Iterable

import numpy as np
from numpy.typing import ArrayLike, NDArray

ATOL = 0.0000001

Expand All @@ -16,7 +19,8 @@ def normalize_angle(x: float) -> float:
return t


def normalize_axis(axis: np.ndarray):
def normalize_axis(axis: ArrayLike) -> NDArray[np.float_]:
axis = np.asarray(axis, dtype=np.float_)
norm = np.linalg.norm(axis)
axis /= norm
return axis
Expand All @@ -27,7 +31,7 @@ def normalize_axis(axis: np.ndarray):
Z = np.array([[1, 0], [0, -1]])


def can1(axis: Tuple[float, float, float], angle: float, phase: float = 0) -> np.ndarray:
def can1(axis: Iterable[float], angle: float, phase: float = 0) -> NDArray[np.complex_]:
nx, ny, nz = axis
norm = math.sqrt(nx**2 + ny**2 + nz**2)
assert norm > 0.00000001
Expand All @@ -40,10 +44,10 @@ def can1(axis: Tuple[float, float, float], angle: float, phase: float = 0) -> np
math.cos(angle / 2) * np.identity(2) - 1j * math.sin(angle / 2) * (nx * X + ny * Y + nz * Z)
)

return result
return np.asarray(result, dtype=np.complex_)


def are_matrices_equivalent_up_to_global_phase(matrix_a: np.ndarray, matrix_b: np.ndarray) -> bool:
def are_matrices_equivalent_up_to_global_phase(matrix_a: NDArray[np.complex_], matrix_b: NDArray[np.complex_]) -> bool:
first_non_zero = next(
(i, j) for i in range(matrix_a.shape[0]) for j in range(matrix_a.shape[1]) if abs(matrix_a[i, j]) > ATOL
)
Expand Down
7 changes: 4 additions & 3 deletions opensquirrel/decomposer/cnot_decomposer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import math

from opensquirrel.common import ATOL
Expand All @@ -17,8 +19,7 @@ class CNOTDecomposer(Decomposer):
Source of the math: https://threeplusone.com/pubs/on_gates.pdf, chapter 7.5 "ABC decomposition"
"""

@staticmethod
def decompose(g: Gate) -> [Gate]:
def decompose(self, g: Gate) -> list[Gate]:
if not isinstance(g, ControlledGate):
# Do nothing:
# - BlochSphereRotation's are only single-qubit,
Expand All @@ -37,7 +38,7 @@ def decompose(g: Gate) -> [Gate]:
# See https://threeplusone.com/pubs/on_gates.pdf

# Try special case first, see https://arxiv.org/pdf/quant-ph/9503016.pdf lemma 5.5
controlled_rotation_times_x = general_merger.compose_bloch_sphere_rotations(X(target_qubit), g.target_gate)
controlled_rotation_times_x = general_merger.compose_bloch_sphere_rotations(X(target_qubit), g.target_gate) # type: ignore[arg-type]
theta0_with_x, theta1_with_x, theta2_with_x = get_zyz_decomposition_angles(
controlled_rotation_times_x.angle, controlled_rotation_times_x.axis
)
Expand Down
17 changes: 10 additions & 7 deletions opensquirrel/decomposer/general_decomposer.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Callable, List
from collections.abc import Callable, Iterable

from opensquirrel.common import are_matrices_equivalent_up_to_global_phase
from opensquirrel.ir import IR, Gate


class Decomposer(ABC):
@staticmethod

@abstractmethod
def decompose(gate: Gate) -> [Gate]:
def decompose(self, gate: Gate) -> list[Gate]:
raise NotImplementedError()


Expand Down Expand Up @@ -51,17 +53,18 @@ def decompose(ir: IR, decomposer: Decomposer):


class _GenericReplacer(Decomposer):
def __init__(self, gate_generator: Callable[..., Gate], replacement_function):
def __init__(self, gate_generator: Callable[..., Gate], replacement_function: Callable[..., list[Gate]]) -> None:
self.gate_generator = gate_generator
self.replacement_function = replacement_function

def decompose(self, g: Gate) -> [Gate]:
def decompose(self, g: Gate) -> list[Gate]:
if g.is_anonymous or g.generator != self.gate_generator:
return [g]
return self.replacement_function(*g.arguments)
arguments = tuple() if g.arguments is None else g.arguments
return self.replacement_function(*arguments)


def replace(ir: IR, gate_generator: Callable[..., Gate], f):
def replace(ir: IR, gate_generator: Callable[..., Gate], f: Callable[..., list[Gate]]):
"""Does the same as decomposer, but only applies to a given gate."""
generic_replacer = _GenericReplacer(gate_generator, f)

Expand Down
6 changes: 4 additions & 2 deletions opensquirrel/decomposer/mckay_decomposer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from math import atan2, cos, pi, sin, sqrt

from opensquirrel.common import ATOL, normalize_angle
Expand All @@ -7,8 +9,8 @@


class McKayDecomposer(Decomposer):
@staticmethod
def decompose(g: Gate) -> [Gate]:

def decompose(self, g: Gate) -> list[Gate]:
"""Return the McKay decomposition of a 1-qubit gate as a list of gates.
gate ----> Rz.Rx(pi/2).Rz.Rx(pi/2).Rz

Expand Down
10 changes: 6 additions & 4 deletions opensquirrel/decomposer/zyz_decomposer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import math
from typing import Tuple
from collections.abc import Iterable

from opensquirrel.common import ATOL
from opensquirrel.decomposer.general_decomposer import Decomposer
Expand All @@ -8,7 +10,7 @@
from opensquirrel.utils.identity_filter import filter_out_identities


def get_zyz_decomposition_angles(alpha: float, axis: Tuple[float, float, float]) -> Tuple[float, float, float]:
def get_zyz_decomposition_angles(alpha: float, axis: Iterable[float]) -> tuple[float, float, float]:
"""
Gives the angles used in the Z-Y-Z decomposition of the Bloch sphere rotation
characterized by a rotation around `axis` of angle `alpha`.
Expand All @@ -31,6 +33,7 @@ def get_zyz_decomposition_angles(alpha: float, axis: Tuple[float, float, float])
if abs(alpha - math.pi) < ATOL:
# alpha == pi, math.tan(alpha / 2) is not defined.

p: float
if abs(nz) < ATOL:
theta2 = math.pi
p = 0
Expand Down Expand Up @@ -74,8 +77,7 @@ def get_zyz_decomposition_angles(alpha: float, axis: Tuple[float, float, float])


class ZYZDecomposer(Decomposer):
@staticmethod
def decompose(g: Gate) -> [Gate]:
def decompose(self, g: Gate) -> list[Gate]:
if not isinstance(g, BlochSphereRotation):
# Only decomposer single-qubit gates.
return [g]
Expand Down
Loading
Loading