2.7. Conditional Execution

The qctoolkit is desinged to support conditional execution of pulse (segments). This allows pulse designers to play back different waveforms depending on, e.g., environment data or state measurements of the quantum dot. Conditional execution may be implemented via trigger-based jumps to instructions directly on the playback device or emulated in software by choosing which pulse to send to the hardware for playback, if the hardware itself does not support branching.

Since the decision whether a condition will be evaluated software- or hardware-based depends on the hardware setup of the experiment and not on the pulse template the qctoolkit relies on an indirection scheme for conditions which is similar to the handling of parameters, as you will see in the following.

qctoolkit offers two PulseTemplate subclasses for conditional execution: LoopPulseTemplate and BranchPulseTemplate (Note: BranchPulseTemplate is currently pending implementation (`issue 22 <https://github.com/qutech/qc-toolkit/issues/22>`__)).

LoopPulseTemplate takes an identifier for a condition and a subtemplate which is repeated as long as the condition evaluates to True. Let’s assume that we want to construct a pulse that waits until some initialization is completed using a TablePulseTemplate representing a zero-pulse of length 5 and a LoopPulseTemplate:

In [1]:
from qctoolkit.pulses import LoopPulseTemplate, TablePulseTemplate

wait_template = TablePulseTemplate()
wait_template.add_entry(5, 0)

loop_template = LoopPulseTemplate('initialization_in_progress', wait_template)

loop_template is now configured to evaluate a condition called ‘initialization_in_progress’. How this condition is implemented needs not to be specified to declare pulse templates.

We will now look into the actual implementation of conditions. The abstract interface of conditions is the Condition class. As mentioned in the beginning, conditions can be evaluated software- and hardware-based. The classes SoftwareCondition and HardwareCondition represent this in the qctoolkit. Note that these classes don’t do the actual evaluation but encapsulate it against the Sequencer and are used to generate correct sequences for both cases. Instances of Conditions are passed directly into the Sequencer and mapped to the PulseTemplates via the identifier, similar to parameters.

2.7.1. Software-Based Conditions

SoftwareCondition takes a callback function which is called to evaluate the condition (and thus must return a boolean value). During the sequencing process, this function will be called. If the return value is True, the subtemplate will be included in the instruction sequence and another evaluation will be made until the return value is False. The generated instruction sequence will thus contain a number of repetitions of the subtemplate but no actual jumping instructions, the loop is essentially unrolled. The callback function is passed an integer value indicating the current iteration of the evaluation.

As an example, we could use this to repeat the wait_template a fixed number of times, say 5, as follows:

In [2]:
from qctoolkit.pulses import SoftwareCondition, Sequencer

constant_repeat_condition = SoftwareCondition(lambda x: x < 5)
conditions = {'initialization_in_progress': constant_repeat_condition}

s = Sequencer()
parameters = {}
s.push(loop_template, parameters, conditions)
instructions = s.build()
print(instructions)
[<qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007870C50>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007870DA0>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007870E10>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007870E80>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x0000000007870EF0>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x0000000007870F28>]

We obtain an instruction sequence that repeats an execution instruction (for the wait_template waveform) five times. This is, of course, a very simple example. The callback function passed into the SoftwareCondition instance will more likely evaluate some measured data. Since this might not always be available in the sequencing pass, the callback may return None, which will interrupt the sequencing process similar to the `requires_stop method of the Parameter class <05Parameters.ipynb>`__.

2.7.2. Hardware-Based Conditions

Since software-based evaluation of conditions is slow and might interrupt the sequencing process, it might not always be applicable. If supported by the hardware, hardware-based condition evaluation is preferrable. For the sequencing process, this is represented by instances of HardwareCondition, which only take some identifier of the hardware trigger. This must first be obtained from the hardware currently not implemented and is embedded in the generated instruction sequence which will contain jump instructions.

Assuming we have a hardware setup with a trigger that fires continuously until temperature threshold is reached and we want our pulse from above to repeat as long as the trigger fires. We can realize this as follows:

In [3]:
from qctoolkit.pulses import HardwareCondition, Trigger

# stub representation for the trigger which must be obtained from the hardware in a real use case
temperature_trigger = Trigger()

temperature_trigger_condition = HardwareCondition(temperature_trigger)
conditions = {'initialization_in_progress': temperature_trigger_condition}

s = Sequencer()
parameters = {}
s.push(loop_template, parameters, conditions)
instructions = s.build()
print(instructions)
[<qctoolkit.pulses.instructions.CJMPInstruction object at 0x000000000787F080>, <qctoolkit.pulses.instructions.STOPInstruction object at 0x000000000787F160>, <qctoolkit.pulses.instructions.EXECInstruction object at 0x000000000787F128>, <qctoolkit.pulses.instructions.GOTOInstruction object at 0x000000000787F1D0>]

As you can see in the output, the sequencing process now procudes instructions that perform conditional jumps and returns with goto instructions. The details are omitted here, suffice it to say that the trigger is embedded in the conditional jump instruction and this sequence will loop on the hardware as long as the trigger fires.