Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/easyscience/fitting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from .available_minimizers import AvailableMinimizers
from .fitter import Fitter
from .minimizers.utils import FitResults

# Causes circular import
# from .multi_fitter import MultiFitter # noqa: F401, E402

all = [Fitter, FitResults]
all = [AvailableMinimizers, Fitter, FitResults]
85 changes: 85 additions & 0 deletions src/easyscience/fitting/available_minimizers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import warnings
from enum import Enum
from enum import auto

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:
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
4 changes: 2 additions & 2 deletions src/easyscience/fitting/fitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
82 changes: 8 additions & 74 deletions src/easyscience/fitting/minimizers/factory.py
Original file line number Diff line number Diff line change
@@ -1,83 +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()

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 == '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:
Expand All @@ -89,6 +21,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')
Expand Down
17 changes: 14 additions & 3 deletions src/easyscience/fitting/minimizers/minimizer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -95,7 +95,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,...}.
Expand All @@ -122,6 +122,17 @@ 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, str]:
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}

if self._method is not None:
return {'method': self._method}

return {}

@abstractmethod
def convert_to_pars_obj(self, par_list: Optional[Union[list]] = None):
"""
Expand Down
10 changes: 3 additions & 7 deletions src/easyscience/fitting/minimizers/minimizer_bumps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 0 additions & 6 deletions src/easyscience/fitting/minimizers/minimizer_dfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
12 changes: 3 additions & 9 deletions src/easyscience/fitting/minimizers/minimizer_lmfit.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def supported_methods() -> List[str]:
return [
'least_squares',
'leastsq',
'differential_evolution',
'powell',
'cobyla',
]
Expand Down Expand Up @@ -108,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))
Expand All @@ -139,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:
Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests/Fitting/test_fitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions tests/unit_tests/Fitting/minimizers/test_factory.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
assert len(AvailableMinimizers) == 11
Loading