Skip to content

QuAM Pulses API

Welcome to the QuAM Pulses API Documentation. The QuAM Pulses module offers a versatile framework for creating and controlling pulse schemes essential for quantum operations. Information can be found in QuAM Pulses Documentation in the User Guide.

This section provides detailed API references for various pulse types—ranging from simple waveforms to complex modulated pulses—tailored for precise quantum state manipulation and measurement. Explore the properties, methods, and examples to effectively integrate these pulse components into your quantum experiments.

BaseReadoutPulse

Bases: Pulse, ABC

QuAM abstract base component for a general readout pulse.

Readout pulse classes should usually inherit from ReadoutPulse, the exception being when a custom integration weights function is required.

Parameters:

Name Type Description Default
length int

The length of the pulse in samples.

required
digital_marker (str, list)

The digital marker to use for the pulse. Default is "ON".

required
Source code in quam/components/pulses.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
@quam_dataclass
class BaseReadoutPulse(Pulse, ABC):
    """QuAM abstract base component for a general  readout pulse.

    Readout pulse classes should usually inherit from `ReadoutPulse`, the
    exception being when a custom integration weights function is required.

    Args:
        length (int): The length of the pulse in samples.
        digital_marker (str, list, optional): The digital marker to use for the pulse.
            Default is "ON".
    """

    operation: ClassVar[str] = "measurement"
    digital_marker: str = "ON"

    # TODO Understand why the thresholds were added.
    threshold: float = None
    rus_exit_threshold: float = None

    _weight_labels: ClassVar[List[str]] = ["iw1", "iw2", "iw3"]

    @property
    def integration_weights_names(self):
        return [f"{self.name}{str_ref.DELIMITER}{name}" for name in self._weight_labels]

    @property
    def integration_weights_mapping(self):
        return dict(zip(self._weight_labels, self.integration_weights_names))

    @abstractmethod
    def integration_weights_function(self) -> Dict[str, List[Tuple[float, int]]]:
        """Abstract method to calculate the integration weights.

        Returns:
            Dict containing keys "real", "imag", "minus_real", "minus_imag".
            Values are lists of tuples of (weight, length) pairs.
        """
        ...

    def _config_add_integration_weights(self, config: dict):
        """Add the integration weights to the config"""
        integration_weights = self.integration_weights_function()

        config["integration_weights"][self.integration_weights_names[0]] = {
            "cosine": integration_weights["real"],
            "sine": integration_weights["minus_imag"],
        }
        config["integration_weights"][self.integration_weights_names[1]] = {
            "cosine": integration_weights["imag"],
            "sine": integration_weights["real"],
        }
        config["integration_weights"][self.integration_weights_names[2]] = {
            "cosine": integration_weights["minus_imag"],
            "sine": integration_weights["minus_real"],
        }

        pulse_config = config["pulses"][self.pulse_name]
        pulse_config["integration_weights"] = self.integration_weights_mapping

    def apply_to_config(self, config: dict) -> None:
        """Adds this readout pulse to the QUA configuration.

        See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config]
        for details.
        """
        super().apply_to_config(config)
        self._config_add_integration_weights(config)

apply_to_config(config)

Adds this readout pulse to the QUA configuration.

See QuamComponent.apply_to_config for details.

Source code in quam/components/pulses.py
349
350
351
352
353
354
355
356
def apply_to_config(self, config: dict) -> None:
    """Adds this readout pulse to the QUA configuration.

    See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config]
    for details.
    """
    super().apply_to_config(config)
    self._config_add_integration_weights(config)

integration_weights_function() abstractmethod

Abstract method to calculate the integration weights.

Returns:

Type Description
Dict[str, List[Tuple[float, int]]]

Dict containing keys "real", "imag", "minus_real", "minus_imag".

Dict[str, List[Tuple[float, int]]]

Values are lists of tuples of (weight, length) pairs.

Source code in quam/components/pulses.py
319
320
321
322
323
324
325
326
327
@abstractmethod
def integration_weights_function(self) -> Dict[str, List[Tuple[float, int]]]:
    """Abstract method to calculate the integration weights.

    Returns:
        Dict containing keys "real", "imag", "minus_real", "minus_imag".
        Values are lists of tuples of (weight, length) pairs.
    """
    ...

DragCosinePulse

Bases: Pulse

Cosine based DRAG pulse that compensate for the leakage and AC stark shift.

