Skip to content

Commit 2d31d51

Browse files
authored
Merge pull request #7346 from samantha-ho/samanthaho/self_registering_parameters
Allow Parameter subclasses to define and unpack their own implicit dependencies and inferences
2 parents ac1313a + 95ac04d commit 2d31d51

39 files changed

Lines changed: 1562 additions & 397 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Registration and Unpacking interfaces created in ParameterBse
2+
3+
``ParameterBase`` now implements new `depends_on``, ``is_controlled_by``, and ``has_control_of``properties that allow subclasses to define ``InterDependencies_`` relationships directly
4+
``ParameterBase.unpack_self`` allows subclasses to unpack themselves during ``DataSaver.add_result``, which removes the requirement for users to add pre-defined ``InterDependencies_`` results explicitly
5+
``Measurement.register_parameter`` has been refactored to follow the relationship links defined in parameter subclasses and automatically register related parameters with the appropriate relationships
6+
``DataSaver.add_result`` has been refactored to take advantage of the new ``unpack_self`` method
7+
8+
Breaking Changes
9+
- A dependent parameter registered with an independent parameter as its ``setpoints`` no longer requires that the independent parameter be registered first, if the independent parameter is ParameterBase subclass and not a str
10+
- Previously, a ParameterWithSetpoints whose setpoints values were explicitly added in add_result would use the explicit version. Now, an error is raised if the explicit values are not within some tolerance of the internal values (as with other duplication).
11+
- ``DataSaver.add_result`` signature has changed from ``*res_tuple`` to ``*result_tuples``
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "a338885a",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from typing import TYPE_CHECKING\n",
11+
"\n",
12+
"import numpy as np\n",
13+
"\n",
14+
"from qcodes.dataset import (\n",
15+
" Measurement,\n",
16+
" initialise_or_create_database_at,\n",
17+
" load_or_create_experiment,\n",
18+
")\n",
19+
"from qcodes.parameters import (\n",
20+
" ManualParameter,\n",
21+
" Parameter,\n",
22+
" ParameterBase,\n",
23+
")\n",
24+
"\n",
25+
"if TYPE_CHECKING:\n",
26+
" from qcodes.dataset.data_set_protocol import ValuesType\n",
27+
" from qcodes.parameters import ParameterBase, ParamRawDataType"
28+
]
29+
},
30+
{
31+
"cell_type": "markdown",
32+
"id": "fd4cb8f8",
33+
"metadata": {},
34+
"source": [
35+
"# Parameter-defined InterDependencies\n",
36+
"\n",
37+
"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](../../dataset/interdependentparams.rst)."
38+
]
39+
},
40+
{
41+
"cell_type": "markdown",
42+
"id": "a7967c55",
43+
"metadata": {},
44+
"source": [
45+
"## Interdependency Definitions:\n",
46+
"- `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\n",
47+
"- `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.\n",
48+
"- `has_control_of`: The opposite direction of the `is_controlled_by` relationship\n",
49+
"\n",
50+
"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.\n",
51+
"\n",
52+
"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."
53+
]
54+
},
55+
{
56+
"cell_type": "markdown",
57+
"id": "7a188625",
58+
"metadata": {},
59+
"source": [
60+
"# ControllingParameter Example"
61+
]
62+
},
63+
{
64+
"cell_type": "code",
65+
"execution_count": 2,
66+
"id": "32651dfa",
67+
"metadata": {},
68+
"outputs": [],
69+
"source": [
70+
"class ControllingParameter(Parameter):\n",
71+
" def __init__(\n",
72+
" self, name: str, components: dict[Parameter, tuple[float, float]]\n",
73+
" ) -> None:\n",
74+
" super().__init__(name=name, get_cmd=False)\n",
75+
" # dict of Parameter to (slope, offset) of components\n",
76+
" self._components_dict: dict[Parameter, tuple[float, float]] = components\n",
77+
" for param in self._components_dict.keys():\n",
78+
" self._has_control_of.add(param)\n",
79+
" param.is_controlled_by.add(self)\n",
80+
"\n",
81+
" def set_raw(self, value: \"ParamRawDataType\") -> None:\n",
82+
" # Set all dependent parameters based on their slope and offsets\n",
83+
" for param, slope_offset in self._components_dict.items():\n",
84+
" param(value * slope_offset[0] + slope_offset[1])\n",
85+
"\n",
86+
" def get_raw(self) -> \"ParamRawDataType\":\n",
87+
" return self.cache.get()\n",
88+
"\n",
89+
" def unpack_self(\n",
90+
" self, value: \"ValuesType\"\n",
91+
" ) -> list[tuple[\"ParameterBase\", \"ValuesType\"]]:\n",
92+
" assert isinstance(value, float)\n",
93+
" unpacked_results = super().unpack_self(value)\n",
94+
" for param, slope_offset in self._components_dict.items():\n",
95+
" unpacked_results.append((param, value * slope_offset[0] + slope_offset[1]))\n",
96+
" return unpacked_results"
97+
]
98+
},
99+
{
100+
"cell_type": "code",
101+
"execution_count": 3,
102+
"id": "9af7d477",
103+
"metadata": {},
104+
"outputs": [],
105+
"source": [
106+
"param1 = ManualParameter(\"param1\", initial_value=0)\n",
107+
"param2 = ManualParameter(\"param2\", initial_value=0)\n",
108+
"control = ControllingParameter(\"control\", components={param1: (1, 0), param2: (-1, 10)})\n",
109+
"\n",
110+
"meas_param = Parameter(\"meas\", get_cmd=lambda: param1() + param2() - 5.0)"
111+
]
112+
},
113+
{
114+
"cell_type": "markdown",
115+
"id": "9241ee47",
116+
"metadata": {},
117+
"source": [
118+
"## ControllingParameter self-registration of components\n",
119+
"\n",
120+
"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."
121+
]
122+
},
123+
{
124+
"cell_type": "code",
125+
"execution_count": 4,
126+
"id": "bb26c0f0",
127+
"metadata": {},
128+
"outputs": [
129+
{
130+
"data": {
131+
"text/plain": [
132+
"{'control': ParamSpecBase('control', 'numeric', 'control', ''),\n",
133+
" 'param1': ParamSpecBase('param1', 'numeric', 'param1', ''),\n",
134+
" 'param2': ParamSpecBase('param2', 'numeric', 'param2', '')}"
135+
]
136+
},
137+
"execution_count": 4,
138+
"metadata": {},
139+
"output_type": "execute_result"
140+
}
141+
],
142+
"source": [
143+
"initialise_or_create_database_at(\"experiments.db\")\n",
144+
"exp = load_or_create_experiment(\"InterDependencies_ examples\")\n",
145+
"meas = Measurement(exp=exp, name=\"self registration example\")\n",
146+
"meas.register_parameter(control)\n",
147+
"\n",
148+
"meas.parameters"
149+
]
150+
},
151+
{
152+
"cell_type": "markdown",
153+
"id": "dce92a39",
154+
"metadata": {},
155+
"source": [
156+
"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."
157+
]
158+
},
159+
{
160+
"cell_type": "markdown",
161+
"id": "870e9165",
162+
"metadata": {},
163+
"source": [
164+
"## ControllingParameter self-unpacking\n",
165+
"\n",
166+
"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."
167+
]
168+
},
169+
{
170+
"cell_type": "code",
171+
"execution_count": 5,
172+
"id": "1eb9f5e2",
173+
"metadata": {},
174+
"outputs": [
175+
{
176+
"name": "stdout",
177+
"output_type": "stream",
178+
"text": [
179+
"Starting experimental run with id: 6. \n"
180+
]
181+
}
182+
],
183+
"source": [
184+
"with meas.run() as datasaver:\n",
185+
" for i in np.linspace(0, 1, 11):\n",
186+
" control(i)\n",
187+
" datasaver.add_result((control, control()))\n",
188+
" ds = datasaver.dataset"
189+
]
190+
},
191+
{
192+
"cell_type": "code",
193+
"execution_count": 6,
194+
"id": "c48afcbe",
195+
"metadata": {},
196+
"outputs": [
197+
{
198+
"data": {
199+
"text/plain": [
200+
"{'param1': {'param1': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]),\n",
201+
" 'control': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])},\n",
202+
" 'param2': {'param2': array([10. , 9.9, 9.8, 9.7, 9.6, 9.5, 9.4, 9.3, 9.2, 9.1, 9. ]),\n",
203+
" 'control': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])}}"
204+
]
205+
},
206+
"execution_count": 6,
207+
"metadata": {},
208+
"output_type": "execute_result"
209+
}
210+
],
211+
"source": [
212+
"ds.get_parameter_data()"
213+
]
214+
},
215+
{
216+
"cell_type": "markdown",
217+
"id": "95f34917",
218+
"metadata": {},
219+
"source": [
220+
"### But does it work with dond?\n",
221+
"\n",
222+
"Yes."
223+
]
224+
},
225+
{
226+
"cell_type": "code",
227+
"execution_count": 10,
228+
"id": "73823b84",
229+
"metadata": {},
230+
"outputs": [
231+
{
232+
"name": "stdout",
233+
"output_type": "stream",
234+
"text": [
235+
"Starting experimental run with id: 8. Using 'qcodes.dataset.dond'\n"
236+
]
237+
},
238+
{
239+
"data": {
240+
"text/plain": [
241+
"{'meas': {'meas': array([5., 5., 5., 5., 5., 5., 5., 5., 5., 5., 5.]),\n",
242+
" 'control': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]),\n",
243+
" 'param1': array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ]),\n",
244+
" 'param2': array([10. , 9.9, 9.8, 9.7, 9.6, 9.5, 9.4, 9.3, 9.2, 9.1, 9. ])}}"
245+
]
246+
},
247+
"execution_count": 10,
248+
"metadata": {},
249+
"output_type": "execute_result"
250+
}
251+
],
252+
"source": [
253+
"from qcodes.dataset import LinSweep, dond\n",
254+
"\n",
255+
"ds, _, _ = dond(LinSweep(control, 0, 1, 11), meas_param)\n",
256+
"ds.get_parameter_data()"
257+
]
258+
}
259+
],
260+
"metadata": {
261+
"kernelspec": {
262+
"display_name": "py311",
263+
"language": "python",
264+
"name": "python3"
265+
},
266+
"language_info": {
267+
"codemirror_mode": {
268+
"name": "ipython",
269+
"version": 3
270+
},
271+
"file_extension": ".py",
272+
"mimetype": "text/x-python",
273+
"name": "python",
274+
"nbconvert_exporter": "python",
275+
"pygments_lexer": "ipython3",
276+
"version": "3.11.8"
277+
}
278+
},
279+
"nbformat": 4,
280+
"nbformat_minor": 5
281+
}

