Download

Download this file as Jupyter notebook: pulse_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 DCWaveforms to demonstrate the envelopes. RF waveforms shows how to generate the more commonly used RFWaveforms 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

\[E(t) + i\beta E'(t),\]

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

\[E(t) = A\text{sech}\left(\frac{t-\mu}{\sigma}\right)\]

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

\[E(t) = A\sin(2\pi f_{\mathrm{int}} t + \phi)\]

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()

Download

Download this file as Jupyter notebook: pulse_envelopes.ipynb.