Skip to content

[CQT-293] Remove generators #412

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 13 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
8 changes: 4 additions & 4 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ _Output_:

qubit[1] q

Anonymous gate: BlochSphereRotation(Qubit[0], axis=[1. 0. 0.], angle=3.14159, phase=0.0)
BlochSphereRotation(qubit=Qubit[0], axis=[1. 0. 0.], angle=3.14159, phase=0.0)

In the above example, OpenSquirrel has merged all the Rx gates together.
Yet, for now, OpenSquirrel does not recognize that this results in a single Rx
Expand Down Expand Up @@ -435,9 +435,9 @@ print(ZYZDecomposer().decompose(H(0)))
```
_Output_:

[BlochSphereRotation(Qubit[0], axis=Axis[0. 0. 1.], angle=1.5707963267948966, phase=0.0),
BlochSphereRotation(Qubit[0], axis=Axis[0. 1. 0.], angle=1.5707963267948966, phase=0.0),
BlochSphereRotation(Qubit[0], axis=Axis[0. 0. 1.], angle=1.5707963267948966, phase=0.0)]
[BlochSphereRotation(qubit=Qubit[0], axis=Axis[0. 0. 1.], angle=1.5707963267948966, phase=0.0),
BlochSphereRotation(qubit=Qubit[0], axis=Axis[0. 1. 0.], angle=1.5707963267948966, phase=0.0),
BlochSphereRotation(qubit=Qubit[0], axis=Axis[0. 0. 1.], angle=1.5707963267948966, phase=0.0)]


## Exporting a circuit
Expand Down
38 changes: 19 additions & 19 deletions opensquirrel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
from opensquirrel.circuit import Circuit
from opensquirrel.circuit_builder import CircuitBuilder
from opensquirrel.default_instructions import (
from opensquirrel.ir import (
CNOT,
CR,
CZ,
SWAP,
X90,
Y90,
Barrier,
CRk,
H,
I,
Init,
Measure,
MinusX90,
MinusY90,
Reset,
Rx,
Ry,
Rz,
S,
Sdag,
SDagger,
T,
Tdag,
TDagger,
Wait,
X,
Y,
Z,
barrier,
init,
measure,
mX90,
mY90,
reset,
wait,
)
from opensquirrel.writer import writer

Expand All @@ -37,27 +37,27 @@
"SWAP",
"X90",
"Y90",
"Barrier",
"CRk",
"Circuit",
"CircuitBuilder",
"H",
"I",
"Init",
"Measure",
"MinusX90",
"MinusY90",
"Reset",
"Rx",
"Ry",
"Rz",
"S",
"Sdag",
"SDagger",
"T",
"Tdag",
"TDagger",
"Wait",
"X",
"Y",
"Z",
"barrier",
"init",
"mX90",
"mY90",
"measure",
"reset",
"wait",
"writer",
]
6 changes: 3 additions & 3 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,14 @@ def map(self, mapper: Mapper) -> None:

remap_ir(self, mapper.get_mapping())

def replace(self, gate_generator: Callable[..., Gate], f: Callable[..., list[Gate]]) -> None:
def replace(self, gate: type[Gate], replacement_gates_function: 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
`replacement_gates_function` is a callable that takes the arguments of the gate that is to be replaced and
returns the decomposition as a list of gates.
"""
from opensquirrel.passes.decomposer import general_decomposer

general_decomposer.replace(self.ir, gate_generator, f)
general_decomposer.replace(self.ir, gate, replacement_gates_function)

def export(self, fmt: ExportFormat | None = None) -> Any:
if fmt == ExportFormat.QUANTIFY_SCHEDULER:
Expand Down
86 changes: 22 additions & 64 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
from __future__ import annotations

import inspect
from collections.abc import Callable
from copy import deepcopy
from functools import partial
from typing import Any

from typing_extensions import Self

from opensquirrel import instruction_library
from opensquirrel.circuit import Circuit
from opensquirrel.ir import (
ANNOTATIONS_TO_TYPE_MAP,
IR,
Bit,
BitLike,
Instruction,
Qubit,
QubitLike,
is_bit_like_annotation,
is_qubit_like_annotation,
)
from opensquirrel.default_instructions import default_instruction_set
from opensquirrel.ir import IR, Bit, BitLike, Instruction, Qubit, QubitLike
from opensquirrel.register_manager import BitRegister, QubitRegister, RegisterManager


Expand Down Expand Up @@ -56,20 +45,6 @@ def __init__(self, qubit_register_size: int, bit_register_size: int = 0) -> None
def __getattr__(self, attr: Any) -> Callable[..., Self]:
return partial(self._add_instruction, attr)

def _add_instruction(self, attr: str, *args: Any) -> Self:
if attr in instruction_library.gate_set:
generator_f_gate = instruction_library.get_gate_f(attr)
self._check_generator_f_args(generator_f_gate, attr, args)
self.ir.add_gate(generator_f_gate(*args))
elif attr in instruction_library.non_unitary_set:
generator_f_non_unitary = instruction_library.get_non_unitary_f(attr)
self._check_generator_f_args(generator_f_non_unitary, attr, args)
self.ir.add_non_unitary(generator_f_non_unitary(*args))
else:
msg = f"unknown instruction '{attr}'"
raise ValueError(msg)
return self

def _check_qubit_out_of_bounds_access(self, qubit: QubitLike) -> None:
"""Throw error if qubit index is outside the qubit register range.

Expand All @@ -92,44 +67,27 @@ def _check_bit_out_of_bounds_access(self, bit: BitLike) -> None:
msg = "bit index is out of bounds"
raise IndexError(msg)

def _check_generator_f_args(
self,
generator_f: Callable[..., Instruction],
attr: str,
args: tuple[Any, ...],
) -> None:
"""General instruction validation function.
The function checks if each instruction has the proper arguments
and if the qubit and bits are within the register range.
def _check_out_of_bounds_access(self, instruction: Instruction) -> None:
for qubit in instruction.get_qubit_operands():
self._check_qubit_out_of_bounds_access(qubit)
for bit in instruction.get_bit_operands():
self._check_bit_out_of_bounds_access(bit)

Args:
generator_f: Instruction function
attr: Type of instruction
args: Arguments parsed into the function
"""
for i, par in enumerate(inspect.signature(generator_f).parameters.values()):
try:
expected_type = (
ANNOTATIONS_TO_TYPE_MAP[par.annotation] if isinstance(par.annotation, str) else par.annotation
)
except KeyError as e:
msg = "unknown annotation type"
raise TypeError(msg) from e

# Fix for Python 3.9
try:
is_incorrect_type = not isinstance(args[i], expected_type) # type: ignore
except TypeError:
# Expected type is probably a Union, which works differently in Python 3.9
is_incorrect_type = not isinstance(args[i], expected_type.__args__) # type: ignore

if is_incorrect_type:
msg = f"wrong argument type for instruction `{attr}`, got {type(args[i])} but expected {expected_type}"
raise TypeError(msg)
if is_qubit_like_annotation(expected_type):
self._check_qubit_out_of_bounds_access(args[i])
elif is_bit_like_annotation(expected_type):
self._check_bit_out_of_bounds_access(args[i])
def _add_instruction(self, attr: str, *args: Any) -> Self:
if attr not in default_instruction_set:
msg = f"unknown instruction '{attr}'"
raise ValueError(msg)

try:
instruction = default_instruction_set[attr](*args)
except TypeError:
msg = f"trying to build '{attr}' with the wrong number or type of arguments: '{args}'"
raise TypeError(msg) from None

self._check_out_of_bounds_access(instruction)

self.ir.add_statement(instruction)
return self

def to_circuit(self) -> Circuit:
return Circuit(deepcopy(self.register_manager), deepcopy(self.ir))
45 changes: 44 additions & 1 deletion opensquirrel/circuit_matrix_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@
import numpy as np
from numpy.typing import NDArray

from opensquirrel.ir import Gate, IRVisitor
from opensquirrel.ir import (
CNOT,
CR,
CZ,
SWAP,
BlochSphereRotation,
BsrWithAngleParam,
BsrWithoutParams,
ControlledGate,
CRk,
Gate,
IRVisitor,
MatrixGate,
)
from opensquirrel.utils import get_matrix

if TYPE_CHECKING:
Expand All @@ -21,6 +34,36 @@ 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_bloch_sphere_rotation(self, gate: BlochSphereRotation) -> None:
self.visit_gate(gate)

def visit_bsr_without_params(self, gate: BsrWithoutParams) -> None:
self.visit_gate(gate)

def visit_bsr_with_angle_params(self, gate: BsrWithAngleParam) -> None:
self.visit_gate(gate)

def visit_matrix_gate(self, gate: MatrixGate) -> None:
self.visit_gate(gate)

def visit_swap(self, gate: SWAP) -> None:
self.visit_gate(gate)

def visit_controlled_gate(self, gate: ControlledGate) -> None:
self.visit_gate(gate)

def visit_cnot(self, gate: CNOT) -> None:
self.visit_gate(gate)

def visit_cz(self, gate: CZ) -> None:
self.visit_gate(gate)

def visit_cr(self, gate: CR) -> None:
self.visit_gate(gate)

def visit_crk(self, gate: CRk) -> None:
self.visit_gate(gate)


def get_circuit_matrix(circuit: Circuit) -> NDArray[np.complex128]:
"""Compute the (large) unitary matrix corresponding to the circuit.
Expand Down
26 changes: 15 additions & 11 deletions opensquirrel/default_gate_modifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,36 @@ class GateModifier:


class InverseGateModifier(GateModifier):
def __init__(self, generator_f_gate: Callable[..., BlochSphereRotation]) -> None:
self.generator_f_gate = generator_f_gate
def __init__(self, gate_generator: Callable[..., BlochSphereRotation]) -> None:
self.gate_generator = gate_generator

def __call__(self, *args: Any) -> BlochSphereRotation:
gate: BlochSphereRotation = self.generator_f_gate(*args)
gate: BlochSphereRotation = self.gate_generator(*args)
modified_angle = gate.angle * -1
modified_phase = gate.phase * -1
return BlochSphereRotation(qubit=gate.qubit, axis=gate.axis, angle=modified_angle, phase=modified_phase)
return BlochSphereRotation.try_match_replace_with_default(
BlochSphereRotation(qubit=gate.qubit, axis=gate.axis, angle=modified_angle, phase=modified_phase)
)


class PowerGateModifier(GateModifier):
def __init__(self, exponent: SupportsFloat, generator_f_gate: Callable[..., BlochSphereRotation]) -> None:
def __init__(self, exponent: SupportsFloat, gate_generator: Callable[..., BlochSphereRotation]) -> None:
self.exponent = exponent
self.generator_f_gate = generator_f_gate
self.gate_generator = gate_generator

def __call__(self, *args: Any) -> BlochSphereRotation:
gate: BlochSphereRotation = self.generator_f_gate(*args)
gate: BlochSphereRotation = self.gate_generator(*args)
modified_angle = gate.angle * float(self.exponent)
modified_phase = gate.phase * float(self.exponent)
return BlochSphereRotation(qubit=gate.qubit, axis=gate.axis, angle=modified_angle, phase=modified_phase)
return BlochSphereRotation.try_match_replace_with_default(
BlochSphereRotation(qubit=gate.qubit, axis=gate.axis, angle=modified_angle, phase=modified_phase)
)


class ControlGateModifier(GateModifier):
def __init__(self, generator_f_gate: Callable[..., BlochSphereRotation]) -> None:
self.generator_f_gate = generator_f_gate
def __init__(self, gate_generator: Callable[..., BlochSphereRotation]) -> None:
self.gate_generator = gate_generator

def __call__(self, control: QubitLike, *args: Any) -> ControlledGate:
gate: BlochSphereRotation = self.generator_f_gate(*args)
gate: BlochSphereRotation = self.gate_generator(*args)
return ControlledGate(control, gate)
Loading