Source code for quantify_scheduler.device_under_test.transmon_element

# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
from typing import Any, Dict, Tuple
import math

from qcodes.instrument import InstrumentChannel
from qcodes.instrument.base import InstrumentBase
from qcodes.instrument.parameter import (
    ManualParameter,
    Parameter,
)
from qcodes.utils import validators
from quantify_scheduler.backends.graph_compilation import (
    DeviceCompilationConfig,
    OperationCompilationConfig,
)
from quantify_scheduler.helpers.validators import Numbers
from quantify_scheduler.device_under_test.device_element import DeviceElement
from quantify_scheduler.operations import (
    pulse_factories,
    pulse_library,
    measurement_factories,
)


[docs]class Ports(InstrumentChannel): """ Submodule containing the ports. """ def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None: super().__init__(parent=parent, name=name)
[docs] self.microwave = Parameter( name="microwave", instrument=self, initial_cache_value=kwargs.get("microwave", f"{parent.name}:mw"), set_cmd=False, )
"""Name of the element's microwave port."""
[docs] self.flux = Parameter( name="flux", instrument=self, initial_cache_value=kwargs.get("flux", f"{parent.name}:fl"), set_cmd=False, )
"""Name of the element's flux port."""
[docs] self.readout = Parameter( name="readout", instrument=self, initial_cache_value=kwargs.get("readout", f"{parent.name}:res"), set_cmd=False, )
"""Name of the element's readout port."""
[docs]class ClocksFrequencies(InstrumentChannel): """ Submodule containing the clock frequencies specifying the transitions to address. """ def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None: super().__init__(parent=parent, name=name)
[docs] self.f01 = ManualParameter( name="f01", instrument=self, label="Qubit frequency", unit="Hz", initial_value=kwargs.get("f01", math.nan), vals=Numbers(min_value=0, max_value=1e12, allow_nan=True), )
"""Frequency of the 01 clock"""
[docs] self.f12 = ManualParameter( name="f12", instrument=self, label="Frequency of the |1>-|2> transition", unit="Hz", initial_value=kwargs.get("f12", math.nan), vals=Numbers(min_value=0, max_value=1e12, allow_nan=True), )
"""Frequency of the 12 clock"""
[docs] self.readout = ManualParameter( name="readout", instrument=self, label="Readout frequency", unit="Hz", initial_value=kwargs.get("readout", math.nan), vals=Numbers(min_value=0, max_value=1e12, allow_nan=True), )
"""Frequency of the ro clock. """
[docs]class IdlingReset(InstrumentChannel): """ Submodule containing parameters for doing a reset by idling. """ def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None: super().__init__(parent=parent, name=name)
[docs] self.duration = ManualParameter( name="duration", instrument=self, initial_value=kwargs.get("duration", 200e-6), unit="s", vals=validators.Numbers(min_value=0, max_value=1), )
"""Duration of the passive qubit reset (initialization by relaxation)."""
[docs]class RxyDRAG(InstrumentChannel): """ Submodule containing parameters for performing an Rxy operation using a DRAG pulse. """ def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None: super().__init__(parent=parent, name=name)
[docs] self.amp180 = ManualParameter( name="amp180", instrument=self, label=r"$\pi-pulse amplitude$", initial_value=kwargs.get("amp180", math.nan), unit="", vals=Numbers(min_value=-10, max_value=10, allow_nan=True), )
r"""Amplitude required to perform a $\pi$ pulse."""
[docs] self.motzoi = ManualParameter( name="motzoi", instrument=self, initial_value=kwargs.get("motzoi", 0), unit="", vals=validators.Numbers(min_value=-1, max_value=1), )
"""Ratio between the Gaussian Derivative (D) and Gaussian (G) components of the DRAG pulse."""
[docs] self.duration = ManualParameter( name="duration", instrument=self, initial_value=kwargs.get("duration", 20e-9), unit="s", vals=validators.Numbers(min_value=0, max_value=1), )
"""Duration of the control pulse.""" self.add_submodule( name="reference_magnitude", submodule=ReferenceMagnitude( parent=self, name="reference_magnitude", dBm=kwargs.get("reference_magnitude_dBm", math.nan), V=kwargs.get("reference_magnitude_V", math.nan), A=kwargs.get("reference_magnitude_A", math.nan), ), )
[docs]class DispersiveMeasurement(InstrumentChannel): """ Submodule containing parameters to perform a measurement using :func:`~quantify_scheduler.operations.measurement_factories.dispersive_measurement` """ def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None: super().__init__(parent=parent, name=name) pulse_types = validators.Enum("SquarePulse")
[docs] self.pulse_type = ManualParameter( name="pulse_type", instrument=self, initial_value=kwargs.get("pulse_type", "SquarePulse"), vals=pulse_types, )
"""Envelope function that defines the shape of the readout pulse prior to modulation."""
[docs] self.pulse_amp = ManualParameter( name="pulse_amp", instrument=self, initial_value=kwargs.get("pulse_amp", 0.25), unit="", vals=validators.Numbers(min_value=0, max_value=1), )
"""Amplitude of the readout pulse."""
[docs] self.pulse_duration = ManualParameter( name="pulse_duration", instrument=self, initial_value=kwargs.get("pulse_duration", 300e-9), unit="s", vals=validators.Numbers(min_value=0, max_value=1), )
"""Duration of the readout pulse."""
[docs] self.acq_channel = ManualParameter( name="acq_channel", instrument=self, initial_value=kwargs.get("acq_channel", 0), unit="#", vals=validators.Ints(min_value=0), )
"""Acquisition channel of to this device element."""
[docs] self.acq_delay = ManualParameter( name="acq_delay", instrument=self, initial_value=kwargs.get("acq_delay", 0), unit="s", # in principle the values should be a few 100 ns but the validator is here # only to protect against silly typos that lead to out of memory errors. vals=validators.Numbers(min_value=0, max_value=100e-6), )
"""Delay between the start of the readout pulse and the start of the acquisition. Note that some hardware backends do not support starting a pulse and the acquisition in the same clock cycle making 0 delay an invalid value."""
[docs] self.integration_time = ManualParameter( name="integration_time", instrument=self, initial_value=kwargs.get("integration_time", 1e-6), unit="s", # in principle the values should be a few us but the validator is here # only to protect against silly typos that lead to out of memory errors. vals=validators.Numbers(min_value=0, max_value=100e-6), )
"""Integration time for the readout acquisition."""
[docs] self.reset_clock_phase = ManualParameter( name="reset_clock_phase", instrument=self, initial_value=kwargs.get("reset_clock_phase", True), vals=validators.Bool(), )
"""The phase of the measurement clock will be reset by the control hardware at the start of each measurement if ``reset_clock_phase=True``."""
[docs] self.acq_weights_a = ManualParameter( name="acq_weights_a", instrument=self, initial_value=kwargs.get("acq_weights_a", None), vals=validators.Arrays(), )
"""The weights for the I path. Used when specifying the ``"NumericalWeightedIntegrationComplex"`` acquisition protocol."""
[docs] self.acq_weights_b = ManualParameter( name="acq_weights_b", instrument=self, initial_value=kwargs.get("acq_weights_b", None), vals=validators.Arrays(), )
"""The weights for the Q path. Used when specifying the ``"NumericalWeightedIntegrationComplex"`` acquisition protocol."""
[docs] self.acq_weights_sampling_rate = ManualParameter( name="acq_weights_sampling_rate", instrument=self, initial_value=kwargs.get("acq_weights_sampling_rate", None), vals=validators.Numbers(min_value=1, max_value=10e9), )
"""The sample rate of the weights arrays, in Hertz. Used when specifying the ``"NumericalWeightedIntegrationComplex"`` acquisition protocol.""" ro_acq_weight_type_validator = validators.Enum("SSB", "Numerical") self.acq_weight_type = ManualParameter( name="acq_weight_type", instrument=self, initial_value=kwargs.get("acq_weight_type", "SSB"), vals=ro_acq_weight_type_validator, ) self.add_submodule( name="reference_magnitude", submodule=ReferenceMagnitude( parent=self, name="reference_magnitude", dBm=kwargs.get("reference_magnitude_dBm", math.nan), V=kwargs.get("reference_magnitude_V", math.nan), A=kwargs.get("reference_magnitude_A", math.nan), ), )
[docs] self.acq_rotation = ManualParameter( "acq_rotation", instrument=self, initial_value=kwargs.get("acq_rotation", 0), )
"""The phase rotation in degrees required to perform thresholded acquisition. Note that rotation is performed before the threshold. For more details see :class:`~quantify_scheduler.operations.acquisition_library.ThresholdedAcquisition`.""" # noqa
[docs] self.acq_threshold = ManualParameter( "acq_threshold", instrument=self, initial_value=kwargs.get("acq_threshold", 0), )
"""The threshold value against which the rotated and integrated result is compared against. For more details see :class:`~quantify_scheduler.operations.acquisition_library.ThresholdedAcquisition`.""" # noqa
[docs]class ReferenceMagnitude(InstrumentChannel): """ Submodule which describes an amplitude / power reference level, with respect to which pulse amplitudes are defined. This can be specified in units of "V", "dBm" or "A". Only one unit parameter may have a defined value at a time. If we call the set method for any given unit parameter, all other unit parameters will be automatically set to nan. """ def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None: super().__init__(parent=parent, name=name) self.dBm = Parameter( "reference_magnitude_dBm", instrument=self, initial_value=kwargs.get("dBm", math.nan), set_cmd=lambda value: self._set_parameter(value, "reference_magnitude_dBm"), unit="dBm", vals=Numbers(allow_nan=True), ) self.V = Parameter( "reference_magnitude_V", instrument=self, initial_value=kwargs.get("V", math.nan), set_cmd=lambda value: self._set_parameter(value, "reference_magnitude_V"), unit="V", vals=Numbers(allow_nan=True), ) self.A = Parameter( "reference_magnitude_A", instrument=self, initial_value=kwargs.get("A", math.nan), set_cmd=lambda value: self._set_parameter(value, "reference_magnitude_A"), unit="A", vals=Numbers(allow_nan=True), )
[docs] def _set_parameter(self, value: float, parameter: str): """ Set the value of one of the unit parameters, while setting all the other unit parameters to nan. """ for name, par in self.parameters.items(): if name == parameter: par.cache.set(value) elif not math.isnan(value): par.cache.set(math.nan)
[docs] def get_val_unit(self) -> Tuple[float, str]: """ Get the value of the amplitude reference and its unit, if one is defined. If a value is defined for more than one unit, raise an exception. Returns ---------- value The value of the amplitude reference unit The unit in which this value is specified """ value_and_unit = math.nan, "" for param in self.parameters.values(): if not math.isnan(value := param()): if math.isnan(value_and_unit[0]): value_and_unit = value, param.unit # type: ignore else: raise ValueError( "ReferenceMagnitude values defined for multiple units. Only " "one unit may be defined at a time." ) return value_and_unit
[docs]class BasicTransmonElement(DeviceElement): """ A device element representing a single fixed-frequency transmon qubit coupled to a readout resonator. """ def __init__(self, name: str, **kwargs): """ Parameters ---------- name The name of the transmon element. kwargs Can be used to pass submodule initialization data by using submodule name as keyword and as argument a dictionary containing the submodule parameter names and their value. """ submodules_to_add = { "reset": IdlingReset, "rxy": RxyDRAG, "measure": DispersiveMeasurement, "ports": Ports, "clock_freqs": ClocksFrequencies, } submodule_data = { sub_name: kwargs.pop(sub_name, {}) for sub_name in submodules_to_add.keys() } super().__init__(name, **kwargs) for sub_name, sub_class in submodules_to_add.items(): self.add_submodule( sub_name, sub_class( parent=self, name=sub_name, **submodule_data.get(sub_name, {}) ), )
[docs] self.reset: IdlingReset
"""Submodule :class:`~.IdlingReset`."""
[docs] self.rxy: RxyDRAG
"""Submodule :class:`~.RxyDRAG`."""
[docs] self.measure: DispersiveMeasurement
"""Submodule :class:`~.DispersiveMeasurement`."""
[docs] self.ports: Ports
"""Submodule :class:`~.Ports`."""
[docs] self.clock_freqs: ClocksFrequencies
"""Submodule :class:`~.ClocksFrequencies`."""
[docs] def _generate_config(self) -> Dict[str, Dict[str, OperationCompilationConfig]]: """ Generates part of the device configuration specific to a single qubit. This method is intended to be used when this object is part of a device object containing multiple elements. """ qubit_config = { f"{self.name}": { "reset": OperationCompilationConfig( factory_func=pulse_library.IdlePulse, factory_kwargs={ "duration": self.reset.duration(), }, ), # example of a pulse with a parametrized mapping, using a factory "Rxy": OperationCompilationConfig( factory_func=pulse_factories.rxy_drag_pulse, factory_kwargs={ "amp180": self.rxy.amp180(), "motzoi": self.rxy.motzoi(), "port": self.ports.microwave(), "clock": f"{self.name}.01", "duration": self.rxy.duration(), "reference_magnitude": pulse_library.ReferenceMagnitude.from_parameter( self.rxy.reference_magnitude ), }, gate_info_factory_kwargs=[ "theta", "phi", ], # the keys from the gate info to pass to the factory function ), "Rz": OperationCompilationConfig( factory_func=pulse_factories.phase_shift, factory_kwargs={ "clock": f"{self.name}.01", }, gate_info_factory_kwargs=[ "theta", ], # the keys from the gate info to pass to the factory function ), # the measurement also has a parametrized mapping, and uses a # factory function. "measure": OperationCompilationConfig( factory_func=measurement_factories.dispersive_measurement, factory_kwargs={ "port": self.ports.readout(), "clock": f"{self.name}.ro", "pulse_type": self.measure.pulse_type(), "pulse_amp": self.measure.pulse_amp(), "pulse_duration": self.measure.pulse_duration(), "acq_delay": self.measure.acq_delay(), "acq_duration": self.measure.integration_time(), "acq_channel": self.measure.acq_channel(), "acq_protocol_default": "SSBIntegrationComplex", "reset_clock_phase": self.measure.reset_clock_phase(), "reference_magnitude": pulse_library.ReferenceMagnitude.from_parameter( self.measure.reference_magnitude ), "acq_weights_a": self.measure.acq_weights_a(), "acq_weights_b": self.measure.acq_weights_b(), "acq_weights_sampling_rate": self.measure.acq_weights_sampling_rate(), "acq_rotation": self.measure.acq_rotation(), "acq_threshold": self.measure.acq_threshold(), }, gate_info_factory_kwargs=["acq_index", "bin_mode", "acq_protocol"], ), } } return qubit_config
[docs] def generate_device_config(self) -> DeviceCompilationConfig: """ Generates a valid device config for the quantify-scheduler making use of the :func:`~.circuit_to_device._compile_circuit_to_device` function. This enables the settings of this qubit to be used in isolation. .. note: This config is only valid for single qubit experiments. """ cfg_dict = { "backend": "quantify_scheduler.backends" ".circuit_to_device._compile_circuit_to_device", "elements": self._generate_config(), "clocks": { f"{self.name}.01": self.clock_freqs.f01(), f"{self.name}.12": self.clock_freqs.f12(), f"{self.name}.ro": self.clock_freqs.readout(), }, "edges": {}, } dev_cfg = DeviceCompilationConfig.model_validate(cfg_dict) return dev_cfg