diff --git a/docs/docs/tutorials/analysis.ipynb b/docs/docs/tutorials/analysis.ipynb index 623fd942..0d30fd4b 100644 --- a/docs/docs/tutorials/analysis.ipynb +++ b/docs/docs/tutorials/analysis.ipynb @@ -213,10 +213,7 @@ " instrument_model=instrument_model,\n", ")\n", "\n", - "# We need to hack in the resolution model from the vanadium analysis,\n", - "# since the setters and getters overwrite the model. This will be fixed\n", - "# asap.\n", - "diffusion_analysis.instrument_model._resolution_model = (\n", + "diffusion_analysis.instrument_model.resolution_model = (\n", " vanadium_analysis.instrument_model.resolution_model\n", ")\n", "\n", @@ -305,10 +302,7 @@ " instrument_model=instrument_model,\n", ")\n", "\n", - "# We again need to hack in the resolution model from the vanadium\n", - "# analysis, since the setters and getters overwrite the model. This will\n", - "# be fixed asap.\n", - "diffusion_model_analysis.instrument_model._resolution_model = (\n", + "diffusion_model_analysis.instrument_model.resolution_model = (\n", " vanadium_analysis.instrument_model.resolution_model\n", ")\n", "diffusion_model_analysis.instrument_model.resolution_model.fix_all_parameters()\n", diff --git a/docs/docs/tutorials/tutorial2_nanoparticles.ipynb b/docs/docs/tutorials/tutorial2_nanoparticles.ipynb index e7308dbf..b79984df 100644 --- a/docs/docs/tutorials/tutorial2_nanoparticles.ipynb +++ b/docs/docs/tutorials/tutorial2_nanoparticles.ipynb @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "bca91d3c", "metadata": {}, "outputs": [], @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "4fbb90da", "metadata": {}, "outputs": [], @@ -78,7 +78,7 @@ "resolution_experiment = Experiment(display_name='Nanoparticles, 1.5 K')\n", "\n", "file_path = pooch.retrieve(\n", - " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/tutorial2/docs/docs/tutorials/data/nano_1p5K.h5',\n", + " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/nano_1p5K.h5',\n", " known_hash='32935cfaa5e623137ea5f4ba972d0c921bca8d0e067486666cdcf23abf27823e',\n", ")\n", "\n", @@ -216,7 +216,7 @@ "resolution_experiment = Experiment(display_name='Nanoparticles, 1.5 K')\n", "\n", "file_path = pooch.retrieve(\n", - " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/tutorial2/docs/docs/tutorials/data/nano_150K.h5',\n", + " url='https://github.com/easyscience/dynamics-lib/raw/refs/heads/master/docs/docs/tutorials/data/nano_150K.h5',\n", " known_hash='dc3abdaf6d2165f4dbc5df4e0de1890af8d4cd290f2c79ac7b319ccbe44117b8',\n", ")\n", "\n", diff --git a/pixi.lock b/pixi.lock index efafa5aa..5f682ecf 100644 --- a/pixi.lock +++ b/pixi.lock @@ -3888,8 +3888,8 @@ packages: requires_python: '>=3.5' - pypi: ./ name: easydynamics - version: 0.4.0+dev5 - sha256: ff8f55922804cdb622d0eb0aecd00105aeb30e470aa79b05bf6f92556ad8ce67 + version: 0.4.0+devdirty15 + sha256: 03e3e912335d4cddf2e70179da113fb35cb4cad133450b3a9e8583aaf5927e6e requires_dist: - darkdetect - easyscience diff --git a/pyproject.toml b/pyproject.toml index 17452cd7..36950e9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -202,11 +202,11 @@ select = [ #'FURB', # https://docs.astral.sh/ruff/rules/#refurb-furb 'I', # https://docs.astral.sh/ruff/rules/#isort-i #'N', # https://docs.astral.sh/ruff/rules/#pep8-naming-n - #'NPY', # https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy + 'NPY', # https://docs.astral.sh/ruff/rules/#numpy-specific-rules-npy #'PGH', # https://docs.astral.sh/ruff/rules/#pygrep-hooks-pgh - #'PERF', # https://docs.astral.sh/ruff/rules/#perflint-perf - #'RUF', # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf - #'TRY', # https://docs.astral.sh/ruff/rules/#tryceratops-try + 'PERF', # https://docs.astral.sh/ruff/rules/#perflint-perf + 'RUF', # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf + #'TRY', # https://docs.astral.sh/ruff/rules/#tryceratops-try # overly restrictive 'UP', # https://docs.astral.sh/ruff/rules/#pyupgrade-up # pycodestyle (E, W) rules 'E', # https://docs.astral.sh/ruff/rules/#error-e @@ -215,40 +215,40 @@ select = [ #'PLC', # https://docs.astral.sh/ruff/rules/#convention-plc #'PLE', # https://docs.astral.sh/ruff/rules/#error-ple #'PLR', # https://docs.astral.sh/ruff/rules/#refactor-plr - #'PLW', # https://docs.astral.sh/ruff/rules/#warning-plw + #'PLW', # https://docs.astral.sh/ruff/rules/#warning-plw # Good to enable # flake8 rules - 'A', # https://docs.astral.sh/ruff/rules/#flake8-builtins-a - 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann - 'ARG', # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg - #'ASYNC', # https://docs.astral.sh/ruff/rules/#flake8-async-async - 'B', # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b - #'BLE', # https://docs.astral.sh/ruff/rules/#flake8-blind-except-ble + 'A', # https://docs.astral.sh/ruff/rules/#flake8-builtins-a + 'ANN', # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann + 'ARG', # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + 'ASYNC', # https://docs.astral.sh/ruff/rules/#flake8-async-async + 'B', # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + #'BLE', # https://docs.astral.sh/ruff/rules/#flake8-blind-except-ble # enable when base classes have been merged in 'C4', # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4 'COM', # https://docs.astral.sh/ruff/rules/#flake8-commas-com - #'DTZ', # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz - #'EM', # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em + 'DTZ', # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz + #'EM', # https://docs.astral.sh/ruff/rules/#flake8-errmsg-em # Unsure if I want it - requires all error messages to be rewritten 'FA', # https://docs.astral.sh/ruff/rules/#flake8-future-annotations-fa - #'FBT', # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt - #'FIX', # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix - #'G', # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g - #'ICN', # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn - #'INP', # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp - #'ISC', # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc - #'LOG', # https://docs.astral.sh/ruff/rules/#flake8-logging-log - #'PIE', # https://docs.astral.sh/ruff/rules/#flake8-pie-pie - #'PT', # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt - 'PTH', # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth - #'PYI', # https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi - 'RET', # https://docs.astral.sh/ruff/rules/#flake8-return-ret - #'RSE', # https://docs.astral.sh/ruff/rules/#flake8-raise-rse - 'S', # https://docs.astral.sh/ruff/rules/#flake8-bandit-s - 'SIM', # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim - #'SLF', # https://docs.astral.sh/ruff/rules/#flake8-self-slf - #'SLOT', # https://docs.astral.sh/ruff/rules/#flake8-slots-slot - #'T20', # https://docs.astral.sh/ruff/rules/#flake8-print-t20 - 'TC', # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc - #'TD', # https://docs.astral.sh/ruff/rules/#flake8-todos-td - 'TID', # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid + #'FBT', # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt # should eventually be enabled, but may break serialization + 'FIX', # https://docs.astral.sh/ruff/rules/#flake8-fixme-fix + 'G', # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g + 'ICN', # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn + 'INP', # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp + 'ISC', # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + 'LOG', # https://docs.astral.sh/ruff/rules/#flake8-logging-log + 'PIE', # https://docs.astral.sh/ruff/rules/#flake8-pie-pie + #'PT', # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt # Should eventually be enabled, but quite a few errors to fix + 'PTH', # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth + 'PYI', # https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi + 'RET', # https://docs.astral.sh/ruff/rules/#flake8-return-ret + 'RSE', # https://docs.astral.sh/ruff/rules/#flake8-raise-rse + 'S', # https://docs.astral.sh/ruff/rules/#flake8-bandit-s + 'SIM', # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + 'SLF', # https://docs.astral.sh/ruff/rules/#flake8-self-slf + 'SLOT', # https://docs.astral.sh/ruff/rules/#flake8-slots-slot + 'T20', # https://docs.astral.sh/ruff/rules/#flake8-print-t20 + 'TC', # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc + 'TD', # https://docs.astral.sh/ruff/rules/#flake8-todos-td + 'TID', # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid ] # Exceptions to the linting rules @@ -256,8 +256,7 @@ select = [ # Ignore specific rules globally ignore = [ 'COM812', # https://docs.astral.sh/ruff/rules/missing-trailing-comma/ - # The following is replaced by 'D'/[tool.ruff.lint.pydocstyle] and [tool.pydoclint] - 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc + # The following is replaced by 'D'/[tool.ruff.lint.pydocstyle] and [tool.pydoclint] 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc # Disable, as [tool.format_docstring] split one-line docstrings into the canonical multi-line layout 'D200', # https://docs.astral.sh/ruff/rules/unnecessary-multiline-docstring/ ] @@ -273,6 +272,7 @@ ignore = [ 'DOC', # https://docs.astral.sh/ruff/rules/#pydoclint-doc 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ 'S101', # https://docs.astral.sh/ruff/rules/assert/ + 'SLF', # https://docs.astral.sh/ruff/rules/#flake8-self-slf # may want to eventually enable it, but accessing private methods is quite useful in tests ] 'docs/**' = [ 'INP001', # https://docs.astral.sh/ruff/rules/implicit-namespace-package/ diff --git a/src/easydynamics/analysis/analysis.py b/src/easydynamics/analysis/analysis.py index bb937e34..666edfd1 100644 --- a/src/easydynamics/analysis/analysis.py +++ b/src/easydynamics/analysis/analysis.py @@ -567,14 +567,18 @@ def _fit_all_Q_simultaneously(self) -> FitResults: ys = [] ws = [] - for analysis in self.analysis_list: - x, y, weight, _ = self.experiment._extract_x_y_weights_only_finite(analysis.Q_index) + for analysis1d in self.analysis_list: + x, y, weight, _ = self.experiment._extract_x_y_weights_only_finite( # noqa: SLF001 + analysis1d.Q_index + ) xs.append(x) ys.append(y) ws.append(weight) # Make sure the convolver is up to date for this Q index - analysis._convolver = analysis._create_convolver(energy=x) + analysis1d._convolver = analysis1d._create_convolver( # noqa: SLF001 + energy=x + ) mf = MultiFitter( fit_objects=self.analysis_list, @@ -655,10 +659,10 @@ def _create_components_dataset( energy = self.energy datasets = [ - analysis._create_components_dataset_single_Q( + analysis1d._create_components_dataset_single_Q( # noqa: SLF001 add_background=add_background, energy=energy ) - for analysis in self.analysis_list + for analysis1d in self.analysis_list ] return sc.concat(datasets, dim='Q') diff --git a/src/easydynamics/analysis/analysis1d.py b/src/easydynamics/analysis/analysis1d.py index 36cc7c8c..614cbebf 100644 --- a/src/easydynamics/analysis/analysis1d.py +++ b/src/easydynamics/analysis/analysis1d.py @@ -195,7 +195,7 @@ def fit(self) -> FitResults: fit_function=self.as_fit_function(), ) - x, y, weights, _ = self.experiment._extract_x_y_weights_only_finite( + x, y, weights, _ = self.experiment._extract_x_y_weights_only_finite( # noqa: SLF001 Q_index=self._require_Q_index() ) fit_result = fitter.fit(x=x, y=y, weights=weights) @@ -671,7 +671,6 @@ def _create_convolver( if resolution_components.is_empty: return None - # TODO: allow convolution options to be set. return Convolution( sample_components=sample_components, resolution_components=resolution_components, diff --git a/src/easydynamics/convolution/analytical_convolution.py b/src/easydynamics/convolution/analytical_convolution.py index a835f215..2fec57eb 100644 --- a/src/easydynamics/convolution/analytical_convolution.py +++ b/src/easydynamics/convolution/analytical_convolution.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from typing import ClassVar + import numpy as np import scipp as sc from easyscience.variable import Parameter @@ -26,7 +28,7 @@ class AnalyticalConvolution(ConvolutionBase): # Mapping of supported component type pairs to convolution methods. # Delta functions are handled separately. - _CONVOLUTIONS = { + _CONVOLUTIONS: ClassVar[dict[str, object]] = { ('Gaussian', 'Gaussian'): '_convolute_gaussian_gaussian', ('Gaussian', 'Lorentzian'): '_convolute_gaussian_lorentzian', ('Gaussian', 'Voigt'): '_convolute_gaussian_voigt', diff --git a/src/easydynamics/convolution/convolution.py b/src/easydynamics/convolution/convolution.py index 32d3fb96..c313d0cf 100644 --- a/src/easydynamics/convolution/convolution.py +++ b/src/easydynamics/convolution/convolution.py @@ -1,6 +1,8 @@ # SPDX-FileCopyrightText: 2026 EasyScience contributors # SPDX-License-Identifier: BSD-3-Clause +from typing import ClassVar + import numpy as np import scipp as sc from easyscience.variable import Parameter @@ -33,7 +35,7 @@ class Convolution(NumericalConvolutionBase): # When these attributes are changed, the convolution plan # needs to be rebuilt - _invalidate_plan_on_change = { + _invalidate_plan_on_change: ClassVar[dict[str, object]] = { 'energy', '_energy', '_energy_grid', @@ -231,11 +233,10 @@ def _build_convolution_plan(self) -> None: # If temperature is not set, check if all # resolution components can be convolved analytically with # this sample component - pair_is_analytic = [] - for resolution_component in self._resolution_components.components: - pair_is_analytic.append( - self._check_if_pair_is_analytic(sample_component, resolution_component) - ) + pair_is_analytic = [ + self._check_if_pair_is_analytic(sample_component, resolution_component) + for resolution_component in self._resolution_components.components + ] # If all resolution components can be convolved analytically # with this sample component, add it to analytical # sample model. If not, it goes to numerical sample model. diff --git a/src/easydynamics/convolution/convolution_base.py b/src/easydynamics/convolution/convolution_base.py index d328dbed..aa36c24d 100644 --- a/src/easydynamics/convolution/convolution_base.py +++ b/src/easydynamics/convolution/convolution_base.py @@ -222,7 +222,7 @@ def energy_unit(self, _unit_str: str) -> None: raise AttributeError( f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) def convert_energy_unit(self, energy_unit: str | sc.Unit) -> None: """ diff --git a/src/easydynamics/convolution/numerical_convolution_base.py b/src/easydynamics/convolution/numerical_convolution_base.py index e2d6f5b6..c5db7326 100644 --- a/src/easydynamics/convolution/numerical_convolution_base.py +++ b/src/easydynamics/convolution/numerical_convolution_base.py @@ -432,8 +432,8 @@ def __repr__(self) -> str: return ( f'{self.__class__.__name__}(' f'energy=array of shape {self.energy.values.shape},\n ' - f'sample_components={repr(self.sample_components)}, \n' - f'resolution_components={repr(self.resolution_components)},\n ' + f'sample_components={self.sample_components!r}, \n' + f'resolution_components={self.resolution_components!r},\n ' f'energy_unit={self._energy_unit}, ' f'upsample_factor={self.upsample_factor}, ' f'extension_factor={self.extension_factor}, ' diff --git a/src/easydynamics/sample_model/__init__.py b/src/easydynamics/sample_model/__init__.py index f52c44bd..6f75bafd 100644 --- a/src/easydynamics/sample_model/__init__.py +++ b/src/easydynamics/sample_model/__init__.py @@ -21,18 +21,18 @@ from easydynamics.sample_model.sample_model import SampleModel __all__ = [ + 'BackgroundModel', + 'BrownianTranslationalDiffusion', 'ComponentCollection', + 'DampedHarmonicOscillator', + 'DeltaFunction', + 'Exponential', + 'ExpressionComponent', 'Gaussian', + 'InstrumentModel', 'Lorentzian', - 'Voigt', - 'DeltaFunction', - 'DampedHarmonicOscillator', 'Polynomial', - 'Exponential', - 'BrownianTranslationalDiffusion', - 'SampleModel', 'ResolutionModel', - 'BackgroundModel', - 'InstrumentModel', - 'ExpressionComponent', + 'SampleModel', + 'Voigt', ] diff --git a/src/easydynamics/sample_model/component_collection.py b/src/easydynamics/sample_model/component_collection.py index fd5b5ebd..f5f1a45a 100644 --- a/src/easydynamics/sample_model/component_collection.py +++ b/src/easydynamics/sample_model/component_collection.py @@ -173,7 +173,7 @@ def unit(self, _unit_str: str) -> None: raise AttributeError( f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) def convert_unit(self, unit: str | sc.Unit) -> None: """ diff --git a/src/easydynamics/sample_model/components/__init__.py b/src/easydynamics/sample_model/components/__init__.py index 747dc594..940f41a7 100644 --- a/src/easydynamics/sample_model/components/__init__.py +++ b/src/easydynamics/sample_model/components/__init__.py @@ -13,12 +13,12 @@ from easydynamics.sample_model.components.voigt import Voigt __all__ = [ - 'Gaussian', - 'Lorentzian', - 'Voigt', - 'DeltaFunction', 'DampedHarmonicOscillator', - 'Polynomial', + 'DeltaFunction', 'Exponential', 'ExpressionComponent', + 'Gaussian', + 'Lorentzian', + 'Polynomial', + 'Voigt', ] diff --git a/src/easydynamics/sample_model/components/expression_component.py b/src/easydynamics/sample_model/components/expression_component.py index d87c0e07..79ce73ef 100644 --- a/src/easydynamics/sample_model/components/expression_component.py +++ b/src/easydynamics/sample_model/components/expression_component.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing import ClassVar import sympy as sp from easyscience.variable import Parameter @@ -29,7 +30,7 @@ class ExpressionComponent(ModelComponent): # ------------------------- # Allowed symbolic functions # ------------------------- - _ALLOWED_FUNCS = { + _ALLOWED_FUNCS: ClassVar[dict[str, object]] = { # Exponentials & logs 'exp': sp.exp, 'log': sp.log, @@ -62,12 +63,12 @@ class ExpressionComponent(ModelComponent): # ------------------------- # Allowed constants # ------------------------- - _ALLOWED_CONSTANTS = { + _ALLOWED_CONSTANTS: ClassVar[dict[str, object]] = { 'pi': sp.pi, 'E': sp.E, } - _RESERVED_NAMES = {'x'} + _RESERVED_NAMES: ClassVar[dict[str, object]] = {'x'} def __init__( self, diff --git a/src/easydynamics/sample_model/components/model_component.py b/src/easydynamics/sample_model/components/model_component.py index f4d0c15e..743a1aeb 100644 --- a/src/easydynamics/sample_model/components/model_component.py +++ b/src/easydynamics/sample_model/components/model_component.py @@ -72,7 +72,7 @@ def unit(self, _unit_str: str) -> None: raise AttributeError( f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) def fix_all_parameters(self) -> None: """Fix all parameters in the model component.""" @@ -123,7 +123,7 @@ def _prepare_x_for_evaluate( f'Found {ncoords} coordinates: {coord_names}.' ) # get the coordinate, it's a sc.Variable - coord_name, coord_obj = next(iter(coords.items())) + _, coord_obj = next(iter(coords.items())) x = coord_obj if isinstance(x, sc.Variable): # Need to check if the units are consistent, @@ -235,7 +235,6 @@ def evaluate(self, x: Numeric | list | np.ndarray | sc.Variable | sc.DataArray) np.ndarray Evaluated function values. """ - pass def __repr__(self) -> str: """ diff --git a/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py b/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py index 177d5e79..8525546b 100644 --- a/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py +++ b/src/easydynamics/sample_model/diffusion_model/diffusion_model_base.py @@ -96,7 +96,7 @@ def unit(self, _unit_str: str) -> None: raise AttributeError( f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) @property def scale(self) -> Parameter: diff --git a/src/easydynamics/sample_model/instrument_model.py b/src/easydynamics/sample_model/instrument_model.py index 00cf2544..6468cd3b 100644 --- a/src/easydynamics/sample_model/instrument_model.py +++ b/src/easydynamics/sample_model/instrument_model.py @@ -259,7 +259,7 @@ def unit(self, _unit_str: str) -> None: raise AttributeError( f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) @property def energy_offset(self) -> Parameter: diff --git a/src/easydynamics/sample_model/model_base.py b/src/easydynamics/sample_model/model_base.py index 1b5d9363..7c5711b9 100644 --- a/src/easydynamics/sample_model/model_base.py +++ b/src/easydynamics/sample_model/model_base.py @@ -173,7 +173,7 @@ def unit(self, _unit_str: str) -> None: raise AttributeError( f'Unit is read-only. Use convert_unit to change the unit between allowed types ' f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) def convert_unit(self, unit: str | sc.Unit) -> None: """ diff --git a/src/easydynamics/sample_model/sample_model.py b/src/easydynamics/sample_model/sample_model.py index e1ec6b05..b89829ad 100644 --- a/src/easydynamics/sample_model/sample_model.py +++ b/src/easydynamics/sample_model/sample_model.py @@ -302,7 +302,7 @@ def temperature_unit(self, _value: str | sc.Unit) -> None: raise AttributeError( f'Temperature_unit is read-only. Use convert_temperature_unit to change the unit between allowed types ' # noqa: E501 f'or create a new {self.__class__.__name__} with the desired unit.' - ) # noqa: E501 + ) def convert_temperature_unit(self, unit: str | sc.Unit) -> None: """ @@ -439,8 +439,6 @@ def _generate_component_collections(self) -> None: Generate ComponentCollections from the DiffusionModels for each Q and add the components from self._components. """ - # TODO regenerate automatically if Q, diffusion models - # or components have changed super()._generate_component_collections() if self._Q is None: diff --git a/src/easydynamics/utils/detailed_balance.py b/src/easydynamics/utils/detailed_balance.py index 796afaff..6ee9e19f 100644 --- a/src/easydynamics/utils/detailed_balance.py +++ b/src/easydynamics/utils/detailed_balance.py @@ -23,8 +23,8 @@ def detailed_balance_factor( - energy: int | float | list | np.ndarray | sc.Variable, - temperature: int | float | sc.Variable | Parameter, + energy: float | list | np.ndarray | sc.Variable, + temperature: float | sc.Variable | Parameter, energy_unit: str | sc.Unit = 'meV', temperature_unit: str | sc.Unit = 'K', divide_by_temperature: bool = True, @@ -37,9 +37,9 @@ def detailed_balance_factor( Parameters ---------- - energy : int | float | list | np.ndarray | sc.Variable + energy : float | list | np.ndarray | sc.Variable The energy transfer. If number, assumed to be in meV unless energy_unit is set. - temperature : int | float | sc.Variable | Parameter + temperature : float | sc.Variable | Parameter The temperature. If number, assumed to be in K unless temperature_unit is set. energy_unit : str | sc.Unit, default='meV' Unit for energy if energy is given as a number or list. @@ -179,7 +179,7 @@ def detailed_balance_factor( def _convert_to_scipp_variable( - value: int | float | list | np.ndarray | Parameter | sc.Variable, + value: float | list | np.ndarray | Parameter | sc.Variable, name: str, unit: str | None = None, ) -> sc.Variable: @@ -188,7 +188,7 @@ def _convert_to_scipp_variable( Parameters ---------- - value : int | float | list | np.ndarray | Parameter | sc.Variable + value : float | list | np.ndarray | Parameter | sc.Variable The value to convert. Can be a number, list, numpy array, Parameter, or scipp Variable. If a number or list, the unit must be specified in the unit argument. name : str diff --git a/tests/unit/easydynamics/analysis/test_analysis.py b/tests/unit/easydynamics/analysis/test_analysis.py index 25274a55..67e93aab 100644 --- a/tests/unit/easydynamics/analysis/test_analysis.py +++ b/tests/unit/easydynamics/analysis/test_analysis.py @@ -177,7 +177,7 @@ def test_fit_with_invalid_fit_method(self, analysis): # WHEN / THEN / EXPECT with pytest.raises( ValueError, - match="Invalid fit method. Choose 'independent' or 'simultaneous'.", + match=r"Invalid fit method. Choose 'independent' or 'simultaneous'.", ): analysis.fit(fit_method='invalid_fit_method') @@ -646,7 +646,7 @@ def test_fit_all_Q_simultaneously(self, analysis): expected_fit_objects = analysis.analysis_list expected_fit_functions = analysis.get_fit_functions() mock_fitter.assert_called_once() - args, kwargs = mock_fitter.call_args + _, kwargs = mock_fitter.call_args assert kwargs['fit_objects'] == expected_fit_objects assert kwargs['fit_functions'] == expected_fit_functions @@ -663,7 +663,7 @@ def test_fit_all_Q_simultaneously(self, analysis): expected_ws.append(1.0 / np.sqrt(data.variances)) fake_fitter_instance.fit.assert_called_once() - args, kwargs = fake_fitter_instance.fit.call_args + _, kwargs = fake_fitter_instance.fit.call_args np.testing.assert_array_equal(kwargs['x'], expected_xs) np.testing.assert_array_equal(kwargs['y'], expected_ys) np.testing.assert_array_equal(kwargs['weights'], expected_ws) diff --git a/tests/unit/easydynamics/analysis/test_analysis1d.py b/tests/unit/easydynamics/analysis/test_analysis1d.py index 43763011..dbd0ae51 100644 --- a/tests/unit/easydynamics/analysis/test_analysis1d.py +++ b/tests/unit/easydynamics/analysis/test_analysis1d.py @@ -262,7 +262,7 @@ def test_plot_calls_plopp_with_correct_arguments(self, analysis1d): mock_plot.assert_called_once() # Inspect arguments - args, kwargs = mock_plot.call_args + args, _ = mock_plot.call_args dataset_passed = args[0] @@ -344,7 +344,7 @@ def test_verify_energy_raises(self, analysis1d): energy = np.array([10.0, 20.0]) # THEN / EXPECT - with pytest.raises(TypeError, match='Energy must be a sc.Variable or None'): + with pytest.raises(TypeError, match=r'Energy must be a sc.Variable or None'): analysis1d._verify_energy(energy) def test_calculate_energy_with_offset(self, analysis1d): @@ -399,7 +399,7 @@ def test_evaluate_components_no_components(self, analysis1d): # EXPECT assert isinstance(result, np.ndarray) assert result.shape == (len(analysis1d.experiment.energy),) - assert np.all(result == 0.0) + assert np.all(result == pytest.approx(0.0)) def test_evaluate_components_no_convolution(self, analysis1d): # WHEN @@ -608,7 +608,7 @@ def test_create_convolver(self, analysis1d): assert kwargs['resolution_components'] is resolution_components assert sc.identical(kwargs['energy'], analysis1d.energy) assert kwargs['temperature'] is analysis1d.temperature - assert kwargs['energy_offset'] == 123.0 + assert kwargs['energy_offset'] == pytest.approx(123.0) assert result == MockConvolution.return_value diff --git a/tests/unit/easydynamics/analysis/test_analysis_base.py b/tests/unit/easydynamics/analysis/test_analysis_base.py index 4b47c59e..59b0ff90 100644 --- a/tests/unit/easydynamics/analysis/test_analysis_base.py +++ b/tests/unit/easydynamics/analysis/test_analysis_base.py @@ -163,7 +163,7 @@ def test_Q_property(self, analysis_base): def test_Q_setter_raises(self, analysis_base): with pytest.raises( AttributeError, - match='Q is a read-only property derived from the Experiment.', + match=r'Q is a read-only property derived from the Experiment.', ): analysis_base.Q = [1, 2, 3] @@ -183,7 +183,7 @@ def test_energy_property(self, analysis_base): def test_energy_setter_raises(self, analysis_base): with pytest.raises( AttributeError, - match='energy is a read-only property derived from the Experiment.', + match=r'energy is a read-only property derived from the Experiment.', ): analysis_base.energy = [10, 20, 30] diff --git a/tests/unit/easydynamics/convolution/test_analytical_convolution.py b/tests/unit/easydynamics/convolution/test_analytical_convolution.py index 162a920b..c9d3f46e 100644 --- a/tests/unit/easydynamics/convolution/test_analytical_convolution.py +++ b/tests/unit/easydynamics/convolution/test_analytical_convolution.py @@ -126,7 +126,7 @@ def mock_convolute_analytic_pair(sample_component, resolution_component): # noq # EXPECT # 2 sample components x 2 resolution components - assert np.all(result == 4.0) + assert np.all(result == pytest.approx(4.0)) assert mocked_pair.call_count == 4 # Gather the actual calls to verify correct pairs @@ -178,7 +178,7 @@ def mock_convolute_analytic_pair(sample_component, resolution_component): # noq # EXPECT # 1 sample component x 1 resolution component - assert np.all(result == 1.0) + assert np.all(result == pytest.approx(1.0)) assert mocked_pair.call_count == 1 # Gather the actual calls to verify correct pairs diff --git a/tests/unit/easydynamics/convolution/test_convolution.py b/tests/unit/easydynamics/convolution/test_convolution.py index 468df680..26a95037 100644 --- a/tests/unit/easydynamics/convolution/test_convolution.py +++ b/tests/unit/easydynamics/convolution/test_convolution.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause from contextlib import nullcontext +from typing import ClassVar from unittest.mock import patch import numpy as np @@ -74,7 +75,7 @@ def test_init(self, default_convolution): assert isinstance(default_convolution._sample_components, ComponentCollection) assert isinstance(default_convolution._resolution_components, ComponentCollection) assert default_convolution.upsample_factor == 5 - assert default_convolution.extension_factor == 0.2 + assert default_convolution.extension_factor == pytest.approx(0.2) assert default_convolution.temperature is None assert default_convolution.energy_unit == 'meV' assert default_convolution.normalize_detailed_balance is True @@ -108,7 +109,7 @@ def test_init_components(self, convolution_with_components): assert isinstance(convolution_with_components._sample_components, ComponentCollection) assert isinstance(convolution_with_components._resolution_components, ComponentCollection) assert convolution_with_components.upsample_factor == 5 - assert convolution_with_components.extension_factor == 0.2 + assert convolution_with_components.extension_factor == pytest.approx(0.2) assert convolution_with_components.temperature is None assert convolution_with_components.energy_unit == 'meV' assert convolution_with_components.normalize_detailed_balance is True @@ -317,7 +318,7 @@ def test_convolve_delta_functions(self, default_convolution): assert np.allclose(result, expected_values) # List of analytic functions - analytic_functions = [ + analytic_functions: ClassVar[list[object]] = [ Gaussian(display_name='G', area=1.0, center=0.0, width=0.1), Lorentzian(display_name='L', area=1.0, center=0.0, width=0.1), Voigt( @@ -330,14 +331,19 @@ def test_convolve_delta_functions(self, default_convolution): ] # List of non-analytic functions - non_analytic_functions = [ + non_analytic_functions: ClassVar[list[object]] = [ DampedHarmonicOscillator(display_name='DHO', area=1.0, center=1.0, width=0.1), Polynomial(display_name='P', coefficients=[1.0, 0.0, 0.0]), ] - all_functions_except_delta = analytic_functions + non_analytic_functions - all_functions = all_functions_except_delta + [ - DeltaFunction(display_name='Delta', area=1.0, center=0.0) + all_functions_except_delta: ClassVar[list[object]] = [ + *analytic_functions, + *non_analytic_functions, + ] + + all_functions: ClassVar[list[object]] = [ + *all_functions_except_delta, + DeltaFunction(display_name='Delta', area=1.0, center=0.0), ] @pytest.mark.parametrize('function1', all_functions, ids=lambda f: f.__class__.__name__) diff --git a/tests/unit/easydynamics/convolution/test_convolution_base.py b/tests/unit/easydynamics/convolution/test_convolution_base.py index 44ae133c..68e30388 100644 --- a/tests/unit/easydynamics/convolution/test_convolution_base.py +++ b/tests/unit/easydynamics/convolution/test_convolution_base.py @@ -169,7 +169,7 @@ def test_energy_setter_invalid_type_raises(self, convolution_base): # WHEN THEN EXPECT with pytest.raises( TypeError, - match='Energy must be a Number, a numpy ndarray or a scipp Variable.', + match=r'Energy must be a Number, a numpy ndarray or a scipp Variable.', ): convolution_base.energy = 'invalid' @@ -181,7 +181,7 @@ def test_energy_unit_setter_raises(self, convolution_base): # WHEN THEN EXPECT with pytest.raises( AttributeError, - match='Use convert_unit to change the unit between allowed types ', + match=r'Use convert_unit to change the unit between allowed types ', ): convolution_base.energy_unit = 'K' @@ -198,7 +198,7 @@ def test_convert_energy_unit_invalid_type_raises(self, convolution_base): # WHEN THEN EXPECT with pytest.raises( TypeError, - match='Energy unit must be a string or scipp unit.', + match=r'Energy unit must be a string or scipp unit.', ): convolution_base.convert_energy_unit(123) @@ -206,7 +206,7 @@ def test_convert_energy_unit_invalid_unit_rollback(self, convolution_base): # WHEN THEN with pytest.raises( UnitError, - match='Conversion from `meV` to `s` is not valid.', + match=r'Conversion from `meV` to `s` is not valid.', ): convolution_base.convert_energy_unit('s') @@ -221,7 +221,7 @@ def test_convert_energy_unit_invalid_offset_unit_rollback(self, convolution_base # THEN with pytest.raises( UnitError, - match='Conversion from `s` to `meV` is not valid.', + match=r'Conversion from `s` to `meV` is not valid.', ): convolution_base.convert_energy_unit('meV') @@ -246,7 +246,7 @@ def test_energy_offset_setter_invalid_type_raises(self, convolution_base): # WHEN THEN EXPECT with pytest.raises( TypeError, - match='Energy_offset must be a number or a Parameter.', + match=r'Energy_offset must be a number or a Parameter.', ): convolution_base.energy_offset = 'invalid' @@ -277,8 +277,8 @@ def test_sample_components_setter_invalid_type_raises(self, convolution_base): with pytest.raises( TypeError, match=( - '`sample_components` is an instance of str, ' - 'but must be a ComponentCollection or ModelComponent.' + r'`sample_components` is an instance of str, ' + r'but must be a ComponentCollection or ModelComponent.' ), ): convolution_base.sample_components = 'invalid' @@ -301,8 +301,8 @@ def test_resolution_components_setter_invalid_type_raises(self, convolution_base with pytest.raises( TypeError, match=( - '`resolution_components` is an instance of str, ' - 'but must be a ComponentCollection or ModelComponent.' + r'`resolution_components` is an instance of str, ' + r'but must be a ComponentCollection or ModelComponent.' ), ): convolution_base.resolution_components = 'invalid' diff --git a/tests/unit/easydynamics/convolution/test_numerical_convolution.py b/tests/unit/easydynamics/convolution/test_numerical_convolution.py index 29e5dfd6..320d6c1d 100644 --- a/tests/unit/easydynamics/convolution/test_numerical_convolution.py +++ b/tests/unit/easydynamics/convolution/test_numerical_convolution.py @@ -46,7 +46,7 @@ def test_init(self, default_numerical_convolution): default_numerical_convolution._resolution_components, ComponentCollection ) assert default_numerical_convolution.upsample_factor == 5 - assert default_numerical_convolution.extension_factor == 0.2 + assert default_numerical_convolution.extension_factor == pytest.approx(0.2) assert default_numerical_convolution.temperature is None assert default_numerical_convolution.energy_unit == 'meV' assert default_numerical_convolution.normalize_detailed_balance is True diff --git a/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py b/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py index 3934eefb..3cc00bdc 100644 --- a/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py +++ b/tests/unit/easydynamics/convolution/test_numerical_convolution_base.py @@ -44,7 +44,7 @@ def test_init(self, default_numerical_convolution_base): ComponentCollection, ) assert default_numerical_convolution_base.upsample_factor == 5 - assert default_numerical_convolution_base.extension_factor == 0.2 + assert default_numerical_convolution_base.extension_factor == pytest.approx(0.2) assert default_numerical_convolution_base.temperature is None assert default_numerical_convolution_base.energy_unit == 'meV' assert default_numerical_convolution_base.normalize_detailed_balance is True @@ -100,7 +100,7 @@ def test_init_raises_type_error_for_invalid_temperature(self): invalid_temperature = 'invalid_temperature' # THEN EXPECT - with pytest.raises(TypeError, match='Temperature must be None, a number or a Parameter.'): + with pytest.raises(TypeError, match=r'Temperature must be None, a number or a Parameter.'): NumericalConvolutionBase( energy=energy, sample_components=sample_components, @@ -120,7 +120,7 @@ def test_init_raises_type_error_for_invalid_temperature_unit(self): invalid_temperature_unit = 123 # Not a string or sc.Unit # THEN EXPECT - with pytest.raises(TypeError, match='Temperature_unit must be a string or sc.Unit.'): + with pytest.raises(TypeError, match=r'Temperature_unit must be a string or sc.Unit.'): NumericalConvolutionBase( energy=energy, sample_components=sample_components, @@ -287,7 +287,7 @@ def test_temperature_setter_does_not_replace_parameter( # EXPECT assert default_numerical_convolution_base.temperature is temp_param - assert default_numerical_convolution_base.temperature.value == 350.0 + assert default_numerical_convolution_base.temperature.value == pytest.approx(350.0) def test_temperature_setter_raises(self, default_numerical_convolution_base): """ diff --git a/tests/unit/easydynamics/experiment/test_experiment.py b/tests/unit/easydynamics/experiment/test_experiment.py index 8430d13f..7ffc83c8 100644 --- a/tests/unit/easydynamics/experiment/test_experiment.py +++ b/tests/unit/easydynamics/experiment/test_experiment.py @@ -305,7 +305,7 @@ def test_get_masked_energy(self, experiment_with_data): # EXPECT assert len(masked_energy) == 1 - assert masked_energy.values == 30.0 + assert masked_energy.values == pytest.approx(30.0) def test_get_masked_energy_no_data_returns_None(self): "Test getting masked energy returns zero when no data is present" @@ -526,7 +526,9 @@ def test_extract_x_y_weights_only_finite(self, experiment_with_data): assert np.isfinite(x).all() assert np.isfinite(y).all() assert np.isfinite(weights).all() - assert weights[0] == 1.0 / (experiment_with_data.data.variances[Q_index][2] ** 0.5) + assert weights[0] == pytest.approx( + 1.0 / (experiment_with_data.data.variances[Q_index][2] ** 0.5) + ) assert len(x) == len(y) == len(weights) == 1 # 2 values should be removed # Mask should indicate which values were removed assert np.array_equal(mask, [False, False, True]) 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 702c0bfc..79e1913f 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 @@ -24,17 +24,17 @@ def test_init_no_inputs(self): # EXPECT assert dho.display_name == 'DampedHarmonicOscillator' - assert dho.area.value == 1.0 - assert dho.center.value == 1.0 - assert dho.width.value == 1.0 + 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' def test_initialization(self, dho: DampedHarmonicOscillator): # WHEN THEN EXPECT assert dho.display_name == 'TestDHO' - assert dho.area.value == 2.0 - assert dho.center.value == 1.5 - assert dho.width.value == 0.3 + 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): @@ -88,7 +88,7 @@ def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( ValueError, - match='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', @@ -190,9 +190,9 @@ def test_convert_unit(self, dho: DampedHarmonicOscillator): # EXPECT assert dho.unit == 'microeV' - assert dho.area.value == 2 * 1e3 - assert dho.center.value == 1.5 * 1e3 - assert dho.width.value == 0.3 * 1e3 + 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) def test_copy(self, dho: DampedHarmonicOscillator): # WHEN THEN diff --git a/tests/unit/easydynamics/sample_model/components/test_delta_function.py b/tests/unit/easydynamics/sample_model/components/test_delta_function.py index b5c4bc11..be1e33a0 100644 --- a/tests/unit/easydynamics/sample_model/components/test_delta_function.py +++ b/tests/unit/easydynamics/sample_model/components/test_delta_function.py @@ -23,16 +23,16 @@ def test_init_no_inputs(self): # EXPECT assert delta_function.display_name == 'DeltaFunction' - assert delta_function.area.value == 1.0 - assert delta_function.center.value == 0.0 + assert delta_function.area.value == pytest.approx(1.0) + assert delta_function.center.value == pytest.approx(0.0) assert delta_function.unit == 'meV' assert delta_function.center.fixed is True def test_initialization(self, delta_function: DeltaFunction): # WHEN THEN EXPECT assert delta_function.display_name == 'TestDeltaFunction' - assert delta_function.area.value == 2.0 - assert delta_function.center.value == 0.5 + assert delta_function.area.value == pytest.approx(2.0) + assert delta_function.center.value == pytest.approx(0.5) assert delta_function.unit == 'meV' @pytest.mark.parametrize( @@ -167,7 +167,7 @@ def test_center_is_fixed_if_set_to_None(self, delta_function: DeltaFunction): delta_function.center = None # EXPECT - assert delta_function.center.value == 0.0 + assert delta_function.center.value == pytest.approx(0.0) assert delta_function.center.fixed is True def test_get_all_parameters(self, delta_function: DeltaFunction): @@ -190,8 +190,8 @@ def test_convert_unit(self, delta_function: DeltaFunction): # EXPECT assert delta_function.unit == 'microeV' - assert delta_function.area.value == 2 * 1e3 - assert delta_function.center.value == 0.5 * 1e3 + assert delta_function.area.value == pytest.approx(2 * 1e3) + assert delta_function.center.value == pytest.approx(0.5 * 1e3) def test_copy(self, delta_function: DeltaFunction): # WHEN THEN diff --git a/tests/unit/easydynamics/sample_model/components/test_exponential.py b/tests/unit/easydynamics/sample_model/components/test_exponential.py index d61be76e..2a824532 100644 --- a/tests/unit/easydynamics/sample_model/components/test_exponential.py +++ b/tests/unit/easydynamics/sample_model/components/test_exponential.py @@ -28,17 +28,17 @@ def test_init_no_inputs(self): # THEN EXPECT assert exponential.display_name == 'Exponential' - assert exponential.amplitude.value == 1.0 - assert exponential.center.value == 0.0 - assert exponential.rate.value == 1.0 + 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' def test_initialization(self, exponential: Exponential): # WHEN THEN EXPECT assert exponential.display_name == 'TestExponential' - assert exponential.amplitude.value == 2.0 - assert exponential.center.value == 0.5 - assert exponential.rate.value == 1.2 + 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): @@ -133,7 +133,7 @@ def test_center_is_fixed_if_set_to_None(self, exponential: Exponential): exponential.center = None # EXPECT - assert exponential.center.value == 0.0 + assert exponential.center.value == pytest.approx(0.0) assert exponential.center.fixed is True def test_evaluate(self, exponential: Exponential): @@ -172,33 +172,33 @@ def test_convert_unit(self, exponential: Exponential): # THEN EXPECT assert exponential.unit == 'microeV' - assert exponential.amplitude.value == 2.0 * 1e3 - assert exponential.center.value == 0.5 * 1e3 + 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 == 1.2 / 1e3 + assert exponential.rate.value == pytest.approx(1.2 / 1e3) 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='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='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') # THEN EXPECT - values should be unchanged assert exponential.unit == 'meV' - assert exponential.amplitude.value == 2.0 + assert exponential.amplitude.value == pytest.approx(2.0) assert exponential.amplitude.unit == 'meV' - assert exponential.center.value == 0.5 + assert exponential.center.value == pytest.approx(0.5) assert exponential.center.unit == 'meV' - assert exponential.rate.value == 1.2 + assert exponential.rate.value == pytest.approx(1.2) assert exponential.rate.unit == '1/meV' def test_copy(self, exponential: Exponential): diff --git a/tests/unit/easydynamics/sample_model/components/test_expression_component.py b/tests/unit/easydynamics/sample_model/components/test_expression_component.py index 4a7a43fd..27399844 100644 --- a/tests/unit/easydynamics/sample_model/components/test_expression_component.py +++ b/tests/unit/easydynamics/sample_model/components/test_expression_component.py @@ -23,16 +23,16 @@ def test_init_valid(self, expr: ExpressionComponent): assert expr.display_name == 'TestExpression' assert expr.unit == 'meV' - assert expr.A.value == 2.0 - assert expr.x0.value == 0.5 - assert expr.sigma.value == 0.6 + assert expr.A.value == pytest.approx(2.0) + assert expr.x0.value == pytest.approx(0.5) + assert expr.sigma.value == pytest.approx(0.6) def test_init_without_parameters(self): # WHEN THEN expr = ExpressionComponent('A * x', parameters=None) # EXPECT - assert expr.A.value == 1.0 # default + assert expr.A.value == pytest.approx(1.0) # default def test_invalid_expression_raises(self): # WHEN THEN EXPECT @@ -88,7 +88,7 @@ def test_parameter_setter(self, expr: ExpressionComponent): expr.A = 3.0 # EXPECT - assert expr.A.value == 3.0 + assert expr.A.value == pytest.approx(3.0) assert isinstance(expr.A, Parameter) def test_parameter_getter_invalid_name(self, expr: ExpressionComponent): @@ -131,8 +131,8 @@ def test_missing_parameter_defaults(self): expr = ExpressionComponent('A * x + B', parameters={'A': 2.0}) # EXPECT - assert expr.A.value == 2.0 - assert expr.B.value == 1.0 # default + assert expr.A.value == pytest.approx(2.0) + assert expr.B.value == pytest.approx(1.0) # default def test_dir_includes_parameters(self, expr: ExpressionComponent): # WHEN THEN diff --git a/tests/unit/easydynamics/sample_model/components/test_gaussian.py b/tests/unit/easydynamics/sample_model/components/test_gaussian.py index b1ad6f92..7119852a 100644 --- a/tests/unit/easydynamics/sample_model/components/test_gaussian.py +++ b/tests/unit/easydynamics/sample_model/components/test_gaussian.py @@ -22,18 +22,18 @@ def test_init_no_inputs(self): # EXPECT assert gaussian.display_name == 'Gaussian' - assert gaussian.area.value == 1.0 - assert gaussian.center.value == 0.0 - assert gaussian.width.value == 1.0 + 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.center.fixed is True def test_initialization(self, gaussian: Gaussian): # WHEN THEN EXPECT assert gaussian.display_name == 'TestGaussian' - assert gaussian.area.value == 2.0 - assert gaussian.center.value == 0.5 - assert gaussian.width.value == 0.6 + 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): @@ -85,7 +85,9 @@ def test_input_type_validation_raises(self, kwargs, expected_message): def test_negative_width_raises(self): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='The width of a Gaussian must be greater than zero.'): + with pytest.raises( + ValueError, match=r'The width of a Gaussian must be greater than zero.' + ): Gaussian( display_name='TestGaussian', area=2.0, @@ -150,7 +152,7 @@ def test_center_is_fixed_if_set_to_None(self, gaussian: Gaussian): gaussian.center = None # EXPECT - assert gaussian.center.value == 0.0 + assert gaussian.center.value == pytest.approx(0.0) assert gaussian.center.fixed is True def test_get_all_parameters(self, gaussian: Gaussian): @@ -190,9 +192,9 @@ def test_convert_unit(self, gaussian: Gaussian): # EXPECT assert gaussian.unit == 'microeV' - assert gaussian.area.value == 2 * 1e3 - assert gaussian.center.value == 0.5 * 1e3 - assert gaussian.width.value == 0.6 * 1e3 + 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) def test_copy(self, gaussian: Gaussian): # WHEN THEN diff --git a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py index 5f87032d..4ce1e1db 100644 --- a/tests/unit/easydynamics/sample_model/components/test_lorentzian.py +++ b/tests/unit/easydynamics/sample_model/components/test_lorentzian.py @@ -24,18 +24,18 @@ def test_init_no_inputs(self): # EXPECT assert lorentzian.display_name == 'Lorentzian' - assert lorentzian.area.value == 1.0 - assert lorentzian.center.value == 0.0 - assert lorentzian.width.value == 1.0 + 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.center.fixed is True def test_initialization(self, lorentzian: Lorentzian): # WHEN THEN EXPECT assert lorentzian.display_name == 'TestLorentzian' - assert lorentzian.area.value == 2.0 - assert lorentzian.center.value == 0.5 - assert lorentzian.width.value == 0.6 + 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): @@ -88,7 +88,7 @@ def test_input_type_validation_raises(self, kwargs, expected_message): def test_negative_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match='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', @@ -152,7 +152,7 @@ def test_center_is_fixed_if_set_to_None(self, lorentzian: Lorentzian): lorentzian.center = None # EXPECT - assert lorentzian.center.value == 0.0 + assert lorentzian.center.value == pytest.approx(0.0) assert lorentzian.center.fixed is True def test_get_all_parameters(self, lorentzian: Lorentzian): @@ -189,9 +189,9 @@ def test_convert_unit(self, lorentzian: Lorentzian): # EXPECT assert lorentzian.unit == 'microeV' - assert lorentzian.area.value == 2 * 1e3 - assert lorentzian.center.value == 0.5 * 1e3 - assert lorentzian.width.value == 0.6 * 1e3 + 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) def test_copy(self, lorentzian: Lorentzian): # WHEN THEN diff --git a/tests/unit/easydynamics/sample_model/components/test_mixins.py b/tests/unit/easydynamics/sample_model/components/test_mixins.py index 7c5160ae..4989f76e 100644 --- a/tests/unit/easydynamics/sample_model/components/test_mixins.py +++ b/tests/unit/easydynamics/sample_model/components/test_mixins.py @@ -23,10 +23,10 @@ def test_create_area_parameter_from_numeric(self, dummy_model, area_input, unit) # EXPECT assert isinstance(area_param, Parameter) assert area_param.name == 'TestModel area' - assert area_param.value == float(area_input) + assert area_param.value == pytest.approx(area_input) assert area_param.unit == unit assert not area_param.fixed - assert area_param.min == 0.0 + assert area_param.min == pytest.approx(0.0) def test_create_area_parameter_from_parameter(self, dummy_model): # WHEN @@ -37,7 +37,7 @@ def test_create_area_parameter_from_parameter(self, dummy_model): # EXPECT assert area_param is area_input # Should be the same object - assert area_param.min == 0.0 + assert area_param.min == pytest.approx(0.0) def test_create_area_parameter_invalid_type_raises(self, dummy_model): # WHEN THEN EXPECT @@ -75,7 +75,7 @@ def test_create_center_parameter_from_numeric(self, dummy_model, center_input, u # EXPECT assert isinstance(center_param, Parameter) assert center_param.name == 'TestModel center' - assert center_param.value == float(center_input) + assert center_param.value == pytest.approx(center_input) assert center_param.unit == unit assert not center_param.fixed @@ -88,7 +88,7 @@ def test_create_center_parameter_from_None(self, dummy_model, fix_if_none): # EXPECT assert isinstance(center_param, Parameter) assert center_param.name == 'TestModel center' - assert center_param.value == 0.0 + assert center_param.value == pytest.approx(0.0) assert center_param.unit == 'meV' assert center_param.fixed == fix_if_none @@ -134,7 +134,7 @@ def test_create_width_parameter_from_numeric(self, dummy_model, width_input, uni # EXPECT assert isinstance(width_param, Parameter) assert width_param.name == 'TestModel width' - assert width_param.value == float(width_input) + assert width_param.value == pytest.approx(width_input) assert width_param.unit == unit assert not width_param.fixed diff --git a/tests/unit/easydynamics/sample_model/components/test_model_component.py b/tests/unit/easydynamics/sample_model/components/test_model_component.py index 9e47d4e9..514ced73 100644 --- a/tests/unit/easydynamics/sample_model/components/test_model_component.py +++ b/tests/unit/easydynamics/sample_model/components/test_model_component.py @@ -8,8 +8,6 @@ from easydynamics.sample_model.components.model_component import ModelComponent -Numeric = float | int - class DummyComponent(ModelComponent): def __init__(self): @@ -42,13 +40,13 @@ def test_convert_unit(self, dummy: DummyComponent): # EXPECT assert dummy.unit == 'microeV' - assert dummy.area.value == 1 * 1e3 - assert dummy.center.value == 2 * 1e3 - assert dummy.width.value == 3 * 1e3 + assert dummy.area.value == pytest.approx(1 * 1e3) + assert dummy.center.value == pytest.approx(2 * 1e3) + assert dummy.width.value == pytest.approx(3 * 1e3) def test_convert_unit_incorrect_unit_raises(self, dummy: DummyComponent): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Unit must be a string or sc.Unit'): + with pytest.raises(TypeError, match=r'Unit must be a string or sc.Unit'): dummy.convert_unit(123) def test_free_and_fix_all_parameters(self, dummy): @@ -150,7 +148,7 @@ def test_prepare_x_for_evaluate_with_different_unit_warns(self, dummy): # THEN EXPECT with pytest.warns( UserWarning, - match='Input x has unit µeV, but DummyComponent component ', + match='Input x has unit [µμ]eV, but DummyComponent component ', ): x_prepared = dummy._prepare_x_for_evaluate(x) @@ -158,7 +156,7 @@ def test_prepare_x_for_evaluate_with_different_unit_warns(self, dummy): assert isinstance(x_prepared, np.ndarray) assert x_prepared.shape == (3,) np.testing.assert_array_equal(x_prepared, [1.0, 2.0, 3.0]) - assert dummy.unit == 'µeV' - assert dummy.area.value == 1.0 * 1e3 - assert dummy.center.value == 2.0 * 1e3 - assert dummy.width.value == 3.0 * 1e3 + assert dummy.unit == 'µeV' # noqa: RUF001 + assert dummy.area.value == pytest.approx(1.0 * 1e3) + assert dummy.center.value == pytest.approx(2.0 * 1e3) + assert dummy.width.value == pytest.approx(3.0 * 1e3) diff --git a/tests/unit/easydynamics/sample_model/components/test_polynomial.py b/tests/unit/easydynamics/sample_model/components/test_polynomial.py index aa7790d2..5e42e677 100644 --- a/tests/unit/easydynamics/sample_model/components/test_polynomial.py +++ b/tests/unit/easydynamics/sample_model/components/test_polynomial.py @@ -22,15 +22,15 @@ def test_init_no_inputs(self): # EXPECT assert polynomial.display_name == 'Polynomial' - assert polynomial.coefficients[0].value == 0.0 + assert polynomial.coefficients[0].value == pytest.approx(0.0) assert polynomial.unit == 'meV' def test_initialization(self, polynomial: Polynomial): # WHEN THEN EXPECT assert polynomial.display_name == 'TestPolynomial' - assert polynomial.coefficients[0].value == 1.0 - assert polynomial.coefficients[1].value == -2.0 - assert polynomial.coefficients[2].value == 3.0 + assert polynomial.coefficients[0].value == pytest.approx(1.0) + assert polynomial.coefficients[1].value == pytest.approx(-2.0) + assert polynomial.coefficients[2].value == pytest.approx(3.0) @pytest.mark.parametrize( 'kwargs, expected_message', @@ -60,7 +60,7 @@ def test_input_type_validation_raises(self, kwargs, expected_message): def test_init_no_coefficients_raises(self): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='At least one coefficient must be provided.'): + with pytest.raises(ValueError, match=r'At least one coefficient must be provided.'): Polynomial(display_name='TestPolynomial', coefficients=[]) def test_negative_value_warns_in_evaluate(self): diff --git a/tests/unit/easydynamics/sample_model/components/test_voigt.py b/tests/unit/easydynamics/sample_model/components/test_voigt.py index f4ea949d..5bf866d8 100644 --- a/tests/unit/easydynamics/sample_model/components/test_voigt.py +++ b/tests/unit/easydynamics/sample_model/components/test_voigt.py @@ -30,20 +30,20 @@ def test_init_no_inputs(self): # EXPECT assert voigt.display_name == 'Voigt' - assert voigt.area.value == 1.0 - assert voigt.center.value == 0.0 - assert voigt.gaussian_width.value == 1.0 - assert voigt.lorentzian_width.value == 1.0 + 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.center.fixed is True def test_initialization(self, voigt: Voigt): # WHEN THEN EXPECT assert voigt.display_name == 'TestVoigt' - assert voigt.area.value == 2.0 - assert voigt.center.value == 0.5 - assert voigt.gaussian_width.value == 0.6 - assert voigt.lorentzian_width.value == 0.7 + 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): @@ -133,7 +133,7 @@ def test_input_type_validation_raises(self, kwargs, expected_message): def test_negative_gaussian_width_raises(self): # WHEN THEN EXPECT with pytest.raises( - ValueError, match='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', @@ -148,7 +148,7 @@ def test_negative_lorentzian_width_raises(self): # WHEN THEN EXPECT with pytest.raises( ValueError, - match='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', @@ -217,7 +217,7 @@ def test_center_is_fixed_if_set_to_None(self, voigt: Voigt): voigt.center = None # EXPECT - assert voigt.center.value == 0.0 + assert voigt.center.value == pytest.approx(0.0) assert voigt.center.fixed is True def test_evaluate(self, voigt: Voigt): @@ -243,7 +243,7 @@ def test_center_is_fixed_if_init_to_None(self): ) # EXPECT - assert test_voigt.center.value == 0.0 + assert test_voigt.center.value == pytest.approx(0.0) assert test_voigt.center.fixed is True def test_convert_unit(self, voigt: Voigt): @@ -252,10 +252,10 @@ def test_convert_unit(self, voigt: Voigt): # EXPECT assert voigt.unit == 'microeV' - assert voigt.area.value == 2 * 1e3 - assert voigt.center.value == 0.5 * 1e3 - assert voigt.gaussian_width.value == 0.6 * 1e3 - assert voigt.lorentzian_width.value == 0.7 * 1e3 + 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) + assert voigt.lorentzian_width.value == pytest.approx(0.7 * 1e3) def test_get_all_parameters(self, voigt: Voigt): # WHEN THEN diff --git a/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py b/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py index 80ec766f..be25890e 100644 --- a/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py +++ b/tests/unit/easydynamics/sample_model/diffusion_model/test_brownian_translational_diffusion.py @@ -26,8 +26,8 @@ def test_init_default(self, brownian_diffusion_model): # WHEN THEN EXPECT assert brownian_diffusion_model.display_name == 'BrownianTranslationalDiffusion' assert brownian_diffusion_model.unit == 'meV' - assert brownian_diffusion_model.scale.value == 1.0 - assert brownian_diffusion_model.diffusion_coefficient.value == 1.0 + assert brownian_diffusion_model.scale.value == pytest.approx(1.0) + assert brownian_diffusion_model.diffusion_coefficient.value == pytest.approx(1.0) @pytest.mark.parametrize( 'kwargs,expected_exception, expected_message', @@ -70,16 +70,16 @@ def test_diffusion_coefficient_setter(self, brownian_diffusion_model): brownian_diffusion_model.diffusion_coefficient = 3.0 # THEN EXPECT - assert brownian_diffusion_model.diffusion_coefficient.value == 3.0 + assert brownian_diffusion_model.diffusion_coefficient.value == pytest.approx(3.0) def test_diffusion_coefficient_setter_raises(self, brownian_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='diffusion_coefficient must be a number.'): + with pytest.raises(TypeError, match=r'diffusion_coefficient must be a number.'): brownian_diffusion_model.diffusion_coefficient = 'invalid' # Invalid type def test_diffusion_coefficient_setter_negative_raises(self, brownian_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='diffusion_coefficient must be non-negative.'): + with pytest.raises(ValueError, match=r'diffusion_coefficient must be non-negative.'): brownian_diffusion_model.diffusion_coefficient = -1.0 # Invalid negative value def test_calculate_width_type_error(self, brownian_diffusion_model): @@ -170,7 +170,7 @@ def test_create_component_collections_component_name_must_be_string( self, brownian_diffusion_model ): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='component_name must be a string.'): + with pytest.raises(TypeError, match=r'component_name must be a string.'): brownian_diffusion_model.create_component_collections( Q=np.array([0.1, 0.2, 0.3]), component_display_name=123 ) @@ -182,7 +182,7 @@ def test_create_component_collections_Q_type_error(self, brownian_diffusion_mode def test_create_component_collections_Q_1dimensional_error(self, brownian_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='Q must be a 1-dimensional array.'): + with pytest.raises(ValueError, match=r'Q must be a 1-dimensional array.'): brownian_diffusion_model.create_component_collections( Q=np.array([[0.1, 0.2], [0.3, 0.4]]) ) # Invalid shape diff --git a/tests/unit/easydynamics/sample_model/diffusion_model/test_diffusion_model.py b/tests/unit/easydynamics/sample_model/diffusion_model/test_diffusion_model.py index 67f654b2..c46dc561 100644 --- a/tests/unit/easydynamics/sample_model/diffusion_model/test_diffusion_model.py +++ b/tests/unit/easydynamics/sample_model/diffusion_model/test_diffusion_model.py @@ -20,7 +20,7 @@ def test_unit_setter_raises(self, diffusion_model): # WHEN THEN EXPECT with pytest.raises( AttributeError, - match='Unit is read-only. Use convert_unit to change the unit between allowed types', + match=r'Unit is read-only. Use convert_unit to change the unit between allowed types', ): diffusion_model.unit = 'eV' @@ -29,16 +29,16 @@ def test_scale_setter(self, diffusion_model): diffusion_model.scale = 2.0 # THEN EXPECT - assert diffusion_model.scale.value == 2.0 + assert diffusion_model.scale.value == pytest.approx(2.0) def test_scale_setter_negative_raises(self, diffusion_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='scale must be non-negative.'): + with pytest.raises(ValueError, match=r'scale must be non-negative.'): diffusion_model.scale = -1.0 # Invalid negative value def test_scale_setter_raises(self, diffusion_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='scale must be a number.'): + with pytest.raises(TypeError, match=r'scale must be a number.'): diffusion_model.scale = 'invalid' # Invalid type def test_repr(self, diffusion_model): diff --git a/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py b/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py index 756d422f..9388a2db 100644 --- a/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py +++ b/tests/unit/easydynamics/sample_model/diffusion_model/test_jump_translational_diffusion.py @@ -26,9 +26,9 @@ def test_init_default(self, jump_diffusion_model): # WHEN THEN EXPECT assert jump_diffusion_model.display_name == 'JumpTranslationalDiffusion' assert jump_diffusion_model.unit == 'meV' - assert jump_diffusion_model.scale.value == 1.0 - assert jump_diffusion_model.diffusion_coefficient.value == 1.0 - assert jump_diffusion_model.relaxation_time.value == 1.0 + assert jump_diffusion_model.scale.value == pytest.approx(1.0) + assert jump_diffusion_model.diffusion_coefficient.value == pytest.approx(1.0) + assert jump_diffusion_model.relaxation_time.value == pytest.approx(1.0) @pytest.mark.parametrize( 'kwargs,expected_exception, expected_message', @@ -84,16 +84,16 @@ def test_diffusion_coefficient_setter(self, jump_diffusion_model): jump_diffusion_model.diffusion_coefficient = 3.0 # THEN EXPECT - assert jump_diffusion_model.diffusion_coefficient.value == 3.0 + assert jump_diffusion_model.diffusion_coefficient.value == pytest.approx(3.0) def test_diffusion_coefficient_setter_raises(self, jump_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='diffusion_coefficient must be a number.'): + with pytest.raises(TypeError, match=r'diffusion_coefficient must be a number.'): jump_diffusion_model.diffusion_coefficient = 'invalid' # Invalid type def test_diffusion_coefficient_setter_negative_raises(self, jump_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='diffusion_coefficient must be non-negative.'): + with pytest.raises(ValueError, match=r'diffusion_coefficient must be non-negative.'): jump_diffusion_model.diffusion_coefficient = -1.0 # Invalid negative value def test_relaxation_time_setter(self, jump_diffusion_model): @@ -101,16 +101,16 @@ def test_relaxation_time_setter(self, jump_diffusion_model): jump_diffusion_model.relaxation_time = 2.5 # THEN EXPECT - assert jump_diffusion_model.relaxation_time.value == 2.5 + assert jump_diffusion_model.relaxation_time.value == pytest.approx(2.5) def test_relaxation_time_setter_raises(self, jump_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='relaxation_time must be a number.'): + with pytest.raises(TypeError, match=r'relaxation_time must be a number.'): jump_diffusion_model.relaxation_time = 'invalid' # Invalid type def test_relaxation_time_setter_negative_raises(self, jump_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='relaxation_time must be non-negative.'): + with pytest.raises(ValueError, match=r'relaxation_time must be non-negative.'): jump_diffusion_model.relaxation_time = -1.0 # Invalid negative value def test_calculate_width_type_error(self, jump_diffusion_model): @@ -208,7 +208,7 @@ def test_create_component_collections_component_name_must_be_string( self, jump_diffusion_model ): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='component_name must be a string.'): + with pytest.raises(TypeError, match=r'component_name must be a string.'): jump_diffusion_model.create_component_collections( Q=np.array([0.1, 0.2, 0.3]), component_display_name=123 ) @@ -220,7 +220,7 @@ def test_create_component_collections_Q_type_error(self, jump_diffusion_model): def test_create_component_collections_Q_1dimensional_error(self, jump_diffusion_model): # WHEN THEN EXPECT - with pytest.raises(ValueError, match='Q must be a 1-dimensional array.'): + with pytest.raises(ValueError, match=r'Q must be a 1-dimensional array.'): jump_diffusion_model.create_component_collections( Q=np.array([[0.1, 0.2], [0.3, 0.4]]) ) # Invalid shape diff --git a/tests/unit/easydynamics/sample_model/test_component_collection.py b/tests/unit/easydynamics/sample_model/test_component_collection.py index 15ea38ba..c7d41aa1 100644 --- a/tests/unit/easydynamics/sample_model/test_component_collection.py +++ b/tests/unit/easydynamics/sample_model/test_component_collection.py @@ -66,7 +66,7 @@ def test_init_with_components(self): def test_init_with_invalid_components_raises(self): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Component must be.'): + with pytest.raises(TypeError, match='Component must be'): ComponentCollection(components=['NotAComponent']) def test_init_with_invalid_list_of_components_raises(self): @@ -152,7 +152,7 @@ def test_component_setter(self, component_collection): def test_component_setter_invalid_raises(self, component_collection): # WHEN THEN EXPECT - with pytest.raises(TypeError, match=' must be instances of ModelComponent.'): + with pytest.raises(TypeError, match=r' must be instances of ModelComponent.'): component_collection.components = ['NotAComponent'] with pytest.raises(TypeError, match='components must be a list of'): @@ -174,7 +174,7 @@ def test_is_empty(self): def test_is_empty_setter(self, component_collection): # WHEN THEN EXPECT - with pytest.raises(AttributeError, match='is_empty is a read-only property.'): + with pytest.raises(AttributeError, match=r'is_empty is a read-only property.'): component_collection.is_empty = True def test_component_setter_empty_list(self, component_collection): @@ -204,7 +204,7 @@ def test_convert_unit(self, component_collection): def test_convert_unit_incorrect_unit_raises(self, component_collection): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Unit must be a string or sc.Unit'): + with pytest.raises(TypeError, match=r'Unit must be a string or sc.Unit'): component_collection.convert_unit(123) def test_convert_unit_failure_rolls_back(self, component_collection): @@ -224,7 +224,7 @@ def convert_unit(self, _unit: str) -> None: } # EXPECT - with pytest.raises(RuntimeError, match='Conversion failed.'): + with pytest.raises(RuntimeError, match=r'Conversion failed.'): component_collection.convert_unit('eV') # Check that all components have their original units @@ -235,7 +235,7 @@ def test_set_unit(self, component_collection): # WHEN THEN EXPECT with pytest.raises( AttributeError, - match='Unit is read-only. Use convert_unit to change the unit', + match=r'Unit is read-only. Use convert_unit to change the unit', ): component_collection.unit = 'eV' @@ -255,7 +255,7 @@ def test_evaluate_no_components_returns_zero(self): x = np.linspace(-5, 5, 100) # EXPECT result = component_collection.evaluate(x) - assert np.all(result == 0.0) + assert np.all(result == pytest.approx(0.0)) assert result.shape == x.shape def test_evaluate_component(self, component_collection): @@ -283,7 +283,7 @@ def test_evaluate_component_no_components_raises(self): component_collection = ComponentCollection(display_name='EmptyModel') x = np.linspace(-5, 5, 100) # EXPECT - with pytest.raises(ValueError, match='No components in the model to evaluate.'): + with pytest.raises(ValueError, match=r'No components in the model to evaluate.'): component_collection.evaluate_component(x, 'AnyComponent') def test_evaluate_component_invalid_name_type_raises(self, component_collection): @@ -293,7 +293,7 @@ def test_evaluate_component_invalid_name_type_raises(self, component_collection) # THEN EXPECT with pytest.raises( TypeError, - match="Component unique name must be a string, got instead.", + match=r"Component unique name must be a string, got instead.", ): component_collection.evaluate_component(x, 123) @@ -312,7 +312,7 @@ def test_normalize_area_no_components_raises(self): # WHEN THEN component_collection = ComponentCollection(display_name='EmptyModel') # EXPECT - with pytest.raises(ValueError, match='No components in the model to normalize.'): + with pytest.raises(ValueError, match=r'No components in the model to normalize.'): component_collection.normalize_area() @pytest.mark.parametrize( @@ -326,7 +326,7 @@ def test_normalize_area_not_finite_area_raises(self, component_collection, area_ component_collection.components[1].area = area_value # EXPECT - with pytest.raises(ValueError, match='cannot normalize.'): + with pytest.raises(ValueError, match=r'cannot normalize'): component_collection.normalize_area() def test_normalize_area_non_area_component_warns(self, component_collection): diff --git a/tests/unit/easydynamics/sample_model/test_instrument_model.py b/tests/unit/easydynamics/sample_model/test_instrument_model.py index dad4ae9f..e8983b0d 100644 --- a/tests/unit/easydynamics/sample_model/test_instrument_model.py +++ b/tests/unit/easydynamics/sample_model/test_instrument_model.py @@ -172,7 +172,7 @@ def test_unit_setter_raises(self, instrument_model): # WHEN / THEN / EXPECT with pytest.raises( AttributeError, - match='Unit is read-only. Use convert_unit to change the unit between allowed types ', + match=r'Unit is read-only. Use convert_unit to change the unit between allowed types ', ): instrument_model.unit = 'meV' @@ -184,7 +184,7 @@ def test_energy_offset_setter(self, instrument_model): instrument_model.energy_offset = 1.0 # EXPECT - assert instrument_model.energy_offset.value == 1.0 + assert instrument_model.energy_offset.value == pytest.approx(1.0) instrument_model._on_energy_offset_change.assert_called_once() def test_energy_offset_setter_raises(self, instrument_model): diff --git a/tests/unit/easydynamics/sample_model/test_model_base.py b/tests/unit/easydynamics/sample_model/test_model_base.py index 41f1cf59..5ad066a2 100644 --- a/tests/unit/easydynamics/sample_model/test_model_base.py +++ b/tests/unit/easydynamics/sample_model/test_model_base.py @@ -268,7 +268,7 @@ def test_convert_unit_invalid_raises(self, model_base): def test_convert_unit_incorrect_unit_raises(self, model_base): # WHEN THEN EXPECT - with pytest.raises(TypeError, match='Unit must be a string or sc.Unit'): + with pytest.raises(TypeError, match=r'Unit must be a string or sc.Unit'): model_base.convert_unit(123) def test_components_setter(self, model_base): diff --git a/tests/unit/easydynamics/sample_model/test_sample_model.py b/tests/unit/easydynamics/sample_model/test_sample_model.py index 6c20f0b0..a093b1c1 100644 --- a/tests/unit/easydynamics/sample_model/test_sample_model.py +++ b/tests/unit/easydynamics/sample_model/test_sample_model.py @@ -65,7 +65,7 @@ def test_init(self, sample_model): assert isinstance(model.diffusion_models, list) assert len(model.diffusion_models) == 1 assert isinstance(model.diffusion_models[0], BrownianTranslationalDiffusion) - assert model.temperature.value == 10.0 + assert model.temperature.value == pytest.approx(10.0) assert model.divide_by_temperature is True np.testing.assert_array_equal(model.Q, np.array([1.0, 2.0, 3.0])) @@ -205,7 +205,7 @@ def test_temperature_setter(self, sample_model): model.temperature = 20.0 # EXPECT - assert model.temperature.value == 20.0 + assert model.temperature.value == pytest.approx(20.0) # THEN model.temperature = None @@ -217,7 +217,7 @@ def test_temperature_setter(self, sample_model): model.temperature = 0.0 # EXPECT - assert model.temperature.value == 0.0 + assert model.temperature.value == pytest.approx(0.0) @pytest.mark.parametrize( 'invalid_value', @@ -233,7 +233,7 @@ def test_temperature_setter_raises_with_invalid_type(self, invalid_value, sample # WHEN / THEN / EXPECT with pytest.raises( (TypeError, ValueError), - match='temperature must be a number or None|temperature must be non-negative', + match=r'temperature must be a number or None|temperature must be non-negative', ): sample_model.temperature = invalid_value @@ -347,9 +347,9 @@ def test_generate_component_collections(self, sample_model): assert isinstance(collection, ComponentCollection) assert len(collection.components) == 3 # 3 components assert collection.components[0].display_name == 'TestGaussian1' - assert collection.components[0].area.value == 1.0 + assert collection.components[0].area.value == pytest.approx(1.0) assert collection.components[1].display_name == 'TestLorentzian1' - assert collection.components[1].area.value == 2.0 + assert collection.components[1].area.value == pytest.approx(2.0) assert collection.components[2].display_name == 'Brownian diffusion' assert isinstance(collection.components[2], Lorentzian) diff --git a/tests/unit/easydynamics/utils/test_detailed_balance.py b/tests/unit/easydynamics/utils/test_detailed_balance.py index 7662dff3..bebf5407 100644 --- a/tests/unit/easydynamics/utils/test_detailed_balance.py +++ b/tests/unit/easydynamics/utils/test_detailed_balance.py @@ -21,7 +21,7 @@ def test_energy_unit_not_string_error(self): T = 100 energy_unit = 5 # Then Expect - with pytest.raises(TypeError, match='energy_unit must be a string.'): + with pytest.raises(TypeError, match=r'energy_unit must be a string.'): detailed_balance_factor(energy, T, energy_unit=energy_unit) @pytest.mark.parametrize('temperature_unit', [5, 5.0, {}, []]) @@ -30,7 +30,7 @@ def test_temperature_unit_not_string_error(self, temperature_unit): energy = 2.0 T = 100 # Then Expect - with pytest.raises(TypeError, match='temperature_unit must be a string.'): + with pytest.raises(TypeError, match=r'temperature_unit must be a string.'): detailed_balance_factor(energy, T, temperature_unit=temperature_unit) def test_divide_by_temperature_not_bool_error(self): @@ -39,7 +39,7 @@ def test_divide_by_temperature_not_bool_error(self): T = 100 divide_by_temperature = 'yes' # Then Expect - with pytest.raises(TypeError, match='divide_by_temperature must be True or False.'): + with pytest.raises(TypeError, match=r'divide_by_temperature must be True or False.'): detailed_balance_factor(energy, T, divide_by_temperature=divide_by_temperature) @pytest.mark.parametrize( @@ -219,7 +219,7 @@ def test_energy_unit_warning(self): # Then with pytest.warns( UserWarning, - match='Input energy has unit µeV, but energy_unit was set to meV. Using µeV.', + match='Input energy has unit [µμ]eV, but energy_unit was set to meV. Using [µμ]eV.', ): result = detailed_balance_factor( energy=energy,