Interpolated Waveforms
[1]:
import numpy as np
import matplotlib.pyplot as plt
import pulser
from pulser import Pulse, Sequence, Register
from pulser.devices import AnalogDevice
from pulser.waveforms import InterpolatedWaveform
In some particular applications (the most notorious being its use in optimal control at the pulse level) it is useful to define a waveform through the interpolation of a set of data points. For these cases, Pulser provides the InterpolatedWaveform
class.
In its most stripped back form, the InterpolatedWavefrom
requires two things: a duration
and a list of values
. The placement of these values along the times axis defaults to spacing the values evenly along the whole duration of the waveform.
[2]:
# A standard interpolation (using PchipInterpolator)
duration = 1000
values = [0, 1, 4.4, 2, 3, 1, 0]
int_wf = InterpolatedWaveform(duration, values)
int_wf.draw()
If we want to specify the time coordinate of each data point, we can do so through the times
option. These should come in a list of values betwen 0
and 1
, which are interperted as fractions of the total duration. As an example, below we specify that the values
are all bunched in the first half of the waveform, except for the last one, which is left at the end.
[3]:
ts = np.r_[np.linspace(0.0, 0.5, num=len(values) - 1), 1]
int_wf_t = InterpolatedWaveform(duration, values, times=ts)
int_wf_t.draw()
The other crucial component is the interpolator
. Currently, the class supports two interpolator classes from the scipy.interpolate
module: PchipInterpolator
(chosen by default) and interp1d
. Below, we change the interpolator to interp1d
, which does a linear interpolation by default:
[4]:
int_wf2 = InterpolatedWaveform(duration, values, interpolator="interp1d")
int_wf2.draw()
One can also change the optional parameters of the chosen interpolator by giving them to InterpolatedWaveform
. For example, the interpolation with interp1d
can be cubic instead of linear by changing the kind
parameter.
Naturally, the choice of interpolator will dictate which extra parameters can be optionally provided. As such, one must refer to the chosen interpolator’s documentation for the specific details.
[5]:
int_wf3 = InterpolatedWaveform(
duration, values, interpolator="interp1d", kind="cubic"
)
int_wf3.draw()
Finally, an InterpolatedWaveform
can be streched or contracted in both magnitude and duration:
[6]:
# Streching an interpolated waveform
int_wf_stretch_y = int_wf * 2
int_wf_stretch_x = int_wf.change_duration(duration * 2)
fig, ax = plt.subplots()
ax.plot(int_wf.samples, label="Original")
ax.plot(int_wf_stretch_y.samples, label="Streched in Y")
ax.plot(int_wf_stretch_x.samples, label="Streched in X")
ax.legend()
plt.show()
Use in a parameterized sequence
The InterpolatedWaveform
is used like any other waveform. In the contexts where it usually comes up, it might be helpful to incorporate it in a parametrized sequence. Below, we’ll show how to make a parametrized sequence where the data points for the amplitude and detuning waveforms are variables.
[7]:
reg = Register.square(1)
param_seq = Sequence(reg, AnalogDevice)
param_seq.declare_channel("rydberg_global", "rydberg_global", initial_target=0)
amp_vals = param_seq.declare_variable("amp_vals", size=5, dtype=float)
det_vals = param_seq.declare_variable("det_vals", size=4, dtype=float)
amp_wf = InterpolatedWaveform(1000, amp_vals)
det_wf = InterpolatedWaveform(
1000, det_vals, interpolator="interp1d", kind="cubic"
)
pls = Pulse(amp_wf, det_wf, 0)
param_seq.add(pls, "rydberg_global")
From here, we can build some random sequence by specifying the values of amp_vals
and det_vals
. For example:
[8]:
seq1 = param_seq.build(amp_vals=[0, 2, 1, 2, 0], det_vals=[0, -5, 5, -5])
seq1.draw()
Notice how the interpolation points are automatically drawn on top of the waveforms. You can disable this option through the draw_interp_pts
parameter:
[9]:
seq1.draw(draw_interp_pts=False)