Download
Download this file as Jupyter notebook: envelopes.ipynb.
Introduction to pulse envelopes
This guide gives an overview of the types of pulse envelopes available in Keysight’s Quantum Control System and demonstrates how to initialize them, add them to waveforms in a programs, and plot them.
Note
Throughout the examples in this tutorial we are using
DCWaveform
s to demonstrate the
envelopes. Introduction to RF waveforms shows how to generate the more commonly used
RFWaveform
s with envelopes.
[2]:
import keysight.qcs as qcs
import numpy as np
Built-In Envelopes
Constant Envelope
A constant envelope is the simplest pulse shape. It realizes a constant amplitude for a duration. In this example we configure a program with one AWG channel and add a DC waveform with a constant envelope to it.
[3]:
# initialize a constant envelope
envelope = qcs.ConstantEnvelope()
dc_wav = qcs.DCWaveform(duration=2, envelope=envelope, amplitude=1)
# instantiate an empty program
program = qcs.Program()
# instantiate an awg channel
awg = qcs.Channels(range(1), "awg_channel")
# add the envelope to the program
program.add_waveform(dc_wav, awg)
# render
program.render(sample_rate=1e3)
Gaussian Envelope
In this example we define a DC waveform with a Gaussian envelope with a duration of 32 ns and standard deviation of 8 ns.
[4]:
# initialize a Gaussian
envelope = qcs.GaussianEnvelope(num_sigma=2)
dc_wav = qcs.DCWaveform(32e-9, envelope, 1)
# initialize the program and add the envelope
program = qcs.Program()
program.add_waveform(dc_wav, awg)
# render
program.render()
Flat-Top Gaussian Envelope
Here we realize a Flat-Top Gaussian envelope. During an initial rise and equal lower
time, the pulse is Gaussian. In the middle, for a time duration
, the envelope is
constant and equal to its maximum value.
In this example we make use of the built-in Scalar
class, which allows us to modify parameters after instantiation. We render the
envelope once, then render it again with different parameters. This behavior is used
for example in swept programs.
[5]:
# initialize parameters
duration = qcs.Scalar("duration", value=32e-9, dtype=float)
amplitude = qcs.Scalar("amplitude", value=0.8, dtype=float)
rise = qcs.Scalar("rise", value=8e-9, dtype=float)
# initialize a Gaussian DC Waveform
dc_wav = qcs.DCWaveform(
duration=2 * rise, envelope=qcs.GaussianEnvelope(), amplitude=amplitude
)
# initialize the program and add the flattop using the to_flattop method
program = qcs.Program()
program.add_waveform(dc_wav.to_flattop(duration), awg)
# render
program.render()
We now change the parameters defining the envelope and re-render it.
[6]:
duration.value = 64e-9
amplitude.value = 0.6
rise.value = 20e-9
# render
program.render()
Derivative Envelope
The derivative envelope is specified by a base_envelope
and optional
step_size
. The base_envelope
can be any envelope defined by Keysight’s Quantum Control System.
[7]:
base_envelope = qcs.GaussianEnvelope(num_sigma=5)
envelope = qcs.DerivativeEnvelope(base_envelope)
dc_wav = qcs.DCWaveform(2, envelope, 0.7)
# initialize the program and add the envelope
program = qcs.Program()
program.add_waveform(dc_wav, awg)
# render
program.render(sample_rate=1e3)
DRAG Envelope
Derivative Removal by Adiabatic Gate (DRAG) envelopes were introduced in Motzoi et al. as a means of offsetting the noise introduced during the application of a pulse by applying another pulse in an orthogonal axis. The envelope applied orthogonal to the base envelope is described by the derivative of the base envelope scaled with some DRAG coefficient. Often, pulses applied simultaneously in different axes are stored in the real and imaginary parts of an envelope. We therefore describe the DRAG pulse of a base envelope \(E(t)\) as
where \(\beta\) is the drag coefficient and \(E'(t)\) is the derivative of \(E(t)\) with respect to time.
Most DRAG envelopes are not explicitly defined in Keysight’s Quantum Control System. However, a method that automatically constructs the relevant DRAG envelope given a base envelope using numerical differentiation is provided.
In this example, we assemble a DRAG envelope with a Gaussian as the base shape. Here, the DRAG coefficient is \(\beta=0.1\).
[8]:
# initialize a 30ns Gaussian RF Waveform at 5GHz
gauss = qcs.RFWaveform(
duration=30e-9, envelope=qcs.GaussianEnvelope(), amplitude=0.8, rf_frequency=5e9
)
# initialize the program and add the drag pulse with a coefficient of 0.05
program = qcs.Program()
program.add_waveform(gauss.drag(0.05), awg)
# render
program.render(sample_rate=5e9, plot_imaginary=True)
Arbitrary envelopes
An arbitrary envelope allows a user to specify any shape of pulse.
Example 1: linear interpolation
In this example we implement a flat-top pulse with linear interpolation from zero to
the maximum value during the interval [0, rise_time]
. The amplitude then falls
back to zero on the interval [duration-fall_time, duration]
. Arbitrary envelopes
can be specified as a list of sample times and corresponding amplitudes, in which case
linear interpolation is applied between specified points.
[9]:
duration = 3
rise_time = 0.5
fall_time = 1
amplitude = 0.6
# specify the samples for linear interpolation
times = [0, rise_time, duration - fall_time, duration]
samples = [0, amplitude, amplitude, 0]
# initialize the envelope
envelope = qcs.ArbitraryEnvelope(times, samples)
dc_wav = qcs.DCWaveform(duration, envelope, amplitude)
# initialize the program and add the envelope
program = qcs.Program()
program.add_waveform(dc_wav, awg)
# render
program.render(sample_rate=1e3)
Example 2: functional definition with sech envelope
Rather than specifying an envelope with a collection of discrete points, an envelope can be specified with a function that defines the envelope’s shape.
In this example we implement a sech envelope. The envelope \(E(t)\) is given by
where \(A\) is the scaling parameter, \(\mu\) is the mean and \(\sigma\) is the standard deviation.
[10]:
# define the sech envelope
def sech(times, A, sigma):
return A / np.cosh((times - times[-1] / 2) / sigma)
A = 0.45
sigma = 0.15
# initialize the envelope
envelope = qcs.ArbitraryEnvelope.sample(lambda t: sech(t, A, sigma), n_samples=100)
duration = 1
dc_wav = qcs.DCWaveform(duration, envelope, 1)
# initialize the program and add the envelope
program = qcs.Program()
program.add_waveform(dc_wav, awg)
program.render(sample_rate=1e3)
Example 3: functional definition with sine envelope
In this example we implement a sinusoidal envelope. The envelope \(E(t)\) is given by
where \(A\) is the amplitude, \(f_{\mathrm{int}}\) is the frequency, and \(\phi\) is the phase of the envelope. Note that as envelopes do not explicitly depend on time, parameters such as the frequency are unitless and can be specified as integer multiples of periods. Here we specify a sine wave that covers half a period:
[11]:
# define the sine envelope
def sine(input_times, amplitude, freq, phase):
return amplitude * np.sin(2 * np.pi * freq * input_times + phase)
amplitude = 0.4
phase = 0
freq = 0.5
# initialize the envelope
envelope = qcs.ArbitraryEnvelope.sample(
lambda t: sine(t, amplitude, freq, phase), n_samples=100
)
duration = 1
dc_wav = qcs.DCWaveform(duration, envelope, amplitude)
# initialize the program and add the envelope
program = qcs.Program()
program.add_waveform(dc_wav, awg)
program.render(sample_rate=1e3)
Download
Download this file as Jupyter notebook: envelopes.ipynb.