Skip to content
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

166 type hinting #173

Merged
merged 34 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 33 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"]
29 changes: 15 additions & 14 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from __future__ import annotations

from typing import Callable, Dict

import numpy as np
from collections.abc import Callable, Mapping
from typing import TYPE_CHECKING, Any, Literal

from opensquirrel.decomposer import general_decomposer
from opensquirrel.decomposer.general_decomposer import Decomposer
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
from opensquirrel.default_measurements import default_measurement_set
from opensquirrel.exporter.export_format import ExportFormat
from opensquirrel.ir import IR, Gate, Measure
from opensquirrel.mapper import IdentityMapper, Mapper
from opensquirrel.mapper import Mapper
from opensquirrel.register_manager import RegisterManager


Expand Down Expand Up @@ -38,7 +37,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 @@ -49,17 +48,19 @@ def __repr__(self) -> str:

return writer.circuit_to_string(self)

def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Circuit):
return False
return self.register_manager == other.register_manager and self.ir == other.ir

@classmethod
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,
rturrado marked this conversation as resolved.
Show resolved Hide resolved
gate_aliases: Mapping[str, Callable[..., Gate]] = default_gate_aliases,
measurement_set: list[Callable[..., Measure]] = default_measurement_set,
) -> Circuit:
"""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 +92,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 +101,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 +113,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:
rturrado marked this conversation as resolved.
Show resolved Hide resolved
"""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
65 changes: 39 additions & 26 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
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
from opensquirrel.default_measurements import default_measurement_set
from opensquirrel.instruction_library import GateLibrary, MeasurementLibrary
from opensquirrel.ir import IR, Comment, Gate, Qubit
from opensquirrel.ir import IR, Comment, Gate, Measure
from opensquirrel.register_manager import RegisterManager

if TYPE_CHECKING:
from typing import Self


class CircuitBuilder(GateLibrary, MeasurementLibrary):
"""
Expand Down Expand Up @@ -41,40 +46,48 @@ 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,
rturrado marked this conversation as resolved.
Show resolved Hide resolved
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.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):
def _add_instruction(self, attr: str, *args: Any) -> Self:
if any(attr == measure.__name__ for measure in self.measurement_set):
generator_f_measure = MeasurementLibrary.get_measurement_f(self, attr)
self._check_generator_f_args(generator_f_measure, attr, args)
self.ir.add_measurement(generator_f_measure(*args))
else:
generator_f_gate = GateLibrary.get_gate_f(self, attr)
self._check_generator_f_args(generator_f_gate, attr, args)
self.ir.add_gate(generator_f_gate(*args))
return self

@staticmethod
def _check_generator_f_args(generator_f: Callable[..., Gate | Measure], attr: str, args: tuple[Any, ...]) -> None:
for i, par in enumerate(inspect.signature(generator_f).parameters.values()):
if isinstance(par.annotation, str):
if args[i].__class__.__name__ != par.annotation:
raise TypeError(
f"Wrong argument type for instruction `{attr}`, got {type(args[i])} but expected"
f" {par.annotation}"
f"Wrong argument type for instruction `{attr}`, got {type(args[i])} but expected {par.annotation}"
)

return add_comment if attr == "comment" else add_instruction
elif 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)
27 changes: 19 additions & 8 deletions opensquirrel/circuit_matrix_calculator.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np
from numpy.typing import NDArray

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

if TYPE_CHECKING:
from opensquirrel.circuit import Circuit


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
5 changes: 3 additions & 2 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]:
rturrado marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(g, ControlledGate):
# Do nothing:
# - BlochSphereRotation's are only single-qubit,
Expand Down
31 changes: 17 additions & 14 deletions opensquirrel/decomposer/general_decomposer.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
from __future__ import annotations

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

from opensquirrel.circuit_matrix_calculator import get_circuit_matrix
from opensquirrel.common import are_matrices_equivalent_up_to_global_phase
from opensquirrel.ir import IR, Gate
from opensquirrel.reindexer import get_reindexed_circuit


class Decomposer(ABC):
@staticmethod

@abstractmethod
def decompose(gate: Gate) -> [Gate]:
def decompose(self, gate: Gate) -> list[Gate]:
rturrado marked this conversation as resolved.
Show resolved Hide resolved
raise NotImplementedError()


def check_gate_replacement(gate: Gate, replacement_gates: List[Gate]):
def check_gate_replacement(gate: Gate, replacement_gates: Iterable[Gate]) -> None:
gate_qubit_indices = [q.index for q in gate.get_qubit_operands()]
replacement_gates_qubit_indices = set()
[replacement_gates_qubit_indices.update([q.index for q in g.get_qubit_operands()]) for g in replacement_gates]
for g in replacement_gates:
replacement_gates_qubit_indices.update([q.index for q in g.get_qubit_operands()])

if set(gate_qubit_indices) != replacement_gates_qubit_indices:
raise ValueError(f"Replacement for gate {gate.name} does not seem to operate on the right qubits")

from opensquirrel.circuit_matrix_calculator import get_circuit_matrix
from opensquirrel.reindexer import get_reindexed_circuit

replaced_matrix = get_circuit_matrix(get_reindexed_circuit([gate], gate_qubit_indices))
replacement_matrix = get_circuit_matrix(get_reindexed_circuit(replacement_gates, gate_qubit_indices))
if not are_matrices_equivalent_up_to_global_phase(replaced_matrix, replacement_matrix):
raise Exception(f"Replacement for gate {gate.name} does not preserve the quantum state")


def decompose(ir: IR, decomposer: Decomposer):
def decompose(ir: IR, decomposer: Decomposer) -> None:
"""Applies `decomposer` to every gate in the circuit, replacing each gate by the output of `decomposer`.
When `decomposer` decides to not decomposer a gate, it needs to return a list with the intact gate as single
element.
Expand All @@ -43,25 +45,26 @@ def decompose(ir: IR, decomposer: Decomposer):
continue

gate = statement
replacement_gates: List[Gate] = decomposer.decompose(statement)
replacement_gates: list[Gate] = decomposer.decompose(statement)
check_gate_replacement(gate, replacement_gates)

ir.statements[statement_index : statement_index + 1] = replacement_gates
statement_index += len(replacement_gates)


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:
rturrado marked this conversation as resolved.
Show resolved Hide resolved
self.gate_generator = gate_generator
self.replacement_function = replacement_function

def decompose(self, g: Gate) -> [Gate]:
def decompose(self, g: Gate) -> list[Gate]:
rturrado marked this conversation as resolved.
Show resolved Hide resolved
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]]) -> None:
"""Does the same as decomposer, but only applies to a given gate."""
generic_replacer = _GenericReplacer(gate_generator, f)

Expand Down
Loading
Loading