From b82bd785427619c91ce03acb59ae455c73375b5d Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 1 Jun 2026 11:01:50 +0200 Subject: [PATCH 1/4] remove parameters as input --- .../components/damped_harmonic_oscillator.py | 44 ++--- .../sample_model/components/delta_function.py | 28 +-- .../sample_model/components/exponential.py | 54 +++--- .../sample_model/components/gaussian.py | 12 +- .../sample_model/components/lorentzian.py | 38 ++-- .../sample_model/components/mixins.py | 71 ++++---- .../test_damped_harmonic_oscillator.py | 109 +++++------- .../components/test_exponential.py | 110 +++++------- .../sample_model/components/test_gaussian.py | 106 +++++------ .../components/test_lorentzian.py | 100 ++++------- .../sample_model/components/test_mixins.py | 141 ++++++--------- .../sample_model/components/test_voigt.py | 167 ++++++++---------- 12 files changed, 430 insertions(+), 550 deletions(-) diff --git a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py index 86fd6c0b..2990efdc 100644 --- a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py +++ b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py @@ -26,11 +26,11 @@ class DampedHarmonicOscillator(CreateParametersMixin, ModelComponent): def __init__( self, - area: Numeric | Parameter = 1.0, - center: Numeric | Parameter = 1.0, - width: Numeric | Parameter = 1.0, - unit: str | sc.Unit = 'meV', - name: str = 'DampedHarmonicOscillator', + area: Numeric = 1.0, + center: Numeric = 1.0, + width: Numeric = 1.0, + unit: str | sc.Unit = "meV", + name: str = "DampedHarmonicOscillator", display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -39,11 +39,11 @@ def __init__( Parameters ---------- - area : Numeric | Parameter, default=1.0 + area : Numeric , default=1.0 Area under the curve. - center : Numeric | Parameter, default=1.0 + center : Numeric , default=1.0 Resonance frequency, approximately the peak position. - width : Numeric | Parameter, default=1.0 + width : Numeric , default=1.0 Damping constant, approximately the half width at half max (HWHM) of the peaks. By default, 1.0. unit : str | sc.Unit, default='meV' @@ -108,7 +108,7 @@ def area(self, value: Numeric) -> None: If the value is not a number. """ if not isinstance(value, Numeric): - raise TypeError('area must be a number') + raise TypeError("area must be a number") self._area.value = value @property @@ -141,10 +141,10 @@ def center(self, value: Numeric) -> None: If the value is not positive. """ if not isinstance(value, Numeric): - raise TypeError('center must be a number') + raise TypeError("center must be a number") if float(value) <= 0: - raise ValueError('center must be positive') + raise ValueError("center must be positive") self._center.value = value @property @@ -177,14 +177,16 @@ def width(self, value: Numeric) -> None: If the value is not positive. """ if not isinstance(value, Numeric): - raise TypeError('width must be a number') + raise TypeError("width must be a number") if float(value) <= 0: - raise ValueError('width must be positive') + raise ValueError("width must be positive") self._width.value = value - def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: + def evaluate( + self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray + ) -> np.ndarray: r""" Evaluate the Damped Harmonic Oscillator at the given x values. @@ -207,7 +209,9 @@ def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) normalization = 2 * self.center.value**2 * self.width.value / np.pi # No division by zero here, width>0 enforced in setter - denominator = (x**2 - self.center.value**2) ** 2 + (2 * self.width.value * x) ** 2 + denominator = (x**2 - self.center.value**2) ** 2 + ( + 2 * self.width.value * x + ) ** 2 return self.area.value * normalization / (denominator) @@ -221,9 +225,9 @@ def __repr__(self) -> str: A string representation of the Damped Harmonic Oscillator. """ return ( - f'DampedHarmonicOscillator(name = {self.name}, display_name = {self.display_name}, ' - f'unit = {self._unit},\n ' - f' area = {self.area},\n ' - f' center = {self.center},\n ' - f' width = {self.width})' + f"DampedHarmonicOscillator(name = {self.name}, display_name = {self.display_name}, " + f"unit = {self._unit},\n " + f" area = {self.area},\n " + f" center = {self.center},\n " + f" width = {self.width})" ) diff --git a/src/easydynamics/sample_model/components/delta_function.py b/src/easydynamics/sample_model/components/delta_function.py index d63780fa..030299ad 100644 --- a/src/easydynamics/sample_model/components/delta_function.py +++ b/src/easydynamics/sample_model/components/delta_function.py @@ -30,10 +30,10 @@ class DeltaFunction(CreateParametersMixin, ModelComponent): def __init__( self, - center: Numeric | Parameter | None = None, - area: Numeric | Parameter = 1.0, - unit: str | sc.Unit = 'meV', - name: str = 'DeltaFunction', + center: Numeric | None = None, + area: Numeric = 1.0, + unit: str | sc.Unit = "meV", + name: str = "DeltaFunction", display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -42,9 +42,9 @@ def __init__( Parameters ---------- - center : Numeric | Parameter | None, default=None + center : Numeric | None, default=None Center of the delta function. If None, it will be centered at 0 and fixed. - area : Numeric | Parameter, default=1.0 + area : Numeric , default=1.0 Total area under the curve. unit : str | sc.Unit, default='meV' Unit of the parameters. @@ -103,7 +103,7 @@ def area(self, value: Numeric) -> None: """ if not isinstance(value, Numeric): - raise TypeError('area must be a number') + raise TypeError("area must be a number") self._area.value = value @property @@ -139,10 +139,12 @@ def center(self, value: Numeric | None) -> None: value = 0.0 self._center.fixed = True if not isinstance(value, Numeric): - raise TypeError('center must be a number') + raise TypeError("center must be a number") self._center.value = value - def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: + def evaluate( + self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray + ) -> np.ndarray: """ Evaluate the Delta function at the given x values. @@ -200,8 +202,8 @@ def __repr__(self) -> str: """ return ( - f'DeltaFunction(name = {self.name}, display_name = {self.display_name}, ' - f'unit = {self.unit},\n' - f' area = {self.area},\n' - f' center = {self.center})' + f"DeltaFunction(name = {self.name}, display_name = {self.display_name}, " + f"unit = {self.unit},\n" + f" area = {self.area},\n" + f" center = {self.center})" ) diff --git a/src/easydynamics/sample_model/components/exponential.py b/src/easydynamics/sample_model/components/exponential.py index b331b162..9ac7a54a 100644 --- a/src/easydynamics/sample_model/components/exponential.py +++ b/src/easydynamics/sample_model/components/exponential.py @@ -25,11 +25,11 @@ class Exponential(CreateParametersMixin, ModelComponent): def __init__( self, - amplitude: Numeric | Parameter = 1.0, - center: Numeric | Parameter | None = None, - rate: Numeric | Parameter = 1.0, - unit: str | sc.Unit = 'meV', - name: str = 'Exponential', + amplitude: Numeric = 1.0, + center: Numeric | None = None, + rate: Numeric = 1.0, + unit: str | sc.Unit = "meV", + name: str = "Exponential", display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -38,11 +38,11 @@ def __init__( Parameters ---------- - amplitude : Numeric | Parameter, default=1.0 + amplitude : Numeric , default=1.0 Amplitude of the Exponential. - center : Numeric | Parameter | None, default=None + center : Numeric | None, default=None Center of the Exponential. If None, the center is fixed at 0. - rate : Numeric | Parameter, default=1.0 + rate : Numeric , default=1.0 Decay or growth constant of the Exponential. unit : str | sc.Unit, default='meV' Unit of the parameters. @@ -70,26 +70,30 @@ def __init__( ) if not isinstance(amplitude, (Parameter, Numeric)): - raise TypeError('amplitude must be a number or a Parameter.') + raise TypeError("amplitude must be a number or a Parameter.") if isinstance(amplitude, Numeric): if not np.isfinite(amplitude): - raise ValueError('amplitude must be a finite number or a Parameter') + raise ValueError("amplitude must be a finite number or a Parameter") - amplitude = Parameter(name=name + ' amplitude', value=float(amplitude), unit=unit) + amplitude = Parameter( + name=name + " amplitude", value=float(amplitude), unit=unit + ) center = self._create_center_parameter( center=center, name=name, fix_if_none=True, unit=self._unit ) if not isinstance(rate, (Parameter, Numeric)): - raise TypeError('rate must be a number or a Parameter.') + raise TypeError("rate must be a number or a Parameter.") if isinstance(rate, Numeric): if not np.isfinite(rate): - raise ValueError('rate must be a finite number or a Parameter') + raise ValueError("rate must be a finite number or a Parameter") - rate = Parameter(name=name + ' rate', value=float(rate), unit='1/' + str(unit)) + rate = Parameter( + name=name + " rate", value=float(rate), unit="1/" + str(unit) + ) self._amplitude = amplitude self._center = center @@ -125,7 +129,7 @@ def amplitude(self, value: Numeric) -> None: """ if not isinstance(value, Numeric): - raise TypeError('amplitude must be a number') + raise TypeError("amplitude must be a number") self._amplitude.value = value @property @@ -162,7 +166,7 @@ def center(self, value: Numeric | None) -> None: self._center.fixed = True if not isinstance(value, Numeric): - raise TypeError('center must be a number') + raise TypeError("center must be a number") self._center.value = value @property @@ -193,7 +197,7 @@ def rate(self, value: Numeric) -> None: If the value is not a number. """ if not isinstance(value, Numeric): - raise TypeError('rate must be a number') + raise TypeError("rate must be a number") self._rate.value = value @@ -243,21 +247,21 @@ def convert_unit(self, unit: str | sc.Unit) -> None: """ if not isinstance(unit, (str, sc.Unit)): - raise TypeError('unit must be a string or sc.Unit') + raise TypeError("unit must be a string or sc.Unit") old_unit = self._unit pars = [self.amplitude, self.center] try: for p in pars: p.convert_unit(unit) - self.rate.convert_unit('1/' + str(unit)) + self.rate.convert_unit("1/" + str(unit)) self._unit = unit except Exception as e: # Attempt to rollback on failure try: for p in pars: p.convert_unit(old_unit) - self.rate.convert_unit('1/' + str(old_unit)) + self.rate.convert_unit("1/" + str(old_unit)) except Exception: # noqa: S110 pass # Best effort rollback raise e @@ -273,9 +277,9 @@ def __repr__(self) -> str: """ return ( - f'Exponential(name = {self.name}, display_name = {self.display_name}, ' - f'unit = {self._unit},\n ' - f' amplitude = {self.amplitude},\n ' - f' center = {self.center},\n ' - f' rate = {self.rate})' + f"Exponential(name = {self.name}, display_name = {self.display_name}, " + f"unit = {self._unit},\n " + f" amplitude = {self.amplitude},\n " + f" center = {self.center},\n " + f" rate = {self.rate})" ) diff --git a/src/easydynamics/sample_model/components/gaussian.py b/src/easydynamics/sample_model/components/gaussian.py index 54cfe97c..558cfd8d 100644 --- a/src/easydynamics/sample_model/components/gaussian.py +++ b/src/easydynamics/sample_model/components/gaussian.py @@ -33,9 +33,9 @@ class Gaussian(CreateParametersMixin, ModelComponent): def __init__( self, - area: Numeric | Parameter = 1.0, - center: Numeric | Parameter | None = None, - width: Numeric | Parameter = 1.0, + area: Numeric = 1.0, + center: Numeric | None = None, + width: Numeric = 1.0, unit: str | sc.Unit = 'meV', name: str = 'Gaussian', display_name: str | None = None, @@ -46,11 +46,11 @@ def __init__( Parameters ---------- - area : Numeric | Parameter, default=1.0 + area : Numeric , default=1.0 Area of the Gaussian. - center : Numeric | Parameter | None, default=None + center : Numeric | None, default=None Center of the Gaussian. If None. - width : Numeric | Parameter, default=1.0 + width : Numeric , default=1.0 Standard deviation. unit : str | sc.Unit, default='meV' Unit of the parameters. diff --git a/src/easydynamics/sample_model/components/lorentzian.py b/src/easydynamics/sample_model/components/lorentzian.py index 81453fb8..6fc8ebc7 100644 --- a/src/easydynamics/sample_model/components/lorentzian.py +++ b/src/easydynamics/sample_model/components/lorentzian.py @@ -30,11 +30,11 @@ class Lorentzian(CreateParametersMixin, ModelComponent): def __init__( self, - area: Numeric | Parameter = 1.0, - center: Numeric | Parameter | None = None, - width: Numeric | Parameter = 1.0, - unit: str | sc.Unit = 'meV', - name: str = 'Lorentzian', + area: Numeric = 1.0, + center: Numeric | None = None, + width: Numeric = 1.0, + unit: str | sc.Unit = "meV", + name: str = "Lorentzian", display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -43,11 +43,11 @@ def __init__( Parameters ---------- - area : Numeric | Parameter, default=1.0 + area : Numeric , default=1.0 Area of the Lorentzian. - center : Numeric | Parameter | None, default=None + center : Numeric | None, default=None Center of the Lorentzian. If None, defaults to 0 and is fixed. - width : Numeric | Parameter, default=1.0 + width : Numeric , default=1.0 Half width at half maximum (HWHM). unit : str | sc.Unit, default='meV' Unit of the parameters. @@ -106,7 +106,7 @@ def area(self, value: Numeric) -> None: If the value is not a number. """ if not isinstance(value, Numeric): - raise TypeError('area must be a number') + raise TypeError("area must be a number") self._area.value = value @property @@ -141,7 +141,7 @@ def center(self, value: Numeric | None) -> None: value = 0.0 self._center.fixed = True if not isinstance(value, Numeric): - raise TypeError('center must be a number') + raise TypeError("center must be a number") self._center.value = value @property @@ -174,13 +174,15 @@ def width(self, value: Numeric) -> None: If the value is not positive. """ if not isinstance(value, Numeric): - raise TypeError('width must be a number') + raise TypeError("width must be a number") if float(value) <= 0: - raise ValueError('width must be positive') + raise ValueError("width must be positive") self._width.value = value - def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: + def evaluate( + self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray + ) -> np.ndarray: r""" Evaluate the Lorentzian at the given x values. @@ -220,9 +222,9 @@ def __repr__(self) -> str: A string representation of the Lorentzian. """ return ( - f'Lorentzian(name = {self.name}, display_name = {self.display_name}, ' - f'unit = {self._unit},\n' - f' area = {self.area},\n' - f' center = {self.center},\n' - f' width = {self.width})' + f"Lorentzian(name = {self.name}, display_name = {self.display_name}, " + f"unit = {self._unit},\n" + f" area = {self.area},\n" + f" center = {self.center},\n" + f" width = {self.width})" ) diff --git a/src/easydynamics/sample_model/components/mixins.py b/src/easydynamics/sample_model/components/mixins.py index 0d760865..ef82f3a0 100644 --- a/src/easydynamics/sample_model/components/mixins.py +++ b/src/easydynamics/sample_model/components/mixins.py @@ -24,9 +24,9 @@ class CreateParametersMixin: def _create_area_parameter( self, - area: Numeric | Parameter, + area: Numeric, name: str, - unit: str | sc.Unit = 'meV', + unit: str | sc.Unit = "meV", minimum_area: float = MINIMUM_AREA, ) -> Parameter: """ @@ -37,8 +37,8 @@ def _create_area_parameter( Parameters ---------- - area : Numeric | Parameter - The area value or Parameter. + area : Numeric + The area value. name : str The name of the model component. unit : str | sc.Unit, default='meV' @@ -49,27 +49,26 @@ def _create_area_parameter( Raises ------ TypeError - If area is not a number or a Parameter. + If area is not a number. ValueError - If area is not a finite number or if the area Parameter has a non-finite value. + If area is not a finite number. Returns ------- Parameter The validated area Parameter. """ - if not isinstance(area, (Parameter, Numeric)): - raise TypeError('area must be a number or a Parameter.') + if not isinstance(area, Numeric): + raise TypeError("area must be a number.") if isinstance(area, Numeric): if not np.isfinite(area): - raise ValueError('area must be a finite number or a Parameter') - - area = Parameter(name=name + ' area', value=float(area), unit=unit) + raise ValueError("area must be a finite number.") + area = Parameter(name=name + " area", value=float(area), unit=unit) if area.value < 0: warnings.warn( - f'The area of {name} is negative, which may not be physically meaningful.', + f"The area of {name} is negative, which may not be physically meaningful.", UserWarning, stacklevel=3, ) @@ -81,10 +80,10 @@ def _create_area_parameter( def _create_center_parameter( self, - center: Numeric | Parameter | None, + center: Numeric | None, name: str, fix_if_none: bool, - unit: str | sc.Unit = 'meV', + unit: str | sc.Unit = "meV", enforce_minimum_center: bool = False, ) -> Parameter: """ @@ -92,8 +91,8 @@ def _create_center_parameter( Parameters ---------- - center : Numeric | Parameter | None - The center value or Parameter. + center : Numeric | None + The center value. name : str The name of the model component. fix_if_none : bool @@ -106,41 +105,41 @@ def _create_center_parameter( Raises ------ TypeError - If center is not None, a number, or a Parameter. + If center is not None or a number. ValueError - If center is a number but not finite, or if center is a Parameter but has a non-finite - value. + If center is a number but not finite. + Returns ------- Parameter The validated center Parameter. """ - if center is not None and not isinstance(center, (Numeric, Parameter)): - raise TypeError('center must be None, a number, or a Parameter.') + if center is not None and not isinstance(center, Numeric): + raise TypeError("center must be None or a number.") if center is None: center = Parameter( - name=name + ' center', + name=name + " center", value=0.0, unit=unit, fixed=fix_if_none, ) elif isinstance(center, Numeric): if not np.isfinite(center): - raise ValueError('center must be None, a finite number or a Parameter') + raise ValueError("center must be None or a finite number.") - center = Parameter(name=name + ' center', value=float(center), unit=unit) + center = Parameter(name=name + " center", value=float(center), unit=unit) if enforce_minimum_center and center.min < DHO_MINIMUM_CENTER: center.min = DHO_MINIMUM_CENTER return center def _create_width_parameter( self, - width: Numeric | Parameter, + width: Numeric, name: str, - param_name: str = 'width', - unit: str | sc.Unit = 'meV', + param_name: str = "width", + unit: str | sc.Unit = "meV", minimum_width: float = MINIMUM_WIDTH, ) -> Parameter: """ @@ -148,8 +147,8 @@ def _create_width_parameter( Parameters ---------- - width : Numeric | Parameter - The width value or Parameter. + width : Numeric + The width value. name : str The name of the model component. param_name : str, default='width' @@ -162,7 +161,7 @@ def _create_width_parameter( Raises ------ TypeError - If width is not a number or a Parameter. + If width is not a number. ValueError If width is non-positive. @@ -171,19 +170,19 @@ def _create_width_parameter( Parameter The validated width Parameter. """ - if not isinstance(width, (Numeric, Parameter)): - raise TypeError(f'{param_name} must be a number or a Parameter.') + if not isinstance(width, Numeric): + raise TypeError(f"{param_name} must be a number.") if isinstance(width, Numeric): if not np.isfinite(width): - raise ValueError(f'{param_name} must be a finite number or a Parameter') + raise ValueError(f"{param_name} must be a finite number") if float(width) < minimum_width: raise ValueError( - f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' + f"The {param_name} of a {self.__class__.__name__} must be greater than zero." ) width = Parameter( - name=name + ' ' + param_name, + name=name + " " + param_name, value=float(width), unit=unit, min=minimum_width, @@ -191,7 +190,7 @@ def _create_width_parameter( else: if width.value <= 0: raise ValueError( - f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' + f"The {param_name} of a {self.__class__.__name__} must be greater than zero." ) if width.min < minimum_width: width.min = minimum_width diff --git a/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py b/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py index a420fa40..8139cb03 100644 --- a/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py +++ b/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py @@ -15,12 +15,12 @@ class TestDampedHarmonicOscillator: @pytest.fixture def dho(self): return DampedHarmonicOscillator( - name='TestDHOName', - display_name='TestDHO', + name="TestDHOName", + display_name="TestDHO", area=2.0, center=1.5, width=0.3, - unit='meV', + unit="meV", ) def test_init_no_inputs(self): @@ -28,98 +28,76 @@ def test_init_no_inputs(self): dho = DampedHarmonicOscillator() # EXPECT - assert dho.display_name == 'DampedHarmonicOscillator' + assert dho.display_name == "DampedHarmonicOscillator" assert dho.area.value == pytest.approx(1.0) assert dho.center.value == pytest.approx(1.0) assert dho.width.value == pytest.approx(1.0) - assert dho.unit == 'meV' + assert dho.unit == "meV" def test_initialization(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT - assert dho.display_name == 'TestDHO' + assert dho.display_name == "TestDHO" assert dho.area.value == pytest.approx(2.0) assert dho.center.value == pytest.approx(1.5) assert dho.width.value == pytest.approx(0.3) - assert dho.unit == 'meV' - - def test_init_with_parameters(self): - # WHEN - area_param = Parameter(name='area_param', value=3.0, unit='meV') - center_param = Parameter(name='center_param', value=1.0, unit='meV') - width_param = Parameter(name='width_param', value=0.8, unit='meV') - - # THEN - dho = DampedHarmonicOscillator( - display_name='Paramdho', - area=area_param, - center=center_param, - width=width_param, - unit='meV', - ) - - # EXPECT - assert dho.display_name == 'Paramdho' - assert dho.area is area_param - assert dho.center is center_param - assert dho.width is width_param - assert dho.unit == 'meV' + assert dho.unit == "meV" @pytest.mark.parametrize( - 'kwargs, expected_message', + "kwargs, expected_message", [ ( - {'area': 'invalid', 'center': 0.5, 'width': 0.6, 'unit': 'meV'}, - 'area must be a number', + {"area": "invalid", "center": 0.5, "width": 0.6, "unit": "meV"}, + "area must be a number", ), ( - {'area': 2.0, 'center': 'invalid', 'width': 0.6, 'unit': 'meV'}, - 'center must be ', + {"area": 2.0, "center": "invalid", "width": 0.6, "unit": "meV"}, + "center must be ", ), ( - {'area': 2.0, 'center': 0.5, 'width': 'invalid', 'unit': 'meV'}, - 'width must be a number', + {"area": 2.0, "center": 0.5, "width": "invalid", "unit": "meV"}, + "width must be a number", ), ( - {'area': 2.0, 'center': 0.5, 'width': 0.6, 'unit': 123}, - 'unit must be None', + {"area": 2.0, "center": 0.5, "width": 0.6, "unit": 123}, + "unit must be None", ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - DampedHarmonicOscillator(display_name='DampedHarmonicOscillator', **kwargs) + DampedHarmonicOscillator(display_name="DampedHarmonicOscillator", **kwargs) def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( ValueError, - match=r'The width of a DampedHarmonicOscillator must be greater than zero.', + match=r"The width of a DampedHarmonicOscillator must be greater than zero.", ): DampedHarmonicOscillator( - display_name='TestDampedHarmonicOscillator', + display_name="TestDampedHarmonicOscillator", area=2.0, center=0.5, width=-0.6, - unit='meV', + unit="meV", ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match='may not be physically meaningful'): + with pytest.warns(UserWarning, match="may not be physically meaningful"): DampedHarmonicOscillator( - display_name='TestDampedHarmonicOscillator', + display_name="TestDampedHarmonicOscillator", area=-2.0, center=0.5, width=0.6, - unit='meV', + unit="meV", ) @pytest.mark.parametrize( - 'prop, valid_value, invalid_value, invalid_message', + "prop, valid_value, invalid_value, invalid_message", [ - ('area', 3.0, 'invalid', r'must be a number'), - ('center', 0.6, 'invalid', r'must be a number'), - ('width', 0.7, 'invalid', r'must be a number'), + ("area", 3.0, "invalid", r"must be a number"), + ("center", 0.6, "invalid", r"must be a number"), + ("width", 0.7, "invalid", r"must be a number"), ], ) def test_property_setters( @@ -140,12 +118,12 @@ def test_property_setters( def test_center_setter_negative_raises(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='center must be positive'): + with pytest.raises(ValueError, match="center must be positive"): dho.center = -1.0 def test_width_must_be_positive(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='width must be positive'): + with pytest.raises(ValueError, match="width must be positive"): dho.width = -0.5 def test_evaluate(self, dho: DampedHarmonicOscillator): @@ -157,7 +135,12 @@ def test_evaluate(self, dho: DampedHarmonicOscillator): # EXPECT expected_result = ( - 2 * 2.0 * (1.5**2) * (0.3) / np.pi / ((x**2 - 1.5**2) ** 2 + (2 * 0.3 * x) ** 2) + 2 + * 2.0 + * (1.5**2) + * (0.3) + / np.pi + / ((x**2 - 1.5**2) ** 2 + (2 * 0.3 * x) ** 2) ) np.testing.assert_allclose(result, expected_result, rtol=1e-5) @@ -169,9 +152,9 @@ def test_get_all_parameters(self, dho: DampedHarmonicOscillator): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'TestDHOName area', - 'TestDHOName center', - 'TestDHOName width', + "TestDHOName area", + "TestDHOName center", + "TestDHOName width", } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -191,10 +174,10 @@ def test_area_matches_parameter(self, dho: DampedHarmonicOscillator): def test_convert_unit(self, dho: DampedHarmonicOscillator): # WHEN THEN - dho.convert_unit('microeV') + dho.convert_unit("microeV") # EXPECT - assert dho.unit == 'microeV' + assert dho.unit == "microeV" assert dho.area.value == pytest.approx(2 * 1e3) assert dho.center.value == pytest.approx(1.5 * 1e3) assert dho.width.value == pytest.approx(0.3 * 1e3) @@ -223,9 +206,9 @@ def test_repr(self, dho: DampedHarmonicOscillator): repr_str = repr(dho) # EXPECT - assert 'DampedHarmonicOscillator' in repr_str - assert 'name = TestDHOName' in repr_str - assert 'unit = meV' in repr_str - assert 'area =' in repr_str - assert 'center =' in repr_str - assert 'width =' in repr_str + assert "DampedHarmonicOscillator" in repr_str + assert "name = TestDHOName" in repr_str + assert "unit = meV" in repr_str + assert "area =" in repr_str + assert "center =" in repr_str + assert "width =" in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_exponential.py b/tests/unit/easydynamics/sample_model/components/test_exponential.py index 97df632e..7eb7b665 100644 --- a/tests/unit/easydynamics/sample_model/components/test_exponential.py +++ b/tests/unit/easydynamics/sample_model/components/test_exponential.py @@ -15,12 +15,12 @@ class TestExponential: @pytest.fixture def exponential(self): return Exponential( - name='ExponentialName', - display_name='TestExponential', + name="ExponentialName", + display_name="TestExponential", amplitude=2.0, center=0.5, rate=1.2, - unit='meV', + unit="meV", ) def test_init_no_inputs(self): @@ -28,86 +28,64 @@ def test_init_no_inputs(self): exponential = Exponential() # THEN EXPECT - assert exponential.display_name == 'Exponential' + assert exponential.display_name == "Exponential" assert exponential.amplitude.value == pytest.approx(1.0) assert exponential.center.value == pytest.approx(0.0) assert exponential.rate.value == pytest.approx(1.0) - assert exponential.unit == 'meV' + assert exponential.unit == "meV" def test_initialization(self, exponential: Exponential): # WHEN THEN EXPECT - assert exponential.display_name == 'TestExponential' + assert exponential.display_name == "TestExponential" assert exponential.amplitude.value == pytest.approx(2.0) assert exponential.center.value == pytest.approx(0.5) assert exponential.rate.value == pytest.approx(1.2) - assert exponential.unit == 'meV' - - def test_init_with_parameters(self): - # WHEN - amplitude_param = Parameter(name='amp_param', value=3.0, unit='meV') - center_param = Parameter(name='center_param', value=1.0, unit='meV') - rate_param = Parameter(name='rate_param', value=0.5, unit='1/meV') - - # THEN - exponential = Exponential( - display_name='ParamExponential', - amplitude=amplitude_param, - center=center_param, - rate=rate_param, - unit='meV', - ) - - # EXPECT - assert exponential.display_name == 'ParamExponential' - assert exponential.amplitude is amplitude_param - assert exponential.center is center_param - assert exponential.rate is rate_param - assert exponential.unit == 'meV' + assert exponential.unit == "meV" @pytest.mark.parametrize( - 'kwargs, expected_message', + "kwargs, expected_message", [ ( - {'amplitude': 'invalid', 'center': 0.5, 'rate': 1.0, 'unit': 'meV'}, - 'amplitude must be a number', + {"amplitude": "invalid", "center": 0.5, "rate": 1.0, "unit": "meV"}, + "amplitude must be a number", ), ( - {'amplitude': 2.0, 'center': 'invalid', 'rate': 1.0, 'unit': 'meV'}, - 'center must be None, a number', + {"amplitude": 2.0, "center": "invalid", "rate": 1.0, "unit": "meV"}, + "center must be None or a number", ), ( - {'amplitude': 2.0, 'center': 0.5, 'rate': 'invalid', 'unit': 'meV'}, - 'rate must be a number or a Parameter', + {"amplitude": 2.0, "center": 0.5, "rate": "invalid", "unit": "meV"}, + "rate must be a number", ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Exponential(display_name='TestExponential', **kwargs) + Exponential(display_name="TestExponential", **kwargs) @pytest.mark.parametrize( - 'kwargs, expected_message', + "kwargs, expected_message", [ ( - {'amplitude': np.nan, 'center': 0.5, 'rate': 1.0, 'unit': 'meV'}, - 'amplitude must be a finite number or a Parameter', + {"amplitude": np.nan, "center": 0.5, "rate": 1.0, "unit": "meV"}, + "amplitude must be a finite number or a Parameter", ), ( - {'amplitude': 2.0, 'center': 0.5, 'rate': np.nan, 'unit': 'meV'}, - 'rate must be a finite number or a Parameter', + {"amplitude": 2.0, "center": 0.5, "rate": np.nan, "unit": "meV"}, + "rate must be a finite number or a Parameter", ), ], ) def test_input_value_validation_raises(self, kwargs, expected_message): with pytest.raises(ValueError, match=expected_message): - Exponential(display_name='TestExponential', **kwargs) + Exponential(display_name="TestExponential", **kwargs) @pytest.mark.parametrize( - 'prop, valid_value, invalid_value, invalid_message', + "prop, valid_value, invalid_value, invalid_message", [ - ('amplitude', 3.0, 'invalid', r'must be a number'), - ('center', 0.7, 'invalid', r'must be a number'), - ('rate', 1.5, 'invalid', r'must be a number'), + ("amplitude", 3.0, "invalid", r"must be a number"), + ("center", 0.7, "invalid", r"must be a number"), + ("rate", 1.5, "invalid", r"must be a number"), ], ) def test_property_setters( @@ -157,9 +135,9 @@ def test_get_all_parameters(self, exponential: Exponential): assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'ExponentialName amplitude', - 'ExponentialName center', - 'ExponentialName rate', + "ExponentialName amplitude", + "ExponentialName center", + "ExponentialName rate", } actual_names = {param.name for param in params} @@ -168,39 +146,39 @@ def test_get_all_parameters(self, exponential: Exponential): def test_convert_unit(self, exponential: Exponential): # WHEN - exponential.convert_unit('microeV') + exponential.convert_unit("microeV") # THEN EXPECT - assert exponential.unit == 'microeV' + assert exponential.unit == "microeV" assert exponential.amplitude.value == pytest.approx(2.0 * 1e3) assert exponential.center.value == pytest.approx(0.5 * 1e3) # rate should scale inversely assert exponential.rate.value == pytest.approx(1.2 / 1e3) - assert str(exponential.rate.unit) == '1/ueV' + assert str(exponential.rate.unit) == "1/ueV" def test_convert_unit_incorrect_unit_raises(self, exponential: Exponential): # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r'unit must be a string or sc.Unit'): + with pytest.raises(TypeError, match=r"unit must be a string or sc.Unit"): exponential.convert_unit(123) def test_convert_unit_rollback(self, exponential: Exponential): # WHEN with pytest.raises( UnitError, - match=r'Failed to convert unit: Conversion from `meV` to `m` is not valid.', + match=r"Failed to convert unit: Conversion from `meV` to `m` is not valid.", ): - exponential.convert_unit('m') + exponential.convert_unit("m") # THEN EXPECT - values should be unchanged - assert exponential.unit == 'meV' + assert exponential.unit == "meV" assert exponential.amplitude.value == pytest.approx(2.0) - assert exponential.amplitude.unit == 'meV' + assert exponential.amplitude.unit == "meV" assert exponential.center.value == pytest.approx(0.5) - assert exponential.center.unit == 'meV' + assert exponential.center.unit == "meV" assert exponential.rate.value == pytest.approx(1.2) - assert exponential.rate.unit == '1/meV' + assert exponential.rate.unit == "1/meV" def test_copy(self, exponential: Exponential): # WHEN @@ -226,9 +204,9 @@ def test_repr(self, exponential: Exponential): repr_str = repr(exponential) # THEN EXPECT - assert 'Exponential' in repr_str - assert 'name = ExponentialName' in repr_str - assert 'unit = meV' in repr_str - assert 'amplitude =' in repr_str - assert 'center =' in repr_str - assert 'rate =' in repr_str + assert "Exponential" in repr_str + assert "name = ExponentialName" in repr_str + assert "unit = meV" in repr_str + assert "amplitude =" in repr_str + assert "center =" in repr_str + assert "rate =" in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_gaussian.py b/tests/unit/easydynamics/sample_model/components/test_gaussian.py index 9bf0b13d..91b895ae 100644 --- a/tests/unit/easydynamics/sample_model/components/test_gaussian.py +++ b/tests/unit/easydynamics/sample_model/components/test_gaussian.py @@ -15,12 +15,12 @@ class TestGaussian: @pytest.fixture def gaussian(self): return Gaussian( - name='GaussianName', - display_name='TestGaussian', + name="GaussianName", + display_name="TestGaussian", area=2.0, center=0.5, width=0.6, - unit='meV', + unit="meV", ) def test_init_no_inputs(self): @@ -28,98 +28,82 @@ def test_init_no_inputs(self): gaussian = Gaussian() # EXPECT - assert gaussian.display_name == 'Gaussian' + assert gaussian.display_name == "Gaussian" assert gaussian.area.value == pytest.approx(1.0) assert gaussian.center.value == pytest.approx(0.0) assert gaussian.width.value == pytest.approx(1.0) - assert gaussian.unit == 'meV' + assert gaussian.unit == "meV" assert gaussian.center.fixed is True def test_initialization(self, gaussian: Gaussian): # WHEN THEN EXPECT - assert gaussian.display_name == 'TestGaussian' + assert gaussian.display_name == "TestGaussian" assert gaussian.area.value == pytest.approx(2.0) assert gaussian.center.value == pytest.approx(0.5) assert gaussian.width.value == pytest.approx(0.6) - assert gaussian.unit == 'meV' - - def test_init_with_parameters(self): - # WHEN - area_param = Parameter(name='area_param', value=3.0, unit='meV') - center_param = Parameter(name='center_param', value=1.0, unit='meV') - width_param = Parameter(name='width_param', value=0.8, unit='meV') - - # THEN - gaussian = Gaussian( - display_name='ParamGaussian', - area=area_param, - center=center_param, - width=width_param, - unit='meV', - ) - - # EXPECT - assert gaussian.display_name == 'ParamGaussian' - assert gaussian.area is area_param - assert gaussian.center is center_param - assert gaussian.width is width_param - assert gaussian.unit == 'meV' + assert gaussian.unit == "meV" @pytest.mark.parametrize( - 'kwargs, expected_message', + "kwargs, expected_message", [ ( - {'area': 'invalid', 'center': 0.5, 'width': 0.6, 'unit': 'meV'}, - 'area must be a number', + {"area": "invalid", "center": 0.5, "width": 0.6, "unit": "meV"}, + "area must be a number", ), ( - {'area': 2.0, 'center': 'invalid', 'width': 0.6, 'unit': 'meV'}, - 'center must be None, a number', + {"area": 2.0, "center": "invalid", "width": 0.6, "unit": "meV"}, + "center must be None or a number", ), ( - {'area': 2.0, 'center': 0.5, 'width': 'invalid', 'unit': 'meV'}, - 'width must be a number', + {"area": 2.0, "center": 0.5, "width": "invalid", "unit": "meV"}, + "width must be a number", ), ( - {'area': 2.0, 'center': 0.5, 'width': 0.6, 'unit': 123}, - 'unit must be None', + {"area": 2.0, "center": 0.5, "width": 0.6, "unit": 123}, + "unit must be None", ), ], + ids=[ + "invalid area", + "invalid center", + "invalid width", + "invalid unit", + ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Gaussian(display_name='TestGaussian', **kwargs) + Gaussian(display_name="TestGaussian", **kwargs) def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match=r'The width of a Gaussian must be greater than zero.' + ValueError, match=r"The width of a Gaussian must be greater than zero." ): Gaussian( - display_name='TestGaussian', + display_name="TestGaussian", area=2.0, center=0.5, width=-0.6, - unit='meV', + unit="meV", ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match='may not be physically meaningful'): + with pytest.warns(UserWarning, match="may not be physically meaningful"): Gaussian( - display_name='TestGaussian', + display_name="TestGaussian", area=-2.0, center=0.5, width=0.6, - unit='meV', + unit="meV", ) @pytest.mark.parametrize( - 'prop, valid_value, invalid_value, invalid_message', + "prop, valid_value, invalid_value, invalid_message", [ - ('area', 3.0, 'invalid', r'must be a number'), - ('center', 0.6, 'invalid', r'must be a number'), - ('width', 0.7, 'invalid', r'must be a number'), + ("area", 3.0, "invalid", r"must be a number"), + ("center", 0.6, "invalid", r"must be a number"), + ("width", 0.7, "invalid", r"must be a number"), ], ) def test_property_setters( @@ -135,7 +119,7 @@ def test_property_setters( def test_width_must_be_positive(self, gaussian: Gaussian): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='width must be positive'): + with pytest.raises(ValueError, match="width must be positive"): gaussian.width = -0.5 def test_evaluate(self, gaussian: Gaussian): @@ -171,9 +155,9 @@ def test_get_all_parameters(self, gaussian: Gaussian): assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'GaussianName area', - 'GaussianName center', - 'GaussianName width', + "GaussianName area", + "GaussianName center", + "GaussianName width", } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -195,10 +179,10 @@ def test_area_matches_parameter(self, gaussian: Gaussian): def test_convert_unit(self, gaussian: Gaussian): # WHEN THEN - gaussian.convert_unit('microeV') + gaussian.convert_unit("microeV") # EXPECT - assert gaussian.unit == 'microeV' + assert gaussian.unit == "microeV" assert gaussian.area.value == pytest.approx(2 * 1e3) assert gaussian.center.value == pytest.approx(0.5 * 1e3) assert gaussian.width.value == pytest.approx(0.6 * 1e3) @@ -238,9 +222,9 @@ def test_repr(self, gaussian: Gaussian): # WHEN THEN repr_str = repr(gaussian) # EXPECT - assert 'Gaussian' in repr_str - assert 'name = GaussianName' in repr_str - assert 'unit = meV' in repr_str - assert 'area =' in repr_str - assert 'center =' in repr_str - assert 'width =' in repr_str + assert "Gaussian" in repr_str + assert "name = GaussianName" in repr_str + assert "unit = meV" in repr_str + assert "area =" in repr_str + assert "center =" in repr_str + assert "width =" in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py index 3c7809c6..2444368b 100644 --- a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py +++ b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py @@ -15,12 +15,12 @@ class TestLorentzian: @pytest.fixture def lorentzian(self): return Lorentzian( - name='LorentzianName', - display_name='TestLorentzian', + name="LorentzianName", + display_name="TestLorentzian", area=2.0, center=0.5, width=0.6, - unit='meV', + unit="meV", ) def test_init_no_inputs(self): @@ -28,98 +28,76 @@ def test_init_no_inputs(self): lorentzian = Lorentzian() # EXPECT - assert lorentzian.display_name == 'Lorentzian' + assert lorentzian.display_name == "Lorentzian" assert lorentzian.area.value == pytest.approx(1.0) assert lorentzian.center.value == pytest.approx(0.0) assert lorentzian.width.value == pytest.approx(1.0) - assert lorentzian.unit == 'meV' + assert lorentzian.unit == "meV" assert lorentzian.center.fixed is True def test_initialization(self, lorentzian: Lorentzian): # WHEN THEN EXPECT - assert lorentzian.display_name == 'TestLorentzian' + assert lorentzian.display_name == "TestLorentzian" assert lorentzian.area.value == pytest.approx(2.0) assert lorentzian.center.value == pytest.approx(0.5) assert lorentzian.width.value == pytest.approx(0.6) - assert lorentzian.unit == 'meV' - - def test_init_with_parameters(self): - # WHEN - area_param = Parameter(name='area_param', value=3.0, unit='meV') - center_param = Parameter(name='center_param', value=1.0, unit='meV') - width_param = Parameter(name='width_param', value=0.8, unit='meV') - - # THEN - lorentzian = Lorentzian( - display_name='ParamLorentzian', - area=area_param, - center=center_param, - width=width_param, - unit='meV', - ) - - # EXPECT - assert lorentzian.display_name == 'ParamLorentzian' - assert lorentzian.area is area_param - assert lorentzian.center is center_param - assert lorentzian.width is width_param - assert lorentzian.unit == 'meV' + assert lorentzian.unit == "meV" @pytest.mark.parametrize( - 'kwargs, expected_message', + "kwargs, expected_message", [ ( - {'area': 'invalid', 'center': 0.5, 'width': 0.6, 'unit': 'meV'}, - 'area must be a number', + {"area": "invalid", "center": 0.5, "width": 0.6, "unit": "meV"}, + "area must be a number", ), ( - {'area': 2.0, 'center': 'invalid', 'width': 0.6, 'unit': 'meV'}, - 'center must be None', + {"area": 2.0, "center": "invalid", "width": 0.6, "unit": "meV"}, + "center must be None", ), ( - {'area': 2.0, 'center': 0.5, 'width': 'invalid', 'unit': 'meV'}, - 'width must be a number', + {"area": 2.0, "center": 0.5, "width": "invalid", "unit": "meV"}, + "width must be a number", ), ( - {'area': 2.0, 'center': 0.5, 'width': 0.6, 'unit': 123}, - 'unit must be None', + {"area": 2.0, "center": 0.5, "width": 0.6, "unit": 123}, + "unit must be None", ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Lorentzian(display_name='TestLorentzian', **kwargs) + Lorentzian(display_name="TestLorentzian", **kwargs) def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match=r'The width of a Lorentzian must be greater than zero.' + ValueError, match=r"The width of a Lorentzian must be greater than zero." ): Lorentzian( - display_name='TestLorentzian', + display_name="TestLorentzian", area=2.0, center=0.5, width=-0.6, - unit='meV', + unit="meV", ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match='may not be physically meaningful'): + with pytest.warns(UserWarning, match="may not be physically meaningful"): Lorentzian( - display_name='TestLorentzian', + display_name="TestLorentzian", area=-2.0, center=0.5, width=0.6, - unit='meV', + unit="meV", ) @pytest.mark.parametrize( - 'prop, valid_value, invalid_value, invalid_message', + "prop, valid_value, invalid_value, invalid_message", [ - ('area', 3.0, 'invalid', r' must be a number'), - ('center', 0.6, 'invalid', r' must be a number'), - ('width', 0.7, 'invalid', r' must be a number'), + ("area", 3.0, "invalid", r" must be a number"), + ("center", 0.6, "invalid", r" must be a number"), + ("width", 0.7, "invalid", r" must be a number"), ], ) def test_property_setters( @@ -135,7 +113,7 @@ def test_property_setters( def test_width_must_be_positive(self, lorentzian: Lorentzian): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='width must be positive'): + with pytest.raises(ValueError, match="width must be positive"): lorentzian.width = -0.5 def test_evaluate(self, lorentzian: Lorentzian): @@ -168,9 +146,9 @@ def test_get_all_parameters(self, lorentzian: Lorentzian): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'LorentzianName area', - 'LorentzianName center', - 'LorentzianName width', + "LorentzianName area", + "LorentzianName center", + "LorentzianName width", } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -190,10 +168,10 @@ def test_area_matches_parameter(self, lorentzian: Lorentzian): def test_convert_unit(self, lorentzian: Lorentzian): # WHEN THEN - lorentzian.convert_unit('microeV') + lorentzian.convert_unit("microeV") # EXPECT - assert lorentzian.unit == 'microeV' + assert lorentzian.unit == "microeV" assert lorentzian.area.value == pytest.approx(2 * 1e3) assert lorentzian.center.value == pytest.approx(0.5 * 1e3) assert lorentzian.width.value == pytest.approx(0.6 * 1e3) @@ -222,9 +200,9 @@ def test_repr(self, lorentzian: Lorentzian): repr_str = repr(lorentzian) # EXPECT - assert 'Lorentzian' in repr_str - assert 'name = LorentzianName' in repr_str - assert 'unit = meV' in repr_str - assert 'area =' in repr_str - assert 'center =' in repr_str - assert 'width =' in repr_str + assert "Lorentzian" in repr_str + assert "name = LorentzianName" in repr_str + assert "unit = meV" in repr_str + assert "area =" in repr_str + assert "center =" in repr_str + assert "width =" in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_mixins.py b/tests/unit/easydynamics/sample_model/components/test_mixins.py index 4989f76e..4ef4f18d 100644 --- a/tests/unit/easydynamics/sample_model/components/test_mixins.py +++ b/tests/unit/easydynamics/sample_model/components/test_mixins.py @@ -14,169 +14,140 @@ def dummy_model(self): return CreateParametersMixin() # ------------- Area---------------------- - @pytest.mark.parametrize('unit', ['meV', 'eV']) - @pytest.mark.parametrize('area_input', [2, 2.0]) + @pytest.mark.parametrize("unit", ["meV", "eV"]) + @pytest.mark.parametrize("area_input", [2, 2.0]) def test_create_area_parameter_from_numeric(self, dummy_model, area_input, unit): # WHEN THEN - area_param = dummy_model._create_area_parameter(area_input, 'TestModel', unit=unit) + area_param = dummy_model._create_area_parameter( + area_input, "TestModel", unit=unit + ) # EXPECT assert isinstance(area_param, Parameter) - assert area_param.name == 'TestModel area' + assert area_param.name == "TestModel area" assert area_param.value == pytest.approx(area_input) assert area_param.unit == unit assert not area_param.fixed assert area_param.min == pytest.approx(0.0) - def test_create_area_parameter_from_parameter(self, dummy_model): - # WHEN - area_input = Parameter(name='input_area', value=3.0, unit='meV', fixed=True) - - # THEN - area_param = dummy_model._create_area_parameter(area_input, 'TestModel') - - # EXPECT - assert area_param is area_input # Should be the same object - assert area_param.min == pytest.approx(0.0) - def test_create_area_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='area must be a number or a Parameter'): - dummy_model._create_area_parameter('invalid', 'TestModel') + with pytest.raises(TypeError, match="area must be a number"): + dummy_model._create_area_parameter("invalid", "TestModel") @pytest.mark.parametrize( - 'non_finite_area', + "non_finite_area", [ np.nan, np.inf, -np.inf, ], ) - def test_create_area_parameter_invalid_numeric_raises(self, dummy_model, non_finite_area): + def test_create_area_parameter_invalid_numeric_raises( + self, dummy_model, non_finite_area + ): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='area must be a finite number or a Parameter'): - dummy_model._create_area_parameter(non_finite_area, 'TestModel') + with pytest.raises(ValueError, match="area must be a finite number"): + dummy_model._create_area_parameter(non_finite_area, "TestModel") def test_negative_area_warns(self, dummy_model): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match='may not be physically meaningful'): - area_param = dummy_model._create_area_parameter(-2.0, 'TestModel') + with pytest.warns(UserWarning, match="may not be physically meaningful"): + area_param = dummy_model._create_area_parameter(-2.0, "TestModel") - assert area_param.min == -float('inf') # No min constraint for negative area + assert area_param.min == -float("inf") # No min constraint for negative area # ------------- Center---------------------- - @pytest.mark.parametrize('unit', ['meV', 'eV']) - @pytest.mark.parametrize('center_input', [0, 0.0]) - def test_create_center_parameter_from_numeric(self, dummy_model, center_input, unit): + @pytest.mark.parametrize("unit", ["meV", "eV"]) + @pytest.mark.parametrize("center_input", [0, 0.0]) + def test_create_center_parameter_from_numeric( + self, dummy_model, center_input, unit + ): # WHEN THEN center_param = dummy_model._create_center_parameter( - center_input, 'TestModel', fix_if_none=False, unit=unit + center_input, "TestModel", fix_if_none=False, unit=unit ) # EXPECT assert isinstance(center_param, Parameter) - assert center_param.name == 'TestModel center' + assert center_param.name == "TestModel center" assert center_param.value == pytest.approx(center_input) assert center_param.unit == unit assert not center_param.fixed - @pytest.mark.parametrize('fix_if_none', [True, False]) + @pytest.mark.parametrize("fix_if_none", [True, False]) def test_create_center_parameter_from_None(self, dummy_model, fix_if_none): # WHEN THEN center_param = dummy_model._create_center_parameter( - None, 'TestModel', fix_if_none=fix_if_none + None, "TestModel", fix_if_none=fix_if_none ) # EXPECT assert isinstance(center_param, Parameter) - assert center_param.name == 'TestModel center' + assert center_param.name == "TestModel center" assert center_param.value == pytest.approx(0.0) - assert center_param.unit == 'meV' + assert center_param.unit == "meV" assert center_param.fixed == fix_if_none - def test_create_center_parameter_from_parameter(self, dummy_model): - # WHEN - center_input = Parameter(name='input_center', value=1.0, unit='meV', fixed=True) - - # THEN - center_param = dummy_model._create_center_parameter( - center_input, 'TestModel', fix_if_none=False - ) - - # EXPECT - assert center_param is center_input # Should be the same object - def test_create_center_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='center must be None, a number, or a Parameter'): - dummy_model._create_center_parameter('invalid', 'TestModel', fix_if_none=False) + with pytest.raises(TypeError, match="center must be None or a number"): + dummy_model._create_center_parameter( + "invalid", "TestModel", fix_if_none=False + ) @pytest.mark.parametrize( - 'non_finite_center', + "non_finite_center", [ np.nan, np.inf, -np.inf, ], ) - def test_create_center_parameter_invalid_numeric_raises(self, dummy_model, non_finite_center): + def test_create_center_parameter_invalid_numeric_raises( + self, dummy_model, non_finite_center + ): # WHEN THEN EXPECT - with pytest.raises( - ValueError, match='center must be None, a finite number or a Parameter' - ): - dummy_model._create_center_parameter(non_finite_center, 'TestModel', fix_if_none=False) + with pytest.raises(ValueError, match="center must be None or a finite number"): + dummy_model._create_center_parameter( + non_finite_center, "TestModel", fix_if_none=False + ) - @pytest.mark.parametrize('unit', ['meV', 'eV']) - @pytest.mark.parametrize('width_input', [2, 2.0]) + @pytest.mark.parametrize("unit", ["meV", "eV"]) + @pytest.mark.parametrize("width_input", [2, 2.0]) def test_create_width_parameter_from_numeric(self, dummy_model, width_input, unit): # WHEN THEN width_param = dummy_model._create_width_parameter( - width_input, 'TestModel', param_name='width', unit=unit + width_input, "TestModel", param_name="width", unit=unit ) # EXPECT assert isinstance(width_param, Parameter) - assert width_param.name == 'TestModel width' + assert width_param.name == "TestModel width" assert width_param.value == pytest.approx(width_input) assert width_param.unit == unit assert not width_param.fixed - def test_create_width_parameter_from_parameter(self, dummy_model): - # WHEN - width_input = Parameter(name='input_width', value=3.0, unit='meV', fixed=True) - - # THEN - width_param = dummy_model._create_width_parameter( - width_input, 'TestModel', param_name='width' - ) - - # EXPECT - assert width_param is width_input # Should be the same object - - def test_create_width_parameter_from_parameter_negative_raises(self, dummy_model): - # WHEN - width_input = Parameter(name='input_width', value=-1.0, unit='meV', fixed=True) - - # THEN EXPECT - with pytest.raises(ValueError, match=' must be greater than zero'): - dummy_model._create_width_parameter(width_input, 'TestModel', param_name='width') - def test_create_width_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='width must be a number or a Parameter'): - dummy_model._create_width_parameter('invalid', 'TestModel', param_name='width') + with pytest.raises(TypeError, match="width must be a number"): + dummy_model._create_width_parameter( + "invalid", "TestModel", param_name="width" + ) def test_create_width_parameter_negative_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match=' must be greater than zero'): - dummy_model._create_width_parameter(-1, 'TestModel', param_name='width') + with pytest.raises(ValueError, match=" must be greater than zero"): + dummy_model._create_width_parameter(-1, "TestModel", param_name="width") @pytest.mark.parametrize( - 'non_finite_center', + "non_finite_center", [ np.nan, np.inf, -np.inf, ], ) - def test_create_width_parameter_invalid_numeric_raises(self, dummy_model, non_finite_center): + def test_create_width_parameter_invalid_numeric_raises( + self, dummy_model, non_finite_center + ): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='width must be a finite number or a Parameter'): - dummy_model._create_width_parameter(non_finite_center, 'TestModel') + with pytest.raises(ValueError, match="width must be a finite number"): + dummy_model._create_width_parameter(non_finite_center, "TestModel") diff --git a/tests/unit/easydynamics/sample_model/components/test_voigt.py b/tests/unit/easydynamics/sample_model/components/test_voigt.py index 4d28c73a..2164f8e8 100644 --- a/tests/unit/easydynamics/sample_model/components/test_voigt.py +++ b/tests/unit/easydynamics/sample_model/components/test_voigt.py @@ -16,13 +16,13 @@ class TestVoigt: @pytest.fixture def voigt(self): return Voigt( - name='VoigtName', - display_name='TestVoigt', + name="VoigtName", + display_name="TestVoigt", area=2.0, center=0.5, gaussian_width=0.6, lorentzian_width=0.7, - unit='meV', + unit="meV", ) def test_init_no_inputs(self): @@ -30,159 +30,134 @@ def test_init_no_inputs(self): voigt = Voigt() # EXPECT - assert voigt.display_name == 'Voigt' + assert voigt.display_name == "Voigt" assert voigt.area.value == pytest.approx(1.0) assert voigt.center.value == pytest.approx(0.0) assert voigt.gaussian_width.value == pytest.approx(1.0) assert voigt.lorentzian_width.value == pytest.approx(1.0) - assert voigt.unit == 'meV' + assert voigt.unit == "meV" assert voigt.center.fixed is True def test_initialization(self, voigt: Voigt): # WHEN THEN EXPECT - assert voigt.display_name == 'TestVoigt' + assert voigt.display_name == "TestVoigt" assert voigt.area.value == pytest.approx(2.0) assert voigt.center.value == pytest.approx(0.5) assert voigt.gaussian_width.value == pytest.approx(0.6) assert voigt.lorentzian_width.value == pytest.approx(0.7) - assert voigt.unit == 'meV' - - def test_init_with_parameters(self): - # WHEN - area_param = Parameter(name='area_param', value=3.0, unit='meV') - center_param = Parameter(name='center_param', value=1.0, unit='meV') - gaussian_width_param = Parameter(name='gaussian_width_param', value=0.8, unit='meV') - lorentzian_width_param = Parameter(name='lorentzian_width_param', value=0.9, unit='meV') - - # THEN - voigt = Voigt( - display_name='ParamVoigt', - area=area_param, - center=center_param, - gaussian_width=gaussian_width_param, - lorentzian_width=lorentzian_width_param, - unit='meV', - ) - - # EXPECT - assert voigt.display_name == 'ParamVoigt' - assert voigt.area is area_param - assert voigt.center is center_param - assert voigt.gaussian_width is gaussian_width_param - assert voigt.lorentzian_width is lorentzian_width_param - assert voigt.unit == 'meV' + assert voigt.unit == "meV" @pytest.mark.parametrize( - 'kwargs, expected_message', + "kwargs, expected_message", [ ( { - 'area': 'invalid', - 'center': 0.5, - 'gaussian_width': 0.6, - 'lorentzian_width': 0.7, - 'unit': 'meV', + "area": "invalid", + "center": 0.5, + "gaussian_width": 0.6, + "lorentzian_width": 0.7, + "unit": "meV", }, - 'area must be a number', + "area must be a number", ), ( { - 'area': 2.0, - 'center': 'invalid', - 'gaussian_width': 0.6, - 'lorentzian_width': 0.7, - 'unit': 'meV', + "area": 2.0, + "center": "invalid", + "gaussian_width": 0.6, + "lorentzian_width": 0.7, + "unit": "meV", }, - 'center must be None', + "center must be None", ), ( { - 'area': 2.0, - 'center': 0.5, - 'gaussian_width': 'invalid', - 'lorentzian_width': 0.7, - 'unit': 'meV', + "area": 2.0, + "center": 0.5, + "gaussian_width": "invalid", + "lorentzian_width": 0.7, + "unit": "meV", }, - 'gaussian_width must be a number', + "gaussian_width must be a number", ), ( { - 'area': 2.0, - 'center': 0.5, - 'gaussian_width': 0.6, - 'lorentzian_width': 'invalid', - 'unit': 'meV', + "area": 2.0, + "center": 0.5, + "gaussian_width": 0.6, + "lorentzian_width": "invalid", + "unit": "meV", }, - 'lorentzian_width must be a number', + "lorentzian_width must be a number", ), ( { - 'area': 2.0, - 'center': 0.5, - 'gaussian_width': 0.6, - 'lorentzian_width': 0.7, - 'unit': 123, + "area": 2.0, + "center": 0.5, + "gaussian_width": 0.6, + "lorentzian_width": 0.7, + "unit": 123, }, - 'unit must be None,', + "unit must be None,", ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Voigt(display_name='TestVoigt', **kwargs) + Voigt(display_name="TestVoigt", **kwargs) def test_negative_gaussian_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match=r'The gaussian_width of a Voigt must be greater than.' + ValueError, match=r"The gaussian_width of a Voigt must be greater than." ): Voigt( - display_name='TestVoigt', + display_name="TestVoigt", area=2.0, center=0.5, gaussian_width=-0.6, lorentzian_width=0.7, - unit='meV', + unit="meV", ) def test_negative_lorentzian_width_raises(self): # WHEN THEN EXPECT with pytest.raises( ValueError, - match=r'The lorentzian_width of a Voigt must be greater than zero.', + match=r"The lorentzian_width of a Voigt must be greater than zero.", ): Voigt( - display_name='TestVoigt', + display_name="TestVoigt", area=2.0, center=0.5, gaussian_width=0.6, lorentzian_width=-0.7, - unit='meV', + unit="meV", ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match='may not be physically meaningful'): + with pytest.warns(UserWarning, match="may not be physically meaningful"): Voigt( - display_name='TestVoigt', + display_name="TestVoigt", area=-2.0, center=0.5, gaussian_width=0.6, lorentzian_width=0.7, - unit='meV', + unit="meV", ) @pytest.mark.parametrize( - 'prop, valid_value, invalid_value, invalid_message', + "prop, valid_value, invalid_value, invalid_message", [ - ('area', 3.0, 'invalid', r'must be a number'), - ('center', 0.6, 'invalid', r'must be a number'), - ('gaussian_width', 0.7, 'invalid', r'must be a number'), + ("area", 3.0, "invalid", r"must be a number"), + ("center", 0.6, "invalid", r"must be a number"), + ("gaussian_width", 0.7, "invalid", r"must be a number"), ( - 'lorentzian_width', + "lorentzian_width", 0.7, - 'invalid', - r'must be a number', + "invalid", + r"must be a number", ), ], ) @@ -199,14 +174,14 @@ def test_property_setters( def test_gaussian_width_must_be_positive(self, voigt: Voigt): # WHEN THEN - with pytest.raises(ValueError, match='gaussian_width must be positive'): + with pytest.raises(ValueError, match="gaussian_width must be positive"): voigt.gaussian_width = -0.6 def test_lorentzian_width_must_be_positive(self, voigt: Voigt): # WHEN THEN with pytest.raises( ValueError, - match='lorentzian_width must be positive', + match="lorentzian_width must be positive", ): voigt.lorentzian_width = -0.7 @@ -239,7 +214,7 @@ def test_center_is_fixed_if_init_to_None(self): center=None, gaussian_width=0.6, lorentzian_width=0.7, - unit='meV', + unit="meV", ) # EXPECT @@ -248,10 +223,10 @@ def test_center_is_fixed_if_init_to_None(self): def test_convert_unit(self, voigt: Voigt): # WHEN THEN - voigt.convert_unit('microeV') + voigt.convert_unit("microeV") # EXPECT - assert voigt.unit == 'microeV' + assert voigt.unit == "microeV" assert voigt.area.value == pytest.approx(2 * 1e3) assert voigt.center.value == pytest.approx(0.5 * 1e3) assert voigt.gaussian_width.value == pytest.approx(0.6 * 1e3) @@ -265,10 +240,10 @@ def test_get_all_parameters(self, voigt: Voigt): assert len(params) == 4 assert all(isinstance(param, Parameter) for param in params) expected_names = { - 'VoigtName area', - 'VoigtName center', - 'VoigtName gaussian_width', - 'VoigtName lorentzian_width', + "VoigtName area", + "VoigtName center", + "VoigtName gaussian_width", + "VoigtName lorentzian_width", } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -317,10 +292,10 @@ def test_repr(self, voigt: Voigt): repr_str = repr(voigt) # EXPECT - assert 'Voigt' in repr_str - assert 'name = VoigtName' in repr_str - assert 'unit = meV' in repr_str - assert 'area =' in repr_str - assert 'center =' in repr_str - assert 'gaussian_width =' in repr_str - assert 'lorentzian_width =' in repr_str + assert "Voigt" in repr_str + assert "name = VoigtName" in repr_str + assert "unit = meV" in repr_str + assert "area =" in repr_str + assert "center =" in repr_str + assert "gaussian_width =" in repr_str + assert "lorentzian_width =" in repr_str From 1c666d1261017230d90c26746a41b1c0cb6861f0 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Mon, 1 Jun 2026 11:59:27 +0200 Subject: [PATCH 2/4] fix delta_lorentz model --- .../components/damped_harmonic_oscillator.py | 42 +++--- .../sample_model/components/delta_function.py | 26 ++-- .../sample_model/components/exponential.py | 50 +++--- .../sample_model/components/gaussian.py | 4 +- .../sample_model/components/lorentzian.py | 34 ++--- .../sample_model/components/mixins.py | 42 +++--- .../diffusion_model/delta_lorentz.py | 15 +- .../test_damped_harmonic_oscillator.py | 87 +++++------ .../components/test_exponential.py | 88 +++++------ .../sample_model/components/test_gaussian.py | 86 +++++------ .../components/test_lorentzian.py | 78 +++++----- .../sample_model/components/test_mixins.py | 96 +++++------- .../sample_model/components/test_voigt.py | 142 +++++++++--------- 13 files changed, 377 insertions(+), 413 deletions(-) diff --git a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py index 2990efdc..f0048c7e 100644 --- a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py +++ b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py @@ -29,8 +29,8 @@ def __init__( area: Numeric = 1.0, center: Numeric = 1.0, width: Numeric = 1.0, - unit: str | sc.Unit = "meV", - name: str = "DampedHarmonicOscillator", + unit: str | sc.Unit = 'meV', + name: str = 'DampedHarmonicOscillator', display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -39,16 +39,16 @@ def __init__( Parameters ---------- - area : Numeric , default=1.0 + area : Numeric, default=1.0 Area under the curve. - center : Numeric , default=1.0 + center : Numeric, default=1.0 Resonance frequency, approximately the peak position. - width : Numeric , default=1.0 + width : Numeric, default=1.0 Damping constant, approximately the half width at half max (HWHM) of the peaks. By default, 1.0. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" Unit of the parameters. - name : str, default='DampedHarmonicOscillator' + name : str, default="DampedHarmonicOscillator" Name of the component for indexing. display_name : str | None, default=None Display name of the component. @@ -108,7 +108,7 @@ def area(self, value: Numeric) -> None: If the value is not a number. """ if not isinstance(value, Numeric): - raise TypeError("area must be a number") + raise TypeError('area must be a number') self._area.value = value @property @@ -141,10 +141,10 @@ def center(self, value: Numeric) -> None: If the value is not positive. """ if not isinstance(value, Numeric): - raise TypeError("center must be a number") + raise TypeError('center must be a number') if float(value) <= 0: - raise ValueError("center must be positive") + raise ValueError('center must be positive') self._center.value = value @property @@ -177,16 +177,14 @@ def width(self, value: Numeric) -> None: If the value is not positive. """ if not isinstance(value, Numeric): - raise TypeError("width must be a number") + raise TypeError('width must be a number') if float(value) <= 0: - raise ValueError("width must be positive") + raise ValueError('width must be positive') self._width.value = value - def evaluate( - self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray - ) -> np.ndarray: + def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: r""" Evaluate the Damped Harmonic Oscillator at the given x values. @@ -209,9 +207,7 @@ def evaluate( normalization = 2 * self.center.value**2 * self.width.value / np.pi # No division by zero here, width>0 enforced in setter - denominator = (x**2 - self.center.value**2) ** 2 + ( - 2 * self.width.value * x - ) ** 2 + denominator = (x**2 - self.center.value**2) ** 2 + (2 * self.width.value * x) ** 2 return self.area.value * normalization / (denominator) @@ -225,9 +221,9 @@ def __repr__(self) -> str: A string representation of the Damped Harmonic Oscillator. """ return ( - f"DampedHarmonicOscillator(name = {self.name}, display_name = {self.display_name}, " - f"unit = {self._unit},\n " - f" area = {self.area},\n " - f" center = {self.center},\n " - f" width = {self.width})" + f'DampedHarmonicOscillator(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n ' + f' area = {self.area},\n ' + f' center = {self.center},\n ' + f' width = {self.width})' ) diff --git a/src/easydynamics/sample_model/components/delta_function.py b/src/easydynamics/sample_model/components/delta_function.py index 030299ad..e8fd6728 100644 --- a/src/easydynamics/sample_model/components/delta_function.py +++ b/src/easydynamics/sample_model/components/delta_function.py @@ -32,8 +32,8 @@ def __init__( self, center: Numeric | None = None, area: Numeric = 1.0, - unit: str | sc.Unit = "meV", - name: str = "DeltaFunction", + unit: str | sc.Unit = 'meV', + name: str = 'DeltaFunction', display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -44,11 +44,11 @@ def __init__( ---------- center : Numeric | None, default=None Center of the delta function. If None, it will be centered at 0 and fixed. - area : Numeric , default=1.0 + area : Numeric, default=1.0 Total area under the curve. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" Unit of the parameters. - name : str, default='DeltaFunction' + name : str, default="DeltaFunction" Name of the component for indexing. display_name : str | None, default=None Display name of the component. @@ -103,7 +103,7 @@ def area(self, value: Numeric) -> None: """ if not isinstance(value, Numeric): - raise TypeError("area must be a number") + raise TypeError('area must be a number') self._area.value = value @property @@ -139,12 +139,10 @@ def center(self, value: Numeric | None) -> None: value = 0.0 self._center.fixed = True if not isinstance(value, Numeric): - raise TypeError("center must be a number") + raise TypeError('center must be a number') self._center.value = value - def evaluate( - self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray - ) -> np.ndarray: + def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: """ Evaluate the Delta function at the given x values. @@ -202,8 +200,8 @@ def __repr__(self) -> str: """ return ( - f"DeltaFunction(name = {self.name}, display_name = {self.display_name}, " - f"unit = {self.unit},\n" - f" area = {self.area},\n" - f" center = {self.center})" + f'DeltaFunction(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self.unit},\n' + f' area = {self.area},\n' + f' center = {self.center})' ) diff --git a/src/easydynamics/sample_model/components/exponential.py b/src/easydynamics/sample_model/components/exponential.py index 9ac7a54a..336f67e1 100644 --- a/src/easydynamics/sample_model/components/exponential.py +++ b/src/easydynamics/sample_model/components/exponential.py @@ -28,8 +28,8 @@ def __init__( amplitude: Numeric = 1.0, center: Numeric | None = None, rate: Numeric = 1.0, - unit: str | sc.Unit = "meV", - name: str = "Exponential", + unit: str | sc.Unit = 'meV', + name: str = 'Exponential', display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -38,15 +38,15 @@ def __init__( Parameters ---------- - amplitude : Numeric , default=1.0 + amplitude : Numeric, default=1.0 Amplitude of the Exponential. center : Numeric | None, default=None Center of the Exponential. If None, the center is fixed at 0. - rate : Numeric , default=1.0 + rate : Numeric, default=1.0 Decay or growth constant of the Exponential. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" Unit of the parameters. - name : str, default='Exponential' + name : str, default="Exponential" Name of the component for indexing. display_name : str | None, default=None Display name of the component. @@ -70,30 +70,26 @@ def __init__( ) if not isinstance(amplitude, (Parameter, Numeric)): - raise TypeError("amplitude must be a number or a Parameter.") + raise TypeError('amplitude must be a number or a Parameter.') if isinstance(amplitude, Numeric): if not np.isfinite(amplitude): - raise ValueError("amplitude must be a finite number or a Parameter") + raise ValueError('amplitude must be a finite number or a Parameter') - amplitude = Parameter( - name=name + " amplitude", value=float(amplitude), unit=unit - ) + amplitude = Parameter(name=name + ' amplitude', value=float(amplitude), unit=unit) center = self._create_center_parameter( center=center, name=name, fix_if_none=True, unit=self._unit ) if not isinstance(rate, (Parameter, Numeric)): - raise TypeError("rate must be a number or a Parameter.") + raise TypeError('rate must be a number or a Parameter.') if isinstance(rate, Numeric): if not np.isfinite(rate): - raise ValueError("rate must be a finite number or a Parameter") + raise ValueError('rate must be a finite number or a Parameter') - rate = Parameter( - name=name + " rate", value=float(rate), unit="1/" + str(unit) - ) + rate = Parameter(name=name + ' rate', value=float(rate), unit='1/' + str(unit)) self._amplitude = amplitude self._center = center @@ -129,7 +125,7 @@ def amplitude(self, value: Numeric) -> None: """ if not isinstance(value, Numeric): - raise TypeError("amplitude must be a number") + raise TypeError('amplitude must be a number') self._amplitude.value = value @property @@ -166,7 +162,7 @@ def center(self, value: Numeric | None) -> None: self._center.fixed = True if not isinstance(value, Numeric): - raise TypeError("center must be a number") + raise TypeError('center must be a number') self._center.value = value @property @@ -197,7 +193,7 @@ def rate(self, value: Numeric) -> None: If the value is not a number. """ if not isinstance(value, Numeric): - raise TypeError("rate must be a number") + raise TypeError('rate must be a number') self._rate.value = value @@ -247,21 +243,21 @@ def convert_unit(self, unit: str | sc.Unit) -> None: """ if not isinstance(unit, (str, sc.Unit)): - raise TypeError("unit must be a string or sc.Unit") + raise TypeError('unit must be a string or sc.Unit') old_unit = self._unit pars = [self.amplitude, self.center] try: for p in pars: p.convert_unit(unit) - self.rate.convert_unit("1/" + str(unit)) + self.rate.convert_unit('1/' + str(unit)) self._unit = unit except Exception as e: # Attempt to rollback on failure try: for p in pars: p.convert_unit(old_unit) - self.rate.convert_unit("1/" + str(old_unit)) + self.rate.convert_unit('1/' + str(old_unit)) except Exception: # noqa: S110 pass # Best effort rollback raise e @@ -277,9 +273,9 @@ def __repr__(self) -> str: """ return ( - f"Exponential(name = {self.name}, display_name = {self.display_name}, " - f"unit = {self._unit},\n " - f" amplitude = {self.amplitude},\n " - f" center = {self.center},\n " - f" rate = {self.rate})" + f'Exponential(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n ' + f' amplitude = {self.amplitude},\n ' + f' center = {self.center},\n ' + f' rate = {self.rate})' ) diff --git a/src/easydynamics/sample_model/components/gaussian.py b/src/easydynamics/sample_model/components/gaussian.py index 558cfd8d..cab505b1 100644 --- a/src/easydynamics/sample_model/components/gaussian.py +++ b/src/easydynamics/sample_model/components/gaussian.py @@ -46,11 +46,11 @@ def __init__( Parameters ---------- - area : Numeric , default=1.0 + area : Numeric, default=1.0 Area of the Gaussian. center : Numeric | None, default=None Center of the Gaussian. If None. - width : Numeric , default=1.0 + width : Numeric, default=1.0 Standard deviation. unit : str | sc.Unit, default='meV' Unit of the parameters. diff --git a/src/easydynamics/sample_model/components/lorentzian.py b/src/easydynamics/sample_model/components/lorentzian.py index 6fc8ebc7..1232d0b1 100644 --- a/src/easydynamics/sample_model/components/lorentzian.py +++ b/src/easydynamics/sample_model/components/lorentzian.py @@ -33,8 +33,8 @@ def __init__( area: Numeric = 1.0, center: Numeric | None = None, width: Numeric = 1.0, - unit: str | sc.Unit = "meV", - name: str = "Lorentzian", + unit: str | sc.Unit = 'meV', + name: str = 'Lorentzian', display_name: str | None = None, unique_name: str | None = None, ) -> None: @@ -43,15 +43,15 @@ def __init__( Parameters ---------- - area : Numeric , default=1.0 + area : Numeric, default=1.0 Area of the Lorentzian. center : Numeric | None, default=None Center of the Lorentzian. If None, defaults to 0 and is fixed. - width : Numeric , default=1.0 + width : Numeric, default=1.0 Half width at half maximum (HWHM). - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" Unit of the parameters. - name : str, default='Lorentzian' + name : str, default="Lorentzian" Name of the component for indexing. display_name : str | None, default=None Display name for the component. @@ -106,7 +106,7 @@ def area(self, value: Numeric) -> None: If the value is not a number. """ if not isinstance(value, Numeric): - raise TypeError("area must be a number") + raise TypeError('area must be a number') self._area.value = value @property @@ -141,7 +141,7 @@ def center(self, value: Numeric | None) -> None: value = 0.0 self._center.fixed = True if not isinstance(value, Numeric): - raise TypeError("center must be a number") + raise TypeError('center must be a number') self._center.value = value @property @@ -174,15 +174,13 @@ def width(self, value: Numeric) -> None: If the value is not positive. """ if not isinstance(value, Numeric): - raise TypeError("width must be a number") + raise TypeError('width must be a number') if float(value) <= 0: - raise ValueError("width must be positive") + raise ValueError('width must be positive') self._width.value = value - def evaluate( - self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray - ) -> np.ndarray: + def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) -> np.ndarray: r""" Evaluate the Lorentzian at the given x values. @@ -222,9 +220,9 @@ def __repr__(self) -> str: A string representation of the Lorentzian. """ return ( - f"Lorentzian(name = {self.name}, display_name = {self.display_name}, " - f"unit = {self._unit},\n" - f" area = {self.area},\n" - f" center = {self.center},\n" - f" width = {self.width})" + f'Lorentzian(name = {self.name}, display_name = {self.display_name}, ' + f'unit = {self._unit},\n' + f' area = {self.area},\n' + f' center = {self.center},\n' + f' width = {self.width})' ) diff --git a/src/easydynamics/sample_model/components/mixins.py b/src/easydynamics/sample_model/components/mixins.py index ef82f3a0..d9163784 100644 --- a/src/easydynamics/sample_model/components/mixins.py +++ b/src/easydynamics/sample_model/components/mixins.py @@ -26,7 +26,7 @@ def _create_area_parameter( self, area: Numeric, name: str, - unit: str | sc.Unit = "meV", + unit: str | sc.Unit = 'meV', minimum_area: float = MINIMUM_AREA, ) -> Parameter: """ @@ -41,7 +41,7 @@ def _create_area_parameter( The area value. name : str The name of the model component. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" The unit of the area Parameter. minimum_area : float, default=MINIMUM_AREA The minimum allowed area. @@ -59,16 +59,16 @@ def _create_area_parameter( The validated area Parameter. """ if not isinstance(area, Numeric): - raise TypeError("area must be a number.") + raise TypeError('area must be a number.') if isinstance(area, Numeric): if not np.isfinite(area): - raise ValueError("area must be a finite number.") - area = Parameter(name=name + " area", value=float(area), unit=unit) + raise ValueError('area must be a finite number.') + area = Parameter(name=name + ' area', value=float(area), unit=unit) if area.value < 0: warnings.warn( - f"The area of {name} is negative, which may not be physically meaningful.", + f'The area of {name} is negative, which may not be physically meaningful.', UserWarning, stacklevel=3, ) @@ -83,7 +83,7 @@ def _create_center_parameter( center: Numeric | None, name: str, fix_if_none: bool, - unit: str | sc.Unit = "meV", + unit: str | sc.Unit = 'meV', enforce_minimum_center: bool = False, ) -> Parameter: """ @@ -97,7 +97,7 @@ def _create_center_parameter( The name of the model component. fix_if_none : bool Whether to fix the center Parameter if center is None. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" The unit of the center Parameter. enforce_minimum_center : bool, default=False Whether to enforce a minimum center value to avoid zero center in DHO. @@ -116,20 +116,20 @@ def _create_center_parameter( The validated center Parameter. """ if center is not None and not isinstance(center, Numeric): - raise TypeError("center must be None or a number.") + raise TypeError('center must be None or a number.') if center is None: center = Parameter( - name=name + " center", + name=name + ' center', value=0.0, unit=unit, fixed=fix_if_none, ) elif isinstance(center, Numeric): if not np.isfinite(center): - raise ValueError("center must be None or a finite number.") + raise ValueError('center must be None or a finite number.') - center = Parameter(name=name + " center", value=float(center), unit=unit) + center = Parameter(name=name + ' center', value=float(center), unit=unit) if enforce_minimum_center and center.min < DHO_MINIMUM_CENTER: center.min = DHO_MINIMUM_CENTER return center @@ -138,8 +138,8 @@ def _create_width_parameter( self, width: Numeric, name: str, - param_name: str = "width", - unit: str | sc.Unit = "meV", + param_name: str = 'width', + unit: str | sc.Unit = 'meV', minimum_width: float = MINIMUM_WIDTH, ) -> Parameter: """ @@ -151,9 +151,9 @@ def _create_width_parameter( The width value. name : str The name of the model component. - param_name : str, default='width' + param_name : str, default="width" The name of the width parameter. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" The unit of the width Parameter. minimum_width : float, default=MINIMUM_WIDTH The minimum allowed width. @@ -171,18 +171,18 @@ def _create_width_parameter( The validated width Parameter. """ if not isinstance(width, Numeric): - raise TypeError(f"{param_name} must be a number.") + raise TypeError(f'{param_name} must be a number.') if isinstance(width, Numeric): if not np.isfinite(width): - raise ValueError(f"{param_name} must be a finite number") + raise ValueError(f'{param_name} must be a finite number') if float(width) < minimum_width: raise ValueError( - f"The {param_name} of a {self.__class__.__name__} must be greater than zero." + f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' ) width = Parameter( - name=name + " " + param_name, + name=name + ' ' + param_name, value=float(width), unit=unit, min=minimum_width, @@ -190,7 +190,7 @@ def _create_width_parameter( else: if width.value <= 0: raise ValueError( - f"The {param_name} of a {self.__class__.__name__} must be greater than zero." + f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' ) if width.min < minimum_width: width.min = minimum_width diff --git a/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py b/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py index 0a96ee59..55c1cb52 100644 --- a/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py +++ b/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py @@ -88,19 +88,19 @@ def __init__( allowed. Q : Q_type | None, default=None Q values for the model. If None, Q is not set. - unit : str | sc.Unit, default='meV' + unit : str | sc.Unit, default="meV" Unit of the diffusion model. Must be convertible to meV. - name : str, default='DeltaLorentz' + name : str, default="DeltaLorentz" Name of the diffusion model. display_name : str | None, default=None Display name of the diffusion model. - lorentzian_name : str, default='Lorentzian' + lorentzian_name : str, default="Lorentzian" Name of the Lorentzian component. If None, it will be set to the name of the diffusion model. lorentzian_display_name : str | None, default=None Display name of the Lorentzian component. If None, it will be set to the display name of the diffusion model. - delta_name : str, default='Delta function' + delta_name : str, default="Delta function" Name of the delta function component. delta_display_name : str | None, default=None Display name of the delta function component. If None, it will be set to the display @@ -486,16 +486,13 @@ def create_component_collections( # ------------------------------# # Create Lorentzian # ------------------------------# - if self._allow_Q_variation['lorentzian_width'] is True: - width = self._lorentzian_width_list[i] - else: - width = 1.0 lorentzian_component = Lorentzian( name=self.lorentzian_name, display_name=self.lorentzian_display_name, unit=self.unit, - width=width, ) + if self._allow_Q_variation['lorentzian_width'] is True: + lorentzian_component._width = self._lorentzian_width_list[i] # noqa: SLF001 # If the width is allowed to vary with Q it is independent. # If the width is not allowed to vary with Q it must be made diff --git a/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py b/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py index 8139cb03..87f9556c 100644 --- a/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py +++ b/tests/unit/easydynamics/sample_model/components/test_damped_harmonic_oscillator.py @@ -15,12 +15,12 @@ class TestDampedHarmonicOscillator: @pytest.fixture def dho(self): return DampedHarmonicOscillator( - name="TestDHOName", - display_name="TestDHO", + name='TestDHOName', + display_name='TestDHO', area=2.0, center=1.5, width=0.3, - unit="meV", + unit='meV', ) def test_init_no_inputs(self): @@ -28,76 +28,76 @@ def test_init_no_inputs(self): dho = DampedHarmonicOscillator() # EXPECT - assert dho.display_name == "DampedHarmonicOscillator" + assert dho.display_name == 'DampedHarmonicOscillator' assert dho.area.value == pytest.approx(1.0) assert dho.center.value == pytest.approx(1.0) assert dho.width.value == pytest.approx(1.0) - assert dho.unit == "meV" + assert dho.unit == 'meV' def test_initialization(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT - assert dho.display_name == "TestDHO" + assert dho.display_name == 'TestDHO' assert dho.area.value == pytest.approx(2.0) assert dho.center.value == pytest.approx(1.5) assert dho.width.value == pytest.approx(0.3) - assert dho.unit == "meV" + assert dho.unit == 'meV' @pytest.mark.parametrize( - "kwargs, expected_message", + 'kwargs, expected_message', [ ( - {"area": "invalid", "center": 0.5, "width": 0.6, "unit": "meV"}, - "area must be a number", + {'area': 'invalid', 'center': 0.5, 'width': 0.6, 'unit': 'meV'}, + 'area must be a number', ), ( - {"area": 2.0, "center": "invalid", "width": 0.6, "unit": "meV"}, - "center must be ", + {'area': 2.0, 'center': 'invalid', 'width': 0.6, 'unit': 'meV'}, + 'center must be ', ), ( - {"area": 2.0, "center": 0.5, "width": "invalid", "unit": "meV"}, - "width must be a number", + {'area': 2.0, 'center': 0.5, 'width': 'invalid', 'unit': 'meV'}, + 'width must be a number', ), ( - {"area": 2.0, "center": 0.5, "width": 0.6, "unit": 123}, - "unit must be None", + {'area': 2.0, 'center': 0.5, 'width': 0.6, 'unit': 123}, + 'unit must be None', ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - DampedHarmonicOscillator(display_name="DampedHarmonicOscillator", **kwargs) + DampedHarmonicOscillator(display_name='DampedHarmonicOscillator', **kwargs) def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( ValueError, - match=r"The width of a DampedHarmonicOscillator must be greater than zero.", + match=r'The width of a DampedHarmonicOscillator must be greater than zero.', ): DampedHarmonicOscillator( - display_name="TestDampedHarmonicOscillator", + display_name='TestDampedHarmonicOscillator', area=2.0, center=0.5, width=-0.6, - unit="meV", + unit='meV', ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match="may not be physically meaningful"): + with pytest.warns(UserWarning, match='may not be physically meaningful'): DampedHarmonicOscillator( - display_name="TestDampedHarmonicOscillator", + display_name='TestDampedHarmonicOscillator', area=-2.0, center=0.5, width=0.6, - unit="meV", + unit='meV', ) @pytest.mark.parametrize( - "prop, valid_value, invalid_value, invalid_message", + 'prop, valid_value, invalid_value, invalid_message', [ - ("area", 3.0, "invalid", r"must be a number"), - ("center", 0.6, "invalid", r"must be a number"), - ("width", 0.7, "invalid", r"must be a number"), + ('area', 3.0, 'invalid', r'must be a number'), + ('center', 0.6, 'invalid', r'must be a number'), + ('width', 0.7, 'invalid', r'must be a number'), ], ) def test_property_setters( @@ -118,12 +118,12 @@ def test_property_setters( def test_center_setter_negative_raises(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="center must be positive"): + with pytest.raises(ValueError, match='center must be positive'): dho.center = -1.0 def test_width_must_be_positive(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="width must be positive"): + with pytest.raises(ValueError, match='width must be positive'): dho.width = -0.5 def test_evaluate(self, dho: DampedHarmonicOscillator): @@ -135,12 +135,7 @@ def test_evaluate(self, dho: DampedHarmonicOscillator): # EXPECT expected_result = ( - 2 - * 2.0 - * (1.5**2) - * (0.3) - / np.pi - / ((x**2 - 1.5**2) ** 2 + (2 * 0.3 * x) ** 2) + 2 * 2.0 * (1.5**2) * (0.3) / np.pi / ((x**2 - 1.5**2) ** 2 + (2 * 0.3 * x) ** 2) ) np.testing.assert_allclose(result, expected_result, rtol=1e-5) @@ -152,9 +147,9 @@ def test_get_all_parameters(self, dho: DampedHarmonicOscillator): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - "TestDHOName area", - "TestDHOName center", - "TestDHOName width", + 'TestDHOName area', + 'TestDHOName center', + 'TestDHOName width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -174,10 +169,10 @@ def test_area_matches_parameter(self, dho: DampedHarmonicOscillator): def test_convert_unit(self, dho: DampedHarmonicOscillator): # WHEN THEN - dho.convert_unit("microeV") + dho.convert_unit('microeV') # EXPECT - assert dho.unit == "microeV" + assert dho.unit == 'microeV' assert dho.area.value == pytest.approx(2 * 1e3) assert dho.center.value == pytest.approx(1.5 * 1e3) assert dho.width.value == pytest.approx(0.3 * 1e3) @@ -206,9 +201,9 @@ def test_repr(self, dho: DampedHarmonicOscillator): repr_str = repr(dho) # EXPECT - assert "DampedHarmonicOscillator" in repr_str - assert "name = TestDHOName" in repr_str - assert "unit = meV" in repr_str - assert "area =" in repr_str - assert "center =" in repr_str - assert "width =" in repr_str + assert 'DampedHarmonicOscillator' in repr_str + assert 'name = TestDHOName' in repr_str + assert 'unit = meV' in repr_str + assert 'area =' in repr_str + assert 'center =' in repr_str + assert 'width =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_exponential.py b/tests/unit/easydynamics/sample_model/components/test_exponential.py index 7eb7b665..6c2a1c50 100644 --- a/tests/unit/easydynamics/sample_model/components/test_exponential.py +++ b/tests/unit/easydynamics/sample_model/components/test_exponential.py @@ -15,12 +15,12 @@ class TestExponential: @pytest.fixture def exponential(self): return Exponential( - name="ExponentialName", - display_name="TestExponential", + name='ExponentialName', + display_name='TestExponential', amplitude=2.0, center=0.5, rate=1.2, - unit="meV", + unit='meV', ) def test_init_no_inputs(self): @@ -28,64 +28,64 @@ def test_init_no_inputs(self): exponential = Exponential() # THEN EXPECT - assert exponential.display_name == "Exponential" + assert exponential.display_name == 'Exponential' assert exponential.amplitude.value == pytest.approx(1.0) assert exponential.center.value == pytest.approx(0.0) assert exponential.rate.value == pytest.approx(1.0) - assert exponential.unit == "meV" + assert exponential.unit == 'meV' def test_initialization(self, exponential: Exponential): # WHEN THEN EXPECT - assert exponential.display_name == "TestExponential" + assert exponential.display_name == 'TestExponential' assert exponential.amplitude.value == pytest.approx(2.0) assert exponential.center.value == pytest.approx(0.5) assert exponential.rate.value == pytest.approx(1.2) - assert exponential.unit == "meV" + assert exponential.unit == 'meV' @pytest.mark.parametrize( - "kwargs, expected_message", + 'kwargs, expected_message', [ ( - {"amplitude": "invalid", "center": 0.5, "rate": 1.0, "unit": "meV"}, - "amplitude must be a number", + {'amplitude': 'invalid', 'center': 0.5, 'rate': 1.0, 'unit': 'meV'}, + 'amplitude must be a number', ), ( - {"amplitude": 2.0, "center": "invalid", "rate": 1.0, "unit": "meV"}, - "center must be None or a number", + {'amplitude': 2.0, 'center': 'invalid', 'rate': 1.0, 'unit': 'meV'}, + 'center must be None or a number', ), ( - {"amplitude": 2.0, "center": 0.5, "rate": "invalid", "unit": "meV"}, - "rate must be a number", + {'amplitude': 2.0, 'center': 0.5, 'rate': 'invalid', 'unit': 'meV'}, + 'rate must be a number', ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Exponential(display_name="TestExponential", **kwargs) + Exponential(display_name='TestExponential', **kwargs) @pytest.mark.parametrize( - "kwargs, expected_message", + 'kwargs, expected_message', [ ( - {"amplitude": np.nan, "center": 0.5, "rate": 1.0, "unit": "meV"}, - "amplitude must be a finite number or a Parameter", + {'amplitude': np.nan, 'center': 0.5, 'rate': 1.0, 'unit': 'meV'}, + 'amplitude must be a finite number or a Parameter', ), ( - {"amplitude": 2.0, "center": 0.5, "rate": np.nan, "unit": "meV"}, - "rate must be a finite number or a Parameter", + {'amplitude': 2.0, 'center': 0.5, 'rate': np.nan, 'unit': 'meV'}, + 'rate must be a finite number or a Parameter', ), ], ) def test_input_value_validation_raises(self, kwargs, expected_message): with pytest.raises(ValueError, match=expected_message): - Exponential(display_name="TestExponential", **kwargs) + Exponential(display_name='TestExponential', **kwargs) @pytest.mark.parametrize( - "prop, valid_value, invalid_value, invalid_message", + 'prop, valid_value, invalid_value, invalid_message', [ - ("amplitude", 3.0, "invalid", r"must be a number"), - ("center", 0.7, "invalid", r"must be a number"), - ("rate", 1.5, "invalid", r"must be a number"), + ('amplitude', 3.0, 'invalid', r'must be a number'), + ('center', 0.7, 'invalid', r'must be a number'), + ('rate', 1.5, 'invalid', r'must be a number'), ], ) def test_property_setters( @@ -135,9 +135,9 @@ def test_get_all_parameters(self, exponential: Exponential): assert all(isinstance(param, Parameter) for param in params) expected_names = { - "ExponentialName amplitude", - "ExponentialName center", - "ExponentialName rate", + 'ExponentialName amplitude', + 'ExponentialName center', + 'ExponentialName rate', } actual_names = {param.name for param in params} @@ -146,39 +146,39 @@ def test_get_all_parameters(self, exponential: Exponential): def test_convert_unit(self, exponential: Exponential): # WHEN - exponential.convert_unit("microeV") + exponential.convert_unit('microeV') # THEN EXPECT - assert exponential.unit == "microeV" + assert exponential.unit == 'microeV' assert exponential.amplitude.value == pytest.approx(2.0 * 1e3) assert exponential.center.value == pytest.approx(0.5 * 1e3) # rate should scale inversely assert exponential.rate.value == pytest.approx(1.2 / 1e3) - assert str(exponential.rate.unit) == "1/ueV" + assert str(exponential.rate.unit) == '1/ueV' def test_convert_unit_incorrect_unit_raises(self, exponential: Exponential): # WHEN THEN EXPECT - with pytest.raises(TypeError, match=r"unit must be a string or sc.Unit"): + with pytest.raises(TypeError, match=r'unit must be a string or sc.Unit'): exponential.convert_unit(123) def test_convert_unit_rollback(self, exponential: Exponential): # WHEN with pytest.raises( UnitError, - match=r"Failed to convert unit: Conversion from `meV` to `m` is not valid.", + match=r'Failed to convert unit: Conversion from `meV` to `m` is not valid.', ): - exponential.convert_unit("m") + exponential.convert_unit('m') # THEN EXPECT - values should be unchanged - assert exponential.unit == "meV" + assert exponential.unit == 'meV' assert exponential.amplitude.value == pytest.approx(2.0) - assert exponential.amplitude.unit == "meV" + assert exponential.amplitude.unit == 'meV' assert exponential.center.value == pytest.approx(0.5) - assert exponential.center.unit == "meV" + assert exponential.center.unit == 'meV' assert exponential.rate.value == pytest.approx(1.2) - assert exponential.rate.unit == "1/meV" + assert exponential.rate.unit == '1/meV' def test_copy(self, exponential: Exponential): # WHEN @@ -204,9 +204,9 @@ def test_repr(self, exponential: Exponential): repr_str = repr(exponential) # THEN EXPECT - assert "Exponential" in repr_str - assert "name = ExponentialName" in repr_str - assert "unit = meV" in repr_str - assert "amplitude =" in repr_str - assert "center =" in repr_str - assert "rate =" in repr_str + assert 'Exponential' in repr_str + assert 'name = ExponentialName' in repr_str + assert 'unit = meV' in repr_str + assert 'amplitude =' in repr_str + assert 'center =' in repr_str + assert 'rate =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_gaussian.py b/tests/unit/easydynamics/sample_model/components/test_gaussian.py index 91b895ae..f5acf10c 100644 --- a/tests/unit/easydynamics/sample_model/components/test_gaussian.py +++ b/tests/unit/easydynamics/sample_model/components/test_gaussian.py @@ -15,12 +15,12 @@ class TestGaussian: @pytest.fixture def gaussian(self): return Gaussian( - name="GaussianName", - display_name="TestGaussian", + name='GaussianName', + display_name='TestGaussian', area=2.0, center=0.5, width=0.6, - unit="meV", + unit='meV', ) def test_init_no_inputs(self): @@ -28,82 +28,82 @@ def test_init_no_inputs(self): gaussian = Gaussian() # EXPECT - assert gaussian.display_name == "Gaussian" + assert gaussian.display_name == 'Gaussian' assert gaussian.area.value == pytest.approx(1.0) assert gaussian.center.value == pytest.approx(0.0) assert gaussian.width.value == pytest.approx(1.0) - assert gaussian.unit == "meV" + assert gaussian.unit == 'meV' assert gaussian.center.fixed is True def test_initialization(self, gaussian: Gaussian): # WHEN THEN EXPECT - assert gaussian.display_name == "TestGaussian" + assert gaussian.display_name == 'TestGaussian' assert gaussian.area.value == pytest.approx(2.0) assert gaussian.center.value == pytest.approx(0.5) assert gaussian.width.value == pytest.approx(0.6) - assert gaussian.unit == "meV" + assert gaussian.unit == 'meV' @pytest.mark.parametrize( - "kwargs, expected_message", + 'kwargs, expected_message', [ ( - {"area": "invalid", "center": 0.5, "width": 0.6, "unit": "meV"}, - "area must be a number", + {'area': 'invalid', 'center': 0.5, 'width': 0.6, 'unit': 'meV'}, + 'area must be a number', ), ( - {"area": 2.0, "center": "invalid", "width": 0.6, "unit": "meV"}, - "center must be None or a number", + {'area': 2.0, 'center': 'invalid', 'width': 0.6, 'unit': 'meV'}, + 'center must be None or a number', ), ( - {"area": 2.0, "center": 0.5, "width": "invalid", "unit": "meV"}, - "width must be a number", + {'area': 2.0, 'center': 0.5, 'width': 'invalid', 'unit': 'meV'}, + 'width must be a number', ), ( - {"area": 2.0, "center": 0.5, "width": 0.6, "unit": 123}, - "unit must be None", + {'area': 2.0, 'center': 0.5, 'width': 0.6, 'unit': 123}, + 'unit must be None', ), ], ids=[ - "invalid area", - "invalid center", - "invalid width", - "invalid unit", + 'invalid area', + 'invalid center', + 'invalid width', + 'invalid unit', ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Gaussian(display_name="TestGaussian", **kwargs) + Gaussian(display_name='TestGaussian', **kwargs) def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match=r"The width of a Gaussian must be greater than zero." + ValueError, match=r'The width of a Gaussian must be greater than zero.' ): Gaussian( - display_name="TestGaussian", + display_name='TestGaussian', area=2.0, center=0.5, width=-0.6, - unit="meV", + unit='meV', ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match="may not be physically meaningful"): + with pytest.warns(UserWarning, match='may not be physically meaningful'): Gaussian( - display_name="TestGaussian", + display_name='TestGaussian', area=-2.0, center=0.5, width=0.6, - unit="meV", + unit='meV', ) @pytest.mark.parametrize( - "prop, valid_value, invalid_value, invalid_message", + 'prop, valid_value, invalid_value, invalid_message', [ - ("area", 3.0, "invalid", r"must be a number"), - ("center", 0.6, "invalid", r"must be a number"), - ("width", 0.7, "invalid", r"must be a number"), + ('area', 3.0, 'invalid', r'must be a number'), + ('center', 0.6, 'invalid', r'must be a number'), + ('width', 0.7, 'invalid', r'must be a number'), ], ) def test_property_setters( @@ -119,7 +119,7 @@ def test_property_setters( def test_width_must_be_positive(self, gaussian: Gaussian): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="width must be positive"): + with pytest.raises(ValueError, match='width must be positive'): gaussian.width = -0.5 def test_evaluate(self, gaussian: Gaussian): @@ -155,9 +155,9 @@ def test_get_all_parameters(self, gaussian: Gaussian): assert all(isinstance(param, Parameter) for param in params) expected_names = { - "GaussianName area", - "GaussianName center", - "GaussianName width", + 'GaussianName area', + 'GaussianName center', + 'GaussianName width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -179,10 +179,10 @@ def test_area_matches_parameter(self, gaussian: Gaussian): def test_convert_unit(self, gaussian: Gaussian): # WHEN THEN - gaussian.convert_unit("microeV") + gaussian.convert_unit('microeV') # EXPECT - assert gaussian.unit == "microeV" + assert gaussian.unit == 'microeV' assert gaussian.area.value == pytest.approx(2 * 1e3) assert gaussian.center.value == pytest.approx(0.5 * 1e3) assert gaussian.width.value == pytest.approx(0.6 * 1e3) @@ -222,9 +222,9 @@ def test_repr(self, gaussian: Gaussian): # WHEN THEN repr_str = repr(gaussian) # EXPECT - assert "Gaussian" in repr_str - assert "name = GaussianName" in repr_str - assert "unit = meV" in repr_str - assert "area =" in repr_str - assert "center =" in repr_str - assert "width =" in repr_str + assert 'Gaussian' in repr_str + assert 'name = GaussianName' in repr_str + assert 'unit = meV' in repr_str + assert 'area =' in repr_str + assert 'center =' in repr_str + assert 'width =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py index 2444368b..6056bab9 100644 --- a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py +++ b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py @@ -15,12 +15,12 @@ class TestLorentzian: @pytest.fixture def lorentzian(self): return Lorentzian( - name="LorentzianName", - display_name="TestLorentzian", + name='LorentzianName', + display_name='TestLorentzian', area=2.0, center=0.5, width=0.6, - unit="meV", + unit='meV', ) def test_init_no_inputs(self): @@ -28,76 +28,76 @@ def test_init_no_inputs(self): lorentzian = Lorentzian() # EXPECT - assert lorentzian.display_name == "Lorentzian" + assert lorentzian.display_name == 'Lorentzian' assert lorentzian.area.value == pytest.approx(1.0) assert lorentzian.center.value == pytest.approx(0.0) assert lorentzian.width.value == pytest.approx(1.0) - assert lorentzian.unit == "meV" + assert lorentzian.unit == 'meV' assert lorentzian.center.fixed is True def test_initialization(self, lorentzian: Lorentzian): # WHEN THEN EXPECT - assert lorentzian.display_name == "TestLorentzian" + assert lorentzian.display_name == 'TestLorentzian' assert lorentzian.area.value == pytest.approx(2.0) assert lorentzian.center.value == pytest.approx(0.5) assert lorentzian.width.value == pytest.approx(0.6) - assert lorentzian.unit == "meV" + assert lorentzian.unit == 'meV' @pytest.mark.parametrize( - "kwargs, expected_message", + 'kwargs, expected_message', [ ( - {"area": "invalid", "center": 0.5, "width": 0.6, "unit": "meV"}, - "area must be a number", + {'area': 'invalid', 'center': 0.5, 'width': 0.6, 'unit': 'meV'}, + 'area must be a number', ), ( - {"area": 2.0, "center": "invalid", "width": 0.6, "unit": "meV"}, - "center must be None", + {'area': 2.0, 'center': 'invalid', 'width': 0.6, 'unit': 'meV'}, + 'center must be None', ), ( - {"area": 2.0, "center": 0.5, "width": "invalid", "unit": "meV"}, - "width must be a number", + {'area': 2.0, 'center': 0.5, 'width': 'invalid', 'unit': 'meV'}, + 'width must be a number', ), ( - {"area": 2.0, "center": 0.5, "width": 0.6, "unit": 123}, - "unit must be None", + {'area': 2.0, 'center': 0.5, 'width': 0.6, 'unit': 123}, + 'unit must be None', ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Lorentzian(display_name="TestLorentzian", **kwargs) + Lorentzian(display_name='TestLorentzian', **kwargs) def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match=r"The width of a Lorentzian must be greater than zero." + ValueError, match=r'The width of a Lorentzian must be greater than zero.' ): Lorentzian( - display_name="TestLorentzian", + display_name='TestLorentzian', area=2.0, center=0.5, width=-0.6, - unit="meV", + unit='meV', ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match="may not be physically meaningful"): + with pytest.warns(UserWarning, match='may not be physically meaningful'): Lorentzian( - display_name="TestLorentzian", + display_name='TestLorentzian', area=-2.0, center=0.5, width=0.6, - unit="meV", + unit='meV', ) @pytest.mark.parametrize( - "prop, valid_value, invalid_value, invalid_message", + 'prop, valid_value, invalid_value, invalid_message', [ - ("area", 3.0, "invalid", r" must be a number"), - ("center", 0.6, "invalid", r" must be a number"), - ("width", 0.7, "invalid", r" must be a number"), + ('area', 3.0, 'invalid', r' must be a number'), + ('center', 0.6, 'invalid', r' must be a number'), + ('width', 0.7, 'invalid', r' must be a number'), ], ) def test_property_setters( @@ -113,7 +113,7 @@ def test_property_setters( def test_width_must_be_positive(self, lorentzian: Lorentzian): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="width must be positive"): + with pytest.raises(ValueError, match='width must be positive'): lorentzian.width = -0.5 def test_evaluate(self, lorentzian: Lorentzian): @@ -146,9 +146,9 @@ def test_get_all_parameters(self, lorentzian: Lorentzian): assert len(params) == 3 assert all(isinstance(param, Parameter) for param in params) expected_names = { - "LorentzianName area", - "LorentzianName center", - "LorentzianName width", + 'LorentzianName area', + 'LorentzianName center', + 'LorentzianName width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -168,10 +168,10 @@ def test_area_matches_parameter(self, lorentzian: Lorentzian): def test_convert_unit(self, lorentzian: Lorentzian): # WHEN THEN - lorentzian.convert_unit("microeV") + lorentzian.convert_unit('microeV') # EXPECT - assert lorentzian.unit == "microeV" + assert lorentzian.unit == 'microeV' assert lorentzian.area.value == pytest.approx(2 * 1e3) assert lorentzian.center.value == pytest.approx(0.5 * 1e3) assert lorentzian.width.value == pytest.approx(0.6 * 1e3) @@ -200,9 +200,9 @@ def test_repr(self, lorentzian: Lorentzian): repr_str = repr(lorentzian) # EXPECT - assert "Lorentzian" in repr_str - assert "name = LorentzianName" in repr_str - assert "unit = meV" in repr_str - assert "area =" in repr_str - assert "center =" in repr_str - assert "width =" in repr_str + assert 'Lorentzian' in repr_str + assert 'name = LorentzianName' in repr_str + assert 'unit = meV' in repr_str + assert 'area =' in repr_str + assert 'center =' in repr_str + assert 'width =' in repr_str diff --git a/tests/unit/easydynamics/sample_model/components/test_mixins.py b/tests/unit/easydynamics/sample_model/components/test_mixins.py index 4ef4f18d..e30f4bdd 100644 --- a/tests/unit/easydynamics/sample_model/components/test_mixins.py +++ b/tests/unit/easydynamics/sample_model/components/test_mixins.py @@ -14,17 +14,15 @@ def dummy_model(self): return CreateParametersMixin() # ------------- Area---------------------- - @pytest.mark.parametrize("unit", ["meV", "eV"]) - @pytest.mark.parametrize("area_input", [2, 2.0]) + @pytest.mark.parametrize('unit', ['meV', 'eV']) + @pytest.mark.parametrize('area_input', [2, 2.0]) def test_create_area_parameter_from_numeric(self, dummy_model, area_input, unit): # WHEN THEN - area_param = dummy_model._create_area_parameter( - area_input, "TestModel", unit=unit - ) + area_param = dummy_model._create_area_parameter(area_input, 'TestModel', unit=unit) # EXPECT assert isinstance(area_param, Parameter) - assert area_param.name == "TestModel area" + assert area_param.name == 'TestModel area' assert area_param.value == pytest.approx(area_input) assert area_param.unit == unit assert not area_param.fixed @@ -32,122 +30,108 @@ def test_create_area_parameter_from_numeric(self, dummy_model, area_input, unit) def test_create_area_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match="area must be a number"): - dummy_model._create_area_parameter("invalid", "TestModel") + with pytest.raises(TypeError, match='area must be a number'): + dummy_model._create_area_parameter('invalid', 'TestModel') @pytest.mark.parametrize( - "non_finite_area", + 'non_finite_area', [ np.nan, np.inf, -np.inf, ], ) - def test_create_area_parameter_invalid_numeric_raises( - self, dummy_model, non_finite_area - ): + def test_create_area_parameter_invalid_numeric_raises(self, dummy_model, non_finite_area): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="area must be a finite number"): - dummy_model._create_area_parameter(non_finite_area, "TestModel") + with pytest.raises(ValueError, match='area must be a finite number'): + dummy_model._create_area_parameter(non_finite_area, 'TestModel') def test_negative_area_warns(self, dummy_model): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match="may not be physically meaningful"): - area_param = dummy_model._create_area_parameter(-2.0, "TestModel") + with pytest.warns(UserWarning, match='may not be physically meaningful'): + area_param = dummy_model._create_area_parameter(-2.0, 'TestModel') - assert area_param.min == -float("inf") # No min constraint for negative area + assert area_param.min == -float('inf') # No min constraint for negative area # ------------- Center---------------------- - @pytest.mark.parametrize("unit", ["meV", "eV"]) - @pytest.mark.parametrize("center_input", [0, 0.0]) - def test_create_center_parameter_from_numeric( - self, dummy_model, center_input, unit - ): + @pytest.mark.parametrize('unit', ['meV', 'eV']) + @pytest.mark.parametrize('center_input', [0, 0.0]) + def test_create_center_parameter_from_numeric(self, dummy_model, center_input, unit): # WHEN THEN center_param = dummy_model._create_center_parameter( - center_input, "TestModel", fix_if_none=False, unit=unit + center_input, 'TestModel', fix_if_none=False, unit=unit ) # EXPECT assert isinstance(center_param, Parameter) - assert center_param.name == "TestModel center" + assert center_param.name == 'TestModel center' assert center_param.value == pytest.approx(center_input) assert center_param.unit == unit assert not center_param.fixed - @pytest.mark.parametrize("fix_if_none", [True, False]) + @pytest.mark.parametrize('fix_if_none', [True, False]) def test_create_center_parameter_from_None(self, dummy_model, fix_if_none): # WHEN THEN center_param = dummy_model._create_center_parameter( - None, "TestModel", fix_if_none=fix_if_none + None, 'TestModel', fix_if_none=fix_if_none ) # EXPECT assert isinstance(center_param, Parameter) - assert center_param.name == "TestModel center" + assert center_param.name == 'TestModel center' assert center_param.value == pytest.approx(0.0) - assert center_param.unit == "meV" + assert center_param.unit == 'meV' assert center_param.fixed == fix_if_none def test_create_center_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match="center must be None or a number"): - dummy_model._create_center_parameter( - "invalid", "TestModel", fix_if_none=False - ) + with pytest.raises(TypeError, match='center must be None or a number'): + dummy_model._create_center_parameter('invalid', 'TestModel', fix_if_none=False) @pytest.mark.parametrize( - "non_finite_center", + 'non_finite_center', [ np.nan, np.inf, -np.inf, ], ) - def test_create_center_parameter_invalid_numeric_raises( - self, dummy_model, non_finite_center - ): + def test_create_center_parameter_invalid_numeric_raises(self, dummy_model, non_finite_center): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="center must be None or a finite number"): - dummy_model._create_center_parameter( - non_finite_center, "TestModel", fix_if_none=False - ) + with pytest.raises(ValueError, match='center must be None or a finite number'): + dummy_model._create_center_parameter(non_finite_center, 'TestModel', fix_if_none=False) - @pytest.mark.parametrize("unit", ["meV", "eV"]) - @pytest.mark.parametrize("width_input", [2, 2.0]) + @pytest.mark.parametrize('unit', ['meV', 'eV']) + @pytest.mark.parametrize('width_input', [2, 2.0]) def test_create_width_parameter_from_numeric(self, dummy_model, width_input, unit): # WHEN THEN width_param = dummy_model._create_width_parameter( - width_input, "TestModel", param_name="width", unit=unit + width_input, 'TestModel', param_name='width', unit=unit ) # EXPECT assert isinstance(width_param, Parameter) - assert width_param.name == "TestModel width" + assert width_param.name == 'TestModel width' assert width_param.value == pytest.approx(width_input) assert width_param.unit == unit assert not width_param.fixed def test_create_width_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match="width must be a number"): - dummy_model._create_width_parameter( - "invalid", "TestModel", param_name="width" - ) + with pytest.raises(TypeError, match='width must be a number'): + dummy_model._create_width_parameter('invalid', 'TestModel', param_name='width') def test_create_width_parameter_negative_raises(self, dummy_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match=" must be greater than zero"): - dummy_model._create_width_parameter(-1, "TestModel", param_name="width") + with pytest.raises(ValueError, match=' must be greater than zero'): + dummy_model._create_width_parameter(-1, 'TestModel', param_name='width') @pytest.mark.parametrize( - "non_finite_center", + 'non_finite_center', [ np.nan, np.inf, -np.inf, ], ) - def test_create_width_parameter_invalid_numeric_raises( - self, dummy_model, non_finite_center - ): + def test_create_width_parameter_invalid_numeric_raises(self, dummy_model, non_finite_center): # WHEN THEN EXPECT - with pytest.raises(ValueError, match="width must be a finite number"): - dummy_model._create_width_parameter(non_finite_center, "TestModel") + with pytest.raises(ValueError, match='width must be a finite number'): + dummy_model._create_width_parameter(non_finite_center, 'TestModel') diff --git a/tests/unit/easydynamics/sample_model/components/test_voigt.py b/tests/unit/easydynamics/sample_model/components/test_voigt.py index 2164f8e8..5f42704d 100644 --- a/tests/unit/easydynamics/sample_model/components/test_voigt.py +++ b/tests/unit/easydynamics/sample_model/components/test_voigt.py @@ -16,13 +16,13 @@ class TestVoigt: @pytest.fixture def voigt(self): return Voigt( - name="VoigtName", - display_name="TestVoigt", + name='VoigtName', + display_name='TestVoigt', area=2.0, center=0.5, gaussian_width=0.6, lorentzian_width=0.7, - unit="meV", + unit='meV', ) def test_init_no_inputs(self): @@ -30,134 +30,134 @@ def test_init_no_inputs(self): voigt = Voigt() # EXPECT - assert voigt.display_name == "Voigt" + assert voigt.display_name == 'Voigt' assert voigt.area.value == pytest.approx(1.0) assert voigt.center.value == pytest.approx(0.0) assert voigt.gaussian_width.value == pytest.approx(1.0) assert voigt.lorentzian_width.value == pytest.approx(1.0) - assert voigt.unit == "meV" + assert voigt.unit == 'meV' assert voigt.center.fixed is True def test_initialization(self, voigt: Voigt): # WHEN THEN EXPECT - assert voigt.display_name == "TestVoigt" + assert voigt.display_name == 'TestVoigt' assert voigt.area.value == pytest.approx(2.0) assert voigt.center.value == pytest.approx(0.5) assert voigt.gaussian_width.value == pytest.approx(0.6) assert voigt.lorentzian_width.value == pytest.approx(0.7) - assert voigt.unit == "meV" + assert voigt.unit == 'meV' @pytest.mark.parametrize( - "kwargs, expected_message", + 'kwargs, expected_message', [ ( { - "area": "invalid", - "center": 0.5, - "gaussian_width": 0.6, - "lorentzian_width": 0.7, - "unit": "meV", + 'area': 'invalid', + 'center': 0.5, + 'gaussian_width': 0.6, + 'lorentzian_width': 0.7, + 'unit': 'meV', }, - "area must be a number", + 'area must be a number', ), ( { - "area": 2.0, - "center": "invalid", - "gaussian_width": 0.6, - "lorentzian_width": 0.7, - "unit": "meV", + 'area': 2.0, + 'center': 'invalid', + 'gaussian_width': 0.6, + 'lorentzian_width': 0.7, + 'unit': 'meV', }, - "center must be None", + 'center must be None', ), ( { - "area": 2.0, - "center": 0.5, - "gaussian_width": "invalid", - "lorentzian_width": 0.7, - "unit": "meV", + 'area': 2.0, + 'center': 0.5, + 'gaussian_width': 'invalid', + 'lorentzian_width': 0.7, + 'unit': 'meV', }, - "gaussian_width must be a number", + 'gaussian_width must be a number', ), ( { - "area": 2.0, - "center": 0.5, - "gaussian_width": 0.6, - "lorentzian_width": "invalid", - "unit": "meV", + 'area': 2.0, + 'center': 0.5, + 'gaussian_width': 0.6, + 'lorentzian_width': 'invalid', + 'unit': 'meV', }, - "lorentzian_width must be a number", + 'lorentzian_width must be a number', ), ( { - "area": 2.0, - "center": 0.5, - "gaussian_width": 0.6, - "lorentzian_width": 0.7, - "unit": 123, + 'area': 2.0, + 'center': 0.5, + 'gaussian_width': 0.6, + 'lorentzian_width': 0.7, + 'unit': 123, }, - "unit must be None,", + 'unit must be None,', ), ], ) def test_input_type_validation_raises(self, kwargs, expected_message): with pytest.raises(TypeError, match=expected_message): - Voigt(display_name="TestVoigt", **kwargs) + Voigt(display_name='TestVoigt', **kwargs) def test_negative_gaussian_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match=r"The gaussian_width of a Voigt must be greater than." + ValueError, match=r'The gaussian_width of a Voigt must be greater than.' ): Voigt( - display_name="TestVoigt", + display_name='TestVoigt', area=2.0, center=0.5, gaussian_width=-0.6, lorentzian_width=0.7, - unit="meV", + unit='meV', ) def test_negative_lorentzian_width_raises(self): # WHEN THEN EXPECT with pytest.raises( ValueError, - match=r"The lorentzian_width of a Voigt must be greater than zero.", + match=r'The lorentzian_width of a Voigt must be greater than zero.', ): Voigt( - display_name="TestVoigt", + display_name='TestVoigt', area=2.0, center=0.5, gaussian_width=0.6, lorentzian_width=-0.7, - unit="meV", + unit='meV', ) def test_negative_area_warns(self): # WHEN THEN EXPECT - with pytest.warns(UserWarning, match="may not be physically meaningful"): + with pytest.warns(UserWarning, match='may not be physically meaningful'): Voigt( - display_name="TestVoigt", + display_name='TestVoigt', area=-2.0, center=0.5, gaussian_width=0.6, lorentzian_width=0.7, - unit="meV", + unit='meV', ) @pytest.mark.parametrize( - "prop, valid_value, invalid_value, invalid_message", + 'prop, valid_value, invalid_value, invalid_message', [ - ("area", 3.0, "invalid", r"must be a number"), - ("center", 0.6, "invalid", r"must be a number"), - ("gaussian_width", 0.7, "invalid", r"must be a number"), + ('area', 3.0, 'invalid', r'must be a number'), + ('center', 0.6, 'invalid', r'must be a number'), + ('gaussian_width', 0.7, 'invalid', r'must be a number'), ( - "lorentzian_width", + 'lorentzian_width', 0.7, - "invalid", - r"must be a number", + 'invalid', + r'must be a number', ), ], ) @@ -174,14 +174,14 @@ def test_property_setters( def test_gaussian_width_must_be_positive(self, voigt: Voigt): # WHEN THEN - with pytest.raises(ValueError, match="gaussian_width must be positive"): + with pytest.raises(ValueError, match='gaussian_width must be positive'): voigt.gaussian_width = -0.6 def test_lorentzian_width_must_be_positive(self, voigt: Voigt): # WHEN THEN with pytest.raises( ValueError, - match="lorentzian_width must be positive", + match='lorentzian_width must be positive', ): voigt.lorentzian_width = -0.7 @@ -214,7 +214,7 @@ def test_center_is_fixed_if_init_to_None(self): center=None, gaussian_width=0.6, lorentzian_width=0.7, - unit="meV", + unit='meV', ) # EXPECT @@ -223,10 +223,10 @@ def test_center_is_fixed_if_init_to_None(self): def test_convert_unit(self, voigt: Voigt): # WHEN THEN - voigt.convert_unit("microeV") + voigt.convert_unit('microeV') # EXPECT - assert voigt.unit == "microeV" + assert voigt.unit == 'microeV' assert voigt.area.value == pytest.approx(2 * 1e3) assert voigt.center.value == pytest.approx(0.5 * 1e3) assert voigt.gaussian_width.value == pytest.approx(0.6 * 1e3) @@ -240,10 +240,10 @@ def test_get_all_parameters(self, voigt: Voigt): assert len(params) == 4 assert all(isinstance(param, Parameter) for param in params) expected_names = { - "VoigtName area", - "VoigtName center", - "VoigtName gaussian_width", - "VoigtName lorentzian_width", + 'VoigtName area', + 'VoigtName center', + 'VoigtName gaussian_width', + 'VoigtName lorentzian_width', } actual_names = {param.name for param in params} assert actual_names == expected_names @@ -292,10 +292,10 @@ def test_repr(self, voigt: Voigt): repr_str = repr(voigt) # EXPECT - assert "Voigt" in repr_str - assert "name = VoigtName" in repr_str - assert "unit = meV" in repr_str - assert "area =" in repr_str - assert "center =" in repr_str - assert "gaussian_width =" in repr_str - assert "lorentzian_width =" in repr_str + assert 'Voigt' in repr_str + assert 'name = VoigtName' in repr_str + assert 'unit = meV' in repr_str + assert 'area =' in repr_str + assert 'center =' in repr_str + assert 'gaussian_width =' in repr_str + assert 'lorentzian_width =' in repr_str From f9659857659cc4bf8899ed1155c416b78f98233d Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Tue, 2 Jun 2026 23:06:47 +0200 Subject: [PATCH 3/4] PR comments --- .../components/damped_harmonic_oscillator.py | 4 +- .../sample_model/components/delta_function.py | 4 +- .../sample_model/components/exponential.py | 4 +- .../sample_model/components/lorentzian.py | 4 +- .../sample_model/components/mixins.py | 65 ++++++++----------- .../diffusion_model/delta_lorentz.py | 8 +-- 6 files changed, 40 insertions(+), 49 deletions(-) diff --git a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py index f0048c7e..e7ea9561 100644 --- a/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py +++ b/src/easydynamics/sample_model/components/damped_harmonic_oscillator.py @@ -46,9 +46,9 @@ def __init__( width : Numeric, default=1.0 Damping constant, approximately the half width at half max (HWHM) of the peaks. By default, 1.0. - unit : str | sc.Unit, default="meV" + unit : str | sc.Unit, default='meV' Unit of the parameters. - name : str, default="DampedHarmonicOscillator" + name : str, default='DampedHarmonicOscillator' Name of the component for indexing. display_name : str | None, default=None Display name of the component. diff --git a/src/easydynamics/sample_model/components/delta_function.py b/src/easydynamics/sample_model/components/delta_function.py index e8fd6728..c10e6185 100644 --- a/src/easydynamics/sample_model/components/delta_function.py +++ b/src/easydynamics/sample_model/components/delta_function.py @@ -46,9 +46,9 @@ def __init__( Center of the delta function. If None, it will be centered at 0 and fixed. area : Numeric, default=1.0 Total area under the curve. - unit : str | sc.Unit, default="meV" + unit : str | sc.Unit, default='meV' Unit of the parameters. - name : str, default="DeltaFunction" + name : str, default='DeltaFunction' Name of the component for indexing. display_name : str | None, default=None Display name of the component. diff --git a/src/easydynamics/sample_model/components/exponential.py b/src/easydynamics/sample_model/components/exponential.py index 336f67e1..8894a065 100644 --- a/src/easydynamics/sample_model/components/exponential.py +++ b/src/easydynamics/sample_model/components/exponential.py @@ -44,9 +44,9 @@ def __init__( Center of the Exponential. If None, the center is fixed at 0. rate : Numeric, default=1.0 Decay or growth constant of the Exponential. - unit : str | sc.Unit, default="meV" + unit : str | sc.Unit, default='meV' Unit of the parameters. - name : str, default="Exponential" + name : str, default='Exponential' Name of the component for indexing. display_name : str | None, default=None Display name of the component. diff --git a/src/easydynamics/sample_model/components/lorentzian.py b/src/easydynamics/sample_model/components/lorentzian.py index 1232d0b1..88051d16 100644 --- a/src/easydynamics/sample_model/components/lorentzian.py +++ b/src/easydynamics/sample_model/components/lorentzian.py @@ -49,9 +49,9 @@ def __init__( Center of the Lorentzian. If None, defaults to 0 and is fixed. width : Numeric, default=1.0 Half width at half maximum (HWHM). - unit : str | sc.Unit, default="meV" + unit : str | sc.Unit, default='meV' Unit of the parameters. - name : str, default="Lorentzian" + name : str, default='Lorentzian' Name of the component for indexing. display_name : str | None, default=None Display name for the component. diff --git a/src/easydynamics/sample_model/components/mixins.py b/src/easydynamics/sample_model/components/mixins.py index d9163784..f424207c 100644 --- a/src/easydynamics/sample_model/components/mixins.py +++ b/src/easydynamics/sample_model/components/mixins.py @@ -61,22 +61,20 @@ def _create_area_parameter( if not isinstance(area, Numeric): raise TypeError('area must be a number.') - if isinstance(area, Numeric): - if not np.isfinite(area): - raise ValueError('area must be a finite number.') - area = Parameter(name=name + ' area', value=float(area), unit=unit) + if not np.isfinite(area): + raise ValueError('area must be a finite number.') + area_param = Parameter(name=name + ' area', value=float(area), unit=unit) - if area.value < 0: + if area_param.value < 0: warnings.warn( f'The area of {name} is negative, which may not be physically meaningful.', UserWarning, stacklevel=3, ) else: - if area.min < minimum_area: - area.min = minimum_area + area_param.min = minimum_area - return area + return area_param def _create_center_parameter( self, @@ -109,7 +107,6 @@ def _create_center_parameter( ValueError If center is a number but not finite. - Returns ------- Parameter @@ -119,20 +116,20 @@ def _create_center_parameter( raise TypeError('center must be None or a number.') if center is None: - center = Parameter( + center_param = Parameter( name=name + ' center', value=0.0, unit=unit, fixed=fix_if_none, ) - elif isinstance(center, Numeric): + else: if not np.isfinite(center): raise ValueError('center must be None or a finite number.') - center = Parameter(name=name + ' center', value=float(center), unit=unit) - if enforce_minimum_center and center.min < DHO_MINIMUM_CENTER: - center.min = DHO_MINIMUM_CENTER - return center + center_param = Parameter(name=name + ' center', value=float(center), unit=unit) + if enforce_minimum_center and center_param.min < DHO_MINIMUM_CENTER: + center_param.min = DHO_MINIMUM_CENTER + return center_param def _create_width_parameter( self, @@ -173,26 +170,20 @@ def _create_width_parameter( if not isinstance(width, Numeric): raise TypeError(f'{param_name} must be a number.') - if isinstance(width, Numeric): - if not np.isfinite(width): - raise ValueError(f'{param_name} must be a finite number') - - if float(width) < minimum_width: - raise ValueError( - f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' - ) - width = Parameter( - name=name + ' ' + param_name, - value=float(width), - unit=unit, - min=minimum_width, + if not np.isfinite(width): + raise ValueError(f'{param_name} must be a finite number') + + if float(width) < minimum_width: + raise ValueError( + f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' ) - else: - if width.value <= 0: - raise ValueError( - f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' - ) - if width.min < minimum_width: - width.min = minimum_width - - return width + width_param = Parameter( + name=name + ' ' + param_name, + value=float(width), + unit=unit, + min=minimum_width, + ) + if width_param.min < minimum_width: + width_param.min = minimum_width + + return width_param diff --git a/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py b/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py index 55c1cb52..150015a7 100644 --- a/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py +++ b/src/easydynamics/sample_model/diffusion_model/delta_lorentz.py @@ -88,19 +88,19 @@ def __init__( allowed. Q : Q_type | None, default=None Q values for the model. If None, Q is not set. - unit : str | sc.Unit, default="meV" + unit : str | sc.Unit, default='meV' Unit of the diffusion model. Must be convertible to meV. - name : str, default="DeltaLorentz" + name : str, default='DeltaLorentz' Name of the diffusion model. display_name : str | None, default=None Display name of the diffusion model. - lorentzian_name : str, default="Lorentzian" + lorentzian_name : str, default='Lorentzian' Name of the Lorentzian component. If None, it will be set to the name of the diffusion model. lorentzian_display_name : str | None, default=None Display name of the Lorentzian component. If None, it will be set to the display name of the diffusion model. - delta_name : str, default="Delta function" + delta_name : str, default='Delta function' Name of the delta function component. delta_display_name : str | None, default=None Display name of the delta function component. If None, it will be set to the display From b6c30e50ddf551f61597a3bccc07b9b235065740 Mon Sep 17 00:00:00 2001 From: henrikjacobsenfys Date: Wed, 3 Jun 2026 09:41:20 +0200 Subject: [PATCH 4/4] Minor fix --- src/easydynamics/sample_model/components/mixins.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/easydynamics/sample_model/components/mixins.py b/src/easydynamics/sample_model/components/mixins.py index f424207c..22ef7bc5 100644 --- a/src/easydynamics/sample_model/components/mixins.py +++ b/src/easydynamics/sample_model/components/mixins.py @@ -177,13 +177,9 @@ def _create_width_parameter( raise ValueError( f'The {param_name} of a {self.__class__.__name__} must be greater than zero.' ) - width_param = Parameter( + return Parameter( name=name + ' ' + param_name, value=float(width), unit=unit, min=minimum_width, ) - if width_param.min < minimum_width: - width_param.min = minimum_width - - return width_param