easyscience 1.1.2__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {easyscience-1.1.2 → easyscience-1.2.0}/PKG-INFO +1 -1
  2. easyscience-1.2.0/src/easyscience/__version__.py +1 -0
  3. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/fitter.py +51 -6
  4. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/minimizer_base.py +8 -2
  5. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/minimizer_bumps.py +9 -3
  6. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/minimizer_dfo.py +24 -10
  7. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/minimizer_lmfit.py +24 -8
  8. easyscience-1.1.2/src/easyscience/__version__.py +0 -1
  9. {easyscience-1.1.2 → easyscience-1.2.0}/.gitignore +0 -0
  10. {easyscience-1.1.2 → easyscience-1.2.0}/LICENSE +0 -0
  11. {easyscience-1.1.2 → easyscience-1.2.0}/README.md +0 -0
  12. {easyscience-1.1.2 → easyscience-1.2.0}/pyproject.toml +0 -0
  13. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Constraints.py +0 -0
  14. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Datasets/__init__.py +0 -0
  15. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Datasets/xarray.py +0 -0
  16. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/Groups.py +0 -0
  17. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/Inferface.py +0 -0
  18. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/ObjectClasses.py +0 -0
  19. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/Variable.py +0 -0
  20. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/__init__.py +0 -0
  21. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/core.py +0 -0
  22. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/job/__init__.py +0 -0
  23. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/job/analysis.py +0 -0
  24. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/job/experiment.py +0 -0
  25. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/job/job.py +0 -0
  26. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/job/theoreticalmodel.py +0 -0
  27. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/new_variable/__init__.py +0 -0
  28. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/new_variable/descriptor_base.py +0 -0
  29. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/new_variable/descriptor_bool.py +0 -0
  30. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/new_variable/descriptor_number.py +0 -0
  31. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/new_variable/descriptor_str.py +0 -0
  32. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/new_variable/parameter.py +0 -0
  33. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Objects/virtual.py +0 -0
  34. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/REDIRECT.py +0 -0
  35. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/Exceptions.py +0 -0
  36. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/__init__.py +0 -0
  37. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/classTools.py +0 -0
  38. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/classUtils.py +0 -0
  39. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/decorators.py +0 -0
  40. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/io/__init__.py +0 -0
  41. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/io/dict.py +0 -0
  42. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/io/json.py +0 -0
  43. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/io/template.py +0 -0
  44. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/io/xml.py +0 -0
  45. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/Utils/string.py +0 -0
  46. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/__init__.py +0 -0
  47. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/__init__.py +0 -0
  48. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/available_minimizers.py +0 -0
  49. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/__init__.py +0 -0
  50. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/factory.py +0 -0
  51. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/minimizers/utils.py +0 -0
  52. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/fitting/multi_fitter.py +0 -0
  53. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/__init__.py +0 -0
  54. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/global_object.py +0 -0
  55. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/hugger/__init__.py +0 -0
  56. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/hugger/hugger.py +0 -0
  57. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/hugger/property.py +0 -0
  58. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/logger.py +0 -0
  59. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/map.py +0 -0
  60. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/global_object/undo_redo.py +0 -0
  61. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/models/__init__.py +0 -0
  62. {easyscience-1.1.2 → easyscience-1.2.0}/src/easyscience/models/polynomial.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: easyscience
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: Generic logic for easyScience libraries
5
5
  Project-URL: homepage, https://github.com/EasyScience/EasyScience
6
6
  Project-URL: documentation, https://easyscience.github.io/EasyScience/
@@ -0,0 +1 @@
1
+ __version__ = '1.2.0'
@@ -26,11 +26,13 @@ class Fitter:
26
26
  def __init__(self, fit_object, fit_function: Callable):
27
27
  self._fit_object = fit_object
28
28
  self._fit_function = fit_function
29
- self._dependent_dims = None
29
+ self._dependent_dims: int = None
30
+ self._tolerance: float = None
31
+ self._max_evaluations: int = None
30
32
 
31
- self._enum_current_minimizer = DEFAULT_MINIMIZER
32
- self._minimizer: MinimizerBase # _minimizer is set in the create method
33
- self._update_minimizer(self._enum_current_minimizer)
33
+ self._minimizer: MinimizerBase = None # set in _update_minimizer
34
+ self._enum_current_minimizer: AvailableMinimizers = None # set in _update_minimizer
35
+ self._update_minimizer(DEFAULT_MINIMIZER)
34
36
 
