2.18. Free Induction Decay - A Real Use Case

The following will give an example of a complex pulse using many of the features discussed in the previous tutorial examles: We will use two channels, parameters and parameter constraints, parameterized measurements and atomic and non-atomic pulse templates. This is based on real experiments. To see another, a bit more artificial example for a pulse setup use case that offers more verbose explanations, see Gate Configuration - A Full Use Case.

We start by creating some atomic pulse templates using PointPT which will be the building blocks for the more complex pulse structure we have in mind.

[1]:
from qupulse.pulses import PointPT, SequencePT, ForLoopPT, RepetitionPT, MappingPT
import qupulse.pulses.plotting
import numpy as np
import sympy as sp
from sympy import sympify as S

channel_names = ['RFX', 'RFY']

S_init = PointPT([(0,        'S_init'),
                  ('t_init', 'S_init')],
                 channel_names=channel_names, identifier='S_init')

meas_wait = PointPT([(0,             'meas'),
                     ('t_meas_wait', 'meas')],
                   channel_names=channel_names)

adprep = PointPT([(0,           'meas'),
                  ('t_ST_prep', 'ST_plus - ST_jump/2', 'linear'),
                  ('t_ST_prep', 'ST_plus + ST_jump/2'),
                  ('t_op',      'op', 'linear')],
                 parameter_constraints=['Abs(ST_plus - ST_jump/2 - meas) <= Abs(ST_plus - meas)',
                                        'Abs(ST_plus - ST_jump/2 - meas)/t_ST_prep <= max_ramp_speed',
                                        'Abs(ST_plus + ST_jump/2 - op)/Abs(t_ST_prep-t_op) <= max_ramp_speed'],
                 channel_names=channel_names, identifier='adprep')

adread = PointPT([(0,           'op'),
                  ('t_ST_read', 'ST_plus + ST_jump/2', 'linear'),
                  ('t_ST_read', 'ST_plus - ST_jump/2'),
                  ('t_meas_start',      'meas', 'linear'),
                  ('t_meas_start + t_meas_duration', 'meas')],
                 parameter_constraints=['Abs(ST_plus - ST_jump/2 - meas) <= Abs(ST_plus - meas)',
                                        'Abs(ST_plus - ST_jump/2 - meas)/t_ST_read <= max_ramp_speed',
                                        'Abs(ST_plus + ST_jump/2 - op)/Abs(t_ST_read-t_op) <= max_ramp_speed'],
                 channel_names=channel_names, identifier='adread',
                 measurements=[('m', 't_meas_start', 't_meas_duration')])

free_induction = PointPT([(0, 'op-eps_J'),
                          ('t_fid', 'op-eps_J')], channel_names=channel_names)

In the next step, we combine our building blocks into more complex pulses step by step. We first define our core functionality pulse template stepped_free_induction. The pulse template pulse surrounds our functionality with pulses to reset/initialize our qubit and allow for data acquisition. We will use pulse in a ForLoopPT looped_pulse to perform a parameter sweep. Our final pulse template experiment repeats this whole thing a number of times to allow for statistical aggregating of measurement data and represents the complete pulse template for our experiment.

[2]:


stepped_free_induction = MappingPT(free_induction, parameter_mapping={'t_fid': 't_start + i_fid*t_step'}, allow_partial_parameter_mapping=True) pulse = SequencePT(S_init, meas_wait, adprep, stepped_free_induction, adread) looped_pulse = ForLoopPT(pulse, loop_index='i_fid', loop_range='N_fid_steps') experiment = RepetitionPT(looped_pulse, 'N_repetitions', identifier='free_induction_decay')
[3]:
print(experiment.parameter_names)
{'max_ramp_speed', 't_meas_start', 'ST_jump', 't_ST_read', 'eps_J', 't_init', 'ST_plus', 'N_repetitions', 't_step', 't_start', 'op', 't_meas_duration', 'S_init', 't_meas_wait', 'N_fid_steps', 't_op', 'meas', 't_ST_prep'}

Let’s use some reasonable (but low) values for our parameters and plot our experiment pulse (we set the number of repeititions of looped_pulse only to 2 so that the plot does not get too stuffed).

Note that we provide numpy arrays of length 2 for some parameters to assign different values for different channels (see also The PointPulseTemplate).

[4]:
%matplotlib notebook

example_values = dict(meas=[0, 0],
                      op=[5, -5],
                      eps_J=[1, -1],
                      ST_plus=[2.5, -2.5],
                      S_init=[-1, -1],
                      ST_jump=[1, -1],
                      max_ramp_speed=0.3,

                      t_init=5,

                      t_meas_wait = 1,

                      t_ST_prep = 10,
                      t_op = 20,

                      t_ST_read = 10,
                      t_meas_start = 20,
                      t_meas_duration=5,

                      t_start=0,
                      t_step=5,
                      N_fid_steps=5, N_repetitions=2)

from qupulse.pulses.plotting import plot

_ = plot(experiment, example_values)

We can clearly make out the many repetitions of our basic functionality pulse and also the varying duration between the voltage peaks due to our parameter sweep (as well as the two-fold repetition of the sweep itself).

Let’s also quickly plot only a single repetition by setting according parameters for our experiment pulse template.

[5]:
example_values['N_fid_steps'] = 1
example_values['N_repetitions'] = 1
example_values['t_start'] = 5

_ = plot(experiment, example_values)

As a last step we will save the pulse and some example parameters so we can use it in other examples.

[6]:
import json
from qupulse.serialization import FilesystemBackend, PulseStorage

pulse_storage = PulseStorage(FilesystemBackend('./serialized_pulses'))

# overwrite all pulses explicitly
pulse_storage.overwrite('adprep', adprep)
pulse_storage.overwrite('S_init', S_init)
pulse_storage.overwrite('adread', adread)
pulse_storage.overwrite('free_induction_decay', experiment)

with open('parameters/free_induction_decay.json', 'w') as parameter_file:
    json.dump(example_values, parameter_file)

print('Successfully saved pulse and example parameters')
Successfully saved pulse and example parameters