From 4427ddbad3495c746ff899f3596db3e79783a944 Mon Sep 17 00:00:00 2001 From: TsafrirA Date: Thu, 29 Jun 2023 10:36:58 +0300 Subject: [PATCH 1/6] Deprecate complex amp support for symbolic pulses. --- qiskit/pulse/library/symbolic_pulses.py | 40 +++--- ...eprecate-complex-amp-41381bd9722bc878.yaml | 17 +++ test/python/pulse/test_parameter_manager.py | 6 +- test/python/pulse/test_pulse_lib.py | 116 ++++++++++-------- test/python/pulse/test_schedule.py | 4 +- test/python/pulse/test_transforms.py | 24 ++-- test/python/qobj/test_pulse_converter.py | 26 +++- test/python/scheduler/test_basic_scheduler.py | 5 +- 8 files changed, 142 insertions(+), 96 deletions(-) create mode 100644 releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 878f6a4741f5..d0801d025912 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -595,15 +595,15 @@ class ScalableSymbolicPulse(SymbolicPulse): additional_msg=( "Instead, use a float for ``amp`` (for the magnitude) and a float for ``angle``" ), - since="0.23.0", - pending=True, + since="0.25.0", + pending=False, predicate=lambda amp: isinstance(amp, complex), ) def __init__( self, pulse_type: str, duration: Union[ParameterExpression, int], - amp: Union[ParameterExpression, float, complex], + amp: Union[ParameterExpression, float], angle: Union[ParameterExpression, float], parameters: Optional[Dict[str, Union[ParameterExpression, complex]]] = None, name: Optional[str] = None, @@ -739,7 +739,7 @@ class Gaussian(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[complex, float, ParameterExpression], + amp: Union[float, ParameterExpression], sigma: Union[float, ParameterExpression], angle: Optional[Union[float, ParameterExpression]] = None, name: Optional[str] = None, @@ -750,7 +750,7 @@ def __new__( Args: duration: Pulse length in terms of the sampling period `dt`. amp: The magnitude of the amplitude of the Gaussian envelope. - Complex amp support will be deprecated. + Complex amp support is deprecated. sigma: A measure of how wide or narrow the Gaussian peak is; described mathematically in the class docstring. angle: The angle of the complex amplitude of the Gaussian envelope. Default value 0. @@ -835,7 +835,7 @@ class GaussianSquare(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[complex, float, ParameterExpression], + amp: Union[float, ParameterExpression], sigma: Union[float, ParameterExpression], width: Optional[Union[float, ParameterExpression]] = None, angle: Optional[Union[float, ParameterExpression]] = None, @@ -848,7 +848,7 @@ def __new__( Args: duration: Pulse length in terms of the sampling period `dt`. amp: The magnitude of the amplitude of the Gaussian and square pulse. - Complex amp support will be deprecated. + Complex amp support is deprecated. sigma: A measure of how wide or narrow the Gaussian risefall is; see the class docstring for more details. width: The duration of the embedded square pulse. @@ -1085,9 +1085,9 @@ def gaussian_square_echo( The Gaussian Square Echo pulse is composed of three pulses. First, a Gaussian Square pulse :math:`f_{echo}(x)` with amplitude ``amp`` and phase ``angle`` playing for half duration, followed by a second Gaussian Square pulse :math:`-f_{echo}(x)` with opposite amplitude - and same phase playing for the rest of the duration. Third a Gaussian Square pulse + and same phase playing for the rest of the duration. Third a Gaussian Square pulse :math:`f_{active}(x)` with amplitude ``active_amp`` and phase ``active_angle`` - playing for the entire duration. The Gaussian Square Echo pulse :math:`g_e()` + playing for the entire duration. The Gaussian Square Echo pulse :math:`g_e()` can be written as: .. math:: @@ -1099,11 +1099,11 @@ def gaussian_square_echo( & \\frac{\\text{duration}}{2} < x\ \\end{cases}\\\\ - One case where this pulse can be used is when implementing a direct CNOT gate with - a cross-resonance superconducting qubit architecture. When applying this pulse to - the target qubit, the active portion can be used to cancel IX terms from the + One case where this pulse can be used is when implementing a direct CNOT gate with + a cross-resonance superconducting qubit architecture. When applying this pulse to + the target qubit, the active portion can be used to cancel IX terms from the cross-resonance drive while the echo portion can reduce the impact of a static ZZ coupling. - + Exactly one of the ``risefall_sigma_ratio`` and ``width`` parameters has to be specified. If ``risefall_sigma_ratio`` is not ``None`` and ``width`` is ``None``: @@ -1122,10 +1122,10 @@ def gaussian_square_echo( .. _citation1: https://iopscience.iop.org/article/10.1088/2058-9565/abe519 - .. |citation1| replace:: *Jurcevic, P., Javadi-Abhari, A., Bishop, L. S., - Lauer, I., Bogorin, D. F., Brink, M., Capelluto, L., G{\"u}nl{\"u}k, O., + .. |citation1| replace:: *Jurcevic, P., Javadi-Abhari, A., Bishop, L. S., + Lauer, I., Bogorin, D. F., Brink, M., Capelluto, L., G{\"u}nl{\"u}k, O., Itoko, T., Kanazawa, N. & others - Demonstration of quantum volume 64 on a superconducting quantum + Demonstration of quantum volume 64 on a superconducting quantum computing system. (Section V)* Args: duration: Pulse length in terms of the sampling period `dt`. @@ -1312,7 +1312,7 @@ class Drag(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[complex, float, ParameterExpression], + amp: Union[float, ParameterExpression], sigma: Union[float, ParameterExpression], beta: Union[float, ParameterExpression], angle: Optional[Union[float, ParameterExpression]] = None, @@ -1324,7 +1324,7 @@ def __new__( Args: duration: Pulse length in terms of the sampling period `dt`. amp: The magnitude of the amplitude of the DRAG envelope. - Complex amp support will be deprecated. + Complex amp support is deprecated. sigma: A measure of how wide or narrow the Gaussian peak is; described mathematically in the class docstring. beta: The correction amplitude. @@ -1383,7 +1383,7 @@ class Constant(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[complex, float, ParameterExpression], + amp: Union[float, ParameterExpression], angle: Optional[Union[float, ParameterExpression]] = None, name: Optional[str] = None, limit_amplitude: Optional[bool] = None, @@ -1393,7 +1393,7 @@ def __new__( Args: duration: Pulse length in terms of the sampling period `dt`. amp: The magnitude of the amplitude of the square envelope. - Complex amp support will be deprecated. + Complex amp support is deprecated. angle: The angle of the complex amplitude of the square envelope. Default value 0. name: Display name for this pulse envelope. limit_amplitude: If ``True``, then limit the amplitude of the diff --git a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml new file mode 100644 index 000000000000..6e34020e3cc6 --- /dev/null +++ b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml @@ -0,0 +1,17 @@ +--- +deprecations: + - | + Initializing a :class:`~qiskit.pulse.library.ScalableSymbolicPulse` with complex `amp` value is now deprecated. + Particularly, the following pulses + + * :class:`~qiskit.pulse.library.Gaussian` + * :class:`~qiskit.pulse.library.GaussianSquare` + * :class:`~qiskit.pulse.library.Drag` + * :class:`~qiskit.pulse.library.Constant` + + initialize a :class:`~qiskit.pulse.library.ScalableSymbolicPulse`, and therefore initializing them with complex + `amp` is now deprecated. + + Instead, one should use two floats for the `amp` and `angle` parameters, where `amp` represents the + magnitude of the complex amplitude, and `angle` represents the angle of the complex amplitude. i.e. the + complex amplitude is given by `amp * exp(1j * angle)`. diff --git a/test/python/pulse/test_parameter_manager.py b/test/python/pulse/test_parameter_manager.py index 4ffa519c0a8a..bb450e0fca0e 100644 --- a/test/python/pulse/test_parameter_manager.py +++ b/test/python/pulse/test_parameter_manager.py @@ -347,7 +347,8 @@ def test_complex_valued_parameter(self): with self.assertWarns(PendingDeprecationWarning): assigned = visitor.visit(test_obj) - ref_obj = pulse.Constant(duration=160, amp=1j * 0.1) + with self.assertWarns(DeprecationWarning): + ref_obj = pulse.Constant(duration=160, amp=1j * 0.1) self.assertEqual(assigned, ref_obj) @@ -363,7 +364,8 @@ def test_complex_value_to_parameter(self): with self.assertWarns(PendingDeprecationWarning): assigned = visitor.visit(test_obj) - ref_obj = pulse.Constant(duration=160, amp=1j * 0.1) + with self.assertWarns(DeprecationWarning): + ref_obj = pulse.Constant(duration=160, amp=1j * 0.1) self.assertEqual(assigned, ref_obj) diff --git a/test/python/pulse/test_pulse_lib.py b/test/python/pulse/test_pulse_lib.py index a289ad018955..3cfa810fd716 100644 --- a/test/python/pulse/test_pulse_lib.py +++ b/test/python/pulse/test_pulse_lib.py @@ -135,11 +135,11 @@ class TestParametricPulses(QiskitTestCase): def test_construction(self): """Test that parametric pulses can be constructed without error.""" - Gaussian(duration=25, sigma=4, amp=0.5j) + Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2) GaussianSquare(duration=150, amp=0.2, sigma=8, width=140) GaussianSquare(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=2.5) - Constant(duration=150, amp=0.1 + 0.4j) - Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4) + Constant(duration=150, amp=0.5, angle=np.pi * 0.23) + Drag(duration=25, amp=0.6, sigma=7.8, beta=4, angle=np.pi * 0.54) Sin(duration=25, amp=0.5, freq=0.1, phase=0.5, angle=0.5) Cos(duration=30, amp=0.5, freq=0.1, phase=-0.5) Sawtooth(duration=40, amp=0.5, freq=0.2, phase=3.14) @@ -151,17 +151,20 @@ def test_complex_amp_deprecation(self): and that pulses are equivalent.""" # Test deprecation warnings and errors: - with self.assertWarns(PendingDeprecationWarning): + with self.assertWarns(DeprecationWarning): Gaussian(duration=25, sigma=4, amp=0.5j) - with self.assertWarns(PendingDeprecationWarning): + with self.assertWarns(DeprecationWarning): GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100) - with self.assertRaises(PulseError): - Gaussian(duration=25, sigma=4, amp=0.5j, angle=1) - with self.assertRaises(PulseError): - GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100, angle=0.1) + with self.assertWarns(DeprecationWarning): + with self.assertRaises(PulseError): + Gaussian(duration=25, sigma=4, amp=0.5j, angle=1) + with self.assertWarns(DeprecationWarning): + with self.assertRaises(PulseError): + GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100, angle=0.1) # Test that new and old API pulses are the same: - gauss_pulse_complex_amp = Gaussian(duration=25, sigma=4, amp=0.5j) + with self.assertWarns(DeprecationWarning): + gauss_pulse_complex_amp = Gaussian(duration=25, sigma=4, amp=0.5j) gauss_pulse_amp_angle = Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2) np.testing.assert_almost_equal( gauss_pulse_amp_angle.get_waveform().samples, @@ -170,7 +173,7 @@ def test_complex_amp_deprecation(self): def test_gaussian_pulse(self): """Test that Gaussian sample pulse matches the pulse library.""" - gauss = Gaussian(duration=25, sigma=4, amp=0.5j) + gauss = Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2) sample_pulse = gauss.get_waveform() self.assertIsInstance(sample_pulse, Waveform) pulse_lib_gauss = gaussian(duration=25, sigma=4, amp=0.5j, zero_ends=True).samples @@ -178,14 +181,16 @@ def test_gaussian_pulse(self): def test_gaussian_square_pulse(self): """Test that GaussianSquare sample pulse matches the pulse library.""" - gauss_sq = GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100) + gauss_sq = GaussianSquare(duration=125, sigma=4, amp=0.5, width=100, angle=np.pi / 2) sample_pulse = gauss_sq.get_waveform() self.assertIsInstance(sample_pulse, Waveform) pulse_lib_gauss_sq = gaussian_square( duration=125, sigma=4, amp=0.5j, width=100, zero_ends=True ).samples np.testing.assert_almost_equal(sample_pulse.samples, pulse_lib_gauss_sq) - gauss_sq = GaussianSquare(duration=125, sigma=4, amp=0.5j, risefall_sigma_ratio=3.125) + gauss_sq = GaussianSquare( + duration=125, sigma=4, amp=0.5, risefall_sigma_ratio=3.125, angle=np.pi / 2 + ) sample_pulse = gauss_sq.get_waveform() self.assertIsInstance(sample_pulse, Waveform) pulse_lib_gauss_sq = gaussian_square( @@ -197,14 +202,17 @@ def test_gauss_square_extremes(self): """Test that the gaussian square pulse can build a gaussian.""" duration = 125 sigma = 4 - amp = 0.5j - gaus_square = GaussianSquare(duration=duration, sigma=sigma, amp=amp, width=0) - gaus = Gaussian(duration=duration, sigma=sigma, amp=amp) + amp = 0.5 + angle = np.pi / 2 + gaus_square = GaussianSquare(duration=duration, sigma=sigma, amp=amp, width=0, angle=angle) + gaus = Gaussian(duration=duration, sigma=sigma, amp=amp, angle=angle) np.testing.assert_almost_equal( gaus_square.get_waveform().samples, gaus.get_waveform().samples ) - gaus_square = GaussianSquare(duration=duration, sigma=sigma, amp=amp, width=121) - const = Constant(duration=duration, amp=amp) + gaus_square = GaussianSquare( + duration=duration, sigma=sigma, amp=amp, width=121, angle=angle + ) + const = Constant(duration=duration, amp=amp, angle=angle) np.testing.assert_almost_equal( gaus_square.get_waveform().samples[2:-2], const.get_waveform().samples[2:-2] ) @@ -213,7 +221,7 @@ def test_gauss_square_passes_validation_after_construction(self): """Test that parameter validation is consistent before and after construction. This previously used to raise an exception: see gh-7882.""" - pulse = GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100) + pulse = GaussianSquare(duration=125, sigma=4, amp=0.5, width=100, angle=np.pi / 2) pulse.validate_parameters() def test_gaussian_square_drag_pulse(self): @@ -350,7 +358,7 @@ def test_gaussian_square_echo_active_amp_validation(self): def test_drag_pulse(self): """Test that the Drag sample pulse matches the pulse library.""" - drag = Drag(duration=25, sigma=4, amp=0.5j, beta=1) + drag = Drag(duration=25, sigma=4, amp=0.5, beta=1, angle=np.pi / 2) sample_pulse = drag.get_waveform() self.assertIsInstance(sample_pulse, Waveform) pulse_lib_drag = pl_drag(duration=25, sigma=4, amp=0.5j, beta=1, zero_ends=True).samples @@ -360,25 +368,26 @@ def test_drag_validation(self): """Test drag parameter validation, specifically the beta validation.""" duration = 25 sigma = 4 - amp = 0.5j + amp = 0.5 + angle = np.pi / 2 beta = 1 - wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta) + wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta, angle=angle) samples = wf.get_waveform().samples self.assertTrue(max(np.abs(samples)) <= 1) with self.assertRaises(PulseError): wf = Drag(duration=duration, sigma=sigma, amp=1.2, beta=beta) beta = sigma**2 with self.assertRaises(PulseError): - wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta) + wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta, angle=angle) # If sigma is high enough, side peaks fall out of range and norm restriction is met sigma = 100 - wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta) + wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta, angle=angle) def test_drag_beta_validation(self): """Test drag beta parameter validation.""" - def check_drag(duration, sigma, amp, beta): - wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta) + def check_drag(duration, sigma, amp, beta, angle=0): + wf = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta, angle=angle) samples = wf.get_waveform().samples self.assertTrue(max(np.abs(samples)) <= 1) @@ -388,7 +397,7 @@ def check_drag(duration, sigma, amp, beta): check_drag(duration=50, sigma=16, amp=-1, beta=2) check_drag(duration=50, sigma=16, amp=1, beta=-2) check_drag(duration=50, sigma=16, amp=1, beta=6) - check_drag(duration=50, sigma=16, amp=-0.5j, beta=25) + check_drag(duration=50, sigma=16, amp=0.5, beta=25, angle=-np.pi / 2) with self.assertRaises(PulseError): check_drag(duration=50, sigma=16, amp=1, beta=20) with self.assertRaises(PulseError): @@ -469,8 +478,10 @@ def test_triangle_pulse(self): def test_constant_samples(self): """Test the constant pulse and its sampled construction.""" - const = Constant(duration=150, amp=0.1 + 0.4j) - self.assertEqual(const.get_waveform().samples[0], 0.1 + 0.4j) + amp = 0.6 + angle = np.pi * 0.7 + const = Constant(duration=150, amp=amp, angle=angle) + self.assertEqual(const.get_waveform().samples[0], amp * np.exp(1j * angle)) self.assertEqual(len(const.get_waveform().samples), 150) def test_parameters(self): @@ -485,11 +496,6 @@ def test_repr(self): """Test the repr methods for parametric pulses.""" gaus = Gaussian(duration=25, amp=0.7, sigma=4, angle=0.3) self.assertEqual(repr(gaus), "Gaussian(duration=25, sigma=4, amp=0.7, angle=0.3)") - gaus = Gaussian( - duration=25, amp=0.1 + 0.7j, sigma=4 - ) # Should be removed once the deprecation of complex - # amp is completed. - self.assertEqual(repr(gaus), "Gaussian(duration=25, sigma=4, amp=(0.1+0.7j), angle=0)") gaus_square = GaussianSquare(duration=20, sigma=30, amp=1.0, width=3) self.assertEqual( repr(gaus_square), "GaussianSquare(duration=20, sigma=30, width=3, amp=1.0, angle=0)" @@ -535,7 +541,7 @@ def test_repr(self): def test_param_validation(self): """Test that parametric pulse parameters are validated when initialized.""" with self.assertRaises(PulseError): - Gaussian(duration=25, sigma=0, amp=0.5j) + Gaussian(duration=25, sigma=0, amp=0.5, angle=np.pi / 2) with self.assertRaises(PulseError): GaussianSquare(duration=150, amp=0.2, sigma=8) with self.assertRaises(PulseError): @@ -564,43 +570,45 @@ def test_param_validation(self): gaussian_square_echo(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10) with self.assertRaises(PulseError): - Constant(duration=150, amp=0.9 + 0.8j) + Constant(duration=150, amp=1.5, angle=np.pi * 0.8) with self.assertRaises(PulseError): - Drag(duration=25, amp=0.2 + 0.3j, sigma=-7.8, beta=4) + Drag(duration=25, amp=0.5, sigma=-7.8, beta=4, angle=np.pi / 3) def test_gaussian_limit_amplitude(self): """Test that the check for amplitude less than or equal to 1 can be disabled.""" with self.assertRaises(PulseError): - Gaussian(duration=100, sigma=1.0, amp=1.1 + 0.8j) + Gaussian(duration=100, sigma=1.0, amp=1.7, angle=np.pi * 1.1) with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Gaussian(duration=100, sigma=1.0, amp=1.1 + 0.8j) + waveform = Gaussian(duration=100, sigma=1.0, amp=1.7, angle=np.pi * 1.1) self.assertGreater(np.abs(waveform.amp), 1.0) def test_gaussian_limit_amplitude_per_instance(self): """Test that the check for amplitude per instance.""" with self.assertRaises(PulseError): - Gaussian(duration=100, sigma=1.0, amp=1.1 + 0.8j) + Gaussian(duration=100, sigma=1.0, amp=1.6, angle=np.pi / 2.5) - waveform = Gaussian(duration=100, sigma=1.0, amp=1.1 + 0.8j, limit_amplitude=False) + waveform = Gaussian( + duration=100, sigma=1.0, amp=1.6, angle=np.pi / 2.5, limit_amplitude=False + ) self.assertGreater(np.abs(waveform.amp), 1.0) def test_gaussian_square_limit_amplitude(self): """Test that the check for amplitude less than or equal to 1 can be disabled.""" with self.assertRaises(PulseError): - GaussianSquare(duration=100, sigma=1.0, amp=1.1 + 0.8j, width=10) + GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 5) with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = GaussianSquare(duration=100, sigma=1.0, amp=1.1 + 0.8j, width=10) + waveform = GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 5) self.assertGreater(np.abs(waveform.amp), 1.0) def test_gaussian_square_limit_amplitude_per_instance(self): """Test that the check for amplitude per instance.""" with self.assertRaises(PulseError): - GaussianSquare(duration=100, sigma=1.0, amp=1.1 + 0.8j, width=10) + GaussianSquare(duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 3) waveform = GaussianSquare( - duration=100, sigma=1.0, amp=1.1 + 0.8j, width=10, limit_amplitude=False + duration=100, sigma=1.0, amp=1.5, width=10, angle=np.pi / 3, limit_amplitude=False ) self.assertGreater(np.abs(waveform.amp), 1.0) @@ -645,35 +653,37 @@ def test_gaussian_square_echo_limit_amplitude_per_instance(self): def test_drag_limit_amplitude(self): """Test that the check for amplitude less than or equal to 1 can be disabled.""" with self.assertRaises(PulseError): - Drag(duration=100, sigma=1.0, beta=1.0, amp=1.1 + 0.8j) + Drag(duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3) with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Drag(duration=100, sigma=1.0, beta=1.0, amp=1.1 + 0.8j) + waveform = Drag(duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3) self.assertGreater(np.abs(waveform.amp), 1.0) def test_drag_limit_amplitude_per_instance(self): """Test that the check for amplitude per instance.""" with self.assertRaises(PulseError): - Drag(duration=100, sigma=1.0, beta=1.0, amp=1.1 + 0.8j) + Drag(duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3) - waveform = Drag(duration=100, sigma=1.0, beta=1.0, amp=1.1 + 0.8j, limit_amplitude=False) + waveform = Drag( + duration=100, sigma=1.0, beta=1.0, amp=1.8, angle=np.pi * 0.3, limit_amplitude=False + ) self.assertGreater(np.abs(waveform.amp), 1.0) def test_constant_limit_amplitude(self): """Test that the check for amplitude less than or equal to 1 can be disabled.""" with self.assertRaises(PulseError): - Constant(duration=100, amp=1.1 + 0.8j) + Constant(duration=100, amp=1.3, angle=0.1) with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False): - waveform = Constant(duration=100, amp=1.1 + 0.8j) + waveform = Constant(duration=100, amp=1.3, angle=0.1) self.assertGreater(np.abs(waveform.amp), 1.0) def test_constant_limit_amplitude_per_instance(self): """Test that the check for amplitude per instance.""" with self.assertRaises(PulseError): - Constant(duration=100, amp=1.1 + 0.8j) + Constant(duration=100, amp=1.6, angle=0.5) - waveform = Constant(duration=100, amp=1.1 + 0.8j, limit_amplitude=False) + waveform = Constant(duration=100, amp=1.6, angle=0.5, limit_amplitude=False) self.assertGreater(np.abs(waveform.amp), 1.0) def test_sin_limit_amplitude(self): diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index e8f6a2f5a84f..654b4ffe39f9 100644 --- a/test/python/pulse/test_schedule.py +++ b/test/python/pulse/test_schedule.py @@ -337,8 +337,8 @@ def test_schedule_with_acquire_on_single_qubit(self): def test_parametric_commands_in_sched(self): """Test that schedules can be built with parametric commands.""" sched = Schedule(name="test_parametric") - sched += Play(Gaussian(duration=25, sigma=4, amp=0.5j), DriveChannel(0)) - sched += Play(Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4), DriveChannel(1)) + sched += Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2), DriveChannel(0)) + sched += Play(Drag(duration=25, amp=0.4, angle=0.5, sigma=7.8, beta=4), DriveChannel(1)) sched += Play(Constant(duration=25, amp=1), DriveChannel(2)) sched_duration = sched.duration sched += ( diff --git a/test/python/pulse/test_transforms.py b/test/python/pulse/test_transforms.py index 50d6cbd94498..2b6d2906fcb0 100644 --- a/test/python/pulse/test_transforms.py +++ b/test/python/pulse/test_transforms.py @@ -437,14 +437,14 @@ def test_parametric_pulses_with_duplicates(self): """Test with parametric pulses.""" schedule = Schedule() drive_channel = DriveChannel(0) - schedule += Play(Gaussian(duration=25, sigma=4, amp=0.5j), drive_channel) - schedule += Play(Gaussian(duration=25, sigma=4, amp=0.5j), drive_channel) + schedule += Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2), drive_channel) + schedule += Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2), drive_channel) schedule += Play(GaussianSquare(duration=150, amp=0.2, sigma=8, width=140), drive_channel) schedule += Play(GaussianSquare(duration=150, amp=0.2, sigma=8, width=140), drive_channel) - schedule += Play(Constant(duration=150, amp=0.1 + 0.4j), drive_channel) - schedule += Play(Constant(duration=150, amp=0.1 + 0.4j), drive_channel) - schedule += Play(Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4), drive_channel) - schedule += Play(Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4), drive_channel) + schedule += Play(Constant(duration=150, amp=0.5, angle=0.7), drive_channel) + schedule += Play(Constant(duration=150, amp=0.5, angle=0.7), drive_channel) + schedule += Play(Drag(duration=25, amp=0.4, angle=-0.3, sigma=7.8, beta=4), drive_channel) + schedule += Play(Drag(duration=25, amp=0.4, angle=-0.3, sigma=7.8, beta=4), drive_channel) compressed_schedule = transforms.compress_pulses([schedule]) original_pulse_ids = get_pulse_ids([schedule]) @@ -456,14 +456,14 @@ def test_parametric_pulses_with_no_duplicates(self): """Test parametric pulses with no duplicates.""" schedule = Schedule() drive_channel = DriveChannel(0) - schedule += Play(Gaussian(duration=25, sigma=4, amp=0.5j), drive_channel) - schedule += Play(Gaussian(duration=25, sigma=4, amp=0.49j), drive_channel) + schedule += Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=np.pi / 2), drive_channel) + schedule += Play(Gaussian(duration=25, sigma=4, amp=0.49, angle=np.pi / 2), drive_channel) schedule += Play(GaussianSquare(duration=150, amp=0.2, sigma=8, width=140), drive_channel) schedule += Play(GaussianSquare(duration=150, amp=0.19, sigma=8, width=140), drive_channel) - schedule += Play(Constant(duration=150, amp=0.1 + 0.4j), drive_channel) - schedule += Play(Constant(duration=150, amp=0.1 + 0.41j), drive_channel) - schedule += Play(Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4), drive_channel) - schedule += Play(Drag(duration=25, amp=0.2 + 0.31j, sigma=7.8, beta=4), drive_channel) + schedule += Play(Constant(duration=150, amp=0.5, angle=0.3), drive_channel) + schedule += Play(Constant(duration=150, amp=0.51, angle=0.3), drive_channel) + schedule += Play(Drag(duration=25, amp=0.5, angle=0.5, sigma=7.8, beta=4), drive_channel) + schedule += Play(Drag(duration=25, amp=0.5, angle=0.51, sigma=7.8, beta=4), drive_channel) compressed_schedule = transforms.compress_pulses([schedule]) original_pulse_ids = get_pulse_ids([schedule]) diff --git a/test/python/qobj/test_pulse_converter.py b/test/python/qobj/test_pulse_converter.py index df0b683b12ec..2bdfeea8708d 100644 --- a/test/python/qobj/test_pulse_converter.py +++ b/test/python/qobj/test_pulse_converter.py @@ -63,22 +63,27 @@ def test_drive_instruction(self): def test_gaussian_pulse_instruction(self): """Test that parametric pulses are correctly converted to PulseQobjInstructions.""" + amp = 0.3 + angle = -0.7 converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Play(Gaussian(duration=25, sigma=15, amp=-0.5 + 0.2j), DriveChannel(0)) + instruction = Play(Gaussian(duration=25, sigma=15, amp=amp, angle=angle), DriveChannel(0)) valid_qobj = PulseQobjInstruction( name="parametric_pulse", pulse_shape="gaussian", ch="d0", t0=0, - parameters={"duration": 25, "sigma": 15, "amp": -0.5 + 0.2j}, + parameters={"duration": 25, "sigma": 15, "amp": amp * np.exp(1j * angle)}, ) self.assertEqual(converter(0, instruction), valid_qobj) def test_gaussian_square_pulse_instruction(self): """Test that parametric pulses are correctly converted to PulseQobjInstructions.""" converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) + amp = 0.7 + angle = -0.6 instruction = Play( - GaussianSquare(duration=1500, sigma=15, amp=-0.5 + 0.2j, width=1300), MeasureChannel(1) + GaussianSquare(duration=1500, sigma=15, amp=amp, width=1300, angle=angle), + MeasureChannel(1), ) valid_qobj = PulseQobjInstruction( @@ -86,7 +91,12 @@ def test_gaussian_square_pulse_instruction(self): pulse_shape="gaussian_square", ch="m1", t0=10, - parameters={"duration": 1500, "sigma": 15, "amp": -0.5 + 0.2j, "width": 1300}, + parameters={ + "duration": 1500, + "sigma": 15, + "amp": amp * np.exp(1j * angle), + "width": 1300, + }, ) self.assertEqual(converter(10, instruction), valid_qobj) @@ -106,15 +116,19 @@ def test_constant_pulse_instruction(self): def test_drag_pulse_instruction(self): """Test that parametric pulses are correctly converted to PulseQobjInstructions.""" + amp = 0.7 + angle = -0.6 converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) - instruction = Play(Drag(duration=25, sigma=15, amp=-0.5 + 0.2j, beta=0.5), DriveChannel(0)) + instruction = Play( + Drag(duration=25, sigma=15, amp=amp, angle=angle, beta=0.5), DriveChannel(0) + ) valid_qobj = PulseQobjInstruction( name="parametric_pulse", pulse_shape="drag", ch="d0", t0=30, - parameters={"duration": 25, "sigma": 15, "amp": -0.5 + 0.2j, "beta": 0.5}, + parameters={"duration": 25, "sigma": 15, "amp": amp * np.exp(1j * angle), "beta": 0.5}, ) self.assertEqual(converter(30, instruction), valid_qobj) diff --git a/test/python/scheduler/test_basic_scheduler.py b/test/python/scheduler/test_basic_scheduler.py index 8b7944eb7883..b9245da8d0c2 100644 --- a/test/python/scheduler/test_basic_scheduler.py +++ b/test/python/scheduler/test_basic_scheduler.py @@ -12,6 +12,7 @@ """Test cases for the pulse scheduler passes.""" +from numpy import pi from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, schedule from qiskit.circuit import Gate, Parameter from qiskit.circuit.library import U1Gate, U2Gate, U3Gate @@ -352,7 +353,9 @@ def test_parametric_input(self): qr = QuantumRegister(1) qc = QuantumCircuit(qr) qc.append(Gate("gauss", 1, []), qargs=[qr[0]]) - custom_gauss = Schedule(Play(Gaussian(duration=25, sigma=4, amp=0.5j), DriveChannel(0))) + custom_gauss = Schedule( + Play(Gaussian(duration=25, sigma=4, amp=0.5, angle=pi / 2), DriveChannel(0)) + ) self.inst_map.add("gauss", [0], custom_gauss) sched = schedule(qc, self.backend, inst_map=self.inst_map) self.assertEqual(sched.instructions[0], custom_gauss.instructions[0]) From e0cb3b52f4c89b77b906a02c4f776ddf105aa542 Mon Sep 17 00:00:00 2001 From: TsafrirA Date: Thu, 29 Jun 2023 11:28:09 +0300 Subject: [PATCH 2/6] assembler test fix --- test/python/compiler/test_assembler.py | 39 +++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py index 04c8b3616a21..debd47e3a48b 100644 --- a/test/python/compiler/test_assembler.py +++ b/test/python/compiler/test_assembler.py @@ -1278,12 +1278,21 @@ def test_assemble_schedule_enum(self): def test_assemble_parametric(self): """Test that parametric pulses can be assembled properly into a PulseQobj.""" + amp = [0.5, 0.6, 1, 0.2] + angle = [np.pi / 2, 0.6, 0, 0] sched = pulse.Schedule(name="test_parametric") - sched += Play(pulse.Gaussian(duration=25, sigma=4, amp=0.5j), DriveChannel(0)) - sched += Play(pulse.Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4), DriveChannel(1)) - sched += Play(pulse.Constant(duration=25, amp=1), DriveChannel(2)) + sched += Play( + pulse.Gaussian(duration=25, sigma=4, amp=amp[0], angle=angle[0]), DriveChannel(0) + ) + sched += Play( + pulse.Drag(duration=25, amp=amp[1], angle=angle[1], sigma=7.8, beta=4), DriveChannel(1) + ) + sched += Play(pulse.Constant(duration=25, amp=amp[2], angle=angle[2]), DriveChannel(2)) sched += ( - Play(pulse.GaussianSquare(duration=150, amp=0.2, sigma=8, width=140), MeasureChannel(0)) + Play( + pulse.GaussianSquare(duration=150, amp=amp[3], angle=angle[3], sigma=8, width=140), + MeasureChannel(0), + ) << sched.duration ) backend = FakeOpenPulse3Q() @@ -1302,16 +1311,24 @@ def test_assemble_parametric(self): self.assertEqual(qobj_insts[1].pulse_shape, "drag") self.assertEqual(qobj_insts[2].pulse_shape, "constant") self.assertEqual(qobj_insts[3].pulse_shape, "gaussian_square") - self.assertDictEqual(qobj_insts[0].parameters, {"duration": 25, "sigma": 4, "amp": 0.5j}) self.assertDictEqual( - qobj_insts[1].parameters, {"duration": 25, "sigma": 7.8, "amp": 0.2 + 0.3j, "beta": 4} + qobj_insts[0].parameters, + {"duration": 25, "sigma": 4, "amp": amp[0] * np.exp(1j * angle[0])}, ) - self.assertDictEqual(qobj_insts[2].parameters, {"duration": 25, "amp": 1}) self.assertDictEqual( - qobj_insts[3].parameters, {"duration": 150, "sigma": 8, "amp": 0.2, "width": 140} + qobj_insts[1].parameters, + {"duration": 25, "sigma": 7.8, "amp": amp[1] * np.exp(1j * angle[1]), "beta": 4}, + ) + self.assertDictEqual( + qobj_insts[2].parameters, {"duration": 25, "amp": amp[2] * np.exp(1j * angle[2])} + ) + self.assertDictEqual( + qobj_insts[3].parameters, + {"duration": 150, "sigma": 8, "amp": amp[3] * np.exp(1j * angle[3]), "width": 140}, ) self.assertEqual( - qobj.to_dict()["experiments"][0]["instructions"][0]["parameters"]["amp"], 0.5j + qobj.to_dict()["experiments"][0]["instructions"][0]["parameters"]["amp"], + amp[0] * np.exp(1j * angle[0]), ) def test_assemble_parametric_unsupported(self): @@ -1319,7 +1336,9 @@ def test_assemble_parametric_unsupported(self): by the backend during assemble time. """ sched = pulse.Schedule(name="test_parametric_to_sample_pulse") - sched += Play(pulse.Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4), DriveChannel(1)) + sched += Play( + pulse.Drag(duration=25, amp=0.5, angle=-0.3, sigma=7.8, beta=4), DriveChannel(1) + ) sched += Play(pulse.Constant(duration=25, amp=1), DriveChannel(2)) backend = FakeOpenPulse3Q() From 2b3b1abe709618a62b2b139c41bc4d469e1d3ae5 Mon Sep 17 00:00:00 2001 From: TsafrirA Date: Sun, 2 Jul 2023 13:26:04 +0300 Subject: [PATCH 3/6] ParameterValueType + release notes edit --- qiskit/pulse/library/symbolic_pulses.py | 30 +++++++++---------- ...eprecate-complex-amp-41381bd9722bc878.yaml | 5 ++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index d0801d025912..50ab19a66966 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -24,7 +24,7 @@ import numpy as np -from qiskit.circuit.parameterexpression import ParameterExpression +from qiskit.circuit.parameterexpression import ParameterExpression, ParameterValueType from qiskit.pulse.exceptions import PulseError from qiskit.pulse.library.pulse import Pulse from qiskit.pulse.library.waveform import Waveform @@ -739,9 +739,9 @@ class Gaussian(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[float, ParameterExpression], - sigma: Union[float, ParameterExpression], - angle: Optional[Union[float, ParameterExpression]] = None, + amp: ParameterValueType, + sigma: ParameterValueType, + angle: Optional[ParameterValueType] = None, name: Optional[str] = None, limit_amplitude: Optional[bool] = None, ) -> ScalableSymbolicPulse: @@ -835,11 +835,11 @@ class GaussianSquare(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[float, ParameterExpression], - sigma: Union[float, ParameterExpression], - width: Optional[Union[float, ParameterExpression]] = None, - angle: Optional[Union[float, ParameterExpression]] = None, - risefall_sigma_ratio: Optional[Union[float, ParameterExpression]] = None, + amp: ParameterValueType, + sigma: ParameterValueType, + width: Optional[ParameterValueType] = None, + angle: Optional[ParameterValueType] = None, + risefall_sigma_ratio: Optional[ParameterValueType] = None, name: Optional[str] = None, limit_amplitude: Optional[bool] = None, ) -> ScalableSymbolicPulse: @@ -1312,10 +1312,10 @@ class Drag(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[float, ParameterExpression], - sigma: Union[float, ParameterExpression], - beta: Union[float, ParameterExpression], - angle: Optional[Union[float, ParameterExpression]] = None, + amp: ParameterValueType, + sigma: ParameterValueType, + beta: ParameterValueType, + angle: Optional[ParameterValueType] = None, name: Optional[str] = None, limit_amplitude: Optional[bool] = None, ) -> ScalableSymbolicPulse: @@ -1383,8 +1383,8 @@ class Constant(metaclass=_PulseType): def __new__( cls, duration: Union[int, ParameterExpression], - amp: Union[float, ParameterExpression], - angle: Optional[Union[float, ParameterExpression]] = None, + amp: ParameterValueType, + angle: Optional[ParameterValueType] = None, name: Optional[str] = None, limit_amplitude: Optional[bool] = None, ) -> ScalableSymbolicPulse: diff --git a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml index 6e34020e3cc6..6515126fa40c 100644 --- a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml +++ b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml @@ -2,15 +2,14 @@ deprecations: - | Initializing a :class:`~qiskit.pulse.library.ScalableSymbolicPulse` with complex `amp` value is now deprecated. - Particularly, the following pulses + This change also affects the following library `SymbolicPulse`: * :class:`~qiskit.pulse.library.Gaussian` * :class:`~qiskit.pulse.library.GaussianSquare` * :class:`~qiskit.pulse.library.Drag` * :class:`~qiskit.pulse.library.Constant` - initialize a :class:`~qiskit.pulse.library.ScalableSymbolicPulse`, and therefore initializing them with complex - `amp` is now deprecated. + Initializing them with complex `amp` is now deprecated as well. Instead, one should use two floats for the `amp` and `angle` parameters, where `amp` represents the magnitude of the complex amplitude, and `angle` represents the angle of the complex amplitude. i.e. the From be05a9ccc7d767d2cb00bad2bfc58f9766ba5a4d Mon Sep 17 00:00:00 2001 From: TsafrirA Date: Sun, 2 Jul 2023 13:26:37 +0300 Subject: [PATCH 4/6] ParameterValueType + release notes edit --- releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml index 6515126fa40c..b284b4b77e65 100644 --- a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml +++ b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml @@ -2,7 +2,7 @@ deprecations: - | Initializing a :class:`~qiskit.pulse.library.ScalableSymbolicPulse` with complex `amp` value is now deprecated. - This change also affects the following library `SymbolicPulse`: + This change also affects the following library pulses: * :class:`~qiskit.pulse.library.Gaussian` * :class:`~qiskit.pulse.library.GaussianSquare` From 97ee67093d093bf748b24407fe1cbf6ec88e58be Mon Sep 17 00:00:00 2001 From: TsafrirA Date: Sun, 2 Jul 2023 13:27:39 +0300 Subject: [PATCH 5/6] Release notes edit --- releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml index b284b4b77e65..23eadb7ff391 100644 --- a/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml +++ b/releasenotes/notes/deprecate-complex-amp-41381bd9722bc878.yaml @@ -13,4 +13,4 @@ deprecations: Instead, one should use two floats for the `amp` and `angle` parameters, where `amp` represents the magnitude of the complex amplitude, and `angle` represents the angle of the complex amplitude. i.e. the - complex amplitude is given by `amp * exp(1j * angle)`. + complex amplitude is given by `amp` * exp(1j * `angle`). From 6cf87c6c1e2838646c3986139da7058901353ce7 Mon Sep 17 00:00:00 2001 From: TsafrirA Date: Wed, 5 Jul 2023 11:12:19 +0300 Subject: [PATCH 6/6] Type hints fix --- qiskit/pulse/library/symbolic_pulses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/pulse/library/symbolic_pulses.py b/qiskit/pulse/library/symbolic_pulses.py index 5bb72202c485..8c90777eae01 100644 --- a/qiskit/pulse/library/symbolic_pulses.py +++ b/qiskit/pulse/library/symbolic_pulses.py @@ -603,8 +603,8 @@ def __init__( self, pulse_type: str, duration: Union[ParameterExpression, int], - amp: Union[ParameterExpression, float], - angle: Union[ParameterExpression, float], + amp: ParameterValueType, + angle: ParameterValueType, parameters: Optional[Dict[str, Union[ParameterExpression, complex]]] = None, name: Optional[str] = None, limit_amplitude: Optional[bool] = None,