Source code for quantify_scheduler.instrument_coordinator.utility

# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""Utility functions for the instrument coordinator and components."""
from __future__ import annotations

import logging
from typing import TYPE_CHECKING

import numpy as np
import xarray
from qcodes.parameters.parameter import Parameter

if TYPE_CHECKING:
    from qcodes.instrument.base import InstrumentBase

[docs] logger = logging.getLogger(__name__)
[docs] def search_settable_param(instrument: InstrumentBase, nested_parameter_name: str) -> Parameter: """ Searches for a settable parameter in nested instrument hierarchies. For example `instrument.submodule_1.channel_1.parameter.` Parameters ---------- instrument: The root QCoDeS instrument where the parameter resides. nested_parameter_name: Hierarchical nested parameter name. Returns ------- Parameter: """ root_param = instrument split_params = nested_parameter_name.split(".") def _search_next_level( child_parameter_name: str | Parameter, root_attr_dicts_list: list ) -> Parameter | None: if callable(child_parameter_name): return child_parameter_name for root_attr_dict in root_attr_dicts_list: if child_parameter_name in root_attr_dict: return root_attr_dict.get(child_parameter_name) return # Search for the parameter within the parameter, function # or submodule delegate_attrs_dict of the instrument for child_parameter_name in split_params: # On the types: _search_next_level returns either None or an object that has the # parameters below. Types are omitted because of their complexity. root_attr_dicts_list = [ root_param.parameters, # type: ignore root_param.submodules, # type: ignore root_param.functions, # type: ignore ] root_param = _search_next_level(child_parameter_name, root_attr_dicts_list) if root_param is None: break if not (isinstance(root_param, Parameter) or callable(root_param)): raise ValueError( f"Could not find settable parameter " f'"{nested_parameter_name}" in instrument "{instrument}"' ) # If the return type is not a Parameter, then we assume it is a structural subtype # (duck typing) of a Parameter. return root_param # type: ignore
[docs] def parameter_value_same_as_cache( instrument: InstrumentBase, parameter_name: str, val: object ) -> bool: """ Returns whether the value of a QCoDeS parameter is the same as the value in cache. Parameters ---------- instrument: The QCoDeS instrument to set the parameter on. parameter_name: Name of the parameter to set. val: Value to set it to. Returns ------- bool """ parameter = search_settable_param(instrument=instrument, nested_parameter_name=parameter_name) # parameter.cache() throws for non-gettable parameters if the cache is invalid. # This order prevents the exception. return parameter.cache.valid and parameter.cache() == val
[docs] def lazy_set(instrument: InstrumentBase, parameter_name: str, val: object) -> None: """ Set the value of a QCoDeS parameter only if it is different from the value in cache. Parameters ---------- instrument: The QCoDeS instrument to set the parameter on. parameter_name: Name of the parameter to set. val: Value to set it to. """ parameter = search_settable_param(instrument=instrument, nested_parameter_name=parameter_name) # parameter.cache() throws for non-gettable parameters if the cache is invalid. # This order prevents the exception. if not parameter_value_same_as_cache(instrument, parameter_name, val): parameter.set(val) else: logger.info(f"Lazy set skipped setting parameter {instrument.name}.{parameter_name}")
[docs] def check_already_existing_acquisition( new_dataset: xarray.Dataset, current_dataset: xarray.Dataset ) -> None: """ Verifies non-overlapping data in new_dataset and current_dataset. If there is, it will raise an error. Parameters ---------- new_dataset New dataset. current_dataset Current dataset. """ conflicting_indices_str = [] for acq_channel, _data_array in new_dataset.items(): if acq_channel in current_dataset: # The return values are two `DataArray`s with only coordinates # which are common in the inputs. common_0, common_1 = xarray.align( new_dataset[acq_channel], current_dataset[acq_channel], join="inner" ) # We need to check if the values are `math.nan`, because if they are, # that means there is no value at that position (xarray standard). def mask_func(x: float, y: float) -> int: return 0 if np.isnan(x) or np.isnan(y) else 1 conflict_mask = xarray.apply_ufunc(mask_func, common_0, common_1, vectorize=True) for conflict in conflict_mask: if conflict.values == [1]: conflicting_coords = [("acq_channel", acq_channel)] conflicting_coords += [(dim, conflict[dim].values) for dim in conflict.coords] coords_str = [f"{dim}={coord}" for dim, coord in conflicting_coords] conflicting_indices_str.append("; ".join(coords_str)) if conflicting_indices_str: conflicting_indices_str = "\n".join(conflicting_indices_str) raise RuntimeError( f"Attempting to gather acquisitions. " f"Make sure an acq_channel, acq_index corresponds to not more than one acquisition.\n" f"The following indices are defined multiple times.\n" f"{conflicting_indices_str}" )