From d1cf4d45ffdce4c2e36526ec12d1b1dba6854c4c Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Fri, 23 Aug 2024 13:40:19 +0200 Subject: [PATCH 1/9] LMFit differnetial_evolution --- src/easyscience/fitting/minimizers/factory.py | 10 +++++++++- src/easyscience/fitting/minimizers/minimizer_lmfit.py | 1 + tests/unit_tests/Fitting/minimizers/test_factory.py | 3 ++- tests/unit_tests/Fitting/test_fitter.py | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/easyscience/fitting/minimizers/factory.py b/src/easyscience/fitting/minimizers/factory.py index 503cc729..9443eece 100644 --- a/src/easyscience/fitting/minimizers/factory.py +++ b/src/easyscience/fitting/minimizers/factory.py @@ -39,6 +39,7 @@ class AvailableMinimizers(Enum): LMFit_leastsq = auto() LMFit_powell = auto() LMFit_cobyla = auto() + LMFit_differential_evolution = auto() if bumps_engine_imported: Bumps = auto() @@ -50,6 +51,7 @@ class AvailableMinimizers(Enum): DFO = auto() DFO_leastsq = auto() + # Temporary solution to convert string to enum def from_string_to_enum(minimizer_name: str) -> AvailableMinimizers: if minimizer_name == 'LMFit': @@ -60,6 +62,8 @@ def from_string_to_enum(minimizer_name: str) -> AvailableMinimizers: minmizer_enum = AvailableMinimizers.LMFit_powell elif minimizer_name == 'LMFit_cobyla': minmizer_enum = AvailableMinimizers.LMFit_cobyla + elif minimizer_name == 'LMFit_differential_evolution': + minmizer_enum = AvailableMinimizers.LMFit_differential_evolution elif minimizer_name == 'Bumps': minmizer_enum = AvailableMinimizers.Bumps @@ -75,7 +79,9 @@ def from_string_to_enum(minimizer_name: str) -> AvailableMinimizers: elif minimizer_name == 'DFO_leastsq': minmizer_enum = AvailableMinimizers.DFO_leastsq else: - raise ValueError(f"Invalid minimizer name: {minimizer_name}. The following minimizers are available: {[minimize.name for minimize in AvailableMinimizers]}") # noqa: E501 + raise ValueError( + f'Invalid minimizer name: {minimizer_name}. The following minimizers are available: {[minimize.name for minimize in AvailableMinimizers]}' + ) # noqa: E501 return minmizer_enum @@ -89,6 +95,8 @@ def factory(minimizer_enum: AvailableMinimizers, fit_object, fit_function: Calla minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='powell') elif minimizer_enum == AvailableMinimizers.LMFit_cobyla: minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='cobyla') + elif minimizer_enum == AvailableMinimizers.LMFit_differential_evolution: + minimizer = LMFit(obj=fit_object, fit_function=fit_function, method='differential_evolution') elif minimizer_enum == AvailableMinimizers.Bumps: minimizer = Bumps(obj=fit_object, fit_function=fit_function, method='amoeba') diff --git a/src/easyscience/fitting/minimizers/minimizer_lmfit.py b/src/easyscience/fitting/minimizers/minimizer_lmfit.py index 59164047..b0e3c7aa 100644 --- a/src/easyscience/fitting/minimizers/minimizer_lmfit.py +++ b/src/easyscience/fitting/minimizers/minimizer_lmfit.py @@ -71,6 +71,7 @@ def supported_methods() -> List[str]: return [ 'least_squares', 'leastsq', + 'differential_evolution', 'powell', 'cobyla', ] diff --git a/tests/unit_tests/Fitting/minimizers/test_factory.py b/tests/unit_tests/Fitting/minimizers/test_factory.py index 9dfccc94..1d5bd76f 100644 --- a/tests/unit_tests/Fitting/minimizers/test_factory.py +++ b/tests/unit_tests/Fitting/minimizers/test_factory.py @@ -51,10 +51,11 @@ def test_available_minimizers(): assert AvailableMinimizers.LMFit_leastsq assert AvailableMinimizers.LMFit_powell assert AvailableMinimizers.LMFit_cobyla + assert AvailableMinimizers.LMFit_differential_evolution assert AvailableMinimizers.Bumps assert AvailableMinimizers.Bumps_simplex assert AvailableMinimizers.Bumps_newton assert AvailableMinimizers.Bumps_lm assert AvailableMinimizers.DFO assert AvailableMinimizers.DFO_leastsq - assert len(AvailableMinimizers) == 10 \ No newline at end of file + assert len(AvailableMinimizers) == 11 \ No newline at end of file diff --git a/tests/unit_tests/Fitting/test_fitter.py b/tests/unit_tests/Fitting/test_fitter.py index e3974e99..e448f817 100644 --- a/tests/unit_tests/Fitting/test_fitter.py +++ b/tests/unit_tests/Fitting/test_fitter.py @@ -166,7 +166,7 @@ def test_available_minimizers(self, fitter: Fitter): # Then Expect assert minimizers == [ - 'LMFit', 'LMFit_leastsq', 'LMFit_powell', 'LMFit_cobyla', + 'LMFit', 'LMFit_leastsq', 'LMFit_powell', 'LMFit_cobyla', 'LMFit_differential_evolution', 'Bumps', 'Bumps_simplex', 'Bumps_newton', 'Bumps_lm', 'DFO', 'DFO_leastsq' ] From 874cbf2a55d2f0ee7e08cadadc34c0448aa9e51e Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Fri, 23 Aug 2024 13:47:43 +0200 Subject: [PATCH 2/9] ruff --- src/easyscience/fitting/minimizers/factory.py | 4 ++-- src/easyscience/fitting/minimizers/minimizer_bumps.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/easyscience/fitting/minimizers/factory.py b/src/easyscience/fitting/minimizers/factory.py index 9443eece..68d63b75 100644 --- a/src/easyscience/fitting/minimizers/factory.py +++ b/src/easyscience/fitting/minimizers/factory.py @@ -80,8 +80,8 @@ def from_string_to_enum(minimizer_name: str) -> AvailableMinimizers: minmizer_enum = AvailableMinimizers.DFO_leastsq else: raise ValueError( - f'Invalid minimizer name: {minimizer_name}. The following minimizers are available: {[minimize.name for minimize in AvailableMinimizers]}' - ) # noqa: E501 + f'Invalid minimizer name: {minimizer_name}. The following minimizers are available: {[minimize.name for minimize in AvailableMinimizers]}' # noqa: E501 + ) return minmizer_enum diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 1840dddc..9aa491a8 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -62,7 +62,7 @@ def all_methods() -> List[str]: @staticmethod def supported_methods() -> List[str]: # only a small subset - methods = ['scipy.leastsq','amoeba', 'newton', 'lm'] + methods = ['scipy.leastsq', 'amoeba', 'newton', 'lm'] return methods def fit( From 009f054dd24649725c32c1ac5ab7371c66f1fd2e Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Fri, 23 Aug 2024 14:00:29 +0200 Subject: [PATCH 3/9] expose available minimizers in fitter module --- src/easyscience/fitting/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/easyscience/fitting/__init__.py b/src/easyscience/fitting/__init__.py index 1018c0c7..3bf7ce30 100644 --- a/src/easyscience/fitting/__init__.py +++ b/src/easyscience/fitting/__init__.py @@ -1,7 +1,8 @@ from .fitter import Fitter +from .minimizers.factory import AvailableMinimizers from .minimizers.utils import FitResults # Causes circular import # from .multi_fitter import MultiFitter # noqa: F401, E402 -all = [Fitter, FitResults] +all = [AvailableMinimizers, Fitter, FitResults] From abf76714ef982b97fbe6d080627675e2bb95fbd0 Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Mon, 26 Aug 2024 09:05:36 +0200 Subject: [PATCH 4/9] introduced available_minimizers module --- src/easyscience/fitting/__init__.py | 2 +- .../fitting/available_minimizers.py | 81 +++++++++++++++++ src/easyscience/fitting/fitter.py | 4 +- src/easyscience/fitting/minimizers/factory.py | 86 ++----------------- .../fitting/minimizers/minimizer_base.py | 4 +- .../integration_tests/Fitting/test_fitter.py | 2 +- .../Fitting/test_fitter_legacy_parameter.py | 2 +- .../Fitting/minimizers/test_factory.py | 4 +- .../minimizers/test_minimizer_lmfit.py | 3 +- tests/unit_tests/Fitting/test_fitter.py | 2 +- 10 files changed, 98 insertions(+), 92 deletions(-) create mode 100644 src/easyscience/fitting/available_minimizers.py diff --git a/src/easyscience/fitting/__init__.py b/src/easyscience/fitting/__init__.py index 3bf7ce30..f34b8b81 100644 --- a/src/easyscience/fitting/__init__.py +++ b/src/easyscience/fitting/__init__.py @@ -1,5 +1,5 @@ +from .available_minimizers import AvailableMinimizers from .fitter import Fitter -from .minimizers.factory import AvailableMinimizers from .minimizers.utils import FitResults # Causes circular import diff --git a/src/easyscience/fitting/available_minimizers.py b/src/easyscience/fitting/available_minimizers.py new file mode 100644 index 00000000..b97b0bcb --- /dev/null +++ b/src/easyscience/fitting/available_minimizers.py @@ -0,0 +1,81 @@ +import warnings +from enum import Enum +from enum import auto + +import pkg_resources + +installed_packages = {pkg.key for pkg in pkg_resources.working_set} + +lmfit_engine_available = False +if 'lmfit' in installed_packages: + lmfit_engine_available = True +else: + # TODO make this a proper message (use logging?) + warnings.warn('LMFit minimization is not available. Probably lmfit has not been installed.', ImportWarning, stacklevel=2) + +bumps_engine_available = False +if 'bumps' in installed_packages: + bumps_engine_available = True +else: + # TODO make this a proper message (use logging?) + warnings.warn('Bumps minimization is not available. Probably bumps has not been installed.', ImportWarning, stacklevel=2) + +dfo_engine_available = False +if 'dfo-ls' in installed_packages: + dfo_engine_available = True +else: + # TODO make this a proper message (use logging?) + warnings.warn('DFO minimization is not available. Probably dfols has not been installed.', ImportWarning, stacklevel=2) + + +class AvailableMinimizers(Enum): + if lmfit_engine_available: + LMFit = auto() + LMFit_leastsq = auto() + LMFit_powell = auto() + LMFit_cobyla = auto() + LMFit_differential_evolution = auto() + + if bumps_engine_available: + Bumps = auto() + Bumps_simplex = auto() + Bumps_newton = auto() + Bumps_lm = auto() + + if dfo_engine_available: + DFO = auto() + DFO_leastsq = auto() + + +# Temporary solution to convert string to enum +def from_string_to_enum(minimizer_name: str) -> AvailableMinimizers: + if minimizer_name == 'LMFit': + minmizer_enum = AvailableMinimizers.LMFit + elif minimizer_name == 'LMFit_leastsq': + minmizer_enum = AvailableMinimizers.LMFit_leastsq + elif minimizer_name == 'LMFit_powell': + minmizer_enum = AvailableMinimizers.LMFit_powell + elif minimizer_name == 'LMFit_cobyla': + minmizer_enum = AvailableMinimizers.LMFit_cobyla + elif minimizer_name == 'LMFit_differential_evolution': + minmizer_enum = AvailableMinimizers.LMFit_differential_evolution + + elif minimizer_name == 'Bumps': + minmizer_enum = AvailableMinimizers.Bumps + elif minimizer_name == 'Bumps_simplex': + minmizer_enum = AvailableMinimizers.Bumps_simplex + elif minimizer_name == 'Bumps_newton': + minmizer_enum = AvailableMinimizers.Bumps_newton + elif minimizer_name == 'Bumps_lm': + minmizer_enum = AvailableMinimizers.Bumps_lm + + elif minimizer_name == 'DFO': + minmizer_enum = AvailableMinimizers.DFO + elif minimizer_name == 'DFO_leastsq': + minmizer_enum = AvailableMinimizers.DFO_leastsq + else: + raise ValueError( + f'Invalid minimizer name: {minimizer_name}. The following minimizers are available: {[minimize.name for minimize in AvailableMinimizers]}' # noqa: E501 + ) + + return minmizer_enum diff --git a/src/easyscience/fitting/fitter.py b/src/easyscience/fitting/fitter.py index 7af26cf4..3d96fa75 100644 --- a/src/easyscience/fitting/fitter.py +++ b/src/easyscience/fitting/fitter.py @@ -9,11 +9,11 @@ import numpy as np +from .available_minimizers import AvailableMinimizers +from .available_minimizers import from_string_to_enum from .minimizers import FitResults from .minimizers import MinimizerBase -from .minimizers.factory import AvailableMinimizers from .minimizers.factory import factory -from .minimizers.factory import from_string_to_enum DEFAULT_MINIMIZER = AvailableMinimizers.LMFit_leastsq diff --git a/src/easyscience/fitting/minimizers/factory.py b/src/easyscience/fitting/minimizers/factory.py index 68d63b75..026a6c9c 100644 --- a/src/easyscience/fitting/minimizers/factory.py +++ b/src/easyscience/fitting/minimizers/factory.py @@ -1,89 +1,15 @@ -import warnings -from enum import Enum -from enum import auto from typing import Callable +from .. import available_minimizers +from ..available_minimizers import AvailableMinimizers from .minimizer_base import MinimizerBase -lmfit_engine_imported = False -try: +if available_minimizers.lmfit_engine_available: from .minimizer_lmfit import LMFit - - lmfit_engine_imported = True -except ImportError: - # TODO make this a proper message (use logging?) - warnings.warn('LMFit minimization is not available. Probably lmfit has not been installed.', ImportWarning, stacklevel=2) - -bumps_engine_imported = False -try: - from .minimizer_bumps import Bumps - - bumps_engine_imported = True -except ImportError: - # TODO make this a proper message (use logging?) - warnings.warn('Bumps minimization is not available. Probably bumps has not been installed.', ImportWarning, stacklevel=2) - -dfo_engine_imported = False -try: +if available_minimizers.dfo_engine_available: from .minimizer_dfo import DFO - - dfo_engine_imported = True -except ImportError: - # TODO make this a proper message (use logging?) - warnings.warn('DFO minimization is not available. Probably dfols has not been installed.', ImportWarning, stacklevel=2) - - -class AvailableMinimizers(Enum): - if lmfit_engine_imported: - LMFit = auto() - LMFit_leastsq = auto() - LMFit_powell = auto() - LMFit_cobyla = auto() - LMFit_differential_evolution = auto() - - if bumps_engine_imported: - Bumps = auto() - Bumps_simplex = auto() - Bumps_newton = auto() - Bumps_lm = auto() - - if dfo_engine_imported: - DFO = auto() - DFO_leastsq = auto() - - -# Temporary solution to convert string to enum -def from_string_to_enum(minimizer_name: str) -> AvailableMinimizers: - if minimizer_name == 'LMFit': - minmizer_enum = AvailableMinimizers.LMFit - elif minimizer_name == 'LMFit_leastsq': - minmizer_enum = AvailableMinimizers.LMFit_leastsq - elif minimizer_name == 'LMFit_powell': - minmizer_enum = AvailableMinimizers.LMFit_powell - elif minimizer_name == 'LMFit_cobyla': - minmizer_enum = AvailableMinimizers.LMFit_cobyla - elif minimizer_name == 'LMFit_differential_evolution': - minmizer_enum = AvailableMinimizers.LMFit_differential_evolution - - elif minimizer_name == 'Bumps': - minmizer_enum = AvailableMinimizers.Bumps - elif minimizer_name == 'Bumps_simplex': - minmizer_enum = AvailableMinimizers.Bumps_simplex - elif minimizer_name == 'Bumps_newton': - minmizer_enum = AvailableMinimizers.Bumps_newton - elif minimizer_name == 'Bumps_lm': - minmizer_enum = AvailableMinimizers.Bumps_lm - - elif minimizer_name == 'DFO': - minmizer_enum = AvailableMinimizers.DFO - elif minimizer_name == 'DFO_leastsq': - minmizer_enum = AvailableMinimizers.DFO_leastsq - else: - raise ValueError( - f'Invalid minimizer name: {minimizer_name}. The following minimizers are available: {[minimize.name for minimize in AvailableMinimizers]}' # noqa: E501 - ) - - return minmizer_enum +if available_minimizers.bumps_engine_available: + from .minimizer_bumps import Bumps def factory(minimizer_enum: AvailableMinimizers, fit_object, fit_function: Callable) -> MinimizerBase: diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 59ef4133..9836bdfb 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -16,8 +16,8 @@ import numpy as np -#causes circular import when Parameter is imported -#from easyscience.Objects.ObjectClasses import BaseObj +# causes circular import when Parameter is imported +# from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.Variable import Parameter from ..Constraints import ObjConstraint diff --git a/tests/integration_tests/Fitting/test_fitter.py b/tests/integration_tests/Fitting/test_fitter.py index 1dbcb6ed..757c3424 100644 --- a/tests/integration_tests/Fitting/test_fitter.py +++ b/tests/integration_tests/Fitting/test_fitter.py @@ -11,7 +11,7 @@ from easyscience.fitting.Constraints import ObjConstraint from easyscience.fitting.fitter import Fitter from easyscience.fitting.minimizers import FitError -from easyscience.fitting.minimizers.factory import AvailableMinimizers +from easyscience.fitting.available_minimizers import AvailableMinimizers from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.new_variable import Parameter diff --git a/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py b/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py index acafae5b..1f43d530 100644 --- a/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py +++ b/tests/integration_tests/Fitting/test_fitter_legacy_parameter.py @@ -11,7 +11,7 @@ from easyscience.fitting.Constraints import ObjConstraint from easyscience.fitting.fitter import Fitter from easyscience.fitting.minimizers import FitError -from easyscience.fitting.minimizers.factory import AvailableMinimizers +from easyscience.fitting.available_minimizers import AvailableMinimizers from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.ObjectClasses import Parameter diff --git a/tests/unit_tests/Fitting/minimizers/test_factory.py b/tests/unit_tests/Fitting/minimizers/test_factory.py index 1d5bd76f..8a0095a3 100644 --- a/tests/unit_tests/Fitting/minimizers/test_factory.py +++ b/tests/unit_tests/Fitting/minimizers/test_factory.py @@ -1,6 +1,6 @@ from easyscience.fitting.minimizers.factory import factory -from easyscience.fitting.minimizers.factory import from_string_to_enum -from easyscience.fitting.minimizers.factory import AvailableMinimizers +from easyscience.fitting.available_minimizers import from_string_to_enum +from easyscience.fitting.available_minimizers import AvailableMinimizers from easyscience.fitting.minimizers import MinimizerBase from unittest.mock import MagicMock import pytest diff --git a/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py b/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py index 0a6c8db9..cf0dc6cc 100644 --- a/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py +++ b/tests/unit_tests/Fitting/minimizers/test_minimizer_lmfit.py @@ -3,11 +3,10 @@ from unittest.mock import MagicMock import easyscience.fitting.minimizers.minimizer_lmfit - +from easyscience.fitting import AvailableMinimizers from easyscience.fitting.minimizers.minimizer_lmfit import LMFit from easyscience.Objects.new_variable import Parameter from lmfit import Parameter as LMParameter -from easyscience.Objects.ObjectClasses import BaseObj from easyscience.fitting.minimizers.utils import FitError diff --git a/tests/unit_tests/Fitting/test_fitter.py b/tests/unit_tests/Fitting/test_fitter.py index e448f817..2095fb99 100644 --- a/tests/unit_tests/Fitting/test_fitter.py +++ b/tests/unit_tests/Fitting/test_fitter.py @@ -4,7 +4,7 @@ import numpy as np import easyscience.fitting.fitter from easyscience.fitting.fitter import Fitter -from easyscience.fitting.minimizers.factory import AvailableMinimizers +from easyscience.fitting.available_minimizers import AvailableMinimizers class TestFitter(): From 14c88dcc84be49c9546d0df158df4eb25bfbce53 Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Mon, 26 Aug 2024 10:50:30 +0200 Subject: [PATCH 5/9] moved method dict to base class --- .../fitting/minimizers/minimizer_base.py | 16 ++++++++++++++++ .../fitting/minimizers/minimizer_bumps.py | 8 ++------ .../fitting/minimizers/minimizer_dfo.py | 6 ------ .../fitting/minimizers/minimizer_lmfit.py | 11 ++--------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 9836bdfb..b7225486 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -20,6 +20,7 @@ # from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.Variable import Parameter +from ..available_minimizers import AvailableMinimizers from ..Constraints import ObjConstraint from .utils import FitError from .utils import FitResults @@ -122,6 +123,21 @@ def evaluate(self, x: np.ndarray, minimizer_parameters: dict[str, float] = None, return self._fit_function(x, **minimizer_parameters, **kwargs) + def _get_method_dict(self, passed_method: Optional[str] = None) -> dict[str, AvailableMinimizers]: + method = {} + + # Set if default method is not None + if self._method is not None: + method['method'] = self._method + + # Set / overwrite if method was passed + if passed_method is not None: + if passed_method in self.supported_methods(): + method['method'] = passed_method + else: + raise FitError(f'Method {passed_method} not available in {self.__class__}') + return method + @abstractmethod def convert_to_pars_obj(self, par_list: Optional[Union[list]] = None): """ diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index 9aa491a8..f571d717 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -96,11 +96,7 @@ def fit( :return: Fit results :rtype: ModelResult """ - default_method = {} - if self._method is not None: - default_method = {'method': self._method} - if method is not None and method in self.supported_methods(): - default_method['method'] = method + method_dict = self._get_method_dict(method) if weights is None: weights = np.sqrt(np.abs(y)) @@ -135,7 +131,7 @@ def fit( global_object.stack.enabled = False try: - model_results = bumps_fit(problem, **default_method, **minimizer_kwargs, **kwargs) + model_results = bumps_fit(problem, **method_dict, **minimizer_kwargs, **kwargs) self._set_parameter_fit_result(model_results, stack_status) results = self._gen_fit_results(model_results) except Exception as e: diff --git a/src/easyscience/fitting/minimizers/minimizer_dfo.py b/src/easyscience/fitting/minimizers/minimizer_dfo.py index fa65e481..042674ff 100644 --- a/src/easyscience/fitting/minimizers/minimizer_dfo.py +++ b/src/easyscience/fitting/minimizers/minimizer_dfo.py @@ -87,12 +87,6 @@ def fit( :return: Fit results :rtype: ModelResult """ - default_method = {} - if self._method is not None: - default_method = {'method': self._method} - if method is not None and method in self.supported_methods(): - default_method['method'] = method - if weights is None: weights = np.sqrt(np.abs(y)) diff --git a/src/easyscience/fitting/minimizers/minimizer_lmfit.py b/src/easyscience/fitting/minimizers/minimizer_lmfit.py index b0e3c7aa..514a9c23 100644 --- a/src/easyscience/fitting/minimizers/minimizer_lmfit.py +++ b/src/easyscience/fitting/minimizers/minimizer_lmfit.py @@ -109,14 +109,7 @@ def fit( :return: Fit results :rtype: ModelResult """ - default_method = {} - if self._method is not None: - default_method = {'method': self._method} - if method is not None: - if method in self.supported_methods(): - default_method['method'] = method - else: - raise FitError(f'Method {method} not available in {self.__class__}') + method_dict = self._get_method_dict(method) if weights is None: weights = 1 / np.sqrt(np.abs(y)) @@ -140,7 +133,7 @@ def fit( if model is None: model = self._make_model() - model_results = model.fit(y, x=x, weights=weights, **default_method, **minimizer_kwargs, **kwargs) + model_results = model.fit(y, x=x, weights=weights, **method_dict, **minimizer_kwargs, **kwargs) self._set_parameter_fit_result(model_results, stack_status) results = self._gen_fit_results(model_results) except Exception as e: From cb50108d657087a37f853a1914645f1a632563d2 Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Mon, 26 Aug 2024 10:58:47 +0200 Subject: [PATCH 6/9] fix typing --- src/easyscience/fitting/minimizers/minimizer_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index b7225486..e7201d58 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -96,7 +96,7 @@ def fit( :return: Fit results """ - def evaluate(self, x: np.ndarray, minimizer_parameters: dict[str, float] = None, **kwargs) -> np.ndarray: + def evaluate(self, x: np.ndarray, minimizer_parameters: Optional[dict[str, float]] = None, **kwargs) -> np.ndarray: """ Evaluate the fit function for values of x. Parameters used are either the latest or user supplied. If the parameters are user supplied, it must be in a dictionary of {'parameter_name': parameter_value,...}. @@ -123,7 +123,7 @@ def evaluate(self, x: np.ndarray, minimizer_parameters: dict[str, float] = None, return self._fit_function(x, **minimizer_parameters, **kwargs) - def _get_method_dict(self, passed_method: Optional[str] = None) -> dict[str, AvailableMinimizers]: + def _get_method_dict(self, passed_method: Optional[str] = None) -> dict[str, str]: method = {} # Set if default method is not None From 3989a254e108807eb8abf6ae3606d441b4461ca2 Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Mon, 26 Aug 2024 12:18:27 +0200 Subject: [PATCH 7/9] syntax --- src/easyscience/fitting/minimizers/minimizer_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index e7201d58..413be5ca 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -20,7 +20,6 @@ # from easyscience.Objects.ObjectClasses import BaseObj from easyscience.Objects.Variable import Parameter -from ..available_minimizers import AvailableMinimizers from ..Constraints import ObjConstraint from .utils import FitError from .utils import FitResults From ad69f2593d12d987b622b6c3e9a4ac0ff3121b6d Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Wed, 28 Aug 2024 13:02:28 +0200 Subject: [PATCH 8/9] PR response --- .../fitting/available_minimizers.py | 7 ++-- .../fitting/minimizers/minimizer_base.py | 16 +++----- .../Fitting/minimizers/test_minimizer_base.py | 38 ++++++++++++++++++- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/easyscience/fitting/available_minimizers.py b/src/easyscience/fitting/available_minimizers.py index b97b0bcb..cc7e2406 100644 --- a/src/easyscience/fitting/available_minimizers.py +++ b/src/easyscience/fitting/available_minimizers.py @@ -1,10 +1,9 @@ +import importlib.metadata import warnings from enum import Enum from enum import auto -import pkg_resources - -installed_packages = {pkg.key for pkg in pkg_resources.working_set} +installed_packages = [x.name for x in importlib.metadata.distributions()] lmfit_engine_available = False if 'lmfit' in installed_packages: @@ -21,7 +20,7 @@ warnings.warn('Bumps minimization is not available. Probably bumps has not been installed.', ImportWarning, stacklevel=2) dfo_engine_available = False -if 'dfo-ls' in installed_packages: +if 'DFO-LS' in installed_packages: dfo_engine_available = True else: # TODO make this a proper message (use logging?) diff --git a/src/easyscience/fitting/minimizers/minimizer_base.py b/src/easyscience/fitting/minimizers/minimizer_base.py index 413be5ca..124c377b 100644 --- a/src/easyscience/fitting/minimizers/minimizer_base.py +++ b/src/easyscience/fitting/minimizers/minimizer_base.py @@ -123,19 +123,15 @@ def evaluate(self, x: np.ndarray, minimizer_parameters: Optional[dict[str, float return self._fit_function(x, **minimizer_parameters, **kwargs) def _get_method_dict(self, passed_method: Optional[str] = None) -> dict[str, str]: - method = {} + if passed_method is not None: + if passed_method not in self.supported_methods(): + raise FitError(f'Method {passed_method} not available in {self.__class__}') + return {'method': passed_method} - # Set if default method is not None if self._method is not None: - method['method'] = self._method + return {'method': self._method} - # Set / overwrite if method was passed - if passed_method is not None: - if passed_method in self.supported_methods(): - method['method'] = passed_method - else: - raise FitError(f'Method {passed_method} not available in {self.__class__}') - return method + return {} @abstractmethod def convert_to_pars_obj(self, par_list: Optional[Union[list]] = None): diff --git a/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py b/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py index 3f483dd5..3618489f 100644 --- a/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py +++ b/tests/unit_tests/Fitting/minimizers/test_minimizer_base.py @@ -168,4 +168,40 @@ def test_create_signature(self, minimizer: MinimizerBase) -> None: InspectParameter('p2', InspectParameter.POSITIONAL_OR_KEYWORD, annotation=_empty, default=2.0) ] expected_signature = Signature(wrapped_parameters) - assert signature == expected_signature \ No newline at end of file + assert signature == expected_signature + + def test_get_method_dict(self, minimizer: MinimizerBase) -> None: + # When Then + result = minimizer._get_method_dict() + + # Expect + assert result == {'method': 'method'} + + def test_get_method_dict_no_self(self, minimizer: MinimizerBase) -> None: + # When + minimizer._method = None + + # Then + result = minimizer._get_method_dict() + + # Expect + assert result == {} + + def test_get_method_dict_supported_method(self, minimizer: MinimizerBase) -> None: + # When + minimizer.supported_methods = MagicMock(return_value=['supported_method']) + + # Then + result = minimizer._get_method_dict('supported_method') + + # Expect + assert result == {'method': 'supported_method'} + + def test_get_method_dict_not_supported_method(self, minimizer: MinimizerBase) -> None: + # When + minimizer.supported_methods = MagicMock(return_value=['supported_method']) + + # Then Expect + with pytest.raises(FitError): + result = minimizer._get_method_dict('not_supported_method') + From 3fccc9d40f4db5cedfb503b28da16636a29a43a7 Mon Sep 17 00:00:00 2001 From: Andreas Pedersen Date: Wed, 28 Aug 2024 13:34:23 +0200 Subject: [PATCH 9/9] revert change as importlib.metadata.distributions is not supported in 3.9 --- src/easyscience/fitting/available_minimizers.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/easyscience/fitting/available_minimizers.py b/src/easyscience/fitting/available_minimizers.py index cc7e2406..c432de85 100644 --- a/src/easyscience/fitting/available_minimizers.py +++ b/src/easyscience/fitting/available_minimizers.py @@ -1,9 +1,14 @@ -import importlib.metadata import warnings from enum import Enum from enum import auto -installed_packages = [x.name for x in importlib.metadata.distributions()] +import pkg_resources + +installed_packages = {pkg.key for pkg in pkg_resources.working_set} + +# Change to importlib.metadata when Python 3.10 is the minimum version +# import importlib.metadata +# installed_packages = [x.name for x in importlib.metadata.distributions()] lmfit_engine_available = False if 'lmfit' in installed_packages: @@ -20,7 +25,7 @@ warnings.warn('Bumps minimization is not available. Probably bumps has not been installed.', ImportWarning, stacklevel=2) dfo_engine_available = False -if 'DFO-LS' in installed_packages: +if 'dfo-ls' in installed_packages: dfo_engine_available = True else: # TODO make this a proper message (use logging?)