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!
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)
Even if no acquisition takes place in a sequence, an |
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.
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 Figure 5:
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", 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)
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)
|
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.
In the above examples, |