Skip to content

Commit 0e311ee

Browse files
elenbaascrturradojuanboscheroGuyPutsdependabot[bot]
authored
Release 0.3.0 (#443)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: rturrado <rturrado@gmail.com> Co-authored-by: Juan Boschero <juan.boschero@tno.nl> Co-authored-by: Guy Puts <guy.puts@tno.nl> Co-authored-by: Guy Puts <38719377+GuyPuts@users.noreply.github.com> Co-authored-by: Juan Carlos Boschero <juanboschero1998@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: rares1609 <79575613+rares1609@users.noreply.github.com> Co-authored-by: Oancea <rares.oancea@tno.nl>
1 parent 0d321ff commit 0e311ee

24 files changed

+711
-566
lines changed

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
with:
2323
python-version: "3.11"
2424
- name: Install poetry
25-
uses: abatilo/actions-poetry@v3
25+
uses: abatilo/actions-poetry@v4
2626
with:
2727
poetry-version: "1.8.3"
2828

.github/workflows/tests.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
with:
1818
python-version: "3.11"
1919
- name: Install poetry
20-
uses: abatilo/actions-poetry@v3
20+
uses: abatilo/actions-poetry@v4
2121
with:
2222
poetry-version: "1.3.2"
2323
- name: Install tox
@@ -47,7 +47,7 @@ jobs:
4747
with:
4848
python-version: ${{ matrix.python-version }}
4949
- name: Install poetry
50-
uses: abatilo/actions-poetry@v3
50+
uses: abatilo/actions-poetry@v4
5151
with:
5252
poetry-version: "1.3.2"
5353
- name: Install tox

CHANGELOG.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,45 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1010
* **Removed** for now removed features.
1111

1212

13-
## [ 0.2.0 ] - [ xxxx-yy-zz ]
13+
## [ 0.3.0 ] - [ 2025-01-30 ]
1414

1515
### Added
16-
- Restore SGMQ notation for barrier groups in cQASMv1 Exporter.
16+
17+
- `NativeGateValidator` validator pass
18+
19+
### Changed
20+
21+
- Relaxed NumPy version requirement to `>=1.26` for all supported Python versions
22+
23+
### Fixed
24+
25+
- Fixed order of merging Bloch sphere rotations
26+
27+
28+
## [ 0.2.0 ] - [ 2025-01-21 ]
29+
30+
### Added
31+
32+
- `init` non-unitary instruction
33+
- `SWAP` two-qubit unitary instruction
34+
- `barrier` and `wait` control instructions
35+
- `SingleQubitGatesMerger` merger pass
36+
- `SWAP2CNOTDecomposer` decomposer pass
37+
- `CNOT2CZDecomposer` decomposer pass
38+
- `RoutingChecker` routing pass
39+
- Restore SGMQ notation for barrier groups in cQASMv1 Exporter
40+
41+
### Changed
42+
43+
- Importing modules, classes, and functionalities simplified
44+
- `merge_single_qubit_gates` method of `Circuit` class,
45+
changed to general `merge` method that accepts custom merger passes
46+
- libQASM 0.6.9 integrated (updated from 0.6.7)
47+
- Refactor: code base adheres to the PEP8 style guide
48+
- Refactor: instruction library simplified
49+
- Refactor: comment nodes removed from IR
50+
51+
### Fixed
52+
53+
- Bug in ABA-decomposer
54+
- Bug in McKay-decomposer (all single-qubit Clifford gates are verified)

opensquirrel/circuit.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from opensquirrel.ir import IR, Gate
1010
from opensquirrel.passes.decomposer import Decomposer
1111
from opensquirrel.passes.mapper import Mapper
12-
from opensquirrel.passes.merger.general_merger import Merger
13-
from opensquirrel.passes.router.general_router import Router
12+
from opensquirrel.passes.merger import Merger
13+
from opensquirrel.passes.router import Router
14+
from opensquirrel.passes.validator import Validator
1415
from opensquirrel.register_manager import RegisterManager
1516

1617

@@ -87,8 +88,12 @@ def qubit_register_name(self) -> str:
8788
def bit_register_name(self) -> str:
8889
return self.register_manager.get_bit_register_name()
8990

91+
def validate(self, validator: Validator) -> None:
92+
"""Generic validator pass. It applies the given validator to the circuit."""
93+
validator.validate(self.ir)
94+
9095
def route(self, router: Router) -> None:
91-
"""Generic router pass. It applies the given Router to the circuit."""
96+
"""Generic router pass. It applies the given router to the circuit."""
9297
router.route(self.ir)
9398

9499
def merge(self, merger: Merger) -> None:

opensquirrel/ir.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def __init__(self, *axis: AxisLike) -> None:
213213
214214
axis: An ``AxisLike`` to create the axis from.
215215
"""
216-
axis_to_parse = axis[0] if len(axis) == 1 else cast(AxisLike, axis)
216+
axis_to_parse = axis[0] if len(axis) == 1 else cast("AxisLike", axis)
217217
self._value = self.normalize(self.parse(axis_to_parse))
218218

219219
@property
@@ -281,7 +281,7 @@ def __getitem__(self, s: slice, /) -> list[np.float64]: ...
281281

282282
def __getitem__(self, index: int | slice, /) -> np.float64 | list[np.float64]:
283283
"""Get the item at `index`."""
284-
return cast(np.float64, self.value[index])
284+
return cast("np.float64", self.value[index])
285285

286286
def __len__(self) -> int:
287287
"""Length of the axis, which is always 3."""
@@ -519,9 +519,13 @@ def name(self) -> str:
519519
return self.generator.__name__
520520
return "Anonymous gate: " + self.__repr__()
521521

522+
@property
523+
def is_named_gate(self) -> bool:
524+
return not (self.generator is None or self.generator.__name__ is None)
525+
522526
@property
523527
def is_anonymous(self) -> bool:
524-
return self.generator is None
528+
return not self.is_named_gate
525529

526530
@staticmethod
527531
def _check_repeated_qubit_operands(qubits: Sequence[Qubit]) -> bool:
@@ -706,7 +710,7 @@ def named_gate(gate_generator: Callable[..., ControlledGate]) -> Callable[..., C
706710

707711

708712
def named_gate(gate_generator: Callable[..., Gate]) -> Callable[..., Gate]:
709-
return cast(Callable[..., Gate], instruction_decorator(gate_generator))
713+
return cast("Callable[..., Gate]", instruction_decorator(gate_generator))
710714

711715

712716
@overload
@@ -730,7 +734,7 @@ def non_unitary(non_unitary_generator: Callable[..., Wait]) -> Callable[..., Wai
730734

731735

732736
def non_unitary(non_unitary_generator: Callable[..., NonUnitary]) -> Callable[..., NonUnitary]:
733-
return cast(Callable[..., NonUnitary], instruction_decorator(non_unitary_generator))
737+
return cast("Callable[..., NonUnitary]", instruction_decorator(non_unitary_generator))
734738

735739

736740
def compare_gates(g1: Gate, g2: Gate) -> bool:

opensquirrel/parser/libqasm/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def _check_analysis_result(result: Any) -> None:
174174
def _get_gate_f(instruction: cqasm.semantic.GateInstruction) -> Callable[..., Gate]:
175175
gate_name = instruction.gate.name
176176
if gate_name in ["inv", "pow", "ctrl"]:
177-
modified_gate_f = cast(Callable[..., BlochSphereRotation], Parser._get_gate_f(instruction.gate))
177+
modified_gate_f = cast("Callable[..., BlochSphereRotation]", Parser._get_gate_f(instruction.gate))
178178
if gate_name == "inv":
179179
return InverseGateModifier(modified_gate_f)
180180
if gate_name == "pow":

opensquirrel/passes/decomposer/aba_decomposer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ def _set_a_b_c_axes_values(self, axis: AxisLike) -> tuple[Any, Any, Any]:
5252
Returns:
5353
A triplet (a, b, c) where a, b, and c are the values of x, y, and z reordered.
5454
"""
55-
_axis = Axis(axis)
56-
return _axis[self.index_a], _axis[self.index_b], _axis[self._find_unused_index()]
55+
axis_ = Axis(axis)
56+
return axis_[self.index_a], axis_[self.index_b], axis_[self._find_unused_index()]
5757

5858
@staticmethod
5959
def _are_b_and_c_axes_in_negative_octant(b_axis_value: float, c_axis_value: float) -> bool:

opensquirrel/passes/decomposer/cnot_decomposer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def decompose(self, g: Gate) -> list[Gate]:
3838
# See https://threeplusone.com/pubs/on_gates.pdf
3939

4040
# Try special case first, see https://arxiv.org/pdf/quant-ph/9503016.pdf lemma 5.5
41-
controlled_rotation_times_x = general_merger.compose_bloch_sphere_rotations(X(target_qubit), g.target_gate)
41+
controlled_rotation_times_x = general_merger.compose_bloch_sphere_rotations(g.target_gate, X(target_qubit))
4242
theta0_with_x, theta1_with_x, theta2_with_x = ZYZDecomposer().get_decomposition_angles(
4343
controlled_rotation_times_x.axis,
4444
controlled_rotation_times_x.angle,

opensquirrel/passes/exporter/cqasmv1_exporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def _dump_barrier_group(indices: list[int]) -> str:
9191

9292

9393
def _get_barrier_index(line: str) -> int:
94-
barrier_index_match = re.search("\d+", line)
94+
barrier_index_match = re.search(r"\d+", line)
9595
if not barrier_index_match:
9696
msg = "expecting a barrier index but found none"
9797
raise CqasmV1ExporterParseError(msg)

opensquirrel/passes/merger/general_merger.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@
1515

1616
def compose_bloch_sphere_rotations(bsr_a: BlochSphereRotation, bsr_b: BlochSphereRotation) -> BlochSphereRotation:
1717
"""Computes the Bloch sphere rotation resulting from the composition of two Bloch sphere rotations.
18-
The first rotation is applied and then the second.
19-
If the final Bloch sphere rotation is anonymous, we will try to match it to a default gate.
18+
The first rotation (A) is applied and then the second (B):
19+
20+
As separate gates:
21+
A q
22+
B q
23+
24+
A linear operations:
25+
(B * A) q
26+
27+
If the final Bloch sphere rotation is anonymous, we try to match it to a default gate.
2028
2129
Uses Rodrigues' rotation formula (see https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula).
2230
"""
@@ -40,7 +48,7 @@ def compose_bloch_sphere_rotations(bsr_a: BlochSphereRotation, bsr_b: BlochSpher
4048
* (
4149
sin(bsr_a.angle / 2) * cos(bsr_b.angle / 2) * bsr_a.axis.value
4250
+ cos(bsr_a.angle / 2) * sin(bsr_b.angle / 2) * bsr_b.axis.value
43-
+ sin(bsr_a.angle / 2) * sin(bsr_b.angle / 2) * np.cross(bsr_a.axis, bsr_b.axis)
51+
+ sin(bsr_a.angle / 2) * sin(bsr_b.angle / 2) * np.cross(bsr_b.axis, bsr_a.axis)
4452
)
4553
),
4654
order_of_magnitude,
@@ -119,8 +127,8 @@ def can_move_before(statement: Statement, statement_group: list[Statement]) -> b
119127
first_statement_from_group = statement_group[0]
120128
if not isinstance(first_statement_from_group, Barrier):
121129
return False
122-
instruction = cast(Instruction, statement)
123-
return can_move_statement_before_barrier(instruction, cast(list[Instruction], statement_group))
130+
instruction = cast("Instruction", statement)
131+
return can_move_statement_before_barrier(instruction, cast("list[Instruction]", statement_group))
124132

125133

126134
def group_linked_barriers(statements: list[Statement]) -> list[list[Statement]]:

opensquirrel/passes/merger/single_qubit_gates_merger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ def merge(self, ir: IR, qubit_register_size: int) -> None:
2222
statement = ir.statements[statement_index]
2323

2424
# Accumulate consecutive Bloch sphere rotations
25-
instruction: Instruction = cast(Instruction, statement)
25+
instruction: Instruction = cast("Instruction", statement)
2626
if isinstance(instruction, BlochSphereRotation):
2727
already_accumulated = accumulators_per_qubit[instruction.qubit]
28-
composed = compose_bloch_sphere_rotations(instruction, already_accumulated)
28+
composed = compose_bloch_sphere_rotations(already_accumulated, instruction)
2929
accumulators_per_qubit[instruction.qubit] = composed
3030
del ir.statements[statement_index]
3131
continue

opensquirrel/passes/router/routing_checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def __init__(self, connectivity: dict[str, list[int]]) -> None:
1111
def route(self, ir: IR) -> None:
1212
non_executable_interactions = []
1313
for statement in ir.statements:
14-
instruction: Instruction = cast(Instruction, statement)
14+
instruction: Instruction = cast("Instruction", statement)
1515
args = instruction.arguments
1616
if args and len(args) > 1 and all(isinstance(arg, Qubit) for arg in args):
1717
qubit_args = [arg for arg in args if isinstance(arg, Qubit)]
@@ -22,7 +22,7 @@ def route(self, ir: IR) -> None:
2222

2323
if non_executable_interactions:
2424
error_message = (
25-
f"The following qubit interactions in the circuit prevent a 1-to-1 mapping:"
25+
f"the following qubit interactions in the circuit prevent a 1-to-1 mapping:"
2626
f"{set(non_executable_interactions)}"
2727
)
2828
raise ValueError(error_message)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Init file for the validator passes."""
2+
3+
from opensquirrel.passes.validator.general_validator import Validator
4+
from opensquirrel.passes.validator.native_gate_validator import NativeGateValidator
5+
6+
__all__ = ["NativeGateValidator", "Validator"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from abc import ABC, abstractmethod
2+
3+
from opensquirrel.ir import IR
4+
5+
6+
class Validator(ABC):
7+
@abstractmethod
8+
def validate(self, ir: IR) -> None:
9+
"""Base validate method to be implemented by inheriting validator classes."""
10+
raise NotImplementedError
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from opensquirrel.ir import IR, Unitary
2+
from opensquirrel.passes.validator import Validator
3+
4+
5+
class NativeGateValidator(Validator):
6+
def __init__(self, native_gate_set: list[str]) -> None:
7+
self.native_gate_set = native_gate_set
8+
9+
def validate(self, ir: IR) -> None:
10+
"""
11+
Check if all unitary gates in the circuit are part of the native gate set.
12+
13+
Args:
14+
ir (IR): The intermediate representation of the circuit to be checked.
15+
16+
Raises:
17+
ValueError: If any unitary gate in the circuit is not part of the native gate set.
18+
"""
19+
gates_not_in_native_gate_set = [
20+
statement.name
21+
for statement in ir.statements
22+
if isinstance(statement, Unitary) and statement.name not in self.native_gate_set
23+
]
24+
if gates_not_in_native_gate_set:
25+
error_message = f"the following gates are not in the native gate set: {set(gates_not_in_native_gate_set)}"
26+
raise ValueError(error_message)

0 commit comments

Comments
 (0)