These DRAG waveforms has been implemented following the next Refs.: Chen et al. PRL, 116, 020501 (2016) https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.116.020501 and Chen's thesis https://web.physics.ucsb.edu/~martinisgroup/theses/Chen2018.pdf

Parameters:

Name Type Description Default
length int

The pulse length in ns.

required
axis_angle float

IQ axis angle of the output pulse in radians. If None (default), the pulse is meant for a single channel or the I port of an IQ channel If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).

required
amplitude float

The amplitude in volts.

required
alpha float

The DRAG coefficient.

required
anharmonicity float

f_21 - f_10 - The differences in energy between the 2-1 and the 1-0 energy levels, in Hz.

required
detuning float

The frequency shift to correct for AC stark shift, in Hz.

required
Source code in quam/components/pulses.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
@quam_dataclass
class DragCosinePulse(Pulse):
    """Cosine based DRAG pulse that compensate for the leakage and AC stark shift.

    These DRAG waveforms has been implemented following the next Refs.:
    Chen et al. PRL, 116, 020501 (2016)
    https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.116.020501
    and Chen's thesis
    https://web.physics.ucsb.edu/~martinisgroup/theses/Chen2018.pdf

    Args:
        length (int): The pulse length in ns.
        axis_angle (float, optional): IQ axis angle of the output pulse in radians.
            If None (default), the pulse is meant for a single channel or the I port
                of an IQ channel
            If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).
        amplitude (float): The amplitude in volts.
        alpha (float): The DRAG coefficient.
        anharmonicity (float): f_21 - f_10 - The differences in energy between the 2-1
            and the 1-0 energy levels, in Hz.
        detuning (float): The frequency shift to correct for AC stark shift, in Hz.
    """

    axis_angle: float
    amplitude: float
    alpha: float
    anharmonicity: float
    detuning: float = 0.0

    def __post_init__(self) -> None:
        return super().__post_init__()

    def waveform_function(self):
        from qualang_tools.config.waveform_tools import drag_cosine_pulse_waveforms

        I, Q = drag_cosine_pulse_waveforms(
            amplitude=self.amplitude,
            length=self.length,
            alpha=self.alpha,
            anharmonicity=self.anharmonicity,
            detuning=self.detuning,
        )
        I, Q = np.array(I), np.array(Q)

        I_rot = I * np.cos(self.axis_angle) - Q * np.sin(self.axis_angle)
        Q_rot = I * np.sin(self.axis_angle) + Q * np.cos(self.axis_angle)

        return I_rot + 1.0j * Q_rot

DragGaussianPulse

Bases: Pulse

Gaussian-based DRAG pulse that compensate for the leakage and AC stark shift.

These DRAG waveforms has been implemented following the next Refs.: Chen et al. PRL, 116, 020501 (2016) https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.116.020501 and Chen's thesis https://web.physics.ucsb.edu/~martinisgroup/theses/Chen2018.pdf

Parameters:

Name Type Description Default
length int

The pulse length in ns.

required
axis_angle float

IQ axis angle of the output pulse in radians. If None (default), the pulse is meant for a single channel or the I port of an IQ channel If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).

required
amplitude float

The amplitude in volts.

required
sigma float

The gaussian standard deviation.

required
alpha float

The DRAG coefficient.

required
anharmonicity float

f_21 - f_10 - The differences in energy between the 2-1 and the 1-0 energy levels, in Hz.

required
detuning float

The frequency shift to correct for AC stark shift, in Hz.

required
subtracted bool

If true, returns a subtracted Gaussian, such that the first and last points will be at 0 volts. This reduces high-frequency components due to the initial and final points offset. Default is true.

