Skip to content

[WIP] Add convenience methods to DelegateParameter #6832

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
4 changes: 4 additions & 0 deletions docs/changes/newsfragments/6832.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
``DelegateParameter`` now includes validators of its source Parameter into its validators. This ensures that a ``DelegateParameter``
with a non numeric source parameter is registered correctly in a measurement when the ``DelegateParameter`` it self does not
set a validator. Furthermore `DelegateParameter`` has gained ``root_source`` and ``root_delegate`` attributes that makes it easier to get the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
set a validator. Furthermore `DelegateParameter`` has gained ``root_source`` and ``root_delegate`` attributes that makes it easier to get the
set a validator. Furthermore ``DelegateParameter`` has gained ``root_source`` and ``root_delegate`` attributes that makes it easier to get the

root source or ``DelegateParameter`` of a ``DelegateParameter`` that delegates to another ``DelegateParameter`` in a chain.
51 changes: 51 additions & 0 deletions src/qcodes/parameters/delegate_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from collections.abc import Sequence
from datetime import datetime

from qcodes.validators.validators import Validator

from .parameter_base import ParamDataType, ParamRawDataType


Expand Down Expand Up @@ -200,6 +202,40 @@ def source(self) -> Parameter | None:
def source(self, source: Parameter | None) -> None:
self._source: Parameter | None = source

@property
def root_source(self) -> Parameter | None:
"""
The root source parameter that this :class:`DelegateParameter` is bound to
or ``None`` if this :class:`DelegateParameter` is unbound. If
the source is it self a DelegateParameter it will recursively return that Parameter's
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the source is it self a DelegateParameter it will recursively return that Parameter's
the source is it self a :class:`DelegateParameter`, this property will recursively return that Parameter's

source until a non DelegateParameter is found. For a non DelegateParameter source
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
source until a non DelegateParameter is found. For a non DelegateParameter source
source until a non DelegateParameter is found. If a source of this parameter is not a DelegateParameter,

this behaves the same as ``self.source``

:getter: Returns the current source.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:getter: Returns the current source.
:getter: Returns the current source parameter that's at the end of a chain of DelegateParameters.

:setter: Sets the source of the first parameter in the tree that has a non-DelegateParameter source.
"""
if isinstance(self.source, DelegateParameter):
return self.source.root_source
else:
return self.source

@root_source.setter
def root_source(self, source: Parameter | None) -> None:
self.root_delegate.source = source

@property
def root_delegate(self) -> DelegateParameter:
"""
If this parameter is part of a chain of DelegateParameters return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If this parameter is part of a chain of DelegateParameters return
If this parameter is part of a chain of DelegateParameters, return

the first Parameter in the chain that has a non DelegateParameter source
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the first Parameter in the chain that has a non DelegateParameter source
the first Parameter in the chain that has a non DelegateParameter source,

else return self.
"""

if not isinstance(self.source, DelegateParameter):
return self
else:
return self.source.root_delegate

@property
def snapshot_value(self) -> bool:
if self.source is None:
Expand Down Expand Up @@ -314,3 +350,18 @@ def validate(self, value: ParamDataType) -> None:
super().validate(value)
if self.source is not None:
self.source.validate(self._from_value_to_raw_value(value))

@property
def validators(self) -> tuple[Validator, ...]:
"""
Tuple of all validators associated with the parameter. Note that this
includes validators of the source parameter if source parameter is set
and has any validators.

:getter: All validators associated with the parameter.
"""
source_validators: tuple[Validator, ...] = (
self.source.validators if self.source is not None else ()
)

return tuple(self._vals) + source_validators
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just wonder, if it's cleaner (and even possible) to use super here?

Suggested change
return tuple(self._vals) + source_validators
return super().validators + source_validators

5 changes: 3 additions & 2 deletions src/qcodes/parameters/parameter_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,10 @@ def vals(self) -> Validator | None:
RuntimeError: If removing the first validator when more than one validator is set.

