Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/easyreflectometry/limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import numpy as np
from easyscience.variable import Parameter

# Fixed-range limit definitions
SLD_LIMITS = (-1.0, 10.0)
SCALE_LIMITS = (0.0, 10.0)


def apply_default_limits(parameter: Parameter, kind: str) -> None:
"""Apply default min/max to a parameter if current bounds are infinite.

:param parameter: The parameter to adjust.
:type parameter: Parameter
:param kind: One of 'thickness', 'roughness', 'sld', 'isld', 'scale'.
:type kind: str
"""
if not parameter.independent:
return

if kind in ('thickness', 'roughness'):
_apply_percentage_limits(parameter)
elif kind in ('sld', 'isld'):
_apply_fixed_limits(parameter, *SLD_LIMITS)
elif kind == 'scale':
_apply_fixed_limits(parameter, *SCALE_LIMITS)


def _apply_percentage_limits(parameter: Parameter) -> None:
"""Set min to 50% and max to 200% of the current value, only if current bounds are inf."""
value = parameter.value
if value == 0.0:
return
if np.isinf(parameter.min):
parameter.min = 0.5 * value
if np.isinf(parameter.max):
parameter.max = 2.0 * value


def _apply_fixed_limits(parameter: Parameter, low: float, high: float) -> None:
"""Set fixed min/max, only if current bounds are inf."""
if np.isinf(parameter.min) and low <= parameter.value:
parameter.min = low
if np.isinf(parameter.max) and high >= parameter.value:
parameter.max = high
2 changes: 2 additions & 0 deletions src/easyreflectometry/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from easyscience import global_object
from easyscience.variable import Parameter

