Skip to content

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 and measurement rules will be covered in the subsequent Timing Rules and Measurement Rules chapters.

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:

Figure 1: An illustration of a left-aligned section. The left and right boundaries of the section and pulse are determined by their 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.

Figure 2: An illustration of a right-aligned section. The pulse position is now determined from the end of the section, not the beginning. The section contents are executed "as late as possible".

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)

Figure 3: An illustration of a pulse in a section, where the length of the section is not explicitly specified and is therefore determined by its contents.

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.

Figure 4: A 1 μs long section where the contents, two 100 ns long pulses separated by a 100 ns long delay time are played as late as possible, i.e., are right-aligned, in the section.

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.

Figure 5: A 1 μs long section with two experiment signal lines. The sequence of pulse commands on each line obey the right-alignment of the section.

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)

Figure 6: Two sections each containing one signal line. The first section is right-aligned and the second is left-aligned. Reminder: Because both sections are contained directly within a real time loop, they are left-aligned.

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)

Figure 7: The same sections as before, now nested within a right-aligned parent section. Note that the bottom signal line, 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.

Figure 8: An illustration which shows how sections can be nested and reused with 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)

Figure 9: An illustration which shows how the 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)

Figure 10: An illustration which shows how the 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.