Skip to content

Commit

Permalink
Release 0.3.0 (#443)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: rturrado <[email protected]>
Co-authored-by: Juan Boschero <[email protected]>
Co-authored-by: Guy Puts <[email protected]>
Co-authored-by: Guy Puts <[email protected]>
Co-authored-by: Juan Carlos Boschero <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: rares1609 <[email protected]>
Co-authored-by: Oancea <[email protected]>
  • Loading branch information
9 people authored Jan 30, 2025
1 parent 0d321ff commit 0e311ee
Show file tree
Hide file tree
Showing 24 changed files with 711 additions and 566 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
with:
python-version: "3.11"
- name: Install poetry
uses: abatilo/actions-poetry@v3
uses: abatilo/actions-poetry@v4
with:
poetry-version: "1.8.3"

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
with:
python-version: "3.11"
- name: Install poetry
uses: abatilo/actions-poetry@v3
uses: abatilo/actions-poetry@v4
with:
poetry-version: "1.3.2"
- name: Install tox
Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
uses: abatilo/actions-poetry@v3
uses: abatilo/actions-poetry@v4
with:
poetry-version: "1.3.2"
- name: Install tox
Expand Down
42 changes: 40 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,45 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* **Removed** for now removed features.


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

### Added
- Restore SGMQ notation for barrier groups in cQASMv1 Exporter.

- `NativeGateValidator` validator pass

### Changed

- Relaxed NumPy version requirement to `>=1.26` for all supported Python versions

### Fixed

- Fixed order of merging Bloch sphere rotations


## [ 0.2.0 ] - [ 2025-01-21 ]

### Added

- `init` non-unitary instruction
- `SWAP` two-qubit unitary instruction
- `barrier` and `wait` control instructions
- `SingleQubitGatesMerger` merger pass
- `SWAP2CNOTDecomposer` decomposer pass
- `CNOT2CZDecomposer` decomposer pass
- `RoutingChecker` routing pass
- Restore SGMQ notation for barrier groups in cQASMv1 Exporter

### Changed

- Importing modules, classes, and functionalities simplified
- `merge_single_qubit_gates` method of `Circuit` class,
changed to general `merge` method that accepts custom merger passes
- libQASM 0.6.9 integrated (updated from 0.6.7)
- Refactor: code base adheres to the PEP8 style guide
- Refactor: instruction library simplified
- Refactor: comment nodes removed from IR

### Fixed

- Bug in ABA-decomposer
- Bug in McKay-decomposer (all single-qubit Clifford gates are verified)
11 changes: 8 additions & 3 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from opensquirrel.ir import IR, Gate
from opensquirrel.passes.decomposer import Decomposer
from opensquirrel.passes.mapper import Mapper
from opensquirrel.passes.merger.general_merger import Merger
from opensquirrel.passes.router.general_router import Router
from opensquirrel.passes.merger import Merger
from opensquirrel.passes.router import Router
from opensquirrel.passes.validator import Validator
from opensquirrel.register_manager import RegisterManager


Expand Down Expand Up @@ -87,8 +88,12 @@ def qubit_register_name(self) -> str:
def bit_register_name(self) -> str:
return self.register_manager.get_bit_register_name()

def validate(self, validator: Validator) -> None:
"""Generic validator pass. It applies the given validator to the circuit."""
validator.validate(self.ir)

def route(self, router: Router) -> None:
"""Generic router pass. It applies the given Router to the circuit."""
"""Generic router pass. It applies the given router to the circuit."""
router.route(self.ir)

def merge(self, merger: Merger) -> None:
Expand Down
14 changes: 9 additions & 5 deletions opensquirrel/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def __init__(self, *axis: AxisLike) -> None:
axis: An ``AxisLike`` to create the axis from.
"""
axis_to_parse = axis[0] if len(axis) == 1 else cast(AxisLike, axis)
axis_to_parse = axis[0] if len(axis) == 1 else cast("AxisLike", axis)
self._value = self.normalize(self.parse(axis_to_parse))

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

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

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

@property
def is_named_gate(self) -> bool:
return not (self.generator is None or self.generator.__name__ is None)

@property
def is_anonymous(self) -> bool:
return self.generator is None
return not self.is_named_gate

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


def named_gate(gate_generator: Callable[..., Gate]) -> Callable[..., Gate]:
return cast(Callable[..., Gate], instruction_decorator(gate_generator))
return cast("Callable[..., Gate]", instruction_decorator(gate_generator))


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


def non_unitary(non_unitary_generator: Callable[..., NonUnitary]) -> Callable[..., NonUnitary]:
return cast(Callable[..., NonUnitary], instruction_decorator(non_unitary_generator))
return cast("Callable[..., NonUnitary]", instruction_decorator(non_unitary_generator))


def compare_gates(g1: Gate, g2: Gate) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion opensquirrel/parser/libqasm/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _check_analysis_result(result: Any) -> None:
def _get_gate_f(instruction: cqasm.semantic.GateInstruction) -> Callable[..., Gate]:
gate_name = instruction.gate.name
if gate_name in ["inv", "pow", "ctrl"]:
modified_gate_f = cast(Callable[..., BlochSphereRotation], Parser._get_gate_f(instruction.gate))
modified_gate_f = cast("Callable[..., BlochSphereRotation]", Parser._get_gate_f(instruction.gate))
if gate_name == "inv":
return InverseGateModifier(modified_gate_f)
if gate_name == "pow":
Expand Down
4 changes: 2 additions & 2 deletions opensquirrel/passes/decomposer/aba_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ def _set_a_b_c_axes_values(self, axis: AxisLike) -> tuple[Any, Any, Any]:
Returns:
A triplet (a, b, c) where a, b, and c are the values of x, y, and z reordered.
"""
_axis = Axis(axis)
return _axis[self.index_a], _axis[self.index_b], _axis[self._find_unused_index()]
axis_ = Axis(axis)
return axis_[self.index_a], axis_[self.index_b], axis_[self._find_unused_index()]

@staticmethod
def _are_b_and_c_axes_in_negative_octant(b_axis_value: float, c_axis_value: float) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion opensquirrel/passes/decomposer/cnot_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def decompose(self, g: Gate) -> list[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(g.target_gate, X(target_qubit))
theta0_with_x, theta1_with_x, theta2_with_x = ZYZDecomposer().get_decomposition_angles(
controlled_rotation_times_x.axis,
controlled_rotation_times_x.angle,
Expand Down
2 changes: 1 addition & 1 deletion opensquirrel/passes/exporter/cqasmv1_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _dump_barrier_group(indices: list[int]) -> str:


def _get_barrier_index(line: str) -> int:
barrier_index_match = re.search("\d+", line)
barrier_index_match = re.search(r"\d+", line)
if not barrier_index_match:
msg = "expecting a barrier index but found none"
raise CqasmV1ExporterParseError(msg)
Expand Down
18 changes: 13 additions & 5 deletions opensquirrel/passes/merger/general_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@

def compose_bloch_sphere_rotations(bsr_a: BlochSphereRotation, bsr_b: BlochSphereRotation) -> BlochSphereRotation:
"""Computes the Bloch sphere rotation resulting from the composition of two Bloch sphere rotations.
The first rotation is applied and then the second.
If the final Bloch sphere rotation is anonymous, we will try to match it to a default gate.
The first rotation (A) is applied and then the second (B):
As separate gates:
A q
B q
A linear operations:
(B * A) q
If the final Bloch sphere rotation is anonymous, we try to match it to a default gate.
Uses Rodrigues' rotation formula (see https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula).
"""
Expand All @@ -40,7 +48,7 @@ def compose_bloch_sphere_rotations(bsr_a: BlochSphereRotation, bsr_b: BlochSpher
* (
sin(bsr_a.angle / 2) * cos(bsr_b.angle / 2) * bsr_a.axis.value
+ cos(bsr_a.angle / 2) * sin(bsr_b.angle / 2) * bsr_b.axis.value
+ sin(bsr_a.angle / 2) * sin(bsr_b.angle / 2) * np.cross(bsr_a.axis, bsr_b.axis)
+ sin(bsr_a.angle / 2) * sin(bsr_b.angle / 2) * np.cross(bsr_b.axis, bsr_a.axis)
)
),
order_of_magnitude,
Expand Down Expand Up @@ -119,8 +127,8 @@ def can_move_before(statement: Statement, statement_group: list[Statement]) -> b
first_statement_from_group = statement_group[0]
if not isinstance(first_statement_from_group, Barrier):
return False
instruction = cast(Instruction, statement)
return can_move_statement_before_barrier(instruction, cast(list[Instruction], statement_group))
instruction = cast("Instruction", statement)
return can_move_statement_before_barrier(instruction, cast("list[Instruction]", statement_group))


def group_linked_barriers(statements: list[Statement]) -> list[list[Statement]]:
Expand Down
4 changes: 2 additions & 2 deletions opensquirrel/passes/merger/single_qubit_gates_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def merge(self, ir: IR, qubit_register_size: int) -> None:
statement = ir.statements[statement_index]

# Accumulate consecutive Bloch sphere rotations
instruction: Instruction = cast(Instruction, statement)
instruction: Instruction = cast("Instruction", statement)
if isinstance(instruction, BlochSphereRotation):
already_accumulated = accumulators_per_qubit[instruction.qubit]
composed = compose_bloch_sphere_rotations(instruction, already_accumulated)
composed = compose_bloch_sphere_rotations(already_accumulated, instruction)
accumulators_per_qubit[instruction.qubit] = composed
del ir.statements[statement_index]
continue
Expand Down
4 changes: 2 additions & 2 deletions opensquirrel/passes/router/routing_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def __init__(self, connectivity: dict[str, list[int]]) -> None:
def route(self, ir: IR) -> None:
non_executable_interactions = []
for statement in ir.statements:
instruction: Instruction = cast(Instruction, statement)
instruction: Instruction = cast("Instruction", statement)
args = instruction.arguments
if args and len(args) > 1 and all(isinstance(arg, Qubit) for arg in args):
qubit_args = [arg for arg in args if isinstance(arg, Qubit)]
Expand All @@ -22,7 +22,7 @@ def route(self, ir: IR) -> None:

if non_executable_interactions:
error_message = (
f"The following qubit interactions in the circuit prevent a 1-to-1 mapping:"
f"the following qubit interactions in the circuit prevent a 1-to-1 mapping:"
f"{set(non_executable_interactions)}"
)
raise ValueError(error_message)
6 changes: 6 additions & 0 deletions opensquirrel/passes/validator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Init file for the validator passes."""

from opensquirrel.passes.validator.general_validator import Validator
from opensquirrel.passes.validator.native_gate_validator import NativeGateValidator

__all__ = ["NativeGateValidator", "Validator"]
10 changes: 10 additions & 0 deletions opensquirrel/passes/validator/general_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from abc import ABC, abstractmethod

from opensquirrel.ir import IR


class Validator(ABC):
@abstractmethod
def validate(self, ir: IR) -> None:
"""Base validate method to be implemented by inheriting validator classes."""
raise NotImplementedError
26 changes: 26 additions & 0 deletions opensquirrel/passes/validator/native_gate_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from opensquirrel.ir import IR, Unitary
from opensquirrel.passes.validator import Validator


class NativeGateValidator(Validator):
def __init__(self, native_gate_set: list[str]) -> None:
self.native_gate_set = native_gate_set

def validate(self, ir: IR) -> None:
"""
Check if all unitary gates in the circuit are part of the native gate set.
Args:
ir (IR): The intermediate representation of the circuit to be checked.
Raises:
ValueError: If any unitary gate in the circuit is not part of the native gate set.
"""
gates_not_in_native_gate_set = [
statement.name
for statement in ir.statements
if isinstance(statement, Unitary) and statement.name not in self.native_gate_set
]
if gates_not_in_native_gate_set:
error_message = f"the following gates are not in the native gate set: {set(gates_not_in_native_gate_set)}"
raise ValueError(error_message)
Loading

0 comments on commit 0e311ee

Please sign in to comment.