# 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 typing import Any, Dict, Optional, Tuple
from collections import abc
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 `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.
"""
def __init__(
self,
parent: compiler_container.CompilerContainer,
name: str,
total_play_time: float,
instrument_cfg: Dict[str, Any],
):
"""
Constructor for a local oscillator compiler.
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 _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, repetitions: int = 1) -> Optional[Dict[str, Any]]:
"""
Compiles the program for the LO InstrumentCoordinator component.
Parameters
----------
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"),
valid_ios=[f"complex_output_{i}" for i in [0, 1]]
+ [f"real_output_{i}" for i in range(4)],
)
# 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"),
valid_ios=[f"complex_output_{i}" for i in [0]]
+ [f"real_output_{i}" for i in range(2)]
+ [f"complex_input_{i}" for i in [0]]
+ [f"real_input_{i}" for i in range(2)],
)
[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=0.25,
mixer_dc_offset_range=BoundedParameter(min_val=-50, max_val=50, units="mV"),
valid_ios=[f"complex_output_{i}" for i in [0, 1]],
output_map={
"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=0.25,
mixer_dc_offset_range=BoundedParameter(min_val=-50, max_val=50, units="mV"),
valid_ios=[f"complex_output_{i}" for i in [0]]
+ [f"complex_input_{i}" for i in [0]],
default_marker=0b0011,
)
[docs]class Cluster(compiler_abc.ControlDeviceCompiler):
"""
Compiler class for a Qblox cluster.
"""
[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,
):
"""
Constructor for a Cluster compiler object.
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.
"""
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,
)
assert hasattr(instance, "is_pulsar")
instance.is_pulsar = False
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, repetitions: int = 1) -> Optional[Dict[str, Any]]:
"""
Performs the compilation.
Parameters
----------
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
)
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] = {
"Pulsar_QCM": QcmModule,
"Pulsar_QRM": QrmModule,
"Pulsar_QCM_RF": QcmRfModule,
"Pulsar_QRM_RF": QrmRfModule,
"Cluster": Cluster,
"LocalOscillator": LocalOscillator,
}
"""Maps the names in the hardware config to their appropriate compiler classes."""