"""
validators = self.validators

if len(self._vals):
return self._vals[0]
if len(validators):
return validators[0]
else:
return None

Expand Down
45 changes: 44 additions & 1 deletion tests/dataset/measurement/test_measurement_context_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@
from qcodes.dataset.export_config import DataExportType
from qcodes.dataset.measurements import Measurement
from qcodes.dataset.sqlite.connection import atomic_transaction
from qcodes.parameters import ManualParameter, Parameter, expand_setpoints_helper
from qcodes.parameters import (
DelegateParameter,
ManualParameter,
Parameter,
expand_setpoints_helper,
)
from qcodes.station import Station
from qcodes.validators import ComplexNumbers
from tests.common import retry_until_does_not_throw


Expand Down Expand Up @@ -201,6 +207,43 @@ def test_register_custom_parameter(DAC) -> None:
)


def test_register_delegate_parameters():
x_param = Parameter("x", set_cmd=None, get_cmd=None)

complex_param = Parameter(
"complex_param", get_cmd=None, set_cmd=None, vals=ComplexNumbers()
)
delegate_param = DelegateParameter("delegate", source=complex_param)

meas = Measurement()

meas.register_parameter(x_param)
meas.register_parameter(delegate_param, setpoints=(x_param,))
assert len(meas.parameters) == 2
assert meas.parameters["delegate"].type == "complex"
assert meas.parameters["x"].type == "numeric"


def test_register_delegate_parameters_with_late_source():
x_param = Parameter("x", set_cmd=None, get_cmd=None)

complex_param = Parameter(
"complex_param", get_cmd=None, set_cmd=None, vals=ComplexNumbers()
)
delegate_param = DelegateParameter("delegate", source=None)

meas = Measurement()

meas.register_parameter(x_param)

delegate_param.source = complex_param

meas.register_parameter(delegate_param, setpoints=(x_param,))
assert len(meas.parameters) == 2
assert meas.parameters["delegate"].type == "complex"
assert meas.parameters["x"].type == "numeric"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets also add a test for delegate of delegate of complex

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A -> B -> C -> None

Add source to C

verify that A has the correct validator.



def test_unregister_parameter(DAC, DMM) -> None:
"""
Test the unregistering of parameters.
Expand Down
154 changes: 153 additions & 1 deletion tests/parameter/test_delegate_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,18 +574,23 @@ def test_value_validation() -> None:
source_param = Parameter("source", set_cmd=None, get_cmd=None)
delegate_param = DelegateParameter("delegate", source=source_param)

# Test case where source parameter validator is None and delegate parameter validator is
# specified.
delegate_param.vals = vals.Numbers(-10, 10)
source_param.vals = None
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(11)

# Test where delegate parameter validator is None and source parameter validator is
# specified.
delegate_param.vals = None
source_param.vals = vals.Numbers(-5, 5)
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(6)

# Test case where source parameter validator is more restricted than delegate parameter.
delegate_param.vals = vals.Numbers(-10, 10)
source_param.vals = vals.Numbers(-5, 5)
delegate_param.validate(1)
Expand All @@ -594,6 +599,66 @@ def test_value_validation() -> None:
with pytest.raises(ValueError):
delegate_param.validate(11)

# Test case that the order of setting validator on source and delegate parameters does not matter.
source_param.vals = vals.Numbers(-5, 5)
delegate_param.vals = vals.Numbers(-10, 10)
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(6)
with pytest.raises(ValueError):
delegate_param.validate(11)

# Test case where delegate parameter validator is more restricted than source parameter.
delegate_param.vals = vals.Numbers(-5, 5)
source_param.vals = vals.Numbers(-10, 10)
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(6)
with pytest.raises(ValueError):
delegate_param.validate(11)

# Test case that the order of setting validator on source and delegate parameters does not matter.
source_param.vals = vals.Numbers(-10, 10)
delegate_param.vals = vals.Numbers(-5, 5)
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(6)
with pytest.raises(ValueError):
delegate_param.validate(11)


def test_validator_delegates_as_expected() -> None:
source_param = Parameter("source", set_cmd=None, get_cmd=None)
delegate_param = DelegateParameter("delegate", source=source_param)
some_validator = vals.Numbers(-10, 10)
source_param.vals = some_validator
delegate_param.vals = None
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(11)
assert delegate_param.validators == (some_validator,)
assert delegate_param.vals == some_validator


def test_validator_delegates_and_source() -> None:
source_param = Parameter("source", set_cmd=None, get_cmd=None)
delegate_param = DelegateParameter("delegate", source=source_param)
some_validator = vals.Numbers(-10, 10)
some_other_validator = vals.Numbers(-5, 5)
source_param.vals = some_validator
delegate_param.vals = some_other_validator
delegate_param.validate(1)
with pytest.raises(ValueError):
delegate_param.validate(6)
assert delegate_param.validators == (some_other_validator, some_validator)
assert delegate_param.vals == some_other_validator