required
Source code in quam/components/pulses.py
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
@quam_dataclass
class DragGaussianPulse(Pulse):
    """Gaussian-based DRAG pulse that compensate for the leakage and AC stark shift.

    These DRAG waveforms has been implemented following the next Refs.:
    Chen et al. PRL, 116, 020501 (2016)
    https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.116.020501
    and Chen's thesis
    https://web.physics.ucsb.edu/~martinisgroup/theses/Chen2018.pdf

    Args:
        length (int): The pulse length in ns.
        axis_angle (float, optional): IQ axis angle of the output pulse in radians.
            If None (default), the pulse is meant for a single channel or the I port
                of an IQ channel
            If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).
        amplitude (float): The amplitude in volts.
        sigma (float): The gaussian standard deviation.
        alpha (float): The DRAG coefficient.
        anharmonicity (float): f_21 - f_10 - The differences in energy between the 2-1
            and the 1-0 energy levels, in Hz.
        detuning (float): The frequency shift to correct for AC stark shift, in Hz.
        subtracted (bool): If true, returns a subtracted Gaussian, such that the first
            and last points will be at 0 volts. This reduces high-frequency components
            due to the initial and final points offset. Default is true.

    """

    axis_angle: float
    amplitude: float
    sigma: float
    alpha: float
    anharmonicity: float
    detuning: float = 0.0
    subtracted: bool = True

    def __post_init__(self) -> None:
        return super().__post_init__()

    def waveform_function(self):
        from qualang_tools.config.waveform_tools import drag_gaussian_pulse_waveforms

        I, Q = drag_gaussian_pulse_waveforms(
            amplitude=self.amplitude,
            length=self.length,
            sigma=self.sigma,
            alpha=self.alpha,
            anharmonicity=self.anharmonicity,
            detuning=self.detuning,
            subtracted=self.subtracted,
        )
        I, Q = np.array(I), np.array(Q)

        I_rot = I * np.cos(self.axis_angle) - Q * np.sin(self.axis_angle)
        Q_rot = I * np.sin(self.axis_angle) + Q * np.cos(self.axis_angle)

        return I_rot + 1.0j * Q_rot

FlatTopGaussianPulse

Bases: Pulse

Gaussian pulse with flat top QuAM component.

Parameters:

Name Type Description Default
length int

The total length of the pulse in samples.

required
amplitude float

The amplitude of the pulse in volts.

required
axis_angle float

IQ axis angle of the output pulse in radians. If None (default), the pulse is meant for a single channel or the I port of an IQ channel If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).

required
flat_length int

The length of the pulse's flat top in samples. The rise and fall lengths are calculated from the total length and the flat length.

required
Source code in quam/components/pulses.py
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
@quam_dataclass
class FlatTopGaussianPulse(Pulse):
    """Gaussian pulse with flat top QuAM component.

    Args:
        length (int): The total length of the pulse in samples.
        amplitude (float): The amplitude of the pulse in volts.
        axis_angle (float, optional): IQ axis angle of the output pulse in radians.
            If None (default), the pulse is meant for a single channel or the I port
                of an IQ channel
            If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).
        flat_length (int): The length of the pulse's flat top in samples.
            The rise and fall lengths are calculated from the total length and the
            flat length.
    """

    amplitude: float
    axis_angle: float = None
    flat_length: int

    def waveform_function(self):
        from qualang_tools.config.waveform_tools import flattop_gaussian_waveform

        rise_fall_length = (self.length - self.flat_length) // 2
        if not self.flat_length + 2 * rise_fall_length == self.length:
            raise ValueError(
                "FlatTopGaussianPulse rise_fall_length (=length-flat_length) must be"
                f" a multiple of 2 ({self.length} - {self.flat_length} ="
                f" {self.length - self.flat_length})"
            )

        waveform = flattop_gaussian_waveform(
            amplitude=self.amplitude,
            flat_length=self.flat_length,
            rise_fall_length=rise_fall_length,
            return_part="all",
        )
        waveform = np.array(waveform)

        if self.axis_angle is not None:
            waveform = waveform * np.exp(1j * self.axis_angle)

        return waveform

GaussianPulse

Bases: Pulse

Gaussian pulse QuAM component.

Parameters:

Name Type Description Default
amplitude float

The amplitude of the pulse in volts.

required
length int

The length of the pulse in samples.

required
sigma float

The standard deviation of the gaussian pulse. Should generally be less than half the length of the pulse.

required
axis_angle float

IQ axis angle of the output pulse in radians. If None (default), the pulse is meant for a single channel or the I port of an IQ channel If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).

required
subtracted bool

If true, returns a subtracted Gaussian, such that the first and last points will be at 0 volts. This reduces high-frequency components due to the initial and final points offset. Default is true.

