Skip to content

Introduce new acquisition channels mapping for future use

Explanation of changes

Overview

This change is part of acquisition redesign (&5 (closed)), and is intended to partially replace the current acquisition metadata. This change only introduces some new acquisition mappings, but ultimately does not use it anywhere to make the MR relatively short. (In a subsequent MR we will use it.)

This change introduces two acquisition mappings, both of them are backend independent mappings.

  • AcquisitionChannelData, which contains the following for each acquisition channel.
    • acquisition protocol,
    • bin mode,
    • coords.
  • SchedulableLabelToAcquisitionIndex, which is a mapping for each schedulable to acquisition index.

This MR only implements the introduction of the backend-independent acquisition mappings (1st step) in 5. Implementation for quantify-scheduler!

Motivation of changes

Please first take a look at the agreed interface design to understand the motivation: Implementation plan for Quantify-scheduler.

New acquisition mappings

First, read 5. Implementation for quantify-scheduler.

This MR only implements the introduction of the backend-independent acquisition mappings (1st step)!

Detailed explanation of the implementation

The goal of the MR is to generate the hardware independent mappings (acquisition channels data and schedulable to acquisition index). This is implemented by the quantify_scheduler.helpers.generate_acq_channels_data.generate_acq_channels_data function (this is the only thing that this MR implements practically).

First, please take a look at tests/scheduler/helpers/test_generate_acq_channels_data.py to see the examples, and see what return data is generated for which example schedule. These generate AcquisitionChannelData (defined in quantify_scheduler.schedules.schedule) and SchedulableLabelToAcquisitionIndex (defined in quantify_scheduler.helpers.generate_acq_channels_data.generate_acq_channels_data) for each schedule.

In generate_acq_channels_data the _generate_acq_channels_data is called twice. To understand this, see thread: !1110 (comment 2163924753).

Note on coords

Coordinates (coords) are labels or properties or additional meta information the user can add to each acquisition (see examples in &5 (closed)). At this we still do not implement full functionality for coords in this MR, so they are only placeholders and only relevant for append mode binned acquisitions. At this stage the user has no way of specifying any coords for an acquisition, so they'll be empty in all cases, except for append mode binned acquisition. For append mode binned acquisition, they will represent the loop cycle number (iterator basically).

High-level overview of generate_acq_channels_data

