# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""Compiler classes for Qblox backend."""
from __future__ import annotations
from collections import abc
from typing import Any, Dict, Optional, Tuple
from quantify_scheduler.backends.qblox import compiler_abc, compiler_container
from quantify_scheduler.backends.qblox.constants import (
NUMBER_OF_SEQUENCERS_QCM,
NUMBER_OF_SEQUENCERS_QRM,
)
from quantify_scheduler.backends.types.qblox import (
BoundedParameter,
LOSettings,
StaticHardwareProperties,
)
[docs]
class LocalOscillator(compiler_abc.InstrumentCompiler):
"""
Implementation of an :class:`~quantify_scheduler.backends.qblox.compiler_abc.InstrumentCompiler` that compiles for a generic LO. The main
difference between this class and the other compiler classes is that it doesn't take
pulses and acquisitions.
Parameters
----------
parent
Reference to the parent container object.
name
QCoDeS name of the device it compiles for.
total_play_time
Total time execution of the schedule should go on for. This parameter is
used to ensure that the different devices, potentially with different clock
rates, can work in a synchronized way when performing multiple executions of
the schedule.
instrument_cfg
The part of the hardware mapping dict referring to this instrument.
"""
def __init__(
self,
parent: compiler_container.CompilerContainer,
name: str,
total_play_time: float,
instrument_cfg: Dict[str, Any],
):
def _extract_parameter(
parameter_dict: Dict[str, Optional[float]]
) -> Tuple[str, Optional[float]]:
items: abc.ItemsView = parameter_dict.items()
return list(items)[0]
super().__init__(
parent=parent,
name=name,
total_play_time=total_play_time,
instrument_cfg=instrument_cfg,
)
self._settings = LOSettings.from_mapping(instrument_cfg)
self.freq_param_name, self._frequency = _extract_parameter(
self._settings.frequency
)
self.power_param_name, self._power = _extract_parameter(self._settings.power)
@property
[docs]
def frequency(self) -> Optional[float]:
"""
Getter for the frequency.
Returns
-------
:
The current frequency.
"""
return self._frequency
@frequency.setter
def frequency(self, value: float):
"""
Sets the lo frequency for this device if no frequency is specified, but raises
an exception otherwise.
Parameters
----------
value
The frequency to set it to.
Raises
------
ValueError
Occurs when a frequency has been previously set and attempting to set the
frequency to a different value than what it is currently set to. This would
indicate an invalid configuration in the hardware mapping.
"""
if self._frequency is not None:
if value != self._frequency:
raise ValueError(
f"Attempting to set LO {self.name} to frequency {value}, "
f"while it has previously already been set to "
f"{self._frequency}!"
)
self._frequency = value
[docs]
def compile(self, debug_mode, repetitions: int = 1) -> Optional[Dict[str, Any]]:
"""
Compiles the program for the LO InstrumentCoordinator component.
Parameters
----------
debug_mode
Debug mode can modify the compilation process,
so that debugging of the compilation process is easier.
repetitions
Number of times execution the schedule is repeated.
Returns
-------
:
Dictionary containing all the information the InstrumentCoordinator
component needs to set the parameters appropriately.
"""
if self._frequency is None:
return None
return {
f"{self.name}.{self.freq_param_name}": self._frequency,
f"{self.name}.{self.power_param_name}": self._power,
}
[docs]
class QcmModule(compiler_abc.QbloxBasebandModule):
"""QCM specific implementation of the qblox compiler."""
[docs]
supports_acquisition: bool = False
[docs]
static_hw_properties: StaticHardwareProperties = StaticHardwareProperties(
instrument_type="QCM",
max_sequencers=NUMBER_OF_SEQUENCERS_QCM,
max_awg_output_voltage=2.5,
mixer_dc_offset_range=BoundedParameter(min_val=-2.5, max_val=2.5, units="V"),
channel_name_to_connected_io_indices={
"complex_output_0": (0, 1),
"complex_output_1": (2, 3),
"real_output_0": (0,),
"real_output_1": (1,),
"real_output_2": (2,),
"real_output_3": (3,),
"digital_output_0": (0,),
"digital_output_1": (1,),
"digital_output_2": (2,),
"digital_output_3": (3,),
},
)
# pylint: disable=invalid-name
[docs]
class QrmModule(compiler_abc.QbloxBasebandModule):
"""QRM specific implementation of the qblox compiler."""
[docs]
supports_acquisition: bool = True
[docs]
static_hw_properties: StaticHardwareProperties = StaticHardwareProperties(
instrument_type="QRM",
max_sequencers=NUMBER_OF_SEQUENCERS_QRM,
max_awg_output_voltage=0.5,
mixer_dc_offset_range=BoundedParameter(min_val=-0.5, max_val=0.5, units="V"),
channel_name_to_connected_io_indices={
"complex_output_0": (0, 1),
"complex_input_0": (0, 1),
"real_output_0": (0,),
"real_output_1": (1,),
"real_input_0": (0,),
"real_input_1": (1,),
"digital_output_0": (0,),
"digital_output_1": (1,),
"digital_output_2": (2,),
"digital_output_3": (3,),
},
)
[docs]
class QcmRfModule(compiler_abc.QbloxRFModule):
"""QCM-RF specific implementation of the qblox compiler."""
[docs]
supports_acquisition: bool = False
[docs]
static_hw_properties: StaticHardwareProperties = StaticHardwareProperties(
instrument_type="QCM_RF",
max_sequencers=NUMBER_OF_SEQUENCERS_QCM,
max_awg_output_voltage=None,
mixer_dc_offset_range=BoundedParameter(min_val=-50, max_val=50, units="mV"),
channel_name_to_connected_io_indices={
"complex_output_0": (0, 1),
"complex_output_1": (2, 3),
"digital_output_0": (0,),
"digital_output_1": (1,),
},
channel_name_to_digital_marker={
"complex_output_0": 0b0001,
"complex_output_1": 0b0010,
},
default_marker=0b0011,
)
[docs]
class QrmRfModule(compiler_abc.QbloxRFModule):
"""QRM-RF specific implementation of the qblox compiler."""
[docs]
supports_acquisition: bool = True
[docs]
static_hw_properties: StaticHardwareProperties = StaticHardwareProperties(
instrument_type="QRM_RF",
max_sequencers=NUMBER_OF_SEQUENCERS_QRM,
max_awg_output_voltage=None,
mixer_dc_offset_range=BoundedParameter(min_val=-50, max_val=50, units="mV"),
channel_name_to_connected_io_indices={
"complex_output_0": (0, 1),
"complex_input_0": (0, 1),
"digital_output_0": (0,),
"digital_output_1": (1,),
},
default_marker=0b0011,
)
[docs]
class Cluster(compiler_abc.ControlDeviceCompiler):
"""
Compiler class for a Qblox cluster.
Parameters
----------
parent
Reference to the parent object.
name
Name of the `QCoDeS` instrument this compiler object corresponds to.
total_play_time
Total time execution of the schedule should go on for.
instrument_cfg
The part of the hardware configuration dictionary referring to this device. This is one
of the inner dictionaries of the overall hardware config.
latency_corrections
Dict containing the delays for each port-clock combination. This is
specified in the top layer of hardware config.
"""
[docs]
compiler_classes: Dict[str, type] = {
"QCM": QcmModule,
"QRM": QrmModule,
"QCM_RF": QcmRfModule,
"QRM_RF": QrmRfModule,
}
"""References to the individual module compiler classes that can be used by the
cluster."""
[docs]
supports_acquisition: bool = True
"""Specifies that the Cluster supports performing acquisitions."""
def __init__(
self,
parent: compiler_container.CompilerContainer,
name: str,
total_play_time: float,
instrument_cfg: Dict[str, Any],
latency_corrections: Optional[Dict[str, float]] = None,
):
super().__init__(
parent=parent,
name=name,
total_play_time=total_play_time,
instrument_cfg=instrument_cfg,
latency_corrections=latency_corrections,
)
self.instrument_compilers: dict = self.construct_instrument_compilers()
self.latency_corrections = latency_corrections
[docs]
def construct_instrument_compilers(self) -> Dict[str, compiler_abc.QbloxBaseModule]:
"""
Constructs the compilers for the modules inside the cluster.
Returns
-------
:
A dictionary with the name of the instrument as key and the value its
compiler.
"""
instrument_compilers = {}
for name, cfg in self.instrument_cfg.items():
if not isinstance(cfg, dict):
continue # not an instrument definition
if "instrument_type" not in cfg:
raise KeyError(
f"Module {name} of cluster {self.name} is specified in "
f"the config, but does not specify an 'instrument_type'."
f"\n\nValid values: {self.compiler_classes.keys()}"
)
instrument_type: str = cfg["instrument_type"]
if instrument_type not in self.compiler_classes:
raise KeyError(
f"Specified unknown instrument_type {instrument_type} as"
f" a module for cluster {self.name}. Please select one "
f"of: {self.compiler_classes.keys()}."
)
compiler_type: type = self.compiler_classes[instrument_type]
instance = compiler_type(
parent=self,
name=name,
total_play_time=self.total_play_time,
instrument_cfg=cfg,
latency_corrections=self.latency_corrections,
)
instrument_compilers[name] = instance
return instrument_compilers
[docs]
def prepare(self) -> None:
"""Prepares the instrument compiler for compilation by assigning the data."""
self.distribute_data()
for compiler in self.instrument_compilers.values():
compiler.prepare()
[docs]
def distribute_data(self) -> None:
"""
Distributes the pulses and acquisitions assigned to the cluster over the
individual module compilers.
"""
for compiler in self.instrument_compilers.values():
for portclock in compiler.portclocks:
port, clock = portclock
if portclock in self._pulses:
for pulse in self._pulses[portclock]:
compiler.add_pulse(port, clock, pulse)
if portclock in self._acquisitions:
for acq in self._acquisitions[portclock]:
compiler.add_acquisition(port, clock, acq)
[docs]
def compile(
self, debug_mode: bool, repetitions: int = 1
) -> Optional[Dict[str, Any]]:
"""
Performs the compilation.
Parameters
----------
debug_mode
Debug mode can modify the compilation process,
so that debugging of the compilation process is easier.
repetitions
Amount of times to repeat execution of the schedule.
Returns
-------
:
The part of the compiled instructions relevant for this instrument.
"""
program = {}
program["settings"] = {"reference_source": self.instrument_cfg["ref"]}
sequence_to_file = self.instrument_cfg.get("sequence_to_file", None)
for compiler in self.instrument_compilers.values():
instrument_program = compiler.compile(
repetitions=repetitions,
sequence_to_file=sequence_to_file,
debug_mode=debug_mode,
)
if instrument_program is not None and len(instrument_program) > 0:
program[compiler.name] = instrument_program
if len(program) == 0:
program = None
return program
[docs]
COMPILER_MAPPING: Dict[str, type] = {
"Cluster": Cluster,
"LocalOscillator": LocalOscillator,
}
"""Maps the names in the hardware config to their appropriate compiler classes."""