Sections and Pulses¶
Sections provide a framework which enables you to bring your
Experiment
from idea to implementation using a structured set of rules
on how and when to play pulses. This framework gives predictable,
reproducible results and provides precise timing, without the need for
you to keep track of details like device sampling rates. Because
sections use experiment signal lines,
the same experiment can be reused on a variety of different hardware by
simply using a new map between experimental and logical signals. In this
chapter, the concepts underpinning sections and pulses are introduced.
Details of the associated timing rules will be covered in the subsequent
Timing Rules chapter.
An example Jupyter notebook that follows this chapter is on our GitHub. Download it and experiment with the alignment and timings of sections and pulses yourself!
And if you are interested to know more about what pulse shapes are available, have a look at our YouTube demo video:
Overview¶
The general idea behind sections and pulses is as follows:
- Sections and pulses can be thought of as “boxes” which have temporal
boundaries on both the left and right, and are defined by their
length
. - The
length
of a section can either be explicitly defined by the user or is automatically calculated so that all the contents of the section will fit within it. - Sections contain either
pulse
commands (play
,reserve
,delay
) OR other sections, never pulses and sections together. - Pulses, pulse commands, and sections use experimental signal lines, and these lines are unavailable for further use until the pulse, pulse command, or section has released them.
- The “boxes” which contain sections and pulses cannot overlap if they share a common signal line.
- The specified
alignment
(more below) of sections is not inherited from any parent or other sections, i.e., all sections have their own independent alignment rules.
Let’s build up an Experiment to demonstrate the concepts of Sections and
Pulses. In the below code, a Gaussian pulse is played on the drive
experimental signal line. The section containing the signal is of a
specified fixed length, and the section specifies that its contents are
aligned to the left.
## A pulse to be used in the Experiment
x90 = pulse_library.gaussian(uid="x90", length=100e-9, amplitude=0.66)
## Define the Experiment
exp = Experiment(
uid="SectionIntro",
signals=[
ExperimentSignal("drive"),
],
)
### The pulse sequence:
## Real time loop
with exp.acquire_loop_rt(uid="RT_shots", count=1):
## Left-aligned section of fixed length
with exp.section(
uid="excitation",
length=2e-6,
alignment=SectionAlignment.LEFT,
):
## Section contents
exp.play(signal="drive", pulse=x90)
Note
Even if no acquisition takes place in a sequence, an acquire_loop_rt
is still required. This is because this loop governs the overall timing
of the pulse sequence by correctly timing and triggering the instruments
used (once a signal map
is specified) and through the repitition_mode
, and the
averaging_mode
(CYCLIC
or SEQUENTIAL
). In fact, acquire_loop_rt
behaves as a special type of section which provides the real time
boundary for the experiment: it may only contain sweeps or sections, not
pulse commands, and its contents are always left-aligned. Explanation
and examples of performing a sweep within the real time acquisition are
included here.
An illustration of the "excitation" section and its contents from the above code:
length
parameter. Sections which have pulses or pulse commands on ExperimentSignal
lines make the lines unavailable for use until the section has finished execution. Multiple pulses and pulse commands are possible within sections on lines within sections, though, and are played in a logical order. Left-alignment dictates that the contents of this section occur "as early as possible" within the section.
Alignment¶
Sections can have a SectionAlignment
of LEFT
or RIGHT
. If the
alignment of the above section is instead chosen to be to the right:
## Right-aligned section of fixed length
with exp.section(
uid="excitation",
length=2e-6,
alignment=SectionAlignment.RIGHT
):
## Section contents
exp.play(signal="drive", pulse=x90)
then a pulse in the section will have its end aligned to the end of the section, instead of the beginning.
Note
If a section length is not specified, then the section’s length will be determined by its contents. In this case, for a single pulse, either LEFT or RIGHT alignment will result in a section with boundaries as show in the next figure.
## Left Aligned section of fixed length
with exp.section(uid="excitation", alignment=SectionAlignment.RIGHT):
## Section contents
exp.play(signal="drive", pulse=x90)
Signal Delays¶
Pulses may be separated from one another on signal lines using delay
.
Let’s add a second pulse on the same ExperimentSignal
line with a 100
ns delay from the first, all contained in a fixed-width, right-aligned
section:
### The pulse sequence:
## Real time loop
with exp.acquire_loop_rt(uid="RT_shots", count=1):
## Right Aligned section of fixed length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
The delay
must have an associated ExperimentSignal
and time. The
section contents are executed in order, as shown in Figure 4.
Now, let’s introduce a second ExperimentSignal
, "drive1" and put it in
the same section.
## Define two different pulses
x90 = pulse_library.gaussian(uid="x90", length=100e-9, amplitude=0.66)
x180 = pulse_library.gaussian(uid="x180", length=200e-9, amplitude=0.66)
## Define the Experiment
exp = Experiment(
uid="RT_Loop_Test",
signals=[
ExperimentSignal("drive"),
ExperimentSignal("drive1")
],
)
### The pulse sequence:
## Real time loop
with exp.acquire_loop_rt(uid="RT_shots", count=1):
## Right-aligned section with 1 us length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
exp.play(signal="drive1", pulse=x180)
exp.delay(signal="drive1", time=50e-9)
exp.play(signal="drive1", pulse=x90)
This pulse sequence is illustrated in the following figure.
Both sets of pulses are played as late as possible in the section, due to the section’s right-alignment. Having multiple pulses in a single section can be convenient if the pulse timings have a fixed relationship to one another, as might be the case with acquisition and readout. For control pulses in complex experiments, calculating the pulse timings within a single section can be cumbersome and difficult to implement. Therefore, let’s see how we can manipulate our pulses by putting them in separate sections.
Multiple and Nested Sections¶
In most experiments, you will make use of more than one section to precisely control the timing of your sequence:
### The pulse sequence:
## Real time loop
with exp.acquire_loop_rt(uid="RT_shots", count=1):
## Right-aligned section with 1 us length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
## Left-aligned section with 500 ns length
with exp.section(
uid="excitation1",
alignment=SectionAlignment.LEFT,
length=500e-9
):
## Section contents
exp.play(signal="drive1", pulse=x180)
exp.delay(signal="drive1", time=50e-9)
exp.play(signal="drive1", pulse=x90)
We can further control the pulse timings and positions by specifying the length of the sections, using alignment, ordering sections, and nesting the sections as subsections of a parent section. By nesting the two excitation sections within a parent section, we can explicitly control their alignment relative to one another:
### The pulse sequence:
## Real time loop
with exp.acquire_loop_rt(uid="RT_shots", count=1):
## Parent section with right alignment
with exp.section(uid="parent", alignment=SectionAlignment.RIGHT):
## Right-aligned section with 1 us length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
## Left-aligned section with 500 ns length
with exp.section(
uid="excitation1",
=SectionAlignment.LEFT, length=500e-9):
#Section contents
exp.play(signal="drive1", pulse=x180)
exp.delay(signal="drive1", time=50e-9)
exp.play(signal="drive1", pulse=x90)
drive1
is not in use until the excitation1
section begins.
Reusing Sections¶
Sections can be reused with with
… as section_name:
and
exp.add(section = section_name)
:
## Parent section with right alignment
with exp.section(uid="parent", alignment=SectionAlignment.RIGHT):
## Right-aligned section with 1 us length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
## Left-aligned section with 500 ns length
with exp.section(
uid="excitation1",
alignment=SectionAlignment.LEFT,
length=500e-9
) as excitation1:
#Section contents
exp.play(signal="drive1", pulse=x180)
exp.delay(signal="drive1", time=50e-9)
exp.play(signal="drive1", pulse=x90)
exp.add(section = excitation1)
exp.add(section = excitation1)
or equivalently with the Section
class in the following way:
my_section = Section(uid="my_section", alignment=SectionAlignment.LEFT,length=500e-9)
my_section.play(signal="drive1", pulse=x180)
my_section.delay(signal="drive1", time=50e-9)
my_section.play(signal="drive1", pulse=x90)
exp.add(section = my_section)
Note
exp.add(section = my_section)
takes a section object, not the uid
string of a section as an argument.
exp.add(section = section_name)
. The length of the parent section is not specified and is determined by the nested sections.
The play_after
Command¶
If we’d like to have the "excitation1" sections played after the
"excitation" section, we can use the play_after
command:
## Parent section with right alignment
with exp.section(uid="parent", alignment=SectionAlignment.RIGHT):
## Right-aligned section with 1us length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
## Left-aligned section with 500 ns length
with exp.section(
uid="excitation1",
alignment=SectionAlignment.LEFT,
length=500e-9,
play_after="excitation"
) as excitation1:
#Section contents
exp.play(signal="drive1", pulse=x180)
exp.delay(signal="drive1", time=50e-9)
exp.play(signal="drive1", pulse=x90)
exp.add(section = excitation1)
exp.add(section = excitation1)
play_after
command can be used to time sections relative to one another.
Reserving Signals¶
We can reserve
experimental signal lines so that they may not be used
until the section in which they are reserved is no longer active. Here,
we reserve the "drive1" line used in the "excitation" section, and so
both "drive" and "drive1" are included in the "excitation" section:
## Parent section with right alignment
with exp.section(uid="parent", alignment=SectionAlignment.RIGHT):
## Right-aligned section with 1 us length
with exp.section(
uid="excitation",
alignment=SectionAlignment.RIGHT,
length=1e-6
):
## Section contents
exp.play(signal="drive", pulse=x90)
exp.delay(signal="drive", time=100e-9)
exp.play(signal="drive", pulse=x90)
exp.reserve(signal="drive1")
## Left-aligned section with 500 ns length
with exp.section(
uid="excitation1",
alignment=SectionAlignment.LEFT,
length=500e-9
) as excitation1:
## Section contents
exp.play(signal="drive1",pulse=x180)
exp.delay(signal="drive1",time=50e-9)
exp.play(signal="drive1",pulse=x90)
exp.add(section = excitation1)
reserve
command is used to include signal lines in sections even when no pulse commands are assigned to them.
Note
In the above examples, play_after
and exp.reserve
result in the same
relative timing of pulses, but they are not equivalent. It is best
practice to use play_after
to control the order of sections, while
reserve
might be used to prevent pulses being played on signal lines,
where those pulses might interfere with other controls or measurements.