Skip to content

Commit

Permalink
Merge pull request #6865 from jenshnielsen/delegate_parameter_validators
Browse files Browse the repository at this point in the history
Add source validators to delegate parameter validators
  • Loading branch information
jenshnielsen authored Feb 7, 2025
2 parents 83d4cd5 + 8154a2d commit eaa354b
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 3 deletions.
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

0 comments on commit eaa354b

Please sign in to comment.