required
Source code in quam/components/pulses.py
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
@quam_dataclass
class GaussianPulse(Pulse):
    """Gaussian pulse QuAM component.

    Args:
        amplitude (float): The amplitude of the pulse in volts.
        length (int): The length of the pulse in samples.
        sigma (float): The standard deviation of the gaussian pulse.
            Should generally be less than half the length of the pulse.
        axis_angle (float, optional): IQ axis angle of the output pulse in radians.
            If None (default), the pulse is meant for a single channel or the I port
                of an IQ channel
            If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).
        subtracted (bool): If true, returns a subtracted Gaussian, such that the first
            and last points will be at 0 volts. This reduces high-frequency components
            due to the initial and final points offset. Default is true.
    """

    amplitude: float
    length: int
    sigma: float
    axis_angle: float = None
    subtracted: bool = True

    def waveform_function(self):
        t = np.arange(self.length, dtype=int)
        center = (self.length - 1) / 2
        waveform = self.amplitude * np.exp(-((t - center) ** 2) / (2 * self.sigma**2))

        if self.subtracted:
            waveform = waveform - waveform[-1]

        if self.axis_angle is not None:
            waveform = waveform * np.exp(1j * self.axis_angle)

        return waveform

Pulse

Bases: QuamComponent

QuAM base component for a pulse.

Pulses are added to a channel using

channel.operations["pulse_name"] = pulse

The Pulse class is an abstract base class, and should not be instantiated directly. Instead, use one of the subclasses such as: - ConstantReadoutPulse - DragPulse - SquarePulse - GaussianPulse or create a custom subclass. In this case, the method waveform_function should be implemented.

Parameters:

Name Type Description Default
operation str

The operation of the pulse, either "control" or "measurement". Default is "control".

required
length int

The length of the pulse in samples.

required
digital_marker (str, list)

The digital marker to use for the pulse. Can be a string, in which case it is a reference to a digital marker in the config, or a list of tuples of (sample, length) pairs. Default is None.

required
Note

The unique pulse label is automatically generated from the channel name and the pulse name, the same for the waveform and digital marker names. The pulse label is defined as "{channel_name}.{pulse_name}.pulse". The waveform label is defined as "{channel_name}.{pulse_name}.wf". The digital marker label is defined as "{channel_name}.{pulse_name}.dm".

