Skip to content

Phase and Amplitude of Pulses

The following section explains the details of the technical behavior of pulses in LabOne Q.

Pulse playback

The most well-known way to play a pulse with any phase or amplitude is to specify them directly in the play command., pulse=my_pulse, amplitude=0.8, phase=pi/2)

For complex signals, a non-zero phase phase multiplies the sampled waveform my_pulse with the following modulation

\[ \exp\left(-j\phi\right) \]

This is a direct consequence of the mixer sign convention employed in LabOne Q.

Mixer sign convention

LabOne Q uses the negative mixer sign convention for the entire signal processing. This means that for realising a signal in the upper sideband, the waveform samples need the following modulation (note the negative sign of the exponent):

\[ \exp\left(-j\omega t\right) \]

This convention is consistent with the behavior of most physical IQ mixers. When feeding the real and imaginary parts of the waveform to a real-world mixer, the upconverted signal then lies in the upper sideband of the carrier for positive \(\omega\).

For real-valued RF signals, LabOne Q discards the imaginary part of a complex signal.

Pulse definition

When defining a functional pulse from the pulse_library, you can specify an overall amplitude (the so-called pulse amplitude) already at this stage.

my_pulse = pulse_library.const(amplitude=0.5)
#                              ^^^^^^^^^^^^^

If the amplitude is also specified additionally in the pulse command play, LabOne Q will use their product when sampling the pulse.

my_pulse = pulse_library.const(amplitude=0.5), pulse=my_pulse, amplitude=0.8)

This will play a pulse with 0.5 × 0.8 = 0.4 FSR amplitude.

Naturally, phase and amplitude only take effect within the current play() command.

Complex amplitudes

The amplitude in may be a complex number. This also affects the phase of the pulse that is played.

The following examples are equivalent:

phase = np.pi / 6.0
phasor = np.exp(-1.0j * phase)  # Note the sign!, phase=phase), amplitude=phasor)

The effect of a complex amplitude is equivalent to multiplying a sampled pulse by that same amplitude. Hence again, in accordance with the sign convention, the exponent is negative for a positive phase shift.

SW Oscillator phase

For SW oscillators, LabOne Q will compute the phase of that oscillator at the start of the pulse playback. It will then add that phase on top of any other specified phase (via amplitude=... or phase=...) when computing the samples of the pulse.

Mathematically, the effect of the SW carrier is a multiplication of the baseband samples by the following exponential:

\[ \exp\left(-j\underbrace{\omega (t - t_\mathrm{ref})}_{\phi_\mathrm{osc}}\right) \]

Phase reset by averaging loop

A phase reset is defined as an operation that forces \(\phi_\mathrm{osc}\) to zero, by redefining \(t_\mathrm{ref}\).

The oscillator's phase is accumulated since the last time the oscillator's phase was reset. LabOne Q can of course only determine this if the timing of the pulse vs this reference time is unambiguous. For this reason, the beginning of the averaging loop is always implicitly assumed to reset the phases of all SW oscillators, even with reset_oscillator_phase=False.

# "the_signal" is SW modulated
exp.signals["the_signal"].oscillator.modulation_type = ModulationType.SOFTWARE

with exp.acquire_loop_rt(count):
    # Every iteration of the averaging loop resets the oscillator phase.
    # On the oscilloscope, every iteration will look identical, and the
    # phase is not continuous."the_signal", the_pulse)

This requirement ensures that we can reuse the same waveform across all iterations, and roll the loop in SeqC. If the phase were required to be continuous, we would need a new waveform for every iteration, unless the repetition length happened to be commensurate with the oscillator frequency.

Phase reset by sweep loops

By default, the phase of the software oscillator is continuous across sweep loop iterations.

The phase can optionally be reset at the beginning of a sweep loop by passing the reset_oscillator_phase=True argument to the definition of the sweep.

Acquire and measure pulses in integration mode are an exception. Only the relative phase between measure pulse and integration weights matters; their absolute phase is irrelevant. The oscillator phase of such pulses is thus always set to zero at the start of every readout sequence. All readout pulses (and integration weights) use the same waveform, regardless of their absolute timing.

Setting absolute oscillator phase

The set_oscillator_phase argument for software oscillators in play() simply forces the oscillator to a certain phase. Note, that set_oscillator_phase=0 is equivalent to a phase reset and that in contrast to the phase argument of the same play() command, the set_oscillator_phase causes a persistent phase change. Put differently, set_oscillator_phase affects all subsequent play() commands up to the next persistent phase change.

Incrementing oscillator phase

The increment_oscillator_phase applies a persistent increment to the phase. In contrast to the set_oscillator_phase option described above, the effect of the increment_oscillator_phase can be reversed by applying a suitable decrement.

For SW oscillators all phase increments are tracked by the compiler and which then applies the phase increment during the sampling process.


The total phase increment is statically determined beforehand and e.g. cannot depend on conditional changes as would be the case in the following example

with exp.match(...):
   with"the_signal", pulse=None, increment_oscillator_phase=1.0)

HW Oscillator phase

Hardware oscillators differ from the software oscillators discussed above in subtle but important aspects.

Phase resets

The absolute phase of a HW oscillator does not need to be part of the waveform. You are thus free to maintain a continuous phase across all iterations of the averaging loops.

To reset the phase explicitly, say to line up two oscillators with incommensurate frequencies, the reset_oscillator_phase option can be used in the sweep or the averaging loop.

with exp.acquire_loop_rt(count, reset_oscillator_phase=True):

Compared to the instant phase reset in software oscillators, hardware oscillators take some time to reset their phase after the command is issued. This delay is instrument-specific and LabOne Q will automatically insert it before each start of the loop iteration. This waiting time is not part of the body of the loop and can therefore cause repetition times larger than the loop body itself.

If the subsequent example is executed on a HDAWG, the loop repetition period would be 1 us + 80 ns. Specifying the repetition rate through repetition_time =... properly takes oscillator resets into account.

with exp.acquire_loop_rt(count, reset_oscillator_phase=True):
    # Oscillator reset happens here, before entering the 'body' section
    with exp.section(uid="body", length=1e-6):

By comparison, no delays are inserted if the experiment uses software oscillators only.

Incrementing oscillator phase

Incrementing the phase of a hardware oscillator using the increment_oscillator_phase option will actually not affect the phase of this oscillator directly. Instead, for instruments that support phase offsets via the command table (i.e. HDAWG and SHFSG) LabOne Q will rotate the baseband of the pulse. Other instruments which do not support phase offsets via the command table, still require the waveform to contain the phase information and also do not allow conditional phase increments.

Setting absolute oscillator phase

set_oscillator_phase is not allowed on hardware oscillators.

Pulse replacement

When replacing a pulse, the controller will run a 'lightweight' version of the compiler to recompute the waveforms. Instead of a full recompilation of the DSL, the controller makes use of a look-up table, the pulse-waveform map, emitted by the compiler as part of the compiled experiment. It contains the position of a given pulse in each waveform and the required transformations of the amplitude and phase.