Source code for qblox_scheduler.operations.acquisition_library

# SPDX-FileCopyrightText: Copyright 2020-2025, Quantify Consortium
# SPDX-FileCopyrightText: © 2021 Qblox <https://qblox.com>
# SPDX-License-Identifier: BSD-3-Clause

"""Standard acquisition protocols for use with the qblox_scheduler."""

from __future__ import annotations

from copy import copy
from typing import TYPE_CHECKING

import numpy as np

from qblox_scheduler.enums import (
    BinMode,
    DualThresholdedTriggerCountLabels,
    TimeRef,
    TimeSource,
    TriggerCondition,
)
from qblox_scheduler.operations.operation import Operation
from qblox_scheduler.resources import DigitalClockResource

if TYPE_CHECKING:
    from collections.abc import Hashable


[docs] class Acquisition(Operation): """ An operation representing data acquisition at the quantum-device abstraction layer. An Acquisition must consist of (at least) an AcquisitionProtocol specifying how the acquired signal is to be processed, and an AcquisitionLabel and AcquisitionIndex specifying where the acquired data is to be stored in the RawDataset. N.B. This class helps differentiate an acquisition operation from the regular operations. This enables us to use :func:`~.qblox_scheduler.schedules._visualization.pulse_diagram.plot_acquisition_operations` to highlight acquisition pulses in the pulse diagrams. """
[docs] class Trace(Acquisition): """ The Trace acquisition protocol measures a signal s(t). Only processing performed is rescaling and adding units based on a calibrated scale. Values are returned as a raw trace (numpy array of float datatype). Length of this array depends on the sampling rate of the acquisition device. .. important:: The exact duration of this operation, and the possible bin modes may depend on the control hardware. Please consult your hardware vendor's :ref:`Reference guide` for more information. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The acquisition duration in seconds. acq_label The data channel in which the acquisition is stored, is by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. """ def __init__( self, duration: float, port: str, clock: str, acq_label: Hashable = 0, coords: dict | None = None, ) -> None: if not isinstance(duration, float): duration = float(duration) super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [], "duration": duration, "port": port, "clock": clock, "acq_label": acq_label, "coords": coords, "bin_mode": BinMode.AVERAGE, "protocol": "Trace", "acq_return_type": np.ndarray, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] return self._get_signature(acq_info)
[docs] class IQIntegration(Acquisition): r""" Weighted integration acquisition protocol returning integrated I and Q as one complex number. If no weights (``weights_a``/``weights_b``) are given, a square window is used as the integration weights (single sideband integration); ``duration`` then defaults to 50 ms and may not exceed 50 ms. If weights are given, the specified weights are used for the integration (with interpolation where necessary), and the acquisition length is limited to the length of the weight memory of 16384 ns. Weights are applied as: .. math:: \widetilde{A} = \int \mathrm{Re}(S(t))\cdot W_A(t) \mathrm{d}t .. math:: \widetilde{B} = \int \mathrm{Im}(S(t))\cdot W_B(t) \mathrm{d}t Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The acquisition duration in seconds. When no weights are given, defaults to 50 ms and is capped at 50 ms. When weights are given, ``duration`` is the time the weight arrays span (the weight sampling rate is derived as ``len(weights_a) / duration``); if omitted, the weights are assumed to be sampled at 1 GHz, so ``duration = len(weights_a) / 1e9`` (capped at 16384 ns). weights_a The list of complex values used as weights :math:`W_A(t)` on the incoming signal's real part. By default ``None``, in which case a square window is used (single sideband integration). weights_b The list of complex values used as weights :math:`W_B(t)` on the incoming signal's imaginary part. By default ``None``. interpolation Specifies the kind of interpolation to use. The string has to be one of 'linear', 'nearest', 'nearest-up', 'zero', 'slinear', 'quadratic', 'cubic', 'previous', or 'next'. 'zero', 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of zeroth, first, second or third order; 'previous' and 'next' simply return the previous or next value of the point; 'nearest-up' and 'nearest' differ when interpolating half-integers (e.g. 0.5, 1.5) in that 'nearest-up' rounds up and 'nearest' rounds down. Default is 'linear'. Only used if weights are given. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. Raises ------ ValueError If exactly one of ``weights_a``/``weights_b`` is given, or if the (derived) duration exceeds the allowed maximum. """ _DEFAULT_DURATION_NO_WEIGHTS = 50e-3 _MAX_DURATION_NO_WEIGHTS = 50e-3 _MAX_DURATION_WEIGHTED = 16384e-9 _DEFAULT_WEIGHTS_SAMPLING_RATE = 1e9 def __init__( self, port: str, clock: str, duration: float | None = None, weights_a: list[complex] | list[float] | np.ndarray | None = None, weights_b: list[complex] | list[float] | np.ndarray | None = None, interpolation: str = "linear", acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, ) -> None: bin_mode = BinMode.AVERAGE_APPEND if (weights_a is None) != (weights_b is None): raise ValueError("Both `weights_a` and `weights_b` must be given, or neither.") super().__init__(name=self.__class__.__name__) if weights_a is None: if duration is None: duration = self._DEFAULT_DURATION_NO_WEIGHTS if duration > self._MAX_DURATION_NO_WEIGHTS: raise ValueError( f"Acquisition duration of {duration} s exceeds the maximum duration " f"of {self._MAX_DURATION_NO_WEIGHTS} s for unweighted (square window) " f"integration." ) waveform_i = { "port": port, "clock": clock, "duration": duration, "wf_func": "qblox_scheduler.waveforms.square", "amplitude": 1, } waveform_q = { "port": port, "clock": clock, "duration": duration, "wf_func": "qblox_scheduler.waveforms.square", "amplitude": (0 + 1j), } waveforms = [waveform_i, waveform_q] protocol = "SSBIntegrationComplex" else: weights_a = np.array(weights_a) weights_b = np.array(weights_b) if duration is not None: weights_sampling_rate = len(weights_a) / duration else: weights_sampling_rate = self._DEFAULT_WEIGHTS_SAMPLING_RATE t_samples = np.arange(len(weights_a)) / weights_sampling_rate duration = len(t_samples) / weights_sampling_rate if duration > self._MAX_DURATION_WEIGHTED: raise ValueError( f"Acquisition duration of {duration} s exceeds the maximum duration " f"of {self._MAX_DURATION_WEIGHTED} s (16384 ns weight memory length) " f"for weighted integration." ) waveforms_a = { "wf_func": "qblox_scheduler.waveforms.interpolated_complex_waveform", "amplitude": 1, "samples": weights_a, "t_samples": t_samples, "interpolation": interpolation, } waveforms_b = { "wf_func": "qblox_scheduler.waveforms.interpolated_complex_waveform", "amplitude": 1, "samples": weights_b, "t_samples": t_samples, "interpolation": interpolation, } waveforms = [waveforms_a, waveforms_b] protocol = "NumericalSeparatedWeightedIntegration" self.data["acquisition_info"] = { "waveforms": waveforms, "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": bin_mode, "average_segment_repetitions": average_segment_repetitions, "acq_return_type": complex, "protocol": protocol, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] port = acq_info["port"] clock = acq_info["clock"] acq_label = acq_info["acq_label"] coords = acq_info["coords"] average_segment_repetitions = acq_info["average_segment_repetitions"] duration = acq_info["duration"] if "samples" in acq_info["waveforms"][0]: weights_a = np.array2string( acq_info["waveforms"][0]["samples"], separator=", ", precision=9 ) weights_b = np.array2string( acq_info["waveforms"][1]["samples"], separator=", ", precision=9 ) interpolation = acq_info["waveforms"][0]["interpolation"] return ( f"{self.__class__.__name__}(weights_a={weights_a}, weights_b={weights_b}, " f"{duration=}, {port=}, {clock=}, {interpolation=}, " f"{acq_label=}, {coords=}, {average_segment_repetitions=})" ) return ( f"{self.__class__.__name__}({port=}, {clock=}, {duration=}, " f"{acq_label=}, {coords=}, {average_segment_repetitions=})" ) def __repr__(self) -> str: return str(self)
[docs] class IQIntegrationMixed(IQIntegration): """ Subclass of :class:`~IQIntegration` returning a real number, combining weighted I and Q. Returns the integrated (I*w_a + Q*w_b) based on the integration weights, rather than the separate I and Q components as one complex number. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. weights_a The list of complex values used as weights :math:`A(t)` on the incoming complex signal. weights_b The list of complex values used as weights :math:`B(t)` on the incoming complex signal. duration The time in seconds spanned by the weight arrays. If omitted, the weights are assumed to be sampled at 1 GHz. Capped at 16384 ns. interpolation Specifies the kind of interpolation to use. The string has to be one of 'linear', 'nearest', 'nearest-up', 'zero', 'slinear', 'quadratic', 'cubic', 'previous', or 'next'. 'zero', 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of zeroth, first, second or third order; 'previous' and 'next' simply return the previous or next value of the point; 'nearest-up' and 'nearest' differ when interpolating half-integers (e.g. 0.5, 1.5) in that 'nearest-up' rounds up and 'nearest' rounds down. Default is 'linear'. Only used if weights are given. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. """ def __init__( self, port: str, clock: str, weights_a: list[complex] | np.ndarray, weights_b: list[complex] | np.ndarray, duration: float | None = None, interpolation: str = "linear", acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, ) -> None: super().__init__( port=port, clock=clock, weights_a=weights_a, weights_b=weights_b, duration=duration, interpolation=interpolation, acq_label=acq_label, coords=coords, average_segment_repetitions=average_segment_repetitions, ) self.data["acquisition_info"]["protocol"] = "IQIntegrationMixed"
[docs] class ThresholdedIQIntegration(Acquisition): """ Acquisition protocol allowing to control rotation and threshold. This acquisition protocol is similar to the :class:`~.IQIntegration` acquisition protocol, but the complex result is now rotated and thresholded to produce a "0" or a "1", as controlled by the parameters for rotation angle `<device_element>.measure.acq_rotation` and threshold value `<device_element>.measure.acq_threshold` in the device configuration (see example below). The rotation angle and threshold value for each qubit can be set through the device configuration. .. admonition:: Note Thresholded acquisition is currently only supported by the Qblox backend. .. admonition:: Examples .. jupyter-execute:: from qblox_scheduler.schedules.schedule import TimeableSchedule from qblox_applications.qblox_scheduler.superconducting.transmon.device_element import ( transmon_element, ) from qblox_scheduler import ThresholdedIQIntegration # set up qubit device_element = transmon_element.BasicTransmonElement("q0") device_element.clock_freqs.readout = 8.0e9 # set rotation and threshold value rotation, threshold = 20, -0.1 device_element.measure.acq_rotation = rotation device_element.measure.acq_threshold = threshold # basic schedule schedule = TimeableSchedule("thresholded acquisition") schedule.add(ThresholdedIQIntegration(port="q0:res", clock="q0.ro", duration=1e-6)) Parameters ---------- port : str The acquisition port. clock : str The clock used to demodulate the acquisition. duration : float The acquisition duration in seconds. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. feedback_trigger_label : str The label corresponding to the feedback trigger, which is mapped by the compiler to a feedback trigger address on hardware, by default None. """ def __init__( self, port: str, clock: str, duration: float, acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, feedback_trigger_label: str | None = None, acq_rotation: float = 0, acq_threshold: float = 0, ) -> None: bin_mode = BinMode.AVERAGE_APPEND waveform_i = { "port": port, "clock": clock, "duration": duration, "wf_func": "qblox_scheduler.waveforms.square", "amplitude": 1, } waveform_q = { "port": port, "clock": clock, "duration": duration, "wf_func": "qblox_scheduler.waveforms.square", "amplitude": (0 + 1j), } super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [waveform_i, waveform_q], "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": bin_mode, "average_segment_repetitions": average_segment_repetitions, "acq_return_type": float if average_segment_repetitions else np.int32, "protocol": "ThresholdedIQIntegration", "feedback_trigger_label": feedback_trigger_label, "acq_threshold": acq_threshold, "acq_rotation": acq_rotation, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] return self._get_signature(acq_info)
[docs] class ThresholdedIQIntegrationMixed(IQIntegrationMixed): """ Subclass of :class:`~IQIntegrationMixed` but Thresholded. Acquisition protocol allowing to control rotation and threshold. This acquisition protocol is similar to the :class:`~.IQIntegration` acquisition protocol, but the complex result is now rotated and thresholded to produce a "0" or a "1", as controlled by the parameters for rotation angle `<qubit>.measure.acq_rotation` and threshold value `<qubit>.measure.acq_threshold` in the device configuration (see example below). The rotation angle and threshold value for each qubit can be set through the device configuration. .. admonition:: Note Thresholded acquisition is currently only supported by the Qblox backend. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. weights_a The list of complex values used as weights :math:`A(t)` on the incoming complex signal. weights_b The list of complex values used as weights :math:`B(t)` on the incoming complex signal. duration The time in seconds spanned by the weight arrays. If omitted, the weights are assumed to be sampled at 1 GHz. Capped at 16384 ns. interpolation Specifies the kind of interpolation to use. The string has to be one of 'linear', 'nearest', 'nearest-up', 'zero', 'slinear', 'quadratic', 'cubic', 'previous', or 'next'. 'zero', 'slinear', 'quadratic' and 'cubic' refer to a spline interpolation of zeroth, first, second or third order; 'previous' and 'next' simply return the previous or next value of the point; 'nearest-up' and 'nearest' differ when interpolating half-integers (e.g. 0.5, 1.5) in that 'nearest-up' rounds up and 'nearest' rounds down. Default is 'linear'. Only used if weights are given. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. feedback_trigger_label : str The label corresponding to the feedback trigger, which is mapped by the compiler to a feedback trigger address on hardware, by default None. """ def __init__( self, port: str, clock: str, weights_a: list[complex] | np.ndarray, weights_b: list[complex] | np.ndarray, duration: float | None = None, interpolation: str = "linear", acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, feedback_trigger_label: str | None = None, acq_threshold: float = 0, ) -> None: super().__init__( port=port, clock=clock, weights_a=weights_a, weights_b=weights_b, duration=duration, interpolation=interpolation, acq_label=acq_label, coords=coords, average_segment_repetitions=average_segment_repetitions, ) self.data["acquisition_info"]["protocol"] = "ThresholdedIQIntegrationMixed" self.data["acquisition_info"]["feedback_trigger_label"] = feedback_trigger_label self.data["acquisition_info"]["acq_threshold"] = acq_threshold self.data["acquisition_info"]["acq_rotation"] = 360 - 45 self.data["acquisition_info"]["acq_return_type"] = ( float if average_segment_repetitions else np.int32 )
[docs] class TriggerCount(Acquisition): """ Trigger counting acquisition protocol returning an integer. The trigger acquisition mode is used to measure how many times the trigger level is surpassed. The level is set in the hardware configuration. .. important:: The exact duration of this operation, and the possible bin modes may depend on the control hardware. Please consult your hardware vendor's :ref:`Reference guide` for more information. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The duration of the operation in seconds. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. fine_start_delay Delays the start of the acquisition by the given amount in seconds. Does not delay the start time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. fine_end_delay Delays the end of the pulse by the given amount in seconds. Does not delay the end time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. """ def __init__( self, port: str, clock: str, duration: float, acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, fine_start_delay: float = 0, fine_end_delay: float = 0, ) -> None: bin_mode = BinMode.AVERAGE_APPEND super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [], "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": bin_mode, "average_segment_repetitions": average_segment_repetitions, "acq_return_type": int, "protocol": "TriggerCount", "fine_start_delay": fine_start_delay, "fine_end_delay": fine_end_delay, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] return self._get_signature(acq_info)
[docs] class TriggerCountDistribution(Acquisition): """ Trigger counting distribution acquisition protocol returning an integer. The trigger acquisition mode is used to measure how many times the trigger level is surpassed. The level is set in the hardware configuration. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The duration of the operation in seconds. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. fine_start_delay Delays the start of the acquisition by the given amount in seconds. Does not delay the start time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. fine_end_delay Delays the end of the pulse by the given amount in seconds. Does not delay the end time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. """ def __init__( self, port: str, clock: str, duration: float, acq_label: Hashable = 0, coords: dict | None = None, fine_start_delay: float = 0, fine_end_delay: float = 0, ) -> None: super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [], "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": BinMode.DISTRIBUTION, "acq_return_type": int, "protocol": "TriggerCountDistribution", "fine_start_delay": fine_start_delay, "fine_end_delay": fine_end_delay, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] return self._get_signature(acq_info)
[docs] class TimetagTrace(Acquisition): """ The TimetagTrace acquisition protocol records timetags within an acquisition window. .. important:: The exact duration of this operation, and the possible bin modes may depend on the control hardware. Please consult your hardware vendor's :ref:`Reference guide` for more information. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The acquisition duration in seconds. acq_label The data channel in which the acquisition is stored, is by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. time_ref Selects the time reference that the timetag is recorded in relation to. String enumeration, one of: * start (default): record relative to the start of the window. * end: record relative to the end of the window. Note that this always yields a negative timetag. * first: syntactic sugar for first#, where # is the current channel. * timestamp: record relative to the timestamp marked using the ``Timestamp`` operation. * ``port``: record relative to the timetag measured on another port. If this option is used, the ``time_ref_port`` argument must be specified as well. The acquisition operation that is measuring the timetag on the *other* port must end before or at the same time as *this* acquisition operation. time_ref_port If the ``port`` time reference is used, ``time_ref_port`` specifies the port on which the other acquisition is executed. fine_start_delay Delays the start of the acquisition by the given amount in seconds. Does not delay the start time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. fine_end_delay Delays the end of the pulse by the given amount in seconds. Does not delay the end time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. """ def __init__( self, duration: float, port: str, clock: str = DigitalClockResource.IDENTITY, acq_label: Hashable = 0, coords: dict | None = None, time_ref: TimeRef | str = TimeRef.START, time_ref_port: str | None = None, fine_start_delay: float = 0, fine_end_delay: float = 0, ) -> None: if not isinstance(duration, float): duration = float(duration) if time_ref == TimeRef.PORT and time_ref_port is None: raise ValueError(f"If {time_ref} is used, `time_ref_port` must contain a valid port.") super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [], "duration": duration, "port": port, "clock": clock, "acq_label": acq_label, "coords": coords, "bin_mode": BinMode.APPEND, # time_source is not settable, because all timetags will be returned # instead of just one. It is fixed to TimeSource.FIRST # because the instrument coordinator uses this to calculate the # relative timestamps. "time_source": TimeSource.FIRST, "time_ref": time_ref, "time_ref_port": time_ref_port, "time_ref_channel": None, # to be filled in by compiler "protocol": "TimetagTrace", "acq_return_type": np.ndarray, "fine_start_delay": fine_start_delay, "fine_end_delay": fine_end_delay, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] return self._get_signature(acq_info)
[docs] class Timetag(Acquisition): """ Acquire a single timetag per acquisition index. .. important:: The exact duration of this operation, and the possible bin modes may depend on the control hardware. Please consult your hardware vendor's :ref:`Reference guide` for more information. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The acquisition duration in seconds. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a device element idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. time_source Selects the timetag data source for this acquisition type. String enumeration, one of: * ``first`` (default): record the first timetag in the window. * ``second``: record the second timetag in the window. Can be used to measure pulse distance when combined with first as reference. * ``last``: record the last timetag in the window. time_ref Selects the time reference that the timetag is recorded in relation to. String enumeration, one of: * ``start`` (default): record relative to the start of the window. * ``end``: record relative to the end of the window. Note that this always yields a negative timetag. * ``first``: record relative to the first timetag in the window. * ``timestamp``: record relative to the timestamp marked using the :class:`~qblox_scheduler.operations.pulse_library.Timestamp` operation. * ``port``: record relative to the timetag measured on another port. If this option is used, the ``time_ref_port`` argument must be specified as well. The acquisition operation that is measuring the timetag on the *other* port must end before or at the same time as *this* acquisition operation. time_ref_port If the ``port`` time reference is used, ``time_ref_port`` specifies the port on which the other acquisition is executed. fine_start_delay Delays the start of the acquisition by the given amount in seconds. Does not delay the start time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. fine_end_delay Delays the end of the pulse by the given amount. Does not delay the end time of the operation in the schedule. If the hardware supports it, this parameter can be used to shift the acquisition window by a small amount of time, independent of the hardware instruction timing grid. Currently only implemented for Qblox QTM modules, which allow only positive values for this parameter. By default 0. """ def __init__( self, duration: float, port: str, clock: str = DigitalClockResource.IDENTITY, acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, time_source: TimeSource | str = TimeSource.FIRST, time_ref: TimeRef | str = TimeRef.START, time_ref_port: str | None = None, fine_start_delay: float = 0, fine_end_delay: float = 0, ) -> None: bin_mode = BinMode.AVERAGE_APPEND super().__init__(name=self.__class__.__name__) if isinstance(time_source, str): time_source = TimeSource(time_source) if isinstance(time_ref, str): time_ref = TimeRef(time_ref) if time_ref == TimeRef.PORT and time_ref_port is None: raise ValueError(f"If {time_ref} is used, `time_ref_port` must contain a valid port.") self.data["acquisition_info"] = { "waveforms": [], "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": bin_mode, "average_segment_repetitions": average_segment_repetitions, "time_source": time_source, "time_ref": time_ref, "time_ref_port": time_ref_port, "time_ref_channel": None, # to be filled in by compiler "acq_return_type": float, "protocol": "Timetag", "fine_start_delay": fine_start_delay, "fine_end_delay": fine_end_delay, } def __str__(self) -> str: acq_info = self.data["acquisition_info"] return self._get_signature(acq_info)
[docs] class ThresholdedTriggerCount(Acquisition): """ Thresholded trigger counting acquisition protocol returning the comparison result with a threshold. If the number of triggers counted is less than the threshold, a 0 is returned, otherwise a 1. The analog threshold for registering a single count is set in the hardware configuration. .. important:: The exact duration of this operation, and the possible bin modes may depend on the control hardware. Please consult your hardware vendor's :ref:`Reference guide` for more information. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The duration of the operation in seconds. threshold The threshold of the ThresholdedTriggerCount acquisition. feedback_trigger_label The label corresponding to the feedback trigger, which is mapped by the compiler to a feedback trigger address on hardware, by default None. Note: this label is merely used to link this acquisition together with a ConditionalOperation. It does not affect the acquisition result. feedback_trigger_condition The comparison condition (greater-equal, less-than) for the ThresholdedTriggerCount acquisition. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a qubit idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. """ def __init__( self, port: str, clock: str, duration: float, threshold: int, *, feedback_trigger_label: str | None = None, feedback_trigger_condition: ( str | TriggerCondition ) = TriggerCondition.GREATER_THAN_EQUAL_TO, acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, ) -> None: bin_mode = BinMode.AVERAGE_APPEND super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [], "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": bin_mode, "average_segment_repetitions": average_segment_repetitions, "thresholded_trigger_count": { "threshold": threshold, "condition": feedback_trigger_condition, }, "feedback_trigger_label": feedback_trigger_label, "feedback_trigger_address": None, "acq_return_type": int, "protocol": "ThresholdedTriggerCount", } def __str__(self) -> str: acq_info = copy(self.data["acquisition_info"]) # The __str__ method is used for serialization. The keys must match the input parameters. acq_info["threshold"] = acq_info["thresholded_trigger_count"]["threshold"] acq_info["feedback_trigger_condition"] = acq_info["thresholded_trigger_count"]["condition"] return self._get_signature(acq_info)
[docs] class DualThresholdedTriggerCount(Acquisition): """ Thresholded trigger count protocol that uses two thresholds. Four outcomes are possible for this measurement, and each of those four can be assigned a label to use in a :class:`~qblox_scheduler.operations.control_flow_library.ConditionalOperation`: - "low" if ``counts < threshold_low``, - "mid" if ``threshold_low <= counts < threshold_high``, - "high" if ``counts >= threshold_high``, - "invalid" if the counts are invalid (can occur in very rare cases, e.g. when the counter overflows). The returned acquisition data is the raw number of counts. .. important:: The exact duration of this operation, and the possible bin modes may depend on the control hardware. Please consult your hardware vendor's :ref:`Reference guide` for more information. Parameters ---------- port The acquisition port. clock The clock used to demodulate the acquisition. duration The duration of the operation in seconds. threshold_low The lower counts threshold of the ThresholdedTriggerCount acquisition. threshold_high The upper counts threshold of the ThresholdedTriggerCount acquisition. label_low The label that can be used to link a result of `counts < threshold_low` to a ConditionalOperation, by default None. label_mid The label that can be used to link a result of `threshold_low <= counts < threshold_high` to a ConditionalOperation, by default None. label_high The label that can be used to link a result of `counts >= threshold_high` to a ConditionalOperation, by default None. label_invalid The label that can be used to link an invalid counts result (e.g. a counter overflow) to a ConditionalOperation, by default None. feedback_trigger_condition The comparison condition (greater-equal, less-than) for the ThresholdedTriggerCount acquisition. acq_label The data channel in which the acquisition is stored, by default 0. Describes the "where" information of the measurement, which typically corresponds to a qubit idx. coords Coords for the acquisition. These coordinates for the measured value for this operation appear in the retrieved acquisition data. For example ``coords={"amp": 0.1}`` has the effect, that the measured value for this acquisition will be associated with ``amp==0.1``. By default ``None``, no coords are added. average_segment_repetitions Specifies whether acquisitions in each segment are averaged or kept separately/appended. """ def __init__( self, port: str, clock: str, duration: float, threshold_low: int, threshold_high: int, *, label_low: str | None = None, label_mid: str | None = None, label_high: str | None = None, label_invalid: str | None = None, acq_label: Hashable = 0, coords: dict | None = None, average_segment_repetitions: bool = True, ) -> None: bin_mode = BinMode.AVERAGE_APPEND super().__init__(name=self.__class__.__name__) self.data["acquisition_info"] = { "waveforms": [], "clock": clock, "port": port, "duration": duration, "acq_label": acq_label, "coords": coords, "bin_mode": bin_mode, "average_segment_repetitions": average_segment_repetitions, "thresholded_trigger_count": { "threshold_low": threshold_low, "threshold_high": threshold_high, }, "feedback_trigger_labels": { DualThresholdedTriggerCountLabels.LOW: label_low, DualThresholdedTriggerCountLabels.MID: label_mid, DualThresholdedTriggerCountLabels.HIGH: label_high, DualThresholdedTriggerCountLabels.INVALID: label_invalid, }, "acq_return_type": int, "protocol": "DualThresholdedTriggerCount", } def __str__(self) -> str: acq_info = copy(self.data["acquisition_info"]) acq_info["threshold_low"] = acq_info["thresholded_trigger_count"]["threshold_low"] acq_info["threshold_high"] = acq_info["thresholded_trigger_count"]["threshold_high"] # Pyright thinks custom StrEnum is not iterable. for suffix in DualThresholdedTriggerCountLabels: # type: ignore acq_info[f"label_{suffix}"] = acq_info["feedback_trigger_labels"][suffix] return self._get_signature(acq_info)