Source code in quam/components/pulses.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
@quam_dataclass
class Pulse(QuamComponent):
    """QuAM base component for a pulse.

    Pulses are added to a channel using
    ```
    channel.operations["pulse_name"] = pulse
    ```

    The `Pulse` class is an abstract base class, and should not be instantiated
    directly. Instead, use one of the subclasses such as:
    - `ConstantReadoutPulse`
    - `DragPulse`
    - `SquarePulse`
    - `GaussianPulse`
    or create a custom subclass. In this case, the method `waveform_function` should
    be implemented.

    Args:
        operation (str): The operation of the pulse, either "control" or "measurement".
            Default is "control".
        length (int): The length of the pulse in samples.
        digital_marker (str, list, optional): The digital marker to use for the pulse.
            Can be a string, in which case it is a reference to a digital marker in the
            config, or a list of tuples of (sample, length) pairs. Default is None.

    Note:
        The unique pulse label is automatically generated from the channel name and
        the pulse name, the same for the waveform and digital marker names.
        The pulse label is defined as `"{channel_name}.{pulse_name}.pulse"`.
        The waveform label is defined as `"{channel_name}.{pulse_name}.wf"`.
        The digital marker label is defined as `"{channel_name}.{pulse_name}.dm"`.

    """

    operation: ClassVar[str] = "control"
    length: int
    id: str = None

    digital_marker: Union[str, List[Tuple[int, int]]] = None

    @property
    def channel(self):
        """The channel to which the pulse is attached, None if no channel is attached"""
        from quam.components.channels import Channel

        if isinstance(self.parent, Channel):
            return self.parent
        elif hasattr(self.parent, "parent") and isinstance(self.parent.parent, Channel):
            return self.parent.parent
        else:
            return None

    @property
    def name(self):
        if self.channel is None:
            raise AttributeError(
                f"Cannot get full name of pulse '{self}' because it is not"
                " attached to a channel"
            )

        if self.id is not None:
            name = self.id
        else:
            name = self.parent.get_attr_name(self)

        return f"{self.channel.name}{str_ref.DELIMITER}{name}"

    @property
    def pulse_name(self):
        return f"{self.name}{str_ref.DELIMITER}pulse"

    @property
    def waveform_name(self):
        return f"{self.name}{str_ref.DELIMITER}wf"

    @property
    def digital_marker_name(self):
        return f"{self.name}{str_ref.DELIMITER}dm"

    def calculate_waveform(self) -> Union[float, complex, List[float], List[complex]]:
        """Calculate the waveform of the pulse.

        This function calls `Pulse.waveform_function`, which should generally be
        subclassed, to generate the waveform.

        This function then processes the results such that IQ waveforms are cast
        into complex values.

        Returns:
            The processed waveform, which can be either
            - a single float for a constant single-channel waveform,
            - a single complex number for a constant IQ waveform,
            - a list of floats for an arbitrary single-channel waveform,
            - a list of complex numbers for an arbitrary IQ waveform,
        """
        waveform = self.waveform_function()

        # Optionally convert IQ waveforms to complex waveform
        if isinstance(waveform, tuple) and len(waveform) == 2:
            if isinstance(waveform[0], (list, np.ndarray)):
                waveform = np.array(waveform[0]) + 1.0j * np.array(waveform[1])
            else:
                waveform = waveform[0] + 1.0j * waveform[1]

        return waveform

    def waveform_function(
        self,
    ) -> Union[
        float,
        complex,
        List[float],
        List[complex],
        Tuple[float, float],
        Tuple[List[float], List[float]],
    ]:
        """Function that returns the waveform of the pulse.

        The waveform function should use the relevant parameters from the pulse, which
        is passed as the only argument.

        This function is called from `Pulse.calculate_waveform`

        Returns:
            The waveform of the pulse. Can be one of the following:
            - a single float for a constant single-channel waveform,
            - a single complex number for a constant IQ waveform,
            - a list of floats for an arbitrary single-channel waveform,
            - a list of complex numbers for an arbitrary IQ waveform,
            - a tuple of floats or float lists for an arbitrary IQ waveform
        """
        ...

    def _config_add_pulse(self, config: Dict[str, Any]):
        """Add the pulse to the config

        The config entry is added to `config["pulses"][self.pulse_name]`
        """
        assert self.operation in ["control", "measurement"]
        assert isinstance(self.length, int)

        pulse_config = config["pulses"][self.pulse_name] = {
            "operation": self.operation,
            "length": self.length,
        }
        if self.digital_marker is not None:
            pulse_config["digital_marker"] = self.digital_marker

    def _config_add_waveforms(self, config):
        """Add the waveform to the config

        For a single waveform, the config entry is added to
        `config["waveforms"]["{channel_name}.{pulse_name}.wf"]`.
        For an IQ waveform, two config entries are added to
        `config["waveforms"]["{channel_name}.{pulse_name}.wf.I"]` and with suffix `Q`.

        Raises:
            ValueError: If the waveform type (single or IQ) does not match the parent
                channel type (SingleChannel, IQChannel, InOutIQChannel, MWChannel,
                InOutMWChannel).
        """

        from quam.components.channels import SingleChannel, IQChannel, MWChannel

        pulse_config = config["pulses"][self.pulse_name]

        waveform = self.calculate_waveform()
        if waveform is None:
            return

        pulse_config["waveforms"] = {}

        if isinstance(waveform, numbers.Number):
            wf_type = "constant"
            if isinstance(waveform, complex):
                waveforms = {"I": waveform.real, "Q": waveform.imag}
            elif isinstance(self.channel, (IQChannel, MWChannel)):
                waveforms = {"I": waveform, "Q": 0.0}
            else:
                waveforms = {"single": waveform}

        elif isinstance(waveform, (list, np.ndarray)):
            wf_type = "arbitrary"
            if np.iscomplexobj(waveform):
                waveforms = {"I": list(waveform.real), "Q": list(waveform.imag)}
            elif isinstance(self.channel, (IQChannel, MWChannel)):
                waveforms = {"I": waveform, "Q": np.zeros_like(waveform)}
            else:
                waveforms = {"single": list(waveform)}
        else:
            raise ValueError("unsupported return type")

        # Add check that waveform type (single or IQ) matches parent
        if "single" in waveforms and not isinstance(self.channel, SingleChannel):
            raise ValueError(
                "Waveform type 'single' not allowed for (IQChannel, MWChannel)"
                f" '{self.channel.name}'"
            )
        elif "I" in waveforms and not isinstance(self.channel, (IQChannel, MWChannel)):
            raise ValueError(
                "Waveform type 'IQ' not allowed for SingleChannel"
                f" '{self.channel.name}'"
            )

        for suffix, waveform in waveforms.items():
            waveform_name = self.waveform_name
            if suffix != "single":
                waveform_name += f"{str_ref.DELIMITER}{suffix}"

            sample_label = "sample" if wf_type == "constant" else "samples"

            config["waveforms"][waveform_name] = {
                "type": wf_type,
                sample_label: waveform,
            }
            pulse_config["waveforms"][suffix] = waveform_name

    def _config_add_digital_markers(self, config):
        """Add the digital marker to the config

        The config entry is added to
        `config["digital_waveforms"]["{channel_name}.{pulse_name}.dm"]` and also
        registered in
        `config["pulses"]["{channel_name}.{pulse_name}.pulse"]["digital_marker"]`.

        If the digital marker is a string, it is assumed to be a reference to a
        digital marker already defined in the config.
        """
        if isinstance(self.digital_marker, str):
            # Use a common config digital marker
            if self.digital_marker not in config["digital_waveforms"]:
                raise KeyError(
                    "{self.name}.digital_marker={self.digital_marker} not in"
                    " config['digital_waveforms']"
                )
            digital_marker_name = self.digital_marker
        else:
            config["digital_waveforms"][self.digital_marker_name] = {
                "samples": self.digital_marker
            }
            digital_marker_name = self.digital_marker_name

        config["pulses"][self.pulse_name]["digital_marker"] = digital_marker_name

    def apply_to_config(self, config: dict) -> None:
        """Adds this pulse, waveform, and digital marker to the QUA configuration.

        See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config]
        for details.
        """
        if self.channel is None:
            return

        self._config_add_pulse(config)
        self._config_add_waveforms(config)

        if self.digital_marker:
            self._config_add_digital_markers(config)

