From f1a549a678a057c5c1341d5099a7e7cdac7db333 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 15 Nov 2023 10:00:39 -0800 Subject: [PATCH 01/29] update: default no longer returning RETIRED devices from get_devices (#796) --- src/braket/aws/aws_device.py | 12 +++++++----- src/braket/aws/aws_session.py | 12 ++++++++---- test/integ_tests/test_device_creation.py | 2 +- .../unit_tests/braket/aws/test_aws_session.py | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index c9a208411..0cff446db 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -564,13 +564,15 @@ def get_devices( >>> AwsDevice.get_devices(types=['SIMULATOR']) Args: - arns (Optional[list[str]]): device ARN list, default is `None` - names (Optional[list[str]]): device name list, default is `None` - types (Optional[list[AwsDeviceType]]): device type list, default is `None` + arns (Optional[list[str]]): device ARN filter, default is `None` + names (Optional[list[str]]): device name filter, default is `None` + types (Optional[list[AwsDeviceType]]): device type filter, default is `None` QPUs will be searched for all regions and simulators will only be searched for the region of the current session. - statuses (Optional[list[str]]): device status list, default is `None` - provider_names (Optional[list[str]]): provider name list, default is `None` + statuses (Optional[list[str]]): device status filter, default is `None`. When `None` + is used, RETIRED devices will not be returned. To include RETIRED devices in + the results, use a filter that includes "RETIRED" for this parameter. + provider_names (Optional[list[str]]): provider name filter, default is `None` order_by (str): field to order result by, default is `name`. Accepted values are ['arn', 'name', 'type', 'provider_name', 'status'] aws_session (Optional[AwsSession]): An AWS session object. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 9523f0573..2ebc3c8d2 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -622,10 +622,12 @@ def search_devices( all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. Args: - arns (Optional[list[str]]): device ARN list, default is `None`. - names (Optional[list[str]]): device name list, default is `None`. - types (Optional[list[str]]): device type list, default is `None`. - statuses (Optional[list[str]]): device status list, default is `None`. + arns (Optional[list[str]]): device ARN filter, default is `None`. + names (Optional[list[str]]): device name filter, default is `None`. + types (Optional[list[str]]): device type filter, default is `None`. + statuses (Optional[list[str]]): device status filter, default is `None`. When `None` + is used, RETIRED devices will not be returned. To include RETIRED devices in + the results, use a filter that includes "RETIRED" for this parameter. provider_names (Optional[list[str]]): provider name list, default is `None`. Returns: @@ -645,6 +647,8 @@ def search_devices( continue if statuses and result["deviceStatus"] not in statuses: continue + if statuses is None and result["deviceStatus"] == "RETIRED": + continue if provider_names and result["providerName"] not in provider_names: continue results.append(result) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index decd7d876..4cb7de2b1 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -18,7 +18,7 @@ from braket.aws import AwsDevice from braket.devices import Devices -RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" +RIGETTI_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index d2ee45a3a..c61d22606 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -673,6 +673,18 @@ def test_cancel_job_surfaces_errors(exception_type, aws_session): }, ], ), + ( + {"statuses": ["RETIRED"]}, + [ + { + "deviceArn": "arn4", + "deviceName": "name4", + "deviceType": "QPU", + "deviceStatus": "RETIRED", + "providerName": "pname3", + }, + ], + ), ( {"provider_names": ["pname2"]}, [ @@ -745,6 +757,13 @@ def test_search_devices(input, output, aws_session): "deviceStatus": "ONLINE", "providerName": "pname2", }, + { + "deviceArn": "arn4", + "deviceName": "name4", + "deviceType": "QPU", + "deviceStatus": "RETIRED", + "providerName": "pname3", + }, ] } ] From 0485562077c83eec9c2780f78c8ec44995d6d684 Mon Sep 17 00:00:00 2001 From: ykharkov <112575584+ykharkov@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:02:29 -0500 Subject: [PATCH 02/29] doc: Add matrix expressions to docstrings (#756) * Add matrix expressions to docstrings * linter * fix r-strings * Capitalize H gate * Rename theta->phi * Address comments * Update src/braket/circuits/gates.py Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> * add matrices to subrountines * Remove [Registers this function into the circuit class] line from docstrings --------- Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Cody Wang Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Co-authored-by: Abe Coull Co-authored-by: Aaron Berdy --- src/braket/circuits/gates.py | 719 +++++++++++++++++++++++++++++++---- 1 file changed, 639 insertions(+), 80 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index dbc2e1a0b..9a4214c37 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -60,7 +60,14 @@ class H(Gate): - """Hadamard gate.""" + r"""Hadamard gate. + + Unitary matrix: + + .. math:: \mathtt{H} = \frac{1}{\sqrt{2}} \begin{bmatrix} + 1 & 1 \\ + 1 & -1 \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["H"]) @@ -91,7 +98,13 @@ def h( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Hadamard gate. + + Unitary matrix: + + .. math:: \mathtt{H} = \frac{1}{\sqrt{2}} \begin{bmatrix} + 1 & 1 \\ + 1 & -1 \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -125,7 +138,14 @@ def h( class I(Gate): # noqa: E742, E261 - """Identity gate.""" + r"""Identity gate. + + Unitary matrix: + + .. math:: \mathtt{I} = \begin{bmatrix} + 1 & 0 \\ + 0 & 1 \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["I"]) @@ -156,7 +176,13 @@ def i( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Identity gate. + + Unitary matrix: + + .. math:: \mathtt{I} = \begin{bmatrix} + 1 & 0 \\ + 0 & 1 \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -190,7 +216,15 @@ def i( class X(Gate): - """Pauli-X gate.""" + r"""Pauli-X gate. + + Unitary matrix: + + .. math:: \mathtt{X} = \begin{bmatrix} + 0 & 1 \\ + 1 & 0 + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["X"]) @@ -221,7 +255,14 @@ def x( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Pauli-X gate. + + Unitary matrix: + + .. math:: \mathtt{X} = \begin{bmatrix} + 0 & 1 \\ + 1 & 0 + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -255,7 +296,15 @@ def x( class Y(Gate): - """Pauli-Y gate.""" + r"""Pauli-Y gate. + + Unitary matrix: + + .. math:: \mathtt{Y} = \begin{bmatrix} + 0 & -i \\ + i & 0 + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Y"]) @@ -286,7 +335,14 @@ def y( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Pauli-Y gate. + + Unitary matrix: + + .. math:: \mathtt{Y} = \begin{bmatrix} + 0 & -i \\ + i & 0 + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -320,7 +376,15 @@ def y( class Z(Gate): - """Pauli-Z gate.""" + r"""Pauli-Z gate. + + Unitary matrix: + + .. math:: \mathtt{Z} = \begin{bmatrix} + 1 & 0 \\ + 0 & -1 + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Z"]) @@ -351,7 +415,12 @@ def z( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Pauli-Z gate. + + .. math:: \mathtt{Z} = \begin{bmatrix} + 1 & 0 \\ + 0 & -1 + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -385,7 +454,15 @@ def z( class S(Gate): - """S gate.""" + r"""S gate. + + Unitary matrix: + + .. math:: \mathtt{S} = \begin{bmatrix} + 1 & 0 \\ + 0 & i + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["S"]) @@ -416,7 +493,12 @@ def s( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""S gate. + + .. math:: \mathtt{S} = \begin{bmatrix} + 1 & 0 \\ + 0 & i + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -450,7 +532,15 @@ def s( class Si(Gate): - """Conjugate transpose of S gate.""" + r"""Conjugate transpose of S gate. + + Unitary matrix: + + .. math:: \mathtt{S}^\dagger = \begin{bmatrix} + 1 & 0 \\ + 0 & -i + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Si"]) @@ -481,7 +571,12 @@ def si( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Conjugate transpose of S gate. + + .. math:: \mathtt{S}^\dagger = \begin{bmatrix} + 1 & 0 \\ + 0 & -i + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -515,7 +610,15 @@ def si( class T(Gate): - """T gate.""" + r"""T gate. + + Unitary matrix: + + .. math:: \mathtt{T} = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \pi/4} + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["T"]) @@ -546,7 +649,12 @@ def t( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""T gate. + + .. math:: \mathtt{T} = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \pi/4} + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -580,7 +688,15 @@ def t( class Ti(Gate): - """Conjugate transpose of T gate.""" + r"""Conjugate transpose of T gate. + + Unitary matrix: + + .. math:: \mathtt{T}^\dagger = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{-i \pi/4} + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Ti"]) @@ -611,7 +727,12 @@ def ti( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Conjugate transpose of T gate. + + .. math:: \mathtt{T}^\dagger = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{-i \pi/4} + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -645,7 +766,15 @@ def ti( class V(Gate): - """Square root of not gate.""" + r"""Square root of X gate (V gate). + + Unitary matrix: + + .. math:: \mathtt{V} = \frac{1}{2}\begin{bmatrix} + 1+i & 1-i \\ + 1-i & 1+i + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["V"]) @@ -676,7 +805,12 @@ def v( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Square root of X gate (V gate). + + .. math:: \mathtt{V} = \frac{1}{2}\begin{bmatrix} + 1+i & 1-i \\ + 1-i & 1+i + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -710,7 +844,15 @@ def v( class Vi(Gate): - """Conjugate transpose of square root of not gate.""" + r"""Conjugate transpose of square root of X gate (conjugate transpose of V). + + Unitary matrix: + + .. math:: \mathtt{V}^\dagger = \frac{1}{2}\begin{bmatrix} + 1-i & 1+i \\ + 1+i & 1-i + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["Vi"]) @@ -741,7 +883,12 @@ def vi( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Conjugate transpose of square root of X gate (conjugate transpose of V). + + .. math:: \mathtt{V}^\dagger = \frac{1}{2}\begin{bmatrix} + 1-i & 1+i \\ + 1+i & 1-i + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s) @@ -778,7 +925,14 @@ def vi( class Rx(AngledGate): - """X-axis rotation gate. + r"""X-axis rotation gate. + + Unitary matrix: + + .. math:: \mathtt{R_x}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & -i \sin{(\phi/2)} \\ + -i \sin{(\phi/2)} & \cos{(\phi/2)} + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -799,7 +953,7 @@ def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any: return ir.Rx.construct(target=target[0], angle=self.angle) def to_matrix(self) -> np.ndarray: - """Returns a matrix representation of this gate. + r"""Returns a matrix representation of this gate. Returns: ndarray: The matrix representation of this gate. """ @@ -824,7 +978,12 @@ def rx( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""X-axis rotation gate. + + .. math:: \mathtt{R_x}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & -i \sin{(\phi/2)} \\ + -i \sin{(\phi/2)} & \cos{(\phi/2)} + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s). @@ -858,7 +1017,14 @@ def rx( class Ry(AngledGate): - """Y-axis rotation gate. + r"""Y-axis rotation gate. + + Unitary matrix: + + .. math:: \mathtt{R_y}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & -\sin{(\phi/2)} \\ + \sin{(\phi/2)} & \cos{(\phi/2)} + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -879,7 +1045,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Ry.construct(target=target[0], angle=self.angle) def to_matrix(self) -> np.ndarray: - """Returns a matrix representation of this gate. + r"""Returns a matrix representation of this gate. Returns: ndarray: The matrix representation of this gate. """ @@ -904,7 +1070,12 @@ def ry( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Y-axis rotation gate. + + .. math:: \mathtt{R_y}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & -\sin{(\phi/2)} \\ + \sin{(\phi/2)} & \cos{(\phi/2)} + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s). @@ -923,6 +1094,7 @@ def ry( Returns: Iterable[Instruction]: Rx instruction. + Examples: >>> circ = Circuit().ry(0, 0.15) """ @@ -938,7 +1110,14 @@ def ry( class Rz(AngledGate): - """Z-axis rotation gate. + r"""Z-axis rotation gate. + + Unitary matrix: + + .. math:: \mathtt{R_z}(\phi) = \begin{bmatrix} + e^{-i \phi/2} & 0 \\ + 0 & e^{i \phi/2} + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -980,7 +1159,12 @@ def rz( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Z-axis rotation gate. + + .. math:: \mathtt{R_z}(\phi) = \begin{bmatrix} + e^{-i \phi/2} & 0 \\ + 0 & e^{i \phi/2} + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s). @@ -1014,7 +1198,14 @@ def rz( class PhaseShift(AngledGate): - """Phase shift gate. + r"""Phase shift gate. + + Unitary matrix: + + .. math:: \mathtt{PhaseShift}(\phi) = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \phi} + \end{bmatrix} Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -1054,7 +1245,12 @@ def phaseshift( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""Phase shift gate. + + .. math:: \mathtt{PhaseShift}(\phi) = \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \phi} + \end{bmatrix} Args: target (QubitSetInput): Target qubit(s). @@ -1095,7 +1291,17 @@ def phaseshift( class CNot(Gate): - """Controlled NOT gate.""" + r"""Controlled NOT gate. + + Unitary matrix: + + .. math:: \mathtt{CNOT} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 \\ + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "X"]) @@ -1128,7 +1334,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cnot(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled NOT gate. + + .. math:: \mathtt{CNOT} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 \\ + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1155,7 +1368,17 @@ def cnot(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instru class Swap(Gate): - """Swap gate.""" + r"""Swap gate. + + Unitary matrix: + + .. math:: \mathtt{SWAP} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["SWAP", "SWAP"]) @@ -1195,7 +1418,14 @@ def swap( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Swap gate. + + .. math:: \mathtt{SWAP} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -1230,7 +1460,17 @@ def swap( class ISwap(Gate): - """ISwap gate.""" + r"""ISwap gate. + + Unitary matrix: + + .. math:: \mathtt{iSWAP} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & i & 0 \\ + 0 & i & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ISWAP", "ISWAP"]) @@ -1270,7 +1510,14 @@ def iswap( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""ISwap gate. + + .. math:: \mathtt{iSWAP} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & i & 0 \\ + 0 & i & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -1305,7 +1552,16 @@ def iswap( class PSwap(AngledGate): - """PSwap gate. + r"""PSwap gate. + + Unitary matrix: + + .. math:: \mathtt{PSWAP}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & e^{i \phi} & 0 \\ + 0 & e^{i \phi} & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -1357,7 +1613,14 @@ def pswap( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""PSwap gate. + + .. math:: \mathtt{PSWAP}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 0 & e^{i \phi} & 0 \\ + 0 & e^{i \phi} & 0 & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -1393,10 +1656,20 @@ def pswap( class XY(AngledGate): - """XY gate. + r"""XY gate. + + Unitary matrix: + + .. math:: \mathtt{XY}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos{(\phi/2)} & i\sin{(\phi/2)} & 0 \\ + 0 & i\sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Reference: https://arxiv.org/abs/1912.04424v1 + Args: angle (Union[FreeParameterExpression, float]): angle in radians. """ @@ -1419,7 +1692,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any: return ir.XY.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: - """Returns a matrix representation of this gate. + r"""Returns a matrix representation of this gate. Returns: ndarray: The matrix representation of this gate. """ @@ -1453,7 +1726,14 @@ def xy( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""XY gate. + + .. math:: \mathtt{XY}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & \cos{(\phi/2)} & i\sin{(\phi/2)} & 0 \\ + 0 & i\sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ + 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -1489,7 +1769,16 @@ def xy( class CPhaseShift(AngledGate): - """Controlled phase shift gate. + r"""Controlled phase shift gate. + + Unitary matrix: + + .. math:: \mathtt{CPhaseShift}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \phi} + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -1527,7 +1816,14 @@ def cphaseshift( angle: Union[FreeParameterExpression, float], power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled phase shift gate. + + .. math:: \mathtt{CPhaseShift}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \phi} + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1558,7 +1854,16 @@ def cphaseshift( class CPhaseShift00(AngledGate): - """Controlled phase shift gate for phasing the \\|00> state. + r"""Controlled phase shift gate for phasing the \|00> state. + + Unitary matrix: + + .. math:: \mathtt{CPhaseShift00}(\phi) = \begin{bmatrix} + e^{i \phi} & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -1596,7 +1901,14 @@ def cphaseshift00( angle: Union[FreeParameterExpression, float], power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled phase shift gate for phasing the \|00> state. + + .. math:: \mathtt{CPhaseShift00}(\phi) = \begin{bmatrix} + e^{i \phi} & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1627,7 +1939,16 @@ def cphaseshift00( class CPhaseShift01(AngledGate): - """Controlled phase shift gate for phasing the \\|01> state. + r"""Controlled phase shift gate for phasing the \|01> state. + + Unitary matrix: + + .. math:: \mathtt{CPhaseShift01}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & e^{i \phi} & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -1665,7 +1986,14 @@ def cphaseshift01( angle: Union[FreeParameterExpression, float], power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled phase shift gate for phasing the \|01> state. + + .. math:: \mathtt{CPhaseShift01}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & e^{i \phi} & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1696,7 +2024,16 @@ def cphaseshift01( class CPhaseShift10(AngledGate): - """Controlled phase shift gate for phasing the \\|10> state. + r"""Controlled phase shift gate for phasing the \\|10> state. + + Unitary matrix: + + .. math:: \mathtt{CPhaseShift10}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & e^{i \phi} & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -1734,7 +2071,14 @@ def cphaseshift10( angle: Union[FreeParameterExpression, float], power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled phase shift gate for phasing the \\|10> state. + + .. math:: \mathtt{CPhaseShift10}(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & e^{i \phi} & 0 \\ + 0 & 0 & 0 & 1 + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1765,7 +2109,17 @@ def cphaseshift10( class CV(Gate): - """Controlled Sqrt of NOT gate.""" + r"""Controlled Sqrt of X gate. + + Unitary matrix: + + .. math:: \mathtt{CV} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0.5+0.5i & 0.5-0.5i \\ + 0 & 0 & 0.5-0.5i & 0.5+0.5i + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "V"]) @@ -1798,7 +2152,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cv(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled Sqrt of X gate. + + .. math:: \mathtt{CV} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0.5+0.5i & 0.5-0.5i \\ + 0 & 0 & 0.5-0.5i & 0.5+0.5i + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1825,7 +2186,17 @@ def cv(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruct class CY(Gate): - """Controlled Pauli-Y gate.""" + r"""Controlled Pauli-Y gate. + + Unitary matrix: + + .. math:: \mathtt{CY} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & -i \\ + 0 & 0 & i & 0 + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Y"]) @@ -1858,7 +2229,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cy(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled Pauli-Y gate. + + .. math:: \mathtt{CY} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & -i \\ + 0 & 0 & i & 0 + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1885,7 +2263,17 @@ def cy(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruct class CZ(Gate): - """Controlled Pauli-Z gate.""" + r"""Controlled Pauli-Z gate. + + Unitary matrix: + + .. math:: \mathtt{CZ} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "Z"]) @@ -1910,7 +2298,14 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def cz(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled Pauli-Z gate. + + .. math:: \mathtt{CZ} = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & -1 + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -1937,7 +2332,17 @@ def cz(control: QubitSetInput, target: QubitInput, power: float = 1) -> Instruct class ECR(Gate): - """An echoed RZX(pi/2) gate.""" + r"""An echoed RZX(pi/2) gate (ECR gate). + + Unitary matrix: + + .. math:: \mathtt{ECR} = \begin{bmatrix} + 0 & 0 & 1 & i \\ + 0 & 0 & i & 1 \\ + 1 & -i & 0 & 0 \\ + -i & 1 & 0 & 0 + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["ECR", "ECR"]) @@ -1976,7 +2381,14 @@ def ecr( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""An echoed RZX(pi/2) gate (ECR gate). + + .. math:: \mathtt{ECR} = \begin{bmatrix} + 0 & 0 & 1 & i \\ + 0 & 0 & i & 1 \\ + 1 & -i & 0 & 0 \\ + -i & 1 & 0 & 0 + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -2011,7 +2423,16 @@ def ecr( class XX(AngledGate): - """Ising XX coupling gate. + r"""Ising XX coupling gate. + + Unitary matrix: + + .. math:: \mathtt{XX}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & 0 & 0 & -i \sin{(\phi/2)} \\ + 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ + 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ + -i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} + \end{bmatrix}. Reference: https://arxiv.org/abs/1707.06356 @@ -2037,7 +2458,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any: return ir.XX.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: - """Returns a matrix representation of this gate. + r"""Returns a matrix representation of this gate. Returns: ndarray: The matrix representation of this gate. """ @@ -2071,7 +2492,14 @@ def xx( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Ising XX coupling gate. + + .. math:: \mathtt{XX}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & 0 & 0 & -i \sin{(\phi/2)} \\ + 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ + 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ + -i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -2107,7 +2535,16 @@ def xx( class YY(AngledGate): - """Ising YY coupling gate. + r"""Ising YY coupling gate. + + Unitary matrix: + + .. math:: \mathtt{YY}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & 0 & 0 & i \sin{(\phi/2)} \\ + 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ + 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ + i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} + \end{bmatrix}. Reference: https://arxiv.org/abs/1707.06356 @@ -2133,7 +2570,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any: return ir.YY.construct(targets=[target[0], target[1]], angle=self.angle) def to_matrix(self) -> np.ndarray: - """Returns a matrix representation of this gate. + r"""Returns a matrix representation of this gate. Returns: ndarray: The matrix representation of this gate. """ @@ -2167,7 +2604,14 @@ def yy( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Ising YY coupling gate. + + .. math:: \mathtt{YY}(\phi) = \begin{bmatrix} + \cos{(\phi/2)} & 0 & 0 & i \sin{(\phi/2)} \\ + 0 & \cos{(\phi/2)} & -i \sin{(\phi/2)} & 0 \\ + 0 & -i \sin{(\phi/2)} & \cos{(\phi/2)} & 0 \\ + i \sin{(\phi/2)} & 0 & 0 & \cos{(\phi/2)} + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -2203,7 +2647,16 @@ def yy( class ZZ(AngledGate): - """Ising ZZ coupling gate. + r"""Ising ZZ coupling gate. + + Unitary matrix: + + .. math:: \mathtt{ZZ}(\phi) = \begin{bmatrix} + e^{-i\phi/2} & 0 & 0 & 0 \\ + 0 & e^{i\phi/2} & 0 & 0 \\ + 0 & 0 & e^{i\phi/2} & 0 \\ + 0 & 0 & 0 & e^{-i\phi/2} + \end{bmatrix}. Reference: https://arxiv.org/abs/1707.06356 @@ -2257,7 +2710,14 @@ def zz( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Ising ZZ coupling gate. + + .. math:: \mathtt{ZZ}(\phi) = \begin{bmatrix} + e^{-i\phi/2} & 0 & 0 & 0 \\ + 0 & e^{i\phi/2} & 0 & 0 \\ + 0 & 0 & e^{i\phi/2} & 0 \\ + 0 & 0 & 0 & e^{-i\phi/2} + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -2296,7 +2756,21 @@ def zz( class CCNot(Gate): - """CCNOT gate or Toffoli gate.""" + r"""CCNOT gate or Toffoli gate. + + Unitary matrix: + + .. math:: \mathtt{CCNOT} = \begin{bmatrix} + 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "C", "X"]) @@ -2341,7 +2815,18 @@ def ccnot( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""CCNOT gate or Toffoli gate. + + .. math:: \mathtt{CCNOT} = \begin{bmatrix} + 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + \end{bmatrix}. Args: control1 (QubitInput): Control qubit 1 index. @@ -2379,7 +2864,21 @@ def ccnot( class CSwap(Gate): - """Controlled Swap gate.""" + r"""Controlled Swap gate. + + Unitary matrix: + + .. math:: \mathtt{CSWAP} = \begin{bmatrix} + 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ + \end{bmatrix}. + """ def __init__(self): super().__init__(qubit_count=None, ascii_symbols=["C", "SWAP", "SWAP"]) @@ -2421,7 +2920,18 @@ def cswap( target2: QubitInput, power: float = 1, ) -> Instruction: - """Registers this function into the circuit class. + r"""Controlled Swap gate. + + .. math:: \mathtt{CSWAP} = \begin{bmatrix} + 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ + \end{bmatrix}. Args: control (QubitSetInput): Control qubit(s). The last control qubit @@ -2452,7 +2962,14 @@ def cswap( class GPi(AngledGate): - """IonQ GPi gate. + r"""IonQ GPi gate. + + Unitary matrix: + + .. math:: \mathtt{GPi}(\phi) = \begin{bmatrix} + 0 & e^{-i \phi} \\ + e^{i \phi} & 0 + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -2497,7 +3014,12 @@ def gpi( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""IonQ GPi gate. + + .. math:: \mathtt{GPi}(\phi) = \begin{bmatrix} + 0 & e^{-i \phi} \\ + e^{i \phi} & 0 + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s). @@ -2531,7 +3053,14 @@ def gpi( class GPi2(AngledGate): - """IonQ GPi2 gate. + r"""IonQ GPi2 gate. + + Unitary matrix: + + .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix} + 1 & -i e^{-i \phi} \\ + -i e^{i \phi} & 1 + \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -2576,7 +3105,12 @@ def gpi2( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""IonQ GPi2 gate. + + .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix} + 1 & -i e^{-i \phi} \\ + -i e^{i \phi} & 1 + \end{bmatrix}. Args: target (QubitSetInput): Target qubit(s). @@ -2610,12 +3144,26 @@ def gpi2( class MS(TripleAngledGate): - """IonQ Mølmer-Sørenson gate. + r"""IonQ Mølmer-Sørensen gate. + + Unitary matrix: + + .. math:: &\mathtt{MS}(\phi_0, \phi_1, \theta) =\\ &\begin{bmatrix} + \cos{\frac{\theta}{2}} & 0 & + 0 & -ie^{-i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} \\ + 0 & \cos{\frac{\theta}{2}} & + -ie^{-i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & 0 \\ + 0 & -ie^{i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & + \cos{\frac{\theta}{2}} & 0 \\ + -ie^{i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} & 0 + & 0 & \cos{\frac{\theta}{2}} + \end{bmatrix}. Args: angle_1 (Union[FreeParameterExpression, float]): angle in radians. angle_2 (Union[FreeParameterExpression, float]): angle in radians. angle_3 (Union[FreeParameterExpression, float]): angle in radians. + Default value is angle_3=pi/2. """ def __init__( @@ -2689,7 +3237,18 @@ def ms( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Iterable[Instruction]: - """Registers this function into the circuit class. + r"""IonQ Mølmer-Sørensen gate. + + .. math:: &\mathtt{MS}(\phi_0, \phi_1, \theta) =\\ &\begin{bmatrix} + \cos{\frac{\theta}{2}} & 0 & + 0 & -ie^{-i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} \\ + 0 & \cos{\frac{\theta}{2}} & + -ie^{-i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & 0 \\ + 0 & -ie^{i (\phi_0 - \phi_1)}\sin{\frac{\theta}{2}} & + \cos{\frac{\theta}{2}} & 0 \\ + -ie^{i (\phi_0 + \phi_1)}\sin{\frac{\theta}{2}} & 0 + & 0 & \cos{\frac{\theta}{2}} + \end{bmatrix}. Args: target1 (QubitInput): Target qubit 1 index. @@ -2729,7 +3288,7 @@ def ms( class Unitary(Gate): - """Arbitrary unitary gate + """Arbitrary unitary gate. Args: matrix (numpy.ndarray): Unitary matrix which defines the gate. @@ -2792,7 +3351,7 @@ def _transform_matrix_to_ir(matrix: np.ndarray) -> list: @staticmethod @circuit.subroutine(register=True) def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> Instruction: - """Registers this function into the circuit class. + r"""Arbitrary unitary gate. Args: targets (QubitSet): Target qubits. @@ -2849,7 +3408,7 @@ def pulse_sequence(self) -> PulseSequence: @property def parameters(self) -> list[FreeParameter]: - """Returns the list of `FreeParameter` s associated with the gate.""" + r"""Returns the list of `FreeParameter` s associated with the gate.""" return list(self._pulse_sequence.parameters) def bind_values(self, **kwargs) -> PulseGate: From 20ac4e468e03cbb24ea43b619804e0fa9734b2dc Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:46:06 -0500 Subject: [PATCH 03/29] Fix broken link to example notebook (#802) --- doc/examples-hybrid-jobs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples-hybrid-jobs.rst b/doc/examples-hybrid-jobs.rst index af873407c..56a1d9ecf 100644 --- a/doc/examples-hybrid-jobs.rst +++ b/doc/examples-hybrid-jobs.rst @@ -8,7 +8,7 @@ Learn more about hybrid jobs on Amazon Braket. :maxdepth: 2 ************************************************************************************************************************************************************************************************ -`Creating your first Hybrid Job `_ +`Creating your first Hybrid Job `_ ************************************************************************************************************************************************************************************************ This tutorial shows how to run your first Amazon Braket Hybrid Job. From 176c6a79393373c853de25d83665f2f7bd451f38 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 17 Nov 2023 04:07:56 +0000 Subject: [PATCH 04/29] prepare release v1.62.1 --- CHANGELOG.md | 11 +++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19b0fba87..876595e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.62.1 (2023-11-17) + +### Bug Fixes and Other Changes + + * Fix broken link to example notebook + * update: default no longer returning RETIRED devices from get_devices + +### Documentation Changes + + * Add matrix expressions to docstrings + ## v1.62.0 (2023-11-09) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8cc1a9a43..26e9bb0a8 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.62.1.dev0" +__version__ = "1.62.1" From 44a2bbb39d0142e74a4e39c3bf5aca0cf2785cfe Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 17 Nov 2023 04:07:56 +0000 Subject: [PATCH 05/29] update development version to v1.62.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 26e9bb0a8..c0e18d0a0 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.62.1" +__version__ = "1.62.2.dev0" From 5d8e38c3807d41631450af494b0d65db2b745f2f Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 20 Nov 2023 10:52:30 -0800 Subject: [PATCH 06/29] infra: code freeze workflow (#767) Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- .github/workflows/code-freeze.yml | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/code-freeze.yml diff --git a/.github/workflows/code-freeze.yml b/.github/workflows/code-freeze.yml new file mode 100644 index 000000000..5aff0abc7 --- /dev/null +++ b/.github/workflows/code-freeze.yml @@ -0,0 +1,39 @@ +name: Code Freeze + +on: + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +env: + FROZEN: ${{ vars.FROZEN }} + UNFROZEN_PREFIX: ${{ vars.UNFROZEN_PREFIX }} + +jobs: + check-pr-frozen-status: + runs-on: ubuntu-latest + steps: + - name: Fetch PR data and check if merge allowed + if: env.FROZEN == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_DATA=$(curl -s \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}) + BRANCH_NAME=$(echo $PR_DATA | jq .head.ref -r) + PR_TITLE=$(echo $PR_DATA | jq .title -r) + + echo $BRANCH_NAME + echo $PR_TITLE + + if [[ "$BRANCH_NAME" != $UNFROZEN_PREFIX* ]] && + [[ "$PR_TITLE" != fix:* && "$PR_TITLE" != *"[critical]"* ]]; then + echo "Error: You can only merge from branches that start with '$UNFROZEN_PREFIX', or PRs titled with 'fix: ' and containing '[critical]'." + exit 1 + fi From 8e8cdaa5cd1c62befafafd6621f8700ce64369f6 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 4 Dec 2023 16:44:08 -0800 Subject: [PATCH 07/29] Add Forte 1 device (#827) --- src/braket/devices/devices.py | 1 + test/integ_tests/test_cost_tracking.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 8f8730336..f4fe8ff8d 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -31,6 +31,7 @@ class _IonQ(str, Enum): Harmony = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" Aria1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" Aria2 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-2" + Forte1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Forte-1" class _OQC(str, Enum): Lucy = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 60ddc1b52..7e97c6f78 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -20,9 +20,12 @@ from braket.aws import AwsDevice, AwsSession from braket.circuits import Circuit +from braket.devices import Devices from braket.tracking import Tracker from braket.tracking.tracker import MIN_SIMULATOR_DURATION +_RESERVATION_ONLY_DEVICES = {Devices.IonQ.Forte1} + @pytest.mark.parametrize( "qpu", @@ -91,7 +94,7 @@ def test_all_devices_price_search(): tasks = {} for region in AwsDevice.REGIONS: s = AwsSession(boto3.Session(region_name=region)) - for device in devices: + for device in [device for device in devices if device.arn not in _RESERVATION_ONLY_DEVICES]: try: s.get_device(device.arn) From 8a8ef4ed00fed48c5a173fc28060a3ee9d530de4 Mon Sep 17 00:00:00 2001 From: Angela Guo Date: Tue, 5 Dec 2023 05:41:30 -0800 Subject: [PATCH 08/29] feat: Allow reservation ARN in task and job creation (#826) --- examples/hybrid_job.py | 9 +- examples/reservation.py | 24 +++++ src/braket/aws/aws_device.py | 15 ++- src/braket/aws/aws_quantum_job.py | 6 ++ src/braket/aws/aws_quantum_task.py | 32 ++++++- src/braket/aws/aws_quantum_task_batch.py | 13 +++ src/braket/jobs/hybrid_job.py | 6 ++ src/braket/jobs/quantum_job_creation.py | 18 ++++ src/braket/tracking/tracker.py | 18 +++- src/braket/tracking/tracking_events.py | 1 + test/integ_tests/test_reservation_arn.py | 91 +++++++++++++++++++ .../braket/aws/common_test_utils.py | 10 ++ test/unit_tests/braket/aws/test_aws_device.py | 63 +++++++++++-- .../braket/aws/test_aws_quantum_job.py | 10 +- .../braket/aws/test_aws_quantum_task.py | 60 ++++++++++++ .../braket/aws/test_aws_quantum_task_batch.py | 52 ++++++++++- .../unit_tests/braket/jobs/test_hybrid_job.py | 5 + .../braket/jobs/test_quantum_job_creation.py | 20 ++++ .../braket/tracking/test_tracker.py | 31 ++++++- 19 files changed, 463 insertions(+), 21 deletions(-) create mode 100644 examples/reservation.py create mode 100644 test/integ_tests/test_reservation_arn.py diff --git a/examples/hybrid_job.py b/examples/hybrid_job.py index 2e09d6463..7f54c9955 100644 --- a/examples/hybrid_job.py +++ b/examples/hybrid_job.py @@ -18,7 +18,14 @@ from braket.jobs.metrics import log_metric -@hybrid_job(device=Devices.Amazon.SV1, wait_until_complete=True) +@hybrid_job( + device=Devices.Amazon.SV1, + wait_until_complete=True, + # If you want to run the job in a device reservation window, + # change the device to the one you've reserved, + # uncomment the following line and fill in your reservation ARN + # reservation_arn="" +) def run_hybrid_job(num_tasks=1): # declare AwsDevice within the hybrid job device = AwsDevice(get_job_device_arn()) diff --git a/examples/reservation.py b/examples/reservation.py new file mode 100644 index 000000000..682f71f50 --- /dev/null +++ b/examples/reservation.py @@ -0,0 +1,24 @@ +# 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 braket.aws import AwsDevice +from braket.circuits import Circuit +from braket.devices import Devices + +bell = Circuit().h(0).cnot(0, 1) +device = AwsDevice(Devices.IonQ.Aria1) + +# To run a task in a device reservation, change the device to the one you reserved +# and fill in your reservation ARN +task = device.run(bell, shots=100, reservation_arn="reservation ARN") +print(task.result().measurement_counts) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 0cff446db..503834dd8 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -121,6 +121,7 @@ def run( poll_interval_seconds: Optional[float] = None, inputs: Optional[dict[str, float]] = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, + reservation_arn: str | None = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTask: @@ -150,6 +151,11 @@ def run( The calibration is defined for a particular `Gate` on a particular `QubitSet` and is represented by a `PulseSequence`. Default: None. + reservation_arn (str | None): The reservation ARN provided by Braket Direct + to reserve exclusive usage for the device to run the quantum task on. + Note: If you are creating tasks in a job that itself was created reservation ARN, + those tasks do not need to be created with the reservation ARN. + Default: None. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -199,6 +205,7 @@ def run( poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, gate_definitions=gate_definitions, + reservation_arn=reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -233,6 +240,7 @@ def run_batch( poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Optional[Union[dict[str, float], list[dict[str, float]]]] = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, + reservation_arn: Optional[str] = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -263,7 +271,11 @@ def run_batch( gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): A `dict[tuple[Gate, QubitSet], PulseSequence]]` for a user defined gate calibration. The calibration is defined for a particular `Gate` on a particular `QubitSet` - and is represented by a `PulseSequence`. + and is represented by a `PulseSequence`. Default: None. + reservation_arn (Optional[str]): The reservation ARN provided by Braket Direct + to reserve exclusive usage for the device to run the quantum task on. + Note: If you are creating tasks in a job that itself was created reservation ARN, + those tasks do not need to be created with the reservation ARN. Default: None. Returns: @@ -290,6 +302,7 @@ def run_batch( poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, gate_definitions=gate_definitions, + reservation_arn=reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 8ae4bc129..1e929e857 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -81,6 +81,7 @@ def create( aws_session: AwsSession | None = None, tags: dict[str, str] | None = None, logger: Logger = getLogger(__name__), + reservation_arn: str | None = None, ) -> AwsQuantumJob: """Creates a hybrid job by invoking the Braket CreateJob API. @@ -175,6 +176,10 @@ def create( while waiting for quantum task to be in a terminal state. Default is `getLogger(__name__)` + reservation_arn (str | None): the reservation window arn provided by Braket + Direct to reserve exclusive usage for the device to run the hybrid job on. + Default: None. + Returns: AwsQuantumJob: Hybrid job tracking the execution on Amazon Braket. @@ -201,6 +206,7 @@ def create( checkpoint_config=checkpoint_config, aws_session=aws_session, tags=tags, + reservation_arn=reservation_arn, ) job_arn = aws_session.create_job(**create_job_kwargs) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index fa58d911d..c490a4190 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -105,6 +105,7 @@ def create( tags: dict[str, str] | None = None, inputs: dict[str, float] | None = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None = None, + reservation_arn: str | None = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -151,6 +152,12 @@ def create( a `PulseSequence`. Default: None. + reservation_arn (str | None): The reservation ARN provided by Braket Direct + to reserve exclusive usage for the device to run the quantum task on. + Note: If you are creating tasks in a job that itself was created reservation ARN, + those tasks do not need to be created with the reservation ARN. + Default: None. + Returns: AwsQuantumTask: AwsQuantumTask tracking the quantum task execution on the device. @@ -179,6 +186,18 @@ def create( create_task_kwargs.update({"tags": tags}) inputs = inputs or {} + if reservation_arn: + create_task_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) + if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} unbounded_parameters = param_names - set(inputs.keys()) @@ -477,6 +496,12 @@ async def _wait_for_completion( self._result = None return None + def _has_reservation_arn_from_metadata(self, current_metadata: dict[str, Any]) -> bool: + for association in current_metadata.get("associations", []): + if association.get("type") == "RESERVATION_TIME_WINDOW_ARN": + return True + return False + def _download_result( self, ) -> Union[ @@ -488,7 +513,12 @@ def _download_result( current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", ) self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) - task_event = {"arn": self.id, "status": self.state(), "execution_duration": None} + task_event = { + "arn": self.id, + "status": self.state(), + "execution_duration": None, + "has_reservation_arn": self._has_reservation_arn_from_metadata(current_metadata), + } try: task_event[ "execution_duration" diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index adf15bda3..24ff15530 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -61,6 +61,7 @@ def __init__( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] | None = None, + reservation_arn: str | None = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ): @@ -91,6 +92,11 @@ def __init__( inputs (Union[dict[str, float], list[dict[str, float]]] | None): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. + reservation_arn (str | None): The reservation ARN provided by Braket Direct + to reserve exclusive usage for the device to run the quantum task on. + Note: If you are creating tasks in a job that itself was created reservation ARN, + those tasks do not need to be created with the reservation ARN. + Default: None. """ self._tasks = AwsQuantumTaskBatch._execute( aws_session, @@ -103,6 +109,7 @@ def __init__( poll_timeout_seconds, poll_interval_seconds, inputs, + reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) @@ -120,6 +127,7 @@ def __init__( self._poll_timeout_seconds = poll_timeout_seconds self._poll_interval_seconds = poll_interval_seconds self._inputs = inputs + self._reservation_arn = reservation_arn self._aws_quantum_task_args = aws_quantum_task_args self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @@ -197,6 +205,7 @@ def _execute( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] = None, + reservation_arn: str | None = None, *args, **kwargs, ) -> list[AwsQuantumTask]: @@ -217,6 +226,7 @@ def _execute( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, inputs=input_map, + reservation_arn=reservation_arn, *args, **kwargs, ) @@ -248,6 +258,7 @@ def _create_task( shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: dict[str, float] = None, + reservation_arn: str | None = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -259,6 +270,7 @@ def _create_task( shots, poll_interval_seconds=poll_interval_seconds, inputs=inputs, + reservation_arn=reservation_arn, *args, **kwargs, ) @@ -347,6 +359,7 @@ def retry_unsuccessful_tasks(self) -> bool: self._max_workers, self._poll_timeout_seconds, self._poll_interval_seconds, + self._reservation_arn, *self._aws_quantum_task_args, **self._aws_quantum_task_kwargs, ) diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index ae17c2715..707f18fd5 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -63,6 +63,7 @@ def hybrid_job( aws_session: AwsSession | None = None, tags: dict[str, str] | None = None, logger: Logger = getLogger(__name__), + reservation_arn: str | None = None, ) -> Callable: """Defines a hybrid job by decorating the entry point function. The job will be created when the decorated function is called. @@ -152,6 +153,10 @@ def hybrid_job( logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default: `getLogger(__name__)` + reservation_arn (str | None): the reservation window arn provided by Braket + Direct to reserve exclusive usage for the device to run the hybrid job on. + Default: None. + Returns: Callable: the callable for creating a Hybrid Job. """ @@ -205,6 +210,7 @@ def job_wrapper(*args, **kwargs) -> Callable: "output_data_config": output_data_config, "aws_session": aws_session, "tags": tags, + "reservation_arn": reservation_arn, } for key, value in optional_args.items(): if value is not None: diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index b935de7eb..657ed0829 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -55,6 +55,7 @@ def prepare_quantum_job( checkpoint_config: CheckpointConfig | None = None, aws_session: AwsSession | None = None, tags: dict[str, str] | None = None, + reservation_arn: str | None = None, ) -> dict: """Creates a hybrid job by invoking the Braket CreateJob API. @@ -140,6 +141,10 @@ def prepare_quantum_job( hybrid job. Default: {}. + reservation_arn (str | None): the reservation window arn provided by Braket + Direct to reserve exclusive usage for the device to run the hybrid job on. + Default: None. + Returns: dict: Hybrid job tracking the execution on Amazon Braket. @@ -174,6 +179,7 @@ def prepare_quantum_job( job_name, "script", ) + if AwsSession.is_s3_uri(source_module): _process_s3_source_module(source_module, entry_point, aws_session, code_location) else: @@ -230,6 +236,18 @@ def prepare_quantum_job( "tags": tags, } + if reservation_arn: + create_job_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) + return create_job_kwargs diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index c30fc774e..84b294ad2 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -159,8 +159,12 @@ def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: + details["execution_duration"] ) billed_duration = ( - device_stats.get("billed_execution_duration", timedelta(0)) - + details["billed_duration"] + timedelta(0) + if details.get("has_reservation_arn") + else ( + device_stats.get("billed_execution_duration", timedelta(0)) + + details["billed_duration"] + ) ) device_stats["execution_duration"] = duration @@ -196,14 +200,20 @@ def _(self, event: _TaskCompletionEvent) -> None: # Update task completion data corresponding to the arn only if it exists in resources if event.arn in resources: resources[event.arn]["status"] = event.status + has_reservation_arn = event.has_reservation_arn + resources[event.arn]["has_reservation_arn"] = has_reservation_arn if event.execution_duration: duration = timedelta(milliseconds=event.execution_duration) resources[event.arn]["execution_duration"] = duration - resources[event.arn]["billed_duration"] = max(duration, MIN_SIMULATOR_DURATION) + resources[event.arn]["billed_duration"] = ( + timedelta(milliseconds=0) + if has_reservation_arn + else max(duration, MIN_SIMULATOR_DURATION) + ) def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: - if details["status"] in ["FAILED", "CANCELLED"]: + if details["status"] in ["FAILED", "CANCELLED"] or details.get("has_reservation_arn"): return Decimal(0) task_region = task_arn.split(":")[3] diff --git a/src/braket/tracking/tracking_events.py b/src/braket/tracking/tracking_events.py index 6f37183c0..793ff038c 100644 --- a/src/braket/tracking/tracking_events.py +++ b/src/braket/tracking/tracking_events.py @@ -33,6 +33,7 @@ class _TaskCreationEvent(_TrackingEvent): class _TaskCompletionEvent(_TrackingEvent): execution_duration: Optional[float] status: str + has_reservation_arn: bool = False @dataclass diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py new file mode 100644 index 000000000..e0736f802 --- /dev/null +++ b/test/integ_tests/test_reservation_arn.py @@ -0,0 +1,91 @@ +# 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 sys + +import pytest +from botocore.exceptions import ClientError +from test_create_quantum_job import decorator_python_version + +from braket.aws import AwsDevice +from braket.aws.aws_quantum_job import AwsQuantumJob +from braket.circuits import Circuit +from braket.devices import Devices +from braket.jobs import get_job_device_arn, hybrid_job + + +@pytest.fixture +def reservation_arn(aws_session): + return ( + f"arn:aws:braket:{aws_session.region}" + f":{aws_session.account_id}:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ) + + +def test_create_task_via_invalid_reservation_arn_on_qpu(reservation_arn): + circuit = Circuit().h(0) + device = AwsDevice(Devices.IonQ.Harmony) + + with pytest.raises(ClientError, match="Reservation arn is invalid"): + device.run( + circuit, + shots=10, + reservation_arn=reservation_arn, + ) + + +def test_create_task_via_reservation_arn_on_simulator(reservation_arn): + circuit = Circuit().h(0) + device = AwsDevice(Devices.Amazon.SV1) + + with pytest.raises(ClientError, match="Braket Direct is not supported for"): + device.run( + circuit, + shots=10, + reservation_arn=reservation_arn, + ) + + +def test_create_job_via_invalid_reservation_arn_on_qpu(aws_session, reservation_arn): + with pytest.raises(ClientError, match="Reservation arn is invalid"): + AwsQuantumJob.create( + device=Devices.IonQ.Harmony, + source_module="test/integ_tests/job_test_script.py", + entry_point="job_test_script:start_here", + wait_until_complete=True, + aws_session=aws_session, + hyperparameters={"test_case": "completed"}, + reservation_arn=reservation_arn, + ) + + +@pytest.mark.xfail( + (sys.version_info.major, sys.version_info.minor) != decorator_python_version(), + raises=RuntimeError, + reason="Python version mismatch", +) +def test_create_job_with_decorator_via_invalid_reservation_arn(reservation_arn): + with pytest.raises(ClientError, match="Reservation arn is invalid"): + + @hybrid_job( + device=Devices.IonQ.Aria1, + reservation_arn=reservation_arn, + ) + def hello_job(): + device = AwsDevice(get_job_device_arn()) + bell = Circuit().h(0).cnot(0, 1) + task = device.run(bell, shots=10) + measurements = task.result().measurements + return measurements + + hello_job() diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 5dcec5fbc..2975912f1 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -201,6 +201,7 @@ def run_and_assert( poll_interval_seconds, # Treated as positional arg inputs, # Treated as positional arg gate_definitions, # Treated as positional arg + reservation_arn, # Treated as positional arg extra_args, extra_kwargs, ): @@ -222,6 +223,8 @@ def run_and_assert( run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} + if reservation_arn: + run_kwargs.update({"reservation_arn": reservation_arn}) task = device.run(circuit, *run_args, **run_kwargs) assert task == task_mock @@ -237,6 +240,7 @@ def run_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) @@ -263,6 +267,7 @@ def run_batch_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ): @@ -291,6 +296,8 @@ def run_batch_and_assert( run_args.append(gate_definitions) run_args += extra_args if extra_args else [] run_kwargs = extra_kwargs or {} + if reservation_arn: + run_kwargs.update({"reservation_arn": reservation_arn}) batch = device.run_batch(circuits, *run_args, **run_kwargs) assert batch.tasks == [task_mock for _ in range(len(circuits))] @@ -306,6 +313,7 @@ def run_batch_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) @@ -333,6 +341,7 @@ def _create_task_args_and_kwargs( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ): @@ -352,6 +361,7 @@ def _create_task_args_and_kwargs( else default_poll_interval, "inputs": inputs, "gate_definitions": gate_definitions, + "reservation_arn": reservation_arn, } ) return create_args, create_kwargs diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 3770d38a2..29adf2517 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -987,6 +987,16 @@ def test_run_no_extra(aws_quantum_task_mock, device, circuit): ) +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_reservation_arn(aws_quantum_task_mock, device, circuit): + _run_and_assert( + aws_quantum_task_mock, + device, + circuit, + reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", + ) + + @patch("braket.aws.aws_quantum_task.AwsQuantumTask") def test_run_param_circuit_with_no_inputs( aws_quantum_task_mock, single_circuit_input, device, s3_destination_folder @@ -1024,6 +1034,38 @@ def test_run_param_circuit_with_inputs( ) +@patch("braket.aws.aws_session.boto3.Session") +@patch("braket.aws.aws_session.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_param_circuit_with_reservation_arn_batch_task( + aws_quantum_task_mock, + aws_session_mock, + boto_session_mock, + single_circuit_input, + device, + s3_destination_folder, +): + inputs = {"theta": 0.2} + circ_1 = Circuit().rx(angle=0.2, target=0) + circuits = [circ_1, single_circuit_input] + + _run_batch_and_assert( + aws_quantum_task_mock, + aws_session_mock, + device, + circuits, + s3_destination_folder, + 10, + 20, + 50, + 43200, + 0.25, + inputs, + None, + reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", + ) + + @patch("braket.aws.aws_session.boto3.Session") @patch("braket.aws.aws_session.AwsSession") @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") @@ -1308,13 +1350,14 @@ def test_default_bucket_not_called(aws_quantum_task_mock, device, circuit, s3_de AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, circuit, s3_destination_folder, - None, - None, - None, - None, - None, - None, - None, + shots=None, + poll_timeout_seconds=None, + poll_interval_seconds=None, + inputs=None, + gate_definitions=None, + reservation_arn=None, + extra_args=None, + extra_kwargs=None, ) device._aws_session.default_bucket.assert_not_called() @@ -1375,6 +1418,8 @@ def test_run_with_positional_args_and_kwargs( 0.25, {}, ["foo"], + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", + None, {"bar": 1, "baz": 2}, ) @@ -1489,6 +1534,7 @@ def _run_and_assert( poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg gate_definitions=None, # Treated as positional arg + reservation_arn=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1506,6 +1552,7 @@ def _run_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) @@ -1524,6 +1571,7 @@ def _run_batch_and_assert( poll_interval_seconds=None, # Treated as positional arg inputs=None, # Treated as positional arg gate_definitions=None, # Treated as positional arg + reservation_arn=None, # Treated as positional arg extra_args=None, extra_kwargs=None, ): @@ -1544,6 +1592,7 @@ def _run_batch_and_assert( poll_interval_seconds, inputs, gate_definitions, + reservation_arn, extra_args, extra_kwargs, ) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 19b46d72b..3a36d8e75 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -516,7 +516,12 @@ def device_arn(request): @pytest.fixture -def prepare_job_args(aws_session, device_arn): +def reservation_arn(): + return "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + + +@pytest.fixture +def prepare_job_args(aws_session, device_arn, reservation_arn): return { "device": device_arn, "source_module": Mock(), @@ -535,6 +540,7 @@ def prepare_job_args(aws_session, device_arn): "checkpoint_config": Mock(), "aws_session": aws_session, "tags": Mock(), + "reservation_arn": reservation_arn, } @@ -1027,7 +1033,7 @@ def test_initialize_session_local_device(mock_new_session, aws_session): assert AwsQuantumJob._initialize_session(None, device, logger) == mock_new_session() -def test_bad_arn_format(aws_session): +def test_bad_device_arn_format(aws_session): logger = logging.getLogger(__name__) device_not_found = ( "Device ARN is not a valid format: bad-arn-format. For valid Braket ARNs, " diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 99270ad25..656c37dcf 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -203,6 +203,29 @@ def test_metadata_call_if_none(quantum_task): quantum_task._aws_session.get_quantum_task.assert_called_with(quantum_task.id) +def test_has_reservation_arn_from_metadata(quantum_task): + metadata_true = { + "associations": [ + { + "arn": "123", + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + assert quantum_task._has_reservation_arn_from_metadata(metadata_true) + + metadata_false = { + "status": "RUNNING", + "associations": [ + { + "arn": "123", + "type": "other", + } + ], + } + assert not quantum_task._has_reservation_arn_from_metadata(metadata_false) + + def test_queue_position(quantum_task): state_1 = "QUEUED" _mock_metadata(quantum_task._aws_session, state_1) @@ -560,6 +583,31 @@ def test_create_ahs_problem(aws_session, arn, ahs_problem): ) +def test_create_task_with_reservation_arn(aws_session, arn, ahs_problem): + aws_session.create_quantum_task.return_value = arn + shots = 21 + reservation_arn = ( + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ) + AwsQuantumTask.create( + aws_session, + SIMULATOR_ARN, + ahs_problem, + S3_TARGET, + shots, + reservation_arn=reservation_arn, + ) + + _assert_create_quantum_task_called_with( + aws_session, + SIMULATOR_ARN, + ahs_problem.to_ir().json(), + S3_TARGET, + shots, + reservation_arn=reservation_arn, + ) + + def test_create_pulse_sequence(aws_session, arn, pulse_sequence): expected_openqasm = "\n".join( [ @@ -1098,6 +1146,7 @@ def _assert_create_quantum_task_called_with( shots, device_parameters=None, tags=None, + reservation_arn=None, ): test_kwargs = { "deviceArn": arn, @@ -1111,6 +1160,17 @@ def _assert_create_quantum_task_called_with( test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) if tags is not None: test_kwargs.update({"tags": tags}) + if reservation_arn: + test_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) aws_session.create_quantum_task.assert_called_with(**test_kwargs) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py b/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py index 5802db110..2c748ecea 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py @@ -34,7 +34,16 @@ def test_creation(mock_create): batch_size = 10 batch = AwsQuantumTaskBatch( - Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=10 + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert batch.size == batch_size assert batch.tasks == [task_mock for _ in range(batch_size)] @@ -53,7 +62,16 @@ def test_successful(mock_create): batch_size = 15 batch = AwsQuantumTaskBatch( - Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=10 + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert batch.size == batch_size assert not batch.unfinished @@ -71,7 +89,16 @@ def test_unsuccessful(mock_create): mock_create.return_value = task_mock batch = AwsQuantumTaskBatch( - Mock(), "foo", [Circuit().h(0).cnot(0, 1)], S3_TARGET, 1000, max_parallel=10 + Mock(), + "foo", + [Circuit().h(0).cnot(0, 1)], + S3_TARGET, + 1000, + max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert not batch.unfinished assert batch.unsuccessful == {task_id} @@ -106,6 +133,10 @@ def test_retry(mock_create): S3_TARGET, 1000, max_parallel=10, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) assert not batch.unfinished assert batch.results(max_retries=0) == [None, result] @@ -142,6 +173,10 @@ def test_abort(mock_threadpool): S3_TARGET, 1000, max_parallel=num_workers, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) @@ -153,7 +188,16 @@ def test_early_abort(mock_submit): with pytest.raises(KeyboardInterrupt): AwsQuantumTaskBatch( - Mock(), "foo", _circuits(batch_size), S3_TARGET, 1000, max_parallel=num_workers + Mock(), + "foo", + _circuits(batch_size), + S3_TARGET, + 1000, + max_parallel=num_workers, + reservaion_arn=( + "arn:aws:braket:us-west-2:123456789123:" + "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ), ) diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index b7b7485d7..e757c6a69 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -105,6 +105,9 @@ def test_decorator_non_defaults( output_data_config = OutputDataConfig(s3Path="s3") aws_session = MagicMock() tags = {"my_tag": "my_value"} + reservation_arn = ( + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + ) logger = getLogger(__name__) with tempfile.TemporaryDirectory() as tempdir: @@ -135,6 +138,7 @@ def test_decorator_non_defaults( output_data_config=output_data_config, aws_session=aws_session, tags=tags, + reservation_arn=reservation_arn, logger=logger, ) def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: @@ -184,6 +188,7 @@ def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: aws_session=aws_session, tags=tags, logger=logger, + reservation_arn=reservation_arn, ) included_module = importlib.import_module("job_module") mock_register.assert_called_with(included_module) diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py index 1b2c6b5b4..bef4fd643 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -175,6 +175,11 @@ def checkpoint_config(bucket, s3_prefix): ) +@pytest.fixture +def reservation_arn(): + return "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + + @pytest.fixture def generate_get_job_response(): def _get_job_response(**kwargs): @@ -247,6 +252,7 @@ def create_job_args( output_data_config, checkpoint_config, tags, + reservation_arn, ): if request.param == "fixtures": return dict( @@ -268,6 +274,7 @@ def create_job_args( "checkpoint_config": checkpoint_config, "aws_session": aws_session, "tags": tags, + "reservation_arn": reservation_arn, }.items() if value is not None ) @@ -325,6 +332,7 @@ def _translate_creation_args(create_job_args): hyperparameters = {str(key): str(value) for key, value in hyperparameters.items()} input_data = create_job_args["input_data"] or {} instance_config = create_job_args["instance_config"] or InstanceConfig() + reservation_arn = create_job_args["reservation_arn"] if create_job_args["distribution"] == "data_parallel": distributed_hyperparams = { "sagemaker_distributed_dataparallel_enabled": "true", @@ -367,6 +375,18 @@ def _translate_creation_args(create_job_args): "tags": tags, } + if reservation_arn: + test_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) + return test_kwargs diff --git a/test/unit_tests/braket/tracking/test_tracker.py b/test/unit_tests/braket/tracking/test_tracker.py index fc660b809..a97ce57bc 100644 --- a/test/unit_tests/braket/tracking/test_tracker.py +++ b/test/unit_tests/braket/tracking/test_tracker.py @@ -85,6 +85,18 @@ def test_receive_fake_event(empty_tracker): _TaskCreationEvent( arn="no_price:::region", shots=1000, is_job_task=False, device="something_else" ), + _TaskCreationEvent( + arn="unbilled_task0:::region", + shots=100, + is_job_task=True, + device="qpu/foo", + ), + _TaskCreationEvent( + arn="unbilled_task1:::region", + shots=100, + is_job_task=True, + device="qpu/foo", + ), ] GET_EVENTS = [ @@ -101,6 +113,18 @@ def test_receive_fake_event(empty_tracker): ), _TaskCompletionEvent(arn="task_fail:::region", execution_duration=12345, status="FAILED"), _TaskCompletionEvent(arn="task_cancel:::region", execution_duration=None, status="CANCELLED"), + _TaskCompletionEvent( + arn="unbilled_task0:::region", + execution_duration=123, + status="COMPLETED", + has_reservation_arn=True, + ), + _TaskCompletionEvent( + arn="unbilled_task1:::region", + execution_duration=123, + status="COMPLETED", + has_reservation_arn=True, + ), ] @@ -174,7 +198,12 @@ def test_simulator_task_cost(price_mock, completed_tracker): def test_quantum_task_statistics(completed_tracker): stats = completed_tracker.quantum_tasks_statistics() expected = { - "qpu/foo": {"shots": 200, "tasks": {"COMPLETED": 1, "FAILED": 1}}, + "qpu/foo": { + "shots": 400, + "tasks": {"COMPLETED": 3, "FAILED": 1}, + "execution_duration": timedelta(microseconds=246000), + "billed_execution_duration": timedelta(0), + }, "simulator/bar": { "shots": 1000, "tasks": {"COMPLETED": 2, "CREATED": 1}, From c6ff90fc297d47ae9a7af2b0131948f2a0119269 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Dec 2023 18:17:00 +0000 Subject: [PATCH 09/29] prepare release v1.63.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 876595e2d..7f3514294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.63.0 (2023-12-05) + +### Features + + * Allow reservation ARN in task and job creation + +### Bug Fixes and Other Changes + + * Add Forte 1 device + ## v1.62.1 (2023-11-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c0e18d0a0..f9e931cef 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.62.2.dev0" +__version__ = "1.63.0" From 75e0bfd20abe69577248da749936dc29633426ab Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Dec 2023 18:17:00 +0000 Subject: [PATCH 10/29] update development version to v1.63.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f9e931cef..70ca418cd 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.63.0" +__version__ = "1.63.1.dev0" From aea53ae66f9dc23e78cdd31aaf30654fc21c005b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 12:30:14 -0800 Subject: [PATCH 11/29] infra: bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 (#825) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.11. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/b7f401de30cb6434a1e19f805ff006643653240e...2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 10aef9c29..e4d4e68c5 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e # release/v1 + uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf # release/v1 with: password: ${{ secrets.pypi_token }} From 2bb1e743da965f6cc459b52d3cf580a53357741e Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:07:39 -0800 Subject: [PATCH 12/29] update: adding a test to check for circular imports (#812) --- test/unit_tests/braket/test_imports.py | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/unit_tests/braket/test_imports.py diff --git a/test/unit_tests/braket/test_imports.py b/test/unit_tests/braket/test_imports.py new file mode 100644 index 000000000..bd545d943 --- /dev/null +++ b/test/unit_tests/braket/test_imports.py @@ -0,0 +1,48 @@ +import importlib +import multiprocessing +import os +import pathlib + +import pytest + + +def test_for_import_cycles(): + # Note, because of all the multiprocessing in this test, when running 'tox', the process + # threads may be made to wait as other tests are running in parallel, making it seems like + # this test is much slower than it actually is. However, splitting the test into a + # parameterized version wasn't able to correctly detect some circular imports when running tox. + modules = get_modules_to_test() + processes = [] + multiprocessing.set_start_method("spawn") + for module in modules: + # We create a separate process to make sure the imports do not interfere with each-other. + process = multiprocessing.Process(target=import_module, args=(module,)) + processes.append(process) + process.start() + + for index, process in enumerate(processes): + process.join() + if process.exitcode != 0: + pytest.fail( + f"Unable to import '{modules[index]}'." + " If all other tests are passing, check for cyclical dependencies." + ) + + +def get_modules_to_test(): + curr_path = pathlib.Path(__file__).resolve() + while "test" in str(curr_path): + curr_path = curr_path.parent + curr_path = curr_path.joinpath("src") + curr_path_len = len(str(curr_path)) + len(os.sep) + modules = [] + for dir_, temp, files in os.walk(curr_path): + # Rather than testing every single python file we just test modules, for now. + if "__init__.py" in files: + braket_module = dir_[curr_path_len:].replace(os.sep, ".") + modules.append(braket_module) + return modules + + +def import_module(module): + importlib.import_module(module) From dedfb7206f301913d343520b9b2a6b4c7db62562 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 6 Dec 2023 11:09:52 -0800 Subject: [PATCH 13/29] infra: no unfrozen prefix when variable is not present (#810) --- .github/workflows/code-freeze.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/code-freeze.yml b/.github/workflows/code-freeze.yml index 5aff0abc7..d19f5b48d 100644 --- a/.github/workflows/code-freeze.yml +++ b/.github/workflows/code-freeze.yml @@ -32,8 +32,18 @@ jobs: echo $BRANCH_NAME echo $PR_TITLE - if [[ "$BRANCH_NAME" != $UNFROZEN_PREFIX* ]] && - [[ "$PR_TITLE" != fix:* && "$PR_TITLE" != *"[critical]"* ]]; then - echo "Error: You can only merge from branches that start with '$UNFROZEN_PREFIX', or PRs titled with 'fix: ' and containing '[critical]'." - exit 1 + # if it's not a critical fix + if ! [[ "$PR_TITLE" == fix\(critical\):* ]]; then + # and there's an unfrozen prefix + if ! [[ -z $UNFROZEN_PREFIX ]]; then + # check if the branch matches unfrozen prefix + if [[ "$BRANCH_NAME" != $UNFROZEN_PREFIX* ]]; then + echo "Error: You can only merge from branches that start with '$UNFROZEN_PREFIX', or PRs titled with prefix 'fix(critical): '." + exit 1 + fi + # repo is fully frozen + else + echo "Error: You can only merge PRs titled with prefix 'fix(critical): '." + exit 1 + fi fi From 608a8dc3f72da42ee8913eb38fb9078e2121e50a Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 6 Dec 2023 12:42:20 -0800 Subject: [PATCH 14/29] feat: add str, repr and getitem to BasisState (#808) --- src/braket/circuits/basis_state.py | 12 ++++ .../braket/circuits/test_basis_state.py | 58 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py index 814444e75..b6ce11bc8 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -33,6 +33,18 @@ def __iter__(self): def __eq__(self, other): return self.state == other.state + def __bool__(self): + return any(self.state) + + def __str__(self): + return self.as_string + + def __repr__(self): + return f'BasisState("{self.as_string}")' + + def __getitem__(self, item): + return BasisState(self.state[item]) + BasisStateInput = Union[int, list[int], str, BasisState] diff --git a/test/unit_tests/braket/circuits/test_basis_state.py b/test/unit_tests/braket/circuits/test_basis_state.py index 023494fae..166e7c8fc 100644 --- a/test/unit_tests/braket/circuits/test_basis_state.py +++ b/test/unit_tests/braket/circuits/test_basis_state.py @@ -51,6 +51,58 @@ ), ) def test_as_props(basis_state_input, size, as_tuple, as_int, as_string): - assert BasisState(basis_state_input, size).as_tuple == as_tuple - assert BasisState(basis_state_input, size).as_int == as_int - assert BasisState(basis_state_input, size).as_string == as_string + basis_state = BasisState(basis_state_input, size) + assert basis_state.as_tuple == as_tuple + assert basis_state.as_int == as_int + assert basis_state.as_string == as_string == str(basis_state) + assert repr(basis_state) == f'BasisState("{as_string}")' + + +@pytest.mark.parametrize( + "basis_state_input, index, substate_input", + ( + ( + "1001", + slice(None), + "1001", + ), + ( + "1001", + 3, + "1", + ), + ( + "1010", + slice(None, None, 2), + "11", + ), + ( + "1010", + slice(1, None, 2), + "00", + ), + ( + "1010", + slice(None, -2), + "10", + ), + ( + "1010", + -1, + "0", + ), + ), +) +def test_indexing(basis_state_input, index, substate_input): + assert BasisState(basis_state_input)[index] == BasisState(substate_input) + + +def test_bool(): + assert all( + [ + BasisState("100"), + BasisState("111"), + BasisState("1"), + ] + ) + assert not BasisState("0") From 788b8b68a2835ee7ffb1d415c4dda0f4cf29c6bc Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Dec 2023 16:17:28 +0000 Subject: [PATCH 15/29] prepare release v1.64.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3514294..c8c50a7da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.64.0 (2023-12-07) + +### Features + + * add str, repr and getitem to BasisState + +### Bug Fixes and Other Changes + + * update: adding a test to check for circular imports + ## v1.63.0 (2023-12-05) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 70ca418cd..38e0376ee 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.63.1.dev0" +__version__ = "1.64.0" From 372cd53be84bbd27e1360b2ba15a5823404cd2da Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Dec 2023 16:17:28 +0000 Subject: [PATCH 16/29] update development version to v1.64.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 38e0376ee..156b961b8 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.0" +__version__ = "1.64.1.dev0" From d47c6fcfa16832fedc4fc8a95b7f3a6846e1a7fe Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:28:29 -0800 Subject: [PATCH 17/29] infra: allow tox to use logical processors (#824) Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 94f28ec7d..0a6ad7c49 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ test=pytest xfail_strict = true # https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html addopts = - --verbose -n auto --durations=0 --durations-min=1 + --verbose -n logical --durations=0 --durations-min=1 testpaths = test/unit_tests [isort] diff --git a/setup.py b/setup.py index f15777485..86ec939e3 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ "pytest", "pytest-cov", "pytest-rerunfailures", - "pytest-xdist", + "pytest-xdist[psutil]", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-apidoc", From bc126bc5062a26a4c9df04a3c2bd022866769e51 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:47:39 -0800 Subject: [PATCH 18/29] infra: ignore rsyncdir warning raised by rsync testing in pytest-cov (#817) --- setup.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.cfg b/setup.cfg index 0a6ad7c49..3bad103a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,11 @@ xfail_strict = true addopts = --verbose -n logical --durations=0 --durations-min=1 testpaths = test/unit_tests +filterwarnings= + # Issue #557 in `pytest-cov` (currently v4.x) has not moved for a while now, + # but once a resolution has been adopted we can drop this "ignore". + # Ref: https://github.com/pytest-dev/pytest-cov/issues/557 + ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning [isort] line_length = 100 From 37bad90832b07a7ba81fbe88d1a2ae0213562847 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:18:31 -0700 Subject: [PATCH 19/29] infra: bump actions/setup-python from 4.7.1 to 5.0.0 (#833) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.1 to 5.0.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236...0a5c61591373683505ea898e09a3ea4f39ef2b9c) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index dbe9599c1..1795135e7 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index cfd395241..aca79d599 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e4d4e68c5..b12cde750 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0c02a7561..56ba81bf4 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index f8e01fd72..2c30b8ec0 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: '3.x' - name: Install wheel From 7787d27123845eab2e5416fa1dd975ac8409a406 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:14:00 -0800 Subject: [PATCH 20/29] infra: Install only tox for python-package workflow (#791) --- .github/workflows/python-package.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 56ba81bf4..42dda2020 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -31,8 +31,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install --upgrade pip - pip install -e .[test] + pip install tox - name: Run unit tests run: | tox -e unit-tests From 12f1387e0c715b398ed102a996fd436b77f297a5 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:00:57 -0500 Subject: [PATCH 21/29] fix: make filter more convenient (#718) * make filter more convenient * changes according to feedback * remove Union type hint --------- Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Cody Wang --- src/braket/circuits/gate_calibrations.py | 25 +++++++++++-------- .../braket/circuits/test_gate_calibration.py | 19 +++++++++++--- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py index 6cbdd97d1..57013df4a 100644 --- a/src/braket/circuits/gate_calibrations.py +++ b/src/braket/circuits/gate_calibrations.py @@ -14,7 +14,7 @@ from __future__ import annotations from copy import deepcopy -from typing import Any, Optional +from typing import Any from braket.circuits.gate import Gate from braket.circuits.serialization import ( @@ -91,35 +91,40 @@ def __len__(self): return len(self._pulse_sequences) def filter( - self, gates: Optional[list[Gate]] = None, qubits: Optional[QubitSet] = None - ) -> Optional[GateCalibrations]: + self, + gates: list[Gate] | None = None, + qubits: QubitSet | list[QubitSet] | None = None, + ) -> GateCalibrations: """ Filters the data based on optional lists of gates and QubitSets. Args: - gates (Optional[list[Gate]]): An optional list of gates to filter on. - qubits (Optional[QubitSet]): An optional `QubitSet` to filter on. + gates (list[Gate] | None): An optional list of gates to filter on. + qubits (QubitSet | list[QubitSet] | None): An optional `QubitSet` or + list of `QubitSet` to filter on. Returns: - Optional[GateCalibrations]: A filtered GateCalibrations object. Otherwise, returns - none if no matches are found. + GateCalibrations: A filtered GateCalibrations object. """ # noqa: E501 keys = self.pulse_sequences.keys() + if isinstance(qubits, QubitSet): + qubits = [qubits] filtered_calibration_keys = [ tup for tup in keys - if (gates is None or tup[0] in gates) and (qubits is None or qubits.issubset(tup[1])) + if (gates is None or tup[0] in gates) + and (qubits is None or any(qset.issubset(tup[1]) for qset in qubits)) ] return GateCalibrations( {k: v for (k, v) in self.pulse_sequences.items() if k in filtered_calibration_keys}, ) - def to_ir(self, calibration_key: Optional[tuple[Gate, QubitSet]] = None) -> str: + def to_ir(self, calibration_key: tuple[Gate, QubitSet] | None = None) -> str: """ Returns the defcal representation for the `GateCalibrations` object. Args: - calibration_key (Optional[tuple[Gate, QubitSet]]): An optional key to get a specific defcal. + calibration_key (tuple[Gate, QubitSet] | None): An optional key to get a specific defcal. Default: None Returns: diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py index 31c2384db..c95ce74a3 100644 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -57,19 +57,30 @@ def test_gc_copy(pulse_sequence): def test_filter(pulse_sequence): - calibration_key = (Gate.Z(), QubitSet([0, 1])) - calibration_key_2 = (Gate.H(), QubitSet([0, 1])) + calibration_key = (Gate.Z(), QubitSet([0])) + calibration_key_2 = (Gate.H(), QubitSet([1])) + calibration_key_3 = (Gate.CZ(), QubitSet([0, 1])) calibration = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} + { + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + calibration_key_3: pulse_sequence, + } ) expected_calibration_1 = GateCalibrations({calibration_key: pulse_sequence}) expected_calibration_2 = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} + {calibration_key: pulse_sequence, calibration_key_3: pulse_sequence} ) expected_calibration_3 = GateCalibrations({calibration_key_2: pulse_sequence}) + expected_calibration_4 = GateCalibrations({}) + expected_calibration_5 = calibration + expected_calibration_6 = GateCalibrations({calibration_key_3: pulse_sequence}) assert expected_calibration_1 == calibration.filter(gates=[Gate.Z()]) assert expected_calibration_2 == calibration.filter(qubits=QubitSet(0)) assert expected_calibration_3 == calibration.filter(gates=[Gate.H()], qubits=QubitSet(1)) + assert expected_calibration_4 == calibration.filter(gates=[Gate.Z()], qubits=QubitSet(1)) + assert expected_calibration_5 == calibration.filter(qubits=[QubitSet(0), QubitSet(1)]) + assert expected_calibration_6 == calibration.filter(qubits=QubitSet([0, 1])) def test_to_ir(pulse_sequence): From c1eb6644aab1dde1165d5d13e8a556eb04374bb5 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 12 Dec 2023 16:16:20 +0000 Subject: [PATCH 22/29] prepare release v1.64.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c50a7da..64c04fee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.64.1 (2023-12-12) + +### Bug Fixes and Other Changes + + * make filter more convenient + ## v1.64.0 (2023-12-07) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 156b961b8..1f45b0ddf 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.1.dev0" +__version__ = "1.64.1" From f44ab586d67a86464d3de7be282bcf878776a940 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 12 Dec 2023 16:16:20 +0000 Subject: [PATCH 23/29] update development version to v1.64.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1f45b0ddf..d63d59133 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.1" +__version__ = "1.64.2.dev0" From 228f0802716bc9cef5d886fd2aab3e6ec100929b Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:19:06 -0800 Subject: [PATCH 24/29] fix: treating OpenQASM builtin types as constants (#829) --- src/braket/circuits/braket_program_context.py | 7 +++-- .../braket/circuits/test_circuit.py | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 9d8c69afe..19e2c986f 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -14,7 +14,7 @@ from typing import Optional, Union import numpy as np -from sympy import Expr +from sympy import Expr, Number from braket.circuits import Circuit, Instruction from braket.circuits.gates import Unitary @@ -147,5 +147,8 @@ def handle_parameter_value( otherwise wraps the symbolic expression as a `FreeParameterExpression`. """ if isinstance(value, Expr): - return FreeParameterExpression(value) + evaluated_value = value.evalf() + if isinstance(evaluated_value, Number): + return evaluated_value + return FreeParameterExpression(evaluated_value) return value diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 5824967b8..71cff0de7 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1718,6 +1718,36 @@ def foo( inputs={}, ), ), + ( + Circuit().rx(0, np.pi), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "rx(π) q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().rx(0, 2 * np.pi), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "rx(τ) q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), ], ) def test_from_ir(expected_circuit, ir): From b33191f4204388e33a0af52cae2dafb979aff6af Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Dec 2023 16:18:49 +0000 Subject: [PATCH 25/29] prepare release v1.64.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c04fee2..3fc496e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.64.2 (2023-12-19) + +### Bug Fixes and Other Changes + + * treating OpenQASM builtin types as constants + ## v1.64.1 (2023-12-12) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d63d59133..d2cdebb9d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.2.dev0" +__version__ = "1.64.2" From d45c9c8429877ec843fa5886672a974189be51ff Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Dec 2023 16:18:49 +0000 Subject: [PATCH 26/29] update development version to v1.64.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d2cdebb9d..a30db2260 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.2" +__version__ = "1.64.3.dev0" From 5d8d04c850459ac54633042ac41a8c0eebc3dc1c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 20 Dec 2023 18:25:24 -0500 Subject: [PATCH 27/29] feat: add U and GPhase gates (#799) * Add U gate * modification according to feedback * fix linters * clean commented code * first version of gphase * handle drawing and control global phase * Adding a global phase attribute * add global phase to circuit unitary * first draft tests to check coverage * add more tests * add test with parametric global phase * add test for neg control qubit printing * clean up * simplify ctrl-gphase transform * feat: add str, repr and getitem to BasisState * add repr coverage * add index * add pop * fix phase target qubit * fix typing * add index and pop tests * fix code coverage * move unitary matrices * use a subindex in MomentKey * print global phase integration * fix docstrings * fix circuits zero total global phase * fix edge cases * fix to_unitary * temporary fix that checks classname * clean up test conditions * change logic according to feedback * update docstring * clean tests * update tests * replace control symbols * use box drawing characters * Revert "use box drawing characters" This reverts commit ccb81fa641fea45c7b3b8b2a597e7c4804e60b25. * Revert "replace control symbols" This reverts commit 4efb8bc53ba1d6bb42ba3df395a71ec324f7ea0d. * simplify all gphase case * change preprare_y_axis function name * create an helper function to compute the global phase * make control_basis_state more explicit * add comment and clean grouping * add test_only_neg_control_qubits * parametrize test_one_gate_with_global_phase * reformat * change to printing with fixed precision * fix docstring --------- Co-authored-by: Aaron Berdy --- src/braket/circuits/ascii_circuit_diagram.py | 148 +++++++++-- src/braket/circuits/braket_program_context.py | 11 +- src/braket/circuits/circuit.py | 13 +- src/braket/circuits/gate.py | 2 +- src/braket/circuits/gates.py | 237 +++++++++++++++++- src/braket/circuits/moments.py | 24 +- src/braket/circuits/translations.py | 2 + .../braket/circuits/test_angled_gate.py | 2 +- .../circuits/test_ascii_circuit_diagram.py | 84 +++++++ .../braket/circuits/test_circuit.py | 46 ++++ test/unit_tests/braket/circuits/test_gates.py | 100 +++++++- 11 files changed, 639 insertions(+), 30 deletions(-) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index daef01793..2c7024574 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -21,8 +21,10 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.moments import MomentType from braket.circuits.noise import Noise from braket.circuits.result_type import ResultType +from braket.registers.qubit import Qubit from braket.registers.qubit_set import QubitSet @@ -44,23 +46,26 @@ def build_diagram(circuit: cir.Circuit) -> str: if not circuit.instructions: return "" + if all(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + return f"Global phase: {circuit.global_phase}" + circuit_qubits = circuit.qubits circuit_qubits.sort() - # Y Axis Column - y_axis_width = len(str(int(max(circuit_qubits)))) - y_axis_str = "{0:{width}} : |\n".format("T", width=y_axis_width + 1) - for qubit in circuit_qubits: - y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) - y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) + y_axis_str, global_phase = AsciiCircuitDiagram._prepare_diagram_vars( + circuit, circuit_qubits + ) time_slices = circuit.moments.time_slices() column_strs = [] # Moment columns for time, instructions in time_slices.items(): + global_phase = AsciiCircuitDiagram._compute_moment_global_phase( + global_phase, instructions + ) moment_str = AsciiCircuitDiagram._ascii_diagram_column_set( - str(time), circuit_qubits, instructions + str(time), circuit_qubits, instructions, global_phase ) column_strs.append(moment_str) @@ -71,7 +76,7 @@ def build_diagram(circuit: cir.Circuit) -> str: if target_result_types: column_strs.append( AsciiCircuitDiagram._ascii_diagram_column_set( - "Result Types", circuit_qubits, target_result_types + "Result Types", circuit_qubits, target_result_types, global_phase ) ) @@ -84,6 +89,9 @@ def build_diagram(circuit: cir.Circuit) -> str: # Time on top and bottom lines.append(lines[0]) + if global_phase: + lines.append(f"\nGlobal phase: {global_phase}") + # Additional result types line on bottom if additional_result_types: lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") @@ -97,6 +105,49 @@ def build_diagram(circuit: cir.Circuit) -> str: return "\n".join(lines) + @staticmethod + def _prepare_diagram_vars( + circuit: cir.Circuit, circuit_qubits: QubitSet + ) -> tuple[str, float | None]: + # Y Axis Column + y_axis_width = len(str(int(max(circuit_qubits)))) + y_axis_str = "{0:{width}} : |\n".format("T", width=y_axis_width + 1) + + global_phase = None + if any(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + y_axis_str += "{0:{width}} : |\n".format("GP", width=y_axis_width) + global_phase = 0 + + for qubit in circuit_qubits: + y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) + y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) + + return y_axis_str, global_phase + + @staticmethod + def _compute_moment_global_phase( + global_phase: float | None, items: list[Instruction] + ) -> float | None: + """ + Compute the integrated phase at a certain moment. + + Args: + global_phase (float | None): The integrated phase up to the computed moment + items (list[Instruction]): list of instructions + + Returns: + float | None: The updated integrated phase. + """ + moment_phase = 0 + for item in items: + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + moment_phase += item.operator.angle + return global_phase + moment_phase if global_phase is not None else None + @staticmethod def _ascii_group_items( circuit_qubits: QubitSet, @@ -120,7 +171,15 @@ def _ascii_group_items( ): continue - if (isinstance(item, ResultType) and not item.target) or ( + # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range + # to an empty list and we just add it to the first group below. + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + qubit_range = QubitSet() + elif (isinstance(item, ResultType) and not item.target) or ( isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) ): qubit_range = circuit_qubits @@ -175,7 +234,10 @@ def _categorize_result_types( @staticmethod def _ascii_diagram_column_set( - col_title: str, circuit_qubits: QubitSet, items: list[Union[Instruction, ResultType]] + col_title: str, + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None, ) -> str: """ Return a set of columns in the ASCII string diagram of the circuit for a list of items. @@ -184,6 +246,7 @@ def _ascii_diagram_column_set( col_title (str): title of column set circuit_qubits (QubitSet): qubits in circuit items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this set Returns: str: An ASCII string diagram for the column set. @@ -193,7 +256,7 @@ def _ascii_diagram_column_set( groupings = AsciiCircuitDiagram._ascii_group_items(circuit_qubits, items) column_strs = [ - AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1]) + AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1], global_phase) for grouping in groupings ] @@ -220,7 +283,9 @@ def _ascii_diagram_column_set( @staticmethod def _ascii_diagram_column( - circuit_qubits: QubitSet, items: list[Union[Instruction, ResultType]] + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None = None, ) -> str: """ Return a column in the ASCII string diagram of the circuit for a given list of items. @@ -228,9 +293,10 @@ def _ascii_diagram_column( Args: circuit_qubits (QubitSet): qubits in circuit items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column Returns: - str: An ASCII string diagram for the specified moment in time for a column. + str: an ASCII string diagram for the specified moment in time for a column. """ symbols = {qubit: "-" for qubit in circuit_qubits} margins = {qubit: " " for qubit in circuit_qubits} @@ -252,12 +318,26 @@ def _ascii_diagram_column( num_after = len(circuit_qubits) - 1 after = ["|"] * (num_after - 1) + ([marker] if num_after else []) ascii_symbols = [ascii_symbol] + after + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = QubitSet() + qubits = circuit_qubits + ascii_symbols = "-" * len(circuit_qubits) else: if isinstance(item.target, list): target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) else: target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) + map_control_qubit_states = AsciiCircuitDiagram._build_map_control_qubits( + item, control_qubits + ) + target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) @@ -288,20 +368,54 @@ def _ascii_diagram_column( else ascii_symbols[item_qubit_index] ) elif qubit in control_qubits: - symbols[qubit] = "C" + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" else: symbols[qubit] = "|" # Set the margin to be a connector if not on the first qubit - if qubit != min(target_and_control): + if target_and_control and qubit != min(target_and_control): margins[qubit] = "|" - symbols_width = max([len(symbol) for symbol in symbols.values()]) + output = AsciiCircuitDiagram._create_output(symbols, margins, circuit_qubits, global_phase) + return output + @staticmethod + def _create_output( + symbols: dict[Qubit, str], + margins: dict[Qubit, str], + qubits: QubitSet, + global_phase: float | None, + ) -> str: + symbols_width = max([len(symbol) for symbol in symbols.values()]) output = "" - for qubit in circuit_qubits: + + if global_phase is not None: + global_phase_str = ( + f"{global_phase:.2f}" if isinstance(global_phase, float) else str(global_phase) + ) + symbols_width = max([symbols_width, len(global_phase_str)]) + output += "{0:{fill}{align}{width}}|\n".format( + global_phase_str, + fill=" ", + align="^", + width=symbols_width, + ) + + for qubit in qubits: output += "{0:{width}}\n".format(margins[qubit], width=symbols_width + 1) output += "{0:{fill}{align}{width}}\n".format( symbols[qubit], fill="-", align="<", width=symbols_width + 1 ) return output + + @staticmethod + def _build_map_control_qubits(item: Instruction, control_qubits: QubitSet) -> dict(Qubit, int): + control_state = getattr(item, "control_state", None) + if control_state is not None: + map_control_qubit_states = { + qubit: state for qubit, state in zip(control_qubits, control_state) + } + else: + map_control_qubit_states = {qubit: 1 for qubit in control_qubits} + + return map_control_qubit_states diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 19e2c986f..863513565 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -56,8 +56,15 @@ def is_builtin_gate(self, name: str) -> bool: user_defined_gate = self.is_user_defined_gate(name) return name in BRAKET_GATES and not user_defined_gate - def add_phase_instruction(self, target: tuple[int], phase_value: int) -> None: - raise NotImplementedError + def add_phase_instruction(self, target: tuple[int], phase_value: float) -> None: + """Add a global phase to the circuit. + + Args: + target (tuple[int]): Unused + phase_value (float): The phase value to be applied + """ + instruction = Instruction(BRAKET_GATES["gphase"](phase_value)) + self._circuit.add_instruction(instruction) def add_gate_instruction( self, gate_name: str, target: tuple[int], *params, ctrl_modifiers: list[int], power: float diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 76e986087..aeae8585d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -26,7 +26,7 @@ from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction -from braket.circuits.moments import Moments +from braket.circuits.moments import Moments, MomentType from braket.circuits.noise import Noise from braket.circuits.noise_helpers import ( apply_noise_to_gates, @@ -156,6 +156,17 @@ def depth(self) -> int: """int: Get the circuit depth.""" return self._moments.depth + @property + def global_phase(self) -> float: + """float: Get the global phase of the circuit.""" + return sum( + [ + instr.operator.angle + for moment, instr in self._moments.items() + if moment.moment_type == MomentType.GLOBAL_PHASE + ] + ) + @property def instructions(self) -> list[Instruction]: """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 3c59409e5..2183a2329 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -198,7 +198,7 @@ def _to_openqasm( return ( f"{inv_prefix}{power_prefix}{control_prefix}" - f"{self._qasm_name}{param_string} {', '.join(qubits)};" + f"{self._qasm_name}{param_string}{','.join([f' {qubit}' for qubit in qubits])};" ) @property diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 9a4214c37..e955c4bb0 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -30,7 +30,7 @@ angled_ascii_characters, get_angle, ) -from braket.circuits.basis_state import BasisStateInput +from braket.circuits.basis_state import BasisState, BasisStateInput from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -215,6 +215,118 @@ def i( Gate.register_gate(I) +class GPhase(AngledGate): + r"""Global phase gate. + + Unitary matrix: + + .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + + Args: + angle (Union[FreeParameterExpression, float]): angle in radians. + """ + + def __init__(self, angle: Union[FreeParameterExpression, float]): + # Avoid parent constructor because _qubit_count must be zero + self._qubit_count = self.fixed_qubit_count() + self._ascii_symbols = [] + + if angle is None: + raise ValueError("angle must not be None") + if isinstance(angle, FreeParameterExpression): + self._parameters = [angle] + else: + self._parameters = [float(angle)] # explicit casting in case angle is e.g. np.float32 + + @property + def _qasm_name(self) -> str: + return "gphase" + + def adjoint(self) -> list[Gate]: + return [GPhase(-self.angle)] + + def to_matrix(self) -> np.ndarray: + return np.exp(1j * self.angle) * np.eye(1, dtype=complex) + + def bind_values(self, **kwargs) -> AngledGate: + return get_angle(self, **kwargs) + + @staticmethod + def fixed_qubit_count() -> int: + return 0 + + @staticmethod + @circuit.subroutine(register=True) + def gphase( + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Instruction | Iterable[Instruction]: + r"""Global phase gate. + + If the gate is applied with control/negative control modifiers, it is translated in an + equivalent gate using the following definition: `phaseshift(λ) = ctrl @ gphase(λ)`. + The rightmost control qubit is used for the translation. If the polarity of the rightmost + control modifier is negative, the following identity is used: + `negctrl @ gphase(λ) q = x q; ctrl @ gphase(λ) q; x q`. + + Unitary matrix: + + .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + + Args: + angle (Union[FreeParameterExpression, float]): Phase in radians. + control (Optional[QubitSetInput]): Control qubit(s). Default None. + control_state (Optional[BasisStateInput]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control`. Will be ignored if `control` is not present. May be represented as a + string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent + controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being + in the \\|1⟩ state. Default "1" * len(control). + power (float): Integer or fractional power to raise the gate to. Negative + powers will be split into an inverse, accompanied by the positive power. + Default 1. + + Returns: + Instruction | Iterable[Instruction]: GPhase instruction. + + Examples: + >>> circ = Circuit().gphase(0.45) + """ + if control is not None: + control_qubits = QubitSet(control) + + control_state = ( + control_state if control_state is not None else (1,) * len(control_qubits) + ) + control_basis_state = BasisState(control_state, len(control_qubits)) + + phaseshift_target = control_qubits[-1] + phaseshift_instruction = PhaseShift.phaseshift( + phaseshift_target, + angle, + control=control_qubits[:-1], + control_state=control_basis_state[:-1], + power=power, + ) + return ( + phaseshift_instruction + if control_basis_state[-1] + else [ + X.x(phaseshift_target), + phaseshift_instruction, + X.x(phaseshift_target), + ] + ) + + return Instruction(GPhase(angle), power=power) + + +Gate.register_gate(GPhase) + + class X(Gate): r"""Pauli-X gate. @@ -1287,6 +1399,129 @@ def phaseshift( Gate.register_gate(PhaseShift) +class U(TripleAngledGate): + r"""Generalized single-qubit rotation gate. + + Unitary matrix: + + .. math:: \mathtt{U}(\theta, \phi, \lambda) = \begin{bmatrix} + \cos{(\theta/2)} & -e^{i \lambda} \sin{(\theta/2)} \\ + e^{i \phi} \sin{(\theta/2)} & -e^{i (\phi + \lambda)} \cos{(\theta/2)} + \end{bmatrix}. + + Args: + angle_1 (Union[FreeParameterExpression, float]): theta angle in radians. + angle_2 (Union[FreeParameterExpression, float]): phi angle in radians. + angle_3 (Union[FreeParameterExpression, float]): lambda angle in radians. + """ + + def __init__( + self, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float], + ): + super().__init__( + angle_1=angle_1, + angle_2=angle_2, + angle_3=angle_3, + qubit_count=None, + ascii_symbols=[_multi_angled_ascii_characters("U", angle_1, angle_2, angle_3)], + ) + + @property + def _qasm_name(self) -> str: + return "U" + + def to_matrix(self) -> np.ndarray: + r"""Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ + _theta = self.angle_1 + _phi = self.angle_2 + _lambda = self.angle_3 + return np.array( + [ + [ + np.cos(_theta / 2), + -np.exp(1j * _lambda) * np.sin(_theta / 2), + ], + [ + np.exp(1j * _phi) * np.sin(_theta / 2), + np.exp(1j * (_phi + _lambda)) * np.cos(_theta / 2), + ], + ] + ) + + def adjoint(self) -> list[Gate]: + return [U(-self.angle_1, -self.angle_3, -self.angle_2)] + + @staticmethod + def fixed_qubit_count() -> int: + return 1 + + def bind_values(self, **kwargs) -> TripleAngledGate: + return _get_angles(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def u( + target: QubitSetInput, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: + r"""Generalized single-qubit rotation gate. + + Unitary matrix: + + .. math:: \mathtt{U}(\theta, \phi, \lambda) = \begin{bmatrix} + \cos{(\theta/2)} & -e^{i \lambda} \sin{(\theta/2)} \\ + e^{i \phi} \sin{(\theta/2)} & -e^{i (\phi + \lambda)} \cos{(\theta/2)} + \end{bmatrix}. + + Args: + target (QubitSetInput): Target qubit(s) + angle_1 (Union[FreeParameterExpression, float]): theta angle in radians. + angle_2 (Union[FreeParameterExpression, float]): phi angle in radians. + angle_3 (Union[FreeParameterExpression, float]): lambda angle in radians. + control (Optional[QubitSetInput]): Control qubit(s). Default None. + control_state (Optional[BasisStateInput]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control`. Will be ignored if `control` is not present. May be represented as a + string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent + controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being + in the \\|1⟩ state. Default "1" * len(control). + power (float): Integer or fractional power to raise the gate to. Negative + powers will be split into an inverse, accompanied by the positive power. + Default 1. + + Returns: + Iterable[Instruction]: U instruction. + + Examples: + >>> circ = Circuit().u(0, 0.15, 0.34, 0.52) + """ + return [ + Instruction( + U(angle_1, angle_2, angle_3), + target=qubit, + control=control, + control_state=control_state, + power=power, + ) + for qubit in QubitSet(target) + ] + + +Gate.register_gate(U) + + # Two qubit gates # diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 7cd8e0a9d..6e87db78d 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -19,6 +19,7 @@ from typing import Any, NamedTuple, Union from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.noise import Noise from braket.registers.qubit import Qubit @@ -42,6 +43,7 @@ class MomentType(str, Enum): INITIALIZATION_NOISE = "initialization_noise" READOUT_NOISE = "readout_noise" COMPILER_DIRECTIVE = "compiler_directive" + GLOBAL_PHASE = "global_phase" class MomentsKey(NamedTuple): @@ -59,6 +61,7 @@ class MomentsKey(NamedTuple): qubits: QubitSet moment_type: MomentType noise_index: int + subindex: int = 0 class Moments(Mapping[MomentsKey, Instruction]): @@ -106,6 +109,7 @@ def __init__(self, instructions: Iterable[Instruction] | None = None): self._qubits = QubitSet() self._depth = 0 self._time_all_qubits = -1 + self._number_gphase_in_current_moment = 0 self.add(instructions or []) @@ -181,6 +185,17 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._time_all_qubits = time elif isinstance(operator, Noise): self.add_noise(instruction) + elif isinstance(operator, Gate) and operator.name == "GPhase": + time = self._get_qubit_times(self._max_times.keys()) + 1 + self._number_gphase_in_current_moment += 1 + key = MomentsKey( + time, + QubitSet([]), + MomentType.GLOBAL_PHASE, + 0, + self._number_gphase_in_current_moment, + ) + self._moments[key] = instruction else: qubit_range = instruction.target.union(instruction.control) time = self._update_qubit_times(qubit_range) @@ -188,14 +203,15 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._qubits.update(qubit_range) self._depth = max(self._depth, time + 1) + def _get_qubit_times(self, qubits: QubitSet) -> int: + return max([self._max_time_for_qubit(qubit) for qubit in qubits] + [self._time_all_qubits]) + def _update_qubit_times(self, qubits: QubitSet) -> int: - qubit_max_times = [self._max_time_for_qubit(qubit) for qubit in qubits] + [ - self._time_all_qubits - ] - time = max(qubit_max_times) + 1 + time = self._get_qubit_times(qubits) + 1 # Update time for all specified qubits for qubit in qubits: self._max_times[qubit] = time + self._number_gphase_in_current_moment = 0 return time def add_noise( diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index ba536594f..bbb194be3 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -31,6 +31,7 @@ from braket.ir.jaqcd.program_v1 import Results BRAKET_GATES = { + "gphase": braket_gates.GPhase, "i": braket_gates.I, "h": braket_gates.H, "x": braket_gates.X, @@ -55,6 +56,7 @@ "rx": braket_gates.Rx, "ry": braket_gates.Ry, "rz": braket_gates.Rz, + "U": braket_gates.U, "swap": braket_gates.Swap, "iswap": braket_gates.ISwap, "pswap": braket_gates.PSwap, diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index e76756e29..4e093e5b4 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -31,7 +31,7 @@ def test_is_operator(angled_gate): def test_angle_is_none(): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="angle must not be None"): AngledGate(qubit_count=1, ascii_symbols=["foo"], angle=None) 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 3f3268f83..916bfb050 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import numpy as np +import pytest from braket.circuits import ( AsciiCircuitDiagram, @@ -29,6 +30,10 @@ def test_empty_circuit(): assert AsciiCircuitDiagram.build_diagram(Circuit()) == "" +def test_only_gphase_circuit(): + assert AsciiCircuitDiagram.build_diagram(Circuit().gphase(0.1)) == "Global phase: 0.1" + + def test_one_gate_one_qubit(): circ = Circuit().h(0) expected = ("T : |0|", " ", "q0 : -H-", "", "T : |0|") @@ -58,6 +63,35 @@ def test_one_gate_one_qubit_rotation_with_parameter(): _assert_correct_diagram(circ, expected) +@pytest.mark.parametrize("target", [0, 1]) +def test_one_gate_with_global_phase(target): + circ = Circuit().x(target=target).gphase(0.15) + expected = ( + "T : |0| 1 |", + "GP : |0|0.15|", + " ", + f"q{target} : -X------", + "", + "T : |0| 1 |", + "", + "Global phase: 0.15", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_with_zero_global_phase(): + circ = Circuit().gphase(-0.15).x(target=0).gphase(0.15) + expected = ( + "T : | 0 | 1 |", + "GP : |-0.15|0.00|", + " ", + "q0 : -X----------", + "", + "T : | 0 | 1 |", + ) + _assert_correct_diagram(circ, expected) + + def test_one_gate_one_qubit_rotation_with_unicode(): theta = FreeParameter("\u03B8") circ = Circuit().rx(angle=theta, target=0) @@ -74,6 +108,24 @@ def test_one_gate_one_qubit_rotation_with_unicode(): _assert_correct_diagram(circ, expected) +def test_one_gate_with_parametric_expression_global_phase_(): + theta = FreeParameter("\u03B8") + circ = Circuit().x(target=0).gphase(2 * theta).x(0).gphase(1) + expected = ( + "T : |0| 1 | 2 |", + "GP : |0|2*θ|2*θ + 1.0|", + " ", + "q0 : -X-X-------------", + "", + "T : |0| 1 | 2 |", + "", + "Global phase: 2*θ + 1.0", + "", + "Unassigned parameters: [θ].", + ) + _assert_correct_diagram(circ, expected) + + def test_one_gate_one_qubit_rotation_with_parameter_assigned(): theta = FreeParameter("theta") circ = Circuit().rx(angle=theta, target=0) @@ -187,6 +239,38 @@ def test_connector_across_two_qubits(): _assert_correct_diagram(circ, expected) +def test_neg_control_qubits(): + circ = Circuit().x(2, control=[0, 1], control_state=[0, 1]) + expected = ( + "T : |0|", + " ", + "q0 : -N-", + " | ", + "q1 : -C-", + " | ", + "q2 : -X-", + "", + "T : |0|", + ) + _assert_correct_diagram(circ, expected) + + +def test_only_neg_control_qubits(): + circ = Circuit().x(2, control=[0, 1], control_state=0) + expected = ( + "T : |0|", + " ", + "q0 : -N-", + " | ", + "q1 : -N-", + " | ", + "q2 : -X-", + "", + "T : |0|", + ) + _assert_correct_diagram(circ, expected) + + def test_connector_across_three_qubits(): circ = Circuit().x(control=(3, 4), target=5).h(range(2, 6)) expected = ( diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 71cff0de7..928cff757 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1748,6 +1748,22 @@ def foo( inputs={}, ), ), + ( + Circuit().gphase(0.15).x(0), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "gphase(0.15);", + "x q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), ], ) def test_from_ir(expected_circuit, ir): @@ -2027,6 +2043,14 @@ def test_to_unitary_with_compiler_directives_returns_expected_unitary(): ) +def test_to_unitary_with_global_phase(): + circuit = Circuit().x(0) + circuit_unitary = np.array([[0, 1], [1, 0]]) + assert np.allclose(circuit.to_unitary(), circuit_unitary) + circuit = circuit.gphase(np.pi / 2) + assert np.allclose(circuit.to_unitary(), 1j * circuit_unitary) + + @pytest.mark.parametrize( "circuit,expected_unitary", [ @@ -2046,6 +2070,8 @@ def test_to_unitary_with_compiler_directives_returns_expected_unitary(): (Circuit().rx(0, 0.15), gates.Rx(0.15).to_matrix()), (Circuit().ry(0, 0.15), gates.Ry(0.15).to_matrix()), (Circuit().rz(0, 0.15), gates.Rz(0.15).to_matrix()), + (Circuit().u(0, 0.15, 0.16, 0.17), gates.U(0.15, 0.16, 0.17).to_matrix()), + (Circuit().gphase(0.15), gates.GPhase(0.15).to_matrix()), (Circuit().phaseshift(0, 0.15), gates.PhaseShift(0.15).to_matrix()), (Circuit().cnot(0, 1), gates.CNot().to_matrix()), (Circuit().cnot(0, 1).add_result_type(ResultType.StateVector()), gates.CNot().to_matrix()), @@ -3134,3 +3160,23 @@ def test_parametrized_pulse_circuit(user_defined_frame): def test_free_param_float_mix(): Circuit().ms(0, 1, 0.1, FreeParameter("theta")) + + +def test_circuit_with_global_phase(): + circuit = Circuit().gphase(0.15).x(0) + assert circuit.global_phase == 0.15 + + assert circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.PHYSICAL + ), + ).source == "\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "gphase(0.15);", + "x $0;", + "b[0] = measure $0;", + ] + ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 05b98ade4..fc8fe7787 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -35,12 +35,21 @@ from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence +class NoTarget: + pass + + class TripleAngle: pass +class SingleNegControlModifier: + pass + + testdata = [ (Gate.H, "h", ir.H, [SingleTarget], {}), + (Gate.GPhase, "gphase", None, [NoTarget, Angle], {}), (Gate.I, "i", ir.I, [SingleTarget], {}), (Gate.X, "x", ir.X, [SingleTarget], {}), (Gate.Y, "y", ir.Y, [SingleTarget], {}), @@ -54,6 +63,7 @@ class TripleAngle: (Gate.Rx, "rx", ir.Rx, [SingleTarget, Angle], {}), (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle], {}), (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle], {}), + (Gate.U, "u", None, [SingleTarget, TripleAngle], {}), (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl], {}), (Gate.CV, "cv", ir.CV, [SingleTarget, SingleControl], {}), (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl], {}), @@ -118,9 +128,11 @@ class TripleAngle: ] parameterizable_gates = [ + Gate.GPhase, Gate.Rx, Gate.Ry, Gate.Rz, + Gate.U, Gate.PhaseShift, Gate.PSwap, Gate.XX, @@ -147,6 +159,10 @@ class TripleAngle: ] +def no_target_valid_input(**kwargs): + return {} + + def single_target_valid_input(**kwargs): return {"target": 2} @@ -171,6 +187,10 @@ def single_control_valid_input(**kwargs): return {"control": 0} +def single_neg_control_valid_input(**kwargs): + return {"control": [0], "control_state": [0]} + + def double_control_valid_ir_input(**kwargs): return {"controls": [0, 1]} @@ -193,11 +213,13 @@ def two_dimensional_matrix_valid_input(**kwargs): valid_ir_switcher = { + "NoTarget": no_target_valid_input, "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_ir_input, "Angle": angle_valid_input, "TripleAngle": triple_angle_valid_input, "SingleControl": single_control_valid_input, + "SingleNegControlModifier": single_neg_control_valid_input, "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, @@ -232,9 +254,13 @@ def create_valid_subroutine_input(irsubclasses, **kwargs): def create_valid_target_input(irsubclasses): input = {} qubit_set = [] + control_qubit_set = [] + control_state = None # based on the concept that control goes first in target input for subclass in irsubclasses: - if subclass == SingleTarget: + if subclass == NoTarget: + qubit_set.extend(list(no_target_valid_input().values())) + elif subclass == SingleTarget: qubit_set.extend(list(single_target_valid_input().values())) elif subclass == DoubleTarget: qubit_set.extend(list(double_target_valid_ir_input().values())) @@ -242,6 +268,9 @@ def create_valid_target_input(irsubclasses): qubit_set.extend(list(multi_target_valid_input().values())) elif subclass == SingleControl: qubit_set = list(single_control_valid_input().values()) + qubit_set + elif subclass == SingleNegControlModifier: + control_qubit_set = list(single_neg_control_valid_input()["control"]) + 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 in (Angle, TwoDimensionalMatrix, TripleAngle): @@ -249,6 +278,8 @@ def create_valid_target_input(irsubclasses): else: raise ValueError("Invalid subclass") input["target"] = QubitSet(qubit_set) + input["control"] = QubitSet(control_qubit_set) + input["control_state"] = control_state return input @@ -282,7 +313,7 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): + elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -434,6 +465,18 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), "rz(0.17) $4;", ), + ( + Gate.U(angle_1=0.17, angle_2=3.45, angle_3=5.21), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "U(0.17, 3.45, 5.21) q[4];", + ), + ( + Gate.U(angle_1=0.17, angle_2=3.45, angle_3=5.21), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "U(0.17, 3.45, 5.21) $4;", + ), ( Gate.XX(angle=0.17), [4, 5], @@ -859,9 +902,53 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar subroutine_input = {"target": multi_targets} if Angle in irsubclasses: subroutine_input.update(angle_valid_input()) + if TripleAngle in irsubclasses: + subroutine_input.update(triple_angle_valid_input()) assert subroutine(**subroutine_input) == Circuit(instruction_list) +@pytest.mark.parametrize( + "control, control_state, instruction_set", + [ + ( + 2, + None, + Instruction(**create_valid_instruction_input(Gate.PhaseShift, [SingleTarget, Angle])), + ), + ( + 2, + [0], + [ + Instruction(operator=Gate.X(), target=2), + Instruction( + **create_valid_instruction_input(Gate.PhaseShift, [SingleTarget, Angle]) + ), + Instruction(operator=Gate.X(), target=2), + ], + ), + ( + [0, 2], + [0, 1], + Instruction( + **create_valid_instruction_input( + Gate.PhaseShift, [SingleTarget, SingleNegControlModifier, Angle] + ) + ), + ), + ], +) +def test_control_gphase_subroutine(control, control_state, instruction_set): + subroutine = getattr(Circuit(), "gphase") + assert subroutine(angle=0.123, control=control, control_state=control_state) == Circuit( + instruction_set + ) + + +def test_angle_gphase_is_none(): + with pytest.raises(ValueError, match="angle must not be None"): + Gate.GPhase(angle=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)) @@ -925,7 +1012,7 @@ def test_large_unitary(): @pytest.mark.parametrize("gate", parameterizable_gates) def test_bind_values(gate): - triple_angled = gate.__name__ in ("MS",) + triple_angled = gate.__name__ in ("MS", "U") num_params = 3 if triple_angled else 1 thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] mapping = {f"theta_{i}": i for i in range(num_params)} @@ -1070,6 +1157,13 @@ def test_pulse_gate_to_matrix(): "10", "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", ), + ( + Gate.GPhase(0.3), + QubitSet([]), + QubitSet([1]), + "1", + "ctrl @ gphase(0.3) q[1];", + ), ), ) def test_gate_control(gate, target, control, control_state, expected_ir): From 1b35781505061f5a328d9fcda658d928890713eb Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Dec 2023 16:15:31 +0000 Subject: [PATCH 28/29] prepare release v1.65.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc496e21..37503c397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.65.0 (2023-12-21) + +### Features + + * add U and GPhase gates + ## v1.64.2 (2023-12-19) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a30db2260..d21a3b476 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.3.dev0" +__version__ = "1.65.0" From 788b198ddc66dc395b9761d1f92e38524d6eeed2 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Dec 2023 16:15:31 +0000 Subject: [PATCH 29/29] update development version to v1.65.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d21a3b476..ec7335e82 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.65.0" +__version__ = "1.65.1.dev0"