docs/examples/plotting/auto_color_scale.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"import numpy as np\n",
106106
"\n",
107107
"from qcodes.dataset.descriptions.dependencies import InterDependencies_\n",
108-
"from qcodes.dataset.descriptions.param_spec import ParamSpecBase\n",
108+
"from qcodes.parameters import ParamSpecBase\n",
109109
"\n",
110110
"\n",
111111
"def dataset_with_outliers_generator(\n",

src/qcodes/dataset/data_export.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
if TYPE_CHECKING:
1313
from qcodes.dataset.data_set_protocol import DataSetProtocol
14-
from qcodes.dataset.descriptions.param_spec import ParamSpecBase
14+
from qcodes.parameters import ParamSpecBase
1515

1616
log = logging.getLogger(__name__)
1717

src/qcodes/dataset/data_set.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@
110110
import pandas as pd
111111
import xarray as xr
112112

113-
from qcodes.dataset.descriptions.param_spec import ParamSpec, ParamSpecBase
113+
from qcodes.dataset.descriptions.param_spec import ParamSpec
114114
from qcodes.dataset.descriptions.versioning.rundescribertypes import Shapes
115-
from qcodes.parameters import ParameterBase
115+
from qcodes.parameters import ParameterBase, ParamSpecBase
116116

117117