35
37
  def fit_constraints(self) -> list:
36
38
  return self._minimizer.fit_constraints()
@@ -110,6 +112,42 @@ class Fitter:
110
112
  """
111
113
  return self._minimizer
112
114
 
115
+ @property
116
+ def tolerance(self) -> float:
117
+ """
118
+ Get the tolerance for the minimizer.
119
+
120
+ :return: Tolerance for the minimizer
121
+ """
122
+ return self._tolerance
123
+
124
+ @tolerance.setter
125
+ def tolerance(self, tolerance: float) -> None:
126
+ """
127
+ Set the tolerance for the minimizer.
128
+
129
+ :param tolerance: Tolerance for the minimizer
130
+ """
131
+ self._tolerance = tolerance
132
+
133
+ @property
134
+ def max_evaluations(self) -> int:
135
+ """
136
+ Get the maximal number of evaluations for the minimizer.
137
+
138
+ :return: Maximal number of steps for the minimizer
139
+ """
140
+ return self._max_evaluations
141
+
142
+ @max_evaluations.setter
143
+ def max_evaluations(self, max_evaluations: int) -> None:
144
+ """
145
+ Set the maximal number of evaluations for the minimizer.
146
+
147
+ :param max_evaluations: Maximal number of steps for the minimizer
148
+ """
149
+ self._max_evaluations = max_evaluations
150
+
113
151
  @property
114
152
  def fit_function(self) -> Callable:
115
153
  """
@@ -175,7 +213,7 @@ class Fitter:
175
213
  re-constitute the independent variables and once the fit is completed, reshape the inputs to those expected.
176
214
  """
177
215
 
178
- @functools.wraps(self.minimizer.fit)
216
+ @functools.wraps(self._minimizer.fit)
179
217
  def inner_fit_callable(
180
218
  x: np.ndarray,
181
219
  y: np.ndarray,
@@ -202,7 +240,14 @@ class Fitter:
202
240
  constraints = self._minimizer.fit_constraints()
203
241
  self.fit_function = fit_fun_wrap
204
242
  self._minimizer.set_fit_constraint(constraints)
205
- f_res = self.minimizer.fit(x_fit, y_new, weights=weights, **kwargs)
243
+ f_res = self._minimizer.fit(
244
+ x_fit,
245
+ y_new,
246
+ weights=weights,
247
+ tolerance=self._tolerance,
248
+ max_evaluations=self._max_evaluations,
249
+ **kwargs,
250
+ )
206
251
 
207
252
  # Postcompute
208
253
  fit_result = self._post_compute_reshaping(f_res, x, y)
@@ -40,7 +40,7 @@ class MinimizerBase(metaclass=ABCMeta):
40
40
  self,
41
41
  obj, #: BaseObj,
42
42
  fit_function: Callable,
43
- minimizer_enum: Optional[AvailableMinimizers] = None,
43
+ minimizer_enum: AvailableMinimizers,
44
44
  ): # todo after constraint changes, add type hint: obj: BaseObj # noqa: E501
45
45
  if minimizer_enum.method not in self.supported_methods():
46
46
  raise FitError(f'Method {minimizer_enum.method} not available in {self.__class__}')
@@ -58,6 +58,10 @@ class MinimizerBase(metaclass=ABCMeta):
58
58
  def all_constraints(self) -> List[ObjConstraint]:
59
59
  return [*self._constraints, *self._object._constraints]
60
60
 
61
+ @property
62
+ def enum(self) -> AvailableMinimizers:
63
+ return self._minimizer_enum
64
+
61
65
  @property
62
66
  def name(self) -> str:
63
67
  return self._minimizer_enum.name
@@ -83,6 +87,8 @@ class MinimizerBase(metaclass=ABCMeta):
83
87
  model: Optional[Callable] = None,
84
88
  parameters: Optional[Parameter] = None,
85
89
  method: Optional[str] = None,
90
+ tolerance: Optional[float] = None,
91
+ max_evaluations: Optional[int] = None,
86
92
  **kwargs,
87
93
  ) -> FitResults:
88
94
  """
@@ -129,7 +135,7 @@ class MinimizerBase(metaclass=ABCMeta):
129
135
 
130
136
  return self._fit_function(x, **minimizer_parameters, **kwargs)
131
137
 
