Source code for qcodes.dataset.measurement_extensions

from __future__ import annotations

import time
from collections.abc import Generator, Sequence
from contextlib import ExitStack, contextmanager
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from opentelemetry import trace

from qcodes.dataset.dond.do_nd import _Sweeper
from qcodes.dataset.dond.do_nd_utils import ParamMeasT, catch_interrupts
from qcodes.dataset.dond.sweeps import AbstractSweep, LinSweep, TogetherSweep
from qcodes.dataset.measurements import DataSaver, Measurement
from qcodes.dataset.threading import process_params_meas
from qcodes.parameters.parameter_base import ParameterBase

if TYPE_CHECKING:
    from qcodes.dataset.experiment_container import Experiment

TRACER = trace.get_tracer(__name__)


[docs] @dataclass class DataSetDefinition: """ A specification for the creation of a Dataset or Measurement object """ name: str """The name to be assigned to the Measurement and dataset""" independent: Sequence[ParameterBase] """A sequence of independent parameters in the Measurement and dataset""" dependent: Sequence[ParameterBase] """A sequence of dependent parameters in the Measurement and dataset Note: All dependent parameters will depend on all independent parameters""" experiment: Experiment | None = None """An optional argument specifying which Experiment this dataset should be written to""" metadata: dict[str, Any] | None = None """An optional dictionary of metadata that will be added to the dataset generated by this definition"""
def setup_measurement_instances( dataset_definitions: Sequence[DataSetDefinition], override_experiment: Experiment | None = None, ) -> list[Measurement]: """Creates a set of Measurement instances and registers parameters Args: dataset_definitions: A set of DataSetDefinitions to create and register parameters for override_experiment: The Experiment all Measurement objects will be part of Returns: A list of Measurement objects """ measurements: list[Measurement] = [] for ds_def in dataset_definitions: ds_experiment = ( override_experiment if override_experiment is not None else ds_def.experiment ) meas = Measurement(name=ds_def.name, exp=ds_experiment) for param in ds_def.independent: meas.register_parameter(param) for param in ds_def.dependent: meas.register_parameter(param, setpoints=ds_def.independent) measurements.append(meas) return measurements
[docs] @contextmanager def datasaver_builder( dataset_definitions: Sequence[DataSetDefinition], *, override_experiment: Experiment | None = None, ) -> Generator[list[DataSaver], Any, None]: """ A utility context manager intended to simplify the creation of datasavers The datasaver builder can be used to streamline the creation of multiple datasavers where all dependent parameters depend on all independent parameters. Args: dataset_definitions: A set of DataSetDefinitions to create and register parameters for override_experiment: Sets the Experiment for all datasets to be written to. This argument overrides any experiments provided in the DataSetDefinition Yields: A list of generated datasavers with parameters registered """ measurement_instances = setup_measurement_instances( dataset_definitions, override_experiment ) with ( TRACER.start_as_current_span("qcodes.dataset.datasaver_builder"), catch_interrupts() as _, ExitStack() as stack, ): datasaver_builder_span = trace.get_current_span() datasavers = [ stack.enter_context(measurement.run(parent_span=datasaver_builder_span)) for measurement in measurement_instances ] for i, datasaver in enumerate(datasavers): ds_def = dataset_definitions[i] if ds_def.metadata is not None: for key, value in ds_def.metadata.items(): datasaver.dataset.add_metadata(tag=key, metadata=value) yield datasavers
def parse_dond_into_args( *params: AbstractSweep | TogetherSweep | ParamMeasT | Sequence[ParamMeasT], ) -> tuple[list[AbstractSweep], list[ParamMeasT]]: """ Parse supplied arguments into sweeps and measurement parameters Measurement parameters may include Callables which are executed in order Args: params: Instances of n sweep classes and m measurement parameters or callables Returns: A tuple of the list of sweeps to perform and a list of the parameters to measure. """ sweep_instances: list[AbstractSweep] = [] params_meas: list[ParamMeasT] = [] for par in params: if isinstance(par, AbstractSweep): sweep_instances.append(par) elif isinstance(par, TogetherSweep): raise ValueError("dond_into does not support TogetherSweeps") elif isinstance(par, Sequence): raise ValueError("dond_into does not support multiple datasets") elif isinstance(par, ParameterBase) and par.gettable: params_meas.append(par) elif callable(par): params_meas.append(par) return sweep_instances, params_meas
[docs] def dond_into( datasaver: DataSaver, *params: AbstractSweep | ParamMeasT, additional_setpoints: Sequence[ParameterBase] = tuple(), ) -> None: """ A doNd-like utility function that writes gridded data to the supplied DataSaver dond_into accepts AbstractSweep objects and measurement parameters or callables. It executes the specified Sweeps, reads the measurement parameters, and stores the resulting data in the datasaver. Args: datasaver: The datasaver to write data to params: Instances of n sweep classes and m measurement parameters, e.g. if linear sweep is considered: .. code-block:: python LinSweep(param_set_1, start_1, stop_1, num_points_1, delay_1), ..., LinSweep(param_set_n, start_n, stop_n, num_points_n, delay_n), param_meas_1, param_meas_2, ..., param_meas_m additional_setpoints: A list of setpoint parameters to be registered in the measurement but not scanned/swept-over. """ # at this stage multiple measurement context managers may be in run state # as datasavers. Here we ensure we bind the parent span to the correct # datasaver. if datasaver._span is not None: context = trace.set_span_in_context(datasaver._span) else: context = None with TRACER.start_as_current_span("qcodes.dataset.dond_into", context=context): sweep_instances, params_meas = parse_dond_into_args(*params) sweeper = _Sweeper(sweep_instances, additional_setpoints) for set_events in sweeper: results: dict[ParameterBase, Any] = {} additional_setpoints_data = process_params_meas(additional_setpoints) for set_event in set_events: if set_event.should_set: set_event.parameter(set_event.new_value) for act in set_event.actions: act() time.sleep(set_event.delay) if set_event.get_after_set: results[set_event.parameter] = set_event.parameter() else: results[set_event.parameter] = set_event.new_value meas_value_pair = process_params_meas(params_meas) for meas_param, value in meas_value_pair: results[meas_param] = value datasaver.add_result( *list(results.items()), *additional_setpoints_data, )
[docs] class LinSweeper(LinSweep): """ An iterable version of the LinSweep class Iterations of this object, set the next setpoint and then wait the delay time """ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._setpoints = self.get_setpoints() self._iter_index = 0 def __iter__(self) -> LinSweeper: return self def __next__(self) -> float: if self._iter_index < self._num_points: set_val = self._setpoints[self._iter_index] self._param(set_val) time.sleep(self._delay) self._iter_index += 1 return set_val else: raise StopIteration