From 4ae118e8bd969c27d7b953253f604abc0816af9c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Fri, 8 Sep 2023 11:29:18 -0400 Subject: [PATCH 01/17] add duration type to FreeParameterExpression --- src/braket/pulse/ast/free_parameters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 41c541da8..1c0fad9a2 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -19,6 +19,9 @@ from oqpy.program import Program from oqpy.timing import OQDurationLiteral + def to_ast(self) -> ast.Identifier: + return self + class _FreeParameterTransformer(QASMTransformer): """Walk the AST and evaluate FreeParameterExpressions.""" @@ -37,7 +40,7 @@ def visit_Identifier( using the given parameter values. Args: - identifier (Identifier): The identifier. + identifier (_FreeParameterExpressionIdentifier): The identifier. Returns: Union[Identifier, FloatLiteral]: The transformed identifier. From f4d89f8b1d65bc11276ef011de37c436dc9a37d9 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Fri, 8 Sep 2023 20:15:06 -0400 Subject: [PATCH 02/17] move to_ast to FreeParameterExprsesion --- src/braket/parametric/free_parameter_expression.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index fdd2f5474..3c117966b 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -41,6 +41,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr Args: expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use. + _type (Optional[ClassicalType]): type of the expression Raises: NotImplementedError: Raised if the expression is not of type @@ -57,6 +58,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr ast.Pow: self.__pow__, ast.USub: self.__neg__, } + self._type = _type if _type is not None else FloatType() if isinstance(expression, FreeParameterExpression): self._expression = expression.expression elif isinstance(expression, (Number, sympy.Expr)): From 7dd1f7923bd8983d8125c86527cdee08b3859955 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 14 Sep 2023 20:23:10 -0400 Subject: [PATCH 03/17] add precision about the expression type --- src/braket/parametric/free_parameter_expression.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 3c117966b..2cb2da1fa 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -41,7 +41,10 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr Args: expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use. - _type (Optional[ClassicalType]): type of the expression + _type (Optional[ClassicalType]): The OpenQASM3 type associated with the expression. + Subtypes of openqasm3.ast.ClassicalType are used to specify how to express the + expression in the OpenQASM3 IR. Any type other than DurationType is considered + as FloatType. Raises: NotImplementedError: Raised if the expression is not of type From 9148f8baad2b8dbe6b2b8dc72fa04862c9c2dc21 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 28 Sep 2023 13:27:34 -0400 Subject: [PATCH 04/17] add test for modified waveforms --- .../braket/pulse/test_pulse_sequence.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index da8e0a8a3..fbaaac365 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -80,6 +80,33 @@ def test_pulse_sequence_with_user_defined_frame(user_defined_frame): assert pulse_sequence.to_ir() == expected_str +def test_pulse_sequence_with_modified_wf(predefined_frame_1): + pulse_sequence = PulseSequence().play( + predefined_frame_1, ConstantWaveform(length=1e-7, iq=complex(1), id="constant_wf") + ) + expected_str = "\n".join( + [ + "OPENQASM 3.0;", + "cal {", + " waveform constant_wf = constant(100.0ns, 1.0);", + " play(predefined_frame_1, constant_wf);", + "}", + ] + ) + expected_str_after_mod = "\n".join( + [ + "OPENQASM 3.0;", + "cal {", + " waveform constant_wf = constant(200.0ns, 1.0);", + " play(predefined_frame_1, constant_wf);", + "}", + ] + ) + assert pulse_sequence.to_ir() == expected_str + pulse_sequence._waveforms["constant_wf"].length = 2e-7 + assert pulse_sequence.to_ir() == expected_str_after_mod + + def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined_frame_2): param = FreeParameter("a") + 2 * FreeParameter("b") pulse_sequence = ( From 59000e5d441ca0a02317771b2b71dd7dcc8a715d Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 28 Sep 2023 20:49:21 -0400 Subject: [PATCH 05/17] link pulse sequence to waveforms --- src/braket/pulse/pulse_sequence.py | 10 +- src/braket/pulse/waveforms.py | 196 ++++++++++++++++-- .../braket/pulse/test_pulse_sequence.py | 49 ++++- 3 files changed, 237 insertions(+), 18 deletions(-) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index a788b38c0..a7ba4bf48 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -44,9 +44,17 @@ def __init__(self): self._capture_v0_count = 0 self._program = Program(simplify_constants=False) self._frames = {} - self._waveforms = {} + self._waveforms = WaveformDict({}, self) self._free_parameters = set() + @property + def waveform(self): + return self._waveforms + + @waveform.setter + def waveform(self, value): + self._waveforms = value + def to_time_trace(self) -> PulseSequenceTrace: """Generate an approximate trace of the amplitude, frequency, phase for each frame contained in the PulseSequence, under the action of the instructions contained in diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 9ca43050a..27c259224 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -19,7 +19,15 @@ from typing import Optional, Union import numpy as np -from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 +from oqpy import ( + WaveformVar, + bool_, + complex128, + convert_float_to_duration, + declare_waveform_generator, + duration, + float64, +) from oqpy.base import OQPyExpression from braket.parametric.free_parameter import FreeParameter @@ -30,6 +38,19 @@ from braket.parametric.parameterizable import Parameterizable +class WaveformDict(dict): + def __init__(self, wf_dict: dict, pulse_sequence): + for wf in wf_dict.values(): + wf._pulse_sequence = pulse_sequence + super().__init__(wf_dict) + self._pulse_sequence = pulse_sequence + + def __setitem__(self, key: str, value: Waveform): + value = deepcopy(value) + value._pulse_sequence = self._pulse_sequence + super().__setitem__(key, value) + + class Waveform(ABC): """A waveform is a time-dependent envelope that can be used to emit signals on an output port or receive signals from an input port. As such, when transmitting signals to the qubit, a @@ -39,6 +60,9 @@ class Waveform(ABC): for more details. """ + def __init__(self) -> None: + self._pulse_sequence = None + @abstractmethod def _to_oqpy_expression(self) -> OQPyExpression: """Returns an OQPyExpression defining this waveform.""" @@ -82,8 +106,19 @@ def __init__(self, amplitudes: list[complex], id: Optional[str] = None): id (Optional[str]): The identifier used for declaring this waveform. A random string of ascii characters is assigned by default. """ - self.amplitudes = list(amplitudes) + self._amplitudes = list(amplitudes) self.id = id or _make_identifier_name() + super().__init__() + + @property + def amplitudes(self): + return self._amplitudes + + @amplitudes.setter + def amplitudes(self, value): + self._amplitudes = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression = value def __repr__(self) -> str: return f"ArbitraryWaveform('id': {self.id}, 'amplitudes': {self.amplitudes})" @@ -140,9 +175,34 @@ def __init__( id (Optional[str]): The identifier used for declaring this waveform. A random string of ascii characters is assigned by default. """ - self.length = length - self.iq = iq + self._length = length + self._iq = iq self.id = id or _make_identifier_name() + super().__init__() + + @property + def iq(self): + return self._iq + + @iq.setter + def iq(self, value): + self._iq = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "iq" + ] = value + + @property + def length(self): + return self._length + + @length.setter + def length(self, value): + self._length = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "length" + ] = convert_float_to_duration(value) def __repr__(self) -> str: return f"ConstantWaveform('id': {self.id}, 'length': {self.length}, 'iq': {self.iq})" @@ -256,12 +316,73 @@ def __init__( id (Optional[str]): The identifier used for declaring this waveform. A random string of ascii characters is assigned by default. """ - self.length = length - self.sigma = sigma - self.beta = beta - self.amplitude = amplitude - self.zero_at_edges = zero_at_edges + self._length = length + self._sigma = sigma + self._beta = beta + self._amplitude = amplitude + self._zero_at_edges = zero_at_edges self.id = id or _make_identifier_name() + super().__init__() + + @property + def length(self): + return self._length + + @length.setter + def length(self, value): + self._length = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "length" + ] = convert_float_to_duration(value) + + @property + def sigma(self): + return self._sigma + + @sigma.setter + def sigma(self, value): + self._sigma = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "sigma" + ] = convert_float_to_duration(value) + + @property + def beta(self): + return self._beta + + @beta.setter + def beta(self, value): + self._beta = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "beta" + ] = value + + @property + def amplitude(self): + return self._amplitude + + @amplitude.setter + def amplitude(self, value): + self._amplitude = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "amplitude" + ] = value + + @property + def zero_at_edges(self): + return self._zero_at_edges + + @zero_at_edges.setter + def zero_at_edges(self, value): + self._zero_at_edges = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "zero_at_edges" + ] = value def __repr__(self) -> str: return ( @@ -396,11 +517,60 @@ def __init__( id (Optional[str]): The identifier used for declaring this waveform. A random string of ascii characters is assigned by default. """ - self.length = length - self.sigma = sigma - self.amplitude = amplitude - self.zero_at_edges = zero_at_edges + self._length = length + self._sigma = sigma + self._amplitude = amplitude + self._zero_at_edges = zero_at_edges self.id = id or _make_identifier_name() + super().__init__() + + @property + def length(self): + return self._length + + @length.setter + def length(self, value): + self._length = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "length" + ] = convert_float_to_duration(value) + + @property + def sigma(self): + return self._sigma + + @sigma.setter + def sigma(self, value): + self._sigma = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "sigma" + ] = convert_float_to_duration(value) + + @property + def amplitude(self): + return self._amplitude + + @amplitude.setter + def amplitude(self, value): + self._amplitude = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "amplitude" + ] = value + + @property + def zero_at_edges(self): + return self._zero_at_edges + + @zero_at_edges.setter + def zero_at_edges(self, value): + self._zero_at_edges = value + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + "zero_at_edges" + ] = value def __repr__(self) -> str: return ( diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index fbaaac365..97639dbde 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -81,15 +81,34 @@ def test_pulse_sequence_with_user_defined_frame(user_defined_frame): def test_pulse_sequence_with_modified_wf(predefined_frame_1): - pulse_sequence = PulseSequence().play( - predefined_frame_1, ConstantWaveform(length=1e-7, iq=complex(1), id="constant_wf") + pulse_sequence = ( + PulseSequence() + .play(predefined_frame_1, GaussianWaveform(length=1e-3, sigma=0.7, id="gauss_wf")) + .play( + predefined_frame_1, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), + ) + .play( + predefined_frame_1, + ConstantWaveform(length=4e-3, iq=complex(2, 0.3), id="constant_wf"), + ) + .play( + predefined_frame_1, + ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), + ) ) expected_str = "\n".join( [ "OPENQASM 3.0;", "cal {", - " waveform constant_wf = constant(100.0ns, 1.0);", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", + " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_1, drag_gauss_wf);", " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_1, arb_wf);", "}", ] ) @@ -97,13 +116,35 @@ def test_pulse_sequence_with_modified_wf(predefined_frame_1): [ "OPENQASM 3.0;", "cal {", - " waveform constant_wf = constant(200.0ns, 1.0);", + " waveform gauss_wf = gaussian(17.0ns, 100.0ms, 0.2, true);", + " waveform drag_gauss_wf = drag_gaussian(1.0us, 100.0ms, 0.25, 0.3, true);", + " waveform constant_wf = constant(200.0ns, 0.5);", + " waveform arb_wf = {-1.0 - 0.4im, 0, -0.3, -0.1 - 0.2im};", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_1, drag_gauss_wf);", " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_1, arb_wf);", "}", ] ) assert pulse_sequence.to_ir() == expected_str + pulse_sequence._waveforms["constant_wf"].iq = 0.5 pulse_sequence._waveforms["constant_wf"].length = 2e-7 + + pulse_sequence._waveforms["gauss_wf"].length = 17e-9 + pulse_sequence._waveforms["gauss_wf"].sigma = 0.1 + pulse_sequence._waveforms["gauss_wf"].amplitude = 0.2 + pulse_sequence._waveforms["gauss_wf"].zero_at_edges = True + + pulse_sequence._waveforms["drag_gauss_wf"].length = 1e-6 + pulse_sequence._waveforms["drag_gauss_wf"].sigma = 0.1 + pulse_sequence._waveforms["drag_gauss_wf"].beta = 0.25 + pulse_sequence._waveforms["drag_gauss_wf"].amplitude = 0.3 + pulse_sequence._waveforms["drag_gauss_wf"].zero_at_edges = True + + pulse_sequence._waveforms["arb_wf"].amplitudes = [-complex(1, 0.4), 0, -0.3, -complex(0.1, 0.2)] + + # pulse_sequence.update_waveform(waveform_name="constant_wf", length=2e-7) assert pulse_sequence.to_ir() == expected_str_after_mod From 59463b1ceeafe606dc2354feb230184c7c606213 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 28 Sep 2023 20:57:35 -0400 Subject: [PATCH 06/17] fix typos --- src/braket/pulse/pulse_sequence.py | 6 ++--- .../braket/pulse/test_pulse_sequence.py | 25 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index a7ba4bf48..5ff664e85 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -48,11 +48,11 @@ def __init__(self): self._free_parameters = set() @property - def waveform(self): + def waveforms(self): return self._waveforms - @waveform.setter - def waveform(self, value): + @waveforms.setter + def waveforms(self, value): self._waveforms = value def to_time_trace(self) -> PulseSequenceTrace: diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 97639dbde..7ea9bb6e6 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -128,23 +128,22 @@ def test_pulse_sequence_with_modified_wf(predefined_frame_1): ] ) assert pulse_sequence.to_ir() == expected_str - pulse_sequence._waveforms["constant_wf"].iq = 0.5 - pulse_sequence._waveforms["constant_wf"].length = 2e-7 + pulse_sequence.waveforms["constant_wf"].iq = 0.5 + pulse_sequence.waveforms["constant_wf"].length = 2e-7 - pulse_sequence._waveforms["gauss_wf"].length = 17e-9 - pulse_sequence._waveforms["gauss_wf"].sigma = 0.1 - pulse_sequence._waveforms["gauss_wf"].amplitude = 0.2 - pulse_sequence._waveforms["gauss_wf"].zero_at_edges = True + pulse_sequence.waveforms["gauss_wf"].length = 17e-9 + pulse_sequence.waveforms["gauss_wf"].sigma = 0.1 + pulse_sequence.waveforms["gauss_wf"].amplitude = 0.2 + pulse_sequence.waveforms["gauss_wf"].zero_at_edges = True - pulse_sequence._waveforms["drag_gauss_wf"].length = 1e-6 - pulse_sequence._waveforms["drag_gauss_wf"].sigma = 0.1 - pulse_sequence._waveforms["drag_gauss_wf"].beta = 0.25 - pulse_sequence._waveforms["drag_gauss_wf"].amplitude = 0.3 - pulse_sequence._waveforms["drag_gauss_wf"].zero_at_edges = True + pulse_sequence.waveforms["drag_gauss_wf"].length = 1e-6 + pulse_sequence.waveforms["drag_gauss_wf"].sigma = 0.1 + pulse_sequence.waveforms["drag_gauss_wf"].beta = 0.25 + pulse_sequence.waveforms["drag_gauss_wf"].amplitude = 0.3 + pulse_sequence.waveforms["drag_gauss_wf"].zero_at_edges = True - pulse_sequence._waveforms["arb_wf"].amplitudes = [-complex(1, 0.4), 0, -0.3, -complex(0.1, 0.2)] + pulse_sequence.waveforms["arb_wf"].amplitudes = [-complex(1, 0.4), 0, -0.3, -complex(0.1, 0.2)] - # pulse_sequence.update_waveform(waveform_name="constant_wf", length=2e-7) assert pulse_sequence.to_ir() == expected_str_after_mod From 3afcfb65c4f2754ae5dfbc0eb8e3e0fc19cc8a35 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 28 Sep 2023 22:44:06 -0400 Subject: [PATCH 07/17] support free parameters --- src/braket/pulse/waveforms.py | 66 +++++++++-------------------------- 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 27c259224..b42ad3698 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -19,15 +19,7 @@ from typing import Optional, Union import numpy as np -from oqpy import ( - WaveformVar, - bool_, - complex128, - convert_float_to_duration, - declare_waveform_generator, - duration, - float64, -) +from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 from oqpy.base import OQPyExpression from braket.parametric.free_parameter import FreeParameter @@ -63,6 +55,12 @@ class Waveform(ABC): def __init__(self) -> None: self._pulse_sequence = None + def _modify_oqpy_waveform_var(self, key, value, type_=float64): + if self._pulse_sequence is not None: + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ + key + ] = self._pulse_sequence._format_parameter_ast(value, type_) + @abstractmethod def _to_oqpy_expression(self) -> OQPyExpression: """Returns an OQPyExpression defining this waveform.""" @@ -187,10 +185,7 @@ def iq(self): @iq.setter def iq(self, value): self._iq = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "iq" - ] = value + self._modify_oqpy_waveform_var("iq", value) @property def length(self): @@ -199,10 +194,7 @@ def length(self): @length.setter def length(self, value): self._length = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "length" - ] = convert_float_to_duration(value) + self._modify_oqpy_waveform_var("length", value, duration) def __repr__(self) -> str: return f"ConstantWaveform('id': {self.id}, 'length': {self.length}, 'iq': {self.iq})" @@ -331,10 +323,7 @@ def length(self): @length.setter def length(self, value): self._length = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "length" - ] = convert_float_to_duration(value) + self._modify_oqpy_waveform_var("length", value, duration) @property def sigma(self): @@ -343,10 +332,7 @@ def sigma(self): @sigma.setter def sigma(self, value): self._sigma = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "sigma" - ] = convert_float_to_duration(value) + self._modify_oqpy_waveform_var("sigma", value, duration) @property def beta(self): @@ -355,10 +341,7 @@ def beta(self): @beta.setter def beta(self, value): self._beta = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "beta" - ] = value + self._modify_oqpy_waveform_var("beta", value) @property def amplitude(self): @@ -367,10 +350,7 @@ def amplitude(self): @amplitude.setter def amplitude(self, value): self._amplitude = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "amplitude" - ] = value + self._modify_oqpy_waveform_var("amplitude", value) @property def zero_at_edges(self): @@ -379,10 +359,7 @@ def zero_at_edges(self): @zero_at_edges.setter def zero_at_edges(self, value): self._zero_at_edges = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "zero_at_edges" - ] = value + self._modify_oqpy_waveform_var("zero_at_edges", value) def __repr__(self) -> str: return ( @@ -531,10 +508,7 @@ def length(self): @length.setter def length(self, value): self._length = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "length" - ] = convert_float_to_duration(value) + self._modify_oqpy_waveform_var("length", value, duration) @property def sigma(self): @@ -543,10 +517,7 @@ def sigma(self): @sigma.setter def sigma(self, value): self._sigma = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "sigma" - ] = convert_float_to_duration(value) + self._modify_oqpy_waveform_var("sigma", value, duration) @property def amplitude(self): @@ -555,10 +526,7 @@ def amplitude(self): @amplitude.setter def amplitude(self, value): self._amplitude = value - if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - "amplitude" - ] = value + self._modify_oqpy_waveform_var("amplitude", value) @property def zero_at_edges(self): From ef83f8d1803745d172766921bdf422626ee13df5 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Wed, 4 Oct 2023 17:16:56 -0400 Subject: [PATCH 08/17] update docstrings --- src/braket/pulse/pulse_sequence.py | 10 ++- src/braket/pulse/waveforms.py | 139 +++++++++++++++++++++++------ 2 files changed, 121 insertions(+), 28 deletions(-) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 5ff664e85..0a237f4fe 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -48,11 +48,17 @@ def __init__(self): self._free_parameters = set() @property - def waveforms(self): + def waveforms(self) -> WaveformDict: return self._waveforms @waveforms.setter - def waveforms(self, value): + def waveforms(self, value: WaveformDict) -> None: + """ + Adds a set of waveforms. + + Args: + value (WaveformDict): the waveform dict. + """ self._waveforms = value def to_time_trace(self) -> PulseSequenceTrace: diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index b42ad3698..def9300e3 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -19,6 +19,7 @@ from typing import Optional, Union import numpy as np +import openpulse.ast as ast from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 from oqpy.base import OQPyExpression @@ -31,7 +32,7 @@ class WaveformDict(dict): - def __init__(self, wf_dict: dict, pulse_sequence): + def __init__(self, wf_dict: Dict, pulse_sequence): for wf in wf_dict.values(): wf._pulse_sequence = pulse_sequence super().__init__(wf_dict) @@ -55,7 +56,9 @@ class Waveform(ABC): def __init__(self) -> None: self._pulse_sequence = None - def _modify_oqpy_waveform_var(self, key, value, type_=float64): + def _modify_oqpy_waveform_var( + self, key: str, value: Any, type_: ast.ClassicalType = float64 + ) -> None: if self._pulse_sequence is not None: self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ key @@ -109,11 +112,20 @@ def __init__(self, amplitudes: list[complex], id: Optional[str] = None): super().__init__() @property - def amplitudes(self): + def amplitudes(self) -> List[complex]: return self._amplitudes @amplitudes.setter - def amplitudes(self, value): + def amplitudes(self, value: List[complex]) -> None: + """ + Sets the list of amplitudes. + + Args: + value (List[complex]): Array of complex values specifying the + waveform amplitude at each timestep. The timestep is determined by the sampling rate + of the frame to which waveform is applied to. + + """ self._amplitudes = value if self._pulse_sequence is not None: self._pulse_sequence._program.undeclared_vars[self.id].init_expression = value @@ -179,20 +191,33 @@ def __init__( super().__init__() @property - def iq(self): + def iq(self) -> complex: return self._iq @iq.setter - def iq(self, value): + def iq(self, value: complex) -> None: + """ + Sets the IQ value. + + Args: + value (complex): complex value specifying the amplitude of the waveform. + """ self._iq = value self._modify_oqpy_waveform_var("iq", value) @property - def length(self): + def length(self) -> Union[float, FreeParameterExpression]: return self._length @length.setter - def length(self, value): + def length(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the length. + + Args: + value (Union[float, FreeParameterExpression]): Value (in seconds) + specifying the duration of the waveform. + """ self._length = value self._modify_oqpy_waveform_var("length", value, duration) @@ -317,47 +342,81 @@ def __init__( super().__init__() @property - def length(self): + def length(self) -> Union[float, FreeParameterExpression]: return self._length @length.setter - def length(self, value): + def length(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the length. + + Args: + value (Union[float, FreeParameterExpression]): Value (in seconds) + specifying the duration of the waveform. + """ self._length = value self._modify_oqpy_waveform_var("length", value, duration) @property - def sigma(self): + def sigma(self) -> Union[float, FreeParameterExpression]: return self._sigma @sigma.setter - def sigma(self, value): + def sigma(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the DRAG gaussian width. + + Args: + value (Union[float, FreeParameterExpression]): A measure (in seconds) of + how wide or narrow the Gaussian peak is. + """ self._sigma = value self._modify_oqpy_waveform_var("sigma", value, duration) @property - def beta(self): + def beta(self) -> Union[float, FreeParameterExpression]: return self._beta @beta.setter - def beta(self, value): + def beta(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the beta value. + + Args: + value (Union[float, FreeParameterExpression]): The correction amplitude. + """ self._beta = value self._modify_oqpy_waveform_var("beta", value) @property - def amplitude(self): + def amplitude(self) -> Union[float, FreeParameterExpression]: return self._amplitude @amplitude.setter - def amplitude(self, value): + def amplitude(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the amplitude. + + Args: + value (Union[float, FreeParameterExpression]): The amplitude of the + waveform envelope. + """ self._amplitude = value self._modify_oqpy_waveform_var("amplitude", value) @property - def zero_at_edges(self): + def zero_at_edges(self) -> bool: return self._zero_at_edges @zero_at_edges.setter - def zero_at_edges(self, value): + def zero_at_edges(self, value: bool) -> None: + """ + Sets if the DRAG gaussian waveform should start and end at zero. + + Args: + value (bool): bool specifying whether the waveform amplitude is clipped to + zero at the edges. + """ self._zero_at_edges = value self._modify_oqpy_waveform_var("zero_at_edges", value) @@ -502,38 +561,66 @@ def __init__( super().__init__() @property - def length(self): + def length(self) -> Union[float, FreeParameterExpression]: return self._length @length.setter - def length(self, value): + def length(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the length. + + Args: + value (Union[float, FreeParameterExpression]): Value (in seconds) specifying the + duration of the waveform. + """ self._length = value self._modify_oqpy_waveform_var("length", value, duration) @property - def sigma(self): + def sigma(self) -> Union[float, FreeParameterExpression]: return self._sigma @sigma.setter - def sigma(self, value): + def sigma(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the gaussian waveform width. + + Args: + value (Union[float, FreeParameterExpression]): A measure (in seconds) of how wide + or narrow the Gaussian peak is. + """ self._sigma = value self._modify_oqpy_waveform_var("sigma", value, duration) @property - def amplitude(self): + def amplitude(self) -> Union[float, FreeParameterExpression]: return self._amplitude @amplitude.setter - def amplitude(self, value): + def amplitude(self, value: Union[float, FreeParameterExpression]) -> None: + """ + Sets the amplitude. + + Args: + value (Union[float, FreeParameterExpression]): The amplitude of the waveform + envelope. + """ self._amplitude = value self._modify_oqpy_waveform_var("amplitude", value) @property - def zero_at_edges(self): + def zero_at_edges(self) -> bool: return self._zero_at_edges @zero_at_edges.setter - def zero_at_edges(self, value): + def zero_at_edges(self, value: bool) -> None: + """ + Sets if the DRAG gaussian waveform should start and end at zero. + + Args: + value (bool): bool specifying whether the waveform amplitude is clipped to + zero at the edges. + """ self._zero_at_edges = value if self._pulse_sequence is not None: self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ From 90db12f83975408163afbfa76be7bb5a572837d8 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Wed, 4 Oct 2023 18:08:35 -0400 Subject: [PATCH 09/17] add type validation --- src/braket/parametric/free_parameter_expression.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 2cb2da1fa..1053b13f5 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -70,6 +70,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr self._expression = self._parse_string_expression(expression).expression else: raise NotImplementedError + self._validate_type() @property def expression(self) -> Union[Number, sympy.Expr]: @@ -108,6 +109,13 @@ def subs( else: return FreeParameterExpression(subbed_expr) + def _validate_type(self) -> None: + if not isinstance(self._type, (FloatType, DurationType)): + raise TypeError( + "FreeParameterExpression must be of type openqasm3.ast.FloatType " + "or openqasm3.ast.DurationType" + ) + def _parse_string_expression(self, expression: str) -> FreeParameterExpression: return self._eval_operation(ast.parse(expression, mode="eval").body) From 3918c78b76d49cb57b0f0952f6c58a893bc2cd22 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Thu, 5 Oct 2023 12:22:45 -0400 Subject: [PATCH 10/17] avoid circuit import because of typing --- src/braket/pulse/waveforms.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index def9300e3..e095f08cc 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -32,10 +32,19 @@ class WaveformDict(dict): - def __init__(self, wf_dict: Dict, pulse_sequence): - for wf in wf_dict.values(): - wf._pulse_sequence = pulse_sequence - super().__init__(wf_dict) + """ + A dict of waveforms. + + Note: + WaveformDict binds a pulse sequence to each waveform that is + added to the dict. It serves as back reference when a + waveform is modified so the OQpy object is also updated. + """ + + def __init__(self, waveform_dict: Dict, pulse_sequence: PulseSequence): + for waveform in waveform_dict.values(): + waveform._pulse_sequence = pulse_sequence + super().__init__(waveform_dict) self._pulse_sequence = pulse_sequence def __setitem__(self, key: str, value: Waveform): From ea9119f1437728537676ff2abf6fecd0bb14cfd9 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Mon, 18 Mar 2024 16:47:21 -0400 Subject: [PATCH 11/17] Merge branch 'main' into jcjaskula-aws/add_in_place_modifications --- src/braket/aws/aws_quantum_task.py | 21 +++++++++++++++++++++ src/braket/pulse/waveforms.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 54f544159..b3800f19c 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -162,6 +162,15 @@ def create( those tasks do not need to be created with the reservation ARN. Default: None. + quiet (bool): Sets the verbosity of the logger to low and does not report queue + position. Default is `False`. + + 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. @@ -191,6 +200,18 @@ def create( inputs = inputs or {} gate_definitions = gate_definitions or {} + if reservation_arn: + create_task_kwargs.update( + { + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + } + ) + if reservation_arn: create_task_kwargs.update( { diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index e095f08cc..79a04d740 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -69,9 +69,9 @@ def _modify_oqpy_waveform_var( self, key: str, value: Any, type_: ast.ClassicalType = float64 ) -> None: if self._pulse_sequence is not None: - self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[ - key - ] = self._pulse_sequence._format_parameter_ast(value, type_) + self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[key] = ( + self._pulse_sequence._format_parameter_ast(value, type_) + ) @abstractmethod def _to_oqpy_expression(self) -> OQPyExpression: From 7d9bc3bf3d5e19ce57623e1a9a7c98f28814a4a6 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Mon, 18 Mar 2024 17:29:40 -0400 Subject: [PATCH 12/17] fix merge --- .../parametric/free_parameter_expression.py | 13 ----------- src/braket/pulse/ast/free_parameters.py | 2 +- src/braket/pulse/waveforms.py | 22 ++++++++++++++++--- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 1053b13f5..fdd2f5474 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -41,10 +41,6 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr Args: expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use. - _type (Optional[ClassicalType]): The OpenQASM3 type associated with the expression. - Subtypes of openqasm3.ast.ClassicalType are used to specify how to express the - expression in the OpenQASM3 IR. Any type other than DurationType is considered - as FloatType. Raises: NotImplementedError: Raised if the expression is not of type @@ -61,7 +57,6 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr ast.Pow: self.__pow__, ast.USub: self.__neg__, } - self._type = _type if _type is not None else FloatType() if isinstance(expression, FreeParameterExpression): self._expression = expression.expression elif isinstance(expression, (Number, sympy.Expr)): @@ -70,7 +65,6 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr self._expression = self._parse_string_expression(expression).expression else: raise NotImplementedError - self._validate_type() @property def expression(self) -> Union[Number, sympy.Expr]: @@ -109,13 +103,6 @@ def subs( else: return FreeParameterExpression(subbed_expr) - def _validate_type(self) -> None: - if not isinstance(self._type, (FloatType, DurationType)): - raise TypeError( - "FreeParameterExpression must be of type openqasm3.ast.FloatType " - "or openqasm3.ast.DurationType" - ) - def _parse_string_expression(self, expression: str) -> FreeParameterExpression: return self._eval_operation(ast.parse(expression, mode="eval").body) diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 1c0fad9a2..c246375e3 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -40,7 +40,7 @@ def visit_Identifier( using the given parameter values. Args: - identifier (_FreeParameterExpressionIdentifier): The identifier. + identifier (Identifier): The identifier. Returns: Union[Identifier, FloatLiteral]: The transformed identifier. diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 79a04d740..f5f3b5d87 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -20,8 +20,16 @@ import numpy as np import openpulse.ast as ast -from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 -from oqpy.base import OQPyExpression +from oqpy import ( + WaveformVar, + bool_, + complex128, + convert_float_to_duration, + declare_waveform_generator, + duration, + float64, +) +from oqpy.base import OQPyExpression, to_ast from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import ( @@ -69,8 +77,16 @@ def _modify_oqpy_waveform_var( self, key: str, value: Any, type_: ast.ClassicalType = float64 ) -> None: if self._pulse_sequence is not None: + self._pulse_sequence._register_free_parameters(value) self._pulse_sequence._program.undeclared_vars[self.id].init_expression.args[key] = ( - self._pulse_sequence._format_parameter_ast(value, type_) + to_ast( + self._pulse_sequence._program, + ( + convert_float_to_duration(value) + if isinstance(type_, ast.DurationType) + else value + ), + ) ) @abstractmethod From e60c277b1450173f01094685c8fa9b9838258716 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Mon, 18 Mar 2024 17:47:49 -0400 Subject: [PATCH 13/17] Merge branch 'main' into jcjaskula-aws/add_in_place_modifications --- src/braket/pulse/waveforms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index f5f3b5d87..51439cbc7 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -49,7 +49,7 @@ class WaveformDict(dict): waveform is modified so the OQpy object is also updated. """ - def __init__(self, waveform_dict: Dict, pulse_sequence: PulseSequence): + def __init__(self, waveform_dict: dict, pulse_sequence: PulseSequence): for waveform in waveform_dict.values(): waveform._pulse_sequence = pulse_sequence super().__init__(waveform_dict) @@ -137,16 +137,16 @@ def __init__(self, amplitudes: list[complex], id: Optional[str] = None): super().__init__() @property - def amplitudes(self) -> List[complex]: + def amplitudes(self) -> list[complex]: return self._amplitudes @amplitudes.setter - def amplitudes(self, value: List[complex]) -> None: + def amplitudes(self, value: list[complex]) -> None: """ Sets the list of amplitudes. Args: - value (List[complex]): Array of complex values specifying the + value (list[complex]): Array of complex values specifying the waveform amplitude at each timestep. The timestep is determined by the sampling rate of the frame to which waveform is applied to. From b75bb74848e7c00a7bb17ae009f41cd6c1cd412b Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Mon, 18 Mar 2024 17:58:43 -0400 Subject: [PATCH 14/17] fix rebase --- src/braket/aws/aws_quantum_task.py | 21 --------------------- src/braket/pulse/ast/free_parameters.py | 3 --- src/braket/pulse/pulse_sequence.py | 2 +- src/braket/pulse/waveforms.py | 6 +++++- 4 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index b3800f19c..54f544159 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -162,15 +162,6 @@ def create( those tasks do not need to be created with the reservation ARN. Default: None. - quiet (bool): Sets the verbosity of the logger to low and does not report queue - position. Default is `False`. - - 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. @@ -200,18 +191,6 @@ def create( inputs = inputs or {} gate_definitions = gate_definitions or {} - if reservation_arn: - create_task_kwargs.update( - { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - } - ) - if reservation_arn: create_task_kwargs.update( { diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index c246375e3..41c541da8 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -19,9 +19,6 @@ from oqpy.program import Program from oqpy.timing import OQDurationLiteral - def to_ast(self) -> ast.Identifier: - return self - class _FreeParameterTransformer(QASMTransformer): """Walk the AST and evaluate FreeParameterExpressions.""" diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 0a237f4fe..8fc628a98 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -31,7 +31,7 @@ from braket.pulse.ast.qasm_transformer import _IRQASMTransformer from braket.pulse.frame import Frame from braket.pulse.pulse_sequence_trace import PulseSequenceTrace -from braket.pulse.waveforms import Waveform +from braket.pulse.waveforms import Waveform, WaveformDict from braket.registers.qubit_set import QubitSet diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 51439cbc7..57be07424 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -16,7 +16,11 @@ import random import string from abc import ABC, abstractmethod -from typing import Optional, Union +from copy import deepcopy +from typing import TYPE_CHECKING, Any, Optional, Union + +if TYPE_CHECKING: + from braket.pulse.pulse_sequence import PulseSequence import numpy as np import openpulse.ast as ast From 98c38931b78da7fc7120322eed6aada186d59bdd Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula Date: Mon, 18 Mar 2024 18:41:25 -0400 Subject: [PATCH 15/17] fix coverage --- src/braket/pulse/pulse_sequence.py | 10 ---------- src/braket/pulse/waveforms.py | 2 +- test/unit_tests/braket/pulse/test_pulse_sequence.py | 10 ++++++++++ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 8fc628a98..40076bb12 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -51,16 +51,6 @@ def __init__(self): def waveforms(self) -> WaveformDict: return self._waveforms - @waveforms.setter - def waveforms(self, value: WaveformDict) -> None: - """ - Adds a set of waveforms. - - Args: - value (WaveformDict): the waveform dict. - """ - self._waveforms = value - def to_time_trace(self) -> PulseSequenceTrace: """Generate an approximate trace of the amplitude, frequency, phase for each frame contained in the PulseSequence, under the action of the instructions contained in diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 57be07424..722f858c9 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -19,7 +19,7 @@ from copy import deepcopy from typing import TYPE_CHECKING, Any, Optional, Union -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from braket.pulse.pulse_sequence import PulseSequence import numpy as np diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 7ea9bb6e6..ff69d3dca 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -23,6 +23,7 @@ Port, PulseSequence, ) +from braket.pulse.waveforms import WaveformDict @pytest.fixture @@ -80,6 +81,15 @@ def test_pulse_sequence_with_user_defined_frame(user_defined_frame): assert pulse_sequence.to_ir() == expected_str +def test_create_waveformdict_with_pulse_sequence(user_defined_frame): + pulse_sequence = PulseSequence().set_frequency(user_defined_frame, 6e6) + wf = ConstantWaveform(1e-3, complex(1, 2), "wf_id") + + waveform_dict = WaveformDict({"wf_id": wf}, pulse_sequence) + assert waveform_dict._pulse_sequence == pulse_sequence + assert waveform_dict["wf_id"]._pulse_sequence == pulse_sequence + + def test_pulse_sequence_with_modified_wf(predefined_frame_1): pulse_sequence = ( PulseSequence() From ab6dd3806cc2d2a1859329f9a91f9c2aaf69b002 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:18:05 -0700 Subject: [PATCH 16/17] Update code-freeze.yml (#1148) --- .github/workflows/code-freeze.yml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/code-freeze.yml b/.github/workflows/code-freeze.yml index d19f5b48d..84a3e85c9 100644 --- a/.github/workflows/code-freeze.yml +++ b/.github/workflows/code-freeze.yml @@ -20,22 +20,13 @@ jobs: - name: Fetch PR data and check if merge allowed if: env.FROZEN == 'true' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_TITLE: ${{ github.event.pull_request.title }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} 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 it's not a critical fix - if ! [[ "$PR_TITLE" == fix\(critical\):* ]]; then + if ! [[ "$PR_TITLE" == fix(critical):* ]]; then # and there's an unfrozen prefix - if ! [[ -z $UNFROZEN_PREFIX ]]; then + if [[ -n "${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): '." From 251a842fe89ed31891a63bf5f457ce04af7db08b Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 17 Dec 2025 02:47:32 -0800 Subject: [PATCH 17/17] linters --- src/braket/pulse/waveforms.py | 38 ++++++------- .../braket/emulation/test_local_emulator.py | 12 ++-- .../braket/pulse/test_pulse_sequence.py | 56 +++++++++---------- 3 files changed, 51 insertions(+), 55 deletions(-) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 78b0c89b7..25a3b1e1d 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -17,14 +17,14 @@ import string from abc import ABC, abstractmethod from copy import deepcopy -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: # pragma: no cover from braket.pulse.pulse_sequence import PulseSequence import numpy as np import scipy as sp -import openpulse.ast as ast +from openpulse import ast from oqpy import ( WaveformVar, bool_, @@ -44,7 +44,7 @@ from braket.parametric.parameterizable import Parameterizable -class WaveformDict(dict): +class WaveformDict(dict): # noqa: FURB189 """ A dict of waveforms. @@ -234,11 +234,11 @@ def iq(self, value: complex) -> None: self._modify_oqpy_waveform_var("iq", value) @property - def length(self) -> Union[float, FreeParameterExpression]: + def length(self) -> float | FreeParameterExpression: return self._length @length.setter - def length(self, value: Union[float, FreeParameterExpression]) -> None: + def length(self, value: float | FreeParameterExpression) -> None: """ Sets the length. @@ -369,11 +369,11 @@ def __init__( super().__init__() @property - def length(self) -> Union[float, FreeParameterExpression]: + def length(self) -> float | FreeParameterExpression: return self._length @length.setter - def length(self, value: Union[float, FreeParameterExpression]) -> None: + def length(self, value: float | FreeParameterExpression) -> None: """ Sets the length. @@ -385,11 +385,11 @@ def length(self, value: Union[float, FreeParameterExpression]) -> None: self._modify_oqpy_waveform_var("length", value, duration) @property - def sigma(self) -> Union[float, FreeParameterExpression]: + def sigma(self) -> float | FreeParameterExpression: return self._sigma @sigma.setter - def sigma(self, value: Union[float, FreeParameterExpression]) -> None: + def sigma(self, value: float | FreeParameterExpression) -> None: """ Sets the DRAG gaussian width. @@ -401,11 +401,11 @@ def sigma(self, value: Union[float, FreeParameterExpression]) -> None: self._modify_oqpy_waveform_var("sigma", value, duration) @property - def beta(self) -> Union[float, FreeParameterExpression]: + def beta(self) -> float | FreeParameterExpression: return self._beta @beta.setter - def beta(self, value: Union[float, FreeParameterExpression]) -> None: + def beta(self, value: float | FreeParameterExpression) -> None: """ Sets the beta value. @@ -416,11 +416,11 @@ def beta(self, value: Union[float, FreeParameterExpression]) -> None: self._modify_oqpy_waveform_var("beta", value) @property - def amplitude(self) -> Union[float, FreeParameterExpression]: + def amplitude(self) -> float | FreeParameterExpression: return self._amplitude @amplitude.setter - def amplitude(self, value: Union[float, FreeParameterExpression]) -> None: + def amplitude(self, value: float | FreeParameterExpression) -> None: """ Sets the amplitude. @@ -587,11 +587,11 @@ def __init__( super().__init__() @property - def length(self) -> Union[float, FreeParameterExpression]: + def length(self) -> float | FreeParameterExpression: return self._length @length.setter - def length(self, value: Union[float, FreeParameterExpression]) -> None: + def length(self, value: float | FreeParameterExpression) -> None: """ Sets the length. @@ -603,11 +603,11 @@ def length(self, value: Union[float, FreeParameterExpression]) -> None: self._modify_oqpy_waveform_var("length", value, duration) @property - def sigma(self) -> Union[float, FreeParameterExpression]: + def sigma(self) -> float | FreeParameterExpression: return self._sigma @sigma.setter - def sigma(self, value: Union[float, FreeParameterExpression]) -> None: + def sigma(self, value: float | FreeParameterExpression) -> None: """ Sets the gaussian waveform width. @@ -619,11 +619,11 @@ def sigma(self, value: Union[float, FreeParameterExpression]) -> None: self._modify_oqpy_waveform_var("sigma", value, duration) @property - def amplitude(self) -> Union[float, FreeParameterExpression]: + def amplitude(self) -> float | FreeParameterExpression: return self._amplitude @amplitude.setter - def amplitude(self, value: Union[float, FreeParameterExpression]) -> None: + def amplitude(self, value: float | FreeParameterExpression) -> None: """ Sets the amplitude. diff --git a/test/unit_tests/braket/emulation/test_local_emulator.py b/test/unit_tests/braket/emulation/test_local_emulator.py index 59cc1fd88..d322d7df3 100644 --- a/test/unit_tests/braket/emulation/test_local_emulator.py +++ b/test/unit_tests/braket/emulation/test_local_emulator.py @@ -123,10 +123,10 @@ def test_program_set(reduced_standardized_json): # Notes for the chosen TARGET_F1Q: # For a "target one qubit gate average gate fidelity" q, suppose, suppose -# we mistakenly set the "target one qubit gate average gate error" as (1-q), +# we mistakenly set the "target one qubit gate average gate error" as (1-q), # then we obtain the input-output state fidelity as 1-2/3 * (1-q) = 2q/3+1/3, # which is different from the target fidelity q. In order to distinguish these -# two values, while making sure that the estimated fidelity is close to the +# two values, while making sure that the estimated fidelity is close to the # target fidelity 99.73% of the time [3 sigma], we want |q - (2q+3+1/3)| = |1-q|/3 # to be large enough, or |1-q|/3 = 2 * (3 * 1/np.sqrt(NUM_SHOTS)). In other words, # we set the difference between the errant and correct estimate to be at least 6 sigma, @@ -134,7 +134,7 @@ def test_program_set(reduced_standardized_json): # # Notes for the chosen TARGET_F2Q: # For a "target two qubit gate average gate fidelity" q, suppose, suppose -# we mistakenly set the "target two qubit gate average gate error" as (1-q), +# we mistakenly set the "target two qubit gate average gate error" as (1-q), # then we obtain the input-output state fidelity as 1-4/5 * (1-q) = 4q/5+1/5, # which is different from the target fidelity q. Using the same argument as above, we # set |q - (4q/5+1/5)| = |1-q|/5 = 2 * (3 * 1/np.sqrt(NUM_SHOTS)). @@ -203,8 +203,8 @@ def test_one_qubit_depolarizing_rate(customized_emulator): result = customized_emulator.run(circ, shots=num_samples).result().measurement_probabilities prob_0 = result["0"] assert abs(TARGET_F1Q - prob_0) < NUM_SIGMA / np.sqrt(num_samples) - # If this unit test failed, it could be statistical fluctuation [with 1/370 probability], - # please retry again. + # If this unit test failed, it could be statistical fluctuation [with 1/370 probability], + # please retry again. def test_two_qubit_depolarizing_rate(customized_emulator): @@ -214,5 +214,5 @@ def test_two_qubit_depolarizing_rate(customized_emulator): result = customized_emulator.run(circ, shots=num_samples).result().measurement_probabilities prob_00 = result["00"] assert abs(TARGET_F2Q - prob_00) < NUM_SIGMA / np.sqrt(num_samples) - # If this unit test failed, it could be statistical fluctuation [with 1/370 probability], + # If this unit test failed, it could be statistical fluctuation [with 1/370 probability], # please retry again. diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 882166264..ba7176200 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -106,36 +106,32 @@ def test_pulse_sequence_with_modified_wf(predefined_frame_1): ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), ) ) - expected_str = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", - " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", - " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_1, drag_gauss_wf);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_1, arb_wf);", - "}", - ] - ) - expected_str_after_mod = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " waveform gauss_wf = gaussian(17.0ns, 100.0ms, 0.2, true);", - " waveform drag_gauss_wf = drag_gaussian(1.0us, 100.0ms, 0.25, 0.3, true);", - " waveform constant_wf = constant(200.0ns, 0.5);", - " waveform arb_wf = {-1.0 - 0.4im, 0, -0.3, -0.1 - 0.2im};", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_1, drag_gauss_wf);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_1, arb_wf);", - "}", - ] - ) + expected_str = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", + " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_1, drag_gauss_wf);", + " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_1, arb_wf);", + "}", + ]) + expected_str_after_mod = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " waveform gauss_wf = gaussian(17.0ns, 100.0ms, 0.2, true);", + " waveform drag_gauss_wf = drag_gaussian(1.0us, 100.0ms, 0.25, 0.3, true);", + " waveform constant_wf = constant(200.0ns, 0.5);", + " waveform arb_wf = {-1.0 - 0.4im, 0, -0.3, -0.1 - 0.2im};", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_1, drag_gauss_wf);", + " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_1, arb_wf);", + "}", + ]) assert pulse_sequence.to_ir() == expected_str pulse_sequence.waveforms["constant_wf"].iq = 0.5 pulse_sequence.waveforms["constant_wf"].length = 2e-7