diff --git a/src/braket/circuits/duration_gate.py b/src/braket/circuits/duration_gate.py new file mode 100644 index 000000000..566b0bd8a --- /dev/null +++ b/src/braket/circuits/duration_gate.py @@ -0,0 +1,203 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +import math +from collections.abc import Sequence +from enum import Enum +from functools import singledispatch +from typing import Optional, Union + +from braket.circuits.free_parameter_expression import FreeParameterExpression +from braket.circuits.gate import Gate +from braket.circuits.parameterizable import Parameterizable + + +class SiTimeUnit(Enum): + """Possible Si unit time types""" + + s = "s" + ms = "ms" + us = "us" + ns = "ns" + + def __str__(self): + return self.value + + def __repr__(self) -> str: + return self.__str__() + + +class DurationGate(Gate, Parameterizable): + """Class `DurationGate` represents a quantum gate that operates on N qubits and a duration.""" + + def __init__( + self, + duration: Union[FreeParameterExpression, float], + qubit_count: Optional[int], + ascii_symbols: Sequence[str], + ): + """Initializes an `DurationGate`. + + Args: + duration (Union[FreeParameterExpression, float]): The duration of the gate in seconds + or expression representation. + qubit_count (Optional[int]): The number of qubits that this gate interacts with. + ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when + printing a diagram of a circuit. The length must be the same as `qubit_count`, and + index ordering is expected to correlate with the target ordering on the instruction. + For instance, if a CNOT instruction has the control qubit on the first index and + target qubit on the second index, the ASCII symbols should have `["C", "X"]` to + correlate a symbol with that index. + + Raises: + ValueError: If the `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count`, or `duration` is `None` + """ + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + if duration is None: + raise ValueError("duration must not be None") + if isinstance(duration, FreeParameterExpression): + self._parameters = [duration] + else: + # explicit casting in case duration is e.g. np.float32 + self._parameters = [float(duration)] + + @property + def parameters(self) -> list[Union[FreeParameterExpression, float]]: + """Returns the parameters associated with the object, either unbound free parameters or + bound values. + + Returns: + list[Union[FreeParameterExpression, float]]: The free parameters or fixed value + associated with the object. + """ + return self._parameters + + @property + def duration(self) -> Union[FreeParameterExpression, float]: + """Returns the duration of the gate + + Returns: + Union[FreeParameterExpression, float]: The duration of the gate + in seconds. + """ + return self._parameters[0] + + def bind_values(self, **kwargs) -> DurationGate: + """Takes in parameters and attempts to assign them to values. + + Returns: + DurationGate: A new Gate of the same type with the requested parameters bound. + + Raises: + NotImplementedError: Subclasses should implement this function. + """ + raise NotImplementedError + + def __eq__(self, other: DurationGate): + return ( + isinstance(other, DurationGate) + and self.name == other.name + and _durations_equal(self.duration, other.duration) + ) + + def __repr__(self): + return f"{self.name}('duration': {self.duration}, 'qubit_count': {self.qubit_count})" + + def __hash__(self): + return hash((self.name, self.qubit_count, self.duration)) + + +@singledispatch +def _durations_equal( + duration_1: Union[FreeParameterExpression, float], + duration_2: Union[FreeParameterExpression, float], +) -> bool: + return isinstance(duration_2, float) and math.isclose(duration_1, duration_2) + + +@_durations_equal.register +def _(duration_1: FreeParameterExpression, duration_2: FreeParameterExpression): + return duration_1 == duration_2 + + +def _duration_str(duration: Union[FreeParameterExpression, float]) -> str: + """Returns the string represtntion of the duration of the gate. + + Returns: + str : The duration of the gate in string + representation to convienient SI units + in ("s", "ms", "us", "ns"). + + Note: + This is used in ASCII and OPENQASM code generation, so please + do not play around with whitespaces here. + + >> delay[30ns] q[4]; # VALID QASM + >> delay[30 ns] q[4]; # INVALID QASM + + """ + if isinstance(duration, FreeParameterExpression): + return str(duration) + else: + # Currently, duration is truncated to 2 decimal places. + # Same as angle in AngledGate). + DURATION_MAX_DIGITS = 2 + + if duration >= 1: + return f"{round(duration, DURATION_MAX_DIGITS)}{SiTimeUnit.s}" + elif duration >= 1e-3: + return f"{round(1e3 * duration, DURATION_MAX_DIGITS)}{SiTimeUnit.ms}" + elif duration >= 1e-6: + return f"{round(1e6 * duration, DURATION_MAX_DIGITS)}{SiTimeUnit.us}" + else: + return f"{round(1e9 * duration, DURATION_MAX_DIGITS)}{SiTimeUnit.ns}" + + +def duration_ascii_characters( + gate_name: str, duration: Union[FreeParameterExpression, float] +) -> str: + """Generates a formatted ascii representation of a duration gate. + + Args: + gate_name (str): The name of the gate. + duration (Union[FreeParameterExpression, float]): The duration + of the gate in seconds. + + Returns: + str: Returns the ascii representation for a duration gate. + + """ + return f"{gate_name}({_duration_str(duration)})" + + +def bind_duration(gate: DurationGate, **kwargs: FreeParameterExpression | str) -> DurationGate: + """Gets the duration with all values substituted in that are requested. + + Args: + gate (DurationGate): The subclass of DurationGate for which the duration is being obtained. + **kwargs (FreeParameterExpression | str): The named parameters that are being filled + for a particular gate. + + Returns: + DurationGate: A new gate of the type of the DurationGate originally used with all + duration updated. + """ + new_duration = ( + gate.duration.subs(kwargs) + if isinstance(gate.duration, FreeParameterExpression) + else gate.duration + ) + return type(gate)(duration=new_duration, qubit_count=gate.qubit_count) diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 453b121fd..3120eec5a 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -18,6 +18,8 @@ from typing import Any, Optional from braket.circuits.basis_state import BasisState, BasisStateInput + +# from braket.circuits.duration_gate import _duration_str from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.serialization import ( IRType, @@ -193,8 +195,11 @@ def _to_openqasm( control_prefix = "" inv_prefix = "inv @ " if power and power < 0 else "" power_prefix = f"pow({abs_power}) @ " if (abs_power := abs(power)) != 1 else "" + param_string = ( - f"({', '.join(map(str, self.parameters))})" if hasattr(self, "parameters") else "" + f"({', '.join(map(str, self.parameters))})" + if hasattr(self, "parameters") and not hasattr(self, "duration") + else "" ) return ( diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index ee5ea684b..73f8533aa 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -32,6 +32,12 @@ get_angle, ) from braket.circuits.basis_state import BasisState, BasisStateInput +from braket.circuits.duration_gate import ( + DurationGate, + _duration_str, + bind_duration, + duration_ascii_characters, +) from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -3847,6 +3853,77 @@ def pulse_gate( Gate.register_gate(PulseGate) +class Barrier(Gate): + r"""Barrier gate.""" + + def __init__(self, qubit_count): + super().__init__(qubit_count=qubit_count, ascii_symbols=["||"] * qubit_count) + + def bind_values(self, **kwargs) -> Any: + raise NotImplementedError + + @property + def _qasm_name(self) -> str: + return "barrier" + + @staticmethod + @circuit.subroutine(register=True) + def barrier(target: QubitSetInput) -> Instruction: + r"""Barrier gate. + + Args: + target (QubitSetInput): Target qubit(s) + + Examples: + >>> circ = Circuit().barrier(target = [0, 1, 2]) + """ + return Instruction(Barrier(len(QubitSet(target))), target=QubitSet(target)) + + +Gate.register_gate(Barrier) + + +class Delay(DurationGate): + r"""Delay gate. Applies delay in seconds.""" + + def __init__(self, qubit_count, duration): + super().__init__( + qubit_count=qubit_count, + duration=duration, + ascii_symbols=[duration_ascii_characters("delay", duration)] * qubit_count, + ) + + def bind_values(self, **kwargs) -> DurationGate: + return bind_duration(self, **kwargs) + + @property + def _qasm_name(self) -> str: + return f"delay[{_duration_str(self.duration)}]" + + def __hash__(self): + return hash((self.name, self.qubit_count, self.duration)) + + @staticmethod + @circuit.subroutine(register=True) + def delay( + target: QubitSetInput, duration: Union[FreeParameterExpression, float] + ) -> Instruction: + r"""Delay gate. Applies delay in seconds. + + Args: + target (QubitSetInput): Target qubit(s) + duration (Union[FreeParameterExpression, float]): Delay in + seconds or in expression representation. + + Examples: + >>> circ = Circuit().delay(target = [0, 1, 2], duration = 30e-9) + """ + return Instruction(Delay(len(QubitSet(target)), duration), target=QubitSet(target)) + + +Gate.register_gate(Delay) + + def format_complex(number: complex) -> str: """Format a complex number into + im to be consumed by the braket unitary pragma diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 85567de28..0e8889313 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -211,12 +211,12 @@ def _draw_symbol( """ top = "" bottom = "" - if symbol in {"C", "N", "SWAP"}: + if symbol in {"C", "N", "SWAP", "||"}: if connection in ["above", "both"]: top = _fill_symbol(cls._vertical_delimiter(), " ") if connection in ["below", "both"]: bottom = _fill_symbol(cls._vertical_delimiter(), " ") - new_symbol = {"C": "●", "N": "◯", "SWAP": "x"} + new_symbol = {"C": "●", "N": "◯", "SWAP": "x", "||": "▒"} # replace SWAP by x # the size of the moment remains as if there was a box with 4 characters inside symbol = _fill_symbol(new_symbol[symbol], cls._qubit_line_character()) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index b10891830..19b4a8c4d 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -160,6 +160,50 @@ def test_qubit_width(): _assert_correct_diagram(circ, expected) +def test_barrier_circuit_visualization(): + circ = Circuit().barrier(target=[0, 100]) + expected = ( + "T : |0 |", + " ", + "q0 : -||-", + " | ", + "q100 : -||-", + "", + "T : |0 |", + ) + _assert_correct_diagram(circ, expected) + + +def test_delay_circuit_visualization(): + circ = Circuit().delay(target=[0, 100], duration=30e-9) + expected = ( + "T : | 0 |", + " ", + "q0 : -delay(30.0ns)-", + " | ", + "q100 : -delay(30.0ns)-", + "", + "T : | 0 |", + ) + _assert_correct_diagram(circ, expected) + + +def test_delay_circuit_free_param_visualization(): + circ = Circuit().delay(target=[0, 100], duration=FreeParameter("td")) + expected = ( + "T : | 0 |", + " ", + "q0 : -delay(td)-", + " | ", + "q100 : -delay(td)-", + "", + "T : | 0 |", + "", + "Unassigned parameters: [td].", + ) + _assert_correct_diagram(circ, expected) + + def test_gate_width(): class Foo(Gate): def __init__(self): diff --git a/test/unit_tests/braket/circuits/test_duration_gate.py b/test/unit_tests/braket/circuits/test_duration_gate.py new file mode 100644 index 000000000..fc3068ef4 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_duration_gate.py @@ -0,0 +1,156 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import pytest + +from braket.circuits import FreeParameter, FreeParameterExpression, Gate +from braket.circuits.duration_gate import DurationGate, SiTimeUnit, _duration_str + + +@pytest.mark.parametrize( + "si_unit, expected", + ( + (SiTimeUnit.s, "s"), + (SiTimeUnit.ms, "ms"), + (SiTimeUnit.us, "us"), + (SiTimeUnit.ns, "ns"), + ), +) +def test_si_unit_str(si_unit, expected): + str(si_unit) == expected + + +@pytest.mark.parametrize( + "si_unit, expected", + ( + (SiTimeUnit.s, "s"), + (SiTimeUnit.ms, "ms"), + (SiTimeUnit.us, "us"), + (SiTimeUnit.ns, "ns"), + ), +) +def test_si_repr(si_unit, expected): + repr(si_unit) == expected + + +@pytest.fixture +def duration_gate(): + return DurationGate(duration=30e-9, qubit_count=1, ascii_symbols=["delay"]) + + +def test_is_operator(duration_gate): + assert isinstance(duration_gate, Gate) + + +def test_repr(duration_gate): + assert repr(duration_gate) == "DurationGate('duration': 3e-08, 'qubit_count': 1)" + + +def test_duration_setter(duration_gate): + with pytest.raises(AttributeError): + duration_gate.duration = 30e-9 + + +def test_duration_is_none(): + with pytest.raises(ValueError, match="duration must not be None"): + DurationGate(qubit_count=1, ascii_symbols=["foo"], duration=None) + + +def test_getters(): + qubit_count = 2 + ascii_symbols = ("foo", "bar") + duration = 30e-9 + gate = DurationGate(duration=duration, qubit_count=qubit_count, ascii_symbols=ascii_symbols) + + assert gate.qubit_count == qubit_count + assert gate.ascii_symbols == ascii_symbols + assert gate.duration == duration + + +def test_equality(): + gate1 = DurationGate(duration=30e-9, qubit_count=1, ascii_symbols=["delay"]) + gate2 = DurationGate(duration=30e-9, qubit_count=1, ascii_symbols=["bar"]) + gate3 = DurationGate(duration=30e-6, qubit_count=1, ascii_symbols=["foo"]) + non_gate = "non gate" + + assert gate1 == gate2 + assert gate1 is not gate2 + assert gate1 != gate3 + assert gate1 != non_gate + + +def test_symbolic_equality(): + symbol1 = FreeParameter("theta") + symbol2 = FreeParameter("phi") + symbol3 = FreeParameter("theta") + gate1 = DurationGate(duration=symbol1, qubit_count=1, ascii_symbols=["bar"]) + gate2 = DurationGate(duration=symbol2, qubit_count=1, ascii_symbols=["bar"]) + gate3 = DurationGate(duration=symbol3, qubit_count=1, ascii_symbols=["bar"]) + other_gate = DurationGate(duration=symbol1, qubit_count=1, ascii_symbols=["foo"]) + + assert gate1 != gate2 + assert gate1 == gate3 + assert gate1 == other_gate + assert gate1 is not other_gate + + +def test_mixed_duration_equality(): + symbol1 = FreeParameter("theta") + gate1 = DurationGate(duration=symbol1, qubit_count=1, ascii_symbols=["bar"]) + gate2 = DurationGate(duration=30e-9, qubit_count=1, ascii_symbols=["foo"]) + + assert gate1 != gate2 + assert gate2 != gate1 + + +def test_hash(): + symbol1 = FreeParameter("theta") + symbol2 = FreeParameter("phi") + symbol3 = FreeParameter("theta") + gate1 = DurationGate(duration=symbol1, qubit_count=1, ascii_symbols=["bar"]) + gate2 = DurationGate(duration=symbol2, qubit_count=1, ascii_symbols=["bar"]) + gate3 = DurationGate(duration=symbol3, qubit_count=1, ascii_symbols=["bar"]) + + assert hash(gate1) != hash(gate2) + assert hash(gate1) == hash(gate3) + + +def test_bind_values(): + theta = FreeParameter("theta") + gate = DurationGate(duration=theta, qubit_count=1, ascii_symbols=["bar"]) + with pytest.raises(NotImplementedError): + gate.bind_values(theta=1) + + +@pytest.mark.parametrize( + "duration, expected", + ( + (30e-0, "30.0s"), + (30e-3, "30.0ms"), + (30e-6, "30.0us"), + (30e-9, "30.0ns"), + (FreeParameter("td"), "td"), + ), +) +def test_duration_str(duration, expected): + actual = _duration_str(duration) + assert actual == expected + + +def test_duration_gate_with_expr(): + expr = FreeParameterExpression(FreeParameter("theta") + 1) + new_expr = expr.subs({"theta": 1}) + gate = DurationGate(duration=new_expr, qubit_count=1, ascii_symbols=["bar"]) + expected = DurationGate(duration=2, qubit_count=1, ascii_symbols=["bar"]) + + assert gate == expected diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 285b530c1..a1d89046e 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -15,6 +15,7 @@ import numpy as np import pytest +from pydantic.v1 import BaseModel, confloat import braket.ir.jaqcd as ir from braket.circuits import Circuit, FreeParameter, Gate, Instruction, QubitSet @@ -39,6 +40,15 @@ class NoTarget: pass +class Duration(BaseModel): + # TODO: deprecate this class when we deprecate jacqd + duration: confloat(ge=0) + + +class NoMatrixGeneration: + pass + + class DoubleAngle: pass @@ -68,6 +78,8 @@ class SingleNegControlModifier: (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle], {}), (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle], {}), (Gate.U, "u", None, [SingleTarget, TripleAngle], {}), + (Gate.Barrier, "barrier", None, [SingleTarget, NoMatrixGeneration], {}), + (Gate.Delay, "delay", None, [SingleTarget, Duration, NoMatrixGeneration], {}), (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl], {}), (Gate.CV, "cv", ir.CV, [SingleTarget, SingleControl], {}), (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl], {}), @@ -133,25 +145,26 @@ class SingleNegControlModifier: ] parameterizable_gates = [ - Gate.GPhase, - Gate.Rx, - Gate.Ry, - Gate.Rz, - Gate.U, - Gate.PhaseShift, - Gate.PSwap, - Gate.XX, - Gate.XY, - Gate.YY, - Gate.ZZ, - Gate.CPhaseShift, - Gate.CPhaseShift00, - Gate.CPhaseShift01, - Gate.CPhaseShift10, - Gate.GPi, - Gate.GPi2, - Gate.PRx, - Gate.MS, + (Gate.GPhase, ["angle"]), + (Gate.Rx, ["angle"]), + (Gate.Ry, ["angle"]), + (Gate.Rz, ["angle"]), + (Gate.U, ["angle_1", "angle_2", "angle_3"]), + (Gate.PhaseShift, ["angle"]), + (Gate.Delay, ["duration"]), + (Gate.PSwap, ["angle"]), + (Gate.XX, ["angle"]), + (Gate.XY, ["angle"]), + (Gate.YY, ["angle"]), + (Gate.ZZ, ["angle"]), + (Gate.CPhaseShift, ["angle"]), + (Gate.CPhaseShift00, ["angle"]), + (Gate.CPhaseShift01, ["angle"]), + (Gate.CPhaseShift10, ["angle"]), + (Gate.GPi, ["angle"]), + (Gate.GPi2, ["angle"]), + (Gate.PRx, ["angle_1", "angle_2"]), + (Gate.MS, ["angle_1", "angle_2", "angle_3"]), ] @@ -170,6 +183,19 @@ def no_target_valid_input(**kwargs): return {} +def no_matrix_valid_input(**kwargs): + qubit_count = 1 + + if kwargs.get("target", None) is not None: + qubit_count = len(kwargs.get("target")) + + return {"qubit_count": qubit_count} + + +def no_matrix_valid_ir_input(**kwargs): + return {} + + def single_target_valid_input(**kwargs): return {"target": 2} @@ -194,6 +220,10 @@ def triple_angle_valid_input(**kwargs): return {"angle_1": 0.123, "angle_2": 4.567, "angle_3": 8.910} +def duration_valid_input(**kwargs): + return {"duration": 30e-9} + + def single_control_valid_input(**kwargs): return {"control": 0} @@ -225,6 +255,7 @@ def two_dimensional_matrix_valid_input(**kwargs): valid_ir_switcher = { "NoTarget": no_target_valid_input, + "NoMatrixGeneration": no_matrix_valid_ir_input, "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_ir_input, "Angle": angle_valid_input, @@ -235,6 +266,7 @@ def two_dimensional_matrix_valid_input(**kwargs): "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, + "Duration": duration_valid_input, } valid_subroutine_switcher = dict( @@ -284,7 +316,14 @@ def create_valid_target_input(irsubclasses): control_state = list(single_neg_control_valid_input()["control_state"]) elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif subclass not in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): + elif subclass not in ( + Angle, + Duration, + NoMatrixGeneration, + TwoDimensionalMatrix, + DoubleAngle, + TripleAngle, + ): raise ValueError("Invalid subclass") input = {"target": QubitSet(qubit_set)} input["control"] = QubitSet(control_qubit_set) @@ -302,6 +341,10 @@ def create_valid_gate_class_input(irsubclasses, **kwargs): input.update(triple_angle_valid_input()) if TwoDimensionalMatrix in irsubclasses: input.update(two_dimensional_matrix_valid_input(**kwargs)) + if NoMatrixGeneration in irsubclasses: + input.update(no_matrix_valid_input(**kwargs)) + if Duration in irsubclasses: + input.update(duration_valid_input(**kwargs)) return input @@ -326,7 +369,9 @@ def calculate_qubit_count(irsubclasses): qubit_count += 3 elif subclass not in ( NoTarget, + NoMatrixGeneration, Angle, + Duration, TwoDimensionalMatrix, DoubleAngle, TripleAngle, @@ -408,6 +453,66 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), "h $4;", ), + ( + Gate.Barrier(1), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "barrier q[4];", + ), + ( + Gate.Barrier(1), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "barrier $4;", + ), + ( + Gate.Barrier(3), + [3, 4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "barrier q[3], q[4], q[5];", + ), + ( + Gate.Barrier(3), + [3, 4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "barrier $3, $4, $5;", + ), + ( + Gate.Delay(qubit_count=1, duration=30e-9), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "delay[30.0ns] q[4];", + ), + ( + Gate.Delay(qubit_count=1, duration=30e-9), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "delay[30.0ns] $4;", + ), + ( + Gate.Delay(qubit_count=3, duration=30e-9), + [3, 4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "delay[30.0ns] q[3], q[4], q[5];", + ), + ( + Gate.Delay(qubit_count=3, duration=30e-9), + [3, 4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "delay[30.0ns] $3, $4, $5;", + ), + ( + Gate.Delay(qubit_count=3, duration=FreeParameter("td")), + [3, 4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "delay[td] q[3], q[4], q[5];", + ), + ( + Gate.Delay(qubit_count=3, duration=FreeParameter("td")), + [3, 4, 5], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "delay[td] $3, $4, $5;", + ), ( Gate.Ry(angle=0.17), [4], @@ -889,12 +994,10 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs ], ) def test_gate_to_ir_openqasm(gate, target, serialization_properties, expected_ir): - assert ( - gate.to_ir( - target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties - ) - == expected_ir + actual_ir = gate.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties ) + assert actual_ir == expected_ir @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) @@ -917,21 +1020,42 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar ) if qubit_count == 1: multi_targets = [0, 1, 2] - instruction_list = [ - Instruction( - operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), - target=target, - ) - for target in multi_targets - ] + subroutine = getattr(Circuit(), subroutine_name) subroutine_input = {"target": multi_targets} + if Angle in irsubclasses: subroutine_input.update(angle_valid_input()) if DoubleAngle in irsubclasses: subroutine_input.update(double_angle_valid_input()) if TripleAngle in irsubclasses: subroutine_input.update(triple_angle_valid_input()) + if Duration in irsubclasses: + subroutine_input.update(duration_valid_input()) + + if NoMatrixGeneration in irsubclasses: + # Updating kwargs so that qubit_count attribute + # can be properly initialized + kwargs.update(subroutine_input) + + # Accounting for delay and barrier gates here + # >>> circ = Circuit().barrier(targets=[0, 1, 2]) + # Produces, barrier q[0], q[1], q[2]; + instruction_list = [ + Instruction( + operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), + target=multi_targets, + ) + ] + else: + instruction_list = [ + Instruction( + operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), + target=target, + ) + for target in multi_targets + ] + assert subroutine(**subroutine_input) == Circuit(instruction_list) @@ -979,19 +1103,21 @@ def test_angle_gphase_is_none(): @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_gate_adjoint_expansion_correct(testclass, subroutine_name, irclass, irsubclasses, kwargs): - gate = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - matrices = [elem.to_matrix() for elem in gate.adjoint()] - matrices.append(gate.to_matrix()) - identity = np.eye(2**gate.qubit_count) - assert np.allclose(functools.reduce(lambda a, b: a @ b, matrices), identity) + if NoMatrixGeneration not in irsubclasses: + gate = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) + matrices = [elem.to_matrix() for elem in gate.adjoint()] + matrices.append(gate.to_matrix()) + identity = np.eye(2**gate.qubit_count) + assert np.allclose(functools.reduce(lambda a, b: a @ b, matrices), identity) @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_gate_to_matrix(testclass, subroutine_name, irclass, irsubclasses, kwargs): - gate1 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - gate2 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) - assert isinstance(gate1.to_matrix(), np.ndarray) - assert gate1.matrix_equivalence(gate2) + if NoMatrixGeneration not in irsubclasses: + gate1 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) + gate2 = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) + assert isinstance(gate1.to_matrix(), np.ndarray) + assert gate1.matrix_equivalence(gate2) @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) @@ -1038,30 +1164,36 @@ def test_large_unitary(): assert unitary.qubit_count == 4 -@pytest.mark.parametrize("gate", parameterizable_gates) -def test_bind_values(gate): - double_angled = gate.__name__ in ["PRx"] - triple_angled = gate.__name__ in ("MS", "U") - num_params = 1 - if triple_angled: - num_params = 3 - elif double_angled: - num_params = 2 - thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] - mapping = {f"theta_{i}": i for i in range(num_params)} - param_gate = gate(*thetas) +def duration_free_param_valid_input(): + return {"duration": FreeParameter("theta_1"), "qubit_count": 1} + + +@pytest.mark.parametrize("gate,attributes", parameterizable_gates) +def test_bind_values(gate, attributes): + + duration = gate.__name__ in ("Delay") + num_params = len(attributes) + + thetas = [FreeParameter(f"theta_{i + 1}") for i in range(num_params)] + mapping = {f"theta_{i + 1}": i + 1 for i in range(num_params)} + + if duration: + gate_inputs = duration_free_param_valid_input() + param_gate = gate(**gate_inputs) + for input, value in gate_inputs.items(): + if isinstance(value, FreeParameter): + gate_inputs[input] = mapping[value.name] + expected = gate(**gate_inputs) + else: + param_gate = gate(*thetas) + expected = gate(*range(1, num_params + 1)) + new_gate = param_gate.bind_values(**mapping) - expected = gate(*range(num_params)) assert type(new_gate) is type(param_gate) and new_gate == expected - if triple_angled: - for angle in new_gate.angle_1, new_gate.angle_2, new_gate.angle_3: - assert isinstance(angle, float) - elif double_angled: - for angle in new_gate.angle_1, new_gate.angle_2: - assert isinstance(angle, float) - else: - assert isinstance(new_gate.angle, float) + + for attr in attributes: + assert isinstance(getattr(new_gate, attr), float) def test_bind_values_pulse_gate(): @@ -1281,7 +1413,17 @@ def test_gate_power(gate, target, power, expected_ir): ) -def test_hash(): - assert hash(Gate.Unitary(Gate.CCNot().to_matrix())) == hash( - Gate.Unitary(Gate.CCNot().to_matrix()) - ) +@pytest.mark.parametrize("gate", [Gate.Unitary(Gate.CCNot().to_matrix()), Gate.Delay(3, 30e-9)]) +def test_hash(gate): + assert hash(gate) == hash(gate) + + +@pytest.mark.parametrize( + "gate, ascii_symbols", + ( + (Gate.Barrier(3), ("||",) * 3), + (Gate.Delay(3, 30e-9), ("delay(30.0ns)",) * 3), + ), +) +def test_ascii_characters(gate, ascii_symbols): + assert gate.ascii_symbols == ascii_symbols diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index efc87ee24..e4a522f4b 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -51,6 +51,21 @@ def test_one_gate_one_qubit(): _assert_correct_diagram(circ, expected) +def test_barrier_circuit_visualization(): + circ = Circuit().barrier(target=[0, 100]) + expected = ( + "T : │ 0 │", + " ", + "q0 : ───▒────", + " │ ", + " │ ", + "q100 : ───▒────", + " ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + def test_one_gate_one_qubit_rotation(): circ = Circuit().rx(angle=3.14, target=0) # Column formats to length of the gate plus the ascii representation for the angle.