diff --git a/opensquirrel/default_gate_modifiers.py b/opensquirrel/default_gate_modifiers.py new file mode 100644 index 0000000..f669c18 --- /dev/null +++ b/opensquirrel/default_gate_modifiers.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from abc import ABC +from collections.abc import Callable +from opensquirrel.ir import BlochSphereRotation, ControlledGate, QubitLike +from typing import SupportsInt + + +class GateModifier(ABC): + pass + + +class InverseGateModifier(GateModifier): + def __init__(self, generator_f_gate: Callable[..., BlochSphereRotation]): + self.generator_f_gate = generator_f_gate + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + gate: BlochSphereRotation = self.generator_f_gate(q) + modified_angle = gate.angle * -1 + return BlochSphereRotation(qubit=gate.qubit, axis=gate.axis, angle=modified_angle, phase=gate.phase) + + +class PowerGateModifier(GateModifier): + def __init__(self, exponent: SupportsInt, generator_f_gate: Callable[..., BlochSphereRotation]): + self.exponent = exponent + self.generator_f_gate = generator_f_gate + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + gate: BlochSphereRotation = self.generator_f_gate(q) + modified_angle = gate.angle * self.exponent + return BlochSphereRotation(qubit=gate.qubit, axis=gate.axis, angle=modified_angle, phase=gate.phase) + + +class ControlGateModifier(GateModifier): + def __init__(self, generator_f_gate: Callable[..., BlochSphereRotation]): + self.generator_f_gate = generator_f_gate + + def __call__(self, control: QubitLike, target: QubitLike) -> ControlledGate: + gate: BlochSphereRotation = self.generator_f_gate(target) + return ControlledGate(control, gate) diff --git a/opensquirrel/default_gates.py b/opensquirrel/default_gates.py index b9b8953..0356690 100644 --- a/opensquirrel/default_gates.py +++ b/opensquirrel/default_gates.py @@ -1,117 +1,211 @@ from __future__ import annotations import math -from collections.abc import Callable -from typing import SupportsInt +from typing import Any from opensquirrel.ir import BlochSphereRotation, ControlledGate, Float, Gate, Int, QubitLike, named_gate +class NamedGateFunctor: + def __init__(self, parameter: Any = None): + self.parameter = parameter + + def __call__(self, *args: Any, **kwargs: Any) -> Any: + pass + + @named_gate -def I(q: QubitLike) -> BlochSphereRotation: # noqa: E743 - return BlochSphereRotation.identity(q) +class I(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: # noqa: E743 + return BlochSphereRotation.identity(q) @named_gate -def H(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(1, 0, 1), angle=math.pi, phase=math.pi / 2) +class H(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(1, 0, 1), angle=math.pi, phase=math.pi / 2) @named_gate -def X(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2) +class X(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2) @named_gate -def X90(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi / 2, phase=0) +class X90(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi / 2, phase=0) @named_gate -def mX90(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=-math.pi / 2, phase=-0) +class mX90(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=-math.pi / 2, phase=-0) @named_gate -def Y(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=math.pi, phase=math.pi / 2) +class Y(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=math.pi, phase=math.pi / 2) @named_gate -def Y90(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=math.pi / 2, phase=0) +class Y90(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=math.pi / 2, phase=0) @named_gate -def mY90(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=-math.pi / 2, phase=0) +class mY90(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=-math.pi / 2, phase=0) @named_gate -def Z(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi, phase=math.pi / 2) +class Z(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi, phase=math.pi / 2) @named_gate -def S(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi / 2, phase=0) +class S(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi / 2, phase=0) @named_gate -def Sdag(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=-math.pi / 2, phase=0) +class Sdag: + def __init__(self, parameter: Any = None): + pass + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=-math.pi / 2, phase=0) @named_gate -def T(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi / 4, phase=0) +class T(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi / 4, phase=0) @named_gate -def Tdag(q: QubitLike) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=-math.pi / 4, phase=0) +class Tdag(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=-math.pi / 4, phase=0) @named_gate -def Rx(q: QubitLike, theta: Float) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=theta.value, phase=0) +class Rx(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + self.theta = Float(self.parameter).value + return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=self.theta, phase=0) @named_gate -def Ry(q: QubitLike, theta: Float) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=theta.value, phase=0) +class Ry(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + self.theta = Float(self.parameter).value + return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=self.theta, phase=0) @named_gate -def Rz(q: QubitLike, theta: Float) -> BlochSphereRotation: - return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=theta.value, phase=0) +class Rz(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, q: QubitLike) -> BlochSphereRotation: + self.theta = Float(self.parameter).value + return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=self.theta, phase=0) @named_gate -def CNOT(control: QubitLike, target: QubitLike) -> ControlledGate: - return ControlledGate(control, X(target)) +class CNOT(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, control: QubitLike, target: QubitLike) -> ControlledGate: + return ControlledGate(control, X()(target)) @named_gate -def CZ(control: QubitLike, target: QubitLike) -> ControlledGate: - return ControlledGate(control, Z(target)) +class CZ(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, control: QubitLike, target: QubitLike) -> ControlledGate: + return ControlledGate(control, Z()(target)) @named_gate -def CR(control: QubitLike, target: QubitLike, theta: Float) -> ControlledGate: - return ControlledGate( - control, - BlochSphereRotation(qubit=target, axis=(0, 0, 1), angle=theta.value, phase=theta.value / 2), - ) +class CR(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, control: QubitLike, target: QubitLike) -> ControlledGate: + self.theta = Float(self.parameter).value + return ControlledGate( + control, + BlochSphereRotation(qubit=target, axis=(0, 0, 1), angle=self.theta, phase=self.theta / 2), + ) @named_gate -def CRk(control: QubitLike, target: QubitLike, k: SupportsInt) -> ControlledGate: - theta = 2 * math.pi / (2 ** Int(k).value) - return ControlledGate(control, BlochSphereRotation(qubit=target, axis=(0, 0, 1), angle=theta, phase=theta / 2)) +class CRk(NamedGateFunctor): + def __init__(self, parameter: Any = None): + super().__init__(parameter) + + def __call__(self, control: QubitLike, target: QubitLike) -> ControlledGate: + self.theta = 2 * math.pi / (2 ** Int(self.parameter).value) + return ControlledGate( + control, + BlochSphereRotation(qubit=target, axis=(0, 0, 1), angle=self.theta, phase=self.theta / 2) + ) -default_bloch_sphere_rotations_without_params: list[Callable[[QubitLike], BlochSphereRotation]] +default_bloch_sphere_rotations_without_params: list[type[NamedGateFunctor]] default_bloch_sphere_rotations_without_params = [ I, H, @@ -127,16 +221,14 @@ def CRk(control: QubitLike, target: QubitLike, k: SupportsInt) -> ControlledGate T, Tdag, ] -default_bloch_sphere_rotations: list[ - Callable[[QubitLike], BlochSphereRotation] | Callable[[QubitLike, Float], BlochSphereRotation] -] +default_bloch_sphere_rotations: list[type[NamedGateFunctor]] default_bloch_sphere_rotations = [ *default_bloch_sphere_rotations_without_params, Rx, Ry, Rz, ] -default_gate_set: list[Callable[..., Gate]] +default_gate_set: list[type[NamedGateFunctor]] default_gate_set = [ *default_bloch_sphere_rotations, CNOT, @@ -145,6 +237,7 @@ def CRk(control: QubitLike, target: QubitLike, k: SupportsInt) -> ControlledGate CRk, ] +default_gate_aliases: dict[str, type[NamedGateFunctor]] default_gate_aliases = { "Hadamard": H, "Identity": I, diff --git a/opensquirrel/instruction_library.py b/opensquirrel/instruction_library.py index ed980e3..80a8284 100644 --- a/opensquirrel/instruction_library.py +++ b/opensquirrel/instruction_library.py @@ -1,10 +1,11 @@ from __future__ import annotations from collections.abc import Callable, Iterable, Mapping +from opensquirrel.default_gates import NamedGateFunctor from typing import TYPE_CHECKING if TYPE_CHECKING: - from opensquirrel.ir import Gate, Measure, Reset + from opensquirrel.ir import Float, Gate, Measure, Reset class InstructionLibrary: @@ -14,21 +15,21 @@ class InstructionLibrary: class GateLibrary(InstructionLibrary): def __init__( self, - gate_set: Iterable[Callable[..., Gate]], - gate_aliases: Mapping[str, Callable[..., Gate]], + gate_set: Iterable[NamedGateFunctor], + gate_aliases: Mapping[str, NamedGateFunctor], ) -> None: self.gate_set = gate_set self.gate_aliases = gate_aliases - def get_gate_f(self, gate_name: str) -> Callable[..., Gate]: + def get_gate_f(self, gate_name: str, gate_parameter: Float) -> type[NamedGateFunctor]: try: generator_f = next(f for f in self.gate_set if f.__name__ == gate_name) except StopIteration as exc: if gate_name not in self.gate_aliases: - msg = f"unknown instruction `{gate_name}`" + msg = f"unknown gate `{gate_name}`" raise ValueError(msg) from exc generator_f = self.gate_aliases[gate_name] - return generator_f + return generator_f(gate_parameter) class MeasureLibrary(InstructionLibrary): diff --git a/opensquirrel/ir.py b/opensquirrel/ir.py index 6dfb900..b1bd326 100644 --- a/opensquirrel/ir.py +++ b/opensquirrel/ir.py @@ -70,54 +70,64 @@ class Expression(IRNode, ABC): @dataclass -class Float(Expression): - value: float - - def accept(self, visitor: IRVisitor) -> Any: - return visitor.visit_float(self) - - def __post_init__(self) -> None: - if isinstance(self.value, SupportsFloat): - self.value = float(self.value) - else: - msg = "value must be a float" - raise TypeError(msg) - - -@dataclass(init=False) class Int(Expression): """Integers used for intermediate representation of ``Statement`` arguments. Attributes: value: value of the ``Int`` object. """ - value: int - def __init__(self, value: SupportsInt) -> None: - """Init of the ``Int`` object. + def __int__(self) -> int: + """Cast the ``Int`` object to a built-in Python ``int``. - Args: - value: value of the ``Int`` object. + Returns: + Built-in Python ``int`` representation of the ``Int``. """ - if isinstance(value, SupportsInt): - self.value = int(value) - return - - msg = "value must be an int" - raise TypeError(msg) + return self.value def accept(self, visitor: IRVisitor) -> Any: return visitor.visit_int(self) - def __int__(self) -> int: - """Cast the ``Int`` object to a building python ``int``. + def __post_init__(self) -> None: + if isinstance(self.value, Int): + self.value = self.value.value + elif isinstance(self.value, SupportsInt): + self.value = int(self.value) + else: + msg = "value must be a float" + raise TypeError(msg) + + +@dataclass +class Float(Expression): + """Floats used for intermediate representation of ``Statement`` arguments. + + Attributes: + value: value of the ``Float`` object. + """ + value: float + + def __int__(self) -> float: + """Cast the ``Float`` object to a built-in Python ``float``. Returns: - Building python ``int`` representation of the ``Int``. + Built-in Python ``float`` representation of the ``Float``. """ return self.value + def accept(self, visitor: IRVisitor) -> Any: + return visitor.visit_float(self) + + def __post_init__(self) -> None: + if isinstance(self.value, Float): + self.value = self.value.value + elif isinstance(self.value, SupportsFloat): + self.value = float(self.value) + else: + msg = "value must be a float" + raise TypeError(msg) + @dataclass class Bit(Expression): @@ -539,44 +549,31 @@ def is_identity(self) -> bool: return self.target_gate.is_identity() -@overload -def named_gate(gate_generator: Callable[..., BlochSphereRotation]) -> Callable[..., BlochSphereRotation]: ... +def named_gate(cls): + class Wrapper: + def __init__(self, parameter: Any = None): + self.named_gate_functor = cls(parameter) + def call_single_qubit_gate(self, q: QubitLike) -> BlochSphereRotation: + return self.named_gate_functor(q) -@overload -def named_gate(gate_generator: Callable[..., MatrixGate]) -> Callable[..., MatrixGate]: ... + def call_two_qubit_gate(self, control: QubitLike, target: QubitLike) -> ControlledGate: + return self.named_gate_functor(control, target) + def __call__(self, *operands) -> Gate: + if len(operands) == 1: + return self.call_single_qubit_gate(operands[0]) + elif len(operands) == 2: + return self.call_two_qubit_gate(operands[0], operands[1]) + else: + raise OSError(f"calling named gate with {len(operands)} qubit operands") -@overload -def named_gate(gate_generator: Callable[..., ControlledGate]) -> Callable[..., ControlledGate]: ... + def __getattr__(self, item): + return getattr(self.named_gate_functor, item) - -def named_gate(gate_generator: Callable[..., Gate]) -> Callable[..., Gate]: - @wraps(gate_generator) - def wrapper(*args: Any, **kwargs: Any) -> Gate: - result = gate_generator(*args, **kwargs) - result.generator = wrapper - - all_args: list[Expression] = [] - for par in inspect.signature(gate_generator).parameters.values(): - next_arg = kwargs[par.name] if par.name in kwargs else args[len(all_args)] - next_annotation = ( - ANNOTATIONS_TO_TYPE_MAP[par.annotation] if isinstance(par.annotation, str) else par.annotation - ) - - # Convert to correct expression for IR - if is_int_annotation(next_annotation): - next_arg = Int(next_arg) - if is_qubit_like_annotation(next_annotation): - next_arg = Qubit(next_arg) - - # Append parsed argument - all_args.append(next_arg) - - result.arguments = tuple(all_args) - return result - - return wrapper + Wrapper.__name__ = cls.__name__ + Wrapper.__doc__ = cls.__doc__ + return Wrapper def named_measure(measure_generator: Callable[..., Measure]) -> Callable[..., Measure]: @@ -670,6 +667,9 @@ def add_measure(self, measure: Measure) -> None: def add_reset(self, reset: Reset) -> None: self.statements.append(reset) + def add_statement(self, statement: Statement) -> None: + self.statements.append(statement) + def add_comment(self, comment: Comment) -> None: self.statements.append(comment) diff --git a/opensquirrel/parser/libqasm/parser.py b/opensquirrel/parser/libqasm/parser.py index f75c15c..d44d5f4 100644 --- a/opensquirrel/parser/libqasm/parser.py +++ b/opensquirrel/parser/libqasm/parser.py @@ -1,16 +1,18 @@ from __future__ import annotations from collections.abc import Callable, Iterable, Mapping -from typing import Any +from types import NoneType +from typing import Any, cast import cqasm.v3x as cqasm from opensquirrel.circuit import Circuit from opensquirrel.default_gates import default_gate_aliases, default_gate_set +from opensquirrel.default_gate_modifiers import ControlGateModifier, InverseGateModifier, PowerGateModifier from opensquirrel.default_measures import default_measure_set from opensquirrel.default_resets import default_reset_set from opensquirrel.instruction_library import GateLibrary, MeasureLibrary, ResetLibrary -from opensquirrel.ir import IR, Bit, Float, Gate, Int, Measure, Qubit, Reset +from opensquirrel.ir import IR, Bit, BlochSphereRotation, Float, Gate, Int, Measure, Qubit, Reset, Statement from opensquirrel.register_manager import RegisterManager @@ -29,9 +31,9 @@ def __init__( @staticmethod def _ast_literal_to_ir_literal( - cqasm_literal_expression: cqasm.values.ConstInt | cqasm.values.ConstFloat, + cqasm_literal_expression: cqasm.values.ConstInt | cqasm.values.ConstFloat | None, ) -> Int | Float | None: - if type(cqasm_literal_expression) not in [cqasm.values.ConstInt, cqasm.values.ConstFloat]: + if type(cqasm_literal_expression) not in [cqasm.values.ConstInt, cqasm.values.ConstFloat, NoneType]: msg = f"unrecognized type: {type(cqasm_literal_expression)}" raise TypeError(msg) if isinstance(cqasm_literal_expression, cqasm.values.ConstInt): @@ -64,6 +66,14 @@ def _is_bit_type(ast_expression: Any) -> bool: ast_type = Parser._type_of(ast_expression) return bool(ast_type == cqasm.types.Bit or ast_type == cqasm.types.BitArray) + @staticmethod + def _is_gate_instruction(ast_node: Any) -> bool: + return isinstance(ast_node, cqasm.semantic.GateInstruction) + + @staticmethod + def _is_non_gate_instruction(ast_node: Any) -> bool: + return isinstance(ast_node, cqasm.semantic.NonGateInstruction) + @staticmethod def _get_qubits( ast_qubit_expression: cqasm.values.VariableRef | cqasm.values.IndexRef, @@ -97,56 +107,18 @@ def _get_bits( return ret @classmethod - def _get_expanded_measure_args(cls, ast_args: Any, register_manager: RegisterManager) -> zip[tuple[Any, ...]]: - """Construct a list with a list of bits and a list of qubits, then return a zip of both lists. - For example: [(Qubit(0), Bit(0)), (Qubit(1), Bit(1))] - - Notice the list is walked in reverse mode. - This is because the AST measure node has a bit first operand and a qubit second operand. - """ - expanded_args: list[list[Any]] = [] - for ast_arg in reversed(ast_args): - if Parser._is_qubit_type(ast_arg): - expanded_args.append(cls._get_qubits(ast_arg, register_manager)) - elif Parser._is_bit_type(ast_arg): - expanded_args.append(cls._get_bits(ast_arg, register_manager)) - else: - msg = "received argument is not a (qu)bit" - raise TypeError(msg) - return zip(*expanded_args) + def _get_expanded_gate_args(cls, ast_args: Any, register_manager: RegisterManager) -> zip[tuple[Any, ...]]: + """Construct a list with a list of qubit operands, then return a zip of all the inner lists. - @classmethod - def _get_expanded_reset_args(cls, ast_args: Any, register_manager: RegisterManager) -> zip[tuple[Any, ...]]: - """Construct a list of qubits and return a zip. - For example: [Qubit(0), Qubit(1), Qubit(2)] - """ - expanded_args: list[Any] = [] - if len(ast_args) < 1: - expanded_args += [Qubit(qubit_index) for qubit_index in range(register_manager.get_qubit_register_size())] - return zip(expanded_args) - for ast_arg in ast_args: - if Parser._is_qubit_type(ast_arg): - expanded_args += cls._get_qubits(ast_arg, register_manager) - else: - msg = "received argument is not a (qu)bit" - raise TypeError(msg) - return zip(expanded_args) + For example, for a CNOT q[0, 1], q[2, 3] instruction, + it first constructs [[Qubit(0), Qubit(1)], [Qubit(2), Qubit(3)]], + and then returns [(Qubit(0), Qubit(2)), (Qubit(1), Qubit(3))] - @classmethod - def _get_expanded_gate_args(cls, ast_args: Any, register_manager: RegisterManager) -> zip[tuple[Any, ...]]: - """Construct a list with a list of qubits and a list of parameters, then return a zip of both lists. - For example: [(Qubit(0), Float(pi)), (Qubit(1), Float(pi))] + Note that _get_expanded_gate_args is only needed for Single-Gate-Multiple-Qubit (SGMQ). """ - number_of_operands = 0 - for ast_arg in ast_args: - if Parser._is_qubit_type(ast_arg): - number_of_operands += Parser._size_of(ast_arg) - expanded_args: list[list[Any]] = [] + expanded_args: list[list[Qubit]] = [] for ast_arg in ast_args: - if Parser._is_qubit_type(ast_arg): - expanded_args.append(cls._get_qubits(ast_arg, register_manager)) - else: - expanded_args.append([cls._ast_literal_to_ir_literal(ast_arg)] * number_of_operands) + expanded_args.append(cls._get_qubits(ast_arg, register_manager)) return zip(*expanded_args) @staticmethod @@ -159,6 +131,28 @@ def _check_analysis_result(result: Any) -> None: if isinstance(result, list): raise OSError("parsing error: " + ", ".join(result)) + def _get_gate_f(self, instruction: cqasm.semantic.GateInstruction) -> Callable[..., Gate]: + gate_name = instruction.gate.name + if gate_name == "inv" or gate_name == "pow" or gate_name == "ctrl": + modified_gate_f = cast(Callable[..., BlochSphereRotation], self._get_gate_f(instruction.gate)) + if gate_name == "inv": + return InverseGateModifier(modified_gate_f) + elif gate_name == "pow": + return PowerGateModifier(instruction.gate.parameter.value, modified_gate_f) + elif gate_name == "ctrl": + return ControlGateModifier(modified_gate_f) + else: + gate_parameter = Parser._ast_literal_to_ir_literal(instruction.gate.parameter) + return self.get_gate_f(gate_name, gate_parameter) + + def _get_non_gate_f(self, instruction: cqasm.semantic.NonGateInstruction) -> Callable[..., Statement]: + if "measure" in instruction.name: + return self.get_measure_f(instruction.name) + elif "reset" in instruction.name: + return self.get_reset_f(instruction.name) + else: + raise OSError("parsing error: unknown non-unitary instruction") + def circuit_from_string(self, s: str) -> Circuit: # Analysis result will be either an Abstract Syntax Tree (AST) or a list of error messages analyzer = Parser._create_analyzer() @@ -172,20 +166,14 @@ def circuit_from_string(self, s: str) -> Circuit: # Parse statements ir = IR() for statement in ast.block.statements: - if "measure" in statement.name: - generator_f_measure = self.get_measure_f(statement.name) - expanded_args = Parser._get_expanded_measure_args(statement.operands, register_manager) - for arg_set in expanded_args: - ir.add_measure(generator_f_measure(*arg_set)) - elif "reset" in statement.name: - generator_f_reset = self.get_reset_f(statement.name) - expanded_args = Parser._get_expanded_reset_args(statement.operands, register_manager) - for arg_set in expanded_args: - ir.add_reset(generator_f_reset(*arg_set)) + instruction_generator: Callable[..., Statement] + if Parser._is_gate_instruction(statement): + instruction_generator = self._get_gate_f(statement) + elif Parser._is_non_gate_instruction(statement): + instruction_generator = self._get_non_gate_f(statement) else: - generator_f_gate = self.get_gate_f(statement.name) - expanded_args = Parser._get_expanded_gate_args(statement.operands, register_manager) - for arg_set in expanded_args: - ir.add_gate(generator_f_gate(*arg_set)) + raise OSError("parsing error: unknown statement") + for args in Parser._get_expanded_gate_args(statement.operands, register_manager): + ir.add_statement(instruction_generator(*args)) return Circuit(register_manager, ir) diff --git a/poetry.lock b/poetry.lock index a0d0f0e..33d3d7c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "adaptive" @@ -2119,31 +2119,31 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "libqasm" -version = "0.6.7" +version = "0.6.8" description = "libqasm Python Package" optional = false python-versions = "*" files = [ - {file = "libqasm-0.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85012c73625ceb44c112a4cab9bd81664118a2b000dd47a47925d6d5711c31b4"}, - {file = "libqasm-0.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4369f7e4f036a1dd77bafcda638dfa2e2b66eda3ef3577ce8a0f650b68103c76"}, - {file = "libqasm-0.6.7-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc5efadc23d948b2065b405021a85e1aac9186c223bff0e8e96c96815ecf31f4"}, - {file = "libqasm-0.6.7-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b4e94ca70cc20a0efdc0fd7cf4322948c44c9b5c867174824d625462289a47"}, - {file = "libqasm-0.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:a39edf23f224912a14925134025209aebc60c56ac0819ed03d032760977e7516"}, - {file = "libqasm-0.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad6f4fce0cfd8bd276e8b7ce2ed91da6f34d634c3a4322e257f374e7386b6fb3"}, - {file = "libqasm-0.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a187d7b7121e8b1a51885a7b56470b94be52f9eb201a0d62afb4766f515eda0a"}, - {file = "libqasm-0.6.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd02d4391cc539fd78459496b92580aa171f0a1660d943c61d162ae58be0d81"}, - {file = "libqasm-0.6.7-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1650be6510a9a6ce002f33aaa66b34ac5d1e269794c8c4683dc77459f3e61b9e"}, - {file = "libqasm-0.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:947863a5715184a59022cbba05b4518c359c49087cbfa0b27eb5eef19ea69559"}, - {file = "libqasm-0.6.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a04b16c604d5c7212bcd1d43d8ce6a6ef08057a8a5e4e793499c2988424bde5a"}, - {file = "libqasm-0.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed64ba178e33c6a48c04ce50642e751ce289c57703ede0f71304befbbc66fb42"}, - {file = "libqasm-0.6.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c04f44925416f9472af5603cf9b52e1aff4ba2d9509327c8af374c7bc22e27"}, - {file = "libqasm-0.6.7-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b8a15e27d79007c18e812d5ec42c62053131100cb38a36f1295476ba3407361"}, - {file = "libqasm-0.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:c74c33d4449355be9f909620e734cce0c7e7026ea56688234968e5ec3a0c12eb"}, - {file = "libqasm-0.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3573a244d5b939b3a51c17557ef56fd7bcafd550ba37a11de8e7f38933629565"}, - {file = "libqasm-0.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94228b90493625381fd23a0473c02b135dc6c9727bf26b0c7f7bfec13a822c66"}, - {file = "libqasm-0.6.7-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:161e67e58f7b78f9f53d97dc5d15c529a29c06c5dc554c92e76e70f71001aaca"}, - {file = "libqasm-0.6.7-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b201ddd0a608b2256f7a2364d3195012bf2e13dd655abb3f078f682676f94b9"}, - {file = "libqasm-0.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:9e7b5064921d1a7e232f5bbf5151fce532972a69135d359c10e5c12bcf7ce9e5"}, + {file = "libqasm-0.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2cc4640f9967bc419f4a686153416258f9ce24d8e453991de6346bc0a330a51b"}, + {file = "libqasm-0.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32293630e5e799967f89b61020097206878c8556ce8410ffa742ff83afe9427f"}, + {file = "libqasm-0.6.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30b7caf93475ca5c9be907bcab33f0e22a25d80404cecb36e73ed40aff37efa1"}, + {file = "libqasm-0.6.8-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee1d91b5ebedc0f485492162350f90f9ac8713e6065a6e27958f9271ea69e844"}, + {file = "libqasm-0.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:c3f6f05995f57604a81e7dbf807c9200916b1c1028d70b02462b31d7add4f54f"}, + {file = "libqasm-0.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ffb0b83aa99b4b3a64009b1e886e840bef09202c4fea511b2a768af1d61549"}, + {file = "libqasm-0.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71e66a61b2fbdba83be1a41e9532a58eade47dc3361814e50dedc1363b007363"}, + {file = "libqasm-0.6.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c4d4c4defbd971db614d984a64770b7ef57dc5291d82ace27d3674eabd9bcab"}, + {file = "libqasm-0.6.8-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b1865314660d3fd22efc58a7dc467c34bb089347907c2ae0034db9d67edd5e5"}, + {file = "libqasm-0.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:ab440b44ffb3827bbe79f88e4a05be46862d9719ef16ab356e23465f12dd7eac"}, + {file = "libqasm-0.6.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a327ed0a54fcc107ba087f5939980cbc4d9babea11ac093b67252809af44569c"}, + {file = "libqasm-0.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7f3e1668071a31092cdc56dad5860c44682da620e3e80dabeeebb0d29ff0eaa"}, + {file = "libqasm-0.6.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5a6b3b80382009c0bb9b64d7cca6cdc09411695349ff2a1986306f873af732"}, + {file = "libqasm-0.6.8-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4734c4945d04f9affa15fd7cd183cbb10ec817ee4eb95668836ae76736a7f3f5"}, + {file = "libqasm-0.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:18f5a4a84a95b7b49f4400302fb0bbaca6cc315dbdb11045adc8d13127ac7dc9"}, + {file = "libqasm-0.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bffef6deb600c4cd6e038fe3226f7f9b830599830bcda8b4423f82a80c4a8d35"}, + {file = "libqasm-0.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8288054ab5485c883a14e6b76abc58201e080ddeae4270730c0fc947892aeb07"}, + {file = "libqasm-0.6.8-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92e81ad1095ef45ba21730f78675a940c08b823a272fd4ca1503c4e283c08d34"}, + {file = "libqasm-0.6.8-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e7ba1e47ee2d4da385d695f0bb3bff6ae7e1658f45400eb7ef6e3700c1264d"}, + {file = "libqasm-0.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:805de2cb6fc643fac33cfcda9fabdda5f730b05270168a656e9a787b997f1a14"}, ] [package.dependencies] @@ -6180,4 +6180,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "fe705a89331a0666c31b654a3705ff2d912a696eec2e0e9cc7a6031ba4033eaa" +content-hash = "7ee11490cc35d9c0a4323cf67a397e3b0541b232bb1e7b28723ef48b361b9ea1" diff --git a/pyproject.toml b/pyproject.toml index 9f1e2c5..c0670f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ numpy = [ { version = "^1.26", python = "<3.10" }, { version = "^2.0", python = "^3.10" }, ] -libqasm = "0.6.7" +libqasm = "0.6.8" networkx = "^3.0.0" [tool.poetry.group.dev.dependencies] diff --git a/test/parser/libqasm/test_libqasm.py b/test/parser/libqasm/test_libqasm.py index f2d6223..c15c7ab 100644 --- a/test/parser/libqasm/test_libqasm.py +++ b/test/parser/libqasm/test_libqasm.py @@ -1,7 +1,8 @@ +import math import pytest from opensquirrel.default_gates import CNOT, CR, CRk, H, I, Ry, X, default_gate_aliases, default_gate_set -from opensquirrel.ir import Float +from opensquirrel.ir import BlochSphereRotation, ControlledGate, Float, Gate from opensquirrel.parser.libqasm.parser import Parser @@ -78,3 +79,45 @@ def test_error(parser: Parser) -> None: def test_wrong_gate_argument_number_or_types(parser: Parser, error_message: str, circuit_string: str) -> None: with pytest.raises(IOError, match=error_message): parser.circuit_from_string(circuit_string) + + +@pytest.mark.parametrize( + ("circuit_string", "expected_result"), + [ + ("version 3.0; qubit q; inv.X q", + [BlochSphereRotation(qubit=0, axis=(1, 0, 0), angle=math.pi * -1, phase=math.pi / 2)]), + ("version 3.0; qubit q; pow(2).X q", + [BlochSphereRotation(qubit=0, axis=(1, 0, 0), angle=math.pi * 2, phase=math.pi / 2)]), + ("version 3.0; qubit q; pow(2).inv.X q", + [BlochSphereRotation(qubit=0, axis=(1, 0, 0), angle=math.pi * -2, phase=math.pi / 2)]), + ("version 3.0; qubit[2] q; ctrl.pow(2).inv.X q[0], q[1]", + [ControlledGate(0, + BlochSphereRotation(qubit=1, axis=(1, 0, 0), angle=math.pi * -2, phase=math.pi / 2))]), + ], + ids=["inv", "pow_2", "pow_2_inv", "ctrl_pow_2_inv"], +) +def test_gate_modifiers(parser: Parser, circuit_string: str, expected_result: list[Gate]) -> None: + circuit = parser.circuit_from_string(circuit_string) + assert circuit.ir.statements == expected_result + + +@pytest.mark.parametrize( + ("circuit_string", "expected_result"), + [ + ("version 3.0; qubit q; X q", + [BlochSphereRotation(qubit=0, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)]), + ("version 3.0; qubit q; Rx(pi) q", + [BlochSphereRotation(qubit=0, axis=(1, 0, 0), angle=math.pi, phase=0)]), + ("version 3.0; qubit[2] q; CNOT q[0], q[1]", + [ControlledGate(0, + BlochSphereRotation(qubit=1, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2))]), + ], + ids=[ + "bloch_sphere_rotation", + "parametrized_bloch_sphere_rotation", + "controlled_gate" + ], +) +def test_named_gates(parser: Parser, circuit_string: str, expected_result: list[Gate]) -> None: + circuit = parser.circuit_from_string(circuit_string) + assert circuit.ir.statements == expected_result