from easyreflectometry.limits import apply_default_limits
from easyreflectometry.sample import BaseAssembly
from easyreflectometry.sample import Sample
from easyreflectometry.utils import get_as_parameter
Expand Down Expand Up @@ -86,6 +87,7 @@ def __init__(
resolution_function = PercentageFwhm(DEFAULTS['resolution']['value'])

scale = get_as_parameter('scale', scale, DEFAULTS)
apply_default_limits(scale, 'scale')
background = get_as_parameter('background', background, DEFAULTS)
self.color = color
self._is_default = False
Expand Down
4 changes: 4 additions & 0 deletions src/easyreflectometry/sample/elements/layers/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from easyscience import global_object
from easyscience.variable import Parameter

from easyreflectometry.limits import apply_default_limits
from easyreflectometry.utils import get_as_parameter

from ...base_core import BaseCore
Expand Down Expand Up @@ -71,12 +72,15 @@ def __init__(
default_dict=DEFAULTS,
unique_name_prefix=f'{unique_name}_Thickness',
)
apply_default_limits(thickness, 'thickness')

roughness = get_as_parameter(
name='roughness',
value=roughness,
default_dict=DEFAULTS,
unique_name_prefix=f'{unique_name}_Roughness',
)
apply_default_limits(roughness, 'roughness')

super().__init__(
name=name,
Expand Down
4 changes: 4 additions & 0 deletions src/easyreflectometry/sample/elements/materials/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from easyscience import global_object
from easyscience.variable import Parameter

from easyreflectometry.limits import apply_default_limits
from easyreflectometry.utils import get_as_parameter

from ...base_core import BaseCore
Expand Down Expand Up @@ -62,12 +63,15 @@ def __init__(
default_dict=DEFAULTS,
unique_name_prefix=f'{unique_name}_Sld',
)
apply_default_limits(sld, 'sld')

isld = get_as_parameter(
name='isld',
value=isld,
default_dict=DEFAULTS,
unique_name_prefix=f'{unique_name}_Isld',
)
apply_default_limits(isld, 'isld')

super().__init__(
name=name,
Expand Down
4 changes: 2 additions & 2 deletions tests/model/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_default(self):
assert_equal(str(p.scale.unit), 'dimensionless')
assert_equal(p.scale.value, 1.0)
assert_equal(p.scale.min, 0.0)
assert_equal(p.scale.max, np.inf)
assert_equal(p.scale.max, 10.0)
assert_equal(p.scale.fixed, True)
assert_equal(p.background.display_name, 'background')
assert_equal(str(p.background.unit), 'dimensionless')
Expand Down Expand Up @@ -73,7 +73,7 @@ def test_from_pars(self):
assert_equal(str(mod.scale.unit), 'dimensionless')
assert_equal(mod.scale.value, 2.0)
assert_equal(mod.scale.min, 0.0)
assert_equal(mod.scale.max, np.inf)
assert_equal(mod.scale.max, 10.0)
assert_equal(mod.scale.fixed, True)
assert_equal(mod.background.display_name, 'background')
assert_equal(str(mod.background.unit), 'dimensionless')
Expand Down
13 changes: 6 additions & 7 deletions tests/sample/elements/layers/test_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import unittest

import numpy as np
from easyscience import global_object
from numpy.testing import assert_almost_equal
from numpy.testing import assert_equal
Expand All @@ -29,13 +28,13 @@ def test_no_arguments(self):
assert_equal(str(p.thickness.unit), 'Å')
assert_equal(p.thickness.value, 10.0)
assert_equal(p.thickness.min, 0.0)
assert_equal(p.thickness.max, np.inf)
assert_equal(p.thickness.max, 20.0)
assert_equal(p.thickness.fixed, True)
assert_equal(p.roughness.display_name, 'roughness')
assert_equal(str(p.roughness.unit), 'Å')
assert_equal(p.roughness.value, 3.3)
assert_equal(p.roughness.min, 0.0)
assert_equal(p.roughness.max, np.inf)
assert_equal(p.roughness.max, 6.6)
assert_equal(p.roughness.fixed, True)

def test_shuffled_arguments(self):
Expand All @@ -48,13 +47,13 @@ def test_shuffled_arguments(self):
assert_equal(str(p.thickness.unit), 'Å')
assert_equal(p.thickness.value, 5.0)
assert_equal(p.thickness.min, 0.0)
assert_equal(p.thickness.max, np.inf)
assert_equal(p.thickness.max, 10.0)
assert_equal(p.thickness.fixed, True)
assert_equal(p.roughness.display_name, 'roughness')
assert_equal(str(p.roughness.unit), 'Å')
assert_equal(p.roughness.value, 2.0)
assert_equal(p.roughness.min, 0.0)
assert_equal(p.roughness.max, np.inf)
assert_equal(p.roughness.max, 4.0)
assert_equal(p.roughness.fixed, True)

def test_only_roughness_key(self):
Expand All @@ -63,7 +62,7 @@ def test_only_roughness_key(self):
assert_equal(str(p.roughness.unit), 'Å')
assert_equal(p.roughness.value, 10.0)
assert_equal(p.roughness.min, 0.0)
assert_equal(p.roughness.max, np.inf)
assert_equal(p.roughness.max, 20.0)
assert_equal(p.roughness.fixed, True)

def test_only_roughness_key_paramter(self):
Expand All @@ -79,7 +78,7 @@ def test_only_thickness_key(self):
assert_equal(str(p.thickness.unit), 'Å')
assert_equal(p.thickness.value, 10.0)
assert_equal(p.thickness.min, 0.0)
assert_equal(p.thickness.max, np.inf)
assert_equal(p.thickness.max, 20.0)
assert_equal(p.thickness.fixed, True)

def test_only_thickness_key_paramter(self):
Expand Down
25 changes: 12 additions & 13 deletions tests/sample/elements/materials/test_material.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
__author__ = 'github.com/arm61'
__version__ = '0.0.1'

import numpy as np
from easyscience import global_object

from easyreflectometry.sample.elements.materials.material import DEFAULTS
Expand All @@ -21,14 +20,14 @@ def test_no_arguments(self):
assert p.sld.display_name == 'sld'
assert str(p.sld.unit) == '1/Å^2'
assert p.sld.value == 4.186
assert p.sld.min == -np.inf
assert p.sld.max == np.inf
assert p.sld.min == -1.0
assert p.sld.max == 10.0
assert p.sld.fixed is True
assert p.isld.display_name == 'isld'
assert str(p.isld.unit) == '1/Å^2'
assert p.isld.value == 0.0
assert p.isld.min == -np.inf
assert p.isld.max == np.inf
assert p.isld.min == -1.0
assert p.isld.max == 10.0
assert p.isld.fixed is True

def test_shuffled_arguments(self):
Expand All @@ -38,23 +37,23 @@ def test_shuffled_arguments(self):
assert p.sld.display_name == 'sld'
assert str(p.sld.unit) == '1/Å^2'
assert p.sld.value == 6.908
assert p.sld.min == -np.inf
assert p.sld.max == np.inf
assert p.sld.min == -1.0
assert p.sld.max == 10.0
assert p.sld.fixed is True
assert p.isld.display_name == 'isld'
assert str(p.isld.unit) == '1/Å^2'
assert p.isld.value == -0.278
assert p.isld.min == -np.inf
assert p.isld.max == np.inf
assert p.isld.min == -1.0
assert p.isld.max == 10.0
assert p.isld.fixed is True

def test_only_sld_key(self):
p = Material(sld=10)
assert p.sld.display_name == 'sld'
assert str(p.sld.unit) == '1/Å^2'
assert p.sld.value == 10
assert p.sld.min == -np.inf
assert p.sld.max == np.inf
assert p.sld.min == -1.0
assert p.sld.max == 10.0
assert p.sld.fixed is True

def test_only_sld_key_parameter(self):
Expand All @@ -69,8 +68,8 @@ def test_only_isld_key(self):
assert p.isld.display_name == 'isld'
assert str(p.isld.unit) == '1/Å^2'
assert p.isld.value == 10
assert p.isld.min == -np.inf
assert p.isld.max == np.inf
assert p.isld.min == -1.0
assert p.isld.max == 10.0
assert p.isld.fixed is True

def test_only_isld_key_parameter(self):
Expand Down
133 changes: 133 additions & 0 deletions tests/test_limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import numpy as np
import pytest
from easyscience.variable import Parameter

from easyreflectometry.limits import SCALE_LIMITS
from easyreflectometry.limits import SLD_LIMITS
from easyreflectometry.limits import apply_default_limits


class TestApplyDefaultLimits:
def test_sld_with_inf_bounds(self):
param = Parameter('sld', 4.186, min=-np.inf, max=np.inf)
apply_default_limits(param, 'sld')
assert param.min == SLD_LIMITS[0]
assert param.max == SLD_LIMITS[1]

def test_isld_with_inf_bounds(self):
param = Parameter('isld', 0.0, min=-np.inf, max=np.inf)
apply_default_limits(param, 'isld')
assert param.min == SLD_LIMITS[0]
assert param.max == SLD_LIMITS[1]

def test_sld_preserves_finite_bounds(self):
param = Parameter('sld', 4.0, min=-2.0, max=8.0)
apply_default_limits(param, 'sld')
assert param.min == -2.0
assert param.max == 8.0

def test_scale_with_inf_bounds(self):
param = Parameter('scale', 1.0, min=0, max=np.inf)
apply_default_limits(param, 'scale')
assert param.min == SCALE_LIMITS[0]
assert param.max == SCALE_LIMITS[1]

def test_scale_preserves_finite_bounds(self):
param = Parameter('scale', 1.0, min=0.5, max=2.0)
apply_default_limits(param, 'scale')
assert param.min == 0.5
assert param.max == 2.0

def test_thickness_percentage_limits(self):
param = Parameter('thickness', 10.0, min=0.0, max=np.inf)
apply_default_limits(param, 'thickness')
assert param.min == 0.0 # 0.0 is finite, not overwritten
assert param.max == 20.0 # 2.0 * 10.0

def test_thickness_both_inf(self):
param = Parameter('thickness', 10.0, min=-np.inf, max=np.inf)
apply_default_limits(param, 'thickness')
assert param.min == 5.0 # 0.5 * 10.0
assert param.max == 20.0 # 2.0 * 10.0

def test_roughness_percentage_limits(self):
param = Parameter('roughness', 3.3, min=0.0, max=np.inf)
apply_default_limits(param, 'roughness')
assert param.min == 0.0 # 0.0 is finite, not overwritten
assert param.max == pytest.approx(6.6) # 2.0 * 3.3

def test_thickness_zero_value_unchanged(self):
param = Parameter('thickness', 0.0, min=0.0, max=np.inf)
apply_default_limits(param, 'thickness')
assert param.min == 0.0
assert param.max == np.inf # unchanged, zero-valued skip

def test_roughness_zero_value_unchanged(self):
param = Parameter('roughness', 0.0, min=-np.inf, max=np.inf)
apply_default_limits(param, 'roughness')
assert param.min == -np.inf
assert param.max == np.inf

def test_thickness_preserves_finite_bounds(self):
param = Parameter('thickness', 10.0, min=2.0, max=25.0)
apply_default_limits(param, 'thickness')
assert param.min == 2.0
assert param.max == 25.0

def test_dependent_parameter_skipped(self):
independent_param = Parameter('sld_main', 4.0, min=-np.inf, max=np.inf)
dependent_param = Parameter('sld_dep', 4.0, min=-np.inf, max=np.inf)
dependent_param.make_dependent_on(dependency_expression='a', dependency_map={'a': independent_param})
apply_default_limits(dependent_param, 'sld')
assert np.isinf(dependent_param.min)
assert np.isinf(dependent_param.max)

def test_unknown_kind_is_noop(self):
param = Parameter('foo', 5.0, min=-np.inf, max=np.inf)
apply_default_limits(param, 'unknown')
assert np.isinf(param.min)
assert np.isinf(param.max)


class TestIntegrationWithConstructors:
def test_material_gets_sld_limits(self):
from easyreflectometry.sample.elements.materials.material import Material

mat = Material(sld=6.36, isld=0.0)
assert mat.sld.min == SLD_LIMITS[0]
assert mat.sld.max == SLD_LIMITS[1]
assert mat.isld.min == SLD_LIMITS[0]
assert mat.isld.max == SLD_LIMITS[1]

def test_layer_gets_percentage_limits(self):
from easyreflectometry.sample.elements.layers.layer import Layer

layer = Layer(thickness=20.0, roughness=5.0)
assert layer.thickness.min == 0.0 # 0.0 is finite, kept
assert layer.thickness.max == 40.0 # 2.0 * 20.0
assert layer.roughness.min == 0.0 # 0.0 is finite, kept
assert layer.roughness.max == 10.0 # 2.0 * 5.0

def test_layer_zero_thickness_unchanged(self):
from easyreflectometry.sample.elements.layers.layer import Layer

layer = Layer(thickness=0.0, roughness=0.0)
assert layer.thickness.min == 0.0
assert layer.thickness.max == np.inf
assert layer.roughness.min == 0.0
assert layer.roughness.max == np.inf

def test_model_gets_scale_limits(self):
from easyreflectometry.model.model import Model

model = Model(scale=1.0)
assert model.scale.min == SCALE_LIMITS[0]
assert model.scale.max == SCALE_LIMITS[1]

def test_existing_parameter_bounds_preserved(self):
from easyreflectometry.sample.elements.materials.material import Material

custom_sld = Parameter('sld', 4.0, min=-0.5, max=7.0)
mat = Material(sld=custom_sld)
assert mat.sld.min == -0.5
assert mat.sld.max == 7.0
Loading