132
- def _get_method_dict(self, passed_method: Optional[str] = None) -> dict[str, str]:
138
+ def _get_method_kwargs(self, passed_method: Optional[str] = None) -> dict[str, str]:
133
139
  if passed_method is not None:
134
140
  if passed_method not in self.supported_methods():
135
141
  raise FitError(f'Method {passed_method} not available in {self.__class__}')
@@ -74,6 +74,8 @@ class Bumps(MinimizerBase):
74
74
  model: Optional[Callable] = None,
75
75
  parameters: Optional[Parameter] = None,
76
76
  method: Optional[str] = None,
77
+ tolerance: Optional[float] = None,
78
+ max_evaluations: Optional[int] = None,
77
79
  minimizer_kwargs: Optional[dict] = None,
78
80
  engine_kwargs: Optional[dict] = None,
79
81
  **kwargs,
@@ -97,7 +99,7 @@ class Bumps(MinimizerBase):
97
99
  :return: Fit results
98
100
  :rtype: ModelResult
99
101
  """
100
- method_dict = self._get_method_dict(method)
102
+ method_dict = self._get_method_kwargs(method)
101
103
 
102
104
  if weights is None:
103
105
  weights = np.sqrt(np.abs(y))
@@ -107,10 +109,14 @@ class Bumps(MinimizerBase):
107
109
 
108
110
  if minimizer_kwargs is None:
109
111
  minimizer_kwargs = {}
110
- # else:
111
- # minimizer_kwargs = {"fit_kws": minimizer_kwargs}
112
112
  minimizer_kwargs.update(engine_kwargs)
113
113
 
114
+ if tolerance is not None:
115
+ minimizer_kwargs['ftol'] = tolerance # tolerance for change in function value
116
+ minimizer_kwargs['xtol'] = tolerance # tolerance for change in parameter value, could be an independent value
117
+ if max_evaluations is not None:
118
+ minimizer_kwargs['steps'] = max_evaluations
119
+
114
120
  if model is None:
115
121
  model_function = self._make_model(parameters=parameters)
116
122
  model = model_function(x, y, weights)
@@ -53,9 +53,7 @@ class DFO(MinimizerBase):
53
53
 
54
54
  @staticmethod
55
55
  def all_methods() -> List[str]:
56
- return [
57
- 'leastsq',
58
- ]
56
+ return ['leastsq']
59
57
 
60
58
  def fit(
61
59
  self,
@@ -65,8 +63,8 @@ class DFO(MinimizerBase):
65
63
  model: Optional[Callable] = None,
66
64
  parameters: Optional[List[Parameter]] = None,
67
65
  method: str = None,
68
- xtol: float = 1e-6,
69
- ftol: float = 1e-8,
66
+ tolerance: Optional[float] = None,
67
+ max_evaluations: Optional[int] = None,
70
68
  **kwargs,
71
69
  ) -> FitResults:
72
70
  """
@@ -110,6 +108,8 @@ class DFO(MinimizerBase):
110
108
  stack_status = global_object.stack.enabled
111
109
  global_object.stack.enabled = False
112
110
 
111
+ kwargs = self._prepare_kwargs(tolerance, max_evaluations, **kwargs)
112
+
113
113
  try:
114
114
  model_results = self._dfo_fit(self._cached_pars, model, **kwargs)
115
115
  self._set_parameter_fit_result(model_results, stack_status)
@@ -239,7 +239,11 @@ class DFO(MinimizerBase):
239
239
  return results
240
240
 
241
241
  @staticmethod
