# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""Python dataclasses for quantify-scheduler json-schemas."""
from dataclasses import dataclass, field
from enum import Enum, unique
from typing import Dict, List, Literal, Optional, Union
from pydantic import Field, field_validator
from typing_extensions import Annotated
from quantify_scheduler.backends.types import common
from quantify_scheduler.structure.model import DataStructure
@unique
[docs]
class DeviceType(str, Enum):
"""Enum of device types."""
@unique
[docs]
class ModulationModeType(str, Enum):
"""
The modulation mode enum type.
Used to set the modulation type to
1. no modulation. ('none')
2. Software premodulation applied in the numerical waveforms. ('premod')
3. Hardware real-time modulation. ('modulate')
See also :class:`~quantify_scheduler.backends.types.zhinst.Modulation` for the use.
"""
@unique
[docs]
class SignalModeType(str, Enum):
"""
The signal output enum type.
Used to set the output signal type to a
modulated or real respectively.
"""
@unique
[docs]
class ReferenceSourceType(str, Enum):
"""
The reference source enum type.
Used to set the source trigger type to
internal or external respectively.
"""
@unique
[docs]
class InstrumentOperationMode(str, Enum):
"""
The InstrumentOperationMode enum defines in what operational mode an instrument is
in.
OPERATING mode sets the Instrument in its default operation mode.
CALIBRATING mode sets the Instrument in calibration mode in which for example the
numeric pulses generated by a backend for an AWG are set to np.ones.
"""
[docs]
CALIBRATING = "calibrate"
[docs]
class Modulation(DataStructure):
"""The backend Modulation record type."""
[docs]
type: ModulationModeType = ModulationModeType.NONE
"""
The modulation mode type select. Allows
to choose between. (default = ModulationModeType.NONE)
1. no modulation. ('none')
2. Software premodulation applied in the numerical waveforms. ('premod')
3. Hardware real-time modulation. ('modulate')
"""
[docs]
interm_freq: float = 0.0
"""The inter-modulation frequency (IF) in Hz. (default = 0.0)."""
[docs]
phase_shift: float = 0.0
"""The IQ modulation phase shift in Degrees. (default = 0.0)."""
[docs]
class LocalOscillator(DataStructure):
"""The backend LocalOscillator record type."""
"""The unique name identifying the combination of instrument and channel/parameters."""
"""The QCodes name of the LocalOscillator."""
[docs]
generic_icc_name: Optional[str] = None
"""The name of the GenericInstrumentCoordinatorComponent attached to this device."""
[docs]
frequency: Optional[dict] = None
"""A dict which tells the generic icc what parameter maps to the local oscillator (LO) frequency in Hz."""
[docs]
frequency_param: Optional[str] = None
"""The parameter on the LO instrument used to control the frequency."""
[docs]
power: Optional[dict] = None
"""A dict which tells the generic icc what parameter maps to the local oscillator (LO) power in dBm."""
[docs]
phase: Optional[dict] = None
"""A dict which tells the generic icc what parameter maps to the local oscillator (LO) phase in radians."""
[docs]
parameters: Optional[dict] = None
"""
A dict which allows setting of channel specific parameters of the device. Cannot
be used together with frequency and power.
"""
[docs]
class Output(DataStructure):
"""
The definition class for zhinst channel properties.
This class maps to the zhinst backend JSON "channel"
properties defined in the hardware mapping.
"""
"""The port resource."""
"""The Clock resource."""
"""The output mode type."""
[docs]
modulation: Modulation = Modulation()
"""The modulation settings."""
[docs]
local_oscillator: Optional[str] = None
"""The LocalOscillator name."""
[docs]
clock_frequency: Optional[float] = None
"""The frequency for the clock resource (AKA RF/signal frequency)."""
"""The output1 IQ modulation gain. Accepted value between -1 and + 1. (default = 1.0)"""
"""The output2 IQ modulation gain. Accepted value between -1 and + 1. (default = 1.0)"""
[docs]
trigger: Optional[int] = None
"""
The ZI Instrument input trigger. (default = None)
Setting this will declare the device secondary.
"""
[docs]
markers: List[Union[str, int]] = []
"""The ZI Instrument output triggers. (default = [])"""
[docs]
mixer_corrections: Optional[common.MixerCorrections] = None
"""The output mixer corrections."""
@field_validator("mixer_corrections", mode="before")
[docs]
def decapitalize_dc_mixer_offsets(cls, v):
"""
Decapitalize the DC mixer offsets.
This is required because the old-style hardare config used capitalized
keys for the DC mixer offsets, while the new-style hardware config uses
lower-case keys.
"""
if isinstance(v, dict):
if "dc_offset_I" in v:
v["dc_offset_i"] = v.pop("dc_offset_I")
if "dc_offset_Q" in v:
v["dc_offset_q"] = v.pop("dc_offset_Q")
return v
[docs]
class Device(DataStructure):
"""
The device definition class for zhinst devices.
This class maps to the zhinst backend JSON "devices"
properties defined in the hardware mapping.
"""
"""The QCodes Instrument name."""
"""The instrument model type. For example, 'UHFQA', 'HDAWG4', 'HDAWG8'."""
[docs]
ref: ReferenceSourceType
[docs]
"""The reference source type."""
"""The first physical channel properties."""
[docs]
channel_1: Optional[Output] = None
"""The second physical channel properties."""
"""The third physical channel properties."""
[docs]
channel_3: Optional[Output] = None
"""The fourth physical channel properties."""
[docs]
channels: List[Output] = Field(default=[], validate_default=True)
"""The list of channels. (auto generated)"""
[docs]
clock_select: Optional[int] = 0
"""
The clock rate divisor which will be used to get
the instruments clock rate from the lookup dictionary in
quantify_scheduler.backends.zhinst_backend.DEVICE_CLOCK_RATES.
For information see zhinst User manuals, section /DEV..../AWGS/n/TIME
Examples: base sampling rate (1.8 GHz) divided by 2^clock_select. (default = 0)
"""
[docs]
channelgrouping: int = 0
"""
The HDAWG channelgrouping property. (default = 0) corresponding to a single
sequencer controlling a pair (2) awg outputs.
"""
[docs]
mode: InstrumentOperationMode = InstrumentOperationMode.OPERATING
"""
The Instruments operation mode.
(default = zhinst.InstrumentOperationMode.OPERATING)
"""
[docs]
device_type: DeviceType = Field(default=DeviceType.NONE, validate_default=True)
"""
The Zurich Instruments hardware type. (default = DeviceType.NONE)
This field is automatically populated.
"""
[docs]
sample_rate: Optional[int] = None
"""
The Instruments sampling clock rate.
This field is automatically populated.
"""
[docs]
n_channels: Optional[int] = Field(default=None, validate_default=True)
"""
The number of physical channels of this ZI Instrument.
This field is automatically populated.
"""
@field_validator("channels")
[docs]
def generate_channel_list(cls, v, info):
"""Generate the channel list."""
if v != []:
raise ValueError(
f"Trying to set 'channels' to {v}, while it is an auto-generated field."
)
v = [info.data["channel_0"]]
if info.data["channel_1"] is not None:
v.append(info.data["channel_1"])
if info.data["channel_2"] is not None:
v.append(info.data["channel_2"])
if info.data["channel_3"] is not None:
v.append(info.data["channel_3"])
return v
@field_validator("n_channels")
[docs]
def calculate_n_channels(cls, v, info):
"""Calculate the number of channels."""
if v is not None:
raise ValueError(
f"Trying to set 'n_channels' to {v}, while it is an auto-generated field."
)
if info.data["type"][-1].isdigit():
digit = int(info.data["type"][-1])
v = digit
else:
v = 1
return v
@field_validator("device_type")
[docs]
def determine_device_type(cls, v, info):
"""Determine the device type."""
if v is not DeviceType.NONE:
raise ValueError(
f"Trying to set 'device_type' to {v}, while it is an auto-generated field."
)
if info.data["type"][-1].isdigit():
v = DeviceType(info.data["type"][:-1])
else:
v = DeviceType(info.data["type"])
return v
[docs]
class CommandTableHeader(DataStructure):
"""The CommandTable header definition."""
[docs]
class CommandTableEntryValue(DataStructure):
"""A CommandTable entry definition with a value."""
[docs]
class CommandTableEntry(DataStructure):
"""The definition of a single CommandTable entry."""
[docs]
waveform: "CommandTableWaveform"
[docs]
class CommandTable(DataStructure):
"""The CommandTable definition for ZI HDAWG."""
[docs]
header: Optional["CommandTableHeader"] = Field(default=None, validate_default=True)
[docs]
table: List["CommandTableEntry"]
@field_validator("header", mode="before")
[docs]
def generate_command_table_header(cls, v, values):
"""Generates command table header."""
if v is not None:
raise ValueError(
f"Trying to set 'header' to {v}, while it is an auto-generated field."
)
return CommandTableHeader()
@unique
[docs]
class QasIntegrationMode(Enum):
"""
Operation mode of all weighted integration units.
NORMAL: Normal mode. The integration weights are given
by the user-programmed filter memory.
SPECTROSCOPY: Spectroscopy mode. The integration weights
are generated by a digital oscillator. This mode offers
enhanced frequency resolution.
"""
@unique
[docs]
class QasResultMode(Enum):
"""UHFQA QAS result mode."""
@unique
[docs]
class QasResultSource(Enum):
"""UHFQA QAS result source."""
[docs]
CROSSTALK_CORRELATION = 4
[docs]
THRESHOLD_CORRELATION = 5
@dataclass
[docs]
class InstrumentInfo:
"""Instrument information record type."""
[docs]
num_samples_per_clock: int # number of samples per clock cycle (sequencer_rate)
[docs]
granularity: int # waveforms need to be a multiple of this many samples.
[docs]
mode: InstrumentOperationMode = InstrumentOperationMode.OPERATING
[docs]
sequencer_rate: float = field(init=False)
def __post_init__(self):
"""Initializes fields after initializing object."""
self.sequencer_rate = self.num_samples_per_clock / self.sample_rate
@dataclass(frozen=True)
[docs]
class Instruction:
"""Sequence base instruction record type."""
@staticmethod
[docs]
def default():
"""
Returns a default Instruction instance.
Returns
-------
Instruction :
"""
return Instruction("None", 0, 0, 0)
@dataclass(frozen=True)
[docs]
class Acquisition(Instruction):
"""
This instruction indicates that an acquisition is to be triggered in the UHFQA.
If a waveform_id is specified, this waveform will be used as the integration weight.
""" # noqa: D404
def __repr__(self):
return (
f"Acquisition(waveform_id: {self.waveform_id}"
f"|abs_time: {self.abs_time * 1e9} ns"
f"|dt: {self.duration * 1e9} ns"
f"|c0: {self.clock_cycle_start}"
)
@dataclass(frozen=True)
[docs]
class Wave(Instruction):
"""This instruction indicates that a waveform should be played.""" # noqa: D404
def __repr__(self):
return (
f"Wave(waveform_id: {self.waveform_id}"
f"|abs_time: {self.abs_time * 1e9} ns"
f"|dt: {self.duration * 1e9} ns"
f"|c0: {self.clock_cycle_start}"
)
[docs]
class ZIChannelDescription(DataStructure):
"""
Information needed to specify a ZI Channel in the :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`.
A single 'channel' represents a complex output, consisting of two physical I/O channels on
the Instrument.
"""
[docs]
mode: Union[Literal["real"], Literal["complex"]]
"""The output mode type."""
[docs]
markers: List[str] = []
"""
Property that specifies which markers to trigger on each sequencer iteration.
The values are used as input for the ``setTrigger`` sequencer instruction.
"""
[docs]
trigger: Optional[int] = None
"""
The ``trigger`` property specifies for a sequencer which digital trigger to wait for.
This value is used as the input parameter for the ``waitDigTrigger`` sequencer instruction.
Setting this will declare the device secondary.
"""
[docs]
class ZIBaseDescription(common.HardwareDescription):
"""Base class for a Zurich Instrument hardware description."""
"""
Property that describes if the instrument uses Markers or Triggers.
- ``int`` Enables sending Marker
- ``ext`` Enables waiting for Marker
- ``none`` Ignores waiting for Marker
"""
[docs]
class ZIHDAWG4Description(ZIBaseDescription):
"""Information needed to specify a HDAWG4 in the :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`."""
[docs]
instrument_type: Literal["HDAWG4"]
"""The instrument type, used to select this datastructure when parsing a :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`."""
"""
The HDAWG channelgrouping property impacting the amount of HDAWG channels per AWG
that must be used.. (default = 0) corresponding to a single sequencer controlling
a pair (2) awg outputs.
"""
"""
The clock rate divisor which will be used to get
the instruments clock rate from the lookup dictionary in
quantify_scheduler.backends.zhinst_backend.DEVICE_CLOCK_RATES.
For information see zhinst User manuals, section /DEV..../AWGS/n/TIME
Examples: base sampling rate (1.8 GHz) divided by 2^clock_select. (default = 0)
"""
[docs]
[docs]
channel_0: Optional[ZIChannelDescription] = None
"""Description of the first channel on this HDAWG (corresponding to 1 or 2 physical output ports)."""
[docs]
[docs]
channel_1: Optional[ZIChannelDescription] = None
"""Description of the second channel on this HDAWG (corresponding to 1 or 2 physical output ports)."""
[docs]
class ZIHDAWG8Description(ZIHDAWG4Description):
"""Information needed to specify a HDAWG8 in the :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`."""
[docs]
instrument_type: Literal["HDAWG8"]
"""The instrument type, used to select this datastructure when parsing a :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`."""
[docs]
channel_2: Optional[ZIChannelDescription] = None
"""Description of the third channel on this HDAWG (corresponding to 1 or 2 physical output ports)."""
[docs]
channel_3: Optional[ZIChannelDescription] = None
"""Description of the fourth channel on this HDAWG (corresponding to 1 or 2 physical output ports)."""
[docs]
class ZIUHFQADescription(ZIBaseDescription):
"""Information needed to specify a UHFQA in the :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`."""
[docs]
instrument_type: Literal["UHFQA"]
"""The instrument type, used to select this datastructure when parsing a :class:`~.quantify_scheduler.backends.zhinst_backend.ZIHardwareCompilationConfig`."""
[docs]
channel_0: Optional[ZIChannelDescription] = None
"""Description of the readout channel on this UHFQA."""
[docs]
ZIHardwareDescription = Annotated[
Union[
ZIHDAWG4Description,
ZIHDAWG8Description,
ZIUHFQADescription,
common.LocalOscillatorDescription,
common.IQMixerDescription,
],
Field(discriminator="instrument_type"),
]
"""
Specifies a piece of Zurich Instruments hardware and its instrument-specific settings.
Currently, the supported instrument types are:
:class:`~.ZIHDAWG4Description`,
:class:`~.ZIHDAWG8Description`,
:class:`~.ZIUHFQADescription`
"""
[docs]
class OutputGain(DataStructure):
"""
Gain settings for a port-clock combination.
These gain values will be set on each control-hardware output
port that is used for this port-clock combination.
.. admonition:: Example
:class: dropdown
.. code-block:: python
hardware_compilation_config.hardware_options.gain = {
"q0:res-q0.ro": Gain(
output_1 = 1,
output_2 = 1
),
}
"""
"""The output 1 IQ modulation gain. Accepted value between -1 and + 1. (default = 1.0)."""
"""The output 2 IQ modulation gain. Accepted value between -1 and + 1. (default = 1.0)."""
[docs]
class ZIHardwareOptions(common.HardwareOptions):
"""
Datastructure containing the hardware options for each port-clock combination.
.. admonition:: Example
:class: dropdown
Here, the HardwareOptions datastructure is created by parsing a
dictionary containing the relevant information.
.. jupyter-execute::
import pprint
from quantify_scheduler.schemas.examples.utils import (
load_json_example_scheme
)
.. jupyter-execute::
from quantify_scheduler.backends.types.zhinst import (
ZIHardwareOptions
)
zi_hw_options_dict = load_json_example_scheme(
"zhinst_hardware_compilation_config.json")["hardware_options"]
pprint.pprint(zi_hw_options_dict)
zi_hw_options = ZIHardwareOptions.model_validate(zi_hw_options_dict)
zi_hw_options
"""
[docs]
output_gain: Optional[Dict[str, OutputGain]] = None
"""
Dictionary containing the gain settings (values) that should be applied
to the outputs that are connected to a certain port-clock combination (keys).
"""