Cluster (HardwareCompilationConfig)#
Under construction
The HardwareCompilationConfig
replaces the the old-style unvalidated json/dict hardware configuration, adding validation of the contents
and restructuring into "hardware_description"
, "hardware_options"
and "connectivity"
. It is still under construction,
once finished the old-style hardware config will be deprecated but still supported until further notice.
In this section we introduce how to configure Qblox Clusters and the options available for them via Quantify. For information about their lower-level functionality, you can consult the Qblox Instruments documentation. For information on the process of compilation to hardware, see Tutorial: Compiling to Hardware.
General hardware compilation config structure, example#
We start by looking at an example config for a single Cluster. The hardware compilation configuration specifies which modules are used ("hardware_descriptions"
) and how they are connected to the quantum device ("connectivity"
), along with some (optional) "hardware_options"
, like modulation frequencies, gains and attenuations, or mixer corrections. The general structure of this configuration file is described in the Hardware compilation configuration section of the User guide.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QCM"
10 },
11 "2": {
12 "instrument_type": "QCM_RF"
13 },
14 }
15 },
16 "lo0": {
17 "instrument_type": "LocalOscillator",
18 "power": 20
19 },
20 },
21 "hardware_options": {
22 "modulation_frequencies": {
23 "q4:mw-q4.01": {
24 "interm_freq": 200e6
25 },
26 "q5:mw-q5.01": {
27 "interm_freq": 50e6
28 },
29 },
30 "mixer_corrections": {
31 "q4:mw-q4.01": {
32 "amp_ratio": 0.9999,
33 "phase_error": -4.2
34 }
35 },
36 },
37 "connectivity": {
38 "cluster0": {
39 "cluster0_module1": {
40 "complex_output_0": {
41 "lo_name": "lo0",
42 "portclock_configs": [
43 {
44 "clock": "q4.01",
45 "port": "q4:mw",
46 },
47 ]
48 },
49 },
50 "cluster0_module2": {
51 "complex_output_0": {
52 "portclock_configs": [
53 {
54 "clock": "q5.01",
55 "port": "q5:mw"
56 }
57 ]
58 },
59 },
60 },
61 },
62}
Notice the "quantify_scheduler.backends.qblox_backend.hardware_compile"
backend is used.
In the example, the Cluster is specified using an instrument with "instrument_type": "Cluster"
. In the backend, the Cluster instrument functions as a collection of modules.
The only instrument types that can be at the top level are:
"Cluster"
,"LocalOscillator"
.
Hardware description#
To compile to a Cluster, one should include a valid ClusterDescription
in the "hardware_description"
part of the hardware compilation config.
The name of the Cluster (the key of the structure, "cluster0"
in the example) can be chosen freely.
- class ClusterDescription(**data: Any)[source]
Information needed to specify a Cluster in the
CompilationConfig
.Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.
- instrument_type[source]
The instrument type, used to select this datastructure when parsing a
CompilationConfig
.
- modules[source]
Description of the modules of this Cluster, using slot index as key.
- ref[source]
The reference source for the instrument.
- sequence_to_file = False[source]
Write sequencer programs to files for (all modules in this) instrument.
Here the modules are described by their respective ClusterModuleDescription
. For example, a QRM-RF module is described by
- class QRMRFDescription(**data: Any)[source]
Information needed to specify a QRM-RF in the
QbloxHardwareCompilationConfig
.Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.
- complex_input_0[source]
Description of the complex input channel on this QRM, corresponding to port I1.
- complex_output_0[source]
Description of the complex output channel on this QRM, corresponding to port O1.
- digital_output_0[source]
Description of the digital (marker) output channel on this QRM, corresponding to port M1.
- digital_output_1[source]
Description of the digital (marker) output channel on this QRM, corresponding to port M2.
- instrument_type[source]
The instrument type of this module.
- sequence_to_file = False[source]
Write sequencer programs to files, for this module.
Channel-specific settings can be set in the {Complex,Real,Digital}ChannelDescription
datastructures.
For example, for a QRM-RF module, the ComplexChannelDescription
is used to describe the settings for the complex output.
To use the default settings, one can omit the channel description from the ClusterModuleDescription
, as is done in the General hardware compilation config structure, example above.
For a complex input/output, this datastructure is:
- class ComplexChannelDescription(**data: Any)[source]
Information needed to specify an complex input/output in the
QbloxHardwareCompilationConfig
.Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.
- downconverter_freq[source]
Downconverter frequency that should be taken into account when determining the modulation frequencies for this channel. Only relevant for users with custom Qblox downconverter hardware.
- marker_debug_mode_enable = False[source]
Setting to send 4 ns trigger pulse on the marker located next to the I/O port along with each operation. The marker will be pulled high at the same time as the module starts playing or acquiring.
- mix_lo = True[source]
Whether IQ mixing with a local oscillator is enabled for this channel. Effectively always True for RF modules.
Marker configuration#
The markers can be configured by adding a "marker_debug_mode_enable"
key to the ComplexChannelDescription
(or RealChannelDescription
).
If the value is set to True
, the operations defined for this I/O will be accompanied by a 4 ns trigger pulse on the marker located next to the I/O port.
The marker will be pulled high at the same time as the module starts playing or acquiring.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QCM",
10 "complex_output_0": {
11 "marker_debug_mode_enable": True,
12 }
13 }
14 }
15 }
16 },
17 "hardware_options": {...},
18 "connectivity": {...},
19}
Write sequencer program to files#
It is possible to optionally include the "sequence_to_file"
key. If set to True
, a file will be created for each sequencer with the program that’s uploaded to the sequencer with the filename <data_dir>/schedules/<year><month><day>-<hour><minute><seconds>-<milliseconds>-<random>_<port>_<clock>.json
in a JSON format, where <random>
is 6 random characters in the range 0-9
, a-f
. The value defaults to False
in case "sequence_to_file"
is not included.
It is also possible to set this parameter per module via its module configuration.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "sequence_to_file": True,
8 "modules": {...}
9 }
10 },
11 "hardware_options": {...},
12 "connectivity": {...}
13}
Downconverter#
Note
This section is only relevant for users with custom Qblox downconverter hardware.
Some users employ a custom Qblox downconverter module. In order to use it with this backend, we specify a "downconverter_freq"
entry in the outputs that are connected to this module, as exemplified below.
The result is that the clock frequency is downconverted such that the signal reaching the target port is at the desired clock frequency, i.e. \(f_\mathrm{out} = f_\mathrm{downconverter} - f_\mathrm{in}\).
For baseband modules, downconversion will not happen if "mix_lo"
is not True
and there is no external LO specified ("mix_lo"
is True
by default). For RF modules, the "mix_lo"
setting is not used (effectively, always True
). Also see helper function determine_clock_lo_interm_freqs()
.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QCM",
10 "complex_output_0": {
11 "downconverter_freq": 9e9,
12 "mix_lo": True,
13 }
14 },
15 "2": {
16 "instrument_type": "QCM_RF",
17 "complex_output_0": {
18 "downconverter_freq": 9e9,
19 }
20 },
21 }
22 },
23 "lo1": {"instrument_type": "LocalOscillator", "power": 20},
24 },
25 "hardware_options": {
26 "modulation_frequencies": {
27 "q0:mw-q0.01": {
28 "interm_freq": 50e6
29 },
30 },
31 },
32 "connectivity": {
33 "cluster0": {
34 "cluster0_module1": {
35 "complex_output_0": {
36 "portclock_configs": [
37 {
38 "clock": "q0.01",
39 "port": "q0:mw"
40 }
41 ]
42 },
43 },
44 "cluster0_module2": {
45 "complex_output_0": {
46 "portclock_configs": [
47 {
48 "clock": "q0.01",
49 "port": "q0:mw"
50 }
51 ]
52 },
53 },
54 },
55 },
56}
Local Oscillator description#
A local oscillator instrument can be used with baseband modules. After adding the instrument, we can reference it in a baseband-module output via the "lo_name"
key.
The two mandatory parameters are the "instrument_type"
(which should be "LocalOscillator"
), and "power"
. The local oscillator frequency is then controlled through the "modulation_frequencies"
hardware option (see Modulation frequencies), under the key of the port-clock combination associated with the baseband-module output.
Note that it is possible to add "generic_icc_name"
as an optional parameter to the local oscillator hardware description, but only the default name "generic"
is supported currently with the Qblox backend.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {...},
5 "lo1": {
6 "instrument_type": "LocalOscillator",
7 "power": 20
8 },
9 },
10 "hardware_options": {
11 "modulation_frequencies": {
12 "q1:mw-q1.01": {
13 "lo_freq": 5e9
14 }
15 }
16 },
17 "connectivity": {
18 "cluster0": {
19 "cluster0_module1": {
20 "complex_output_1": {
21 "lo_name": "lo1",
22 "portclock_configs": [
23 {
24 "clock": "q1.01",
25 "port": "q1:mw"
26 }
27 ]
28 },
29 },
30 },
31 }
32}
Connectivity#
The Connectivity
describes how the inputs/outputs of the Cluster modules are connected to ports on the QuantumDevice
.
Note
The Connectivity
datastructure is currently under development. Information on the connectivity between port-clock combinations on the quantum device and ports on the control hardware is currently included in the old-style hardware configuration file, which should be included in the "connectivity"
field of the HardwareCompilationConfig
.
The possible inputs/outputs are
for
"QCM"
:"complex_output_{0,1}"
,"real_output_{0,1,2,3}"
,for
"QRM"
:"complex_{output,input}_0"
,"real_{output,input}_{0,1}"
,for
"QCM_RF"
:"complex_output_{0,1}"
,for
"QRM_RF"
:"complex_{output,input}_0"
.
Note
For RF hardware, if an output is unused, it will be turned off. This is to ensure that unused local oscillators do not interfere with used outputs.
Port-clock configuration#
Under these "{complex,real}_{x}"
keys, we specify the port-clock combinations an output may target
(see the Ports and clocks for more information on the role of ports and clocks within quantify-scheduler
).
Each module can have at most 6 port-clock combinations defined, and the name for each "port"
and "clock"
combination must be unique. Each of these port-clock combinations is associated with one sequencer in the Qblox hardware.
Note
If you use gate-level operations, you have to follow strict rules for each kind of operation on which port name you can use (what’s the naming convention for each port resource).
"<device element name>:mw"
forRxy
operation (and its derived operations),"<device element name>:res"
for any measure operation,"<device element name>:fl"
for the flux port.
Frequency multiplexing#
It is possible to do frequency multiplexing of the signals by adding multiple port-clock configurations to the same output.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QCM"
10 },
11 }
12 },
13 },
14 "hardware_options": {},
15 "connectivity": {
16 "cluster0": {
17 "cluster0_module1": {
18 "complex_output_0": {
19 "portclock_configs": [
20 {
21 "port": "q0:mw",
22 "clock": "q0.01",
23 },
24 {
25 "port": "q0:mw",
26 "clock": "some_other_clock",
27 }
28 ]
29 },
30 "complex_output_1": {
31 "portclock_configs": [
32 {
33 "port": "q1:mw",
34 "clock": "q1.01",
35 }
36 ]
37 }
38 },
39 }
40 }
41}
In the given example, we added a second port-clock configuration to output 0. Now any signal on port "q0:mw"
with clock "some_other_clock"
will be added digitally to the signal with the same port but clock "q0.01"
.
The Qblox modules have six sequencers available, which sets the upper limit to our multiplexing capabilities.
Note
The backend requires that each combination of a port and a clock is unique, that is, it is possible to use a certain port or clock multiple times but the combination of a port with a certain clock can only be used once in the hardware compilation config.
Complex I/O#
A complex I/O is defined by adding a "complex_{output, input}_<n>"
to the module configuration.
Complex outputs (e.g. complex_output_0
) are used for playbacks, while complex inputs (e.g. complex_input_0
) are used for acquisitions.
However, for readout modules it is possible to use the complex_output_<n>
key for both playbacks and acquisitions.
Note
It is not possible to use the same port-clock combination multiple times in the hardware compilation config. In that case, it is required to use only the complex_output_<n>
key.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QRM"
10 },
11 }
12 }
13 }
14 "hardware_options": {...},
15 "connectivity": {
16 "cluster0": {
17 "cluster0_module1": {
18 "complex_output_0": {
19 "portclock_configs": [
20 {
21 "port": "q0:mw",
22 "clock": "q0.01",
23 }
24 ]
25 },
26 "complex_output_1": {
27 "portclock_configs": [
28 {
29 "port": "q0:res",
30 "clock": "q0.ro",
31 }
32 ]
33 },
34 "complex_input_0": {
35 "portclock_configs": [
36 {
37 "port": "q1:res",
38 "clock": "q1.ro",
39 }
40 ]
41 }
42 }
43 },
44 }
45}
Real I/O#
A real I/O is defined by adding a real_{output, input}_<n>
to the module configuration.
Real outputs (e.g. real_output_0
) are used for playbacks, while real inputs (e.g. real_input_0
) are used for acquisitions.
However, for readout modules it is possible to use the real_output_<n>
key for both playbacks and acquisitions.
When using a real I/O, the backend automatically maps the signals to the correct output paths.
Note
It is not possible to use the same port-clock combination multiple times in the hardware compilation config. In that case, it is required to use only the real_output_<n>
key.
For a real I/O, it is not allowed to use any pulses that have an imaginary component, i.e., only real valued pulses are allowed. If you were to use a complex pulse, the backend will produce an error, e.g., square and ramp pulses are allowed but DRAG pulses are not.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QRM"
10 },
11 }
12 }
13 }
14 "hardware_options": {...},
15 "connectivity": {
16 "cluster0": {
17 "cluster0_module1": {
18 "real_output_0": {
19 "portclock_configs": [
20 {
21 "port": "q0:mw",
22 "clock": "q0.01",
23 }
24 ]
25 },
26 "real_output_1": {
27 "portclock_configs": [
28 {
29 "port": "q0:res",
30 "clock": "q0.ro",
31 }
32 ]
33 },
34 "real_input_0": {
35 "portclock_configs": [
36 {
37 "port": "q1:res",
38 "clock": "q1.ro",
39 }
40 ]
41 }
42 }
43 },
44 }
45}
Digital I/O#
The markers can be controlled by defining a digital I/O, and adding a MarkerPulse
on this I/O.
A digital I/O is defined by adding a "digital_output_n"
to the module configuration. n
is the number of the digital output port.
For a digital I/O only a port is required, no clocks or other parameters are needed.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {...},
4 "hardware_options": {...},
5 "connectivity": {
6 "cluster0": {
7 "cluster0_module1": {
8 "digital_output_0": {
9 "portclock_configs": [
10 {
11 "port": "q0:switch",
12 },
13 ],
14 },
15 },
16 },
17 }
18}
The MarkerPulse
is defined by adding a MarkerPulse
to the sequence in question. It takes the same parameters as any other pulse.
schedule.add(MarkerPulse(duration=52e-9, port="q0:switch"))
Hardware options#
The QbloxHardwareOptions
datastructure contains the settings used in compiling from the quantum-device layer to a set of instructions for the control hardware.
- class QbloxHardwareOptions(**data: Any)[source]
Datastructure containing the hardware options for each port-clock combination.
Example
Here, the HardwareOptions datastructure is created by parsing a dictionary containing the relevant information.
import pprint from quantify_scheduler.schemas.examples.utils import ( load_json_example_scheme )
from quantify_scheduler.backends.types.qblox import ( QbloxHardwareOptions ) qblox_hw_options_dict = load_json_example_scheme( "qblox_hardware_compilation_config.json")["hardware_options"] pprint.pprint(qblox_hw_options_dict)
{'distortion_corrections': {'q0:fl-cl0.baseband': {'clipping_values': [-2.5, 2.5], 'filter_func': 'scipy.signal.lfilter', 'input_var_name': 'x', 'kwargs': {'a': [1], 'b': [0, 0.25, 0.5]}}}, 'input_att': {'q5:res-q5.ro': 10}, 'input_gain': {'q4:res-q4.ro': {'gain_I': 2, 'gain_Q': 3}}, 'latency_corrections': {'q4:mw-q4.01': 8e-09, 'q5:mw-q5.01': 4e-09}, 'mixer_corrections': {'q4:mw-q4.01': {'amp_ratio': 0.9999, 'phase_error': -4.2}, 'q4:res-q4.ro': {'amp_ratio': 0.9997, 'dc_offset_i': -0.054, 'dc_offset_q': -0.034, 'phase_error': -4.0}}, 'modulation_frequencies': {'q0:mw-q0.01': {'interm_freq': 50000000.0, 'lo_freq': None}, 'q0:res-q0.ro': {'interm_freq': None, 'lo_freq': 7800000000.0}, 'q4:mw-q4.01': {'interm_freq': 200000000.0, 'lo_freq': None}, 'q4:res-q4.ro': {'interm_freq': None, 'lo_freq': 7200000000.0}, 'q5:mw-q5.01': {'interm_freq': 50000000.0, 'lo_freq': None}, 'q5:res-q5.ro': {'interm_freq': 50000000.0}, 'q6:mw-q6.01': {'lo_freq': 5000000000.0}}, 'output_att': {'q0:mw-q0.01': 4, 'q0:res-q0.ro': 12, 'q5:mw-q5.01': 4, 'q6:mw-q6.01': 6}, 'sequencer_options': {'qe0:optical_readout-qe0.ge0': {'ttl_acq_threshold': 0.5}}}
The dictionary can be parsed using the
model_validate
method.qblox_hw_options = QbloxHardwareOptions.model_validate(qblox_hw_options_dict) qblox_hw_options
QbloxHardwareOptions(latency_corrections={'q4:mw-q4.01': 8e-09, 'q5:mw-q5.01': 4e-09}, distortion_corrections={'q0:fl-cl0.baseband': DistortionCorrection(filter_func='scipy.signal.lfilter', input_var_name='x', kwargs={'b': [0, 0.25, 0.5], 'a': [1]}, clipping_values=[-2.5, 2.5])}, modulation_frequencies={'q0:res-q0.ro': ModulationFrequencies(interm_freq=None, lo_freq=7800000000.0), 'q0:mw-q0.01': ModulationFrequencies(interm_freq=50000000.0, lo_freq=None), 'q4:mw-q4.01': ModulationFrequencies(interm_freq=200000000.0, lo_freq=None), 'q4:res-q4.ro': ModulationFrequencies(interm_freq=None, lo_freq=7200000000.0), 'q5:mw-q5.01': ModulationFrequencies(interm_freq=50000000.0, lo_freq=None), 'q5:res-q5.ro': ModulationFrequencies(interm_freq=50000000.0, lo_freq=None), 'q6:mw-q6.01': ModulationFrequencies(interm_freq=None, lo_freq=5000000000.0)}, mixer_corrections={'q4:mw-q4.01': MixerCorrections(dc_offset_i=0.0, dc_offset_q=0.0, amp_ratio=0.9999, phase_error=-4.2), 'q4:res-q4.ro': MixerCorrections(dc_offset_i=-0.054, dc_offset_q=-0.034, amp_ratio=0.9997, phase_error=-4.0)}, input_gain={'q4:res-q4.ro': ComplexInputGain(gain_I=2, gain_Q=3)}, output_att={'q0:mw-q0.01': 4, 'q0:res-q0.ro': 12, 'q5:mw-q5.01': 4, 'q6:mw-q6.01': 6}, input_att={'q5:res-q5.ro': 10}, sequencer_options={'qe0:optical_readout-qe0.ge0': SequencerOptions(init_offset_awg_path_0=0.0, init_offset_awg_path_1=0.0, init_gain_awg_path_0=1.0, init_gain_awg_path_1=1.0, ttl_acq_threshold=0.5, qasm_hook_func=None)})
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.
- distortion_corrections[source]
Dictionary containing the distortion corrections (values) that should be applied to waveforms on a certain port-clock combination (keys).
- input_att[source]
Dictionary containing the attenuation settings (values) that should be applied to the inputs that are connected to a certain port-clock combination (keys).
- input_gain[source]
Dictionary containing the input gain settings (values) that should be applied to the inputs that are connected to a certain port-clock combination (keys).
- latency_corrections[source]
Dictionary containing the latency corrections (values) that should be applied to operations on a certain port-clock combination (keys).
- mixer_corrections[source]
Dictionary containing the mixer corrections (values) that should be used for signals on a certain port-clock combination (keys).
- modulation_frequencies[source]
Dictionary containing the modulation frequencies (values) that should be used for signals on a certain port-clock combination (keys).
- output_att[source]
Dictionary containing the attenuation settings (values) that should be applied to the outputs that are connected to a certain port-clock combination (keys).
- sequencer_options[source]
Dictionary containing the options (values) that should be set on the sequencer that is used for a certain port-clock combination (keys).
Modulation frequencies#
The aim of quantify-scheduler
is to only specify the final RF frequency when the signal arrives at the chip, rather than any parameters related to I/Q modulation. However, you still need to provide some parameters for the up/downconversion.
The backend assumes that upconversion happens according to the relation
These frequencies are specified for each port-clock combination in the "modulation_frequencies"
in the "hardware_options"
.
You can specify \(f_{RF}\) in multiple ways. You can specify it when you add a ClockResource
with freq
argument to your Schedule
, or when you specify the BasicTransmonElement.clock_freqs
.
Note
If you use gate-level operations, you have to follow strict rules for the naming of the clock resource, for each kind of operation:
"<transmon name>.01"
forRxy
operation (and its derived operations),"<transmon name>.ro"
for any measure operation,"<transmon name>.12"
for the \(|1\rangle \rightarrow |2\rangle\) transition.
Then:
For baseband modules, you can optionally specify a local oscillator by its name using the
"lo_name"
key in the Connectivity. If you specify it, the"lo_freq"
key in the"modulation_frequencies"
(see the example below) specifies \(f_{LO}\) of this local oscillator. Otherwise, \(f_{LO} = 0\) and \(f_{RF} = f_{IF}\). \(f_{RF} = f_{IF}\) can also be set in the hardware options explicitly with the"interm_freq"
key in the"modulation_frequencies"
.For RF modules, you can specify \(f_{IF}\) through the
"interm_freq"
key, and/or you can specify the local oscillator frequency for the output used for the port-clock combination with the"lo_freq"
, because they have internal local oscillators. Note, if you specify both, the relationship between these frequencies should hold, otherwise you get an error message. It’s important to note, that fast frequency sweeps only work when \(f_{LO}\) is fixed, and \(f_{IF}\) is unspecified. Because of this, it is generally advised to specify \(f_{LO}\) only.
In the following example:
For the baseband modules,
"complex_output_0"
’s \(f_{IF}\) is the same as the"q0.01"
clock resource’s frequency, and"complex_output_1"
’s \(f_{IF}\) is calculated using the frequency of"lo1"
(specified in"modulation_frequencies"
under"q1:mw-q1.01"
) and"q1.01"
.For the RF modules,
"complex_output_0"
’s \(f_{IF}\) is calculated using the provided"lo_freq"
for"q2:mw-q2.01"
and the frequency of"q2.01"
, and for"complex_output_1"
, the \(f_{LO}\) is calculated using the provided"interm_freq"
for"q3:mw-q3.01"
and the frequency of"q3.01"
.
1from quantify_scheduler import Schedule
2from quantify_scheduler.backends.graph_compilation import SerialCompiler
3from quantify_scheduler.device_under_test.quantum_device import QuantumDevice
4from quantify_scheduler.operations.pulse_library import SquarePulse
5from quantify_scheduler.resources import ClockResource
6
7hardware_compilation_cfg = {
8 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
9 "hardware_description": {
10 "cluster0": {
11 "instrument_type": "Cluster",
12 "ref": "internal",
13 "modules": {
14 "1": {
15 "instrument_type": "QCM"
16 },
17 "2": {
18 "instrument_type": "QCM_RF"
19 },
20 }
21 },
22 "lo1": {"instrument_type": "LocalOscillator", "power": 20},
23 },
24 "hardware_options": {
25 "modulation_frequencies": {
26 "q1:mw-q1.01": {
27 "lo_freq": 5e9
28 },
29 "q2:mw-q2.01": {
30 "lo_freq": 7e9
31 },
32 "q3:mw-q3.01": {
33 "interm_freq": 50e6
34 },
35 },
36 },
37 "connectivity": {
38 "cluster0": {
39 "cluster0_module1": {
40 "complex_output_0": {
41 "portclock_configs": [
42 {
43 "clock": "q0.01",
44 "port": "q0:mw"
45 }
46 ]
47 },
48 "complex_output_1": {
49 "lo_name": "lo1",
50 "portclock_configs": [
51 {
52 "clock": "q1.01",
53 "port": "q1:mw"
54 }
55 ]
56 },
57 },
58 "cluster0_module2": {
59 "complex_output_0": {
60 "portclock_configs": [
61 {
62 "clock": "q2.01",
63 "port": "q2:mw"
64 }
65 ]
66 },
67 "complex_output_1": {
68 "portclock_configs": [
69 {
70 "clock": "q3.01",
71 "port": "q3:mw"
72 }
73 ]
74 },
75 },
76 },
77 },
78}
79
80test_sched = Schedule("test_sched")
81test_sched.add_resource(ClockResource(name="q0.01", freq=8e9))
82test_sched.add_resource(ClockResource(name="q1.01", freq=9e9))
83test_sched.add_resource(ClockResource(name="q2.01", freq=8e9))
84test_sched.add_resource(ClockResource(name="q3.01", freq=9e9))
85
86test_sched.add(SquarePulse(amp=1, duration=1e-6, port="q0:mw", clock="q0.01"))
87test_sched.add(SquarePulse(amp=0.25, duration=1e-6, port="q1:mw", clock="q1.01"))
88test_sched.add(SquarePulse(amp=0.25, duration=1e-6, port="q2:mw", clock="q2.01"))
89test_sched.add(SquarePulse(amp=0.25, duration=1e-6, port="q3:mw", clock="q3.01"))
90
91quantum_device = QuantumDevice("DUT")
92quantum_device.hardware_config(hardware_compilation_cfg)
93compiler = SerialCompiler(name="compiler")
94_ = compiler.compile(
95 schedule=test_sched, config=quantum_device.generate_compilation_config()
96)
Mixer corrections#
The backend also supports setting the parameters that are used by the hardware to correct for mixer imperfections in real-time.
We configure this by adding the "mixer_corrections"
to the hardware options for a specific port-clock combination. See the following example.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {...},
4 "connectivity": {...},
5 "hardware_options": {
6 "mixer_corrections": {
7 "q4:mw-q4.01": {
8 "dc_offset_i": -0.054,
9 "dc_offset_q": -0.034,
10 "amp_ratio": 0.9997,
11 "phase_error": -4.0,
12 }
13 }
14 }
15}
Gain and attenuation#
For QRM, QRM-RF and QCM-RF modules you can set the gain and attenuation parameters in dB in the "hardware_options"
.
Gain configuration#
Note, these parameters only affect the QRM modules. For complex inputs you have to specify a tuple (for the I and Q inputs), and for real inputs a scalar value.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QRM"
10 },
11 "2": {
12 "instrument_type": "QRM"
13 },
14 }
15 },
16 },
17 "hardware_options": {
18 "input_gain": {
19 "q0:res-q0.ro": {
20 "gain_I": 2,
21 "gain_Q": 3
22 }
23 "q0:fl-cl0.baseband": 2
24 },
25 },
26 "connectivity": {
27 "cluster0": {
28 "cluster0_module1": {
29 "complex_input_0": {
30 "portclock_configs": [
31 {
32 "clock": "q0.ro",
33 "port": "q0:res",
34 },
35 ]
36 },
37 },
38 "cluster0_module2": {
39 "real_input_0": {
40 "portclock_configs": [
41 {
42 "clock": "cl0.baseband",
43 "port": "q0:fl"
44 }
45 ]
46 },
47 },
48 }
49 }
50}
Attenuation configuration#
The parameters
"output_att"
and"input_att"
for QRM-RF correspond to the qcodes parameters out0_att and in0_att respectively.The parameter
"output_att"
for QCM-RF correspond to the qcodes parameters out0_att and out1_att.
Note, that these parameters only affect RF modules. See Qblox Instruments: QCM-QRM documentation for allowed values.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {
4 "cluster0": {
5 "instrument_type": "Cluster",
6 "ref": "internal",
7 "modules": {
8 "1": {
9 "instrument_type": "QRM_RF"
10 },
11 "2": {
12 "instrument_type": "QCM_RF"
13 },
14 }
15 },
16 },
17 "hardware_options": {
18 "output_att": {
19 "q0:res-q0.ro": 12,
20 "q0:mw-q0.01": 4
21 },
22 "input_att": {
23 "q0:res-q0.ro": 10
24 }
25 },
26 "connectivity": {
27 "cluster0": {
28 "cluster0_module1": {
29 "complex_output_0": {
30 "portclock_configs": [
31 {
32 "clock": "q0.res",
33 "port": "q0:ro",
34 },
35 ]
36 },
37 },
38 "cluster0_module2": {
39 "complex_output_0": {
40 "portclock_configs": [
41 {
42 "clock": "q0.01",
43 "port": "q0:mw"
44 }
45 ]
46 },
47 },
48 }
49 }
50}
Maximum AWG output voltage#
Note
This subsection on max_awg_output_voltage
is still under construction.
Latency corrections#
Latency corrections is a dict
containing the delays for each port-clock combination. It is possible to specify them under the key "latency_corrections"
in the hardware options. See the following example.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {...},
4 "connectivity": {...},
5 "hardware_options": {
6 "latency_corrections": {
7 "q4:mw-q4.01": 8e-9,
8 "q5:mw-q5.01": 4e-9
9 }
10 }
11}
Each correction is in nanoseconds. For each specified port-clock, the program start will be delayed by this amount of time. Note, the delay still has to be a multiple of the grid time.
Distortion corrections#
Distortion corrections apply a function on the pulses which are in the schedule. Note, that this will not be applied to outputs generated by modifying the offset and gain/attenuation. The "distortion_corrections"
is an optional key in the hardware options. See the following example.
1hardware_compilation_cfg = {
2 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
3 "hardware_description": {...},
4 "connectivity": {...},
5 "hardware_options": {
6 "distortion_corrections": {
7 "q0:fl-cl0.baseband": {
8 "filter_func": "scipy.signal.lfilter",
9 "input_var_name": "x",
10 "kwargs": {
11 "b": [0.0, 0.5, 1.0],
12 "a": [1]
13 },
14 "clipping_values": [-2.5, 2.5]
15 }
16 }
17 }
If "distortion_corrections"
are set, then "filter_func"
, "input_var_name"
and "kwargs"
are required. If "clipping_values"
are set, its value must be a list with exactly 2 floats.
Clipping values are the boundaries to which the corrected pulses will be clipped, upon exceeding, these are optional to supply.
The "filter_func"
is a python function that we apply with "kwargs"
arguments. The waveform to be modified will be passed to this function in the argument name specified by "input_var_name"
. The waveform will be passed as a np.ndarray
.
Sequencer options#
Several options are available that are set on the sequencer that is assigned to a certain port-clock combination.
These can be set by adding "sequencer_options"
to the hardware options.
- class SequencerOptions(**data: Any)[source]
Configuration options for a sequencer.
For allowed values, also see Cluster QCoDeS parameters.
Example
hardware_compilation_config.hardware_options.sequencer_options = { "q0:res-q0.ro": { "init_offset_awg_path_0": 0.1, "init_offset_awg_path_1": -0.1, "init_gain_awg_path_0": 0.9, "init_gain_awg_path_1": 1.0, "ttl_acq_threshold": 0.5 "qasm_hook_func": foo } }
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
__init__ uses __pydantic_self__ instead of the more common self for the first arg to allow self as a field name.
- _init_setting_limits(init_setting)[source]
- init_gain_awg_path_0 = 1.0[source]
Specifies what value the sequencer gain for AWG path 0 will be reset to before the start of the experiment.
- init_gain_awg_path_1 = 1.0[source]
Specifies what value the sequencer gain for AWG path 0 will be reset to before the start of the experiment.
- init_offset_awg_path_0 = 0.0[source]
Specifies what value the sequencer offset for AWG path 0 will be reset to before the start of the experiment.
- init_offset_awg_path_1 = 0.0[source]
Specifies what value the sequencer offset for AWG path 1 will be reset to before the start of the experiment.
- qasm_hook_func[source]
Function to inject custom qasm instructions after the compiler inserts the footer and the stop instruction in the generated qasm program.
- ttl_acq_threshold[source]
Threshold value with which to compare the input ADC values of the selected input path.
QASM hook#
It is possible to inject custom qasm instructions for each port-clock combination (sequencer), see the following example to insert a NOP (no operation) at the beginning of the program at line 0.
1def _func_for_hook_test(qasm: QASMProgram):
2 qasm.instructions.insert(
3 0, QASMProgram.get_instruction_as_list(q1asm_instructions.NOP)
4 )
5
6hardware_compilation_cfg = {
7 "backend": "quantify_scheduler.backends.qblox_backend.hardware_compile",
8 "hardware_description": {...},
9 "hardware_options": {
10 "sequencer_options": {
11 "q0:mw-q0.01": {
12 "qasm_hook_func": _func_for_hook_test,
13 }
14 }
15 },
16 "connectivity": {...}
17}
Long waveform support#
The sequencers in Qblox modules have a sample limit of MAX_SAMPLE_SIZE_WAVEFORMS
per sequencer. For certain waveforms, however, it is possible to use the sequencers more efficiently and using less waveform memory, allowing for longer waveforms. This section explains how to do this, utilizing the StitchedPulse
. Also see Long waveforms via StitchedPulse of Tutorial: Schedules and Pulses.
For a few standard waveforms, the square pulse, ramp pulse and staircase pulse, the following helper functions create a
StitchedPulse
that can readily be added to schedules:
1from quantify_scheduler.operations.pulse_factories import (
2 long_ramp_pulse,
3 long_square_pulse,
4 staircase_pulse,
5)
6
7ramp_pulse = long_ramp_pulse(amp=0.5, duration=1e-3, port="q0:mw")
8square_pulse = long_square_pulse(amp=0.5, duration=1e-3, port="q0:mw")
9staircase_pulse = staircase_pulse(
10 start_amp=0.0, final_amp=1.0, num_steps=20, duration=1e-4, port="q0:mw"
11)
More complex waveforms can be created from the
StitchedPulseBuilder
. This class allows you to construct complex waveforms by stitching together available pulses, and adding voltage offsets in between. Voltage offsets can be specified with or without a duration. In the latter case, the offset will hold until the last operation in theStitchedPulse
ends. For example:
1from quantify_scheduler.operations.pulse_library import RampPulse
2from quantify_scheduler.operations.stitched_pulse import StitchedPulseBuilder
3
4trapezoid_pulse = (
5 StitchedPulseBuilder(port="q0:mw", clock="q0.01")
6 .add_pulse(RampPulse(amp=0.5, duration=1e-8, port="q0:mw"))
7 .add_voltage_offset(path_0=0.5, path_1=0.0, duration=1e-7)
8 .add_pulse(RampPulse(amp=-0.5, offset=0.5, duration=1e-8, port="q0:mw"))
9 .build()
10)
11
12repeat_pulse_with_offset = (
13 StitchedPulseBuilder(port="q0:mw", clock="q0.01")
14 .add_pulse(RampPulse(amp=0.2, duration=8e-6, port="q0:mw"))
15 .add_voltage_offset(path_0=0.4, path_1=0.0)
16 .add_pulse(RampPulse(amp=0.2, duration=8e-6, port="q0:mw"))
17 .build()
18)
Pulses and offsets are appended to the end of the last added operation by default. By specifying the
append=False
keyword argument in theadd_pulse
andadd_voltage_offset
methods, in combination with therel_time
argument, you can insert an operation at the specified time relative to the start of theStitchedPulse
. The example below uses this to generate a series of square pulses of various durations and amplitudes:
1from quantify_scheduler.operations.stitched_pulse import StitchedPulseBuilder
2
3offsets = [0.3, 0.4, 0.5]
4durations = [1e-6, 2e-6, 1e-6]
5start_times = [0.0, 2e-6, 6e-6]
6
7builder = StitchedPulseBuilder(port="q0:mw", clock="q0.01")
8
9for offset, duration, t_start in zip(offsets, durations, start_times):
10 builder.add_voltage_offset(
11 path_0=offset, path_1=0.0, duration=duration, append=False, rel_time=t_start
12 )
13
14pulse = builder.build()
Debug mode compilation#
Debug mode can help with debugging by modifying the compilation process slightly.
If "debug_mode"
key in the compilation configuration is set to True
(False
by default), the formatting of the compiled QASM program is made more human-readable by aligning all labels, instructions, argument lists and comments in the program in columns (same indentation level).
Note that adding indentation worsens performance and has no functional value besides aiding the debugging process.
from quantify_scheduler.backends import SerialCompiler
compiler = SerialCompiler(name="compiler")
compilation_config = quantum_device.generate_compilation_config()
compilation_config.debug_mode = True
_ = compiler.compile(
schedule=test_sched, config=compilation_config
)