Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow additional validators on parameter at runtime #5426

Merged
merged 21 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changes/newsfragments/5426.new
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The QCoDeS Parameters have nov gained the ability to use multiple validators.
See `here <../examples/Parameters/Parameters.ipynb>`__ for examples of how to use this.
110 changes: 93 additions & 17 deletions docs/examples/Parameters/Parameters.ipynb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -50,10 +57,10 @@
"source": [
"from typing import Optional\n",
"\n",
"from qcodes.instrument.base import InstrumentBase\n",
"from qcodes import validators\n",
"from qcodes.instrument import InstrumentBase\n",
"from qcodes.parameters import Parameter\n",
"from qcodes.tests.instrument_mocks import DummyInstrument\n",
"from qcodes.utils import validators"
"from qcodes.tests.instrument_mocks import DummyInstrument"
]
},
{
Expand Down Expand Up @@ -88,12 +95,12 @@
" docstring='counts how many times get has been called '\n",
" 'but can be reset to any integer >= 0 by set')\n",
" self._count = 0\n",
" \n",
"\n",
" # you must provide a get method, a set method, or both.\n",
" def get_raw(self):\n",
" self._count += 1\n",
" return self._count\n",
" \n",
"\n",
" def set_raw(self, val):\n",
" self._count = val\n",
" return self._count"
Expand Down Expand Up @@ -201,11 +208,11 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from qcodes.instrument import DelegateParameter"
"from qcodes.parameters import DelegateParameter"
]
},
{
Expand All @@ -217,7 +224,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 8,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -248,7 +255,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -257,7 +264,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 10,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -297,7 +304,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 11,
"metadata": {
"ExecuteTime": {
"end_time": "2021-06-17T08:58:52.491912Z",
Expand All @@ -310,13 +317,13 @@
" def __init__(self, name, dac_param):\n",
" self._dac_param = dac_param\n",
" super().__init__(name)\n",
" \n",
"\n",
" @property\n",
" def underlying_instrument(self) -> Optional[InstrumentBase]:\n",
" return self._dac_param.root_instrument\n",
" \n",
"\n",
" def get_raw(self):\n",
" return self._dac_param.get() "
" return self._dac_param.get()"
]
},
{
Expand All @@ -330,7 +337,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2021-06-17T08:58:48.861043Z",
Expand All @@ -351,7 +358,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 13,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -382,6 +389,75 @@
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Validating parameters"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When a parameter is set it is automatically validated against any validator set for that parameter. Most QCoDeS instrument drivers are configured to only validate input that the instrument can actually set. In the example of dac.ch1 the values that can be set are between -800 and 400. Sometimes it may however, be useful to be able to restrict the valid values even further, perhaps due to limitations in the range of a device under test or a specific calibration range. This can be done either by adding an additional validator or by using the `extra_validator` context manager. "
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Default validators: (<Numbers -800<=v<=400>,)\n",
"Failed to validate setting to 450.\n",
"Validator added: (<Numbers -800<=v<=400>, <Numbers -500<=v<=300>)\n",
"Failed to validate setting to 350.\n",
"Validator context: (<Numbers -800<=v<=400>, <Numbers -500<=v<=-100>)\n",
"Failed to validate setting to 250 within context manager.\n"
]
}
],
"source": [
"dac.ch1\n",
"print(f\"Default validators: {dac.ch1.validators}\")\n",
"\n",
"# if we try to set a value outside the range of the default validator we get an error.\n",
"try:\n",
" dac.ch1(450)\n",
"except Exception:\n",
" print(\"Failed to validate setting to 450.\")\n",
"\n",
"\n",
"dac.ch1.add_validator(validators.Numbers(-500,300))\n",
"print(f\"Validator added: {dac.ch1.validators}\")\n",
"\n",
"# with the new validator set we cannot set the value outside its range.\n",
"try:\n",
" dac.ch1(350)\n",
"except Exception:\n",
" print(\"Failed to validate setting to 350.\")\n",
"\n",
"# but once it is removed we can again set any value in the range of the original validator.\n",
"dac.ch1.remove_validator()\n",
"dac.ch1(350)\n",
"\n",
"# we can also temorarily add a validator using a context manager.\n",
"\n",
"with dac.ch1.extra_validator(validators.Numbers(-500,-100)):\n",
" print(f\"Validator context: {dac.ch1.validators}\")\n",
" # with the new validator set we cannot set the value outside its range.\n",
" try:\n",
" dac.ch1(250)\n",
" except Exception:\n",
" print(\"Failed to validate setting to 250 within context manager.\")\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -406,7 +482,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.0"
"version": "3.11.5"
},
"toc": {
"base_numbering": 1,
Expand Down
25 changes: 18 additions & 7 deletions qcodes/parameters/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,20 +329,31 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType:
if initial_cache_value is not None:
self.cache.set(initial_cache_value)

self._docstring = docstring
self.__doc__ = self._build__doc__()

def _build__doc__(self) -> str:
if len(self.validators) == 0:
validator_docstrings = ["* `vals` None"]
else:
validator_docstrings = [
f"* `vals` {validator!r}" for validator in self.validators
]
# generate default docstring
self.__doc__ = os.linesep.join(
doc = os.linesep.join(
(
"Parameter class:",
"",
"* `name` %s" % self.name,
"* `label` %s" % self.label,
"* `unit` %s" % self.unit,
"* `vals` %s" % repr(self.vals),
f"* `name` {self.name}",
f"* `label` {self.label}",
f"* `unit` {self.unit}",
*validator_docstrings,
)
)
if self._docstring is not None:
doc = os.linesep.join((self._docstring, "", doc))

if docstring is not None:
self.__doc__ = os.linesep.join((docstring, "", self.__doc__))
return doc

@property
def unit(self) -> str:
Expand Down