# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
from typing import Dict, Any
from qcodes.instrument.base import Instrument
from qcodes.instrument import InstrumentChannel
from qcodes.instrument.base import InstrumentBase
from quantify_core.utilities import deprecated
from qcodes.instrument.parameter import (
InstrumentRefParameter,
ManualParameter,
Parameter,
)
from qcodes.utils import validators
from quantify_scheduler.backends.circuit_to_device import (
DeviceCompilationConfig,
OperationCompilationConfig,
)
from quantify_scheduler.helpers.validators import Numbers
from quantify_scheduler.device_under_test.device_element import DeviceElement
[docs]class Ports(InstrumentChannel):
"""
Submodule containing the ports.
"""
def __init__(self, parent: InstrumentBase, name: str, **kwargs: Any) -> None:
super().__init__(parent=parent, name=name)
self.add_parameter(
"microwave",
docstring=r"Name of the element's microwave port.",
initial_cache_value=f"{parent.name}:mw",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"flux",
docstring=r"Name of the element's flux port.",
initial_cache_value=f"{parent.name}:fl",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"readout",
docstring=r"Name of the element's readout port.",
initial_cache_value=f"{parent.name}:res",
parameter_class=Parameter,
set_cmd=False,
)
[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)
self.add_parameter(
"f01",
label="Qubit frequency",
unit="Hz",
parameter_class=ManualParameter,
docstring=f"Frequency of the {parent.name}.01 clock",
initial_value=float("nan"),
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
self.add_parameter(
"f12",
label="Frequency of the |1>-|2> transition",
unit="Hz",
initial_value=float("nan"),
docstring=f"Frequency of the {parent.name}.12 clock",
parameter_class=ManualParameter,
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
self.add_parameter(
"readout",
label="Readout frequency",
unit="Hz",
parameter_class=ManualParameter,
docstring=f"Frequency of the {parent.name}.ro clock. ",
initial_value=float("nan"),
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
[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)
self.add_parameter(
"duration",
docstring="""Duration of the passive qubit reset
(initialization by relaxation).""",
initial_value=200e-6,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
[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)
self.add_parameter(
"amp180",
docstring=r"""Amplitude required to perform a $\pi$ pulse.""",
label=r"$\pi-pulse amplitude$",
initial_value=float("nan"),
unit="V",
parameter_class=ManualParameter,
vals=Numbers(min_value=-10, max_value=10, allow_nan=True),
)
self.add_parameter(
"motzoi",
docstring="""Ratio between the Gaussian Derivative (D) and Gaussian (G)
components of the DRAG pulse.""",
initial_value=0,
unit="",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=-1, max_value=1),
)
self.add_parameter(
"duration",
docstring="""Duration of the control pulse.""",
initial_value=20e-9,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
[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")
self.add_parameter(
"pulse_type",
docstring=(
"Envelope function that defines the shape of "
"the readout pulse prior to modulation."
),
initial_value="SquarePulse",
parameter_class=ManualParameter,
vals=pulse_types,
)
self.add_parameter(
"pulse_amp",
docstring="Amplitude of the readout pulse.",
initial_value=0.25,
unit="V",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=2),
)
self.add_parameter(
"pulse_duration",
docstring="Duration of the readout pulse.",
initial_value=300e-9,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
self.add_parameter(
"acq_channel",
docstring="Acquisition channel of to this device element.",
initial_value=0,
unit="#",
parameter_class=ManualParameter,
vals=validators.Ints(min_value=0),
)
self.add_parameter(
"acq_delay",
docstring="Delay between the start of the readout pulse and the start of the acquisition.",
initial_value=0,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
self.add_parameter(
"integration_time",
docstring="Integration time for the readout acquisition.",
initial_value=1e-6,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
ro_acq_weight_type_validator = validators.Enum("SSB")
self.add_parameter(
"acq_weight_type",
initial_value="SSB",
parameter_class=ManualParameter,
vals=ro_acq_weight_type_validator,
)
[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):
super().__init__(name, **kwargs)
self.add_submodule("reset", IdlingReset(self, "reset"))
self.add_submodule("rxy", RxyDRAG(self, "rxy"))
self.add_submodule("measure", DispersiveMeasurement(self, "measure"))
self.add_submodule("ports", Ports(self, "ports"))
self.add_submodule("clock_freqs", ClocksFrequencies(self, "clock_freqs"))
[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="quantify_scheduler.operations."
+ "pulse_library.IdlePulse",
factory_kwargs={
"duration": self.reset.duration(),
},
),
# example of a pulse with a parametrized mapping, using a factory
"Rxy": OperationCompilationConfig(
factory_func="quantify_scheduler.operations."
+ "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(),
},
gate_info_factory_kwargs=[
"theta",
"phi",
], # 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="quantify_scheduler.operations."
+ "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",
},
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.parse_obj(cfg_dict)
return dev_cfg
@deprecated(
"0.8",
"Consider replacing with BasicTransmonElement or implementing a custom device element.",
)
[docs]class TransmonElement(DeviceElement):
"""
A device element representing a single transmon coupled to a
readout resonator.
This object can be used to generate configuration files compatible with the
:func:`~quantify_scheduler.compilation.add_pulse_information_transmon` function.
"""
def __init__(self, name: str, **kwargs):
"""
Initializes the parent class and adds
:class:`~qcodes.instrument.parameter.Parameter` s /
:class:`~qcodes.instrument.parameter.ManualParameter` s /
:class:`~qcodes.instrument.parameter.InstrumentRefParameter` s to it.
The list of all parameters and their latest (cached) value can be listed as
follows:
.. jupyter-execute::
from quantify_scheduler.device_under_test import transmon_element
q0 = transmon_element.TransmonElement("q0")
q0.print_readable_snapshot()
Parameters
-----------
name:
The name of the transmon element.
"""
super().__init__(name, **kwargs)
# pylint: disable=fixme
# TODO: create DeviceElement parent class and make instrument_coordinator
# a parameter of that class, see issue quantify-scheduler#148
self.add_parameter(
"instrument_coordinator",
initial_value=None,
parameter_class=InstrumentRefParameter,
vals=validators.Strings(),
)
self._add_device_parameters()
[docs] def _add_device_parameters(self):
self.add_parameter(
"init_duration",
docstring="""Duration of the passive qubit reset
(initialization by relaxation).""",
initial_value=200e-6,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
self.add_parameter(
"mw_amp180",
docstring=r"""Amplitude of the $\pi$ pulse
(considering a pulse duration of `mw_pulse_duration`).""",
label=r"$\pi-pulse amplitude$",
initial_value=float("nan"),
unit="V",
parameter_class=ManualParameter,
vals=Numbers(min_value=-10, max_value=10, allow_nan=True),
)
self.add_parameter(
"mw_motzoi",
docstring="""Ratio between the Gaussian Derivative (D) and Gaussian (G)
components of the DRAG pulse.""",
initial_value=0,
unit="",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=-1, max_value=1),
)
self.add_parameter(
"mw_pulse_duration",
docstring=(
"Duration of the pulses applied on the transmon's microwave port."
),
initial_value=20e-9,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
self.add_parameter(
"mw_ef_amp180",
docstring=(
"Amplitude of the pulse necessary to drive the |1>-|2> "
"transition (considering a pulse duration of `mw_pulse_duration`)."
),
unit="V",
initial_value=float("nan"),
parameter_class=ManualParameter,
vals=Numbers(min_value=-10, max_value=10, allow_nan=True),
)
self.add_parameter(
"mw_port",
docstring=r"Name of the transmon's microwave port.",
initial_cache_value=f"{self.name}:mw",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"fl_port",
docstring=r"Name of the transmon's flux port.",
initial_cache_value=f"{self.name}:fl",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"ro_port",
docstring=r"Name of the transmon's readout resonator port.",
initial_cache_value=f"{self.name}:res",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"mw_01_clock",
docstring=r"Name of the clock corresponding to the qubit frequency.",
initial_cache_value=f"{self.name}.01",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"mw_12_clock",
docstring=(
"Name of the clock corresponding to the |1>-|2> transition "
"frequency."
),
initial_cache_value=f"{self.name}.12",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"ro_clock",
docstring=r"Name of the readout resonator clock.",
initial_cache_value=f"{self.name}.ro",
parameter_class=Parameter,
set_cmd=False,
)
self.add_parameter(
"freq_01",
label="Qubit frequency",
unit="Hz",
parameter_class=ManualParameter,
initial_value=float("nan"),
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
self.add_parameter(
"freq_12",
label="Frequency of the |1>-|2> transition",
unit="Hz",
initial_value=float("nan"),
parameter_class=ManualParameter,
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
self.add_parameter(
"ro_freq",
docstring="Frequency of the pulse sent to the readout resonator.",
label="Readout frequency",
unit="Hz",
parameter_class=ManualParameter,
initial_value=float("nan"),
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
self.add_parameter(
"ro_pulse_amp",
docstring="Amplitude of the readout pulse.",
initial_value=0.5,
unit="V",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=-10, max_value=10),
)
self.add_parameter(
"ro_pulse_duration",
docstring="Duration of the readout pulse.",
initial_value=300e-9,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
pulse_types = validators.Enum("SquarePulse")
self.add_parameter(
"ro_pulse_type",
docstring=(
"Envelope function that defines the shape of "
"the readout pulse prior to modulation."
),
initial_value="SquarePulse",
parameter_class=ManualParameter,
vals=pulse_types,
)
self.add_parameter(
"ro_pulse_delay",
docstring="Delay before the execution of the readout pulse.",
label="Readout pulse delay",
initial_value=300e-9,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=-1, max_value=1),
)
self.add_parameter(
"ro_acq_channel",
docstring="Channel corresponding to this qubit.",
initial_value=0,
unit="#",
parameter_class=ManualParameter,
vals=validators.Ints(min_value=0),
)
self.add_parameter(
"ro_acq_delay",
docstring="Delay between the readout pulse and acquisition.",
initial_value=0,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=-1, max_value=1),
)
self.add_parameter(
"ro_acq_integration_time",
docstring="Integration time for the readout acquisition.",
initial_value=1e-6,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
self.add_parameter(
"spec_pulse_duration",
docstring="Duration of the qubit spectroscopy pulse.",
initial_value=8e-6,
unit="s",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1),
)
self.add_parameter(
"spec_pulse_frequency",
docstring="Frequency of the qubit spectroscopy pulse.",
initial_value=float("nan"),
unit="Hz",
parameter_class=ManualParameter,
vals=Numbers(min_value=0, max_value=1e12, allow_nan=True),
)
self.add_parameter(
"spec_pulse_amp",
docstring="Amplitude of the qubit spectroscopy pulse.",
initial_value=0.5,
unit="V",
parameter_class=ManualParameter,
vals=validators.Numbers(min_value=0, max_value=1e12),
)
self.add_parameter(
"spec_pulse_clock",
initial_cache_value=f"{self.name}.01",
parameter_class=Parameter,
set_cmd=False,
)
acquisition_validator = validators.Enum("SSBIntegrationComplex", "Trace")
self.add_parameter(
"acquisition",
docstring=(
"Acquisition mode. Can take either the 'Trace' value, which "
"yields a time trace of the data, or 'SSBIntegrationComplex', "
"which yields integrated single-sideband demodulated "
"data."
),
initial_value="SSBIntegrationComplex",
parameter_class=ManualParameter,
vals=acquisition_validator,
)
ro_acq_weight_type_validator = validators.Enum("SSB")
self.add_parameter(
"ro_acq_weight_type",
initial_value="SSB",
parameter_class=ManualParameter,
vals=ro_acq_weight_type_validator,
)
[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="quantify_scheduler.operations."
+ "pulse_library.IdlePulse",
factory_kwargs={
"duration": self.init_duration(),
},
),
# example of a pulse with a parametrized mapping, using a factory
"Rxy": OperationCompilationConfig(
factory_func="quantify_scheduler.operations."
+ "pulse_factories.rxy_drag_pulse",
factory_kwargs={
"amp180": self.mw_amp180(),
"motzoi": self.mw_motzoi(),
"port": self.mw_port(),
"clock": self.mw_01_clock(),
"duration": self.mw_pulse_duration(),
},
gate_info_factory_kwargs=[
"theta",
"phi",
], # 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="quantify_scheduler.operations."
+ "measurement_factories.dispersive_measurement",
factory_kwargs={
"port": self.ro_port(),
"clock": self.ro_clock(),
"pulse_type": self.ro_pulse_type(),
"pulse_amp": self.ro_pulse_amp(),
"pulse_duration": self.ro_pulse_duration(),
"acq_delay": self.ro_acq_delay(),
"acq_duration": self.ro_acq_integration_time(),
"acq_channel": self.ro_acq_channel(),
"acq_protocol_default": "SSBIntegrationComplex",
},
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": {
self.mw_01_clock(): self.freq_01(),
self.mw_12_clock(): self.freq_12(),
self.ro_clock(): self.ro_freq(),
},
"edges": {},
}
dev_cfg = DeviceCompilationConfig.parse_obj(cfg_dict)
return dev_cfg