Source code for quantify_scheduler.backends.qblox_backend

# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""Compiler backend for Qblox hardware."""
from __future__ import annotations
from copy import deepcopy

import warnings
from typing import Any, Dict, List, Optional

from quantify_scheduler import CompiledSchedule, Schedule
from quantify_scheduler.backends.corrections import (
    apply_distortion_corrections,
    determine_relative_latency_corrections,
)
from quantify_scheduler.backends.graph_compilation import (
    CompilationConfig,
    HardwareOptions,
)
from quantify_scheduler.backends.qblox import compiler_container, constants, helpers
from quantify_scheduler.operations.pulse_factories import long_square_pulse


[docs]def _get_square_pulses_to_replace(schedule: Schedule) -> Dict[str, List[int]]: """Generate a dict referring to long square pulses to replace in the schedule. This function generates a mapping (dict) from the keys in the :meth:`~quantify_scheduler.schedules.schedule.ScheduleBase.operations` dict to a list of indices, which refer to entries in the `"pulse_info"` list that describe a square pulse. Parameters ---------- schedule : Schedule A :class:`~quantify_scheduler.schedules.schedule.Schedule`, possibly containing long square pulses. Returns ------- square_pulse_idx_map : Dict[str, List[int]] The mapping from ``operation_repr`` to ``"pulse_info"`` indices to be replaced. """ square_pulse_idx_map: Dict[str, List[int]] = {} for ref, operation in schedule.operations.items(): square_pulse_idx_to_replace: List[int] = [] for i, pulse_info in enumerate(operation.data["pulse_info"]): if ( pulse_info.get("wf_func", "") == "quantify_scheduler.waveforms.square" and pulse_info["duration"] >= constants.PULSE_STITCHING_DURATION ): square_pulse_idx_to_replace.append(i) if square_pulse_idx_to_replace: square_pulse_idx_map[ref] = square_pulse_idx_to_replace return square_pulse_idx_map
[docs]def _replace_long_square_pulses( schedule: Schedule, pulse_idx_map: Dict[str, List[int]] ) -> Schedule: """Replace any square pulses indicated by pulse_idx_map by a `long_square_pulse`. Parameters ---------- schedule : Schedule A :class:`~quantify_scheduler.schedules.schedule.Schedule`, possibly containing long square pulses. pulse_idx_map : Dict[str, List[int]] A mapping from the keys in the :meth:`~quantify_scheduler.schedules.schedule.ScheduleBase.operations` dict to a list of indices, which refer to entries in the `"pulse_info"` list that describe a square pulse. Returns ------- Schedule The schedule with square pulses longer than :class:`~quantify_scheduler.backends.qblox.constants.PULSE_STITCHING_DURATION` replaced by :func:`~quantify_scheduler.operations.pulse_factories.long_square_pulse`. If no replacements were done, this is the original unmodified schedule. """ schedule = deepcopy(schedule) for ref, square_pulse_idx_to_replace in pulse_idx_map.items(): operation = schedule.operations[ref] while square_pulse_idx_to_replace: pulse_info = operation.data["pulse_info"].pop( square_pulse_idx_to_replace.pop() ) new_square_pulse = long_square_pulse( amp=pulse_info["amp"], duration=pulse_info["duration"], port=pulse_info["port"], clock=pulse_info["clock"], t0=pulse_info["t0"], ) operation.add_pulse(new_square_pulse) return schedule
[docs]def compile_long_square_pulses_to_awg_offsets(schedule: Schedule, **_: Any) -> Schedule: """Replace square pulses in the schedule with long square pulses. Introspects operations in the schedule to find square pulses with a duration longer than :class:`~quantify_scheduler.backends.qblox.constants.PULSE_STITCHING_DURATION`. Any of these square pulses are converted to :func:`~quantify_scheduler.operations.pulse_factories.long_square_pulse`, which consist of AWG voltage offsets. If any operations are to be replaced, a deepcopy will be made of the schedule, which is returned by this function. Otherwise the original unmodified schedule will be returned. Parameters ---------- schedule : Schedule A :class:`~quantify_scheduler.schedules.schedule.Schedule`, possibly containing long square pulses. Returns ------- schedule : Schedule The schedule with square pulses longer than :class:`~quantify_scheduler.backends.qblox.constants.PULSE_STITCHING_DURATION` replaced by :func:`~quantify_scheduler.operations.pulse_factories.long_square_pulse`. If no replacements were done, this is the original unmodified schedule. """ pulse_idx_map = _get_square_pulses_to_replace(schedule) if pulse_idx_map: schedule = _replace_long_square_pulses(schedule, pulse_idx_map) return schedule
[docs]def hardware_compile( schedule: Schedule, config: CompilationConfig | Dict[str, Any] | None = None, # config can be Dict to support (deprecated) calling with hardware config # as positional argument. *, # Support for (deprecated) calling with hardware_cfg as keyword argument: hardware_cfg: Optional[Dict[str, Any]] = None, ) -> CompiledSchedule: """ Generate qblox hardware instructions for executing the schedule. The principle behind the overall compilation is as follows: For every instrument in the hardware configuration, we instantiate a compiler object. Then we assign all the pulses/acquisitions that need to be played by that instrument to the compiler, which then compiles for each instrument individually. This function then returns all the compiled programs bundled together in a dictionary with the QCoDeS name of the instrument as key. Parameters ---------- schedule The schedule to compile. It is assumed the pulse and acquisition info is already added to the operation. Otherwise an exception is raised. config Compilation config for :class:`~quantify_scheduler.backends.graph_compilation.QuantifyCompiler`, of which only the :attr:`.CompilationConfig.connectivity` is currently extracted in this compilation step. hardware_cfg (deprecated) The hardware configuration of the setup. Pass a full compilation config instead using `config` argument. Returns ------- : The compiled schedule. Raises ------ ValueError When both `config` and `hardware_cfg` are supplied. """ if not ((config is not None) ^ (hardware_cfg is not None)): raise ValueError( f"Qblox `{hardware_compile.__name__}` was called with {config=} and " f"{hardware_cfg=}. Please make sure this function is called with " f"one of the two (CompilationConfig recommended)." ) if not isinstance(config, CompilationConfig): warnings.warn( f"Qblox `{hardware_compile.__name__}` will require a full " f"CompilationConfig as input as of quantify-scheduler >= 0.15.0", FutureWarning, ) if isinstance(config, CompilationConfig): # Extract the hardware config from the CompilationConfig hardware_cfg = helpers.generate_hardware_config(compilation_config=config) elif config is not None: # Support for (deprecated) calling with hardware_cfg as positional argument. hardware_cfg = config if "latency_corrections" in hardware_cfg.keys(): # Important: currently only used to validate the input, should also be # used for storing the latency corrections # (see also https://gitlab.com/groups/quantify-os/-/epics/1) HardwareOptions(latency_corrections=hardware_cfg["latency_corrections"]) # Subtract minimum latency to allow for negative latency corrections hardware_cfg["latency_corrections"] = determine_relative_latency_corrections( hardware_cfg ) schedule = apply_distortion_corrections(schedule, hardware_cfg) container = compiler_container.CompilerContainer.from_hardware_cfg( schedule, hardware_cfg ) helpers.assign_pulse_and_acq_info_to_devices( schedule=schedule, hardware_cfg=hardware_cfg, device_compilers=container.instrument_compilers, ) container.prepare() compiled_instructions = container.compile(repetitions=schedule.repetitions) # add the compiled instructions to the schedule data structure schedule["compiled_instructions"] = compiled_instructions # Mark the schedule as a compiled schedule return CompiledSchedule(schedule)