channel property

The channel to which the pulse is attached, None if no channel is attached

apply_to_config(config)

Adds this pulse, waveform, and digital marker to the QUA configuration.

See QuamComponent.apply_to_config for details.

Source code in quam/components/pulses.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def apply_to_config(self, config: dict) -> None:
    """Adds this pulse, waveform, and digital marker to the QUA configuration.

    See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config]
    for details.
    """
    if self.channel is None:
        return

    self._config_add_pulse(config)
    self._config_add_waveforms(config)

    if self.digital_marker:
        self._config_add_digital_markers(config)

calculate_waveform()

Calculate the waveform of the pulse.

This function calls Pulse.waveform_function, which should generally be subclassed, to generate the waveform.

This function then processes the results such that IQ waveforms are cast into complex values.

Returns:

Type Description
Union[float, complex, List[float], List[complex]]

The processed waveform, which can be either

Union[float, complex, List[float], List[complex]]
  • a single float for a constant single-channel waveform,
Union[float, complex, List[float], List[complex]]
  • a single complex number for a constant IQ waveform,
Union[float, complex, List[float], List[complex]]
  • a list of floats for an arbitrary single-channel waveform,
Union[float, complex, List[float], List[complex]]
  • a list of complex numbers for an arbitrary IQ waveform,
Source code in quam/components/pulses.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def calculate_waveform(self) -> Union[float, complex, List[float], List[complex]]:
    """Calculate the waveform of the pulse.

    This function calls `Pulse.waveform_function`, which should generally be
    subclassed, to generate the waveform.

    This function then processes the results such that IQ waveforms are cast
    into complex values.

    Returns:
        The processed waveform, which can be either
        - a single float for a constant single-channel waveform,
        - a single complex number for a constant IQ waveform,
        - a list of floats for an arbitrary single-channel waveform,
        - a list of complex numbers for an arbitrary IQ waveform,
    """
    waveform = self.waveform_function()

    # Optionally convert IQ waveforms to complex waveform
    if isinstance(waveform, tuple) and len(waveform) == 2:
        if isinstance(waveform[0], (list, np.ndarray)):
            waveform = np.array(waveform[0]) + 1.0j * np.array(waveform[1])
        else:
            waveform = waveform[0] + 1.0j * waveform[1]

    return waveform

waveform_function()

Function that returns the waveform of the pulse.

The waveform function should use the relevant parameters from the pulse, which is passed as the only argument.

This function is called from Pulse.calculate_waveform

Returns:

Type Description
Union[float, complex, List[float], List[complex], Tuple[float, float], Tuple[List[float], List[float]]]

The waveform of the pulse. Can be one of the following:

Union[float, complex, List[float], List[complex], Tuple[float, float], Tuple[List[float], List[float]]]
  • a single float for a constant single-channel waveform,
Union[float, complex, List[float], List[complex], Tuple[float, float], Tuple[List[float], List[float]]]
  • a single complex number for a constant IQ waveform,
Union[float, complex, List[float], List[complex], Tuple[float, float], Tuple[List[float], List[float]]]
  • a list of floats for an arbitrary single-channel waveform,
Union[float, complex, List[float], List[complex], Tuple[float, float], Tuple[List[float], List[float]]]
  • a list of complex numbers for an arbitrary IQ waveform,
Union[float, complex, List[float], List[complex], Tuple[float, float], Tuple[List[float], List[float]]]
  • a tuple of floats or float lists for an arbitrary IQ waveform
Source code in quam/components/pulses.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def waveform_function(
    self,
) -> Union[
    float,
    complex,
    List[float],
    List[complex],
    Tuple[float, float],
    Tuple[List[float], List[float]],
]:
    """Function that returns the waveform of the pulse.

    The waveform function should use the relevant parameters from the pulse, which
    is passed as the only argument.

    This function is called from `Pulse.calculate_waveform`

    Returns:
        The waveform of the pulse. Can be one of the following:
        - a single float for a constant single-channel waveform,
        - a single complex number for a constant IQ waveform,
        - a list of floats for an arbitrary single-channel waveform,
        - a list of complex numbers for an arbitrary IQ waveform,
        - a tuple of floats or float lists for an arbitrary IQ waveform
    """
    ...

ReadoutPulse

Bases: BaseReadoutPulse, ABC

QuAM abstract base component for most readout pulses.

This class is a subclass of ReadoutPulse and should be used for most readout pulses. It provides a default implementation of the integration_weights_function method, which is suitable for most cases.

Parameters:

Name Type Description Default
length int

The length of the pulse in samples.

required
digital_marker (str, list)

The digital marker to use for the pulse. Default is "ON".

required
integration_weights (list[float], list[tuple[float, int]])

The integration weights, can be either - a list of floats (one per sample), the length must match the pulse length - a list of tuples of (weight, length) pairs, the sum of the lengths must match the pulse length

required
integration_weights_angle float

The rotation angle for the integration weights in radians.

required
Source code in quam/components/pulses.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
@quam_dataclass
class ReadoutPulse(BaseReadoutPulse, ABC):
    """QuAM abstract base component for most readout pulses.

    This class is a subclass of `ReadoutPulse` and should be used for most readout
    pulses. It provides a default implementation of the `integration_weights_function`
    method, which is suitable for most cases.

    Args:
        length (int): The length of the pulse in samples.
        digital_marker (str, list, optional): The digital marker to use for the pulse.
            Default is "ON".
        integration_weights (list[float], list[tuple[float, int]], optional): The
            integration weights, can be either
            - a list of floats (one per sample), the length must match the pulse length
            - a list of tuples of (weight, length) pairs, the sum of the lengths must
              match the pulse length
        integration_weights_angle (float, optional): The rotation angle for the
            integration weights in radians.
    """

    integration_weights: Union[List[float], List[Tuple[float, int]]] = None
    integration_weights_angle: float = 0

    def integration_weights_function(self) -> List[Tuple[Union[complex, float], int]]:
        from qualang_tools.config import convert_integration_weights

        phase = np.exp(1j * self.integration_weights_angle)

        if self.integration_weights is None or not len(self.integration_weights):
            integration_weights = [(1, self.length)]
        elif isinstance(self.integration_weights[0], float):
            integration_weights = convert_integration_weights(self.integration_weights)
        else:
            integration_weights = self.integration_weights

        return {
            "real": [(phase.real * w, l) for w, l in integration_weights],
            "imag": [(phase.imag * w, l) for w, l in integration_weights],
            "minus_real": [(-phase.real * w, l) for w, l in integration_weights],
            "minus_imag": [(-phase.imag * w, l) for w, l in integration_weights],
        }

SquarePulse

Bases: Pulse

Square pulse QuAM component.

Parameters:

Name Type Description Default
length int

The length of the pulse in samples.

required
digital_marker (str, list)

The digital marker to use for the pulse.

required
amplitude float

The amplitude of the pulse in volts.

required
axis_angle float

IQ axis angle of the output pulse in radians. If None (default), the pulse is meant for a single channel or the I port of an IQ channel If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).