assert delegate_param.source is not None
delegate_param.source.vals = None

assert delegate_param.validators == (some_other_validator,)
assert delegate_param.vals == some_other_validator
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extend to 2 delegate parameters too



def test_value_validation_with_offset_and_scale() -> None:
source_param = Parameter(
Expand Down Expand Up @@ -639,7 +704,7 @@ def test_value_validation_with_offset_and_scale() -> None:
delegate_param.set(1)


def test_delegate_of_delegate_updates_settable_gettable():
def test_delegate_of_delegate_updates_settable_gettable() -> None:
gettable_settable_source_param = Parameter(
"source", set_cmd=None, get_cmd=None, vals=vals.Numbers(-5, 5)
)
Expand Down Expand Up @@ -672,6 +737,93 @@ def test_delegate_of_delegate_updates_settable_gettable():
assert not delegate_param_outer.settable


def test_delegate_of_delegate_root_source() -> None:
gettable_settable_source_param = Parameter(
"source", set_cmd=None, get_cmd=None, vals=vals.Numbers(-5, 5)
)

delegate_param_inner = DelegateParameter(
"delegate_inner", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_outer = DelegateParameter(
"delegate_outer", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_outer.source = delegate_param_inner
delegate_param_inner.source = gettable_settable_source_param

assert delegate_param_outer.root_source == gettable_settable_source_param
assert delegate_param_outer.source is not None
assert delegate_param_outer.source.source == gettable_settable_source_param
assert delegate_param_outer.root_delegate == delegate_param_inner

assert delegate_param_inner.root_source == gettable_settable_source_param
assert delegate_param_inner.source == gettable_settable_source_param
assert delegate_param_inner.root_delegate == delegate_param_inner

delegate_param_outer.root_source = None

assert delegate_param_outer.root_source is None
assert delegate_param_outer.source is not None
assert delegate_param_outer.source.source is None
assert delegate_param_outer.root_delegate == delegate_param_inner

assert delegate_param_inner.root_source is None
assert delegate_param_inner.source is None
assert delegate_param_inner.root_delegate == delegate_param_inner


def test_delegate_chain_root_source() -> None:
gettable_settable_source_param = Parameter(
"source", set_cmd=None, get_cmd=None, vals=vals.Numbers(-5, 5)
)

delegate_param_inner = DelegateParameter(
"delegate_inner", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_middle = DelegateParameter(
"delegate_inner", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_outer = DelegateParameter(
"delegate_outer", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_outer.source = delegate_param_middle
delegate_param_middle.source = delegate_param_inner
delegate_param_inner.source = gettable_settable_source_param

assert delegate_param_outer.root_source == gettable_settable_source_param
assert delegate_param_outer.source is not None
assert delegate_param_outer.source.source == delegate_param_inner
assert isinstance(delegate_param_outer.source.source, DelegateParameter)
assert delegate_param_outer.source.source.source == gettable_settable_source_param
assert delegate_param_outer.root_delegate == delegate_param_inner

assert delegate_param_middle.root_source == gettable_settable_source_param
assert delegate_param_middle.source == delegate_param_inner
assert delegate_param_middle.source.source == gettable_settable_source_param
assert delegate_param_middle.root_delegate == delegate_param_inner

assert delegate_param_inner.root_source == gettable_settable_source_param
assert delegate_param_inner.source == gettable_settable_source_param
assert delegate_param_inner.root_delegate == delegate_param_inner

delegate_param_outer.root_source = None

assert delegate_param_outer.root_source is None
assert delegate_param_outer.source is not None
assert delegate_param_outer.source.source is not None
assert delegate_param_outer.source.source.source is None
assert delegate_param_outer.root_delegate == delegate_param_inner

assert delegate_param_middle.root_source is None
assert delegate_param_middle.source is not None
assert delegate_param_middle.source.source is None
assert delegate_param_outer.root_delegate == delegate_param_inner

assert delegate_param_inner.root_source is None
assert delegate_param_inner.source is None
assert delegate_param_inner.root_delegate == delegate_param_inner


def test_delegate_parameter_context() -> None:
gettable_settable_source_param = Parameter(
"source", set_cmd=None, get_cmd=None, vals=vals.Numbers(-5, 5)
Expand Down
Loading