Source code for quantify_scheduler.backends.qblox.operations.pulse_factories

# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""
Module containing factory functions for pulses on the quantum-device layer.

These factories take a parametrized representation of an operation and create an
instance of the operation itself. The created operations make use of Qblox-specific
hardware features.
"""
from __future__ import annotations

import math

import numpy as np

from quantify_scheduler.backends.qblox import constants, helpers
from quantify_scheduler.backends.qblox.operations.stitched_pulse import (
    StitchedPulse,
    StitchedPulseBuilder,
)
from quantify_scheduler.operations import pulse_library
from quantify_scheduler.resources import BasebandClockResource


[docs] def long_square_pulse( amp: float, duration: float, port: str, clock: str = BasebandClockResource.IDENTITY, t0: float = 0, reference_magnitude: pulse_library.ReferenceMagnitude | None = None, ) -> StitchedPulse: """ Create a long square pulse using DC voltage offsets. .. warning:: This function creates a :class:`~quantify_scheduler.backends.qblox.operations.stitched_pulse.StitchedPulse` object, containing a combination of voltage offsets and waveforms. Overlapping StitchedPulses on the same port and clock may lead to unexpected results. Parameters ---------- amp : float Amplitude of the envelope. duration : float The pulse duration in seconds. port : str Port of the pulse, must be capable of playing a complex waveform. clock : str, optional Clock used to modulate the pulse. 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 Schedule. By default 0. min_operation_time_ns : int, optional Min operation time in ns. The duration of the long_square_pulse must be a multiple of this. By default equal to the min operation time time of Qblox modules. reference_magnitude : optional Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. Returns ------- StitchedPulse A StitchedPulse object containing an offset instruction with the specified amplitude. Raises ------ ValueError When the duration of the pulse is not a multiple of ``grid_time_ns``. """ if duration * 1e9 < constants.MIN_TIME_BETWEEN_OPERATIONS: raise ValueError( f"The duration of a long_square_pulse must be at least " f"{constants.MIN_TIME_BETWEEN_OPERATIONS} ns." f" Duration of offending operation: {duration}." f" Start time: {t0}" ) pulse = ( StitchedPulseBuilder( name=long_square_pulse.__name__, port=port, clock=clock, t0=t0 ) .add_voltage_offset( path_I=np.real(amp), path_Q=np.imag(amp), reference_magnitude=reference_magnitude, ) # The last bit, with duration 'grid time' ns, is replaced by a normal pulse. The # offset is set back to 0 before this pulse, because the Qblox backend might # otherwise lengthen the full operation by adding an 'UpdateParameters' # instruction at the end. .add_voltage_offset( path_I=0.0, path_Q=0.0, rel_time=duration - constants.MIN_TIME_BETWEEN_OPERATIONS * 1e-9, ) .add_pulse( pulse_library.SquarePulse( amp=amp, duration=constants.MIN_TIME_BETWEEN_OPERATIONS * 1e-9, port=port, clock=clock, reference_magnitude=reference_magnitude, ) ) .build() ) return pulse
[docs] def staircase_pulse( start_amp: float, final_amp: float, num_steps: int, duration: float, port: str, clock: str = BasebandClockResource.IDENTITY, t0: float = 0, min_operation_time_ns: int = constants.MIN_TIME_BETWEEN_OPERATIONS, reference_magnitude: pulse_library.ReferenceMagnitude | None = None, ) -> StitchedPulse: """ Create a staircase-shaped pulse using DC voltage offsets. This function generates a real valued staircase pulse, which reaches its final amplitude in discrete steps. In between it will maintain a plateau. .. warning:: This function creates a :class:`~quantify_scheduler.backends.qblox.operations.stitched_pulse.StitchedPulse` object, containing a combination of voltage offsets and waveforms. Overlapping StitchedPulses on the same port and clock may lead to unexpected results. Parameters ---------- start_amp : float Starting amplitude of the staircase envelope function. final_amp : float Final amplitude of the staircase envelope function. num_steps : int The number of plateaus. duration : float Duration of the pulse in seconds. port : str Port of the pulse. clock : str, optional Clock used to modulate the pulse. 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 Schedule. By default 0. min_operation_time_ns : int, optional Min operation time in ns. The duration of the long_square_pulse must be a multiple of this. By default equal to the min operation time time of Qblox modules. reference_magnitude : optional Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. Returns ------- StitchedPulse A StitchedPulse object containing incrementing or decrementing offset instructions. Raises ------ ValueError When the duration of a step is not a multiple of ``grid_time_ns``. """ builder = StitchedPulseBuilder( name=staircase_pulse.__name__, port=port, clock=clock, t0=t0 ) try: step_duration = ( helpers.to_grid_time(duration / num_steps, min_operation_time_ns) * 1e-9 ) except ValueError as err: raise ValueError( f"The duration of each step of the staircase must be a multiple of" f" {min_operation_time_ns} ns." ) from err amps = np.linspace(start_amp, final_amp, num_steps) # The final step is a special case, see below. for amp in amps[:-1]: builder.add_voltage_offset( path_I=amp, path_Q=0.0, duration=step_duration, min_duration=min_operation_time_ns * 1e-9, reference_magnitude=reference_magnitude, ) # The final step is an offset with the last part (of duration 'grid time' ns) # replaced by a pulse. The offset is set back to 0 before the pulse, because the # Qblox backend might otherwise lengthen the full operation by adding an # 'UpdateParameters' instruction at the end. builder.add_voltage_offset( path_I=amps[-1], path_Q=0.0, duration=step_duration - min_operation_time_ns * 1e-9, min_duration=min_operation_time_ns * 1e-9, reference_magnitude=reference_magnitude, ) builder.add_voltage_offset(path_I=0.0, path_Q=0.0) builder.add_pulse( pulse_library.SquarePulse( amp=amps[-1], duration=min_operation_time_ns * 1e-9, port=port, clock=clock, reference_magnitude=reference_magnitude, ) ) pulse = builder.build() return pulse
[docs] def long_ramp_pulse( amp: float, duration: float, port: str, offset: float = 0, clock: str = BasebandClockResource.IDENTITY, t0: float = 0, part_duration_ns: int = constants.STITCHED_PULSE_PART_DURATION_NS, reference_magnitude: pulse_library.ReferenceMagnitude | None = None, ) -> StitchedPulse: """ Creates a long ramp pulse by stitching together shorter ramps. This function creates a long ramp pulse by stitching together ramp pulses of the specified duration ``part_duration_ns``, with DC voltage offset instructions placed in between. .. warning:: This function creates a :class:`~quantify_scheduler.backends.qblox.operations.stitched_pulse.StitchedPulse` object, containing a combination of voltage offsets and waveforms. Overlapping StitchedPulses on the same port and clock may lead to unexpected results. Parameters ---------- amp : float Amplitude of the ramp envelope function. duration : float The pulse duration in seconds. port : str Port of the pulse. offset : float, optional Starting point of the ramp pulse. By default 0. clock : str, optional Clock used to modulate the pulse, by default a BasebandClock is used. t0 : float, optional Time in seconds when to start the pulses relative to the start time of the Operation in the Schedule. By default 0. part_duration_ns : int, optional Duration of each partial ramp in nanoseconds, by default :class:`~quantify_scheduler.backends.qblox.constants.STITCHED_PULSE_PART_DURATION_NS`. reference_magnitude : optional Scaling value and unit for the unitless amplitude. Uses settings in hardware config if not provided. Returns ------- StitchedPulse A ``StitchedPulse`` composed of shorter ramp pulses with varying DC offsets, forming one long ramp pulse. """ dur_ns = helpers.to_grid_time(duration) num_whole_parts = (dur_ns - 1) // part_duration_ns amp_part = part_duration_ns / dur_ns * amp dur_left = (dur_ns - num_whole_parts * part_duration_ns) * 1e-9 amp_left = amp - num_whole_parts * amp_part builder = StitchedPulseBuilder( long_ramp_pulse.__name__, port=port, clock=clock, t0=t0 ) last_sample_voltage = offset for _ in range(num_whole_parts): # Add an offset for each ramp part, except for the first one if the overall ramp # offset is 0. if not (last_sample_voltage == offset and math.isclose(offset, 0.0)): builder.add_voltage_offset( path_I=last_sample_voltage, path_Q=0.0, reference_magnitude=reference_magnitude, ) builder.add_pulse( pulse_library.RampPulse( amp=amp_part, duration=part_duration_ns * 1e-9, port=port, reference_magnitude=reference_magnitude, ) ) last_sample_voltage += amp_part # For the final part, the voltage offset is set to 0, because the Qblox # backend might otherwise lengthen the full operation by adding an # 'UpdateParameters' instruction at the end. # Insert a 0 offset if offsets were inserted above and the last offset is not 0. if not math.isclose(last_sample_voltage, offset) and not math.isclose( last_sample_voltage - amp_part, 0.0 ): builder.add_voltage_offset(path_I=0.0, path_Q=0.0) builder.add_pulse( pulse_library.RampPulse( amp=amp_left, offset=last_sample_voltage, duration=dur_left, port=port, reference_magnitude=reference_magnitude, ) ) pulse = builder.build() return pulse