This page was generated from docs/examples/Parameters/Parameter_defined_InterDependencies.ipynb. Interactive online version: Binder badge.

[1]:
from typing import TYPE_CHECKING

import numpy as np

from qcodes.dataset import (
    Measurement,
    initialise_or_create_database_at,
    load_or_create_experiment,
)
from qcodes.parameters import (
    ManualParameter,
    Parameter,
    ParameterBase,
)

if TYPE_CHECKING:
    from qcodes.dataset.data_set_protocol import ValuesType
    from qcodes.parameters import ParameterBase, ParamRawDataType

Parameter-defined InterDependencies

This example demonstrates how to use the depends_on, has_control_of, and is_controlled_by properties to define granular implicit interdependencies between Parameters. These are described in greater detail in the Interdependent Parameters.

Interdependency Definitions:

  • depends_on: (also setpoints) An experimental relationship, usually the focus of the measurement. A dependent parameter will generally depend_on one or more independent parameters

  • is_controlled_by: (also basis and inferred_from) A well-known or defined relationship, with an explicit mathematical function to describe it. The directionality is important: We say a parameter A is inferred from B if there exists a function f such that f(B) = A.

  • has_control_of: The opposite direction of the is_controlled_by relationship

In this example, we will first create a ControllingParameter class that operates two component parameters in tandem according to simple linear equations. We will look at how it uses the has_control_of and is_controlled_by properties to ensure that these components are properly registered in a Measurement. Finally, we will examine its custom unpack_self method which allows datasaver.add_result to add component results even if they are not explicitly added.

Then we will show how to bind a depends_on relationship to a parameter, and demonstrate how this simplifies handling of fixed and constant dependencies.

ControllingParameter Example

[2]:
class ControllingParameter(Parameter):
    def __init__(
        self, name: str, components: dict[Parameter, tuple[float, float]]
    ) -> None:
        super().__init__(name=name, get_cmd=False)
        # dict of Parameter to (slope, offset) of components
        self._components_dict: dict[Parameter, tuple[float, float]] = components
        for param in self._components_dict.keys():
            self._has_control_of.add(param)
            param.is_controlled_by.add(self)

    def set_raw(self, value: "ParamRawDataType") -> None:
        # Set all dependent parameters based on their slope and offsets
        for param, slope_offset in self._components_dict.items():
            param(value * slope_offset[0] + slope_offset[1])

    def get_raw(self) -> "ParamRawDataType":
        return self.cache.get()

    def unpack_self(
        self, value: "ValuesType"
    ) -> list[tuple["ParameterBase", "ValuesType"]]:
        assert isinstance(value, float)
        unpacked_results = super().unpack_self(value)
        for param, slope_offset in self._components_dict.items():
            unpacked_results.append((param, value * slope_offset[0] + slope_offset[1]))
        return unpacked_results
[3]:
param1 = ManualParameter("param1", initial_value=0)
param2 = ManualParameter("param2", initial_value=0)
control = ControllingParameter("control", components={param1: (1, 0), param2: (-1, 10)})

meas_param = Parameter("meas", get_cmd=lambda: param1() + param2() - 5.0)

ControllingParameter self-registration of components

In the __init__ method of the ControllingParameter, we use two new attributes to define its built-in InterDependencies. The has_control_of property is an ordered set of its internal components. We also add the ControllingParameter instance to the is_controlled_by sets of the components. This lets us register just one of the set param1, param2, control and get the other two for free.

[4]:
initialise_or_create_database_at("experiments.db")
exp = load_or_create_experiment("InterDependencies_ examples")
meas = Measurement(exp=exp, name="self registration example")
meas.register_parameter(control)

meas.parameters
[4]:
{'control': ParamSpecBase('control', 'numeric', 'control', ''),
 'param1': ParamSpecBase('param1', 'numeric', 'param1', ''),
 'param2': ParamSpecBase('param2', 'numeric', 'param2', '')}

In addition to the has_control_of and is_controlled_by properties, there is also a similar depends_on property that can be used to flexibly create something like the ParameterWithSetpoints. The setpoints of a ParameterWithSetpoints are now added to its internal depends_on set, where they are automatically self-registered with the same machinery as we demonstrated above.

ControllingParameter self-unpacking

For qcodes measurements, parameter registration is only the first part of the story. Inside the measurement loop itself, we use datasaver.add_result to save new data to the resulting database. The unpack_self method defined in the ControllingParameter class handles unpacking a ControllingParameter result tuple, so that the data for its components is also saved.

[5]:
with meas.run() as datasaver:
    for i in np.linspace(0, 1, 11):
        control(i)
        datasaver.add_result((control, control()))
    ds = datasaver.dataset
Starting experimental run with id: 1.
[6]:
ds.get_parameter_data()
[6]:
{'param1': {'param1': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]),
  'control': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])},
 'param2': {'param2': array([10. ,  9.9,  9.8,  9.7,  9.6,  9.5,  9.4,  9.3,  9.2,  9.1,  9. ]),
  'control': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])}}

But does it work with dond?

Yes.

[7]:
from qcodes.dataset import LinSweep, dond

ds, _, _ = dond(LinSweep(control, 0, 1, 11), meas_param)
ds.get_parameter_data()
Starting experimental run with id: 2. Using 'qcodes.dataset.dond'
[7]:
{'meas': {'meas': array([5., 5., 5., 5., 5., 5., 5., 5., 5., 5., 5.]),
  'control': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]),
  'param1': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]),
  'param2': array([10. ,  9.9,  9.8,  9.7,  9.6,  9.5,  9.4,  9.3,  9.2,  9.1,  9. ])}}