242
- def _dfo_fit(pars: Dict[str, Parameter], model: Callable, **kwargs):
242
+ def _dfo_fit(
243
+ pars: Dict[str, Parameter],
244
+ model: Callable,
245
+ **kwargs,
246
+ ):
243
247
  """
244
248
  Method to convert EasyScience styling to DFO-LS styling (yes, again)
245
249
 
@@ -261,13 +265,23 @@ class DFO(MinimizerBase):
261
265
  np.array([par.max for par in pars.values()]),
262
266
  )
263
267
  # https://numericalalgorithmsgroup.github.io/dfols/build/html/userguide.html
264
- if np.isinf(bounds).any():
265
- results = dfols.solve(model, pars_values, bounds=bounds, **kwargs)
266
- else:
268
+ if not np.isinf(bounds).any():
267
269
  # It is only possible to scale (normalize) variables if they are bound (different from inf)
268
- results = dfols.solve(model, pars_values, bounds=bounds, scaling_within_bounds=True, **kwargs)
270
+ kwargs['scaling_within_bounds'] = True
271
+
272
+ results = dfols.solve(model, pars_values, bounds=bounds, **kwargs)
269
273
 
270
274
  if 'Success' not in results.msg:
271
275
  raise FitError(f'Fit failed with message: {results.msg}')
272
276
 
273
277
  return results
278
+
279
+ @staticmethod
280
+ def _prepare_kwargs(tolerance: Optional[float] = None, max_evaluations: Optional[int] = None, **kwargs) -> dict[str:str]:
281
+ if max_evaluations is not None:
282
+ kwargs['maxfun'] = max_evaluations # max number of function evaluations
283
+ if tolerance is not None:
284
+ if 0.1 < tolerance: # dfo module throws errer if larger value
285
+ raise ValueError('Tolerance must be equal or smaller than 0.1')
286
+ kwargs['rhoend'] = tolerance # size of the trust region
287
+ return kwargs
@@ -85,6 +85,8 @@ class LMFit(MinimizerBase): # noqa: S101
85
85
  model: Optional[LMModel] = None,
86
86
  parameters: Optional[LMParameters] = None,
87
87
  method: Optional[str] = None,
88
+ tolerance: Optional[float] = None,
89
+ max_evaluations: Optional[int] = None,
88
90
  minimizer_kwargs: Optional[dict] = None,
89
91
  engine_kwargs: Optional[dict] = None,
90
92
  **kwargs,
@@ -110,19 +112,14 @@ class LMFit(MinimizerBase): # noqa: S101
110
112
  :return: Fit results
111
113
  :rtype: ModelResult
112
114
  """
113
- method_dict = self._get_method_dict(method)
114
-
115
115
  if weights is None:
116
116
  weights = 1 / np.sqrt(np.abs(y))
117
117
 
118
118
  if engine_kwargs is None:
119
119
  engine_kwargs = {}
120
120
 
121
- if minimizer_kwargs is None:
122
- minimizer_kwargs = {}
123
- else:
124
- minimizer_kwargs = {'fit_kws': minimizer_kwargs}
125
- minimizer_kwargs.update(engine_kwargs)
121
+ method_kwargs = self._get_method_kwargs(method)
122
+ fit_kws_dict = self._get_fit_kws(method, tolerance, minimizer_kwargs)
126
123
 
127
124
  # Why do we do this? Because a fitting template has to have global_object instantiated outside pre-runtime
128
125
  from easyscience import global_object
@@ -134,7 +131,16 @@ class LMFit(MinimizerBase): # noqa: S101
134
131
  if model is None:
135
132
  model = self._make_model()
136
133
 
137
- model_results = model.fit(y, x=x, weights=weights, **method_dict, **minimizer_kwargs, **kwargs)
134
+ model_results = model.fit(
135
+ y,
136
+ x=x,
137
+ weights=weights,
138
+ max_nfev=max_evaluations,
139
+ fit_kws=fit_kws_dict,
140
+ **method_kwargs,
141
+ **engine_kwargs,
142
+ **kwargs,
143
+ )
138
144
  self._set_parameter_fit_result(model_results, stack_status)
139
145
  results = self._gen_fit_results(model_results)
140
146
  except Exception as e:
@@ -143,6 +149,16 @@ class LMFit(MinimizerBase): # noqa: S101
143
149
  raise FitError(e)
144
150
  return results
145
151
 
152
+ def _get_fit_kws(self, method: str, tolerance: float, minimizer_kwargs: dict[str:str]) -> dict[str:str]:
153
+ if minimizer_kwargs is None:
154
+ minimizer_kwargs = {}
155
+ if tolerance is not None:
156
+ if method in [None, 'least_squares', 'leastsq']:
157
+ minimizer_kwargs['ftol'] = tolerance
158
+ if method in ['differential_evolution', 'powell', 'cobyla']:
159
+ minimizer_kwargs['tol'] = tolerance
160
+ return minimizer_kwargs
161
+
146
162
  def convert_to_pars_obj(self, parameters: Optional[List[Parameter]] = None) -> LMParameters:
147
163
  """
148
164
  Create an lmfit compatible container with the `Parameters` converted from the base object.
@@ -1 +0,0 @@
1
- __version__ = '1.1.2'
File without changes
File without changes
File without changes
File without changes