118118
log = logging.getLogger(__name__)

src/qcodes/dataset/data_set_in_memory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@
5656
import pandas as pd
5757
import xarray as xr
5858

59-
from qcodes.dataset.descriptions.param_spec import ParamSpec, ParamSpecBase
59+
from qcodes.dataset.descriptions.param_spec import ParamSpec
6060
from qcodes.dataset.descriptions.versioning.rundescribertypes import Shapes
61+
from qcodes.parameters import ParamSpecBase
6162

6263
from ..parameters import ParameterBase
6364

src/qcodes/dataset/data_set_protocol.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import numpy.typing as npt
2020

2121
from qcodes.dataset.descriptions.dependencies import InterDependencies_
22-
from qcodes.dataset.descriptions.param_spec import ParamSpec, ParamSpecBase
22+
from qcodes.dataset.descriptions.param_spec import ParamSpec
2323
from qcodes.dataset.export_config import (
2424
DataExportType,
2525
get_data_export_name_elements,
@@ -42,7 +42,7 @@
4242
from qcodes.dataset.descriptions.rundescriber import RunDescriber
4343
from qcodes.dataset.descriptions.versioning.rundescribertypes import Shapes
4444
from qcodes.dataset.linked_datasets.links import Link
45-
from qcodes.parameters import ParameterBase
45+
from qcodes.parameters import ParameterBase, ParamSpecBase
4646

4747
from .data_set_cache import DataSetCache
4848
from .exporters.export_info import ExportInfo

src/qcodes/dataset/descriptions/dependencies.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
import networkx as nx
1616
from typing_extensions import deprecated
1717

18+
from qcodes.parameters import ParamSpecBase
1819
from qcodes.utils import QCoDeSDeprecationWarning
1920

20-
from .param_spec import ParamSpecBase
21-
2221
if TYPE_CHECKING:
2322
from collections.abc import Sequence
2423

0 commit comments

Comments
 (0)