From b5e2fad08c39556799e2ed3407a8f86386c0f031 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 9 Nov 2022 14:59:16 +0900 Subject: [PATCH 01/24] add sampling pulse for jax jit --- qiskit_dynamics/pulse/__init__.py | 1 - qiskit_dynamics/pulse/pulse_to_signals.py | 64 +++++++++++++++++- test/dynamics/pulse/test_pulse_to_signals.py | 71 +++++++++++++++++--- 3 files changed, 126 insertions(+), 10 deletions(-) diff --git a/qiskit_dynamics/pulse/__init__.py b/qiskit_dynamics/pulse/__init__.py index c59275e9d..99733ab58 100644 --- a/qiskit_dynamics/pulse/__init__.py +++ b/qiskit_dynamics/pulse/__init__.py @@ -81,5 +81,4 @@ InstructionToSignals """ - from .pulse_to_signals import InstructionToSignals diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index ce99d0fde..f94d9684f 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -15,7 +15,10 @@ """ from typing import Dict, List, Optional +import functools + import numpy as np +import sympy as sym from qiskit.pulse import ( Schedule, @@ -30,8 +33,11 @@ ControlChannel, AcquireChannel, ) +from qiskit.pulse.exceptions import PulseError +from qiskit.pulse.library import SymbolicPulse from qiskit import QiskitError +from qiskit_dynamics.array import Array from qiskit_dynamics.signals import DiscreteSignal @@ -167,7 +173,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: if isinstance(inst.pulse, Waveform): inst_samples = inst.pulse.samples else: - inst_samples = inst.pulse.get_waveform().samples + inst_samples = get_samples(inst.pulse) # build sample array to append to signal times = self._dt * (start_sample + np.arange(len(inst_samples))) @@ -302,3 +308,59 @@ def _get_channel(self, channel_name: str): raise QiskitError( f"Invalid channel name {channel_name} given to {self.__class__.__name__}." ) from error + + +def get_samples(pulse: SymbolicPulse): + """Return samples filled according to the formula that the pulse + represents and the parameter values it contains. + + Args: + pulse: SymbolicPulse class. + Returns: + Samples of the pulse. + Raises: + PulseError: When parameters are not assigned. + PulseError: When expression for pulse envelope is not assigned. + PulseError: When a free symbol value is not defined in the pulse instance parameters. + """ + envelope = pulse.envelope + pulse_params = pulse.parameters + if pulse.is_parameterized(): + raise PulseError("Unassigned parameter exists. All parameters must be assigned.") + + if envelope is None: + raise PulseError("Pulse envelope expression is not assigned.") + + args = [] + for symbol in sorted(envelope.free_symbols, key=lambda s: s.name): + if symbol.name == "t": + times = Array(np.arange(0, pulse_params["duration"]) + 1 / 2) + args.insert(0, times.data) + continue + try: + args.append(pulse_params[symbol.name]) + except KeyError as ex: + raise PulseError( + f"Pulse parameter '{symbol.name}' is not defined for this instance. " + "Please check your waveform expression is correct." + ) from ex + return _lru_cache_expr(envelope, Array.default_backend())(*args) + + +@functools.lru_cache(maxsize=None) +def _lru_cache_expr(expr: sym.Expr, backend): + """A helper function to get lambdified expression. + + Args: + expr: Symbolic expression to evaluate. + backend: Array backend. + Returns: + lambdified expression. + """ + params = [] + for param in sorted(expr.free_symbols, key=lambda s: s.name): + if param.name == "t": + params.insert(0, param) + continue + params.append(param) + return sym.lambdify(params, expr, modules=backend) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 41c902a4a..1ab1f8ad0 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -36,10 +36,22 @@ from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit import QiskitError +from qiskit_dynamics.array import Array from qiskit_dynamics.pulse import InstructionToSignals +from qiskit_dynamics.pulse.pulse_to_signals import ( + get_samples, + _lru_cache_expr, +) from qiskit_dynamics.signals import DiscreteSignal -from ..common import QiskitDynamicsTestCase +from ..common import QiskitDynamicsTestCase, TestJaxBase + +try: + import jax + import jax.numpy as jnp +# pylint: disable=broad-except +except Exception: + pass class TestPulseToSignals(QiskitDynamicsTestCase): @@ -256,7 +268,6 @@ def test_different_start_times(self): dt=0.1, channels=["d0", "d1"], carriers={"d0": 5.0, "d1": 3.1} ) signals = converter.get_signals(schedule) - # construct samples constant_samples = np.ones(5, dtype=float) * 0.9 phase = np.exp(1j * np.pi / 2.98) @@ -264,8 +275,8 @@ def test_different_start_times(self): samples0 = np.append(np.append(constant_samples, gauss_samples * phase), np.zeros(5)) samples1 = np.append(np.zeros(10), gauss_samples) - self.assertAllClose(signals[0].samples, samples0, atol=1e-14, rtol=1e-14) - self.assertAllClose(signals[1].samples, samples1, atol=1e-14, rtol=1e-14) + self.assertAllClose(signals[0].samples, samples0, atol=1e-7, rtol=1e-7) + self.assertAllClose(signals[1].samples, samples1, atol=1e-7, rtol=1e-7) self.assertTrue(signals[0].carrier_freq == 5.0) self.assertTrue(signals[1].carrier_freq == 3.1) @@ -303,15 +314,59 @@ def test_multiple_channels_with_gaps(self): samples2 = np.append(np.zeros(15), np.append(gauss_samples, np.zeros(10))) samples3 = np.append(np.zeros(20), np.append(gauss_samples, np.zeros(5))) - self.assertAllClose(signals[0].samples, samples0, atol=1e-14, rtol=1e-14) - self.assertAllClose(signals[1].samples, samples1, atol=1e-14, rtol=1e-14) - self.assertAllClose(signals[2].samples, samples2, atol=1e-14, rtol=1e-14) - self.assertAllClose(signals[3].samples, samples3, atol=1e-14, rtol=1e-14) + self.assertAllClose(signals[0].samples, samples0, atol=1e-7, rtol=1e-7) + self.assertAllClose(signals[1].samples, samples1, atol=1e-7, rtol=1e-7) + self.assertAllClose(signals[2].samples, samples2, atol=1e-7, rtol=1e-7) + self.assertAllClose(signals[3].samples, samples3, atol=1e-7, rtol=1e-7) self.assertTrue(signals[0].carrier_freq == 5.0) self.assertTrue(signals[1].carrier_freq == 3.1) self.assertTrue(signals[2].carrier_freq == 0.0) self.assertTrue(signals[3].carrier_freq == 4.0) + def test_get_samples(self): + """Test get samples of Pulse not get_waveform but get_samples function.""" + gauss_get_waveform_samples = ( + pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples + ) + gauss_get_samples = get_samples(Gaussian(duration=5, amp=0.983, sigma=2.0)) + self.assertTrue(isinstance(gauss_get_samples, np.ndarray)) + self.assertAllClose(gauss_get_samples, gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) + + def test_lru_cache_expr(self): + """Test lru_cache of lru_cache_expr function.""" + gauss_envelop = Gaussian(duration=5, amp=0.983, sigma=2.0).envelope + self.assertTrue( + _lru_cache_expr(gauss_envelop, Array.default_backend()) + is _lru_cache_expr(gauss_envelop, Array.default_backend()) + ) + + +class TestJaxGetSamples(QiskitDynamicsTestCase, TestJaxBase): + """Tests get_samples function by using Jax.""" + + def setUp(self): + """Set up gaussian waveform samples for comparison.""" + self.gauss_get_waveform_samples = ( + pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples + ) + + def test_get_samples_with_jax(self): + """Test get samples of Pulse not get_waveform but get_samples function in Jax case.""" + gauss_get_samples = get_samples(Gaussian(duration=5, amp=0.983, sigma=2.0)) + self.assertTrue(isinstance(gauss_get_samples, jnp.ndarray)) + self.assertAllClose( + gauss_get_samples, self.gauss_get_waveform_samples, atol=1e-7, rtol=1e-7 + ) + + def test_jit_get_samples(self): + """Test compiling to get samples of Pulse.""" + + def jit_func(amp, sigma): + return get_samples(Gaussian(duration=5, amp=amp, sigma=sigma)) + + jit_samples = jax.jit(jit_func, static_argnums=(0, 1))(0.983, 2) + self.assertAllClose(jit_samples, self.gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) + @ddt class TestPulseToSignalsFiltering(QiskitDynamicsTestCase): From 7e2c039063d3e21001259e0cf1dd0e551ce0220d Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 11 Nov 2022 14:42:21 +0900 Subject: [PATCH 02/24] add a release note --- ...struction_to_signals-e1de83e3e65a30de.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 releasenotes/notes/get_samples_for_instruction_to_signals-e1de83e3e65a30de.yaml diff --git a/releasenotes/notes/get_samples_for_instruction_to_signals-e1de83e3e65a30de.yaml b/releasenotes/notes/get_samples_for_instruction_to_signals-e1de83e3e65a30de.yaml new file mode 100644 index 000000000..398b12c76 --- /dev/null +++ b/releasenotes/notes/get_samples_for_instruction_to_signals-e1de83e3e65a30de.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + The logic of :class:`.InstructionToSignals` has been updated. + This change allows you to jit compile a function including + the converter with input pulse schedule that contains :class:`.SymbolicPulse`. + + .. code-block:: python + + from functools import partial + import jax + from qiskit import pulse + from qiskit_dynamics.pulse import InstructionToSignals + + @partial(jax.jit, static_argnums=(0, 1)) + def run_simulation(amp, sigma): + converter = InstructionToSignals(dt=1, carriers=None) + with pulse.build() as schedule: + pulse.play(pulse.Gaussian(160, amp, sigma), pulse.DriveChannel(0)) + signals = converter.get_signals(schedule) + + # continue with simulations From 9c87d0846a2503489fb48eb3b890e2a97329b1ca Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 11 Nov 2022 16:13:21 +0900 Subject: [PATCH 03/24] comment change of tolerance --- test/dynamics/pulse/test_pulse_to_signals.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 1ab1f8ad0..5972dbd23 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -275,6 +275,8 @@ def test_different_start_times(self): samples0 = np.append(np.append(constant_samples, gauss_samples * phase), np.zeros(5)) samples1 = np.append(np.zeros(10), gauss_samples) + # set tolerance from 1e-14 to 1e-7 to match the accuracy of clipping samples in class WaveForm. + # see https://github.com/Qiskit/qiskit-terra/blob/ee0b0368e72913cddf1c80ed95bc55e174c65046/qiskit/pulse/library/waveform.py#L56 self.assertAllClose(signals[0].samples, samples0, atol=1e-7, rtol=1e-7) self.assertAllClose(signals[1].samples, samples1, atol=1e-7, rtol=1e-7) self.assertTrue(signals[0].carrier_freq == 5.0) From 8adbc79f3d9cc754af4d35738d81fe50a8237197 Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 25 Nov 2022 17:55:08 +0900 Subject: [PATCH 04/24] change test_jit_get_samples --- example.py | 59 ++++++++++++++++++++ test/dynamics/pulse/test_pulse_to_signals.py | 6 +- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 example.py diff --git a/example.py b/example.py new file mode 100644 index 000000000..03aa882da --- /dev/null +++ b/example.py @@ -0,0 +1,59 @@ +from functools import partial +import jax +from qiskit import pulse +from qiskit_dynamics.pulse import InstructionToSignals +import numpy as np +import sympy as sym +from qiskit_dynamics.array import Array +Array.set_default_backend('jax') + +def _lifted_gaussian( + t: sym.Symbol, + center, + t_zero, + sigma, +) -> sym.Expr: + t_shifted = (t - center).expand() + t_offset = (t_zero - center).expand() + + gauss = sym.exp(-((t_shifted / sigma) ** 2) / 2) + offset = sym.exp(-((t_offset / sigma) ** 2) / 2) + + return (gauss - offset) / (1 - offset) + +@partial(jax.jit) +def run_simulation(amp, sigma): + # angle = 0.1 + # # pulse.Gaussian(duration=160, amp=amp, sigma=sigma, angle=np.pi) + # parameters = {"amp": amp, "sigma": sigma, "angle": angle} + + # # Prepare symbolic expressions + # _t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle") + # _center = _duration / 2 + + # envelope_expr = _amp * _lifted_gaussian(_t, _center, _duration + 1, _sigma) + # # To conform with some old tests, the angle part is inserted only when needed. + # if angle != 0: + # envelope_expr *= sym.exp(1j * _angle) + + # consts_expr = _sigma > 0 + # valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 + + # gaussian_pulse = pulse.SymbolicPulse( + # pulse_type="Gaussian", + # duration=100, + # parameters=parameters, + # name="GP1", + # envelope=envelope_expr, + # constraints=consts_expr, + # valid_amp_conditions=valid_amp_conditions_expr, + # ) + converter = InstructionToSignals(dt=1, carriers=None) + with pulse.build() as schedule: + pulse.play(pulse.Gaussian(duration=160, amp=amp, sigma=sigma, angle=0), pulse.DriveChannel(0)) + signals = converter.get_signals(schedule) +run_simulation(0.983,40) + +# amp=0.983 +# sigma=2 +# print(pulse.Gaussian(duration=160, amp=amp, sigma=sigma, angle=np.pi)) \ No newline at end of file diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 5972dbd23..b2ca6ed52 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -363,10 +363,10 @@ def test_get_samples_with_jax(self): def test_jit_get_samples(self): """Test compiling to get samples of Pulse.""" - def jit_func(amp, sigma): - return get_samples(Gaussian(duration=5, amp=amp, sigma=sigma)) + def jit_func(amp): + return get_samples(Constant(100, amp)) - jit_samples = jax.jit(jit_func, static_argnums=(0, 1))(0.983, 2) + jit_samples = jax.jit(jit_func)(0.1) self.assertAllClose(jit_samples, self.gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) From 6ad5815555a1f9212cf90dc1499d2ec6ce16b5cc Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 2 Dec 2022 16:29:39 +0900 Subject: [PATCH 05/24] add jax_grad test --- example.py | 59 -------------------- test/dynamics/pulse/test_pulse_to_signals.py | 20 ++++++- 2 files changed, 18 insertions(+), 61 deletions(-) delete mode 100644 example.py diff --git a/example.py b/example.py deleted file mode 100644 index 03aa882da..000000000 --- a/example.py +++ /dev/null @@ -1,59 +0,0 @@ -from functools import partial -import jax -from qiskit import pulse -from qiskit_dynamics.pulse import InstructionToSignals -import numpy as np -import sympy as sym -from qiskit_dynamics.array import Array -Array.set_default_backend('jax') - -def _lifted_gaussian( - t: sym.Symbol, - center, - t_zero, - sigma, -) -> sym.Expr: - t_shifted = (t - center).expand() - t_offset = (t_zero - center).expand() - - gauss = sym.exp(-((t_shifted / sigma) ** 2) / 2) - offset = sym.exp(-((t_offset / sigma) ** 2) / 2) - - return (gauss - offset) / (1 - offset) - -@partial(jax.jit) -def run_simulation(amp, sigma): - # angle = 0.1 - # # pulse.Gaussian(duration=160, amp=amp, sigma=sigma, angle=np.pi) - # parameters = {"amp": amp, "sigma": sigma, "angle": angle} - - # # Prepare symbolic expressions - # _t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle") - # _center = _duration / 2 - - # envelope_expr = _amp * _lifted_gaussian(_t, _center, _duration + 1, _sigma) - # # To conform with some old tests, the angle part is inserted only when needed. - # if angle != 0: - # envelope_expr *= sym.exp(1j * _angle) - - # consts_expr = _sigma > 0 - # valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - - # gaussian_pulse = pulse.SymbolicPulse( - # pulse_type="Gaussian", - # duration=100, - # parameters=parameters, - # name="GP1", - # envelope=envelope_expr, - # constraints=consts_expr, - # valid_amp_conditions=valid_amp_conditions_expr, - # ) - converter = InstructionToSignals(dt=1, carriers=None) - with pulse.build() as schedule: - pulse.play(pulse.Gaussian(duration=160, amp=amp, sigma=sigma, angle=0), pulse.DriveChannel(0)) - signals = converter.get_signals(schedule) -run_simulation(0.983,40) - -# amp=0.983 -# sigma=2 -# print(pulse.Gaussian(duration=160, amp=amp, sigma=sigma, angle=np.pi)) \ No newline at end of file diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index b2ca6ed52..9f855acf2 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -15,6 +15,7 @@ from ddt import ddt, data, unpack import numpy as np +import sympy as sym from qiskit import pulse from qiskit.pulse import ( @@ -32,6 +33,7 @@ Gaussian, Constant, Waveform, + SymbolicPulse, ) from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit import QiskitError @@ -364,8 +366,22 @@ def test_jit_get_samples(self): """Test compiling to get samples of Pulse.""" def jit_func(amp): - return get_samples(Constant(100, amp)) - + parameters = {"amp": amp} + _t, _amp, _duration = sym.symbols("t, amp, duration") + envelope_expr = _amp * sym.Piecewise((1, sym.And(_t >= 0, _t <= _duration)), (0, True)) + valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 + # we can use only SymbolicPulse when jax-jitting bacause jax-jitting doesn't correspond to validate_parameters in qiskit.pulse. + instance = SymbolicPulse( + pulse_type="Constant", + duration=100, + parameters=parameters, + envelope=envelope_expr, + valid_amp_conditions=valid_amp_conditions_expr, + ) + return get_samples(instance) + + self.jit_wrap(jit_func)(0.1) + self.jit_grad_wrap(jit_func)(0.1) jit_samples = jax.jit(jit_func)(0.1) self.assertAllClose(jit_samples, self.gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) From c3094274b1dffb59e07bb66f810e56d1a6d3643f Mon Sep 17 00:00:00 2001 From: to24toro <38037695+to24toro@users.noreply.github.com> Date: Fri, 16 Dec 2022 04:26:59 +0900 Subject: [PATCH 06/24] Fix pulse to signal converter (#164) Co-authored-by: Daniel Puzzuoli --- qiskit_dynamics/pulse/pulse_to_signals.py | 44 +++++----- test/dynamics/pulse/test_pulse_to_signals.py | 92 ++++++++++++++++++++ 2 files changed, 113 insertions(+), 23 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index f94d9684f..898291515 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -46,19 +46,17 @@ class InstructionToSignals: The :class:`InstructionsToSignals` class converts a pulse schedule to a list of signals that can be given to a model. This conversion is done by calling the :meth:`get_signals` method on a - schedule. The converter applies to instances of :class:`~qiskit.pulse.Schedule`. Instances of - :class:`~qiskit.pulse.ScheduleBlock` must first be converted to :class:`~qiskit.pulse.Schedule` - using the :func:`~qiskit.pulse.transforms.block_to_schedule` function in Qiskit Pulse. - - The converter can be initialized with the optional arguments ``carriers`` and ``channels``. When - ``channels`` is given, only the signals specified by name in ``channels`` are returned. The - ``carriers`` dictionary specifies the analog carrier frequency of each channel. Here, the keys - are the channel name, e.g. ``d12`` for drive channel number ``12``, and the values are the - corresponding frequency. If a channel is not present in ``carriers`` it is assumed that the - analog carrier frequency is zero. - - See the :meth:`get_signals` method documentation for a detailed description of how pulse - schedules are interpreted and translated into :class:`.DiscreteSignal` objects. + schedule. The converter applies to instances of :class:`Schedule`. Instances of + :class:`ScheduleBlock` must first be converted to :class:`Schedule` using the + :meth:`block_to_schedule` in Qiskit pulse. + + The converter can be initialized with the optional arguments ``carriers`` and ``channels``. + These arguments change the returned signals of :meth:`get_signals`. When ``channels`` is given + then only the signals specified by name in ``channels`` are returned. The ``carriers`` + dictionary allows the user to specify the carrier frequency of the channels. Here, the keys are + the channel name, e.g. ``d12`` for drive channel number 12, and the values are the corresponding + frequency. If a channel is not present in ``carriers`` it is assumed that the carrier frequency + is zero. """ def __init__( @@ -70,14 +68,15 @@ def __init__( """Initialize pulse schedule to signals converter. Args: - dt: Length of the samples. This is required by the converter as pulse schedule are - specified in units of dt and typically do not carry the value of dt with them. - carriers: A dict of analog carrier frequencies. The keys are the names of the channels + dt: Length of the samples. This is required by the converter as pulse + schedule are specified in units of dt and typically do not carry the value of dt + with them. + carriers: A dict of carrier frequencies. The keys are the names of the channels and the values are the corresponding carrier frequency. - channels: A list of channels that the :meth:`get_signals` method should return. This - argument will cause :meth:`get_signals` to return the signals in the same order as - the channels. Channels present in the schedule but absent from channels will not be - included in the returned object. If None is given (the default) then all channels + channels: A list of channels that the :meth:`get_signals` method should return. + This argument will cause :meth:`get_signals` to return the signals in the same order + as the channels. Channels present in the schedule but absent from channels will not + be included in the returned object. If None is given (the default) then all channels present in the pulse schedule are returned. """ @@ -135,9 +134,8 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: Args: schedule: The schedule to represent in terms of signals. Instances of - :class:`~qiskit.pulse.ScheduleBlock` must first be converted to - :class:`~qiskit.pulse.Schedule` using the - :func:`~qiskit.pulse.transforms.block_to_schedule` function in Qiskit Pulse. + :class:`ScheduleBlock` must first be converted to :class:`Schedule` using the + :meth:`block_to_schedule` in Qiskit pulse. Returns: A list of :class:`.DiscreteSignal` instances. diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 9f855acf2..e0d450211 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -234,6 +234,98 @@ def test_delay_and_shift_frequency(self): signals = converter.get_signals(sched) self.assertAllClose(signals[0].samples, all_samples) + def test_set_and_shift_frequency(self): + """Test that ShiftFrequency after SetFrequency is properly converted. It confirms + implementation of phase accumulation is correct.""" + + duration = 20 + unit_dt = 0.222 + sched = Schedule() + sched += SetFrequency(5.5, DriveChannel(0)) + sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) + sched += SetFrequency(6, DriveChannel(0)) + sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) + sched += ShiftFrequency(-0.5, DriveChannel(0)) + sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) + + freq_shift = 0.5 + phase_accumulation = 0.0 + all_samples = np.exp(2j * np.pi * freq_shift * unit_dt * np.arange(0, duration)) + + freq_shift = 1.0 + phase_accumulation -= (6.0 - 5.5) * duration * unit_dt + all_samples = np.append( + all_samples, + np.exp( + 2j + * np.pi + * (freq_shift * unit_dt * np.arange(duration, 2 * duration) + phase_accumulation) + ), + ) + + freq_shift = 0.5 + phase_accumulation -= -0.5 * 2 * duration * unit_dt + all_samples = np.append( + all_samples, + np.exp( + 2j + * np.pi + * ( + freq_shift * unit_dt * np.arange(2 * duration, 3 * duration) + + phase_accumulation + ) + ), + ) + + converter = InstructionToSignals(dt=unit_dt, carriers={"d0": 5.0}) + signals = converter.get_signals(sched) + self.assertAllClose(signals[0].samples, all_samples) + + def test_delay(self): + """Test that Delay is properly reflected.""" + + sched = Schedule() + sched += Play(Constant(duration=10, amp=1.0), DriveChannel(0)) + sched += Delay(10, DriveChannel(0)) + sched += Play(Constant(duration=10, amp=1.0), DriveChannel(0)) + + converter = InstructionToSignals(dt=0.222, carriers={"d0": 5.0}) + signals = converter.get_signals(sched) + samples_with_delay = np.array([1] * 10 + [0] * 10 + [1] * 10) + for idx in range(30): + self.assertEqual(signals[0].samples[idx], samples_with_delay[idx]) + + def test_delay_and_shift_frequency(self): + """Test that delay after SetFrequency is properly converted. + It confirms implementation of phase accumulation is correct.""" + + duration = 20 + unit_dt = 0.222 + sched = Schedule() + sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) + sched += ShiftFrequency(1.0, DriveChannel(0)) + sched += Delay(duration, DriveChannel(0)) + sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) + + freq_shift = 1.0 + phase_accumulation = -1.0 * duration * unit_dt + phase_accumulation = -1.0 * duration * unit_dt + all_samples = np.append( + np.append(np.ones(duration), np.zeros(duration)), + np.exp( + 2j + * np.pi + * ( + freq_shift * unit_dt * np.arange(2 * duration, 3 * duration) + + phase_accumulation + ) + ), + ) + + converter = InstructionToSignals(dt=unit_dt, carriers={"d0": 5.0}) + signals = converter.get_signals(sched) + self.assertAllClose(signals[0].samples, all_samples) + def test_uneven_pulse_length(self): """Test conversion when length of pulses on a schedule is uneven.""" From 5713e13ec4acf29e11711cf5eb730599559b95bc Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 13 Jan 2023 09:23:23 +0900 Subject: [PATCH 07/24] wrap signal samples as Array --- qiskit_dynamics/pulse/pulse_to_signals.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 898291515..ddbd43b06 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -176,9 +176,11 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: # build sample array to append to signal times = self._dt * (start_sample + np.arange(len(inst_samples))) samples = inst_samples * np.exp( - 2.0j * np.pi * freq * times - + 1.0j * phi - + 2.0j * np.pi * phase_accumulations[chan] + Array( + 2.0j * np.pi * freq * times + + 1.0j * phi + + 2.0j * np.pi * phase_accumulations[chan] + ) ) signals[chan].add_samples(start_sample, samples) From 9a3acaa5b20616899cb07c27ff282974c81ef3a3 Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 13 Jan 2023 10:26:10 +0900 Subject: [PATCH 08/24] format --- qiskit_dynamics/solvers/solver_classes.py | 5 ++++- test/dynamics/pulse/test_pulse_to_signals.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/qiskit_dynamics/solvers/solver_classes.py b/qiskit_dynamics/solvers/solver_classes.py index 8cb9784c4..a9fa6153a 100644 --- a/qiskit_dynamics/solvers/solver_classes.py +++ b/qiskit_dynamics/solvers/solver_classes.py @@ -54,7 +54,8 @@ try: - from jax import jit + from jax import core, jit + import jax.numpy as jnp except ImportError: pass @@ -523,6 +524,8 @@ def solve( Array.default_backend() == "jax" and (method == "jax_odeint" or _is_diffrax_method(method)) and all(isinstance(x, Schedule) for x in signals_list) + # check if jit transformation is already performed. + and not (isinstance(jnp.array(0), core.Tracer)) ): all_results = self._solve_schedule_list_jax( t_span_list=t_span_list, diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index e0d450211..ead4c6537 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -369,8 +369,10 @@ def test_different_start_times(self): samples0 = np.append(np.append(constant_samples, gauss_samples * phase), np.zeros(5)) samples1 = np.append(np.zeros(10), gauss_samples) - # set tolerance from 1e-14 to 1e-7 to match the accuracy of clipping samples in class WaveForm. - # see https://github.com/Qiskit/qiskit-terra/blob/ee0b0368e72913cddf1c80ed95bc55e174c65046/qiskit/pulse/library/waveform.py#L56 + # set tolerance from 1e-14 to 1e-7 + # to match the accuracy of clipping samples in class WaveForm. + # see https://github.com/Qiskit/qiskit-terra/blob/ + # ee0b0368e72913cddf1c80ed95bc55e174c65046/qiskit/pulse/library/waveform.py#L56 self.assertAllClose(signals[0].samples, samples0, atol=1e-7, rtol=1e-7) self.assertAllClose(signals[1].samples, samples1, atol=1e-7, rtol=1e-7) self.assertTrue(signals[0].carrier_freq == 5.0) @@ -459,10 +461,13 @@ def test_jit_get_samples(self): def jit_func(amp): parameters = {"amp": amp} - _t, _amp, _duration = sym.symbols("t, amp, duration") - envelope_expr = _amp * sym.Piecewise((1, sym.And(_t >= 0, _t <= _duration)), (0, True)) + _time, _amp, _duration = sym.symbols("t, amp, duration") + envelope_expr = _amp * sym.Piecewise( + (1, sym.And(_time >= 0, _time <= _duration)), (0, True) + ) valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - # we can use only SymbolicPulse when jax-jitting bacause jax-jitting doesn't correspond to validate_parameters in qiskit.pulse. + # we can use only SymbolicPulse when jax-jitting + # bacause jax-jitting doesn't correspond to validate_parameters in qiskit.pulse. instance = SymbolicPulse( pulse_type="Constant", duration=100, From 3c6a78aeef4f3ee0f60416e7c2cb395484092d2f Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 13 Jan 2023 11:15:08 +0900 Subject: [PATCH 09/24] modify test_pulse_to_signals --- test/dynamics/pulse/test_pulse_to_signals.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index ead4c6537..2758d65d6 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -447,8 +447,11 @@ def setUp(self): self.gauss_get_waveform_samples = ( pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples ) + self.constant_get_waveform_samples = ( + pulse.Constant(duration=5, amp=0.1).get_waveform().samples + ) - def test_get_samples_with_jax(self): + def test_get_samples(self): """Test get samples of Pulse not get_waveform but get_samples function in Jax case.""" gauss_get_samples = get_samples(Gaussian(duration=5, amp=0.983, sigma=2.0)) self.assertTrue(isinstance(gauss_get_samples, jnp.ndarray)) @@ -470,7 +473,7 @@ def jit_func(amp): # bacause jax-jitting doesn't correspond to validate_parameters in qiskit.pulse. instance = SymbolicPulse( pulse_type="Constant", - duration=100, + duration=5, parameters=parameters, envelope=envelope_expr, valid_amp_conditions=valid_amp_conditions_expr, @@ -480,7 +483,7 @@ def jit_func(amp): self.jit_wrap(jit_func)(0.1) self.jit_grad_wrap(jit_func)(0.1) jit_samples = jax.jit(jit_func)(0.1) - self.assertAllClose(jit_samples, self.gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) + self.assertAllClose(jit_samples, self.constant_get_waveform_samples, atol=1e-7, rtol=1e-7) @ddt From ccb9bdc5df71a0b96498a5003c593fe095a1910c Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 13 Jan 2023 11:58:15 +0900 Subject: [PATCH 10/24] add test_jit_solve_with_internal_jit --- qiskit_dynamics/pulse/pulse_to_signals.py | 10 +++--- test/dynamics/solvers/test_solver_classes.py | 38 +++++++++++++++++++- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index ddbd43b06..f4f0d100d 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -176,11 +176,11 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: # build sample array to append to signal times = self._dt * (start_sample + np.arange(len(inst_samples))) samples = inst_samples * np.exp( - Array( - 2.0j * np.pi * freq * times - + 1.0j * phi - + 2.0j * np.pi * phase_accumulations[chan] - ) + # Array( + 2.0j * np.pi * freq * times + + 1.0j * phi + + 2.0j * np.pi * phase_accumulations[chan] + # ) ) signals[chan].add_samples(start_sample, samples) diff --git a/test/dynamics/solvers/test_solver_classes.py b/test/dynamics/solvers/test_solver_classes.py index 37a4be93f..7ab7cb199 100644 --- a/test/dynamics/solvers/test_solver_classes.py +++ b/test/dynamics/solvers/test_solver_classes.py @@ -16,6 +16,7 @@ """ import numpy as np +import sympy as sym from ddt import ddt, data, unpack from qiskit import pulse, QiskitError @@ -1220,7 +1221,7 @@ def test_t_eval_t_span_jax_odeint(self): t_span=[0.0, 0.1], y0=np.array([0.0, 1.0]), t_eval=[0.0, 0.05, 0.1], - method="jax_odeint", + method=self.method, ) def test_t_eval_t_span_diffrax(self): @@ -1263,6 +1264,41 @@ def test_t_eval_t_span_jax_expm(self): max_dt=0.05, ) + def test_jit_solve_with_internal_jit(self): + """Test jitting solver with internal jitting works. + Internal jitting should be avoided when using jitting. + This test checks that using _solve_list not _solve_schedule_list_jax + when jitting solvers.solve. + """ + + def constant_pulse(amp): + parameters = {"amp": amp} + _time, _amp, _duration = sym.symbols("t, amp, duration") + envelope_expr = _amp * sym.Piecewise( + (1, sym.And(_time >= 0, _time <= _duration)), (0, True) + ) + valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 + return pulse.SymbolicPulse( + pulse_type="Constant", + duration=5, + parameters=parameters, + envelope=envelope_expr, + valid_amp_conditions=valid_amp_conditions_expr, + ) + + def jit_func(amp): + with pulse.build() as sched: + pulse.play(constant_pulse(amp), pulse.DriveChannel(0)) + + self.ham_solver.solve( + signals=sched, + t_span=[0.0, 0.1], + y0=np.array([0.0, 1.0]), + method=self.method, + ) + + self.jit_wrap(jit_func)(0.1) + @ddt class TestSolverListSimulation(QiskitDynamicsTestCase): From 42d4b8c21a688d6cb05f9114154d393536166c2c Mon Sep 17 00:00:00 2001 From: to24toro Date: Fri, 13 Jan 2023 13:00:41 +0900 Subject: [PATCH 11/24] impl jax-jit for pulse simulation --- qiskit_dynamics/pulse/pulse_to_signals.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index f4f0d100d..46b2e8098 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -176,11 +176,11 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: # build sample array to append to signal times = self._dt * (start_sample + np.arange(len(inst_samples))) samples = inst_samples * np.exp( - # Array( + Array( 2.0j * np.pi * freq * times + 1.0j * phi + 2.0j * np.pi * phase_accumulations[chan] - # ) + ) ) signals[chan].add_samples(start_sample, samples) @@ -188,19 +188,19 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: phases[chan] += inst.phase if isinstance(inst, ShiftFrequency): - frequency_shifts[chan] += inst.frequency - phase_accumulations[chan] -= inst.frequency * start_sample * self._dt + frequency_shifts[chan] = frequency_shifts[chan] + Array(inst.frequency) + phase_accumulations[chan] = phase_accumulations[chan] - inst.frequency * start_sample * self._dt if isinstance(inst, SetPhase): phases[chan] = inst.phase if isinstance(inst, SetFrequency): - phase_accumulations[chan] -= ( + phase_accumulations[chan] = phase_accumulations[chan] - ( (inst.frequency - (frequency_shifts[chan] + signals[chan].carrier_freq)) * start_sample * self._dt ) - frequency_shifts[chan] = inst.frequency - signals[chan].carrier_freq + frequency_shifts[chan] = inst.frequency- signals[chan].carrier_freq # ensure all signals have the same number of samples max_duration = 0 From a1637e094825cf37065cf02f490a856619341f6a Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 1 Feb 2023 17:17:09 +0900 Subject: [PATCH 12/24] format by black --- qiskit_dynamics/pulse/pulse_to_signals.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 46b2e8098..661666b63 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -177,9 +177,9 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: times = self._dt * (start_sample + np.arange(len(inst_samples))) samples = inst_samples * np.exp( Array( - 2.0j * np.pi * freq * times - + 1.0j * phi - + 2.0j * np.pi * phase_accumulations[chan] + 2.0j * np.pi * freq * times + + 1.0j * phi + + 2.0j * np.pi * phase_accumulations[chan] ) ) signals[chan].add_samples(start_sample, samples) @@ -189,7 +189,9 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: if isinstance(inst, ShiftFrequency): frequency_shifts[chan] = frequency_shifts[chan] + Array(inst.frequency) - phase_accumulations[chan] = phase_accumulations[chan] - inst.frequency * start_sample * self._dt + phase_accumulations[chan] = ( + phase_accumulations[chan] - inst.frequency * start_sample * self._dt + ) if isinstance(inst, SetPhase): phases[chan] = inst.phase @@ -200,7 +202,7 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: * start_sample * self._dt ) - frequency_shifts[chan] = inst.frequency- signals[chan].carrier_freq + frequency_shifts[chan] = inst.frequency - signals[chan].carrier_freq # ensure all signals have the same number of samples max_duration = 0 From ef502d372f082586ab835a2bb8a0117a1dd96e52 Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 2 Feb 2023 11:35:43 +0900 Subject: [PATCH 13/24] add type hint --- qiskit_dynamics/pulse/pulse_to_signals.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 661666b63..31cc6bc2e 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -14,7 +14,7 @@ Pulse schedule to Signals converter. """ -from typing import Dict, List, Optional +from typing import Callable ,Dict, List, Optional import functools import numpy as np @@ -312,7 +312,7 @@ def _get_channel(self, channel_name: str): ) from error -def get_samples(pulse: SymbolicPulse): +def get_samples(pulse: SymbolicPulse)-> np.ndarray: """Return samples filled according to the formula that the pulse represents and the parameter values it contains. @@ -350,7 +350,7 @@ def get_samples(pulse: SymbolicPulse): @functools.lru_cache(maxsize=None) -def _lru_cache_expr(expr: sym.Expr, backend): +def _lru_cache_expr(expr: sym.Expr, backend)-> Callable: """A helper function to get lambdified expression. Args: From a73d315e4acdf4025e85e015db4e09545ad2d2fe Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 2 Feb 2023 12:02:44 +0900 Subject: [PATCH 14/24] add test_SymbolicPulse --- test/dynamics/pulse/test_pulse_to_signals.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 2758d65d6..755162138 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -421,22 +421,16 @@ def test_multiple_channels_with_gaps(self): self.assertTrue(signals[2].carrier_freq == 0.0) self.assertTrue(signals[3].carrier_freq == 4.0) - def test_get_samples(self): + def test_SymbolicPulse(self): """Test get samples of Pulse not get_waveform but get_samples function.""" gauss_get_waveform_samples = ( pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples ) - gauss_get_samples = get_samples(Gaussian(duration=5, amp=0.983, sigma=2.0)) - self.assertTrue(isinstance(gauss_get_samples, np.ndarray)) - self.assertAllClose(gauss_get_samples, gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) - - def test_lru_cache_expr(self): - """Test lru_cache of lru_cache_expr function.""" - gauss_envelop = Gaussian(duration=5, amp=0.983, sigma=2.0).envelope - self.assertTrue( - _lru_cache_expr(gauss_envelop, Array.default_backend()) - is _lru_cache_expr(gauss_envelop, Array.default_backend()) - ) + with pulse.build() as schedule: + pulse.play(pulse.Gaussian(duration=5, amp=0.983, sigma=2.0), pulse.DriveChannel(0)) + converter = InstructionToSignals(dt=self._dt, channels=["d0"]) + signals = converter.get_signals(block_to_schedule(schedule)) + self.assertAllClose(signals[0].samples, gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) class TestJaxGetSamples(QiskitDynamicsTestCase, TestJaxBase): From 32ad3857352dc203e1474d7404d05728927f2f31 Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 2 Feb 2023 15:04:13 +0900 Subject: [PATCH 15/24] modify test_SymbolicPulse --- test/dynamics/pulse/test_pulse_to_signals.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 755162138..90a23f637 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -422,7 +422,7 @@ def test_multiple_channels_with_gaps(self): self.assertTrue(signals[3].carrier_freq == 4.0) def test_SymbolicPulse(self): - """Test get samples of Pulse not get_waveform but get_samples function.""" + """Test SymbolicPulse with get samples function.""" gauss_get_waveform_samples = ( pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples ) @@ -445,14 +445,6 @@ def setUp(self): pulse.Constant(duration=5, amp=0.1).get_waveform().samples ) - def test_get_samples(self): - """Test get samples of Pulse not get_waveform but get_samples function in Jax case.""" - gauss_get_samples = get_samples(Gaussian(duration=5, amp=0.983, sigma=2.0)) - self.assertTrue(isinstance(gauss_get_samples, jnp.ndarray)) - self.assertAllClose( - gauss_get_samples, self.gauss_get_waveform_samples, atol=1e-7, rtol=1e-7 - ) - def test_jit_get_samples(self): """Test compiling to get samples of Pulse.""" From 8839c78997c58b30f992e049e533bb00c729263a Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 2 Feb 2023 15:04:41 +0900 Subject: [PATCH 16/24] modify test_SymbolicPulse --- test/dynamics/pulse/test_pulse_to_signals.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 90a23f637..ef2eb08e5 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -421,8 +421,8 @@ def test_multiple_channels_with_gaps(self): self.assertTrue(signals[2].carrier_freq == 0.0) self.assertTrue(signals[3].carrier_freq == 4.0) - def test_SymbolicPulse(self): - """Test SymbolicPulse with get samples function.""" + def test_InstructionToSignals(self): + """Test InstructionToSignals with get samples function.""" gauss_get_waveform_samples = ( pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples ) From bf664128b46330b9cd2f98f17c0ea80f018b13b9 Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 2 Feb 2023 15:31:44 +0900 Subject: [PATCH 17/24] add test_pulse_types_combination_with_jax --- qiskit_dynamics/pulse/pulse_to_signals.py | 6 +-- test/dynamics/pulse/test_pulse_to_signals.py | 47 +++++++++++++++++--- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 31cc6bc2e..3b567666a 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -14,7 +14,7 @@ Pulse schedule to Signals converter. """ -from typing import Callable ,Dict, List, Optional +from typing import Callable, Dict, List, Optional import functools import numpy as np @@ -312,7 +312,7 @@ def _get_channel(self, channel_name: str): ) from error -def get_samples(pulse: SymbolicPulse)-> np.ndarray: +def get_samples(pulse: SymbolicPulse) -> np.ndarray: """Return samples filled according to the formula that the pulse represents and the parameter values it contains. @@ -350,7 +350,7 @@ def get_samples(pulse: SymbolicPulse)-> np.ndarray: @functools.lru_cache(maxsize=None) -def _lru_cache_expr(expr: sym.Expr, backend)-> Callable: +def _lru_cache_expr(expr: sym.Expr, backend) -> Callable: """A helper function to get lambdified expression. Args: diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index ef2eb08e5..e2234376d 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -38,11 +38,9 @@ from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit import QiskitError -from qiskit_dynamics.array import Array from qiskit_dynamics.pulse import InstructionToSignals from qiskit_dynamics.pulse.pulse_to_signals import ( get_samples, - _lru_cache_expr, ) from qiskit_dynamics.signals import DiscreteSignal @@ -50,7 +48,6 @@ try: import jax - import jax.numpy as jnp # pylint: disable=broad-except except Exception: pass @@ -444,11 +441,12 @@ def setUp(self): self.constant_get_waveform_samples = ( pulse.Constant(duration=5, amp=0.1).get_waveform().samples ) + self._dt = 0.222 def test_jit_get_samples(self): """Test compiling to get samples of Pulse.""" - def jit_func(amp): + def jit_func_get_samples(amp): parameters = {"amp": amp} _time, _amp, _duration = sym.symbols("t, amp, duration") envelope_expr = _amp * sym.Piecewise( @@ -466,11 +464,46 @@ def jit_func(amp): ) return get_samples(instance) - self.jit_wrap(jit_func)(0.1) - self.jit_grad_wrap(jit_func)(0.1) - jit_samples = jax.jit(jit_func)(0.1) + self.jit_wrap(jit_func_get_samples)(0.1) + self.jit_grad_wrap(jit_func_get_samples)(0.1) + jit_samples = jax.jit(jit_func_get_samples)(0.1) self.assertAllClose(jit_samples, self.constant_get_waveform_samples, atol=1e-7, rtol=1e-7) + def test_pulse_types_combination_with_jax(self): + """Test that converting schedule including some pulse types with Jax works well""" + + def jit_func_symbolic_pulse(amp): + parameters = {"amp": amp} + _time, _amp, _duration = sym.symbols("t, amp, duration") + envelope_expr = _amp * sym.Piecewise( + (1, sym.And(_time >= 0, _time <= _duration)), (0, True) + ) + valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 + instance = SymbolicPulse( + pulse_type="Constant", + duration=5, + parameters=parameters, + envelope=envelope_expr, + valid_amp_conditions=valid_amp_conditions_expr, + ) + # constrcut a pulse schedule with mixing some pulse types to test jax-jitting it + with pulse.build() as schedule: + pulse.play(instance, pulse.DriveChannel(0)) + pulse.set_phase(0.1, pulse.DriveChannel(0)) + pulse.set_frequency(0.1, pulse.DriveChannel(0)) + pulse.shift_phase(0.1, pulse.DriveChannel(0)) + pulse.set_phase(0.1, pulse.DriveChannel(0)) + pulse.shift_frequency(0.1, pulse.DriveChannel(0)) + pulse.shift_frequency(0.1, pulse.DriveChannel(0)) + pulse.set_frequency(0.1, pulse.DriveChannel(0)) + pulse.shift_phase(0.1, pulse.DriveChannel(0)) + pulse.play(instance, pulse.DriveChannel(0)) + converter = InstructionToSignals(self._dt, carriers={"d0": 5}) + return converter.get_signals(schedule)[0].samples + + self.jit_wrap(jit_func_symbolic_pulse)(0.1) + self.jit_grad_wrap(jit_func_symbolic_pulse)(0.1) + @ddt class TestPulseToSignalsFiltering(QiskitDynamicsTestCase): From 9bc49724fd46432ade0b0a923dc7bb0ba7c67a2c Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 2 Feb 2023 19:11:26 +0900 Subject: [PATCH 18/24] remove redefined fucntion --- test/dynamics/pulse/test_pulse_to_signals.py | 92 -------------------- 1 file changed, 92 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index e2234376d..4e8795c8b 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -231,98 +231,6 @@ def test_delay_and_shift_frequency(self): signals = converter.get_signals(sched) self.assertAllClose(signals[0].samples, all_samples) - def test_set_and_shift_frequency(self): - """Test that ShiftFrequency after SetFrequency is properly converted. It confirms - implementation of phase accumulation is correct.""" - - duration = 20 - unit_dt = 0.222 - sched = Schedule() - sched += SetFrequency(5.5, DriveChannel(0)) - sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) - sched += SetFrequency(6, DriveChannel(0)) - sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) - sched += ShiftFrequency(-0.5, DriveChannel(0)) - sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) - - freq_shift = 0.5 - phase_accumulation = 0.0 - all_samples = np.exp(2j * np.pi * freq_shift * unit_dt * np.arange(0, duration)) - - freq_shift = 1.0 - phase_accumulation -= (6.0 - 5.5) * duration * unit_dt - all_samples = np.append( - all_samples, - np.exp( - 2j - * np.pi - * (freq_shift * unit_dt * np.arange(duration, 2 * duration) + phase_accumulation) - ), - ) - - freq_shift = 0.5 - phase_accumulation -= -0.5 * 2 * duration * unit_dt - all_samples = np.append( - all_samples, - np.exp( - 2j - * np.pi - * ( - freq_shift * unit_dt * np.arange(2 * duration, 3 * duration) - + phase_accumulation - ) - ), - ) - - converter = InstructionToSignals(dt=unit_dt, carriers={"d0": 5.0}) - signals = converter.get_signals(sched) - self.assertAllClose(signals[0].samples, all_samples) - - def test_delay(self): - """Test that Delay is properly reflected.""" - - sched = Schedule() - sched += Play(Constant(duration=10, amp=1.0), DriveChannel(0)) - sched += Delay(10, DriveChannel(0)) - sched += Play(Constant(duration=10, amp=1.0), DriveChannel(0)) - - converter = InstructionToSignals(dt=0.222, carriers={"d0": 5.0}) - signals = converter.get_signals(sched) - samples_with_delay = np.array([1] * 10 + [0] * 10 + [1] * 10) - for idx in range(30): - self.assertEqual(signals[0].samples[idx], samples_with_delay[idx]) - - def test_delay_and_shift_frequency(self): - """Test that delay after SetFrequency is properly converted. - It confirms implementation of phase accumulation is correct.""" - - duration = 20 - unit_dt = 0.222 - sched = Schedule() - sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) - sched += ShiftFrequency(1.0, DriveChannel(0)) - sched += Delay(duration, DriveChannel(0)) - sched += Play(Constant(duration=duration, amp=1.0), DriveChannel(0)) - - freq_shift = 1.0 - phase_accumulation = -1.0 * duration * unit_dt - phase_accumulation = -1.0 * duration * unit_dt - all_samples = np.append( - np.append(np.ones(duration), np.zeros(duration)), - np.exp( - 2j - * np.pi - * ( - freq_shift * unit_dt * np.arange(2 * duration, 3 * duration) - + phase_accumulation - ) - ), - ) - - converter = InstructionToSignals(dt=unit_dt, carriers={"d0": 5.0}) - signals = converter.get_signals(sched) - self.assertAllClose(signals[0].samples, all_samples) - def test_uneven_pulse_length(self): """Test conversion when length of pulses on a schedule is uneven.""" From 39a1e90ef0914433828f93869b84a9cd93387d9c Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 8 Feb 2023 10:49:30 +0900 Subject: [PATCH 19/24] restore previous comments --- qiskit_dynamics/pulse/pulse_to_signals.py | 44 +++++++++++------------ 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 3b567666a..f779c5cd5 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -46,17 +46,17 @@ class InstructionToSignals: The :class:`InstructionsToSignals` class converts a pulse schedule to a list of signals that can be given to a model. This conversion is done by calling the :meth:`get_signals` method on a - schedule. The converter applies to instances of :class:`Schedule`. Instances of - :class:`ScheduleBlock` must first be converted to :class:`Schedule` using the - :meth:`block_to_schedule` in Qiskit pulse. - - The converter can be initialized with the optional arguments ``carriers`` and ``channels``. - These arguments change the returned signals of :meth:`get_signals`. When ``channels`` is given - then only the signals specified by name in ``channels`` are returned. The ``carriers`` - dictionary allows the user to specify the carrier frequency of the channels. Here, the keys are - the channel name, e.g. ``d12`` for drive channel number 12, and the values are the corresponding - frequency. If a channel is not present in ``carriers`` it is assumed that the carrier frequency - is zero. + schedule. The converter applies to instances of :class:`~qiskit.pulse.Schedule`. Instances of + :class:`~qiskit.pulse.ScheduleBlock` must first be converted to :class:`~qiskit.pulse.Schedule` + using the :func:`~qiskit.pulse.transforms.block_to_schedule` function in Qiskit Pulse. + The converter can be initialized with the optional arguments ``carriers`` and ``channels``. When + ``channels`` is given, only the signals specified by name in ``channels`` are returned. The + ``carriers`` dictionary specifies the analog carrier frequency of each channel. Here, the keys + are the channel name, e.g. ``d12`` for drive channel number ``12``, and the values are the + corresponding frequency. If a channel is not present in ``carriers`` it is assumed that the + analog carrier frequency is zero. + See the :meth:`get_signals` method documentation for a detailed description of how pulse + schedules are interpreted and translated into :class:`.DiscreteSignal` objects. """ def __init__( @@ -68,16 +68,14 @@ def __init__( """Initialize pulse schedule to signals converter. Args: - dt: Length of the samples. This is required by the converter as pulse - schedule are specified in units of dt and typically do not carry the value of dt - with them. - carriers: A dict of carrier frequencies. The keys are the names of the channels + dt: Length of the samples. This is required by the converter as pulse schedule are + specified in units of dt and typically do not carry the value of dt with them. + carriers: A dict of analog carrier frequencies. The keys are the names of the channels and the values are the corresponding carrier frequency. - channels: A list of channels that the :meth:`get_signals` method should return. - This argument will cause :meth:`get_signals` to return the signals in the same order - as the channels. Channels present in the schedule but absent from channels will not - be included in the returned object. If None is given (the default) then all channels - present in the pulse schedule are returned. + channels: A list of channels that the :meth:`get_signals` method should return. This + argument will cause :meth:`get_signals` to return the signals in the same order as + the channels. Channels present in the schedule but absent from channels will not be + included in the returned object. If None is given (the default) then all channels """ self._dt = dt @@ -134,8 +132,9 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: Args: schedule: The schedule to represent in terms of signals. Instances of - :class:`ScheduleBlock` must first be converted to :class:`Schedule` using the - :meth:`block_to_schedule` in Qiskit pulse. + :class:`~qiskit.pulse.ScheduleBlock` must first be converted to + :class:`~qiskit.pulse.Schedule` using the + :func:`~qiskit.pulse.transforms.block_to_schedule` function in Qiskit Pulse. Returns: A list of :class:`.DiscreteSignal` instances. @@ -165,7 +164,6 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]: phi = phases[chan] freq = frequency_shifts[chan] if isinstance(inst, Play): - # get the instruction samples inst_samples = None if isinstance(inst.pulse, Waveform): From aa10fbec3e1ade4578533a256b0fa5c4ccf0f1df Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 8 Feb 2023 10:54:56 +0900 Subject: [PATCH 20/24] add removed sentence --- qiskit_dynamics/pulse/pulse_to_signals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index f779c5cd5..0c5db1099 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -76,6 +76,7 @@ def __init__( argument will cause :meth:`get_signals` to return the signals in the same order as the channels. Channels present in the schedule but absent from channels will not be included in the returned object. If None is given (the default) then all channels + present in the pulse schedule are returned. """ self._dt = dt From 454f1194463df8cfe5bf002343e81fa92fdc04b4 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 15 Feb 2023 08:00:50 +0900 Subject: [PATCH 21/24] rename class name from TestJaxGetSamples to TestPulseToSignalsJAXTransformations --- test/dynamics/pulse/test_pulse_to_signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 4e8795c8b..77a31823a 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -338,7 +338,7 @@ def test_InstructionToSignals(self): self.assertAllClose(signals[0].samples, gauss_get_waveform_samples, atol=1e-7, rtol=1e-7) -class TestJaxGetSamples(QiskitDynamicsTestCase, TestJaxBase): +class TestPulseToSignalsJAXTransformations(QiskitDynamicsTestCase, TestJaxBase): """Tests get_samples function by using Jax.""" def setUp(self): From 2814cf0587ceabc6354cbf628560536bcad24577 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 15 Feb 2023 08:34:21 +0900 Subject: [PATCH 22/24] fix codes about TestPulseToSignalsJAXTransformations --- test/dynamics/pulse/test_pulse_to_signals.py | 32 ++++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 77a31823a..03df1fc47 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -39,19 +39,10 @@ from qiskit import QiskitError from qiskit_dynamics.pulse import InstructionToSignals -from qiskit_dynamics.pulse.pulse_to_signals import ( - get_samples, -) from qiskit_dynamics.signals import DiscreteSignal from ..common import QiskitDynamicsTestCase, TestJaxBase -try: - import jax -# pylint: disable=broad-except -except Exception: - pass - class TestPulseToSignals(QiskitDynamicsTestCase): """Tests the conversion between pulse schedules and signals.""" @@ -339,22 +330,19 @@ def test_InstructionToSignals(self): class TestPulseToSignalsJAXTransformations(QiskitDynamicsTestCase, TestJaxBase): - """Tests get_samples function by using Jax.""" + """Tests InstructionToSignals class by using Jax.""" def setUp(self): """Set up gaussian waveform samples for comparison.""" - self.gauss_get_waveform_samples = ( - pulse.Gaussian(duration=5, amp=0.983, sigma=2.0).get_waveform().samples - ) self.constant_get_waveform_samples = ( pulse.Constant(duration=5, amp=0.1).get_waveform().samples ) self._dt = 0.222 - def test_jit_get_samples(self): - """Test compiling to get samples of Pulse.""" + def test_InstructionToSignals_with_JAX(self): + """Test InstructionToSignals with JAX jit.""" - def jit_func_get_samples(amp): + def jit_func_instruction_to_signals(amp): parameters = {"amp": amp} _time, _amp, _duration = sym.symbols("t, amp, duration") envelope_expr = _amp * sym.Piecewise( @@ -370,11 +358,15 @@ def jit_func_get_samples(amp): envelope=envelope_expr, valid_amp_conditions=valid_amp_conditions_expr, ) - return get_samples(instance) + with pulse.build() as schedule: + pulse.play(instance, pulse.DriveChannel(0)) + + converter = InstructionToSignals(self._dt, carriers={"d0": 5}) + return converter.get_signals(schedule)[0].samples - self.jit_wrap(jit_func_get_samples)(0.1) - self.jit_grad_wrap(jit_func_get_samples)(0.1) - jit_samples = jax.jit(jit_func_get_samples)(0.1) + self.jit_wrap(jit_func_instruction_to_signals)(0.1) + self.jit_grad_wrap(jit_func_instruction_to_signals)(0.1) + jit_samples = self.jit_wrap(jit_func_instruction_to_signals)(0.1) self.assertAllClose(jit_samples, self.constant_get_waveform_samples, atol=1e-7, rtol=1e-7) def test_pulse_types_combination_with_jax(self): From f8b055eec8bd5b87b97284bc43907bb04f885fb7 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 15 Feb 2023 11:06:21 +0900 Subject: [PATCH 23/24] modify importing and naming of pulse function --- test/dynamics/pulse/test_pulse_to_signals.py | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/test/dynamics/pulse/test_pulse_to_signals.py b/test/dynamics/pulse/test_pulse_to_signals.py index 1b457c602..a1ff961c1 100644 --- a/test/dynamics/pulse/test_pulse_to_signals.py +++ b/test/dynamics/pulse/test_pulse_to_signals.py @@ -18,6 +18,7 @@ import sympy as sym from qiskit import pulse +from qiskit.pulse import Schedule from qiskit.pulse.transforms.canonicalization import block_to_schedule from qiskit import QiskitError @@ -40,7 +41,7 @@ def setUp(self): def test_pulse_to_signals(self): """Generic test.""" - sched = pulse.Schedule(name="Schedule") + sched = Schedule(name="Schedule") sched += pulse.Play( pulse.Drag(duration=20, amp=0.5, sigma=4, beta=0.5), pulse.DriveChannel(0) ) @@ -72,7 +73,7 @@ def test_shift_phase_to_signals(self): gaussian = pulse.Gaussian(duration=20, amp=0.5, sigma=4) - sched = pulse.Schedule(name="Schedule") + sched = Schedule(name="Schedule") sched += pulse.ShiftPhase(np.pi, pulse.DriveChannel(0)) sched += pulse.Play(gaussian, pulse.DriveChannel(0)) @@ -85,7 +86,7 @@ def test_shift_phase_to_signals(self): def test_carriers_and_dt(self): """Test that the carriers go into the signals.""" - sched = pulse.Schedule(name="Schedule") + sched = Schedule(name="Schedule") sched += pulse.Play(pulse.Gaussian(duration=20, amp=0.5, sigma=4), pulse.DriveChannel(0)) converter = InstructionToSignals(dt=self._dt, carriers={"d0": 5.5e9}) @@ -98,7 +99,7 @@ def test_carriers_and_dt(self): def test_shift_frequency(self): """Test that the frequency is properly taken into account.""" - sched = pulse.Schedule() + sched = Schedule() sched += pulse.ShiftFrequency(1.0, pulse.DriveChannel(0)) sched += pulse.Play(pulse.Constant(duration=10, amp=1.0), pulse.DriveChannel(0)) @@ -111,7 +112,7 @@ def test_shift_frequency(self): def test_set_frequency(self): """Test that SetFrequency is properly converted.""" - sched = pulse.Schedule() + sched = Schedule() sched += pulse.SetFrequency(4.0, pulse.DriveChannel(0)) sched += pulse.Play(pulse.Constant(duration=10, amp=1.0), pulse.DriveChannel(0)) @@ -126,7 +127,7 @@ def test_set_and_shift_frequency(self): implementation of phase accumulation is correct.""" duration = 20 - sched = pulse.Schedule() + sched = Schedule() sched += pulse.SetFrequency(5.5, pulse.DriveChannel(0)) sched += pulse.Play(pulse.Constant(duration=duration, amp=1.0), pulse.DriveChannel(0)) sched += pulse.SetFrequency(6, pulse.DriveChannel(0)) @@ -170,7 +171,7 @@ def test_set_and_shift_frequency(self): def test_delay(self): """Test that Delay is properly reflected.""" - sched = pulse.Schedule() + sched = Schedule() sched += pulse.Play(pulse.Constant(duration=10, amp=1.0), pulse.DriveChannel(0)) sched += pulse.Delay(10, pulse.DriveChannel(0)) sched += pulse.Play(pulse.Constant(duration=10, amp=1.0), pulse.DriveChannel(0)) @@ -186,7 +187,7 @@ def test_delay_and_shift_frequency(self): It confirms implementation of phase accumulation is correct.""" duration = 20 - sched = pulse.Schedule() + sched = Schedule() sched += pulse.Play(pulse.Constant(duration=duration, amp=1.0), pulse.DriveChannel(0)) sched += pulse.ShiftFrequency(1.0, pulse.DriveChannel(0)) sched += pulse.Delay(duration, pulse.DriveChannel(0)) @@ -214,7 +215,7 @@ def test_delay_and_shift_frequency(self): def test_uneven_pulse_length(self): """Test conversion when length of pulses on a schedule is uneven.""" - schedule = pulse.Schedule() + schedule = Schedule() schedule |= pulse.Play(pulse.Waveform(np.ones(10)), pulse.DriveChannel(0)) schedule += pulse.Play(pulse.Constant(20, 1), pulse.DriveChannel(1)) @@ -340,7 +341,7 @@ def jit_func_instruction_to_signals(amp): valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 # we can use only SymbolicPulse when jax-jitting # bacause jax-jitting doesn't correspond to validate_parameters in qiskit.pulse. - instance = SymbolicPulse( + instance = pulse.SymbolicPulse( pulse_type="Constant", duration=5, parameters=parameters, @@ -368,7 +369,7 @@ def jit_func_symbolic_pulse(amp): (1, sym.And(_time >= 0, _time <= _duration)), (0, True) ) valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0 - instance = SymbolicPulse( + instance = pulse.SymbolicPulse( pulse_type="Constant", duration=5, parameters=parameters, From c3338c5b6ddd56a51cdd8fc156eff25174d6e2be Mon Sep 17 00:00:00 2001 From: to24toro Date: Thu, 16 Feb 2023 06:10:12 +0900 Subject: [PATCH 24/24] insert spaces in InstructionToSignals docs --- qiskit_dynamics/pulse/pulse_to_signals.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit_dynamics/pulse/pulse_to_signals.py b/qiskit_dynamics/pulse/pulse_to_signals.py index 0c5db1099..b91f88bc9 100644 --- a/qiskit_dynamics/pulse/pulse_to_signals.py +++ b/qiskit_dynamics/pulse/pulse_to_signals.py @@ -49,12 +49,14 @@ class InstructionToSignals: schedule. The converter applies to instances of :class:`~qiskit.pulse.Schedule`. Instances of :class:`~qiskit.pulse.ScheduleBlock` must first be converted to :class:`~qiskit.pulse.Schedule` using the :func:`~qiskit.pulse.transforms.block_to_schedule` function in Qiskit Pulse. + The converter can be initialized with the optional arguments ``carriers`` and ``channels``. When ``channels`` is given, only the signals specified by name in ``channels`` are returned. The ``carriers`` dictionary specifies the analog carrier frequency of each channel. Here, the keys are the channel name, e.g. ``d12`` for drive channel number ``12``, and the values are the corresponding frequency. If a channel is not present in ``carriers`` it is assumed that the analog carrier frequency is zero. + See the :meth:`get_signals` method documentation for a detailed description of how pulse schedules are interpreted and translated into :class:`.DiscreteSignal` objects. """