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 all 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
203 changes: 203 additions & 0 deletions src/braket/circuits/duration_gate.py
Original file line number Diff line number Diff line change
@@ -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."""
Comment on lines +42 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a note, there is quite a bit of duplication between this DurationGate class and the existing AngledGate class. Did you consider refactoring to make a common base class for instructions with a single float parameter? I guess this would also ideally extend to the DoubleAngledGate and TripleAngledGate classes - so maybe outside the scope of this PR and could be tracked by a separate issue.


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)
7 changes: 6 additions & 1 deletion src/braket/circuits/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
Expand Down
77 changes: 77 additions & 0 deletions src/braket/circuits/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -3847,6 +3853,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=["||"] * qubit_count)

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"

@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,
Manvi-Agrawal marked this conversation as resolved.
Show resolved Hide resolved
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 <a> + <b>im to be consumed by the braket unitary pragma

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
44 changes: 44 additions & 0 deletions test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading
Loading