generate_acq_channel_data function calls into _generate... functions. In each of these _generate... functions there is no return value, but all of them has one ultimate goal: they populate acq_channels_data and schedulable_label_to_acq_index arguments (they're passed as references, so they modify the arguments). First, the _generate_acq_channels_data is called, and it delegates the channel data generation to _generate_acq_channels_data_for_protocol. _generate_acq_channels_data has to go through recursively each subschedule and control flow operation until it finds real acquisition with the condition elif operation.valid_acquisition.

Note on loops

In general, a schedule can be complicated, it could include multiple loops within loops. These loops are processed by _generate_acq_channels_data. (For example when the user does schedule.add(LoopOperation(body=Measure("q0", bin_mode=BinMode.APPEND), repetitions=3)). For binned acquisitions with APPEND bin mode, we have to generate a lot of acquisition indices: imagine a LoopOperation with repetitions=3, which have an other loop inside with repetitions=2. In this case, if the innermost loop has an append mode binned acquisition, the _generate_acq_channels_data_binned must generate 3*2 unique acquisition index for that one acqusition operation. To "inform" the _generate_acq_channels_data_binned function that this acquisition is within a nested loop structure, we use the nested_loop_repetitions: list[int] argument. This argument is generated in the _generate_acq_channels_data function (which goes through subschedules and loops recursively in the schedule). In our previous example this would be nested_loop_repetitions=[3, 2], indicating repetitions for each "level". You can see how this data is generated in _generate_acq_channels_data the following way.

new_nested_loop_repetitions: list[int] = nested_loop_repetitions + [repetitions]

Generating acquisition data for non-binned acquisitions

For non-binned acqusitions, the generated data is simpler. First, the SchedulableLabelToAcquisitionIndex is not generated (they're only generated for binned acquisitions), and the coords are essentially nothing (we still do not implement full functionality for coords in this MR, so they are only placeholders and only relevant for append mode binned acquisitions), they're only just an empty dictionaries.

For these protocols, the _generate_acq_channels_data_for_protocol only validates the acquisitions (whether the binned mode is valid and whether the user given acquisition channel is unique), and then generates the AcquisitionChannelData.

AcquisitionChannelData(
    acq_index_dim_name=("acq_index_" + str(acq_channel)),
    protocol=protocol,
    bin_mode=bin_mode,
    coords={},
)

Generating acquisition data for binned acquisitions

For binned acquisition protocols, both SchedulableLabelToAcquisitionIndex and AcquisitionChannelData are generated.

First, let's remind ourselves what is SchedulableLabelToAcquisitionIndex. When InstrumentCoordinator (or InstrumentCoordinatorComponent) retrieves data from the hardware, the hardware needs to map the hardware acquisition data to an xarray.Dataset); practically it's a mapping from hardware acquisition location to acquisition index. For the backend compiler to generate "hardware acquisition mapping", all it needs is which schedulable maps to which acquisition index. The SchedulableLabelToAcquisitionIndex is a HAL (hardware abstraction layer, backend independent layer) generated data, which could be used by backend compilers to generate their own hardware acquisition location to acquisition index. In theory, backends do not need SchedulableLabelToAcquisitionIndex, only the SchedulableLabelToAcquisitionIndex.

A note on SchedulableLabelToAcquisitionIndex: the schedule is not a flat structure; there could be multiple subschedules inside subschedules and control flows; this is reflected in the keys of the SchedulableLabelToAcquisitionIndex, the keys are tuples. For example if there's a subschedule with schedulable label "1", and inside that there's a control flow with schedulable "2", and inside that (in the body) there's a schedule, and inside that there's an acquisition with schedulable label "3", then the key (the full schedulable label or FullSchedulableLabel) is ("1", "2", None, "3"). (Here None refers to the body of the control flow as a "schedulable label".) And even an acquisition operation could have multiple smaller acquisitions in them (within the acq_info), so the key also needs an index for the acq_info. The whole key for SchedulableLabelToAcquisitionIndex is a Tuple[FullSchedulableLabel, int], where FullSchedulableLabel is a tuple of schedulable labels.

The fact that each acquisition operation has multiple acq_info, and each element in the acq_info is a separate acquisition index is reflected in _generate_acq_channels_data_for_protocol.

for acq_num_in_operation, acq_info in enumerate(acquisitions_info):

It iterates through all acq infos, and the acq_num_in_operation is the index for that acq_info.

For AVERAGE binned mode acquisitions there will always be only acquisition index generated, regardless of the loops. The coords is a list, and each index in that list is the generated acquisition index. (For performance and memory efficiency reasons we do not store the acquisition index separately, the index of the list will do.) The "next" acquisition index which is generated is exactly the length of the current coords.

new_acq_index = len(acq_channel_data.coords)
schedulable_label_to_acq_index[
    (full_schedulable_label, acq_num_in_operation)
] = new_acq_index
acq_channel_data.coords.append(coords)

For APPEND binned mode we generate as many acquisition indices, as the product of the repetitions of the nested loop structure. For example if nested_loop_repetitions = [3,2], then the overall number of generated new indices is 3*2. Moreover, the "loop_repetitions" key in the coords will tell the users as an additional information (remember, coords appear in the returned data for the user) about which cycle of the loop was measured.


Merge checklist

See also merge request guidelines

  • Merge request has been reviewed (in-depth by a knowledgeable contributor), and is approved by a project maintainer.
  • New code is covered by unit tests (or N/A).
  • New code is documented and docstrings use numpydoc format (or N/A).
  • New functionality: considered making private instead of extending public API (or N/A).
  • Public API changed: added @deprecated and entry in deprecated code suggestions (or N/A).
  • Newly added/adjusted documentation and docstrings render properly (or N/A).
  • Pipeline fix or dependency update: post in #software-for-developers channel to merge main back in or update local packages (or N/A).
  • Tested on hardware (or N/A).
  • CHANGELOG.md for breaking changes and AUTHORS.md have been updated (or N/A).
  • Update Hardware backends documentation if backend interface change or N/A
  • Check whether performance is significantly affected by looking at the Performance metrics results.
  • Windows tests in CI pipeline pass (manually triggered by maintainers before merging).
    • Maintainers do not hit Auto-merge, we need to actively check as manual tests do not block pipeline

For reference, the issues workflow is described in the contribution guidelines.

Edited by Gábor Oszkár Dénes

Merge request reports

Loading