Source code for qblox_scheduler.operations.pulse_library

# Repository: https://gitlab.com/qblox/packages/software/qblox-scheduler
# Licensed according to the LICENSE file on the main branch
#
# Copyright 2020-2025, Quantify Consortium
# Copyright 2025, Qblox B.V.
"""Standard pulse-level operations for use with the qblox_scheduler."""

from __future__ import annotations

import warnings
from abc import ABC
from dataclasses import dataclass
from inspect import get_annotations
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Final, Literal
from typing_extensions import TypedDict, Unpack

import numpy as np
from pydantic import ConfigDict, Field, NonNegativeFloat, TypeAdapter
from qcodes import validators

from qblox_scheduler.helpers.deprecation import deprecated_arg_alias
from qblox_scheduler.helpers.importers import export_python_object_to_path_string
from qblox_scheduler.operations.expressions import Expression  # noqa: TC001
from qblox_scheduler.operations.operation import Operation
from qblox_scheduler.resources import BasebandClockResource, DigitalClockResource

if TYPE_CHECKING:
    from collections.abc import Sequence

    from qblox_scheduler.device_under_test.transmon_element import (
        ReferenceMagnitude as ReferenceMagnitudeSubmodule,
    )


# TODO: Deprecate?
@dataclass
[docs] class ReferenceMagnitude: """Dataclass defining a reference level for pulse amplitudes in units of 'V', 'dBm', or 'A'."""
[docs] value: float
[docs] unit: Literal["V", "dBm", "A"]
@classmethod
[docs] def from_parameter( cls, parameter: ReferenceMagnitudeSubmodule | None ) -> ReferenceMagnitude | None: """Initialize from ReferenceMagnitude QCoDeS InstrumentChannel values.""" if not parameter: return None value, unit = parameter.get_val_unit() if np.isnan(value): return None if unit not in (allowed_units := ["V", "dBm", "A"]): raise ValueError(f"Invalid unit: {unit}. Allowed units: {allowed_units}") return cls(value, unit)
def __hash__(self) -> int: return hash((self.value, self.unit)) def __setstate__(self, state: dict[str, Any]) -> dict: self.value = state["value"] self.unit = state["unit"] def __getstate__(self) -> None: dtype = export_python_object_to_path_string(self.__class__) self.__dict__["deserialization_type"] = dtype return self.__dict__
[docs] class PulseCommonArgs(TypedDict, total=False): """Dict for representation and validation of timing-related pulse parameters.""" __pydantic_config__ = ConfigDict(arbitrary_types_allowed=True)
[docs] duration: Annotated[NonNegativeFloat | Expression, Field(default=0.0)]
"""The pulse duration in seconds."""
[docs] port: Annotated[str | None, Field(default=None)]
"""Port of the pulse."""
[docs] clock: Annotated[str, Field(default=BasebandClockResource.IDENTITY)]
"""Clock used to modulate the pulse."""
[docs] t0: Annotated[NonNegativeFloat | Expression, Field(default=0.0)]
""" Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. """
[docs] class PulseSignalArgs(TypedDict, total=False): """Dict for representation and validation of signal-related pulse parameters.""" __pydantic_config__ = ConfigDict(arbitrary_types_allowed=True)
[docs] frequency: Annotated[float | Expression | None, Field(default=None)]
"""Optional frequency to override the clock frequency for this operation."""
[docs] phase_shift: Annotated[float | Expression, Field(default=0.0)]
""" Optional shift to the phase of the NCO. Allowed values: [0, 4*360]. Values outside of this range will be automatically wrapped around. Note that this will be applied to all subsequent operations by default. """
[docs] voltage_offset: Annotated[float | Expression, Field(default=0.0)]
"""Optional static voltage offset. Allowed values: [-1, 1]."""
[docs] class _AllPulseArgs(PulseCommonArgs, PulseSignalArgs): """Dict for annotation of all pulse parameters.""" pass
[docs] class _PulseOperationBase(ABC, Operation): """Base class for pulse operations."""
[docs] wf_func: ClassVar[str | None] = None
[docs] ignored_pulse_params: ClassVar[set[str]] = set()
[docs] _common_args_adapter: ClassVar[TypeAdapter] = TypeAdapter(PulseCommonArgs)
[docs] _signal_args_adapter: ClassVar[TypeAdapter] = TypeAdapter(PulseSignalArgs)
[docs] _ALLOWED_ARGS: Final[set[str]] = ( get_annotations(PulseCommonArgs).keys() | get_annotations(PulseSignalArgs).keys() )
def __init__( self, **pulse_args: Unpack[_AllPulseArgs], ) -> None: super().__init__(name=self.__class__.__name__) # Show a warning if the user passed parameters that have no effect self._warn_ignored_params(pulse_args.keys()) # **pulse_args will catch anything, even non-existent args, so we check whether # there are any outside the accepted pulse args and raise a `TypeError` # that emulates the standard Python behaviour for invalid kwargs. # NOTE: We rely on dict insertion order. if first_invalid_arg := next( (arg for arg in pulse_args if arg not in self._ALLOWED_ARGS), None ): raise TypeError( f"{self.__class__.__name__} got an unexpected keyword argument " f"'{first_invalid_arg}'" ) # Validate common and signal args using Pydantic common_args = self._common_args_adapter.validate_python(pulse_args) signal_args = self._signal_args_adapter.validate_python(pulse_args) # Exclude unset keys (different from excluding defaults) signal_args = {k: signal_args[k] for k in signal_args.keys() & pulse_args.keys()} # Set the `pulse_info` for the operation with validated args self.data["pulse_info"] = {"wf_func": self.wf_func, **common_args, **signal_args} def __str__(self) -> str: """Provide a string representation of the pulse.""" return self._get_signature(self.data["pulse_info"]) @classmethod
[docs] def _warn_ignored_params(cls, given_params: set[str]) -> None: """Warn the user of parameters that have no effect for the current pulse.""" warn_params = sorted(given_params & cls.ignored_pulse_params) if len(warn_params) == 0: return elif len(warn_params) == 1: msg_part = f"{warn_params[0]} parameter has" else: # > 1 msg_part = f"{', '.join(warn_params[:-1])} and {warn_params[-1]} parameters have" warnings.warn( f"The {msg_part} no effect when used with {cls.__name__}", UserWarning, stacklevel=2, )
[docs] class ShiftClockPhase(_PulseOperationBase): """ Operation that shifts the phase of a clock by a specified amount. This is a low-level operation and therefore depends on the backend. Currently only implemented for Qblox backend, refer to :class:`~qblox_scheduler.backends.qblox.operation_handling.virtual.NcoPhaseShiftStrategy` for more details. Parameters ---------- phase_shift The phase shift in degrees. clock The clock of which to shift the phase. t0 Time in seconds when to execute the command relative to the start time of the Operation in the TimeableSchedule. """ def __init__( self, phase_shift: float | Expression, clock: str, t0: float | Expression = 0.0, ) -> None: super().__init__(clock=clock, t0=t0, phase_shift=phase_shift)
[docs] class ResetClockPhase(_PulseOperationBase): """ An operation that resets the phase of the NCO clock. Bear in mind, that this might not reset the full phase, but only just the phase of one component of the frequency. Parameters ---------- clock The clock of which to reset the phase. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. """ def __init__( self, clock: str, t0: float | Expression = 0.0, ) -> None: super().__init__(clock=clock, t0=t0) self.data["pulse_info"]["reset_clock_phase"] = True
[docs] class SetClockFrequency(_PulseOperationBase): """ Operation that sets updates the frequency of a clock. This is a low-level operation and therefore depends on the backend. Currently only implemented for Qblox backend, refer to :class:`~qblox_scheduler.backends.qblox.operation_handling.virtual.NcoSetClockFrequencyStrategy` for more details. Parameters ---------- clock The clock for which a new frequency is to be set. frequency The new frequency in Hz. If None, it will reset to the clock frequency set by the configuration or resource. t0 Time in seconds when to execute the command relative to the start time of the Operation in the TimeableSchedule. """ @deprecated_arg_alias("2.0", clock_freq_new="frequency") def __init__( self, clock: str, frequency: float | Expression | None, t0: float | Expression = 0.0, ) -> None: super().__init__(clock=clock, t0=t0, frequency=frequency) self.data["pulse_info"] |= { "clock_freq_new": self.data["pulse_info"].pop("frequency"), "clock_freq_old": None, "interm_freq_old": None, } def __str__(self) -> str: # FIXME: Necessary to make `eval(str(operation))` work pulse_info = self.data["pulse_info"].copy() pulse_info["frequency"] = pulse_info.pop("clock_freq_new") return self._get_signature(pulse_info)
[docs] class VoltageOffset(_PulseOperationBase): """ Operation that represents setting a constant offset to the output voltage. Please refer to :ref:`sec-qblox-offsets-long-voltage-offsets` in the reference guide for more details. Parameters ---------- offset_path_I : float Offset of path I. offset_path_Q : float Offset of path Q. port : str Port of the voltage offset. clock : str, Optional Clock used to modulate the voltage offset. By default the baseband clock. t0 : float, Optional Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. reference_magnitude : Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. """ def __init__( self, offset_path_I: float | Expression, offset_path_Q: float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, t0: float | Expression = 0.0, reference_magnitude: ReferenceMagnitude | None = None, ) -> None: super().__init__(port=port, clock=clock, t0=t0) self.data["pulse_info"] |= { "offset_path_I": offset_path_I, "offset_path_Q": offset_path_Q, "reference_magnitude": reference_magnitude, }
[docs] class IdlePulse(_PulseOperationBase): """ The IdlePulse Operation is a placeholder for a specified duration of time. Parameters ---------- duration The duration of idle time in seconds. """ def __init__( self, duration: float | Expression, ) -> None: super().__init__(duration=duration)
[docs] class RampPulse(_PulseOperationBase): r""" RampPulse Operation is a pulse that ramps from zero to a set amplitude over its duration. The pulse is given as a function of time :math:`t` and the parameters offset and amplitude by .. math:: P(t) = \mathrm{offset} + t \times \frac{\mathrm{amplitude}}{\mathrm{duration}} Parameters ---------- amplitude Unitless amplitude of the ramp envelope function. duration The pulse duration in seconds. offset Starting point of the ramp pulse port Port of the pulse. clock Clock used to modulate the pulse. By default the baseband clock. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. """
[docs] wf_func = "qblox_scheduler.waveforms.ramp"
@deprecated_arg_alias("2.0", amp="amplitude", offset="voltage_offset") def __init__( self, amplitude: float | Expression, duration: float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, reference_magnitude: ReferenceMagnitude | None = None, voltage_offset: float | Expression = 0.0, t0: float | Expression = 0.0, ) -> None: super().__init__( duration=duration, port=port, clock=clock, t0=t0, voltage_offset=voltage_offset ) self.data["pulse_info"] |= { "amplitude": amplitude, "reference_magnitude": reference_magnitude, "offset": self.data["pulse_info"].pop("voltage_offset"), } def __str__(self) -> str: # FIXME: Necessary to make `eval(str(operation))` work pulse_info = self.data["pulse_info"].copy() pulse_info["voltage_offset"] = pulse_info.pop("offset") return self._get_signature(pulse_info)
[docs] class StaircasePulse(_PulseOperationBase): """ A real valued staircase pulse, which reaches it's final amplitude in discrete steps. In between it will maintain a plateau. Parameters ---------- start_amp Starting unitless amplitude of the staircase envelope function. final_amp Final unitless amplitude of the staircase envelope function. num_steps The number of plateaus. duration Duration of the pulse in seconds. port Port of the pulse. clock Clock used to modulate the pulse. By default the baseband clock. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. """
[docs] wf_func = "qblox_scheduler.waveforms.staircase"
def __init__( self, start_amp: float | Expression, final_amp: float | Expression, num_steps: int, duration: float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0) self.data["pulse_info"] |= { "start_amp": start_amp, "final_amp": final_amp, "reference_magnitude": reference_magnitude, "num_steps": num_steps, }
[docs] class MarkerPulse(_PulseOperationBase): """ Digital pulse that is HIGH for the specified duration. Marker pulse is played on marker output. Currently only implemented for Qblox backend. Parameters ---------- duration Duration of the HIGH signal. port Name of the associated port. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. clock Name of the associated clock. By default :class:`~qblox_scheduler.resources.DigitalClockResource`. This only needs to be specified if a custom clock name is used for a digital channel (for example, when a port-clock combination of a device element is used with a digital channel). fine_start_delay Delays the start of the pulse 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 pulse 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 pulse 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 | Expression, port: str, t0: float | Expression = 0.0, clock: str = DigitalClockResource.IDENTITY, fine_start_delay: float | Expression = 0.0, fine_end_delay: float | Expression = 0.0, ) -> None: super().__init__(duration=duration, port=port, t0=t0, clock=clock) self.data["pulse_info"] |= { "marker_pulse": True, # This distinguishes MarkerPulse from other operations "fine_start_delay": fine_start_delay, "fine_end_delay": fine_end_delay, }
[docs] class SquarePulse(_PulseOperationBase): """ A real-valued pulse with the specified amplitude during the pulse. Parameters ---------- amplitude Unitless complex valued amplitude of the envelope. duration The pulse duration in seconds. port Port of the pulse, must be capable of playing a complex waveform. clock Clock used to modulate the pulse. By default the baseband clock. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.square"
@deprecated_arg_alias("2.0", amp="amplitude") def __init__( self, amplitude: complex | float | Expression | Sequence[complex | float | Expression], duration: complex | float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "reference_magnitude": reference_magnitude, }
[docs] class SuddenNetZeroPulse(_PulseOperationBase): """ A pulse that can be used to implement a conditional phase gate in transmon device elements. The sudden net-zero (SNZ) pulse is defined in :cite:t:`negirneac_high_fidelity_2021`. Parameters ---------- amp_A Unitless amplitude of the main square pulse. amp_B Unitless scaling correction for the final sample of the first square and first sample of the second square pulse. net_zero_A_scale Amplitude scaling correction factor of the negative arm of the net-zero pulse. t_pulse The total duration of the two half square pulses t_phi The idling duration between the two half pulses t_integral_correction The duration in which any non-zero pulse amplitude needs to be corrected. port Port of the pulse, must be capable of playing a complex waveform. clock Clock used to modulate the pulse. By default the baseband clock. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.sudden_net_zero"
# Voltage offset does not make sense here because it's net-zero
[docs] ignored_pulse_params: ClassVar[set[str]] = {"voltage_offset"}
def __init__( self, amp_A: float | Expression, amp_B: float | Expression, net_zero_A_scale: float | Expression, t_pulse: float | Expression, t_phi: float | Expression, t_integral_correction: float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: duration = t_pulse + t_phi + t_integral_correction super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amp_A": amp_A, "amp_B": amp_B, "reference_magnitude": reference_magnitude, "net_zero_A_scale": net_zero_A_scale, "t_pulse": t_pulse, "t_phi": t_phi, "t_integral_correction": t_integral_correction, "phase": 0, }
[docs] def decompose_long_square_pulse( duration: float, duration_max: float, single_duration: bool = False, **kwargs ) -> list[SquarePulse]: """ Generates a list of square pulses equivalent to a (very) long square pulse. Intended to be used for waveform-memory-limited devices. Effectively, only two square pulses, at most, will be needed: a main one of duration ``duration_max`` and a second one for potential mismatch between N ``duration_max`` and overall `duration`. Parameters ---------- duration Duration of the long pulse in seconds. duration_max Maximum duration of square pulses to be generated in seconds. single_duration If ``True``, only square pulses of duration ``duration_max`` will be generated. If ``False``, a square pulse of ``duration`` < ``duration_max`` might be generated if necessary. **kwargs Other keyword arguments to be passed to the :class:`~SquarePulse`. Returns ------- : A list of :class`SquarePulse` s equivalent to the desired long pulse. """ # Sanity checks validator_dur = validators.Numbers(min_value=0.0, max_value=7 * 24 * 3600.0) validator_dur.validate(duration) validator_dur_max = validators.Numbers(min_value=0.0, max_value=duration) validator_dur_max.validate(duration_max) duration_last_pulse = duration % duration_max num_pulses = int(duration // duration_max) pulses = [SquarePulse(duration=duration_max, **kwargs) for _ in range(num_pulses)] if duration_last_pulse != 0.0: duration_last_pulse = duration_max if single_duration else duration_last_pulse pulses.append(SquarePulse(duration=duration_last_pulse, **kwargs)) return pulses
[docs] class SoftSquarePulse(_PulseOperationBase): """ A real valued square pulse convolved with a Hann window for smoothing. Parameters ---------- amplitude Unitless amplitude of the envelope. duration The pulse duration in seconds. port Port of the pulse, must be capable of playing a complex waveform. clock Clock used to modulate the pulse. By default the baseband clock. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.soft_square"
@deprecated_arg_alias("2.0", amp="amplitude") def __init__( self, amplitude: float | Expression | Sequence[float | Expression], duration: float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "reference_magnitude": reference_magnitude, }
[docs] class ChirpPulse(_PulseOperationBase): """ A linear chirp signal. A sinusoidal signal that ramps up in frequency. Parameters ---------- amplitude Unitless amplitude of the envelope. duration Duration of the pulse. port The port of the pulse. clock Clock used to modulate the pulse. start_freq Start frequency of the Chirp. Note that this is the frequency at which the waveform is calculated, this may differ from the clock frequency. end_freq End frequency of the Chirp. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Shift of the start time with respect to the start of the operation. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.chirp"
# Frequency does not make sense here because it's defined by `start_freq` and `end_freq`
[docs] ignored_pulse_params: ClassVar[set[str]] = {"frequency"}
@deprecated_arg_alias("2.0", amp="amplitude") def __init__( self, amplitude: float | Expression | Sequence[float | Expression], duration: float | Expression, port: str, clock: str, start_freq: float | Expression, end_freq: float | Expression, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "reference_magnitude": reference_magnitude, "start_freq": start_freq, "end_freq": end_freq, }
[docs] class DRAGPulse(_PulseOperationBase): r""" A Gaussian pulse with a derivative component added to the out-of-phase channel. It uses the specified amplitude and sigma. If sigma is not specified it is set to 1/4 of the duration. The DRAG pulse is intended for single qubit gates in transmon based systems. It can be calibrated to reduce unwanted excitations of the :math:`|1\rangle - |2\rangle` transition (:cite:t:`motzoi_simple_2009` and :cite:t:`gambetta_analytic_2011`). The waveform is generated using :func:`.waveforms.drag` . Parameters ---------- amplitude Unitless amplitude of the Gaussian envelope. beta Unitless amplitude of the derivative component, the DRAG-pulse parameter. duration The pulse duration in seconds. phase Phase of the pulse in degrees. clock Clock used to modulate the pulse. port Port of the pulse, must be capable of carrying a complex waveform. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. sigma Width of the Gaussian envelope in seconds. If not provided, the sigma is set to 1/4 of the duration. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.drag"
def __init__( self, amplitude: float | Expression, beta: float, phase: float | Expression, duration: float | Expression, port: str, clock: str, reference_magnitude: ReferenceMagnitude | None = None, sigma: float | Expression | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "beta": beta, "reference_magnitude": reference_magnitude, "phase": phase, "nr_sigma": 4 if sigma is None else None, "sigma": sigma, }
[docs] class GaussPulse(_PulseOperationBase): r""" The GaussPulse Operation is a real-valued pulse with the specified amplitude and sigma. If sigma is not specified it is set to 1/4 of the duration. The waveform is generated using :func:`.waveforms.drag` with a beta set to zero, corresponding to a Gaussian pulse. Parameters ---------- amplitude Unitless amplitude of the Gaussian envelope. duration The pulse duration in seconds. phase Phase of the pulse in degrees. clock Clock used to modulate the pulse. By default the baseband clock. port Port of the pulse, must be capable of carrying a complex waveform. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. sigma Width of the Gaussian envelope in seconds. If not provided, the sigma is set to 1/4 of the duration. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.drag"
def __init__( self, amplitude: float | Expression, phase: float | Expression, duration: float | Expression, port: str, clock: str = BasebandClockResource.IDENTITY, reference_magnitude: ReferenceMagnitude | None = None, sigma: float | Expression | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "beta": 0, "reference_magnitude": reference_magnitude, "phase": phase, "nr_sigma": 4 if sigma is None else None, "sigma": sigma, }
@deprecated_arg_alias("2.0", amp="amplitude")
[docs] def create_dc_compensation_pulse( pulses: list[Operation], sampling_rate: float, port: str, t0: float = 0.0, amplitude: float | None = None, reference_magnitude: ReferenceMagnitude | None = None, # noqa: ARG001 duration: float | None = None, ) -> SquarePulse: """ Calculates a SquarePulse to counteract charging effects based on a list of pulses. The compensation is calculated by summing the area of all pulses on the specified port. This gives a first order approximation for the pulse required to compensate the charging. All modulated pulses ignored in the calculation. Parameters ---------- pulses List of pulses to compensate sampling_rate Resolution to calculate the enclosure of the pulses to calculate the area to compensate. amplitude Desired unitless amplitude of the DC compensation SquarePulse. Leave to None to calculate the value for compensation, in this case you must assign a value to duration. The sign of the amplitude is ignored and adjusted automatically to perform the compensation. duration Desired pulse duration in seconds. Leave to None to calculate the value for compensation, in this case you must assign a value to amplitude. The sign of the value of amplitude given in the previous step is adjusted to perform the compensation. port Port to perform the compensation. Any pulse that does not belong to the specified port is ignored. clock Clock used to modulate the pulse. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. phase Phase of the pulse in degrees. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. Returns ------- : Returns a SquarePulse object that compensates all pulses passed as argument. """ # Prevent circular import. from qblox_scheduler.helpers.waveforms import area_pulses def _extract_pulses(pulses: list[Operation], port: str) -> list[dict[str, Any]]: # Collect all pulses for the given port pulse_info_list: list[dict[str, Any]] = [] for pulse in pulses: pulse_info = pulse["pulse_info"] if pulse_info["port"] == port and pulse_info["clock"] == BasebandClockResource.IDENTITY: pulse_info_list.append(pulse_info) return pulse_info_list # Make sure that the list contains at least one element if len(pulses) == 0: raise ValueError( "Attempting to create a DC compensation SquarePulse with no pulses. " "At least one pulse is necessary." ) pulse_info_list: list[dict[str, Any]] = _extract_pulses(pulses, port) # Calculate the area given by the list of pulses area: float = area_pulses(pulse_info_list, sampling_rate) # Calculate the compensation amplitude and duration based on area c_duration: float c_amp: float if amplitude is None and duration is not None: if not duration > 0: raise ValueError( f"Attempting to create a DC compensation SquarePulse specified by {duration=}. " f"Duration must be a positive number." ) c_duration = duration c_amp = -area / c_duration elif amplitude is not None and duration is None: c_amp = -abs(amplitude) if area > 0 else abs(amplitude) c_duration = abs(area / c_amp) else: raise ValueError( "The DC compensation SquarePulse allows either amplitude or duration to " + "be specified, not both. Both amplitude and duration were passed." ) return SquarePulse( amplitude=c_amp, duration=c_duration, port=port, clock=BasebandClockResource.IDENTITY, t0=t0, )
[docs] class WindowOperation(_PulseOperationBase): """ The WindowOperation is an operation for visualization purposes. The :class:`~WindowOperation` has a starting time and duration. Parameters ---------- window_name Name of the window operation """ def __init__( self, window_name: str, duration: float | Expression, t0: float | Expression = 0.0, ) -> None: super().__init__(duration=duration, t0=t0) self.data["pulse_info"]["window_name"] = window_name @property
[docs] def window_name(self) -> str: """Return the window name of this operation.""" return self.data["pulse_info"]["window_name"]
[docs] class NumericalPulse(_PulseOperationBase): """ A pulse where the shape is determined by specifying an array of (complex) points. If points are required between the specified samples (such as could be required by the sampling rate of the hardware), meaning :math:`t[n] < t' < t[n+1]`, `scipy.interpolate.interp1d` will be used to interpolate between the two points and determine the value. Parameters ---------- samples An array of (possibly complex) values specifying the shape of the pulse. t_samples An array of values specifying the corresponding times at which the ``samples`` are evaluated. port The port that the pulse should be played on. clock Clock used to (de)modulate the pulse. By default the baseband clock. amplitude Gain factor between -1 and 1 that multiplies with the samples, by default 1. reference_magnitude Scaling value and unit for the unitless samples. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. interpolation Specifies the type of interpolation used. This is passed as the "kind" argument to `scipy.interpolate.interp1d`. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.interpolated_complex_waveform"
@deprecated_arg_alias("2.0", gain="amplitude") def __init__( self, samples: np.ndarray | list, t_samples: np.ndarray | list, port: str, clock: str = BasebandClockResource.IDENTITY, amplitude: complex | float | Expression | Sequence[complex | float | Expression] = 1.0, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, interpolation: str = "linear", **signal_args: Unpack[PulseSignalArgs], ) -> None: def make_list_from_array(val: np.ndarray[float] | list[float]) -> list[float]: """Needed since numpy arrays break the (de)serialization code (#146).""" if isinstance(val, np.ndarray): new_val: list[float] = val.tolist() return new_val return val duration = t_samples[-1] - t_samples[0] samples, t_samples = map(make_list_from_array, [samples, t_samples]) super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "samples": samples, "t_samples": t_samples, "reference_magnitude": reference_magnitude, "interpolation": interpolation, }
[docs] class SkewedHermitePulse(_PulseOperationBase): """ Hermite pulse intended for single qubit gates in diamond based systems. The waveform is generated using :func:`~qblox_scheduler.waveforms.skewed_hermite`. Parameters ---------- duration The pulse duration in seconds. amplitude Unitless amplitude of the hermite pulse. skewness Skewness in the frequency space. phase Phase of the pulse in degrees. clock Clock used to modulate the pulse. port Port of the pulse, must be capable of carrying a complex waveform. reference_magnitude Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. t0 Time in seconds when to start the pulses relative to the start time of the Operation in the TimeableSchedule. By default 0. **signal_args Additional signal parameters defined in :class:`PulseSignalArgs`. """
[docs] wf_func = "qblox_scheduler.waveforms.skewed_hermite"
def __init__( self, duration: float | Expression, amplitude: float | Expression, skewness: float | Expression, phase: float | Expression, port: str, clock: str, reference_magnitude: ReferenceMagnitude | None = None, t0: float | Expression = 0.0, **signal_args: Unpack[PulseSignalArgs], ) -> None: super().__init__(duration=duration, port=port, clock=clock, t0=t0, **signal_args) self.data["pulse_info"] |= { "amplitude": amplitude, "reference_magnitude": reference_magnitude, "skewness": skewness, "phase": phase, }
[docs] class Timestamp(_PulseOperationBase): """ Operation that marks a time reference for timetags. Specifically, all timetags in :class:`~qblox_scheduler.operations.acquisition_library.Timetag` and :class:`~qblox_scheduler.operations.acquisition_library.TimetagTrace` are measured relative to the timing of this operation, if they have a matching port and clock, and if ``time_ref=TimeRef.TIMESTAMP`` is given as an argument. Parameters ---------- port The same port that the timetag acquisition is defined on. clock The same clock that the timetag acquisition is defined on. t0 Time offset (in seconds) of this Operation, relative to the start time in the TimeableSchedule. By default 0. """ def __init__( self, port: str, t0: float | Expression = 0.0, clock: str = DigitalClockResource.IDENTITY, ) -> None: super().__init__(port=port, clock=clock, t0=t0) self.data["pulse_info"]["timestamp"] = True