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.
exp.play(signal=my_signal, 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
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):
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)
exp.play(signal=my_signal, 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 exp.play()
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!
exp.play(..., phase=phase)
exp.play(..., 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:
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.
exp.play("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, which then applies the phase increment during the sampling process.
Note
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 exp.case(0):
exp.play(signal="the_signal", pulse=None, increment_oscillator_phase=1.0)
with exp.case(1):
pass
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):
pass
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 the start of each 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):
pass
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 by adding an offset to the oscillator phase. The waveform is typically not affected by this.
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.