Skip to content
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

feature: Add delay and barrier for circuits #993

Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a6faeac
Feature: Add delay and barrier for circuits
Manvi-Agrawal Jun 5, 2024
8c05927
doc: Add documentation for delay and barriers
Manvi-Agrawal Jun 5, 2024
704439f
fix: update hash function
Manvi-Agrawal Jun 5, 2024
fbfc53a
rename
Manvi-Agrawal Jun 5, 2024
0ffcdac
fix: whitespace fix
Manvi-Agrawal Jun 5, 2024
aa2d365
Apply suggestions from code review
Manvi-Agrawal Jun 5, 2024
40069fa
Address PR comments
Manvi-Agrawal Jun 5, 2024
f29f49f
Update test/unit_tests/braket/circuits/test_gates.py
Manvi-Agrawal Jun 5, 2024
9358d25
fix: update failing tests
Manvi-Agrawal Jun 5, 2024
2bba6ba
Change duration
Manvi-Agrawal Jun 5, 2024
5f15e12
change: add unit tests as per PR feedback
Manvi-Agrawal Jun 5, 2024
ff8295f
Apply suggestions from code review
Manvi-Agrawal Jun 5, 2024
55018e9
Addressing pr feedback
Manvi-Agrawal Jun 5, 2024
24946be
Merge branch 'hack/delay-barrier' of https://github.com/Manvi-Agrawal…
Manvi-Agrawal Jun 5, 2024
ef44828
pr feedback: new function
Manvi-Agrawal Jun 5, 2024
d8ece2e
fix: update int as param in delay and barrier
Manvi-Agrawal Jun 6, 2024
d7887e5
fix: duration accepts FreeParameter expression
Manvi-Agrawal Jun 6, 2024
6689001
fix: fix lint errors
Manvi-Agrawal Jun 6, 2024
8538e1e
feat: Support SI units, write ascii tests, etc
Manvi-Agrawal Jun 6, 2024
c44e576
fix: lint errors
Manvi-Agrawal Jun 6, 2024
57a42c1
change: Add tests for IR generation of FreeParameter delay.
Manvi-Agrawal Jun 6, 2024
2752a5e
add tests for ASCII and circuit visualization
Manvi-Agrawal Jun 7, 2024
eb5af0b
change: add tests for DurationGate
Manvi-Agrawal Jun 7, 2024
a5745a4
lint fix
Manvi-Agrawal Jun 7, 2024
112cc3b
chore: Add more tests
Manvi-Agrawal Jun 7, 2024
79945c3
fix: failing test
Manvi-Agrawal Jun 7, 2024
5f097e0
revert
Manvi-Agrawal Jun 7, 2024
2adb6f6
Rearrande tests and minor edit
Manvi-Agrawal Jun 7, 2024
ce359f5
Pr feedback and cleanup
Manvi-Agrawal Jun 7, 2024
a07eadd
Merge branch 'main' into hack/delay-barrier
rmshaffer Jun 10, 2024
43c8156
Merge branch 'main' into hack/delay-barrier
rmshaffer Jun 13, 2024
ebc96f0
Merge branch 'main' into hack/delay-barrier
rmshaffer Jun 17, 2024
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
71 changes: 71 additions & 0 deletions src/braket/circuits/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -3847,6 +3847,77 @@ def pulse_gate(
Gate.register_gate(PulseGate)


class Barrier(Gate):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sold yet on whether it should be a Gate or something else like a CompilerDirective.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcjaskula-aws , @rmshaffer , did you get a chance to brainstorm if its okay to proceed with Barrier as a Gate?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jcjaskula-aws - I think CompilerDirective makes the most sense here, since these are not instructions which directly affect the state of the qubits.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Manvi-Agrawal were you able to try converting this to be a CompilerDirective instead of a Gate? Please let us know if you hit any issues with this.

r"""Barrier gate."""

def __init__(self, qubit_count):
super().__init__(qubit_count=qubit_count, ascii_symbols=["||" for i in range(qubit_count)])
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved

def bind_values(self, **kwargs) -> Any:
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
raise NotImplementedError

@property
def _qasm_name(self) -> str:
return "barrier"

def __hash__(self):
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
return hash((self.name, self.qubit_count))

@staticmethod
@circuit.subroutine(register=True)
def barrier(targets: QubitSetInput) -> Instruction:
r"""Barrier gate.

Args:
targets (QubitSetInput): Target qubit(s)

Examples:
>>> circ = Circuit().barrier(targets=[0, 1, 2])
"""
return Instruction(Barrier(len(targets)), target=targets)
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved


Gate.register_gate(Barrier)


class Delay(Gate):
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
r"""Delay gate. Applies delay in seconds."""

def __init__(self, qubit_count, duration):
super().__init__(
qubit_count=qubit_count,
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
ascii_symbols=[f"delay({duration})" for i in range(qubit_count)],
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
)
self.duration = duration

def bind_values(self, **kwargs) -> Any:
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
raise NotImplementedError

@property
def _qasm_name(self) -> str:
return f"delay[{self.duration}s]"
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved

def __hash__(self):
return hash((self.name, self.qubit_count, self.duration))

@staticmethod
@circuit.subroutine(register=True)
def delay(targets: QubitSetInput, duration: float) -> Instruction:
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
r"""Delay gate. Applies delay in seconds.

Args:
targets (QubitSetInput): Target qubit(s)
duration (float): Delay (in seconds).

Examples:
>>> circ = Circuit().delay(targets=[0, 1, 2], duration=30e-9)
"""
return Instruction(Delay(len(targets), duration), target=targets)


Gate.register_gate(Delay)


def format_complex(number: complex) -> str:
"""Format a complex number into <a> + <b>im to be consumed by the braket unitary pragma

Expand Down
98 changes: 82 additions & 16 deletions test/unit_tests/braket/circuits/test_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,6 +40,14 @@ class NoTarget:
pass


class Duration(BaseModel):
duration: confloat(ge=0)
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved


class NoMatrixGeneration:
pass


class DoubleAngle:
pass

Expand Down Expand Up @@ -68,6 +77,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, [MultiTarget, NoMatrixGeneration], {}),
(Gate.Delay, "delay", None, [MultiTarget, Duration, NoMatrixGeneration], {}),
(Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl], {}),
(Gate.CV, "cv", ir.CV, [SingleTarget, SingleControl], {}),
(Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl], {}),
Expand Down Expand Up @@ -170,6 +181,15 @@ def no_target_valid_input(**kwargs):
return {}


def no_matrix_valid_input(**kwargs):
qubit_count = 1
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved

if kwargs.get("target", None) is not None:
qubit_count = len(kwargs.get("target"))

return {"qubit_count": qubit_count}


def single_target_valid_input(**kwargs):
return {"target": 2}

Expand All @@ -194,6 +214,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": 30.0}
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved


def single_control_valid_input(**kwargs):
return {"control": 0}

Expand Down Expand Up @@ -225,6 +249,7 @@ def two_dimensional_matrix_valid_input(**kwargs):

valid_ir_switcher = {
"NoTarget": no_target_valid_input,
"NoMatrix": no_target_valid_input,
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
"SingleTarget": single_target_valid_input,
"DoubleTarget": double_target_valid_ir_input,
"Angle": angle_valid_input,
Expand All @@ -235,6 +260,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(
Expand Down Expand Up @@ -284,7 +310,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)
Expand All @@ -302,6 +335,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


Expand All @@ -326,12 +363,14 @@ def calculate_qubit_count(irsubclasses):
qubit_count += 3
elif subclass not in (
NoTarget,
NoMatrixGeneration,
Angle,
Duration,
TwoDimensionalMatrix,
DoubleAngle,
TripleAngle,
):
raise ValueError("Invalid subclass")
raise ValueError(f"Invalid subclass: {subclass}")
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
return qubit_count


Expand Down Expand Up @@ -408,6 +447,30 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs
OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
"h $4;",
),
(
Gate.Barrier(3),
[3, 4, 5],
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
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=3, duration=30),
[3, 4, 5],
OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL),
"delay[30 s] q[3], q[4], q[5];",
),
(
Gate.Delay(qubit_count=3, duration=30),
[3, 4, 5],
OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL),
"delay[30 s] $3, $4, $5;",
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
),
(
Gate.Ry(angle=0.17),
[4],
Expand Down Expand Up @@ -889,12 +952,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)
Expand Down Expand Up @@ -979,19 +1040,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:
Copy link
Contributor

@jcjaskula-aws jcjaskula-aws Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me think that we need to check if it works (or at least returns an understandable error message) with simulators.

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)
Expand Down Expand Up @@ -1042,6 +1105,7 @@ def test_large_unitary():
def test_bind_values(gate):
double_angled = gate.__name__ in ["PRx"]
triple_angled = gate.__name__ in ("MS", "U")
duration = gate.__name__ in ("Delay")
num_params = 1
if triple_angled:
num_params = 3
Expand All @@ -1060,6 +1124,8 @@ def test_bind_values(gate):
elif double_angled:
for angle in new_gate.angle_1, new_gate.angle_2:
assert isinstance(angle, float)
elif duration:
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
assert isinstance(new_gate.duration, float)
else:
assert isinstance(new_gate.angle, float)

Expand Down