required
Source code in quam/components/pulses.py
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
@quam_dataclass
class SquarePulse(Pulse):
    """Square pulse QuAM component.

    Args:
        length (int): The length of the pulse in samples.
        digital_marker (str, list, optional): The digital marker to use for the pulse.
        amplitude (float): The amplitude of the pulse in volts.
        axis_angle (float, optional): IQ axis angle of the output pulse in radians.
            If None (default), the pulse is meant for a single channel or the I port
                of an IQ channel
            If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).
    """

    amplitude: float
    axis_angle: float = None

    def waveform_function(self):
        waveform = self.amplitude

        if self.axis_angle is not None:
            waveform = waveform * np.exp(1j * self.axis_angle)
        return waveform

SquareReadoutPulse

Bases: ReadoutPulse, SquarePulse

QuAM component for a square readout pulse.

Parameters:

Name Type Description Default
length int

The length of the pulse in samples.

required
digital_marker (str, list)

The digital marker to use for the pulse. Default is "ON".

required
amplitude float

The constant amplitude of the pulse.

required
axis_angle float

IQ axis angle of the output pulse in radians. If None (default), the pulse is meant for a single channel or the I port of an IQ channel If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).

required
integration_weights (list[float], list[tuple[float, int]])

The integration weights, can be either - a list of floats (one per sample), the length must match the pulse length - a list of tuples of (weight, length) pairs, the sum of the lengths must match the pulse length

required
integration_weights_angle float

The rotation angle for the integration weights in radians.

required
Source code in quam/components/pulses.py
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
@quam_dataclass
class SquareReadoutPulse(ReadoutPulse, SquarePulse):
    """QuAM component for a square readout pulse.

    Args:
        length (int): The length of the pulse in samples.
        digital_marker (str, list, optional): The digital marker to use for the pulse.
            Default is "ON".
        amplitude (float): The constant amplitude of the pulse.
        axis_angle (float, optional): IQ axis angle of the output pulse in radians.
            If None (default), the pulse is meant for a single channel or the I port
                of an IQ channel
            If not None, the pulse is meant for an IQ channel (0 is X, pi/2 is Y).
        integration_weights (list[float], list[tuple[float, int]], optional): The
            integration weights, can be either
            - a list of floats (one per sample), the length must match the pulse length
            - a list of tuples of (weight, length) pairs, the sum of the lengths must
              match the pulse length
        integration_weights_angle (float, optional): The rotation angle for the
            integration weights in radians.
    """

    ...

WaveformPulse

Bases: Pulse

Pulse that uses a pre-defined waveform, as opposed to a function.

For a single channel, only waveform_I is required. For an IQ channel, both waveform_I and waveform_Q are required.

The length of the pulse is derived from the length of waveform_I.

Parameters:

Name Type Description Default
waveform_I list[float]

The in-phase waveform.

required
waveform_Q list[float]

The quadrature waveform.

required
Source code in quam/components/pulses.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
@quam_dataclass
class WaveformPulse(Pulse):
    """Pulse that uses a pre-defined waveform, as opposed to a function.

    For a single channel, only `waveform_I` is required.
    For an IQ channel, both `waveform_I` and `waveform_Q` are required.

    The length of the pulse is derived from the length of `waveform_I`.

    Args:
        waveform_I (list[float]): The in-phase waveform.
        waveform_Q (list[float], optional): The quadrature waveform.
    """

    waveform_I: List[float]  # pyright: ignore
    waveform_Q: Optional[List[float]] = None
    # Length is derived from the waveform_I length, but still needs to be declared
    # to satisfy the dataclass, but we'll override its behavior
    length: Optional[int] = None  # pyright: ignore

    @property
    def length(self):  # noqa: 811
        if not isinstance(self.waveform_I, Iterable):
            return None
        return len(self.waveform_I)

    @length.setter
    def length(self, length: Optional[int]):
        if length is not None and not isinstance(length, property):
            raise AttributeError(f"length is not writable with value {length}")

    def waveform_function(self):
        if self.waveform_Q is None:
            return np.array(self.waveform_I)
        return np.array(self.waveform_I) + 1.0j * np.array(self.waveform_Q)

    def to_dict(
        self, follow_references: bool = False, include_defaults: bool = False
    ) -> Dict[str, Any]:
        d = super().to_dict(follow_references, include_defaults)
        d.pop("length")
        return d