Skip to content

Add ABADecomposer class. #225

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 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
6a86c83
Bump mkdocs-material from 9.5.23 to 9.5.25
dependabot[bot] May 27, 2024
be52b7c
Bump coverage from 7.5.1 to 7.5.3
dependabot[bot] May 28, 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
b35ecad
Add CONTRIBUTING.md.
rturrado Jun 3, 2024
b36fba7
Update CONTRIBUTING.md.
rturrado Jun 3, 2024
e1e671e
Update CONTRIBUTING.md.
rturrado 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
5c286b8
Merge pull request #212 from QuTech-Delft/211-doc-add-contributing-md
rturrado Jun 4, 2024
1294aef
Update CONTRIBUTING.md.
rturrado Jun 4, 2024
79f78e5
Update CONTRIBUTING.md.
rturrado Jun 4, 2024
e03617d
Merge pull request #204 from QuTech-Delft/dependabot/pip/coverage-7.5.3
rturrado Jun 4, 2024
08d31cd
Merge pull request #173 from QuTech-Delft/166-type-hinting
S-Linde Jun 4, 2024
35ffdad
Merge pull request #202 from QuTech-Delft/dependabot/pip/mkdocs-mater…
rturrado Jun 4, 2024
0a28527
Merge pull request #216 from QuTech-Delft/update-contributing-md
rturrado Jun 4, 2024
7aa59ef
Bump pylint from 3.1.0 to 3.2.2
dependabot[bot] Jun 4, 2024
932cbef
Update tests decomposer
S-Linde Jun 4, 2024
f832646
Update tests mapper
S-Linde Jun 4, 2024
f3b04a1
Update tests merger
S-Linde Jun 4, 2024
78e3692
Update tests parser
S-Linde Jun 4, 2024
0f60993
Update tests reindexer
S-Linde Jun 4, 2024
60bc0f0
Update tests utils
S-Linde Jun 4, 2024
d4ebc9e
Update tests
S-Linde Jun 4, 2024
0ba419e
Compress matrix calculator tests
S-Linde Jun 4, 2024
8f52f94
Update integration tests
S-Linde Jun 4, 2024
0af6a7f
Update tests replacer
S-Linde Jun 4, 2024
f5a8e54
Update tests zyz decomposer
S-Linde Jun 4, 2024
2eb9109
Merge pull request #194 from QuTech-Delft/dependabot/pip/pylint-3.2.2
rturrado Jun 4, 2024
94e9156
Update tests zyz decomposer
S-Linde Jun 4, 2024
ac9ecf0
Update tests
S-Linde Jun 4, 2024
c8a5d05
Bump pytest from 8.2.0 to 8.2.2
dependabot[bot] Jun 4, 2024
3be6252
Python38 compatibility
S-Linde Jun 5, 2024
c235abe
Merge pull request #218 from QuTech-Delft/dependabot/pip/pytest-8.2.2
rturrado Jun 5, 2024
273325d
Merge pull request #217 from QuTech-Delft/215-unittest_to_pytest
S-Linde Jun 5, 2024
25ca575
Remove annotation check at runtime
S-Linde Jun 10, 2024
8bd1bb0
Merge pull request #222 from QuTech-Delft/219_type_annotations_at_run…
S-Linde Jun 11, 2024
957ce82
Add ABADecomposer class.
rturrado Jun 12, 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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Install tox
run: pip install tox
- name: run tox lint and type
run: tox -e lint #,type
run: tox -e lint,type
unit-test:
name: Unit testing
needs: lint
Expand Down
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
68 changes: 68 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
We recommend working on a feature branch and pull request from there.

## Requirements

- `poetry`,
- `PyCharm` (recommended).

## Creating a feature branch

Make sure your environment contains all the updated versions of the dependencies.

From an OpenSquirrel checkout:

```
$ poetry shell
$ poetry install
```

And that you base your feature branch off an updated `develop`.

From a `poetry` shell (started from an OpenSquirrel checkout):

```
$ git checkout develop
$ git fetch origin
$ git pull
$ git branch <feature branch name>
```

## Before creating the pull request

Make sure the tests and the following linters pass.
From a `poetry` shell (started from an OpenSquirrel checkout):

```
$ pytest -vv
$ mypy . --strict
$ poetry run isort .
$ poetry run black .
```

## Setting the Python interpreter (PyCharm)

You can choose the Python interpreter from the `poetry` environment.

- Go to `Settings` > `Project: OpenSquirrel` > `Python Interpreter`.
- Click on `Add Interpeter`, and then select `Add Local Interpreter`.
- Select `Poetry Environment`, and then `Existing environment`.
- Click on `...` to navigate to the `Interpreter` binary.

## Running/Debugging tests (PyCharm)

To run/debug all tests:

- Right-click on the `test` folder of the Project tree.
- Click `Run 'pytest' in test` or `Debug 'pytest' in test`.

This will also create a `Run/Debug Configuration`.

### Troubleshooting

If breakpoints are not hit during debugging:

- Go to `Run/Debug Configurations`.
- Add `--no-cov` in the `Additional arguments` text box.

This issue may be due to the code coverage module _hijacking_ the tracing mechanism
(check [this link](https://stackoverflow.com/a/56235965/260313) for a more detailed explanation).
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,
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:
"""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,
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
Loading
Loading