Skip to content

Add source validators to delegate parameter validators #6865

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

Merged
Merged
3 changes: 3 additions & 0 deletions docs/changes/newsfragments/6585.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
``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.
17 changes: 17 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 @@ -314,3 +316,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
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
67 changes: 66 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,65 @@ def test_register_custom_parameter(DAC) -> None:
)


def test_register_delegate_parameters() -> None:
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() -> None:
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"


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

complex_param = Parameter(
"complex_param", get_cmd=None, set_cmd=None, vals=ComplexNumbers()
)
delegate_inner = DelegateParameter("delegate_inner", source=None)
delegate_outer = DelegateParameter("delegate_outer", source=None)

meas = Measurement()

meas.register_parameter(x_param)

delegate_outer.source = delegate_inner
delegate_inner.source = complex_param

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


def test_unregister_parameter(DAC, DMM) -> None:
"""
Test the unregistering of parameters.
Expand Down
114 changes: 114 additions & 0 deletions 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,115 @@ 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


def test_validator_delegates_and_source_chain() -> None:
source_param = Parameter("source", set_cmd=None, get_cmd=None)
delegate_inner = DelegateParameter("delegate_inner", source=source_param)
delegate_outer = DelegateParameter("delegate_outer", source=delegate_inner)
source_validator = vals.Numbers(-10, 10)
delegate_inner_validator = vals.Numbers(-7, 7)
delegate_outer_validator = vals.Numbers(-5, 5)

source_param.vals = source_validator
delegate_inner.vals = delegate_inner_validator
delegate_outer.vals = delegate_outer_validator

delegate_outer.validate(1)
with pytest.raises(ValueError):
delegate_outer.validate(6)

delegate_inner.validate(6)
source_param.validate(6)

assert delegate_outer.validators == (
delegate_outer_validator,
delegate_inner_validator,
source_validator,
)
assert delegate_outer.vals == delegate_outer_validator

assert delegate_inner.validators == (
delegate_inner_validator,
source_validator,
)
assert delegate_inner.vals == delegate_inner_validator

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

assert delegate_outer.validators == (
delegate_outer_validator,
source_validator,
)
assert delegate_outer.vals == delegate_outer_validator

assert isinstance(delegate_outer.source, DelegateParameter)
assert delegate_outer.source.source is not None
delegate_outer.source.source.vals = None

assert delegate_outer.validators == (delegate_outer_validator,)
assert delegate_outer.vals == delegate_outer_validator


def test_value_validation_with_offset_and_scale() -> None:
source_param = Parameter(
Expand Down
Loading