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 mergemain
back in or update local packages (or N/A). -
Tested on hardware (or N/A). -
CHANGELOG.md
for breaking changes andAUTHORS.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.