This page was generated from
docs/examples/Parameters/Parameter_defined_InterDependencies.ipynb.
Interactive online version:
.
[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
: (alsosetpoints
) An experimental relationship, usually the focus of the measurement. A dependent parameter will generallydepend_on
one or more independent parametersis_controlled_by
: (alsobasis
andinferred_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 theis_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. ])}}