diff --git a/pyproject.toml b/pyproject.toml index 0f4ff28..6ce612e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ classifiers = [ requires-python = ">=3.11" dependencies = [ "asteval", - "bumps<1.0.0", + "bumps", "DFO-LS", "lmfit", "numpy", diff --git a/src/easyscience/base_classes/based_base.py b/src/easyscience/base_classes/based_base.py index 0af389e..b401d04 100644 --- a/src/easyscience/base_classes/based_base.py +++ b/src/easyscience/base_classes/based_base.py @@ -14,10 +14,10 @@ from ..io import SerializerComponent from ..variable import Parameter +from ..variable.descriptor_base import DescriptorBase if TYPE_CHECKING: from ..fitting.calculators import InterfaceFactoryTemplate - from ..variable.descriptor_base import DescriptorBase class BasedBase(SerializerComponent): diff --git a/src/easyscience/fitting/minimizers/minimizer_bumps.py b/src/easyscience/fitting/minimizers/minimizer_bumps.py index ed2b140..195114a 100644 --- a/src/easyscience/fitting/minimizers/minimizer_bumps.py +++ b/src/easyscience/fitting/minimizers/minimizer_bumps.py @@ -63,7 +63,7 @@ def all_methods() -> List[str]: @staticmethod def supported_methods() -> List[str]: # only a small subset - methods = ['scipy.leastsq', 'amoeba', 'newton', 'lm'] + methods = ['amoeba', 'newton', 'lm'] return methods def fit( @@ -133,7 +133,7 @@ def fit( try: model_results = bumps_fit(problem, **method_dict, **minimizer_kwargs, **kwargs) - self._set_parameter_fit_result(model_results, stack_status) + self._set_parameter_fit_result(model_results, stack_status, problem._parameters) results = self._gen_fit_results(model_results) except Exception as e: for key in self._cached_pars.keys(): @@ -200,7 +200,7 @@ def _make_func(x, y, weights): return _outer(self) - def _set_parameter_fit_result(self, fit_result, stack_status: bool): + def _set_parameter_fit_result(self, fit_result, stack_status: bool, par_list: List[BumpsParameter]): """ Update parameters to their final values and assign a std error to them. @@ -219,7 +219,7 @@ def _set_parameter_fit_result(self, fit_result, stack_status: bool): global_object.stack.enabled = True global_object.stack.beginMacro('Fitting routine') - for index, name in enumerate(self._cached_model._pnames): + for index, name in enumerate([par.name for par in par_list]): dict_name = name[len(MINIMIZER_PARAMETER_PREFIX) :] pars[dict_name].value = fit_result.x[index] pars[dict_name].error = fit_result.dx[index] @@ -242,7 +242,7 @@ def _gen_fit_results(self, fit_results, **kwargs) -> FitResults: results.success = fit_results.success pars = self._cached_pars item = {} - for index, name in enumerate(self._cached_model._pnames): + for index, name in enumerate(self._cached_model.pars.keys()): dict_name = name[len(MINIMIZER_PARAMETER_PREFIX) :] item[name] = pars[dict_name].value diff --git a/tests/unit_tests/fitting/minimizers/test_minimizer_bumps.py b/tests/unit_tests/fitting/minimizers/test_minimizer_bumps.py index 7feaf8e..fc10017 100644 --- a/tests/unit_tests/fitting/minimizers/test_minimizer_bumps.py +++ b/tests/unit_tests/fitting/minimizers/test_minimizer_bumps.py @@ -33,11 +33,11 @@ def test_init_exception(self) -> None: def test_all_methods(self, minimizer: Bumps) -> None: # When Then Expect - assert minimizer.all_methods() == ['amoeba', 'de', 'dream', 'newton', 'scipy.leastsq', 'lm'] + assert minimizer.all_methods() == ['amoeba', 'de', 'dream', 'newton', 'lm'] def test_supported_methods(self, minimizer: Bumps) -> None: # When Then Expect - assert set(minimizer.supported_methods()) == set(['scipy.leastsq','newton', 'lm', 'amoeba']) + assert set(minimizer.supported_methods()) == set(['newton', 'lm', 'amoeba']) def test_fit(self, minimizer: Bumps, monkeypatch) -> None: # When @@ -47,13 +47,18 @@ def test_fit(self, minimizer: Bumps, monkeypatch) -> None: mock_bumps_fit = MagicMock(return_value='fit') monkeypatch.setattr(easyscience.fitting.minimizers.minimizer_bumps, "bumps_fit", mock_bumps_fit) - mock_FitProblem = MagicMock(return_value='fit_problem') + # Prepare a mock parameter with .name = 'pmock_parm_1' + mock_bumps_param = MagicMock() + mock_bumps_param.name = 'pmock_parm_1' + # Patch FitProblem to have _parameters attribute as expected + mock_FitProblem_instance = MagicMock() + mock_FitProblem_instance._parameters = [mock_bumps_param] + mock_FitProblem = MagicMock(return_value=mock_FitProblem_instance) monkeypatch.setattr(easyscience.fitting.minimizers.minimizer_bumps, "FitProblem", mock_FitProblem) mock_model = MagicMock() mock_model_function = MagicMock(return_value=mock_model) minimizer._make_model = MagicMock(return_value=mock_model_function) - minimizer._set_parameter_fit_result = MagicMock() minimizer._gen_fit_results = MagicMock(return_value='gen_fit_results') cached_par = MagicMock() @@ -61,14 +66,21 @@ def test_fit(self, minimizer: Bumps, monkeypatch) -> None: cached_pars = {'mock_parm_1': cached_par} minimizer._cached_pars = cached_pars + # Patch _set_parameter_fit_result to a real function that will not raise KeyError + def fake_set_parameter_fit_result(fit_result, stack_status, par_list): + # Simulate what the real function does: update _cached_pars + for index, name in enumerate([par.name for par in par_list]): + dict_name = name[len('p'):] # Remove prefix 'p' + minimizer._cached_pars[dict_name].value = 42 # Arbitrary value + minimizer._set_parameter_fit_result = fake_set_parameter_fit_result + # Then result = minimizer.fit(x=1.0, y=2.0) # Expect assert result == 'gen_fit_results' - mock_bumps_fit.assert_called_once_with('fit_problem', method='amoeba') + mock_bumps_fit.assert_called_once_with(mock_FitProblem_instance, method='amoeba') minimizer._make_model.assert_called_once_with(parameters=None) - minimizer._set_parameter_fit_result.assert_called_once_with('fit', False) minimizer._gen_fit_results.assert_called_once_with('fit') mock_model_function.assert_called_once_with(1.0, 2.0, 1.4142135623730951) mock_FitProblem.assert_called_once_with(mock_model) @@ -107,15 +119,22 @@ def test_set_parameter_fit_result_no_stack_status(self, minimizer: Bumps): minimizer._cached_pars['b'].value = 'b' mock_cached_model = MagicMock() - mock_cached_model._pnames = ['pa', 'pb'] + mock_cached_model.pars = {'pa':0, 'pb': 0} minimizer._cached_model = mock_cached_model mock_fit_result = MagicMock() mock_fit_result.x = [1.0, 2.0] mock_fit_result.dx = [0.1, 0.2] + # The new argument: par_list (list of mock parameters) + mock_par_a = MagicMock() + mock_par_a.name = 'pa' + mock_par_b = MagicMock() + mock_par_b.name = 'pb' + par_list = [mock_par_a, mock_par_b] + # Then - minimizer._set_parameter_fit_result(mock_fit_result, False) + minimizer._set_parameter_fit_result(mock_fit_result, False, par_list) # Expect assert minimizer._cached_pars['a'].value == 1.0 @@ -136,7 +155,7 @@ def test_gen_fit_results(self, minimizer: Bumps, monkeypatch): mock_cached_model.x = 'x' mock_cached_model.y = 'y' mock_cached_model.dy = 'dy' - mock_cached_model._pnames = ['ppar_1', 'ppar_2'] + mock_cached_model.pars = {'ppar_1': 0, 'ppar_2': 0} minimizer._cached_model = mock_cached_model mock_cached_par_1 = MagicMock()