Tutorial: Compiling to Hardware
See also
The complete source code of this tutorial can be found in
Compilation and hardware execution.ipynb
Compilation and hardware execution.py
Compilation allows converting the schedules introduced in Tutorial: Schedules and Pulses into a set of instructions that can be executed on the control hardware.
In this notebook we will define an example schedule, demonstrate how to compile it, and run it on a virtual hardware setup.
Schedule definition
We start by defining an example schedule.
from quantify_scheduler import Schedule
from quantify_scheduler.operations.pulse_library import SquarePulse
from quantify_scheduler.resources import ClockResource
sched = Schedule("Simple schedule")
square_pulse = sched.add(
SquarePulse(amp=0.2, duration=1e-6, port="q0:res", clock="q0.ro")
)
readout_clock = ClockResource(name="q0.ro", freq=7e9)
sched.add_resource(readout_clock)
sched
Schedule "Simple schedule" containing (1) 1 (unique) operations.
Hardware configuration
In our example setup, we will use a Qblox Cluster containing a QCM-RF module. To compile the schedule, we will need to provide the compiler with a dictionary detailing the hardware configuration.
Please check the documentation on how to properly create such a configuration for the supported backends:
Creating an example Qblox hardware configuration dictionary
Below we create an example hardware configuration dictionary, for the Qblox backend. In this configuration, we include:
The backend that we want to use (the Qblox backend, in this case).
A Cluster containing a QCM-RF module (in the 2nd slot).
A Local Oscillator.
In the QCM-RF output’s settings, interm_freq
(which stands for Intermediate Frequency or IF) is the frequency with which the device modulates the pulses.
In this case, the internal LO frequency is not specified but is automatically calculated by the backend, such that the relation \(\text{clock} = \text{LO} + \text{IF}\) is respected.
hardware_cfg = {
"backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
"cluster0": {
"ref": "internal",
"instrument_type": "Cluster",
"cluster0_module2": {
"instrument_type": "QCM_RF",
"complex_output_0": {
"lo_freq": None,
"dc_mixer_offset_I": -0.00552,
"dc_mixer_offset_Q": -0.00556,
"portclock_configs": [
{
"mixer_amp_ratio": 0.9998,
"mixer_phase_error_deg": -4.1,
"port": "q0:res",
"clock": "q0.ro",
"interm_freq": 50e6,
}
],
},
},
},
}
Note that, for any experiment, all the required instruments need to be present in the hardware config.
Compilation
Now we are ready to proceed to the compilation stage. This will be done in two steps:
Determine the schedule’s absolute timing
During the schedule’s definition, we didn’t assign absolute times to the operations. Instead, only the duration was defined. In order for the instruments to know how to execute the schedule, the absolute timing of the operations has to be calculated.
Hardware compilation
This step generates:
A set of parameters for each of the control stack’s instruments in order to configure them properly for the execution of the schedule at hand. These parameters typically don’t change during the whole execution of the schedule.
A compiled program (for the instruments that require it) containing instructions that dictate what the instrument must do in order for the schedule to be executed.
We can perform each of these steps via determine_absolute_timing()
and hardware_compile()
, respectively.
We start by setting the directory where the compiled schedule files will be stored, via set_datadir.
from quantify_core.data import handling as dh
dh.set_datadir(
dh.default_datadir()
) # Or: from pathlib import Path; dh.set_datadir(Path.home() / "quantify-data")
Data will be saved in:
/home/docs/quantify-data
from quantify_scheduler.compilation import determine_absolute_timing, hardware_compile
sched = determine_absolute_timing(sched)
compiled_sched = hardware_compile(sched, hardware_cfg=hardware_cfg)
The cell above compiles the schedule, returning a CompiledSchedule
object. This class differs from Schedule
in that it is immutable and contains the compiled_instructions
attribute. We inspect these instructions below.
compiled_sched.compiled_instructions
{'cluster0': {'settings': {'reference_source': 'internal'},
'cluster0_module2': {'seq0': {'seq_fn': '/home/docs/quantify-data/schedules/20220727-211629-124-22928a_q0res_q0.ro.json',
'settings': {'nco_en': True,
'sync_en': True,
'connected_outputs': [0, 1],
'modulation_freq': 50000000.0,
'mixer_corr_phase_offset_degree': -4.1,
'mixer_corr_gain_ratio': 0.9998,
'integration_length_acq': None}},
'settings': {'scope_mode_sequencer': None,
'offset_ch0_path0': -5.52,
'offset_ch0_path1': -5.56,
'offset_ch1_path0': None,
'offset_ch1_path1': None,
'lo0_freq': 6950000000.0,
'lo1_freq': None}}}}
Execution on the hardware
In the compiled schedule, we have all the information necessary to execute the schedule.
In this specific case, only sequencer seq0
of the QCM-RF is needed. The compiled schedule contains the filepath where the sequencer’s program is stored, as well as the QCoDeS parameters that need to be set in the device.
Now that we have compiled the schedule, we are almost ready to execute it with our control setup.
We start by connecting to the control instrument.
from qblox_instruments import Cluster, ClusterType
Cluster.close_all() # Close any open connection to a Cluster instrument
cluster0 = Cluster("cluster0", dummy_cfg={"2": ClusterType.CLUSTER_QCM_RF})
And we attach these instruments to the InstrumentCoordinator
via the appropriate InstrumentCoordinatorComponentBase
component wrapper class.
from quantify_scheduler.instrument_coordinator import InstrumentCoordinator
from quantify_scheduler.instrument_coordinator.components.qblox import ClusterComponent
ic = InstrumentCoordinator("ic")
ic.add_component(ClusterComponent(cluster0))
The InstrumentCoordinator
object is responsible for the smooth and in-concert operation of the different instruments, so that the provided schedule is correctly executed.
Essentially, it “coordinates” the control stack instruments, giving the relevant commands to the different instruments of the control stack at each point in time.
The experiment can now be conducted using the methods of InstrumentCoordinator
:
We prepare the instruments with the appropriate settings and upload the schedule program by calling the
prepare()
method and passing the compiled schedule as argument.We start the hardware execution by calling the
start()
method.
Additionally, the wait_done()
method is useful to wait for the experiment to finish and assure the synchronicity of the python script.
# Set the qcodes parameters and upload the schedule program
ic.prepare(compiled_sched)
# Start the hardware execution
ic.start()
# Wait for the experiment to finish or for a timeout
ic.wait_done(timeout_sec=10)
The InstrumentCoordinator
has two more functions which were not covered in this experiment:
retrieve_acquisition()
- In case the schedule contained acquisitions, this method retrieves the acquired data.stop()
- Stops all running instruments.
We conclude this tutorial with the remark that the schedule used in this tutorial was defined purely in terms of pulses. However, quantify-scheduler also supports the usage of quantum gates in schedules. Given that gates may require different pulses when executed in different quantum devices.
Consequently, when using gates, one requires an additional compilation step, called “Device Compilation”, that converts these gates into pulses that can be interpreted by the backend. This use case will be covered in Tutorial: Operations and Qubits.