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. 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=200e-9, 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, pre_delay=20e-9)
program.add_delay(20e-9, awg)
# render
program.render()
Gaussian Envelope#
In this example we define a DC waveform with a Gaussian envelope with a duration of 320 ns and standard deviation of 80 ns.
[4]:
# initialize a Gaussian
envelope = qcs.GaussianEnvelope(num_sigma=2)
dc_wav = qcs.DCWaveform(320e-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 demonstrate a flat-top Gaussian envelope. During the rise and fall
times, 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 can render the
waveform with different envelope parameters to visualize the varying waveforms used
in a parameter-sweep program
[5]:
# initialize parameters
duration = qcs.Scalar("duration", value=40e-9, dtype=float)
amplitude = qcs.Scalar("amplitude", value=0.8, dtype=float)
rise = qcs.Scalar("rise", value=10e-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 create_dc_flattop method
program = qcs.Program()
program.add_waveform(
qcs.DCWaveform.create_dc_flattop(
rise_duration=rise,
hold_duration=duration,
fall_duration=rise,
envelope=qcs.GaussianEnvelope(),
amplitude=amplitude,
),
awg,
)
# render
program.render()
We now change the parameters defining the envelope and re-render it.
[6]:
duration.value = 60e-9
amplitude.value = 0.6
rise.value = 20e-9
# render
program.render()
We can also create a flattop with different durations for the rise and fall times.
[7]:
program = qcs.Program()
program.add_waveform(
qcs.DCWaveform.create_dc_flattop(
rise_duration=rise,
hold_duration=duration,
fall_duration=50e-9,
envelope=qcs.GaussianEnvelope(),
amplitude=amplitude,
),
awg,
)
# 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.
[8]:
base_envelope = qcs.GaussianEnvelope(num_sigma=5)
envelope = qcs.DerivativeEnvelope(base_envelope)
dc_wav = qcs.DCWaveform(200e-9, envelope, 0.7)
# initialize the program and add the envelope
program = qcs.Program()
program.add_waveform(dc_wav, awg)
# render
program.render()
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\).
[9]:
# initialize a 200ns Gaussian RF Waveform.
# We use a frequency of zero to highlight the shape of the envelope.
gauss = qcs.RFWaveform(
duration=200e-9, envelope=qcs.GaussianEnvelope(), amplitude=0.8, rf_frequency=0
)
# initialize the program and add the drag pulse with a coefficient of 0.1
program = qcs.Program()
program.add_waveform(gauss.drag(0.1), awg)
# render
program.render(plot_imaginary=True)
Arbitrary envelopes#
An arbitrary envelope allows a user to specify any shape of pulse.
Example 1: linear interpolation#
Arbitrary envelopes can be specified with a list of sample times and corresponding
amplitudes. When rendering the envelope, linear interpolation is applied between
specified points. In this example we implement an envelope that ramps from zero to
the maximum value during the interval [0, 0.2]
. The amplitude then falls back
to zero on the interval [0.7, 1]
. This example uses a scale from 0 to 1 for
both sample times and amplitudes. The final duration and amplitude is then
determined by the duration and amplitude parameters used when constructing
the waveform.
[10]:
# specify the samples for linear interpolation
times = [0, 0.2, 0.7, 1]
samples = [0, 1, 1, 0]
# initialize the envelope
envelope = qcs.ArbitraryEnvelope(times, samples)
# Initialize the waveform
duration = qcs.Scalar("duration", 100e-9, dtype=float, unit="s")
amplitude = 0.6
dc_wav = qcs.DCWaveform(duration, envelope, amplitude)
# initialize the program and add the waveform
program = qcs.Program()
program.add_waveform(dc_wav, awg)
# render
program.render()
Note that this arbitrary envelope will keep the same shape even if we used a different duration for the waveform. That is, the relative proportions between the rise, hold, and fall times will remain constant.
[11]:
duration.value = 200e-9
# render
program.render()
Alternatively, we could control the relative proportions of the rise, hold and fall times by constructing a flat-top waveform that uses an Arbitrary envelope.
[12]:
# specify the samples for linear interpolation
times = [0, 0.5, 1]
samples = [0, 1, 0]
# initialize the arbitrary envelope
envelope = qcs.ArbitraryEnvelope(times, samples)
# Create a flattop waveform,
# This splits the envelope in half, and specifies times for rise, hold, and fall.
flattop_wav = qcs.DCWaveform.create_dc_flattop(
50e-9, 100e-9, 20e-9, envelope, amplitude
)
program = qcs.Program()
program.add_waveform(flattop_wav, awg)
program.render()
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.
[13]:
# 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 = 100e-9
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()
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:
[14]:
# 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 = 100e-9
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()