ChessAnalysisPipeline 0.0.5__py3-none-any.whl → 0.0.6__py3-none-any.whl

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.

Potentially problematic release.


This version of ChessAnalysisPipeline might be problematic. Click here for more details.

Files changed (41) hide show
  1. CHAP/TaskManager.py +214 -0
  2. CHAP/common/models/integration.py +392 -249
  3. CHAP/common/models/map.py +350 -198
  4. CHAP/common/processor.py +227 -189
  5. CHAP/common/reader.py +52 -39
  6. CHAP/common/utils/fit.py +1197 -991
  7. CHAP/common/utils/general.py +629 -372
  8. CHAP/common/utils/material.py +158 -121
  9. CHAP/common/utils/scanparsers.py +735 -339
  10. CHAP/common/writer.py +31 -25
  11. CHAP/edd/models.py +63 -49
  12. CHAP/edd/processor.py +130 -109
  13. CHAP/edd/reader.py +1 -1
  14. CHAP/edd/writer.py +1 -1
  15. CHAP/inference/processor.py +35 -28
  16. CHAP/inference/reader.py +1 -1
  17. CHAP/inference/writer.py +1 -1
  18. CHAP/pipeline.py +14 -28
  19. CHAP/processor.py +44 -75
  20. CHAP/reader.py +49 -40
  21. CHAP/runner.py +73 -32
  22. CHAP/saxswaxs/processor.py +1 -1
  23. CHAP/saxswaxs/reader.py +1 -1
  24. CHAP/saxswaxs/writer.py +1 -1
  25. CHAP/server.py +130 -0
  26. CHAP/sin2psi/processor.py +1 -1
  27. CHAP/sin2psi/reader.py +1 -1
  28. CHAP/sin2psi/writer.py +1 -1
  29. CHAP/tomo/__init__.py +1 -4
  30. CHAP/tomo/models.py +53 -31
  31. CHAP/tomo/processor.py +1326 -900
  32. CHAP/tomo/reader.py +4 -2
  33. CHAP/tomo/writer.py +4 -2
  34. CHAP/writer.py +47 -41
  35. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/METADATA +1 -1
  36. ChessAnalysisPipeline-0.0.6.dist-info/RECORD +52 -0
  37. ChessAnalysisPipeline-0.0.5.dist-info/RECORD +0 -50
  38. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/LICENSE +0 -0
  39. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/WHEEL +0 -0
  40. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/entry_points.txt +0 -0
  41. {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/top_level.txt +0 -0
CHAP/common/utils/fit.py CHANGED
@@ -1,83 +1,107 @@
1
- #!/usr/bin/env python3
2
-
3
- # -*- coding: utf-8 -*-
1
+ #!/usr/bin/env python
2
+ #-*- coding: utf-8 -*-
3
+ #pylint: disable=
4
4
  """
5
- Created on Mon Dec 6 15:36:22 2021
6
-
7
- @author: rv43
5
+ File : fit.py
6
+ Author : Rolf Verberg <rolfverberg AT gmail dot com>
7
+ Description: General curve fitting module
8
8
  """
9
9
 
10
- import logging
11
-
12
- try:
13
- from asteval import Interpreter, get_ast_names
14
- except:
15
- pass
10
+ # System modules
16
11
  from copy import deepcopy
12
+ from logging import getLogger
13
+ from os import (
14
+ cpu_count,
15
+ mkdir,
16
+ path,
17
+ )
18
+ from re import compile as re_compile
19
+ from re import sub
20
+ from shutil import rmtree
21
+ from sys import float_info
22
+
23
+ # Third party modules
17
24
  try:
18
- from lmfit import Model, Parameters
19
- from lmfit.model import ModelResult
20
- from lmfit.models import ConstantModel, LinearModel, QuadraticModel, PolynomialModel,\
21
- ExponentialModel, StepModel, RectangleModel, ExpressionModel, GaussianModel,\
22
- LorentzianModel
23
- except:
24
- pass
25
+ from joblib import (
26
+ Parallel,
27
+ delayed,
28
+ )
29
+ HAVE_JOBLIB = True
30
+ except ImportError:
31
+ HAVE_JOBLIB = False
32
+ from lmfit import (
33
+ Parameters,
34
+ Model,
35
+ )
36
+ from lmfit.model import ModelResult
37
+ from lmfit.models import (
38
+ ConstantModel,
39
+ LinearModel,
40
+ QuadraticModel,
41
+ PolynomialModel,
42
+ ExponentialModel,
43
+ StepModel,
44
+ RectangleModel,
45
+ ExpressionModel,
46
+ GaussianModel,
47
+ LorentzianModel,
48
+ )
25
49
  import numpy as np
26
- from os import cpu_count, getpid, listdir, mkdir, path
27
- from re import compile, sub
28
- from shutil import rmtree
29
50
  try:
30
- from sympy import diff, simplify
31
- except:
51
+ from sympy import (
52
+ diff,
53
+ simplify,
54
+ )
55
+ except ImportError:
32
56
  pass
33
- try:
34
- from joblib import Parallel, delayed
35
- have_joblib = True
36
- except:
37
- have_joblib = False
38
57
  try:
39
58
  import xarray as xr
40
- have_xarray = True
41
- except:
42
- have_xarray = False
43
-
44
- try:
45
- from .general import illegal_value, is_int, is_dict_series, is_index, index_nearest, \
46
- almost_equal, quick_plot #, eval_expr
47
- except:
48
- try:
49
- from sys import path as syspath
50
- syspath.append(f'/nfs/chess/user/rv43/msnctools/msnctools')
51
- from general import illegal_value, is_int, is_dict_series, is_index, index_nearest, \
52
- almost_equal, quick_plot #, eval_expr
53
- except:
54
- from general import illegal_value, is_int, is_dict_series, is_index, index_nearest, \
55
- almost_equal, quick_plot #, eval_expr
56
-
57
- from sys import float_info
58
- float_min = float_info.min
59
- float_max = float_info.max
59
+ HAVE_XARRAY = True
60
+ except ImportError:
61
+ HAVE_XARRAY = False
62
+
63
+ # Local modules
64
+ from CHAP.common.utils.general import (
65
+ is_int,
66
+ is_num,
67
+ is_dict_series,
68
+ is_index,
69
+ index_nearest,
70
+ input_num,
71
+ quick_plot,
72
+ )
73
+ # eval_expr,
74
+
75
+ logger = getLogger(__name__)
76
+ FLOAT_MIN = float_info.min
77
+ FLOAT_MAX = float_info.max
60
78
 
61
79
  # sigma = fwhm_factor*fwhm
62
80
  fwhm_factor = {
63
- 'gaussian': f'fwhm/(2*sqrt(2*log(2)))',
64
- 'lorentzian': f'0.5*fwhm',
65
- 'splitlorentzian': f'0.5*fwhm', # sigma = sigma_r
66
- 'voight': f'0.2776*fwhm', # sigma = gamma
67
- 'pseudovoight': f'0.5*fwhm'} # fraction = 0.5
81
+ 'gaussian': 'fwhm/(2*sqrt(2*log(2)))',
82
+ 'lorentzian': '0.5*fwhm',
83
+ 'splitlorentzian': '0.5*fwhm', # sigma = sigma_r
84
+ 'voight': '0.2776*fwhm', # sigma = gamma
85
+ 'pseudovoight': '0.5*fwhm', # fraction = 0.5
86
+ }
68
87
 
69
88
  # amplitude = height_factor*height*fwhm
70
89
  height_factor = {
71
- 'gaussian': f'height*fwhm*0.5*sqrt(pi/log(2))',
72
- 'lorentzian': f'height*fwhm*0.5*pi',
73
- 'splitlorentzian': f'height*fwhm*0.5*pi', # sigma = sigma_r
74
- 'voight': f'3.334*height*fwhm', # sigma = gamma
75
- 'pseudovoight': f'1.268*height*fwhm'} # fraction = 0.5
90
+ 'gaussian': 'height*fwhm*0.5*sqrt(pi/log(2))',
91
+ 'lorentzian': 'height*fwhm*0.5*pi',
92
+ 'splitlorentzian': 'height*fwhm*0.5*pi', # sigma = sigma_r
93
+ 'voight': '3.334*height*fwhm', # sigma = gamma
94
+ 'pseudovoight': '1.268*height*fwhm', # fraction = 0.5
95
+ }
96
+
76
97
 
77
98
  class Fit:
78
- """Wrapper class for lmfit
99
+ """
100
+ Wrapper class for lmfit.
79
101
  """
80
102
  def __init__(self, y, x=None, models=None, normalize=True, **kwargs):
103
+ """Initialize Fit."""
104
+ # Third party modules
81
105
  if not isinstance(normalize, bool):
82
106
  raise ValueError(f'Invalid parameter normalize ({normalize})')
83
107
  self._mask = None
@@ -95,28 +119,33 @@ class Fit:
95
119
  self._y_norm = None
96
120
  self._y_range = None
97
121
  if 'try_linear_fit' in kwargs:
98
- try_linear_fit = kwargs.pop('try_linear_fit')
99
- if not isinstance(try_linear_fit, bool):
100
- illegal_value(try_linear_fit, 'try_linear_fit', 'Fit.fit', raise_error=True)
101
- self._try_linear_fit = try_linear_fit
122
+ self._try_linear_fit = kwargs.pop('try_linear_fit')
123
+ if not isinstance(self._try_linear_fit, bool):
124
+ raise ValueError(
125
+ 'Invalid value of keyword argument try_linear_fit '
126
+ f'({self._try_linear_fit})')
102
127
  if y is not None:
103
128
  if isinstance(y, (tuple, list, np.ndarray)):
104
129
  self._x = np.asarray(x)
105
130
  self._y = np.asarray(y)
106
- elif have_xarray and isinstance(y, xr.DataArray):
131
+ elif HAVE_XARRAY and isinstance(y, xr.DataArray):
107
132
  if x is not None:
108
- logging.warning('Ignoring superfluous input x ({x}) in Fit.__init__')
133
+ logger.warning('Ignoring superfluous input x ({x})')
109
134
  if y.ndim != 1:
110
- illegal_value(y.ndim, 'DataArray dimensions', 'Fit:__init__', raise_error=True)
135
+ raise ValueError(
136
+ 'Invalid DataArray dimensions for parameter y '
137
+ f'({y.ndim})')
111
138
  self._x = np.asarray(y[y.dims[0]])
112
139
  self._y = y
113
140
  else:
114
- illegal_value(y, 'y', 'Fit:__init__', raise_error=True)
141
+ raise ValueError(f'Invalid parameter y ({y})')
115
142
  if self._x.ndim != 1:
116
- raise ValueError(f'Invalid dimension for input x ({self._x.ndim})')
143
+ raise ValueError(
144
+ f'Invalid dimension for input x ({self._x.ndim})')
117
145
  if self._x.size != self._y.size:
118
- raise ValueError(f'Inconsistent x and y dimensions ({self._x.size} vs '+
119
- f'{self._y.size})')
146
+ raise ValueError(
147
+ f'Inconsistent x and y dimensions ({self._x.size} vs '
148
+ f'{self._y.size})')
120
149
  if 'mask' in kwargs:
121
150
  self._mask = kwargs.pop('mask')
122
151
  if self._mask is None:
@@ -127,8 +156,9 @@ class Fit:
127
156
  else:
128
157
  self._mask = np.asarray(self._mask).astype(bool)
129
158
  if self._x.size != self._mask.size:
130
- raise ValueError(f'Inconsistent x and mask dimensions ({self._x.size} vs '+
131
- f'{self._mask.size})')
159
+ raise ValueError(
160
+ f'Inconsistent x and mask dimensions ({self._x.size} '
161
+ f'vs {self._mask.size})')
132
162
  y_masked = np.asarray(self._y)[~self._mask]
133
163
  y_min = float(y_masked.min())
134
164
  self._y_range = float(y_masked.max())-y_min
@@ -145,88 +175,109 @@ class Fit:
145
175
 
146
176
  @classmethod
147
177
  def fit_data(cls, y, models, x=None, normalize=True, **kwargs):
148
- return(cls(y, x=x, models=models, normalize=normalize, **kwargs))
178
+ """Class method for Fit."""
179
+ return cls(y, x=x, models=models, normalize=normalize, **kwargs)
149
180
 
150
181
  @property
151
182
  def best_errors(self):
183
+ """Return errors in the best fit parameters."""
152
184
  if self._result is None:
153
- return(None)
154
- return({name:self._result.params[name].stderr for name in sorted(self._result.params)
155
- if name != 'tmp_normalization_offset_c'})
185
+ return None
186
+ return {name:self._result.params[name].stderr
187
+ for name in sorted(self._result.params)
188
+ if name != 'tmp_normalization_offset_c'}
156
189
 
157
190
  @property
158
191
  def best_fit(self):
192
+ """Return the best fit."""
159
193
  if self._result is None:
160
- return(None)
161
- return(self._result.best_fit)
194
+ return None
195
+ return self._result.best_fit
162
196
 
163
- @property
164
197
  def best_parameters(self):
198
+ """Return the best fit parameters."""
165
199
  if self._result is None:
166
- return(None)
200
+ return None
167
201
  parameters = {}
168
202
  for name in sorted(self._result.params):
169
203
  if name != 'tmp_normalization_offset_c':
170
204
  par = self._result.params[name]
171
- parameters[name] = {'value': par.value, 'error': par.stderr,
172
- 'init_value': par.init_value, 'min': par.min, 'max': par.max,
173
- 'vary': par.vary, 'expr': par.expr}
174
- return(parameters)
205
+ parameters[name] = {
206
+ 'value': par.value,
207
+ 'error': par.stderr,
208
+ 'init_value': par.init_value,
209
+ 'min': par.min,
210
+ 'max': par.max,
211
+ 'vary': par.vary, 'expr': par.expr
212
+ }
213
+ return parameters
175
214
 
176
215
  @property
177
216
  def best_results(self):
178
- """Convert the input data array to a data set and add the fit results.
217
+ """
218
+ Convert the input DataArray to a data set and add the fit
219
+ results.
179
220
  """
180
221
  if self._result is None:
181
- return(None)
182
- if not have_xarray:
183
- logging.warning('fit.best_results requires xarray in the conda environment')
184
- return(None)
222
+ return None
223
+ if not HAVE_XARRAY:
224
+ logger.warning(
225
+ 'fit.best_results requires xarray in the conda environment')
226
+ return None
185
227
  if isinstance(self._y, xr.DataArray):
186
228
  best_results = self._y.to_dataset()
187
229
  dims = self._y.dims
188
230
  fit_name = f'{self._y.name}_fit'
189
231
  else:
190
232
  coords = {'x': (['x'], self._x)}
191
- dims = ('x')
233
+ dims = ('x',)
192
234
  best_results = xr.Dataset(coords=coords)
193
235
  best_results['y'] = (dims, self._y)
194
236
  fit_name = 'y_fit'
195
237
  best_results[fit_name] = (dims, self.best_fit)
196
238
  if self._mask is not None:
197
239
  best_results['mask'] = self._mask
198
- best_results.coords['par_names'] = ('peak', [name for name in self.best_values.keys()])
199
- best_results['best_values'] = (['par_names'], [v for v in self.best_values.values()])
200
- best_results['best_errors'] = (['par_names'], [v for v in self.best_errors.values()])
240
+ best_results.coords['par_names'] = ('peak', self.best_values.keys())
241
+ best_results['best_values'] = \
242
+ (['par_names'], self.best_values.values())
243
+ best_results['best_errors'] = \
244
+ (['par_names'], self.best_errors.values())
201
245
  best_results.attrs['components'] = self.components
202
- return(best_results)
246
+ return best_results
203
247
 
204
248
  @property
205
249
  def best_values(self):
250
+ """Return values of the best fit parameters."""
206
251
  if self._result is None:
207
- return(None)
208
- return({name:self._result.params[name].value for name in sorted(self._result.params)
209
- if name != 'tmp_normalization_offset_c'})
252
+ return None
253
+ return {name:self._result.params[name].value
254
+ for name in sorted(self._result.params)
255
+ if name != 'tmp_normalization_offset_c'}
210
256
 
211
257
  @property
212
258
  def chisqr(self):
259
+ """Return the chisqr value of the best fit."""
213
260
  if self._result is None:
214
- return(None)
215
- return(self._result.chisqr)
261
+ return None
262
+ return self._result.chisqr
216
263
 
217
264
  @property
218
265
  def components(self):
266
+ """Return the fit model components info."""
219
267
  components = {}
220
268
  if self._result is None:
221
- logging.warning('Unable to collect components in Fit.components')
222
- return(components)
269
+ logger.warning('Unable to collect components in Fit.components')
270
+ return components
223
271
  for component in self._result.components:
224
272
  if 'tmp_normalization_offset_c' in component.param_names:
225
273
  continue
226
274
  parameters = {}
227
275
  for name in component.param_names:
228
276
  par = self._parameters[name]
229
- parameters[name] = {'free': par.vary, 'value': self._result.params[name].value}
277
+ parameters[name] = {
278
+ 'free': par.vary,
279
+ 'value': self._result.params[name].value,
280
+ }
230
281
  if par.expr is not None:
231
282
  parameters[name]['expr'] = par.expr
232
283
  expr = None
@@ -237,170 +288,198 @@ class Fit:
237
288
  expr = component.expr
238
289
  else:
239
290
  prefix = component.prefix
240
- if len(prefix):
291
+ if prefix:
241
292
  if prefix[-1] == '_':
242
293
  prefix = prefix[:-1]
243
294
  name = f'{prefix} ({component._name})'
244
295
  else:
245
296
  name = f'{component._name}'
246
297
  if expr is None:
247
- components[name] = {'parameters': parameters}
298
+ components[name] = {
299
+ 'parameters': parameters,
300
+ }
248
301
  else:
249
- components[name] = {'expr': expr, 'parameters': parameters}
250
- return(components)
302
+ components[name] = {
303
+ 'expr': expr,
304
+ 'parameters': parameters,
305
+ }
306
+ return components
251
307
 
252
308
  @property
253
309
  def covar(self):
310
+ """Return the covarience matrix of the best fit parameters."""
254
311
  if self._result is None:
255
- return(None)
256
- return(self._result.covar)
312
+ return None
313
+ return self._result.covar
257
314
 
258
315
  @property
259
316
  def init_parameters(self):
317
+ """Return the initial parameters for the fit model."""
260
318
  if self._result is None or self._result.init_params is None:
261
- return(None)
319
+ return None
262
320
  parameters = {}
263
321
  for name in sorted(self._result.init_params):
264
322
  if name != 'tmp_normalization_offset_c':
265
323
  par = self._result.init_params[name]
266
- parameters[name] = {'value': par.value, 'min': par.min, 'max': par.max,
267
- 'vary': par.vary, 'expr': par.expr}
268
- return(parameters)
324
+ parameters[name] = {
325
+ 'value': par.value,
326
+ 'min': par.min,
327
+ 'max': par.max,
328
+ 'vary': par.vary,
329
+ 'expr': par.expr,
330
+ }
331
+ return parameters
269
332
 
270
333
  @property
271
334
  def init_values(self):
335
+ """Return the initial values for the fit parameters."""
272
336
  if self._result is None or self._result.init_params is None:
273
- return(None)
274
- return({name:self._result.init_params[name].value for name in
275
- sorted(self._result.init_params) if name != 'tmp_normalization_offset_c'})
337
+ return None
338
+ return {name:self._result.init_params[name].value
339
+ for name in sorted(self._result.init_params)
340
+ if name != 'tmp_normalization_offset_c'}
276
341
 
277
342
  @property
278
343
  def normalization_offset(self):
344
+ """Return the normalization_offset for the fit model."""
279
345
  if self._result is None:
280
- return(None)
346
+ return None
281
347
  if self._norm is None:
282
- return(0.0)
348
+ return 0.0
349
+ if self._result.init_params is not None:
350
+ normalization_offset = float(
351
+ self._result.init_params['tmp_normalization_offset_c'])
283
352
  else:
284
- if self._result.init_params is not None:
285
- normalization_offset = float(self._result.init_params['tmp_normalization_offset_c'])
286
- else:
287
- normalization_offset = float(self._result.params['tmp_normalization_offset_c'])
288
- return(normalization_offset)
353
+ normalization_offset = float(
354
+ self._result.params['tmp_normalization_offset_c'])
355
+ return normalization_offset
289
356
 
290
357
  @property
291
358
  def num_func_eval(self):
359
+ """
360
+ Return the number of function evaluations for the best fit.
361
+ """
292
362
  if self._result is None:
293
- return(None)
294
- return(self._result.nfev)
363
+ return None
364
+ return self._result.nfev
295
365
 
296
366
  @property
297
367
  def parameters(self):
298
- return({name:{'min': par.min, 'max': par.max, 'vary': par.vary, 'expr': par.expr}
299
- for name, par in self._parameters.items() if name != 'tmp_normalization_offset_c'})
368
+ """Return the fit parameter info."""
369
+ return {name:{'min': par.min, 'max': par.max, 'vary': par.vary,
370
+ 'expr': par.expr} for name, par in self._parameters.items()
371
+ if name != 'tmp_normalization_offset_c'}
300
372
 
301
373
  @property
302
374
  def redchi(self):
375
+ """Return the redchi value of the best fit."""
303
376
  if self._result is None:
304
- return(None)
305
- return(self._result.redchi)
377
+ return None
378
+ return self._result.redchi
306
379
 
307
380
  @property
308
381
  def residual(self):
382
+ """Return the residual in the best fit."""
309
383
  if self._result is None:
310
- return(None)
311
- return(self._result.residual)
384
+ return None
385
+ return self._result.residual
312
386
 
313
387
  @property
314
388
  def success(self):
389
+ """Return the success value for the fit."""
315
390
  if self._result is None:
316
- return(None)
391
+ return None
317
392
  if not self._result.success:
318
- # print(f'ier = {self._result.ier}')
319
- # print(f'lmdif_message = {self._result.lmdif_message}')
320
- # print(f'message = {self._result.message}')
321
- # print(f'nfev = {self._result.nfev}')
322
- # print(f'redchi = {self._result.redchi}')
323
- # print(f'success = {self._result.success}')
324
- if self._result.ier == 0 or self._result.ier == 5:
325
- logging.warning(f'ier = {self._result.ier}: {self._result.message}')
326
- else:
327
- logging.warning(f'ier = {self._result.ier}: {self._result.message}')
328
- return(True)
329
- # self.print_fit_report()
330
- # self.plot()
331
- return(self._result.success)
393
+ logger.warning(
394
+ f'ier = {self._result.ier}: {self._result.message}')
395
+ if self._result.ier and self._result.ier != 5:
396
+ return True
397
+ return self._result.success
332
398
 
333
399
  @property
334
400
  def var_names(self):
335
- """Intended to be used with covar
401
+ """
402
+ Return the variable names for the covarience matrix property.
336
403
  """
337
404
  if self._result is None:
338
- return(None)
339
- return(getattr(self._result, 'var_names', None))
405
+ return None
406
+ return getattr(self._result, 'var_names', None)
340
407
 
341
408
  @property
342
409
  def x(self):
343
- return(self._x)
410
+ """Return the input x-array."""
411
+ return self._x
344
412
 
345
413
  @property
346
414
  def y(self):
347
- return(self._y)
415
+ """Return the input y-array."""
416
+ return self._y
348
417
 
349
418
  def print_fit_report(self, result=None, show_correl=False):
419
+ """Print a fit report."""
350
420
  if result is None:
351
421
  result = self._result
352
422
  if result is not None:
353
423
  print(result.fit_report(show_correl=show_correl))
354
424
 
355
425
  def add_parameter(self, **parameter):
426
+ """Add a fit fit parameter to the fit model."""
356
427
  if not isinstance(parameter, dict):
357
428
  raise ValueError(f'Invalid parameter ({parameter})')
358
429
  if parameter.get('expr') is not None:
359
430
  raise KeyError(f'Invalid "expr" key in parameter {parameter}')
360
431
  name = parameter['name']
361
432
  if not isinstance(name, str):
362
- raise ValueError(f'Invalid "name" value ({name}) in parameter {parameter}')
433
+ raise ValueError(
434
+ f'Invalid "name" value ({name}) in parameter {parameter}')
363
435
  if parameter.get('norm') is None:
364
436
  self._parameter_norms[name] = False
365
437
  else:
366
438
  norm = parameter.pop('norm')
367
439
  if self._norm is None:
368
- logging.warning(f'Ignoring norm in parameter {name} in '+
369
- f'Fit.add_parameter (normalization is turned off)')
440
+ logger.warning(
441
+ f'Ignoring norm in parameter {name} in Fit.add_parameter '
442
+ '(normalization is turned off)')
370
443
  self._parameter_norms[name] = False
371
444
  else:
372
445
  if not isinstance(norm, bool):
373
- raise ValueError(f'Invalid "norm" value ({norm}) in parameter {parameter}')
446
+ raise ValueError(
447
+ f'Invalid "norm" value ({norm}) in parameter '
448
+ f'{parameter}')
374
449
  self._parameter_norms[name] = norm
375
450
  vary = parameter.get('vary')
376
451
  if vary is not None:
377
452
  if not isinstance(vary, bool):
378
- raise ValueError(f'Invalid "vary" value ({vary}) in parameter {parameter}')
453
+ raise ValueError(
454
+ f'Invalid "vary" value ({vary}) in parameter {parameter}')
379
455
  if not vary:
380
456
  if 'min' in parameter:
381
- logging.warning(f'Ignoring min in parameter {name} in '+
382
- f'Fit.add_parameter (vary = {vary})')
457
+ logger.warning(
458
+ f'Ignoring min in parameter {name} in '
459
+ f'Fit.add_parameter (vary = {vary})')
383
460
  parameter.pop('min')
384
461
  if 'max' in parameter:
385
- logging.warning(f'Ignoring max in parameter {name} in '+
386
- f'Fit.add_parameter (vary = {vary})')
462
+ logger.warning(
463
+ f'Ignoring max in parameter {name} in '
464
+ f'Fit.add_parameter (vary = {vary})')
387
465
  parameter.pop('max')
388
466
  if self._norm is not None and name not in self._parameter_norms:
389
- raise ValueError(f'Missing parameter normalization type for paremeter {name}')
467
+ raise ValueError(
468
+ f'Missing parameter normalization type for parameter {name}')
390
469
  self._parameters.add(**parameter)
391
470
 
392
- def add_model(self, model, prefix=None, parameters=None, parameter_norms=None, **kwargs):
393
- # Create the new model
394
- # print(f'at start add_model:\nself._parameters:\n{self._parameters}')
395
- # print(f'at start add_model: kwargs = {kwargs}')
396
- # print(f'parameters = {parameters}')
397
- # print(f'parameter_norms = {parameter_norms}')
398
- # if len(self._parameters.keys()):
399
- # print('\nAt start adding model:')
400
- # self._parameters.pretty_print()
401
- # print(f'parameter_norms:\n{self._parameter_norms}')
471
+ def add_model(
472
+ self, model, prefix=None, parameters=None, parameter_norms=None,
473
+ **kwargs):
474
+ """Add a model component to the fit model."""
475
+ # Third party modules
476
+ from asteval import (
477
+ Interpreter,
478
+ get_ast_names,
479
+ )
480
+
402
481
  if prefix is not None and not isinstance(prefix, str):
403
- logging.warning('Ignoring illegal prefix: {model} {type(model)}')
482
+ logger.warning('Ignoring illegal prefix: {model} {type(model)}')
404
483
  prefix = None
405
484
  if prefix is None:
406
485
  pprefix = ''
@@ -410,61 +489,79 @@ class Fit:
410
489
  if isinstance(parameters, dict):
411
490
  parameters = (parameters, )
412
491
  elif not is_dict_series(parameters):
413
- illegal_value(parameters, 'parameters', 'Fit.add_model', raise_error=True)
492
+ raise ValueError('Invalid parameter parameters ({parameters})')
414
493
  parameters = deepcopy(parameters)
415
494
  if parameter_norms is not None:
416
495
  if isinstance(parameter_norms, dict):
417
496
  parameter_norms = (parameter_norms, )
418
497
  if not is_dict_series(parameter_norms):
419
- illegal_value(parameter_norms, 'parameter_norms', 'Fit.add_model', raise_error=True)
498
+ raise ValueError(
499
+ 'Invalid parameter parameters_norms ({parameters_norms})')
420
500
  new_parameter_norms = {}
421
501
  if callable(model):
422
502
  # Linear fit not yet implemented for callable models
423
503
  self._try_linear_fit = False
424
504
  if parameter_norms is None:
425
505
  if parameters is None:
426
- raise ValueError('Either "parameters" or "parameter_norms" is required in '+
427
- f'{model}')
506
+ raise ValueError(
507
+ 'Either parameters or parameter_norms is required in '
508
+ f'{model}')
428
509
  for par in parameters:
429
510
  name = par['name']
430
511
  if not isinstance(name, str):
431
- raise ValueError(f'Invalid "name" value ({name}) in input parameters')
512
+ raise ValueError(
513
+ f'Invalid "name" value ({name}) in input '
514
+ 'parameters')
432
515
  if par.get('norm') is not None:
433
516
  norm = par.pop('norm')
434
517
  if not isinstance(norm, bool):
435
- raise ValueError(f'Invalid "norm" value ({norm}) in input parameters')
518
+ raise ValueError(
519
+ f'Invalid "norm" value ({norm}) in input '
520
+ 'parameters')
436
521
  new_parameter_norms[f'{pprefix}{name}'] = norm
437
522
  else:
438
523
  for par in parameter_norms:
439
524
  name = par['name']
440
525
  if not isinstance(name, str):
441
- raise ValueError(f'Invalid "name" value ({name}) in input parameters')
526
+ raise ValueError(
527
+ f'Invalid "name" value ({name}) in input '
528
+ 'parameters')
442
529
  norm = par.get('norm')
443
530
  if norm is None or not isinstance(norm, bool):
444
- raise ValueError(f'Invalid "norm" value ({norm}) in input parameters')
531
+ raise ValueError(
532
+ f'Invalid "norm" value ({norm}) in input '
533
+ 'parameters')
445
534
  new_parameter_norms[f'{pprefix}{name}'] = norm
446
535
  if parameters is not None:
447
536
  for par in parameters:
448
537
  if par.get('expr') is not None:
449
- raise KeyError(f'Invalid "expr" key ({par.get("expr")}) in parameter '+
450
- f'{name} for a callable model {model}')
538
+ raise KeyError(
539
+ f'Invalid "expr" key ({par.get("expr")}) in '
540
+ f'parameter {name} for a callable model {model}')
451
541
  name = par['name']
452
542
  if not isinstance(name, str):
453
- raise ValueError(f'Invalid "name" value ({name}) in input parameters')
454
- # RV FIX callable model will need partial deriv functions for any linear pars to get the linearized matrix, so for now skip linear solution option
543
+ raise ValueError(
544
+ f'Invalid "name" value ({name}) in input '
545
+ 'parameters')
546
+ # RV callable model will need partial deriv functions for any linear
547
+ # parameter to get the linearized matrix, so for now skip linear
548
+ # solution option
455
549
  newmodel = Model(model, prefix=prefix)
456
550
  elif isinstance(model, str):
457
- if model == 'constant': # Par: c
551
+ if model == 'constant':
552
+ # Par: c
458
553
  newmodel = ConstantModel(prefix=prefix)
459
554
  new_parameter_norms[f'{pprefix}c'] = True
460
555
  self._linear_parameters.append(f'{pprefix}c')
461
- elif model == 'linear': # Par: slope, intercept
556
+ elif model == 'linear':
557
+ # Par: slope, intercept
462
558
  newmodel = LinearModel(prefix=prefix)
463
559
  new_parameter_norms[f'{pprefix}slope'] = True
464
560
  new_parameter_norms[f'{pprefix}intercept'] = True
465
561
  self._linear_parameters.append(f'{pprefix}slope')
466
562
  self._linear_parameters.append(f'{pprefix}intercept')
467
- elif model == 'quadratic': # Par: a, b, c
563
+ elif model == 'quadratic':
564
+ # Par: a, b, c
468
565
  newmodel = QuadraticModel(prefix=prefix)
469
566
  new_parameter_norms[f'{pprefix}a'] = True
470
567
  new_parameter_norms[f'{pprefix}b'] = True
@@ -472,17 +569,21 @@ class Fit:
472
569
  self._linear_parameters.append(f'{pprefix}a')
473
570
  self._linear_parameters.append(f'{pprefix}b')
474
571
  self._linear_parameters.append(f'{pprefix}c')
475
- elif model == 'polynomial': # Par: c0, c1,..., c7
572
+ elif model == 'polynomial':
573
+ # Par: c0, c1,..., c7
476
574
  degree = kwargs.get('degree')
477
575
  if degree is not None:
478
576
  kwargs.pop('degree')
479
577
  if degree is None or not is_int(degree, ge=0, le=7):
480
- raise ValueError(f'Invalid parameter degree for build-in step model ({degree})')
578
+ raise ValueError(
579
+ 'Invalid parameter degree for build-in step model '
580
+ f'({degree})')
481
581
  newmodel = PolynomialModel(degree=degree, prefix=prefix)
482
582
  for i in range(degree+1):
483
583
  new_parameter_norms[f'{pprefix}c{i}'] = True
484
584
  self._linear_parameters.append(f'{pprefix}c{i}')
485
- elif model == 'gaussian': # Par: amplitude, center, sigma (fwhm, height)
585
+ elif model == 'gaussian':
586
+ # Par: amplitude, center, sigma (fwhm, height)
486
587
  newmodel = GaussianModel(prefix=prefix)
487
588
  new_parameter_norms[f'{pprefix}amplitude'] = True
488
589
  new_parameter_norms[f'{pprefix}center'] = False
@@ -490,10 +591,12 @@ class Fit:
490
591
  self._linear_parameters.append(f'{pprefix}amplitude')
491
592
  self._nonlinear_parameters.append(f'{pprefix}center')
492
593
  self._nonlinear_parameters.append(f'{pprefix}sigma')
493
- # parameter norms for height and fwhm are needed to get correct errors
594
+ # parameter norms for height and fwhm are needed to
595
+ # get correct errors
494
596
  new_parameter_norms[f'{pprefix}height'] = True
495
597
  new_parameter_norms[f'{pprefix}fwhm'] = False
496
- elif model == 'lorentzian': # Par: amplitude, center, sigma (fwhm, height)
598
+ elif model == 'lorentzian':
599
+ # Par: amplitude, center, sigma (fwhm, height)
497
600
  newmodel = LorentzianModel(prefix=prefix)
498
601
  new_parameter_norms[f'{pprefix}amplitude'] = True
499
602
  new_parameter_norms[f'{pprefix}center'] = False
@@ -501,21 +604,27 @@ class Fit:
501
604
  self._linear_parameters.append(f'{pprefix}amplitude')
502
605
  self._nonlinear_parameters.append(f'{pprefix}center')
503
606
  self._nonlinear_parameters.append(f'{pprefix}sigma')
504
- # parameter norms for height and fwhm are needed to get correct errors
607
+ # parameter norms for height and fwhm are needed to
608
+ # get correct errors
505
609
  new_parameter_norms[f'{pprefix}height'] = True
506
610
  new_parameter_norms[f'{pprefix}fwhm'] = False
507
- elif model == 'exponential': # Par: amplitude, decay
611
+ elif model == 'exponential':
612
+ # Par: amplitude, decay
508
613
  newmodel = ExponentialModel(prefix=prefix)
509
614
  new_parameter_norms[f'{pprefix}amplitude'] = True
510
615
  new_parameter_norms[f'{pprefix}decay'] = False
511
616
  self._linear_parameters.append(f'{pprefix}amplitude')
512
617
  self._nonlinear_parameters.append(f'{pprefix}decay')
513
- elif model == 'step': # Par: amplitude, center, sigma
618
+ elif model == 'step':
619
+ # Par: amplitude, center, sigma
514
620
  form = kwargs.get('form')
515
621
  if form is not None:
516
622
  kwargs.pop('form')
517
- if form is None or form not in ('linear', 'atan', 'arctan', 'erf', 'logistic'):
518
- raise ValueError(f'Invalid parameter form for build-in step model ({form})')
623
+ if (form is None or form not in
624
+ ('linear', 'atan', 'arctan', 'erf', 'logistic')):
625
+ raise ValueError(
626
+ 'Invalid parameter form for build-in step model '
627
+ f'({form})')
519
628
  newmodel = StepModel(prefix=prefix, form=form)
520
629
  new_parameter_norms[f'{pprefix}amplitude'] = True
521
630
  new_parameter_norms[f'{pprefix}center'] = False
@@ -523,13 +632,16 @@ class Fit:
523
632
  self._linear_parameters.append(f'{pprefix}amplitude')
524
633
  self._nonlinear_parameters.append(f'{pprefix}center')
525
634
  self._nonlinear_parameters.append(f'{pprefix}sigma')
526
- elif model == 'rectangle': # Par: amplitude, center1, center2, sigma1, sigma2
635
+ elif model == 'rectangle':
636
+ # Par: amplitude, center1, center2, sigma1, sigma2
527
637
  form = kwargs.get('form')
528
638
  if form is not None:
529
639
  kwargs.pop('form')
530
- if form is None or form not in ('linear', 'atan', 'arctan', 'erf', 'logistic'):
531
- raise ValueError('Invalid parameter form for build-in rectangle model '+
532
- f'({form})')
640
+ if (form is None or form not in
641
+ ('linear', 'atan', 'arctan', 'erf', 'logistic')):
642
+ raise ValueError(
643
+ 'Invalid parameter form for build-in rectangle model '
644
+ f'({form})')
533
645
  newmodel = RectangleModel(prefix=prefix, form=form)
534
646
  new_parameter_norms[f'{pprefix}amplitude'] = True
535
647
  new_parameter_norms[f'{pprefix}center1'] = False
@@ -541,86 +653,75 @@ class Fit:
541
653
  self._nonlinear_parameters.append(f'{pprefix}center2')
542
654
  self._nonlinear_parameters.append(f'{pprefix}sigma1')
543
655
  self._nonlinear_parameters.append(f'{pprefix}sigma2')
544
- elif model == 'expression': # Par: by expression
656
+ elif model == 'expression':
657
+ # Par: by expression
545
658
  expr = kwargs['expr']
546
659
  if not isinstance(expr, str):
547
- raise ValueError(f'Invalid "expr" value ({expr}) in {model}')
660
+ raise ValueError(
661
+ f'Invalid "expr" value ({expr}) in {model}')
548
662
  kwargs.pop('expr')
549
663
  if parameter_norms is not None:
550
- logging.warning('Ignoring parameter_norms (normalization determined from '+
551
- 'linearity)}')
664
+ logger.warning(
665
+ 'Ignoring parameter_norms (normalization '
666
+ 'determined from linearity)}')
552
667
  if parameters is not None:
553
668
  for par in parameters:
554
669
  if par.get('expr') is not None:
555
- raise KeyError(f'Invalid "expr" key ({par.get("expr")}) in parameter '+
556
- f'({par}) for an expression model')
670
+ raise KeyError(
671
+ f'Invalid "expr" key ({par.get("expr")}) in '
672
+ f'parameter ({par}) for an expression model')
557
673
  if par.get('norm') is not None:
558
- logging.warning(f'Ignoring "norm" key in parameter ({par}) '+
559
- '(normalization determined from linearity)}')
674
+ logger.warning(
675
+ f'Ignoring "norm" key in parameter ({par}) '
676
+ '(normalization determined from linearity)')
560
677
  par.pop('norm')
561
678
  name = par['name']
562
679
  if not isinstance(name, str):
563
- raise ValueError(f'Invalid "name" value ({name}) in input parameters')
680
+ raise ValueError(
681
+ f'Invalid "name" value ({name}) in input '
682
+ 'parameters')
564
683
  ast = Interpreter()
565
- expr_parameters = [name for name in get_ast_names(ast.parse(expr))
566
- if name != 'x' and name not in self._parameters
567
- and name not in ast.symtable]
568
- # print(f'\nexpr_parameters: {expr_parameters}')
569
- # print(f'expr = {expr}')
684
+ expr_parameters = [
685
+ name for name in get_ast_names(ast.parse(expr))
686
+ if (name != 'x' and name not in self._parameters
687
+ and name not in ast.symtable)]
570
688
  if prefix is None:
571
689
  newmodel = ExpressionModel(expr=expr)
572
690
  else:
573
691
  for name in expr_parameters:
574
692
  expr = sub(rf'\b{name}\b', f'{prefix}{name}', expr)
575
- expr_parameters = [f'{prefix}{name}' for name in expr_parameters]
576
- # print(f'\nexpr_parameters: {expr_parameters}')
577
- # print(f'expr = {expr}')
693
+ expr_parameters = [
694
+ f'{prefix}{name}' for name in expr_parameters]
578
695
  newmodel = ExpressionModel(expr=expr, name=name)
579
- # print(f'\nnewmodel = {newmodel.__dict__}')
580
- # print(f'params_names = {newmodel._param_names}')
581
- # print(f'params_names = {newmodel.param_names}')
582
696
  # Remove already existing names
583
697
  for name in newmodel.param_names.copy():
584
698
  if name not in expr_parameters:
585
699
  newmodel._func_allargs.remove(name)
586
700
  newmodel._param_names.remove(name)
587
- # print(f'params_names = {newmodel._param_names}')
588
- # print(f'params_names = {newmodel.param_names}')
589
701
  else:
590
702
  raise ValueError(f'Unknown build-in fit model ({model})')
591
703
  else:
592
- illegal_value(model, 'model', 'Fit.add_model', raise_error=True)
704
+ raise ValueError('Invalid parameter model ({model})')
593
705
 
594
706
  # Add the new model to the current one
595
- # print('\nBefore adding model:')
596
- # print(f'\nnewmodel = {newmodel.__dict__}')
597
- # if len(self._parameters):
598
- # self._parameters.pretty_print()
599
707
  if self._model is None:
600
708
  self._model = newmodel
601
709
  else:
602
710
  self._model += newmodel
603
711
  new_parameters = newmodel.make_params()
604
712
  self._parameters += new_parameters
605
- # print('\nAfter adding model:')
606
- # print(f'\nnewmodel = {newmodel.__dict__}')
607
- # print(f'\nnew_parameters = {new_parameters}')
608
- # self._parameters.pretty_print()
609
713
 
610
- # Check linearity of expression model paremeters
714
+ # Check linearity of expression model parameters
611
715
  if isinstance(newmodel, ExpressionModel):
612
716
  for name in newmodel.param_names:
613
717
  if not diff(newmodel.expr, name, name):
614
718
  if name not in self._linear_parameters:
615
719
  self._linear_parameters.append(name)
616
720
  new_parameter_norms[name] = True
617
- # print(f'\nADDING {name} TO LINEAR')
618
721
  else:
619
722
  if name not in self._nonlinear_parameters:
620
723
  self._nonlinear_parameters.append(name)
621
724
  new_parameter_norms[name] = False
622
- # print(f'\nADDING {name} TO NONLINEAR')
623
- # print(f'new_parameter_norms:\n{new_parameter_norms}')
624
725
 
625
726
  # Scale the default initial model parameters
626
727
  if self._norm is not None:
@@ -633,150 +734,164 @@ class Fit:
633
734
  value = par.value*self._norm[1]
634
735
  _min = par.min
635
736
  _max = par.max
636
- if not np.isinf(_min) and abs(_min) != float_min:
737
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
637
738
  _min *= self._norm[1]
638
- if not np.isinf(_max) and abs(_max) != float_min:
739
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
639
740
  _max *= self._norm[1]
640
741
  par.set(value=value, min=_min, max=_max)
641
- # print('\nAfter norm defaults:')
642
- # self._parameters.pretty_print()
643
- # print(f'parameters:\n{parameters}')
644
- # print(f'all_parameters:\n{list(self.parameters)}')
645
- # print(f'new_parameter_norms:\n{new_parameter_norms}')
646
- # print(f'parameter_norms:\n{self._parameter_norms}')
647
742
 
648
743
  # Initialize the model parameters from parameters
649
744
  if prefix is None:
650
- prefix = ""
745
+ prefix = ''
651
746
  if parameters is not None:
652
747
  for parameter in parameters:
653
748
  name = parameter['name']
654
749
  if not isinstance(name, str):
655
- raise ValueError(f'Invalid "name" value ({name}) in input parameters')
750
+ raise ValueError(
751
+ f'Invalid "name" value ({name}) in input parameters')
656
752
  if name not in new_parameters:
657
753
  name = prefix+name
658
754
  parameter['name'] = name
659
755
  if name not in new_parameters:
660
- logging.warning(f'Ignoring superfluous parameter info for {name}')
756
+ logger.warning(
757
+ f'Ignoring superfluous parameter info for {name}')
661
758
  continue
662
759
  if name in self._parameters:
663
760
  parameter.pop('name')
664
761
  if 'norm' in parameter:
665
762
  if not isinstance(parameter['norm'], bool):
666
- illegal_value(parameter['norm'], 'norm', 'Fit.add_model',
667
- raise_error=True)
763
+ raise ValueError(
764
+ f'Invalid "norm" value ({norm}) in the '
765
+ f'input parameter {name}')
668
766
  new_parameter_norms[name] = parameter['norm']
669
767
  parameter.pop('norm')
670
768
  if parameter.get('expr') is not None:
671
769
  if 'value' in parameter:
672
- logging.warning(f'Ignoring value in parameter {name} '+
673
- f'(set by expression: {parameter["expr"]})')
770
+ logger.warning(
771
+ f'Ignoring value in parameter {name} '
772
+ f'(set by expression: {parameter["expr"]})')
674
773
  parameter.pop('value')
675
774
  if 'vary' in parameter:
676
- logging.warning(f'Ignoring vary in parameter {name} '+
677
- f'(set by expression: {parameter["expr"]})')
775
+ logger.warning(
776
+ f'Ignoring vary in parameter {name} '
777
+ f'(set by expression: {parameter["expr"]})')
678
778
  parameter.pop('vary')
679
779
  if 'min' in parameter:
680
- logging.warning(f'Ignoring min in parameter {name} '+
681
- f'(set by expression: {parameter["expr"]})')
780
+ logger.warning(
781
+ f'Ignoring min in parameter {name} '
782
+ f'(set by expression: {parameter["expr"]})')
682
783
  parameter.pop('min')
683
784
  if 'max' in parameter:
684
- logging.warning(f'Ignoring max in parameter {name} '+
685
- f'(set by expression: {parameter["expr"]})')
785
+ logger.warning(
786
+ f'Ignoring max in parameter {name} '
787
+ f'(set by expression: {parameter["expr"]})')
686
788
  parameter.pop('max')
687
789
  if 'vary' in parameter:
688
790
  if not isinstance(parameter['vary'], bool):
689
- illegal_value(parameter['vary'], 'vary', 'Fit.add_model',
690
- raise_error=True)
791
+ raise ValueError(
792
+ f'Invalid "vary" value ({parameter["vary"]}) '
793
+ f'in the input parameter {name}')
691
794
  if not parameter['vary']:
692
795
  if 'min' in parameter:
693
- logging.warning(f'Ignoring min in parameter {name} in '+
694
- f'Fit.add_model (vary = {parameter["vary"]})')
796
+ logger.warning(
797
+ f'Ignoring min in parameter {name} '
798
+ f'(vary = {parameter["vary"]})')
695
799
  parameter.pop('min')
696
800
  if 'max' in parameter:
697
- logging.warning(f'Ignoring max in parameter {name} in '+
698
- f'Fit.add_model (vary = {parameter["vary"]})')
801
+ logger.warning(
802
+ f'Ignoring max in parameter {name} '
803
+ f'(vary = {parameter["vary"]})')
699
804
  parameter.pop('max')
700
805
  self._parameters[name].set(**parameter)
701
806
  parameter['name'] = name
702
807
  else:
703
- illegal_value(parameter, 'parameter name', 'Fit.model', raise_error=True)
704
- self._parameter_norms = {**self._parameter_norms, **new_parameter_norms}
705
- # print('\nAfter parameter init:')
706
- # self._parameters.pretty_print()
707
- # print(f'parameters:\n{parameters}')
708
- # print(f'new_parameter_norms:\n{new_parameter_norms}')
709
- # print(f'parameter_norms:\n{self._parameter_norms}')
710
- # print(f'kwargs:\n{kwargs}')
808
+ raise ValueError(
809
+ 'Invalid parameter name in parameters ({name})')
810
+ self._parameter_norms = {
811
+ **self._parameter_norms,
812
+ **new_parameter_norms,
813
+ }
711
814
 
712
815
  # Initialize the model parameters from kwargs
713
816
  for name, value in {**kwargs}.items():
714
817
  full_name = f'{pprefix}{name}'
715
- if full_name in new_parameter_norms and isinstance(value, (int, float)):
818
+ if (full_name in new_parameter_norms
819
+ and isinstance(value, (int, float))):
716
820
  kwargs.pop(name)
717
821
  if self._parameters[full_name].expr is None:
718
822
  self._parameters[full_name].set(value=value)
719
823
  else:
720
- logging.warning(f'Ignoring parameter {name} in Fit.fit (set by expression: '+
721
- f'{self._parameters[full_name].expr})')
722
- # print('\nAfter kwargs init:')
723
- # self._parameters.pretty_print()
724
- # print(f'parameter_norms:\n{self._parameter_norms}')
725
- # print(f'kwargs:\n{kwargs}')
726
-
727
- # Check parameter norms (also need it for expressions to renormalize the errors)
728
- if self._norm is not None and (callable(model) or model == 'expression'):
824
+ logger.warning(
825
+ f'Ignoring parameter {name} (set by expression: '
826
+ f'{self._parameters[full_name].expr})')
827
+
828
+ # Check parameter norms
829
+ # (also need it for expressions to renormalize the errors)
830
+ if (self._norm is not None
831
+ and (callable(model) or model == 'expression')):
729
832
  missing_norm = False
730
833
  for name in new_parameters.valuesdict():
731
834
  if name not in self._parameter_norms:
732
835
  print(f'new_parameters:\n{new_parameters.valuesdict()}')
733
836
  print(f'self._parameter_norms:\n{self._parameter_norms}')
734
- logging.error(f'Missing parameter normalization type for {name} in {model}')
837
+ logger.error(
838
+ f'Missing parameter normalization type for {name} in '
839
+ f'{model}')
735
840
  missing_norm = True
736
841
  if missing_norm:
737
842
  raise ValueError
738
843
 
739
- # print(f'at end add_model:\nself._parameters:\n{list(self.parameters)}')
740
- # print(f'at end add_model: kwargs = {kwargs}')
741
- # print(f'\nat end add_model: newmodel:\n{newmodel.__dict__}\n')
742
- return(kwargs)
844
+ return kwargs
743
845
 
744
846
  def eval(self, x, result=None):
847
+ """Evaluate the best fit."""
745
848
  if result is None:
746
849
  result = self._result
747
850
  if result is None:
748
- return
749
- return(result.eval(x=np.asarray(x))-self.normalization_offset)
851
+ return None
852
+ return result.eval(x=np.asarray(x))-self.normalization_offset
750
853
 
751
- def fit(self, interactive=False, guess=False, **kwargs):
854
+ def fit(self, **kwargs):
855
+ """Fit the model to the input data."""
752
856
  # Check inputs
753
857
  if self._model is None:
754
- logging.error('Undefined fit model')
755
- return
756
- if not isinstance(interactive, bool):
757
- illegal_value(interactive, 'interactive', 'Fit.fit', raise_error=True)
758
- if not isinstance(guess, bool):
759
- illegal_value(guess, 'guess', 'Fit.fit', raise_error=True)
858
+ logger.error('Undefined fit model')
859
+ return None
860
+ if 'interactive' in kwargs:
861
+ interactive = kwargs.pop('interactive')
862
+ if not isinstance(interactive, bool):
863
+ raise ValueError(
864
+ 'Invalid value of keyword argument interactive '
865
+ f'({interactive})')
866
+ else:
867
+ interactive = False
868
+ if 'guess' in kwargs:
869
+ guess = kwargs.pop('guess')
870
+ if not isinstance(guess, bool):
871
+ raise ValueError(
872
+ f'Invalid value of keyword argument guess ({guess})')
873
+ else:
874
+ guess = False
760
875
  if 'try_linear_fit' in kwargs:
761
876
  try_linear_fit = kwargs.pop('try_linear_fit')
762
877
  if not isinstance(try_linear_fit, bool):
763
- illegal_value(try_linear_fit, 'try_linear_fit', 'Fit.fit', raise_error=True)
878
+ raise ValueError(
879
+ 'Invalid value of keyword argument try_linear_fit '
880
+ f'({try_linear_fit})')
764
881
  if not self._try_linear_fit:
765
- logging.warning('Ignore superfluous keyword argument "try_linear_fit" (not '+
766
- 'yet supported for callable models)')
882
+ logger.warning(
883
+ 'Ignore superfluous keyword argument "try_linear_fit" '
884
+ '(not yet supported for callable models)')
767
885
  else:
768
886
  self._try_linear_fit = try_linear_fit
769
- # if self._result is None:
770
- # if 'parameters' in kwargs:
771
- # raise ValueError('Invalid parameter parameters ({kwargs["parameters"]})')
772
- # else:
773
887
  if self._result is not None:
774
888
  if guess:
775
- logging.warning('Ignoring input parameter guess in Fit.fit during refitting')
889
+ logger.warning(
890
+ 'Ignoring input parameter guess during refitting')
776
891
  guess = False
777
892
 
778
893
  # Check for circular expressions
779
- # FIX TODO
894
+ # RV
780
895
  # for name1, par1 in self._parameters.items():
781
896
  # if par1.expr is not None:
782
897
 
@@ -786,28 +901,32 @@ class Fit:
786
901
  if self._mask is not None:
787
902
  self._mask = np.asarray(self._mask).astype(bool)
788
903
  if self._x.size != self._mask.size:
789
- raise ValueError(f'Inconsistent x and mask dimensions ({self._x.size} vs '+
790
- f'{self._mask.size})')
791
-
792
- # Estimate initial parameters with build-in lmfit guess method (only for a single model)
793
- # print(f'\nat start fit: kwargs = {kwargs}')
794
- #RV print('\nAt start of fit:')
795
- #RV self._parameters.pretty_print()
796
- # print(f'parameter_norms:\n{self._parameter_norms}')
904
+ raise ValueError(
905
+ f'Inconsistent x and mask dimensions ({self._x.size} vs '
906
+ f'{self._mask.size})')
907
+
908
+ # Estimate initial parameters with build-in lmfit guess method
909
+ # (only mplemented for a single model)
797
910
  if guess:
798
911
  if self._mask is None:
799
912
  self._parameters = self._model.guess(self._y, x=self._x)
800
913
  else:
801
- self._parameters = self._model.guess(np.asarray(self._y)[~self._mask],
802
- x=self._x[~self._mask])
803
- # print('\nAfter guess:')
804
- # self._parameters.pretty_print()
914
+ self._parameters = self._model.guess(
915
+ np.asarray(self._y)[~self._mask], x=self._x[~self._mask])
805
916
 
806
917
  # Add constant offset for a normalized model
807
918
  if self._result is None and self._norm is not None and self._norm[0]:
808
- self.add_model('constant', prefix='tmp_normalization_offset_', parameters={'name': 'c',
809
- 'value': -self._norm[0], 'vary': False, 'norm': True})
810
- #'value': -self._norm[0]/self._norm[1], 'vary': False, 'norm': False})
919
+ self.add_model(
920
+ 'constant', prefix='tmp_normalization_offset_',
921
+ parameters={
922
+ 'name': 'c',
923
+ 'value': -self._norm[0],
924
+ 'vary': False,
925
+ 'norm': True,
926
+ })
927
+ # 'value': -self._norm[0]/self._norm[1],
928
+ # 'vary': False,
929
+ # 'norm': False,
811
930
 
812
931
  # Adjust existing parameters for refit:
813
932
  if 'parameters' in kwargs:
@@ -815,33 +934,36 @@ class Fit:
815
934
  if isinstance(parameters, dict):
816
935
  parameters = (parameters, )
817
936
  elif not is_dict_series(parameters):
818
- illegal_value(parameters, 'parameters', 'Fit.fit', raise_error=True)
937
+ raise ValueError(
938
+ 'Invalid value of keyword argument parameters '
939
+ f'({parameters})')
819
940
  for par in parameters:
820
941
  name = par['name']
821
942
  if name not in self._parameters:
822
- raise ValueError(f'Unable to match {name} parameter {par} to an existing one')
943
+ raise ValueError(
944
+ f'Unable to match {name} parameter {par} to an '
945
+ 'existing one')
823
946
  if self._parameters[name].expr is not None:
824
- raise ValueError(f'Unable to modify {name} parameter {par} (currently an '+
825
- 'expression)')
947
+ raise ValueError(
948
+ f'Unable to modify {name} parameter {par} '
949
+ '(currently an expression)')
826
950
  if par.get('expr') is not None:
827
- raise KeyError(f'Invalid "expr" key in {name} parameter {par}')
951
+ raise KeyError(
952
+ f'Invalid "expr" key in {name} parameter {par}')
828
953
  self._parameters[name].set(vary=par.get('vary'))
829
954
  self._parameters[name].set(min=par.get('min'))
830
955
  self._parameters[name].set(max=par.get('max'))
831
956
  self._parameters[name].set(value=par.get('value'))
832
- #RV print('\nAfter adjust:')
833
- #RV self._parameters.pretty_print()
834
957
 
835
958
  # Apply parameter updates through keyword arguments
836
- # print(f'kwargs = {kwargs}')
837
- # print(f'parameter_norms = {self._parameter_norms}')
838
959
  for name in set(self._parameters) & set(kwargs):
839
960
  value = kwargs.pop(name)
840
961
  if self._parameters[name].expr is None:
841
962
  self._parameters[name].set(value=value)
842
963
  else:
843
- logging.warning(f'Ignoring parameter {name} in Fit.fit (set by expression: '+
844
- f'{self._parameters[name].expr})')
964
+ logger.warning(
965
+ f'Ignoring parameter {name} (set by expression: '
966
+ f'{self._parameters[name].expr})')
845
967
 
846
968
  # Check for uninitialized parameters
847
969
  for name, par in self._parameters.items():
@@ -849,7 +971,8 @@ class Fit:
849
971
  value = par.value
850
972
  if value is None or np.isinf(value) or np.isnan(value):
851
973
  if interactive:
852
- value = input_num(f'Enter an initial value for {name}', default=1.0)
974
+ value = input_num(
975
+ f'Enter an initial value for {name}', default=1.0)
853
976
  else:
854
977
  value = 1.0
855
978
  if self._norm is None or name not in self._parameter_norms:
@@ -862,20 +985,11 @@ class Fit:
862
985
  linear_model = self._check_linearity_model()
863
986
  except:
864
987
  linear_model = False
865
- # print(f'\n\n--------> linear_model = {linear_model}\n')
866
988
  if kwargs.get('check_only_linearity') is not None:
867
- return(linear_model)
989
+ return linear_model
868
990
 
869
991
  # Normalize the data and initial parameters
870
- #RV print('\nBefore normalization:')
871
- #RV self._parameters.pretty_print()
872
- # print(f'parameter_norms:\n{self._parameter_norms}')
873
992
  self._normalize()
874
- # print(f'norm = {self._norm}')
875
- #RV print('\nAfter normalization:')
876
- #RV self._parameters.pretty_print()
877
- # self.print_fit_report()
878
- # print(f'parameter_norms:\n{self._parameter_norms}')
879
993
 
880
994
  if linear_model:
881
995
  # Perform a linear fit by direct matrix solution with numpy
@@ -883,53 +997,52 @@ class Fit:
883
997
  if self._mask is None:
884
998
  self._fit_linear_model(self._x, self._y_norm)
885
999
  else:
886
- self._fit_linear_model(self._x[~self._mask],
887
- np.asarray(self._y_norm)[~self._mask])
1000
+ self._fit_linear_model(
1001
+ self._x[~self._mask],
1002
+ np.asarray(self._y_norm)[~self._mask])
888
1003
  except:
889
1004
  linear_model = False
890
1005
  if not linear_model:
891
1006
  # Perform a non-linear fit with lmfit
892
1007
  # Prevent initial values from sitting at boundaries
893
- self._parameter_bounds = {name:{'min': par.min, 'max': par.max} for name, par in
894
- self._parameters.items() if par.vary}
1008
+ self._parameter_bounds = {
1009
+ name:{'min': par.min, 'max': par.max}
1010
+ for name, par in self._parameters.items() if par.vary}
895
1011
  for par in self._parameters.values():
896
1012
  if par.vary:
897
1013
  par.set(value=self._reset_par_at_boundary(par, par.value))
898
- # print('\nAfter checking boundaries:')
899
- # self._parameters.pretty_print()
900
1014
 
901
1015
  # Perform the fit
902
1016
  # fit_kws = None
903
1017
  # if 'Dfun' in kwargs:
904
1018
  # fit_kws = {'Dfun': kwargs.pop('Dfun')}
905
- # self._result = self._model.fit(self._y_norm, self._parameters, x=self._x,
906
- # fit_kws=fit_kws, **kwargs)
1019
+ # self._result = self._model.fit(
1020
+ # self._y_norm, self._parameters, x=self._x, fit_kws=fit_kws,
1021
+ # **kwargs)
907
1022
  if self._mask is None:
908
- self._result = self._model.fit(self._y_norm, self._parameters, x=self._x, **kwargs)
1023
+ self._result = self._model.fit(
1024
+ self._y_norm, self._parameters, x=self._x, **kwargs)
909
1025
  else:
910
- self._result = self._model.fit(np.asarray(self._y_norm)[~self._mask],
911
- self._parameters, x=self._x[~self._mask], **kwargs)
912
- #RV print('\nAfter fit:')
913
- # print(f'\nself._result ({self._result}):\n\t{self._result.__dict__}')
914
- #RV self._parameters.pretty_print()
915
- # self.print_fit_report()
1026
+ self._result = self._model.fit(
1027
+ np.asarray(self._y_norm)[~self._mask], self._parameters,
1028
+ x=self._x[~self._mask], **kwargs)
916
1029
 
917
1030
  # Set internal parameter values to fit results upon success
918
1031
  if self.success:
919
1032
  for name, par in self._parameters.items():
920
1033
  if par.expr is None and par.vary:
921
1034
  par.set(value=self._result.params[name].value)
922
- # print('\nAfter update parameter values:')
923
- # self._parameters.pretty_print()
924
1035
 
925
1036
  # Renormalize the data and results
926
1037
  self._renormalize()
927
- #RV print('\nAfter renormalization:')
928
- #RV self._parameters.pretty_print()
929
- # self.print_fit_report()
930
1038
 
931
- def plot(self, y=None, y_title=None, result=None, skip_init=False, plot_comp=True,
932
- plot_comp_legends=False, plot_residual=False, plot_masked_data=True, **kwargs):
1039
+ return None
1040
+
1041
+ def plot(
1042
+ self, y=None, y_title=None, title=None, result=None,
1043
+ skip_init=False, plot_comp=True, plot_comp_legends=False,
1044
+ plot_residual=False, plot_masked_data=True, **kwargs):
1045
+ """Plot the best fit."""
933
1046
  if result is None:
934
1047
  result = self._result
935
1048
  if result is None:
@@ -943,9 +1056,10 @@ class Fit:
943
1056
  mask = self._mask
944
1057
  if y is not None:
945
1058
  if not isinstance(y, (tuple, list, np.ndarray)):
946
- illegal_value(y, 'y', 'Fit.plot')
1059
+ logger.warning('Ignorint invalid parameter y ({y}')
947
1060
  if len(y) != len(self._x):
948
- logging.warning('Ignoring parameter y in Fit.plot (wrong dimension)')
1061
+ logger.warning(
1062
+ 'Ignoring parameter y in plot (wrong dimension)')
949
1063
  y = None
950
1064
  if y is not None:
951
1065
  if y_title is None or not isinstance(y_title, str):
@@ -973,106 +1087,98 @@ class Fit:
973
1087
  num_components -= 1
974
1088
  if num_components > 1:
975
1089
  eval_index = 0
976
- for modelname, y in components.items():
1090
+ for modelname, y_comp in components.items():
977
1091
  if modelname == 'tmp_normalization_offset_':
978
1092
  continue
979
1093
  if modelname == '_eval':
980
1094
  modelname = f'eval{eval_index}'
981
1095
  if len(modelname) > 20:
982
1096
  modelname = f'{modelname[0:16]} ...'
983
- if isinstance(y, (int, float)):
984
- y *= np.ones(self._x[~mask].size)
985
- plots += [(self._x[~mask], y, '--')]
1097
+ if isinstance(y_comp, (int, float)):
1098
+ y_comp *= np.ones(self._x[~mask].size)
1099
+ plots += [(self._x[~mask], y_comp, '--')]
986
1100
  if plot_comp_legends:
987
1101
  if modelname[-1] == '_':
988
1102
  legend.append(modelname[:-1])
989
1103
  else:
990
1104
  legend.append(modelname)
991
- title = kwargs.get('title')
992
- if title is not None:
993
- kwargs.pop('title')
994
- quick_plot(tuple(plots), legend=legend, title=title, block=True, **kwargs)
1105
+ quick_plot(
1106
+ tuple(plots), legend=legend, title=title, block=True, **kwargs)
995
1107
 
996
1108
  @staticmethod
997
- def guess_init_peak(x, y, *args, center_guess=None, use_max_for_center=True):
998
- """ Return a guess for the initial height, center and fwhm for a peak
1109
+ def guess_init_peak(
1110
+ x, y, *args, center_guess=None, use_max_for_center=True):
1111
+ """
1112
+ Return a guess for the initial height, center and fwhm for a
1113
+ single peak.
999
1114
  """
1000
- # print(f'\n\nargs = {args}')
1001
- # print(f'center_guess = {center_guess}')
1002
- # quick_plot(x, y, vlines=center_guess, block=True)
1003
1115
  center_guesses = None
1004
1116
  x = np.asarray(x)
1005
1117
  y = np.asarray(y)
1006
1118
  if len(x) != len(y):
1007
- logging.error(f'Invalid x and y lengths ({len(x)}, {len(y)}), skip initial guess')
1008
- return(None, None, None)
1119
+ logger.error(
1120
+ f'Invalid x and y lengths ({len(x)}, {len(y)}), '
1121
+ 'skip initial guess')
1122
+ return None, None, None
1009
1123
  if isinstance(center_guess, (int, float)):
1010
- if len(args):
1011
- logging.warning('Ignoring additional arguments for single center_guess value')
1124
+ if args:
1125
+ logger.warning(
1126
+ 'Ignoring additional arguments for single center_guess '
1127
+ 'value')
1012
1128
  center_guesses = [center_guess]
1013
1129
  elif isinstance(center_guess, (tuple, list, np.ndarray)):
1014
1130
  if len(center_guess) == 1:
1015
- logging.warning('Ignoring additional arguments for single center_guess value')
1131
+ logger.warning(
1132
+ 'Ignoring additional arguments for single center_guess '
1133
+ 'value')
1016
1134
  if not isinstance(center_guess[0], (int, float)):
1017
- raise ValueError(f'Invalid parameter center_guess ({type(center_guess[0])})')
1135
+ raise ValueError(
1136
+ 'Invalid parameter center_guess '
1137
+ f'({type(center_guess[0])})')
1018
1138
  center_guess = center_guess[0]
1019
1139
  else:
1020
1140
  if len(args) != 1:
1021
- raise ValueError(f'Invalid number of arguments ({len(args)})')
1141
+ raise ValueError(
1142
+ f'Invalid number of arguments ({len(args)})')
1022
1143
  n = args[0]
1023
1144
  if not is_index(n, 0, len(center_guess)):
1024
1145
  raise ValueError('Invalid argument')
1025
1146
  center_guesses = center_guess
1026
1147
  center_guess = center_guesses[n]
1027
1148
  elif center_guess is not None:
1028
- raise ValueError(f'Invalid center_guess type ({type(center_guess)})')
1029
- # print(f'x = {x}')
1030
- # print(f'y = {y}')
1031
- # print(f'center_guess = {center_guess}')
1149
+ raise ValueError(
1150
+ f'Invalid center_guess type ({type(center_guess)})')
1032
1151
 
1033
1152
  # Sort the inputs
1034
1153
  index = np.argsort(x)
1035
1154
  x = x[index]
1036
1155
  y = y[index]
1037
1156
  miny = y.min()
1038
- # print(f'miny = {miny}')
1039
- # print(f'x_range = {x[0]} {x[-1]} {len(x)}')
1040
- # print(f'y_range = {y[0]} {y[-1]} {len(y)}')
1041
- # quick_plot(x, y, vlines=center_guess, block=True)
1042
1157
 
1043
- # xx = x
1044
- # yy = y
1045
1158
  # Set range for current peak
1046
- # print(f'n = {n}')
1047
- # print(f'center_guesses = {center_guesses}')
1048
1159
  if center_guesses is not None:
1049
1160
  if len(center_guesses) > 1:
1050
1161
  index = np.argsort(center_guesses)
1051
1162
  n = list(index).index(n)
1052
- # print(f'n = {n}')
1053
- # print(f'index = {index}')
1054
1163
  center_guesses = np.asarray(center_guesses)[index]
1055
- # print(f'center_guesses = {center_guesses}')
1056
1164
  if n == 0:
1057
- low = 0
1058
- upp = index_nearest(x, (center_guesses[0]+center_guesses[1])/2)
1165
+ low = 0
1166
+ upp = index_nearest(
1167
+ x, (center_guesses[0]+center_guesses[1]) / 2)
1059
1168
  elif n == len(center_guesses)-1:
1060
- low = index_nearest(x, (center_guesses[n-1]+center_guesses[n])/2)
1061
- upp = len(x)
1169
+ low = index_nearest(
1170
+ x, (center_guesses[n-1]+center_guesses[n]) / 2)
1171
+ upp = len(x)
1062
1172
  else:
1063
- low = index_nearest(x, (center_guesses[n-1]+center_guesses[n])/2)
1064
- upp = index_nearest(x, (center_guesses[n]+center_guesses[n+1])/2)
1065
- # print(f'low = {low}')
1066
- # print(f'upp = {upp}')
1173
+ low = index_nearest(
1174
+ x, (center_guesses[n-1]+center_guesses[n]) / 2)
1175
+ upp = index_nearest(
1176
+ x, (center_guesses[n]+center_guesses[n+1]) / 2)
1067
1177
  x = x[low:upp]
1068
1178
  y = y[low:upp]
1069
- # quick_plot(x, y, vlines=(x[0], center_guess, x[-1]), block=True)
1070
1179
 
1071
- # Estimate FHHM
1180
+ # Estimate FWHM
1072
1181
  maxy = y.max()
1073
- # print(f'x_range = {x[0]} {x[-1]} {len(x)}')
1074
- # print(f'y_range = {y[0]} {y[-1]} {len(y)} {miny} {maxy}')
1075
- # print(f'center_guess = {center_guess}')
1076
1182
  if center_guess is None:
1077
1183
  center_index = np.argmax(y)
1078
1184
  center = x[center_index]
@@ -1088,52 +1194,43 @@ class Fit:
1088
1194
  center_index = index_nearest(x, center_guess)
1089
1195
  center = center_guess
1090
1196
  height = y[center_index]-miny
1091
- # print(f'center_index = {center_index}')
1092
- # print(f'center = {center}')
1093
- # print(f'height = {height}')
1094
- half_height = miny+0.5*height
1095
- # print(f'half_height = {half_height}')
1197
+ half_height = miny + 0.5*height
1096
1198
  fwhm_index1 = 0
1097
1199
  for i in range(center_index, fwhm_index1, -1):
1098
1200
  if y[i] < half_height:
1099
1201
  fwhm_index1 = i
1100
1202
  break
1101
- # print(f'fwhm_index1 = {fwhm_index1} {x[fwhm_index1]}')
1102
1203
  fwhm_index2 = len(x)-1
1103
1204
  for i in range(center_index, fwhm_index2):
1104
1205
  if y[i] < half_height:
1105
1206
  fwhm_index2 = i
1106
1207
  break
1107
- # print(f'fwhm_index2 = {fwhm_index2} {x[fwhm_index2]}')
1108
- # quick_plot((x,y,'o'), vlines=(x[fwhm_index1], center, x[fwhm_index2]), block=True)
1109
1208
  if fwhm_index1 == 0 and fwhm_index2 < len(x)-1:
1110
- fwhm = 2*(x[fwhm_index2]-center)
1209
+ fwhm = 2 * (x[fwhm_index2]-center)
1111
1210
  elif fwhm_index1 > 0 and fwhm_index2 == len(x)-1:
1112
- fwhm = 2*(center-x[fwhm_index1])
1211
+ fwhm = 2 * (center-x[fwhm_index1])
1113
1212
  else:
1114
1213
  fwhm = x[fwhm_index2]-x[fwhm_index1]
1115
- # print(f'fwhm_index1 = {fwhm_index1} {x[fwhm_index1]}')
1116
- # print(f'fwhm_index2 = {fwhm_index2} {x[fwhm_index2]}')
1117
- # print(f'fwhm = {fwhm}')
1118
1214
 
1119
- # Return height, center and FWHM
1120
- # quick_plot((x,y,'o'), (xx,yy), vlines=(x[fwhm_index1], center, x[fwhm_index2]), block=True)
1121
- return(height, center, fwhm)
1215
+ return height, center, fwhm
1122
1216
 
1123
1217
  def _check_linearity_model(self):
1124
- """Identify the linearity of all model parameters and check if the model is linear or not
1218
+ """
1219
+ Identify the linearity of all model parameters and check if
1220
+ the model is linear or not.
1125
1221
  """
1126
1222
  if not self._try_linear_fit:
1127
- logging.info('Skip linearity check (not yet supported for callable models)')
1128
- return(False)
1129
- free_parameters = [name for name, par in self._parameters.items() if par.vary]
1223
+ logger.info(
1224
+ 'Skip linearity check (not yet supported for callable models)')
1225
+ return False
1226
+ free_parameters = \
1227
+ [name for name, par in self._parameters.items() if par.vary]
1130
1228
  for component in self._model.components:
1131
1229
  if 'tmp_normalization_offset_c' in component.param_names:
1132
1230
  continue
1133
1231
  if isinstance(component, ExpressionModel):
1134
1232
  for name in free_parameters:
1135
1233
  if diff(component.expr, name, name):
1136
- # print(f'\t\t{component.expr} is non-linear in {name}')
1137
1234
  self._nonlinear_parameters.append(name)
1138
1235
  if name in self._linear_parameters:
1139
1236
  self._linear_parameters.remove(name)
@@ -1149,38 +1246,33 @@ class Fit:
1149
1246
  for nname in free_parameters:
1150
1247
  if name in self._nonlinear_parameters:
1151
1248
  if diff(expr, nname):
1152
- # print(f'\t\t{component} is non-linear in {nname} (through {name} = "{expr}")')
1153
1249
  self._nonlinear_parameters.append(nname)
1154
1250
  if nname in self._linear_parameters:
1155
1251
  self._linear_parameters.remove(nname)
1156
1252
  else:
1157
- assert(name in self._linear_parameters)
1158
- # print(f'\n\nexpr ({type(expr)}) = {expr}\nnname ({type(nname)}) = {nname}\n\n')
1253
+ assert name in self._linear_parameters
1159
1254
  if diff(expr, nname, nname):
1160
- # print(f'\t\t{component} is non-linear in {nname} (through {name} = "{expr}")')
1161
1255
  self._nonlinear_parameters.append(nname)
1162
1256
  if nname in self._linear_parameters:
1163
1257
  self._linear_parameters.remove(nname)
1164
- # print(f'\nfree parameters:\n\t{free_parameters}')
1165
- # print(f'linear parameters:\n\t{self._linear_parameters}')
1166
- # print(f'nonlinear parameters:\n\t{self._nonlinear_parameters}\n')
1167
- if any(True for name in self._nonlinear_parameters if self._parameters[name].vary):
1168
- return(False)
1169
- return(True)
1258
+ if any(True for name in self._nonlinear_parameters
1259
+ if self._parameters[name].vary):
1260
+ return False
1261
+ return True
1170
1262
 
1171
1263
  def _fit_linear_model(self, x, y):
1172
- """Perform a linear fit by direct matrix solution with numpy
1173
1264
  """
1265
+ Perform a linear fit by direct matrix solution with numpy.
1266
+ """
1267
+ # Third party modules
1268
+ from asteval import Interpreter
1269
+
1174
1270
  # Construct the matrix and the free parameter vector
1175
- # print(f'\nparameters:')
1176
- # self._parameters.pretty_print()
1177
- # print(f'\nparameter_norms:\n\t{self._parameter_norms}')
1178
- # print(f'\nlinear_parameters:\n\t{self._linear_parameters}')
1179
- # print(f'nonlinear_parameters:\n\t{self._nonlinear_parameters}')
1180
- free_parameters = [name for name, par in self._parameters.items() if par.vary]
1181
- # print(f'free parameters:\n\t{free_parameters}\n')
1182
- expr_parameters = {name:par.expr for name, par in self._parameters.items()
1183
- if par.expr is not None}
1271
+ free_parameters = \
1272
+ [name for name, par in self._parameters.items() if par.vary]
1273
+ expr_parameters = {
1274
+ name:par.expr for name, par in self._parameters.items()
1275
+ if par.expr is not None}
1184
1276
  model_parameters = []
1185
1277
  for component in self._model.components:
1186
1278
  if 'tmp_normalization_offset_c' in component.param_names:
@@ -1191,175 +1283,162 @@ class Fit:
1191
1283
  if hint.get('expr') is not None:
1192
1284
  expr_parameters.pop(name)
1193
1285
  model_parameters.remove(name)
1194
- # print(f'expr parameters:\n{expr_parameters}')
1195
- # print(f'model parameters:\n\t{model_parameters}\n')
1196
1286
  norm = 1.0
1197
1287
  if self._normalized:
1198
1288
  norm = self._norm[1]
1199
- # print(f'\n\nself._normalized = {self._normalized}\nnorm = {norm}\nself._norm = {self._norm}\n')
1200
1289
  # Add expression parameters to asteval
1201
1290
  ast = Interpreter()
1202
- # print(f'Adding to asteval sym table:')
1203
1291
  for name, expr in expr_parameters.items():
1204
- # print(f'\tadding {name} {expr}')
1205
1292
  ast.symtable[name] = expr
1206
1293
  # Add constant parameters to asteval
1207
- # (renormalize to use correctly in evaluation of expression models)
1294
+ # (renormalize to use correctly in evaluation of expression
1295
+ # models)
1208
1296
  for name, par in self._parameters.items():
1209
1297
  if par.expr is None and not par.vary:
1210
1298
  if self._parameter_norms[name]:
1211
- # print(f'\tadding {name} {par.value*norm}')
1212
1299
  ast.symtable[name] = par.value*norm
1213
1300
  else:
1214
- # print(f'\tadding {name} {par.value}')
1215
1301
  ast.symtable[name] = par.value
1216
- A = np.zeros((len(x), len(free_parameters)), dtype='float64')
1302
+ mat_a = np.zeros((len(x), len(free_parameters)), dtype='float64')
1217
1303
  y_const = np.zeros(len(x), dtype='float64')
1218
1304
  have_expression_model = False
1219
1305
  for component in self._model.components:
1220
1306
  if isinstance(component, ConstantModel):
1221
1307
  name = component.param_names[0]
1222
- # print(f'\nConstant model: {name} {self._parameters[name]}\n')
1223
1308
  if name in free_parameters:
1224
- # print(f'\t\t{name} is a free constant set matrix column {free_parameters.index(name)} to 1.0')
1225
- A[:,free_parameters.index(name)] = 1.0
1309
+ mat_a[:,free_parameters.index(name)] = 1.0
1226
1310
  else:
1227
1311
  if self._parameter_norms[name]:
1228
- delta_y_const = self._parameters[name]*np.ones(len(x))
1312
+ delta_y_const = \
1313
+ self._parameters[name] * np.ones(len(x))
1229
1314
  else:
1230
- delta_y_const = (self._parameters[name]*norm)*np.ones(len(x))
1315
+ delta_y_const = \
1316
+ (self._parameters[name]*norm) * np.ones(len(x))
1231
1317
  y_const += delta_y_const
1232
- # print(f'\ndelta_y_const ({type(delta_y_const)}):\n{delta_y_const}\n')
1233
1318
  elif isinstance(component, ExpressionModel):
1234
1319
  have_expression_model = True
1235
1320
  const_expr = component.expr
1236
- # print(f'\nExpression model:\nconst_expr: {const_expr}\n')
1237
1321
  for name in free_parameters:
1238
1322
  dexpr_dname = diff(component.expr, name)
1239
1323
  if dexpr_dname:
1240
- const_expr = f'{const_expr}-({str(dexpr_dname)})*{name}'
1241
- # print(f'\tconst_expr: {const_expr}')
1324
+ const_expr = \
1325
+ f'{const_expr}-({str(dexpr_dname)})*{name}'
1242
1326
  if not self._parameter_norms[name]:
1243
1327
  dexpr_dname = f'({dexpr_dname})/{norm}'
1244
- # print(f'\t{component.expr} is linear in {name}\n\t\tadd "{str(dexpr_dname)}" to matrix as column {free_parameters.index(name)}')
1245
- fx = [(lambda _: ast.eval(str(dexpr_dname)))(ast(f'x={v}')) for v in x]
1246
- # print(f'\tfx:\n{fx}')
1247
- if len(ast.error):
1248
- raise ValueError(f'Unable to evaluate {dexpr_dname}')
1249
- A[:,free_parameters.index(name)] += fx
1250
- # if self._parameter_norms[name]:
1251
- # print(f'\t\t{component.expr} is linear in {name} add "{str(dexpr_dname)}" to matrix as column {free_parameters.index(name)}')
1252
- # A[:,free_parameters.index(name)] += fx
1253
- # else:
1254
- # print(f'\t\t{component.expr} is linear in {name} add "({str(dexpr_dname)})/{norm}" to matrix as column {free_parameters.index(name)}')
1255
- # A[:,free_parameters.index(name)] += np.asarray(fx)/norm
1256
- # FIX: find another solution if expr not supported by simplify
1328
+ y_expr = [(lambda _: ast.eval(str(dexpr_dname)))
1329
+ (ast(f'x={v}')) for v in x]
1330
+ if ast.error:
1331
+ raise ValueError(
1332
+ f'Unable to evaluate {dexpr_dname}')
1333
+ mat_a[:,free_parameters.index(name)] += y_expr
1334
+ # RV find another solution if expr not supported by
1335
+ # simplify
1257
1336
  const_expr = str(simplify(f'({const_expr})/{norm}'))
1258
- # print(f'\nconst_expr: {const_expr}')
1259
- delta_y_const = [(lambda _: ast.eval(const_expr))(ast(f'x = {v}')) for v in x]
1337
+ delta_y_const = [(lambda _: ast.eval(const_expr))
1338
+ (ast(f'x = {v}')) for v in x]
1260
1339
  y_const += delta_y_const
1261
- # print(f'\ndelta_y_const ({type(delta_y_const)}):\n{delta_y_const}\n')
1262
- if len(ast.error):
1340
+ if ast.error:
1263
1341
  raise ValueError(f'Unable to evaluate {const_expr}')
1264
1342
  else:
1265
- free_model_parameters = [name for name in component.param_names
1266
- if name in free_parameters or name in expr_parameters]
1267
- # print(f'\nBuild-in model ({component}):\nfree_model_parameters: {free_model_parameters}\n')
1268
- if not len(free_model_parameters):
1343
+ free_model_parameters = [
1344
+ name for name in component.param_names
1345
+ if name in free_parameters or name in expr_parameters]
1346
+ if not free_model_parameters:
1269
1347
  y_const += component.eval(params=self._parameters, x=x)
1270
1348
  elif isinstance(component, LinearModel):
1271
- if f'{component.prefix}slope' in free_model_parameters:
1272
- A[:,free_parameters.index(f'{component.prefix}slope')] = x
1349
+ name = f'{component.prefix}slope'
1350
+ if name in free_model_parameters:
1351
+ mat_a[:,free_parameters.index(name)] = x
1273
1352
  else:
1274
- y_const += self._parameters[f'{component.prefix}slope'].value*x
1275
- if f'{component.prefix}intercept' in free_model_parameters:
1276
- A[:,free_parameters.index(f'{component.prefix}intercept')] = 1.0
1353
+ y_const += self._parameters[name].value * x
1354
+ name = f'{component.prefix}intercept'
1355
+ if name in free_model_parameters:
1356
+ mat_a[:,free_parameters.index(name)] = 1.0
1277
1357
  else:
1278
- y_const += self._parameters[f'{component.prefix}intercept'].value* \
1279
- np.ones(len(x))
1358
+ y_const += self._parameters[name].value \
1359
+ * np.ones(len(x))
1280
1360
  elif isinstance(component, QuadraticModel):
1281
- if f'{component.prefix}a' in free_model_parameters:
1282
- A[:,free_parameters.index(f'{component.prefix}a')] = x**2
1361
+ name = f'{component.prefix}a'
1362
+ if name in free_model_parameters:
1363
+ mat_a[:,free_parameters.index(name)] = x**2
1283
1364
  else:
1284
- y_const += self._parameters[f'{component.prefix}a'].value*x**2
1285
- if f'{component.prefix}b' in free_model_parameters:
1286
- A[:,free_parameters.index(f'{component.prefix}b')] = x
1365
+ y_const += self._parameters[name].value * x**2
1366
+ name = f'{component.prefix}b'
1367
+ if name in free_model_parameters:
1368
+ mat_a[:,free_parameters.index(name)] = x
1287
1369
  else:
1288
- y_const += self._parameters[f'{component.prefix}b'].value*x
1289
- if f'{component.prefix}c' in free_model_parameters:
1290
- A[:,free_parameters.index(f'{component.prefix}c')] = 1.0
1370
+ y_const += self._parameters[name].value * x
1371
+ name = f'{component.prefix}c'
1372
+ if name in free_model_parameters:
1373
+ mat_a[:,free_parameters.index(name)] = 1.0
1291
1374
  else:
1292
- y_const += self._parameters[f'{component.prefix}c'].value*np.ones(len(x))
1375
+ y_const += self._parameters[name].value \
1376
+ * np.ones(len(x))
1293
1377
  else:
1294
- # At this point each build-in model must be strictly proportional to each linear
1295
- # model parameter. Without this assumption, the model equation is needed
1296
- # For the current build-in lmfit models, this can only ever be the amplitude
1297
- assert(len(free_model_parameters) == 1)
1378
+ # At this point each build-in model must be
1379
+ # strictly proportional to each linear model
1380
+ # parameter. Without this assumption, the model
1381
+ # equation is needed
1382
+ # For the current build-in lmfit models, this can
1383
+ # only ever be the amplitude
1384
+ assert len(free_model_parameters) == 1
1298
1385
  name = f'{component.prefix}amplitude'
1299
- assert(free_model_parameters[0] == name)
1300
- assert(self._parameter_norms[name])
1386
+ assert free_model_parameters[0] == name
1387
+ assert self._parameter_norms[name]
1301
1388
  expr = self._parameters[name].expr
1302
1389
  if expr is None:
1303
- # print(f'\t{component} is linear in {name} add to matrix as column {free_parameters.index(name)}')
1304
1390
  parameters = deepcopy(self._parameters)
1305
1391
  parameters[name].set(value=1.0)
1306
- index = free_parameters.index(name)
1307
- A[:,free_parameters.index(name)] += component.eval(params=parameters, x=x)
1392
+ mat_a[:,free_parameters.index(name)] += component.eval(
1393
+ params=parameters, x=x)
1308
1394
  else:
1309
1395
  const_expr = expr
1310
- # print(f'\tconst_expr: {const_expr}')
1311
1396
  parameters = deepcopy(self._parameters)
1312
1397
  parameters[name].set(value=1.0)
1313
1398
  dcomp_dname = component.eval(params=parameters, x=x)
1314
- # print(f'\tdcomp_dname ({type(dcomp_dname)}):\n{dcomp_dname}')
1315
1399
  for nname in free_parameters:
1316
- dexpr_dnname = diff(expr, nname)
1400
+ dexpr_dnname = diff(expr, nname)
1317
1401
  if dexpr_dnname:
1318
- assert(self._parameter_norms[name])
1319
- # print(f'\t\td({expr})/d{nname} = {dexpr_dnname}')
1320
- # print(f'\t\t{component} is linear in {nname} (through {name} = "{expr}", add to matrix as column {free_parameters.index(nname)})')
1321
- fx = np.asarray(dexpr_dnname*dcomp_dname, dtype='float64')
1322
- # print(f'\t\tfx ({type(fx)}): {fx}')
1323
- # print(f'free_parameters.index({nname}): {free_parameters.index(nname)}')
1402
+ assert self._parameter_norms[name]
1403
+ y_expr = np.asarray(
1404
+ dexpr_dnname*dcomp_dname, dtype='float64')
1324
1405
  if self._parameter_norms[nname]:
1325
- A[:,free_parameters.index(nname)] += fx
1406
+ mat_a[:,free_parameters.index(nname)] += \
1407
+ y_expr
1326
1408
  else:
1327
- A[:,free_parameters.index(nname)] += fx/norm
1328
- const_expr = f'{const_expr}-({dexpr_dnname})*{nname}'
1329
- # print(f'\t\tconst_expr: {const_expr}')
1409
+ mat_a[:,free_parameters.index(nname)] += \
1410
+ y_expr/norm
1411
+ const_expr = \
1412
+ f'{const_expr}-({dexpr_dnname})*{nname}'
1330
1413
  const_expr = str(simplify(f'({const_expr})/{norm}'))
1331
- # print(f'\tconst_expr: {const_expr}')
1332
- fx = [(lambda _: ast.eval(const_expr))(ast(f'x = {v}')) for v in x]
1333
- # print(f'\tfx: {fx}')
1334
- delta_y_const = np.multiply(fx, dcomp_dname)
1414
+ y_expr = [
1415
+ (lambda _: ast.eval(const_expr))(ast(f'x = {v}'))
1416
+ for v in x]
1417
+ delta_y_const = np.multiply(y_expr, dcomp_dname)
1335
1418
  y_const += delta_y_const
1336
- # print(f'\ndelta_y_const ({type(delta_y_const)}):\n{delta_y_const}\n')
1337
- # print(A)
1338
- # print(y_const)
1339
- solution, residual, rank, s = np.linalg.lstsq(A, y-y_const, rcond=None)
1340
- # print(f'\nsolution ({type(solution)} {solution.shape}):\n\t{solution}')
1341
- # print(f'\nresidual ({type(residual)} {residual.shape}):\n\t{residual}')
1342
- # print(f'\nrank ({type(rank)} {rank.shape}):\n\t{rank}')
1343
- # print(f'\ns ({type(s)} {s.shape}):\n\t{s}\n')
1344
-
1345
- # Assemble result (compensate for normalization in expression models)
1419
+ solution, _, _, _ = np.linalg.lstsq(
1420
+ mat_a, y-y_const, rcond=None)
1421
+
1422
+ # Assemble result
1423
+ # (compensate for normalization in expression models)
1346
1424
  for name, value in zip(free_parameters, solution):
1347
1425
  self._parameters[name].set(value=value)
1348
- if self._normalized and (have_expression_model or len(expr_parameters)):
1426
+ if (self._normalized
1427
+ and (have_expression_model or expr_parameters)):
1349
1428
  for name, norm in self._parameter_norms.items():
1350
1429
  par = self._parameters[name]
1351
1430
  if par.expr is None and norm:
1352
1431
  self._parameters[name].set(value=par.value*self._norm[1])
1353
- # self._parameters.pretty_print()
1354
- # print(f'\nself._parameter_norms:\n\t{self._parameter_norms}')
1355
1432
  self._result = ModelResult(self._model, deepcopy(self._parameters))
1356
1433
  self._result.best_fit = self._model.eval(params=self._parameters, x=x)
1357
- if self._normalized and (have_expression_model or len(expr_parameters)):
1434
+ if (self._normalized
1435
+ and (have_expression_model or expr_parameters)):
1358
1436
  if 'tmp_normalization_offset_c' in self._parameters:
1359
1437
  offset = self._parameters['tmp_normalization_offset_c']
1360
1438
  else:
1361
1439
  offset = 0.0
1362
- self._result.best_fit = (self._result.best_fit-offset-self._norm[0])/self._norm[1]
1440
+ self._result.best_fit = \
1441
+ (self._result.best_fit-offset-self._norm[0]) / self._norm[1]
1363
1442
  if self._normalized:
1364
1443
  for name, norm in self._parameter_norms.items():
1365
1444
  par = self._parameters[name]
@@ -1367,15 +1446,12 @@ class Fit:
1367
1446
  value = par.value/self._norm[1]
1368
1447
  self._parameters[name].set(value=value)
1369
1448
  self._result.params[name].set(value=value)
1370
- # self._parameters.pretty_print()
1371
1449
  self._result.residual = self._result.best_fit-y
1372
1450
  self._result.components = self._model.components
1373
1451
  self._result.init_params = None
1374
- # quick_plot((x, y, '.'), (x, y_const, 'g'), (x, self._result.best_fit, 'k'), (x, self._result.residual, 'r'), block=True)
1375
1452
 
1376
1453
  def _normalize(self):
1377
- """Normalize the data and initial parameters
1378
- """
1454
+ """Normalize the data and initial parameters."""
1379
1455
  if self._normalized:
1380
1456
  return
1381
1457
  if self._norm is None:
@@ -1383,7 +1459,8 @@ class Fit:
1383
1459
  self._y_norm = np.asarray(self._y)
1384
1460
  else:
1385
1461
  if self._y is not None and self._y_norm is None:
1386
- self._y_norm = (np.asarray(self._y)-self._norm[0])/self._norm[1]
1462
+ self._y_norm = \
1463
+ (np.asarray(self._y)-self._norm[0]) / self._norm[1]
1387
1464
  self._y_range = 1.0
1388
1465
  for name, norm in self._parameter_norms.items():
1389
1466
  par = self._parameters[name]
@@ -1391,16 +1468,15 @@ class Fit:
1391
1468
  value = par.value/self._norm[1]
1392
1469
  _min = par.min
1393
1470
  _max = par.max
1394
- if not np.isinf(_min) and abs(_min) != float_min:
1471
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
1395
1472
  _min /= self._norm[1]
1396
- if not np.isinf(_max) and abs(_max) != float_min:
1473
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
1397
1474
  _max /= self._norm[1]
1398
1475
  par.set(value=value, min=_min, max=_max)
1399
1476
  self._normalized = True
1400
1477
 
1401
1478
  def _renormalize(self):
1402
- """Renormalize the data and results
1403
- """
1479
+ """Renormalize the data and results."""
1404
1480
  if self._norm is None or not self._normalized:
1405
1481
  return
1406
1482
  self._normalized = False
@@ -1410,14 +1486,15 @@ class Fit:
1410
1486
  value = par.value*self._norm[1]
1411
1487
  _min = par.min
1412
1488
  _max = par.max
1413
- if not np.isinf(_min) and abs(_min) != float_min:
1489
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
1414
1490
  _min *= self._norm[1]
1415
- if not np.isinf(_max) and abs(_max) != float_min:
1491
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
1416
1492
  _max *= self._norm[1]
1417
1493
  par.set(value=value, min=_min, max=_max)
1418
1494
  if self._result is None:
1419
1495
  return
1420
- self._result.best_fit = self._result.best_fit*self._norm[1]+self._norm[0]
1496
+ self._result.best_fit = (
1497
+ self._result.best_fit*self._norm[1] + self._norm[0])
1421
1498
  for name, par in self._result.params.items():
1422
1499
  if self._parameter_norms.get(name, False):
1423
1500
  if par.stderr is not None:
@@ -1428,17 +1505,19 @@ class Fit:
1428
1505
  value = par.value*self._norm[1]
1429
1506
  if par.init_value is not None:
1430
1507
  par.init_value *= self._norm[1]
1431
- if not np.isinf(_min) and abs(_min) != float_min:
1508
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
1432
1509
  _min *= self._norm[1]
1433
- if not np.isinf(_max) and abs(_max) != float_min:
1510
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
1434
1511
  _max *= self._norm[1]
1435
1512
  par.set(value=value, min=_min, max=_max)
1436
1513
  if hasattr(self._result, 'init_fit'):
1437
- self._result.init_fit = self._result.init_fit*self._norm[1]+self._norm[0]
1514
+ self._result.init_fit = (
1515
+ self._result.init_fit*self._norm[1] + self._norm[0])
1438
1516
  if hasattr(self._result, 'init_values'):
1439
1517
  init_values = {}
1440
1518
  for name, value in self._result.init_values.items():
1441
- if name not in self._parameter_norms or self._parameters[name].expr is not None:
1519
+ if (name not in self._parameter_norms
1520
+ or self._parameters[name].expr is not None):
1442
1521
  init_values[name] = value
1443
1522
  elif self._parameter_norms[name]:
1444
1523
  init_values[name] = value*self._norm[1]
@@ -1449,14 +1528,15 @@ class Fit:
1449
1528
  _min = par.min
1450
1529
  _max = par.max
1451
1530
  value *= self._norm[1]
1452
- if not np.isinf(_min) and abs(_min) != float_min:
1531
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
1453
1532
  _min *= self._norm[1]
1454
- if not np.isinf(_max) and abs(_max) != float_min:
1533
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
1455
1534
  _max *= self._norm[1]
1456
1535
  par.set(value=value, min=_min, max=_max)
1457
1536
  par.init_value = par.value
1458
- # Don't renormalize chisqr, it has no useful meaning in physical units
1459
- #self._result.chisqr *= self._norm[1]*self._norm[1]
1537
+ # Don't renormalize chisqr, it has no useful meaning in
1538
+ # physical units
1539
+ # self._result.chisqr *= self._norm[1]*self._norm[1]
1460
1540
  if self._result.covar is not None:
1461
1541
  for i, name in enumerate(self._result.var_names):
1462
1542
  if self._parameter_norms.get(name, False):
@@ -1465,13 +1545,14 @@ class Fit:
1465
1545
  self._result.covar[i,j] *= self._norm[1]
1466
1546
  if self._result.covar[j,i] is not None:
1467
1547
  self._result.covar[j,i] *= self._norm[1]
1468
- # Don't renormalize redchi, it has no useful meaning in physical units
1469
- #self._result.redchi *= self._norm[1]*self._norm[1]
1548
+ # Don't renormalize redchi, it has no useful meaning in
1549
+ # physical units
1550
+ # self._result.redchi *= self._norm[1]*self._norm[1]
1470
1551
  if self._result.residual is not None:
1471
1552
  self._result.residual *= self._norm[1]
1472
1553
 
1473
1554
  def _reset_par_at_boundary(self, par, value):
1474
- assert(par.vary)
1555
+ assert par.vary
1475
1556
  name = par.name
1476
1557
  _min = self._parameter_bounds[name]['min']
1477
1558
  _max = self._parameter_bounds[name]['max']
@@ -1484,151 +1565,165 @@ class Fit:
1484
1565
  else:
1485
1566
  upp = _max-0.1*abs(_max)
1486
1567
  if value >= upp:
1487
- return(upp)
1568
+ return upp
1488
1569
  else:
1489
1570
  if np.isinf(_max):
1490
1571
  if self._parameter_norms.get(name, False):
1491
- low = _min+0.1*self._y_range
1572
+ low = _min + 0.1*self._y_range
1492
1573
  elif _min == 0.0:
1493
1574
  low = _min+0.1
1494
1575
  else:
1495
- low = _min+0.1*abs(_min)
1576
+ low = _min + 0.1*abs(_min)
1496
1577
  if value <= low:
1497
- return(low)
1578
+ return low
1498
1579
  else:
1499
- low = 0.9*_min+0.1*_max
1500
- upp = 0.1*_min+0.9*_max
1580
+ low = 0.9*_min + 0.1*_max
1581
+ upp = 0.1*_min + 0.9*_max
1501
1582
  if value <= low:
1502
- return(low)
1503
- elif value >= upp:
1504
- return(upp)
1505
- return(value)
1583
+ return low
1584
+ if value >= upp:
1585
+ return upp
1586
+ return value
1506
1587
 
1507
1588
 
1508
1589
  class FitMultipeak(Fit):
1509
- """Fit data with multiple peaks
1590
+ """
1591
+ Wrapper to the Fit class to fit data with multiple peaks
1510
1592
  """
1511
1593
  def __init__(self, y, x=None, normalize=True):
1594
+ """Initialize FitMultipeak."""
1512
1595
  super().__init__(y, x=x, normalize=normalize)
1513
1596
  self._fwhm_max = None
1514
1597
  self._sigma_max = None
1515
1598
 
1516
1599
  @classmethod
1517
- def fit_multipeak(cls, y, centers, x=None, normalize=True, peak_models='gaussian',
1600
+ def fit_multipeak(
1601
+ cls, y, centers, x=None, normalize=True, peak_models='gaussian',
1518
1602
  center_exprs=None, fit_type=None, background=None, fwhm_max=None,
1519
1603
  print_report=False, plot=False, x_eval=None):
1520
- """Make sure that centers and fwhm_max are in the correct units and consistent with expr
1521
- for a uniform fit (fit_type == 'uniform')
1604
+ """Class method for FitMultipeak.
1605
+
1606
+ Make sure that centers and fwhm_max are in the correct units
1607
+ and consistent with expr for a uniform fit (fit_type ==
1608
+ 'uniform').
1522
1609
  """
1523
- if x_eval is not None and not isinstance(x_eval, (tuple, list, np.ndarray)):
1610
+ if (x_eval is not None
1611
+ and not isinstance(x_eval, (tuple, list, np.ndarray))):
1524
1612
  raise ValueError(f'Invalid parameter x_eval ({x_eval})')
1525
1613
  fit = cls(y, x=x, normalize=normalize)
1526
- success = fit.fit(centers, fit_type=fit_type, peak_models=peak_models, fwhm_max=fwhm_max,
1527
- center_exprs=center_exprs, background=background, print_report=print_report,
1528
- plot=plot)
1614
+ success = fit.fit(
1615
+ centers=centers, fit_type=fit_type, peak_models=peak_models,
1616
+ fwhm_max=fwhm_max, center_exprs=center_exprs,
1617
+ background=background, print_report=print_report, plot=plot)
1529
1618
  if x_eval is None:
1530
1619
  best_fit = fit.best_fit
1531
1620
  else:
1532
1621
  best_fit = fit.eval(x_eval)
1533
1622
  if success:
1534
- return(best_fit, fit.residual, fit.best_values, fit.best_errors, fit.redchi, \
1535
- fit.success)
1536
- else:
1537
- return(np.array([]), np.array([]), {}, {}, float_max, False)
1538
-
1539
- def fit(self, centers, fit_type=None, peak_models=None, center_exprs=None, fwhm_max=None,
1540
- background=None, print_report=False, plot=True, param_constraint=False):
1623
+ return (
1624
+ best_fit, fit.residual, fit.best_values, fit.best_errors,
1625
+ fit.redchi, fit.success)
1626
+ return np.array([]), np.array([]), {}, {}, FLOAT_MAX, False
1627
+
1628
+ def fit(
1629
+ self, centers=None, fit_type=None, peak_models=None,
1630
+ center_exprs=None, fwhm_max=None, background=None,
1631
+ print_report=False, plot=True, param_constraint=False, **kwargs):
1632
+ """Fit the model to the input data."""
1633
+ if centers is None:
1634
+ raise ValueError('Missing required parameter centers')
1635
+ if not isinstance(centers, (int, float, tuple, list, np.ndarray)):
1636
+ raise ValueError(f'Invalid parameter centers ({centers})')
1541
1637
  self._fwhm_max = fwhm_max
1542
- # Create the multipeak model
1543
- self._create_model(centers, fit_type, peak_models, center_exprs, background,
1544
- param_constraint)
1545
-
1546
- # RV: Obsolete Normalize the data and results
1547
- # print('\nBefore fit before normalization in FitMultipeak:')
1548
- # self._parameters.pretty_print()
1549
- # self._normalize()
1550
- # print('\nBefore fit after normalization in FitMultipeak:')
1551
- # self._parameters.pretty_print()
1638
+ self._create_model(
1639
+ centers, fit_type, peak_models, center_exprs, background,
1640
+ param_constraint)
1552
1641
 
1553
1642
  # Perform the fit
1554
1643
  try:
1555
1644
  if param_constraint:
1556
- super().fit(fit_kws={'xtol': 1.e-5, 'ftol': 1.e-5, 'gtol': 1.e-5})
1645
+ super().fit(
1646
+ fit_kws={'xtol': 1.e-5, 'ftol': 1.e-5, 'gtol': 1.e-5})
1557
1647
  else:
1558
1648
  super().fit()
1559
1649
  except:
1560
- return(False)
1650
+ return False
1561
1651
 
1562
1652
  # Check for valid fit parameter results
1563
1653
  fit_failure = self._check_validity()
1564
1654
  success = True
1565
1655
  if fit_failure:
1566
1656
  if param_constraint:
1567
- logging.warning(' -> Should not happen with param_constraint set, fail the fit')
1657
+ logger.warning(
1658
+ ' -> Should not happen with param_constraint set, '
1659
+ 'fail the fit')
1568
1660
  success = False
1569
1661
  else:
1570
- logging.info(' -> Retry fitting with constraints')
1571
- self.fit(centers, fit_type, peak_models, center_exprs, fwhm_max=fwhm_max,
1572
- background=background, print_report=print_report, plot=plot,
1573
- param_constraint=True)
1662
+ logger.info(' -> Retry fitting with constraints')
1663
+ self.fit(
1664
+ centers, fit_type, peak_models, center_exprs,
1665
+ fwhm_max=fwhm_max, background=background,
1666
+ print_report=print_report, plot=plot,
1667
+ param_constraint=True)
1574
1668
  else:
1575
- # RV: Obsolete Renormalize the data and results
1576
- # print('\nAfter fit before renormalization in FitMultipeak:')
1577
- # self._parameters.pretty_print()
1578
- # self.print_fit_report()
1579
- # self._renormalize()
1580
- # print('\nAfter fit after renormalization in FitMultipeak:')
1581
- # self._parameters.pretty_print()
1582
- # self.print_fit_report()
1583
-
1584
1669
  # Print report and plot components if requested
1585
1670
  if print_report:
1586
1671
  self.print_fit_report()
1587
1672
  if plot:
1588
- self.plot(skip_init=True, plot_comp=True, plot_comp_legends=True,
1589
- plot_residual=True)
1673
+ self.plot(
1674
+ skip_init=True, plot_comp=True, plot_comp_legends=True,
1675
+ plot_residual=True)
1590
1676
 
1591
- return(success)
1677
+ return success
1678
+
1679
+ def _create_model(
1680
+ self, centers, fit_type=None, peak_models=None, center_exprs=None,
1681
+ background=None, param_constraint=False):
1682
+ """Create the multipeak model."""
1683
+ # Third party modules
1684
+ from asteval import Interpreter
1592
1685
 
1593
- def _create_model(self, centers, fit_type=None, peak_models=None, center_exprs=None,
1594
- background=None, param_constraint=False):
1595
- """Create the multipeak model
1596
- """
1597
1686
  if isinstance(centers, (int, float)):
1598
1687
  centers = [centers]
1599
1688
  num_peaks = len(centers)
1600
1689
  if peak_models is None:
1601
1690
  peak_models = num_peaks*['gaussian']
1602
- elif isinstance(peak_models, str) and peak_models in ('gaussian', 'lorentzian'):
1691
+ elif (isinstance(peak_models, str)
1692
+ and peak_models in ('gaussian', 'lorentzian')):
1603
1693
  peak_models = num_peaks*[peak_models]
1604
1694
  else:
1605
- raise ValueError(f'Invalid peak model parameter ({peak_models})')
1695
+ raise ValueError(f'Invalid parameter peak model ({peak_models})')
1606
1696
  if len(peak_models) != num_peaks:
1607
- raise ValueError(f'Inconsistent number of peaks in peak_models ({len(peak_models)} vs '+
1608
- f'{num_peaks})')
1697
+ raise ValueError(
1698
+ 'Inconsistent number of peaks in peak_models '
1699
+ f'({len(peak_models)} vs {num_peaks})')
1609
1700
  if num_peaks == 1:
1610
1701
  if fit_type is not None:
1611
- logging.debug('Ignoring fit_type input for fitting one peak')
1702
+ logger.debug('Ignoring fit_type input for fitting one peak')
1612
1703
  fit_type = None
1613
1704
  if center_exprs is not None:
1614
- logging.debug('Ignoring center_exprs input for fitting one peak')
1705
+ logger.debug(
1706
+ 'Ignoring center_exprs input for fitting one peak')
1615
1707
  center_exprs = None
1616
1708
  else:
1617
1709
  if fit_type == 'uniform':
1618
1710
  if center_exprs is None:
1619
1711
  center_exprs = [f'scale_factor*{cen}' for cen in centers]
1620
1712
  if len(center_exprs) != num_peaks:
1621
- raise ValueError(f'Inconsistent number of peaks in center_exprs '+
1622
- f'({len(center_exprs)} vs {num_peaks})')
1713
+ raise ValueError(
1714
+ 'Inconsistent number of peaks in center_exprs '
1715
+ f'({len(center_exprs)} vs {num_peaks})')
1623
1716
  elif fit_type == 'unconstrained' or fit_type is None:
1624
1717
  if center_exprs is not None:
1625
- logging.warning('Ignoring center_exprs input for unconstrained fit')
1718
+ logger.warning(
1719
+ 'Ignoring center_exprs input for unconstrained fit')
1626
1720
  center_exprs = None
1627
1721
  else:
1628
- raise ValueError(f'Invalid fit_type in fit_multigaussian {fit_type}')
1722
+ raise ValueError(
1723
+ f'Invalid parameter fit_type ({fit_type})')
1629
1724
  self._sigma_max = None
1630
1725
  if param_constraint:
1631
- min_value = float_min
1726
+ min_value = FLOAT_MIN
1632
1727
  if self._fwhm_max is not None:
1633
1728
  self._sigma_max = np.zeros(num_peaks)
1634
1729
  else:
@@ -1648,17 +1743,23 @@ class FitMultipeak(Fit):
1648
1743
  elif is_dict_series(background):
1649
1744
  for model in deepcopy(background):
1650
1745
  if 'model' not in model:
1651
- raise KeyError(f'Missing keyword "model" in model in background ({model})')
1746
+ raise KeyError(
1747
+ 'Missing keyword "model" in model in background '
1748
+ f'({model})')
1652
1749
  name = model.pop('model')
1653
- parameters=model.pop('parameters', None)
1654
- self.add_model(name, prefix=f'bkgd_{name}_', parameters=parameters, **model)
1750
+ parameters = model.pop('parameters', None)
1751
+ self.add_model(
1752
+ name, prefix=f'bkgd_{name}_', parameters=parameters,
1753
+ **model)
1655
1754
  else:
1656
- raise ValueError(f'Invalid parameter background ({background})')
1755
+ raise ValueError(
1756
+ f'Invalid parameter background ({background})')
1657
1757
 
1658
1758
  # Add peaks and guess initial fit parameters
1659
1759
  ast = Interpreter()
1660
1760
  if num_peaks == 1:
1661
- height_init, cen_init, fwhm_init = self.guess_init_peak(self._x, self._y)
1761
+ height_init, cen_init, fwhm_init = self.guess_init_peak(
1762
+ self._x, self._y)
1662
1763
  if self._fwhm_max is not None and fwhm_init > self._fwhm_max:
1663
1764
  fwhm_init = self._fwhm_max
1664
1765
  ast(f'fwhm = {fwhm_init}')
@@ -1670,16 +1771,20 @@ class FitMultipeak(Fit):
1670
1771
  ast(f'fwhm = {self._fwhm_max}')
1671
1772
  sig_max = ast(fwhm_factor[peak_models[0]])
1672
1773
  self._sigma_max[0] = sig_max
1673
- self.add_model(peak_models[0], parameters=(
1774
+ self.add_model(
1775
+ peak_models[0],
1776
+ parameters=(
1674
1777
  {'name': 'amplitude', 'value': amp_init, 'min': min_value},
1675
1778
  {'name': 'center', 'value': cen_init, 'min': min_value},
1676
- {'name': 'sigma', 'value': sig_init, 'min': min_value, 'max': sig_max}))
1779
+ {'name': 'sigma', 'value': sig_init, 'min': min_value,
1780
+ 'max': sig_max},
1781
+ ))
1677
1782
  else:
1678
1783
  if fit_type == 'uniform':
1679
1784
  self.add_parameter(name='scale_factor', value=1.0)
1680
1785
  for i in range(num_peaks):
1681
- height_init, cen_init, fwhm_init = self.guess_init_peak(self._x, self._y, i,
1682
- center_guess=centers)
1786
+ height_init, cen_init, fwhm_init = self.guess_init_peak(
1787
+ self._x, self._y, i, center_guess=centers)
1683
1788
  if self._fwhm_max is not None and fwhm_init > self._fwhm_max:
1684
1789
  fwhm_init = self._fwhm_max
1685
1790
  ast(f'fwhm = {fwhm_init}')
@@ -1692,61 +1797,81 @@ class FitMultipeak(Fit):
1692
1797
  sig_max = ast(fwhm_factor[peak_models[i]])
1693
1798
  self._sigma_max[i] = sig_max
1694
1799
  if fit_type == 'uniform':
1695
- self.add_model(peak_models[i], prefix=f'peak{i+1}_', parameters=(
1696
- {'name': 'amplitude', 'value': amp_init, 'min': min_value},
1800
+ self.add_model(
1801
+ peak_models[i], prefix=f'peak{i+1}_',
1802
+ parameters=(
1803
+ {'name': 'amplitude', 'value': amp_init,
1804
+ 'min': min_value},
1697
1805
  {'name': 'center', 'expr': center_exprs[i]},
1698
- {'name': 'sigma', 'value': sig_init, 'min': min_value,
1699
- 'max': sig_max}))
1806
+ {'name': 'sigma', 'value': sig_init,
1807
+ 'min': min_value, 'max': sig_max},
1808
+ ))
1700
1809
  else:
1701
- self.add_model('gaussian', prefix=f'peak{i+1}_', parameters=(
1702
- {'name': 'amplitude', 'value': amp_init, 'min': min_value},
1703
- {'name': 'center', 'value': cen_init, 'min': min_value},
1704
- {'name': 'sigma', 'value': sig_init, 'min': min_value,
1705
- 'max': sig_max}))
1810
+ self.add_model(
1811
+ 'gaussian',
1812
+ prefix=f'peak{i+1}_',
1813
+ parameters=(
1814
+ {'name': 'amplitude', 'value': amp_init,
1815
+ 'min': min_value},
1816
+ {'name': 'center', 'value': cen_init,
1817
+ 'min': min_value},
1818
+ {'name': 'sigma', 'value': sig_init,
1819
+ 'min': min_value, 'max': sig_max},
1820
+ ))
1706
1821
 
1707
1822
  def _check_validity(self):
1708
- """Check for valid fit parameter results
1709
- """
1823
+ """Check for valid fit parameter results."""
1710
1824
  fit_failure = False
1711
- index = compile(r'\d+')
1712
- for name, par in self.best_parameters.items():
1825
+ index = re_compile(r'\d+')
1826
+ for name, par in self.best_parameters().items():
1713
1827
  if 'bkgd' in name:
1714
- # if ((name == 'bkgd_c' and par['value'] <= 0.0) or
1715
- # (name.endswith('amplitude') and par['value'] <= 0.0) or
1716
- if ((name.endswith('amplitude') and par['value'] <= 0.0) or
1717
- (name.endswith('decay') and par['value'] <= 0.0)):
1718
- logging.info(f'Invalid fit result for {name} ({par["value"]})')
1828
+ if ((name.endswith('amplitude') and par['value'] <= 0.0)
1829
+ or (name.endswith('decay') and par['value'] <= 0.0)):
1830
+ logger.info(
1831
+ f'Invalid fit result for {name} ({par["value"]})')
1719
1832
  fit_failure = True
1720
- elif (((name.endswith('amplitude') or name.endswith('height')) and
1721
- par['value'] <= 0.0) or
1722
- ((name.endswith('sigma') or name.endswith('fwhm')) and par['value'] <= 0.0) or
1723
- (name.endswith('center') and par['value'] <= 0.0) or
1724
- (name == 'scale_factor' and par['value'] <= 0.0)):
1725
- logging.info(f'Invalid fit result for {name} ({par["value"]})')
1833
+ elif (((name.endswith('amplitude') or name.endswith('height'))
1834
+ and par['value'] <= 0.0)
1835
+ or ((name.endswith('sigma') or name.endswith('fwhm'))
1836
+ and par['value'] <= 0.0)
1837
+ or (name.endswith('center') and par['value'] <= 0.0)
1838
+ or (name == 'scale_factor' and par['value'] <= 0.0)):
1839
+ logger.info(f'Invalid fit result for {name} ({par["value"]})')
1726
1840
  fit_failure = True
1727
- if 'bkgd' not in name and name.endswith('sigma') and self._sigma_max is not None:
1841
+ if ('bkgd' not in name and name.endswith('sigma')
1842
+ and self._sigma_max is not None):
1728
1843
  if name == 'sigma':
1729
1844
  sigma_max = self._sigma_max[0]
1730
1845
  else:
1731
- sigma_max = self._sigma_max[int(index.search(name).group())-1]
1846
+ sigma_max = self._sigma_max[
1847
+ int(index.search(name).group())-1]
1732
1848
  if par['value'] > sigma_max:
1733
- logging.info(f'Invalid fit result for {name} ({par["value"]})')
1849
+ logger.info(
1850
+ f'Invalid fit result for {name} ({par["value"]})')
1734
1851
  fit_failure = True
1735
1852
  elif par['value'] == sigma_max:
1736
- logging.warning(f'Edge result on for {name} ({par["value"]})')
1737
- if 'bkgd' not in name and name.endswith('fwhm') and self._fwhm_max is not None:
1853
+ logger.warning(
1854
+ f'Edge result on for {name} ({par["value"]})')
1855
+ if ('bkgd' not in name and name.endswith('fwhm')
1856
+ and self._fwhm_max is not None):
1738
1857
  if par['value'] > self._fwhm_max:
1739
- logging.info(f'Invalid fit result for {name} ({par["value"]})')
1858
+ logger.info(
1859
+ f'Invalid fit result for {name} ({par["value"]})')
1740
1860
  fit_failure = True
1741
1861
  elif par['value'] == self._fwhm_max:
1742
- logging.warning(f'Edge result on for {name} ({par["value"]})')
1743
- return(fit_failure)
1862
+ logger.warning(
1863
+ f'Edge result on for {name} ({par["value"]})')
1864
+ return fit_failure
1744
1865
 
1745
1866
 
1746
1867
  class FitMap(Fit):
1747
- """Fit a map of data
1748
1868
  """
1749
- def __init__(self, ymap, x=None, models=None, normalize=True, transpose=None, **kwargs):
1869
+ Wrapper to the Fit class to fit dat on a N-dimensional map
1870
+ """
1871
+ def __init__(
1872
+ self, ymap, x=None, models=None, normalize=True, transpose=None,
1873
+ **kwargs):
1874
+ """Initialize FitMap."""
1750
1875
  super().__init__(None)
1751
1876
  self._best_errors = None
1752
1877
  self._best_fit = None
@@ -1766,64 +1891,78 @@ class FitMap(Fit):
1766
1891
  self._transpose = None
1767
1892
  self._try_no_bounds = True
1768
1893
 
1769
- # At this point the fastest index should always be the signal dimension so that the slowest
1770
- # ndim-1 dimensions are the map dimensions
1894
+ # At this point the fastest index should always be the signal
1895
+ # dimension so that the slowest ndim-1 dimensions are the
1896
+ # map dimensions
1771
1897
  if isinstance(ymap, (tuple, list, np.ndarray)):
1772
1898
  self._x = np.asarray(x)
1773
- elif have_xarray and isinstance(ymap, xr.DataArray):
1899
+ elif HAVE_XARRAY and isinstance(ymap, xr.DataArray):
1774
1900
  if x is not None:
1775
- logging.warning('Ignoring superfluous input x ({x}) in Fit.__init__')
1901
+ logger.warning('Ignoring superfluous input x ({x})')
1776
1902
  self._x = np.asarray(ymap[ymap.dims[-1]])
1777
1903
  else:
1778
- illegal_value(ymap, 'ymap', 'FitMap:__init__', raise_error=True)
1904
+ raise ValueError('Invalid parameter ymap ({ymap})')
1779
1905
  self._ymap = ymap
1780
1906
 
1781
1907
  # Verify the input parameters
1782
1908
  if self._x.ndim != 1:
1783
1909
  raise ValueError(f'Invalid dimension for input x {self._x.ndim}')
1784
1910
  if self._ymap.ndim < 2:
1785
- raise ValueError('Invalid number of dimension of the input dataset '+
1786
- f'{self._ymap.ndim}')
1911
+ raise ValueError(
1912
+ 'Invalid number of dimension of the input dataset '
1913
+ f'{self._ymap.ndim}')
1787
1914
  if self._x.size != self._ymap.shape[-1]:
1788
- raise ValueError(f'Inconsistent x and y dimensions ({self._x.size} vs '+
1789
- f'{self._ymap.shape[-1]})')
1915
+ raise ValueError(
1916
+ f'Inconsistent x and y dimensions ({self._x.size} vs '
1917
+ f'{self._ymap.shape[-1]})')
1790
1918
  if not isinstance(normalize, bool):
1791
- logging.warning(f'Invalid value for normalize ({normalize}) in Fit.__init__: '+
1792
- 'setting normalize to True')
1919
+ logger.warning(
1920
+ f'Invalid value for normalize ({normalize}) in Fit.__init__: '
1921
+ 'setting normalize to True')
1793
1922
  normalize = True
1794
1923
  if isinstance(transpose, bool) and not transpose:
1795
1924
  transpose = None
1796
1925
  if transpose is not None and self._ymap.ndim < 3:
1797
- logging.warning(f'Transpose meaningless for {self._ymap.ndim-1}D data maps: ignoring '+
1798
- 'transpose')
1926
+ logger.warning(
1927
+ f'Transpose meaningless for {self._ymap.ndim-1}D data maps: '
1928
+ 'ignoring transpose')
1799
1929
  if transpose is not None:
1800
- if self._ymap.ndim == 3 and isinstance(transpose, bool) and transpose:
1930
+ if (self._ymap.ndim == 3 and isinstance(transpose, bool)
1931
+ and transpose):
1801
1932
  self._transpose = (1, 0)
1802
1933
  elif not isinstance(transpose, (tuple, list)):
1803
- logging.warning(f'Invalid data type for transpose ({transpose}, '+
1804
- f'{type(transpose)}) in Fit.__init__: setting transpose to False')
1805
- elif len(transpose) != self._ymap.ndim-1:
1806
- logging.warning(f'Invalid dimension for transpose ({transpose}, must be equal to '+
1807
- f'{self._ymap.ndim-1}) in Fit.__init__: setting transpose to False')
1934
+ logger.warning(
1935
+ f'Invalid data type for transpose ({transpose}, '
1936
+ f'{type(transpose)}): setting transpose to False')
1937
+ elif transpose != self._ymap.ndim-1:
1938
+ logger.warning(
1939
+ f'Invalid dimension for transpose ({transpose}, must be '
1940
+ f'equal to {self._ymap.ndim-1}): '
1941
+ 'setting transpose to False')
1808
1942
  elif any(i not in transpose for i in range(len(transpose))):
1809
- logging.warning(f'Invalid index in transpose ({transpose}) '+
1810
- f'in Fit.__init__: setting transpose to False')
1811
- elif not all(i==transpose[i] for i in range(self._ymap.ndim-1)):
1943
+ logger.warning(
1944
+ f'Invalid index in transpose ({transpose}): '
1945
+ 'setting transpose to False')
1946
+ elif not all(i == transpose[i] for i in range(self._ymap.ndim-1)):
1812
1947
  self._transpose = transpose
1813
1948
  if self._transpose is not None:
1814
- self._inv_transpose = tuple(self._transpose.index(i)
1815
- for i in range(len(self._transpose)))
1949
+ self._inv_transpose = tuple(
1950
+ self._transpose.index(i)
1951
+ for i in range(len(self._transpose)))
1816
1952
 
1817
1953
  # Flatten the map (transpose if requested)
1818
- # Store the flattened map in self._ymap_norm, whether normalized or not
1954
+ # Store the flattened map in self._ymap_norm, whether
1955
+ # normalized or not
1819
1956
  if self._transpose is not None:
1820
- self._ymap_norm = np.transpose(np.asarray(self._ymap), list(self._transpose)+
1821
- [len(self._transpose)])
1957
+ self._ymap_norm = np.transpose(
1958
+ np.asarray(self._ymap),
1959
+ list(self._transpose) + [len(self._transpose)])
1822
1960
  else:
1823
1961
  self._ymap_norm = np.asarray(self._ymap)
1824
1962
  self._map_dim = int(self._ymap_norm.size/self._x.size)
1825
1963
  self._map_shape = self._ymap_norm.shape[:-1]
1826
- self._ymap_norm = np.reshape(self._ymap_norm, (self._map_dim, self._x.size))
1964
+ self._ymap_norm = np.reshape(
1965
+ self._ymap_norm, (self._map_dim, self._x.size))
1827
1966
 
1828
1967
  # Check if a mask is provided
1829
1968
  if 'mask' in kwargs:
@@ -1834,8 +1973,9 @@ class FitMap(Fit):
1834
1973
  else:
1835
1974
  self._mask = np.asarray(self._mask).astype(bool)
1836
1975
  if self._x.size != self._mask.size:
1837
- raise ValueError(f'Inconsistent mask dimension ({self._x.size} vs '+
1838
- f'{self._mask.size})')
1976
+ raise ValueError(
1977
+ f'Inconsistent mask dimension ({self._x.size} vs '
1978
+ f'{self._mask.size})')
1839
1979
  ymap_masked = np.asarray(self._ymap_norm)[:,~self._mask]
1840
1980
  ymap_min = float(ymap_masked.min())
1841
1981
  ymap_max = float(ymap_masked.max())
@@ -1844,7 +1984,7 @@ class FitMap(Fit):
1844
1984
  self._y_range = ymap_max-ymap_min
1845
1985
  if normalize and self._y_range > 0.0:
1846
1986
  self._norm = (ymap_min, self._y_range)
1847
- self._ymap_norm = (self._ymap_norm-self._norm[0])/self._norm[1]
1987
+ self._ymap_norm = (self._ymap_norm-self._norm[0]) / self._norm[1]
1848
1988
  else:
1849
1989
  self._redchi_cutoff *= self._y_range**2
1850
1990
  if models is not None:
@@ -1857,25 +1997,31 @@ class FitMap(Fit):
1857
1997
 
1858
1998
  @classmethod
1859
1999
  def fit_map(cls, ymap, models, x=None, normalize=True, **kwargs):
1860
- return(cls(ymap, x=x, models=models, normalize=normalize, **kwargs))
2000
+ """Class method for FitMap."""
2001
+ return cls(ymap, x=x, models=models, normalize=normalize, **kwargs)
1861
2002
 
1862
2003
  @property
1863
2004
  def best_errors(self):
1864
- return(self._best_errors)
2005
+ """Return errors in the best fit parameters."""
2006
+ return self._best_errors
1865
2007
 
1866
2008
  @property
1867
2009
  def best_fit(self):
1868
- return(self._best_fit)
2010
+ """Return the best fits."""
2011
+ return self._best_fit
1869
2012
 
1870
2013
  @property
1871
2014
  def best_results(self):
1872
- """Convert the input data array to a data set and add the fit results.
1873
2015
  """
1874
- if self.best_values is None or self.best_errors is None or self.best_fit is None:
1875
- return(None)
1876
- if not have_xarray:
1877
- logging.warning('Unable to load xarray module')
1878
- return(None)
2016
+ Convert the input DataArray to a data set and add the fit
2017
+ results.
2018
+ """
2019
+ if (self.best_values is None or self.best_errors is None
2020
+ or self.best_fit is None):
2021
+ return None
2022
+ if not HAVE_XARRAY:
2023
+ logger.warning('Unable to load xarray module')
2024
+ return None
1879
2025
  best_values = self.best_values
1880
2026
  best_errors = self.best_errors
1881
2027
  if isinstance(self._ymap, xr.DataArray):
@@ -1883,8 +2029,9 @@ class FitMap(Fit):
1883
2029
  dims = self._ymap.dims
1884
2030
  fit_name = f'{self._ymap.name}_fit'
1885
2031
  else:
1886
- coords = {f'dim{n}_index':([f'dim{n}_index'], range(self._ymap.shape[n]))
1887
- for n in range(self._ymap.ndim-1)}
2032
+ coords = {
2033
+ f'dim{n}_index':([f'dim{n}_index'], range(self._ymap.shape[n]))
2034
+ for n in range(self._ymap.ndim-1)}
1888
2035
  coords['x'] = (['x'], self._x)
1889
2036
  dims = list(coords.keys())
1890
2037
  best_results = xr.Dataset(coords=coords)
@@ -1894,26 +2041,31 @@ class FitMap(Fit):
1894
2041
  if self._mask is not None:
1895
2042
  best_results['mask'] = self._mask
1896
2043
  for n in range(best_values.shape[0]):
1897
- best_results[f'{self._best_parameters[n]}_values'] = (dims[:-1], best_values[n])
1898
- best_results[f'{self._best_parameters[n]}_errors'] = (dims[:-1], best_errors[n])
2044
+ best_results[f'{self._best_parameters[n]}_values'] = \
2045
+ (dims[:-1], best_values[n])
2046
+ best_results[f'{self._best_parameters[n]}_errors'] = \
2047
+ (dims[:-1], best_errors[n])
1899
2048
  best_results.attrs['components'] = self.components
1900
- return(best_results)
2049
+ return best_results
1901
2050
 
1902
2051
  @property
1903
2052
  def best_values(self):
1904
- return(self._best_values)
2053
+ """Return values of the best fit parameters."""
2054
+ return self._best_values
1905
2055
 
1906
2056
  @property
1907
2057
  def chisqr(self):
1908
- logging.warning('property chisqr not defined for fit.FitMap')
1909
- return(None)
2058
+ """Return the chisqr value of each best fit."""
2059
+ logger.warning('Undefined property chisqr')
1910
2060
 
1911
2061
  @property
1912
2062
  def components(self):
2063
+ """Return the fit model components info."""
1913
2064
  components = {}
1914
2065
  if self._result is None:
1915
- logging.warning('Unable to collect components in FitMap.components')
1916
- return(components)
2066
+ logger.warning(
2067
+ 'Unable to collect components in FitMap.components')
2068
+ return components
1917
2069
  for component in self._result.components:
1918
2070
  if 'tmp_normalization_offset_c' in component.param_names:
1919
2071
  continue
@@ -1922,9 +2074,15 @@ class FitMap(Fit):
1922
2074
  if self._parameters[name].vary:
1923
2075
  parameters[name] = {'free': True}
1924
2076
  elif self._parameters[name].expr is not None:
1925
- parameters[name] = {'free': False, 'expr': self._parameters[name].expr}
2077
+ parameters[name] = {
2078
+ 'free': False,
2079
+ 'expr': self._parameters[name].expr,
2080
+ }
1926
2081
  else:
1927
- parameters[name] = {'free': False, 'value': self.init_parameters[name]['value']}
2082
+ parameters[name] = {
2083
+ 'free': False,
2084
+ 'value': self.init_parameters[name]['value'],
2085
+ }
1928
2086
  expr = None
1929
2087
  if isinstance(component, ExpressionModel):
1930
2088
  name = component._name
@@ -1933,7 +2091,7 @@ class FitMap(Fit):
1933
2091
  expr = component.expr
1934
2092
  else:
1935
2093
  prefix = component.prefix
1936
- if len(prefix):
2094
+ if prefix:
1937
2095
  if prefix[-1] == '_':
1938
2096
  prefix = prefix[:-1]
1939
2097
  name = f'{prefix} ({component._name})'
@@ -1943,70 +2101,89 @@ class FitMap(Fit):
1943
2101
  components[name] = {'parameters': parameters}
1944
2102
  else:
1945
2103
  components[name] = {'expr': expr, 'parameters': parameters}
1946
- return(components)
2104
+ return components
1947
2105
 
1948
2106
  @property
1949
2107
  def covar(self):
1950
- logging.warning('property covar not defined for fit.FitMap')
1951
- return(None)
2108
+ """
2109
+ Return the covarience matrices of the best fit parameters.
2110
+ """
2111
+ logger.warning('Undefined property covar')
1952
2112
 
1953
2113
  @property
1954
2114
  def max_nfev(self):
1955
- return(self._max_nfev)
2115
+ """
2116
+ Return the maximum number of function evaluations for each fit.
2117
+ """
2118
+ return self._max_nfev
1956
2119
 
1957
2120
  @property
1958
2121
  def num_func_eval(self):
1959
- logging.warning('property num_func_eval not defined for fit.FitMap')
1960
- return(None)
2122
+ """
2123
+ Return the number of function evaluations for each best fit.
2124
+ """
2125
+ logger.warning('Undefined property num_func_eval')
1961
2126
 
1962
2127
  @property
1963
2128
  def out_of_bounds(self):
1964
- return(self._out_of_bounds)
2129
+ """Return the out_of_bounds value of each best fit."""
2130
+ return self._out_of_bounds
1965
2131
 
1966
2132
  @property
1967
2133
  def redchi(self):
1968
- return(self._redchi)
2134
+ """Return the redchi value of each best fit."""
2135
+ return self._redchi
1969
2136
 
1970
2137
  @property
1971
2138
  def residual(self):
2139
+ """Return the residual in each best fit."""
1972
2140
  if self.best_fit is None:
1973
- return(None)
2141
+ return None
1974
2142
  if self._mask is None:
1975
- return(np.asarray(self._ymap)-self.best_fit)
2143
+ residual = np.asarray(self._ymap)-self.best_fit
1976
2144
  else:
1977
- ymap_flat = np.reshape(np.asarray(self._ymap), (self._map_dim, self._x.size))
2145
+ ymap_flat = np.reshape(
2146
+ np.asarray(self._ymap), (self._map_dim, self._x.size))
1978
2147
  ymap_flat_masked = ymap_flat[:,~self._mask]
1979
- ymap_masked = np.reshape(ymap_flat_masked,
1980
- list(self._map_shape)+[ymap_flat_masked.shape[-1]])
1981
- return(ymap_masked-self.best_fit)
2148
+ ymap_masked = np.reshape(
2149
+ ymap_flat_masked,
2150
+ list(self._map_shape) + [ymap_flat_masked.shape[-1]])
2151
+ residual = ymap_masked-self.best_fit
2152
+ return residual
1982
2153
 
1983
2154
  @property
1984
2155
  def success(self):
1985
- return(self._success)
2156
+ """Return the success value for each fit."""
2157
+ return self._success
1986
2158
 
1987
2159
  @property
1988
2160
  def var_names(self):
1989
- logging.warning('property var_names not defined for fit.FitMap')
1990
- return(None)
2161
+ """
2162
+ Return the variable names for the covarience matrix property.
2163
+ """
2164
+ logger.warning('Undefined property var_names')
1991
2165
 
1992
2166
  @property
1993
2167
  def y(self):
1994
- logging.warning('property y not defined for fit.FitMap')
1995
- return(None)
2168
+ """Return the input y-array."""
2169
+ logger.warning('Undefined property y')
1996
2170
 
1997
2171
  @property
1998
2172
  def ymap(self):
1999
- return(self._ymap)
2173
+ """Return the input y-array map."""
2174
+ return self._ymap
2000
2175
 
2001
2176
  def best_parameters(self, dims=None):
2177
+ """Return the best fit parameters."""
2002
2178
  if dims is None:
2003
- return(self._best_parameters)
2004
- if not isinstance(dims, (list, tuple)) or len(dims) != len(self._map_shape):
2005
- illegal_value(dims, 'dims', 'FitMap.best_parameters', raise_error=True)
2179
+ return self._best_parameters
2180
+ if (not isinstance(dims, (list, tuple))
2181
+ or len(dims) != len(self._map_shape)):
2182
+ raise ValueError('Invalid parameter dims ({dims})')
2006
2183
  if self.best_values is None or self.best_errors is None:
2007
- logging.warning(f'Unable to obtain best parameter values for dims = {dims} in '+
2008
- 'FitMap.best_parameters')
2009
- return({})
2184
+ logger.warning(
2185
+ f'Unable to obtain best parameter values for dims = {dims}')
2186
+ return {}
2010
2187
  # Create current parameters
2011
2188
  parameters = deepcopy(self._parameters)
2012
2189
  for n, name in enumerate(self._best_parameters):
@@ -2017,26 +2194,40 @@ class FitMap(Fit):
2017
2194
  for name in sorted(parameters):
2018
2195
  if name != 'tmp_normalization_offset_c':
2019
2196
  par = parameters[name]
2020
- parameters_dict[name] = {'value': par.value, 'error': par.stderr,
2021
- 'init_value': self.init_parameters[name]['value'], 'min': par.min,
2022
- 'max': par.max, 'vary': par.vary, 'expr': par.expr}
2023
- return(parameters_dict)
2197
+ parameters_dict[name] = {
2198
+ 'value': par.value,
2199
+ 'error': par.stderr,
2200
+ 'init_value': self.init_parameters[name]['value'],
2201
+ 'min': par.min,
2202
+ 'max': par.max,
2203
+ 'vary': par.vary,
2204
+ 'expr': par.expr,
2205
+ }
2206
+ return parameters_dict
2024
2207
 
2025
2208
  def freemem(self):
2209
+ """Free memory allocated for parallel processing."""
2026
2210
  if self._memfolder is None:
2027
2211
  return
2028
2212
  try:
2029
2213
  rmtree(self._memfolder)
2030
2214
  self._memfolder = None
2031
2215
  except:
2032
- logging.warning('Could not clean-up automatically.')
2033
-
2034
- def plot(self, dims, y_title=None, plot_residual=False, plot_comp_legends=False,
2035
- plot_masked_data=True):
2036
- if not isinstance(dims, (list, tuple)) or len(dims) != len(self._map_shape):
2037
- illegal_value(dims, 'dims', 'FitMap.plot', raise_error=True)
2038
- if self._result is None or self.best_fit is None or self.best_values is None:
2039
- logging.warning(f'Unable to plot fit for dims = {dims} in FitMap.plot')
2216
+ logger.warning('Could not clean-up automatically.')
2217
+
2218
+ def plot(
2219
+ self, dims=None, y_title=None, plot_residual=False,
2220
+ plot_comp_legends=False, plot_masked_data=True, **kwargs):
2221
+ """Plot the best fits."""
2222
+ if dims is None:
2223
+ dims = [0]*len(self._map_shape)
2224
+ if (not isinstance(dims, (list, tuple))
2225
+ or len(dims) != len(self._map_shape)):
2226
+ raise ValueError('Invalid parameter dims ({dims})')
2227
+ if (self._result is None or self.best_fit is None
2228
+ or self.best_values is None):
2229
+ logger.warning(
2230
+ f'Unable to plot fit for dims = {dims}')
2040
2231
  return
2041
2232
  if y_title is None or not isinstance(y_title, str):
2042
2233
  y_title = 'data'
@@ -2048,7 +2239,8 @@ class FitMap(Fit):
2048
2239
  plots = [(self._x, np.asarray(self._ymap[dims]), 'b.')]
2049
2240
  legend = [y_title]
2050
2241
  if plot_masked_data:
2051
- plots += [(self._x[mask], np.asarray(self._ymap)[(*dims,mask)], 'bx')]
2242
+ plots += \
2243
+ [(self._x[mask], np.asarray(self._ymap)[(*dims,mask)], 'bx')]
2052
2244
  legend += ['masked data']
2053
2245
  plots += [(self._x[~mask], self.best_fit[dims], 'k-')]
2054
2246
  legend += ['best fit']
@@ -2059,8 +2251,9 @@ class FitMap(Fit):
2059
2251
  parameters = deepcopy(self._parameters)
2060
2252
  for name in self._best_parameters:
2061
2253
  if self._parameters[name].vary:
2062
- parameters[name].set(value=
2063
- self.best_values[self._best_parameters.index(name)][dims])
2254
+ parameters[name].set(
2255
+ value=self.best_values[self._best_parameters.index(name)]
2256
+ [dims])
2064
2257
  for component in self._result.components:
2065
2258
  if 'tmp_normalization_offset_c' in component.param_names:
2066
2259
  continue
@@ -2071,7 +2264,7 @@ class FitMap(Fit):
2071
2264
  modelname = f'{prefix}: {component.expr}'
2072
2265
  else:
2073
2266
  prefix = component.prefix
2074
- if len(prefix):
2267
+ if prefix:
2075
2268
  if prefix[-1] == '_':
2076
2269
  prefix = prefix[:-1]
2077
2270
  modelname = f'{prefix} ({component._name})'
@@ -2085,46 +2278,61 @@ class FitMap(Fit):
2085
2278
  plots += [(self._x[~mask], y, '--')]
2086
2279
  if plot_comp_legends:
2087
2280
  legend.append(modelname)
2088
- quick_plot(tuple(plots), legend=legend, title=str(dims), block=True)
2281
+ quick_plot(
2282
+ tuple(plots), legend=legend, title=str(dims), block=True, **kwargs)
2089
2283
 
2090
2284
  def fit(self, **kwargs):
2091
- # t0 = time()
2285
+ """Fit the model to the input data."""
2092
2286
  # Check input parameters
2093
2287
  if self._model is None:
2094
- logging.error('Undefined fit model')
2288
+ logger.error('Undefined fit model')
2095
2289
  if 'num_proc' in kwargs:
2096
2290
  num_proc = kwargs.pop('num_proc')
2097
2291
  if not is_int(num_proc, ge=1):
2098
- illegal_value(num_proc, 'num_proc', 'FitMap.fit', raise_error=True)
2292
+ raise ValueError(
2293
+ 'Invalid value for keyword argument num_proc ({num_proc})')
2099
2294
  else:
2100
2295
  num_proc = cpu_count()
2101
- if num_proc > 1 and not have_joblib:
2102
- logging.warning(f'Missing joblib in the conda environment, running FitMap serially')
2296
+ if num_proc > 1 and not HAVE_JOBLIB:
2297
+ logger.warning(
2298
+ 'Missing joblib in the conda environment, running serially')
2103
2299
  num_proc = 1
2104
2300
  if num_proc > cpu_count():
2105
- logging.warning(f'The requested number of processors ({num_proc}) exceeds the maximum '+
2106
- f'number of processors, num_proc reduced to ({cpu_count()})')
2301
+ logger.warning(
2302
+ f'The requested number of processors ({num_proc}) exceeds the '
2303
+ 'maximum number of processors, num_proc reduced to '
2304
+ f'({cpu_count()})')
2107
2305
  num_proc = cpu_count()
2108
2306
  if 'try_no_bounds' in kwargs:
2109
2307
  self._try_no_bounds = kwargs.pop('try_no_bounds')
2110
2308
  if not isinstance(self._try_no_bounds, bool):
2111
- illegal_value(self._try_no_bounds, 'try_no_bounds', 'FitMap.fit', raise_error=True)
2309
+ raise ValueError(
2310
+ 'Invalid value for keyword argument try_no_bounds '
2311
+ f'({self._try_no_bounds})')
2112
2312
  if 'redchi_cutoff' in kwargs:
2113
2313
  self._redchi_cutoff = kwargs.pop('redchi_cutoff')
2114
2314
  if not is_num(self._redchi_cutoff, gt=0):
2115
- illegal_value(self._redchi_cutoff, 'redchi_cutoff', 'FitMap.fit', raise_error=True)
2315
+ raise ValueError(
2316
+ 'Invalid value for keyword argument redchi_cutoff'
2317
+ f'({self._redchi_cutoff})')
2116
2318
  if 'print_report' in kwargs:
2117
2319
  self._print_report = kwargs.pop('print_report')
2118
2320
  if not isinstance(self._print_report, bool):
2119
- illegal_value(self._print_report, 'print_report', 'FitMap.fit', raise_error=True)
2321
+ raise ValueError(
2322
+ 'Invalid value for keyword argument print_report'
2323
+ f'({self._print_report})')
2120
2324
  if 'plot' in kwargs:
2121
2325
  self._plot = kwargs.pop('plot')
2122
2326
  if not isinstance(self._plot, bool):
2123
- illegal_value(self._plot, 'plot', 'FitMap.fit', raise_error=True)
2327
+ raise ValueError(
2328
+ 'Invalid value for keyword argument plot'
2329
+ f'({self._plot})')
2124
2330
  if 'skip_init' in kwargs:
2125
2331
  self._skip_init = kwargs.pop('skip_init')
2126
2332
  if not isinstance(self._skip_init, bool):
2127
- illegal_value(self._skip_init, 'skip_init', 'FitMap.fit', raise_error=True)
2333
+ raise ValueError(
2334
+ 'Invalid value for keyword argument skip_init'
2335
+ f'({self._skip_init})')
2128
2336
 
2129
2337
  # Apply mask if supplied:
2130
2338
  if 'mask' in kwargs:
@@ -2132,51 +2340,58 @@ class FitMap(Fit):
2132
2340
  if self._mask is not None:
2133
2341
  self._mask = np.asarray(self._mask).astype(bool)
2134
2342
  if self._x.size != self._mask.size:
2135
- raise ValueError(f'Inconsistent x and mask dimensions ({self._x.size} vs '+
2136
- f'{self._mask.size})')
2343
+ raise ValueError(
2344
+ f'Inconsistent x and mask dimensions ({self._x.size} vs '
2345
+ f'{self._mask.size})')
2137
2346
 
2138
2347
  # Add constant offset for a normalized single component model
2139
2348
  if self._result is None and self._norm is not None and self._norm[0]:
2140
- self.add_model('constant', prefix='tmp_normalization_offset_', parameters={'name': 'c',
2141
- 'value': -self._norm[0], 'vary': False, 'norm': True})
2142
- #'value': -self._norm[0]/self._norm[1], 'vary': False, 'norm': False})
2349
+ self.add_model(
2350
+ 'constant',
2351
+ prefix='tmp_normalization_offset_',
2352
+ parameters={
2353
+ 'name': 'c',
2354
+ 'value': -self._norm[0],
2355
+ 'vary': False,
2356
+ 'norm': True,
2357
+ })
2358
+ # 'value': -self._norm[0]/self._norm[1],
2359
+ # 'vary': False,
2360
+ # 'norm': False,
2143
2361
 
2144
2362
  # Adjust existing parameters for refit:
2145
2363
  if 'parameters' in kwargs:
2146
- # print('\nIn FitMap before adjusting existing parameters for refit:')
2147
- # self._parameters.pretty_print()
2148
- # if self._result is None:
2149
- # raise ValueError('Invalid parameter parameters ({parameters})')
2150
- # if self._best_values is None:
2151
- # raise ValueError('Valid self._best_values required for refitting in FitMap.fit')
2152
2364
  parameters = kwargs.pop('parameters')
2153
- # print(f'\nparameters:\n{parameters}')
2154
2365
  if isinstance(parameters, dict):
2155
2366
  parameters = (parameters, )
2156
2367
  elif not is_dict_series(parameters):
2157
- illegal_value(parameters, 'parameters', 'Fit.fit', raise_error=True)
2368
+ raise ValueError(
2369
+ 'Invalid value for keyword argument parameters'
2370
+ f'({parameters})')
2158
2371
  for par in parameters:
2159
2372
  name = par['name']
2160
2373
  if name not in self._parameters:
2161
- raise ValueError(f'Unable to match {name} parameter {par} to an existing one')
2374
+ raise ValueError(
2375
+ f'Unable to match {name} parameter {par} to an '
2376
+ 'existing one')
2162
2377
  if self._parameters[name].expr is not None:
2163
- raise ValueError(f'Unable to modify {name} parameter {par} (currently an '+
2164
- 'expression)')
2165
- value = par.get('value')
2166
- vary = par.get('vary')
2378
+ raise ValueError(
2379
+ f'Unable to modify {name} parameter {par} '
2380
+ '(currently an expression)')
2381
+ value = par.get('value')
2382
+ vary = par.get('vary')
2167
2383
  if par.get('expr') is not None:
2168
- raise KeyError(f'Invalid "expr" key in {name} parameter {par}')
2169
- self._parameters[name].set(value=value, vary=vary, min=par.get('min'),
2170
- max=par.get('max'))
2171
- # Overwrite existing best values for fixed parameters when a value is specified
2172
- # print(f'best values befored resetting:\n{self._best_values}')
2384
+ raise KeyError(
2385
+ f'Invalid "expr" key in {name} parameter {par}')
2386
+ self._parameters[name].set(
2387
+ value=value, vary=vary, min=par.get('min'),
2388
+ max=par.get('max'))
2389
+ # Overwrite existing best values for fixed parameters
2390
+ # when a value is specified
2173
2391
  if isinstance(value, (int, float)) and vary is False:
2174
2392
  for i, nname in enumerate(self._best_parameters):
2175
2393
  if nname == name:
2176
2394
  self._best_values[i] = value
2177
- # print(f'best values after resetting (value={value}, vary={vary}):\n{self._best_values}')
2178
- #RV print('\nIn FitMap after adjusting existing parameters for refit:')
2179
- #RV self._parameters.pretty_print()
2180
2395
 
2181
2396
  # Check for uninitialized parameters
2182
2397
  for name, par in self._parameters.items():
@@ -2189,68 +2404,64 @@ class FitMap(Fit):
2189
2404
  elif self._parameter_norms[name]:
2190
2405
  self._parameters[name].set(value=value*self._norm[1])
2191
2406
 
2192
- # Create the best parameter list, consisting of all varying parameters plus the expression
2193
- # parameters in order to collect their errors
2407
+ # Create the best parameter list, consisting of all varying
2408
+ # parameters plus the expression parameters in order to
2409
+ # collect their errors
2194
2410
  if self._result is None:
2195
2411
  # Initial fit
2196
- assert(self._best_parameters is None)
2197
- self._best_parameters = [name for name, par in self._parameters.items()
2198
- if par.vary or par.expr is not None]
2412
+ assert self._best_parameters is None
2413
+ self._best_parameters = [
2414
+ name for name, par in self._parameters.items()
2415
+ if par.vary or par.expr is not None]
2199
2416
  num_new_parameters = 0
2200
2417
  else:
2201
2418
  # Refit
2202
- assert(len(self._best_parameters))
2203
- self._new_parameters = [name for name, par in self._parameters.items()
2204
- if name != 'tmp_normalization_offset_c' and name not in self._best_parameters and
2205
- (par.vary or par.expr is not None)]
2419
+ assert self._best_parameters
2420
+ self._new_parameters = [
2421
+ name for name, par in self._parameters.items()
2422
+ if name != 'tmp_normalization_offset_c'
2423
+ and name not in self._best_parameters
2424
+ and (par.vary or par.expr is not None)]
2206
2425
  num_new_parameters = len(self._new_parameters)
2207
2426
  num_best_parameters = len(self._best_parameters)
2208
2427
 
2209
- # Flatten and normalize the best values of the previous fit, remove the remaining results
2210
- # of the previous fit
2428
+ # Flatten and normalize the best values of the previous fit,
2429
+ # remove the remaining results of the previous fit
2211
2430
  if self._result is not None:
2212
- # print('\nBefore flatten and normalize:')
2213
- # print(f'self._best_values:\n{self._best_values}')
2214
2431
  self._out_of_bounds = None
2215
2432
  self._max_nfev = None
2216
2433
  self._redchi = None
2217
2434
  self._success = None
2218
2435
  self._best_fit = None
2219
2436
  self._best_errors = None
2220
- assert(self._best_values is not None)
2221
- assert(self._best_values.shape[0] == num_best_parameters)
2222
- assert(self._best_values.shape[1:] == self._map_shape)
2437
+ assert self._best_values is not None
2438
+ assert self._best_values.shape[0] == num_best_parameters
2439
+ assert self._best_values.shape[1:] == self._map_shape
2223
2440
  if self._transpose is not None:
2224
- self._best_values = np.transpose(self._best_values,
2225
- [0]+[i+1 for i in self._transpose])
2226
- self._best_values = [np.reshape(self._best_values[i], self._map_dim)
2441
+ self._best_values = np.transpose(
2442
+ self._best_values, [0]+[i+1 for i in self._transpose])
2443
+ self._best_values = [
2444
+ np.reshape(self._best_values[i], self._map_dim)
2227
2445
  for i in range(num_best_parameters)]
2228
2446
  if self._norm is not None:
2229
2447
  for i, name in enumerate(self._best_parameters):
2230
2448
  if self._parameter_norms.get(name, False):
2231
2449
  self._best_values[i] /= self._norm[1]
2232
- #RV print('\nAfter flatten and normalize:')
2233
- #RV print(f'self._best_values:\n{self._best_values}')
2234
2450
 
2235
- # Normalize the initial parameters (and best values for a refit)
2236
- # print('\nIn FitMap before normalize:')
2237
- # self._parameters.pretty_print()
2238
- # print(f'\nparameter_norms:\n{self._parameter_norms}\n')
2451
+ # Normalize the initial parameters
2452
+ # (and best values for a refit)
2239
2453
  self._normalize()
2240
- # print('\nIn FitMap after normalize:')
2241
- # self._parameters.pretty_print()
2242
- # print(f'\nparameter_norms:\n{self._parameter_norms}\n')
2243
2454
 
2244
2455
  # Prevent initial values from sitting at boundaries
2245
- self._parameter_bounds = {name:{'min': par.min, 'max': par.max}
2246
- for name, par in self._parameters.items() if par.vary}
2456
+ self._parameter_bounds = {
2457
+ name:{'min': par.min, 'max': par.max}
2458
+ for name, par in self._parameters.items() if par.vary}
2247
2459
  for name, par in self._parameters.items():
2248
2460
  if par.vary:
2249
2461
  par.set(value=self._reset_par_at_boundary(par, par.value))
2250
- # print('\nAfter checking boundaries:')
2251
- # self._parameters.pretty_print()
2252
2462
 
2253
- # Set parameter bounds to unbound (only use bounds when fit fails)
2463
+ # Set parameter bounds to unbound
2464
+ # (only use bounds when fit fails)
2254
2465
  if self._try_no_bounds:
2255
2466
  for name in self._parameter_bounds.keys():
2256
2467
  self._parameters[name].set(min=-np.inf, max=np.inf)
@@ -2265,118 +2476,124 @@ class FitMap(Fit):
2265
2476
  self._max_nfev_flat = np.zeros(self._map_dim, dtype=bool)
2266
2477
  self._redchi_flat = np.zeros(self._map_dim, dtype=np.float64)
2267
2478
  self._success_flat = np.zeros(self._map_dim, dtype=bool)
2268
- self._best_fit_flat = np.zeros((self._map_dim, x_size),
2269
- dtype=self._ymap_norm.dtype)
2270
- self._best_errors_flat = [np.zeros(self._map_dim, dtype=np.float64)
2271
- for _ in range(num_best_parameters+num_new_parameters)]
2479
+ self._best_fit_flat = np.zeros(
2480
+ (self._map_dim, x_size), dtype=self._ymap_norm.dtype)
2481
+ self._best_errors_flat = [
2482
+ np.zeros(self._map_dim, dtype=np.float64)
2483
+ for _ in range(num_best_parameters+num_new_parameters)]
2272
2484
  if self._result is None:
2273
- self._best_values_flat = [np.zeros(self._map_dim, dtype=np.float64)
2274
- for _ in range(num_best_parameters)]
2485
+ self._best_values_flat = [
2486
+ np.zeros(self._map_dim, dtype=np.float64)
2487
+ for _ in range(num_best_parameters)]
2275
2488
  else:
2276
2489
  self._best_values_flat = self._best_values
2277
- self._best_values_flat += [np.zeros(self._map_dim, dtype=np.float64)
2278
- for _ in range(num_new_parameters)]
2490
+ self._best_values_flat += [
2491
+ np.zeros(self._map_dim, dtype=np.float64)
2492
+ for _ in range(num_new_parameters)]
2279
2493
  else:
2280
2494
  self._memfolder = './joblib_memmap'
2281
2495
  try:
2282
2496
  mkdir(self._memfolder)
2283
2497
  except FileExistsError:
2284
2498
  pass
2285
- filename_memmap = path.join(self._memfolder, 'out_of_bounds_memmap')
2286
- self._out_of_bounds_flat = np.memmap(filename_memmap, dtype=bool,
2287
- shape=(self._map_dim), mode='w+')
2499
+ filename_memmap = path.join(
2500
+ self._memfolder, 'out_of_bounds_memmap')
2501
+ self._out_of_bounds_flat = np.memmap(
2502
+ filename_memmap, dtype=bool, shape=(self._map_dim), mode='w+')
2288
2503
  filename_memmap = path.join(self._memfolder, 'max_nfev_memmap')
2289
- self._max_nfev_flat = np.memmap(filename_memmap, dtype=bool,
2290
- shape=(self._map_dim), mode='w+')
2504
+ self._max_nfev_flat = np.memmap(
2505
+ filename_memmap, dtype=bool, shape=(self._map_dim), mode='w+')
2291
2506
  filename_memmap = path.join(self._memfolder, 'redchi_memmap')
2292
- self._redchi_flat = np.memmap(filename_memmap, dtype=np.float64,
2293
- shape=(self._map_dim), mode='w+')
2507
+ self._redchi_flat = np.memmap(
2508
+ filename_memmap, dtype=np.float64, shape=(self._map_dim),
2509
+ mode='w+')
2294
2510
  filename_memmap = path.join(self._memfolder, 'success_memmap')
2295
- self._success_flat = np.memmap(filename_memmap, dtype=bool,
2296
- shape=(self._map_dim), mode='w+')
2511
+ self._success_flat = np.memmap(
2512
+ filename_memmap, dtype=bool, shape=(self._map_dim), mode='w+')
2297
2513
  filename_memmap = path.join(self._memfolder, 'best_fit_memmap')
2298
- self._best_fit_flat = np.memmap(filename_memmap, dtype=self._ymap_norm.dtype,
2299
- shape=(self._map_dim, x_size), mode='w+')
2514
+ self._best_fit_flat = np.memmap(
2515
+ filename_memmap, dtype=self._ymap_norm.dtype,
2516
+ shape=(self._map_dim, x_size), mode='w+')
2300
2517
  self._best_errors_flat = []
2301
2518
  for i in range(num_best_parameters+num_new_parameters):
2302
- filename_memmap = path.join(self._memfolder, f'best_errors_memmap_{i}')
2303
- self._best_errors_flat.append(np.memmap(filename_memmap, dtype=np.float64,
2304
- shape=self._map_dim, mode='w+'))
2519
+ filename_memmap = path.join(
2520
+ self._memfolder, f'best_errors_memmap_{i}')
2521
+ self._best_errors_flat.append(
2522
+ np.memmap(filename_memmap, dtype=np.float64,
2523
+ shape=self._map_dim, mode='w+'))
2305
2524
  self._best_values_flat = []
2306
2525
  for i in range(num_best_parameters):
2307
- filename_memmap = path.join(self._memfolder, f'best_values_memmap_{i}')
2308
- self._best_values_flat.append(np.memmap(filename_memmap, dtype=np.float64,
2309
- shape=self._map_dim, mode='w+'))
2526
+ filename_memmap = path.join(
2527
+ self._memfolder, f'best_values_memmap_{i}')
2528
+ self._best_values_flat.append(
2529
+ np.memmap(filename_memmap, dtype=np.float64,
2530
+ shape=self._map_dim, mode='w+'))
2310
2531
  if self._result is not None:
2311
2532
  self._best_values_flat[i][:] = self._best_values[i][:]
2312
2533
  for i in range(num_new_parameters):
2313
- filename_memmap = path.join(self._memfolder,
2314
- f'best_values_memmap_{i+num_best_parameters}')
2315
- self._best_values_flat.append(np.memmap(filename_memmap, dtype=np.float64,
2316
- shape=self._map_dim, mode='w+'))
2534
+ filename_memmap = path.join(
2535
+ self._memfolder,
2536
+ f'best_values_memmap_{i+num_best_parameters}')
2537
+ self._best_values_flat.append(
2538
+ np.memmap(filename_memmap, dtype=np.float64,
2539
+ shape=self._map_dim, mode='w+'))
2317
2540
 
2318
2541
  # Update the best parameter list
2319
2542
  if num_new_parameters:
2320
2543
  self._best_parameters += self._new_parameters
2321
2544
 
2322
- # Perform the first fit to get model component info and initial parameters
2545
+ # Perform the first fit to get model component info and
2546
+ # initial parameters
2323
2547
  current_best_values = {}
2324
- # print(f'0 before:\n{current_best_values}')
2325
- # t1 = time()
2326
- self._result = self._fit(0, current_best_values, return_result=True, **kwargs)
2327
- # t2 = time()
2328
- # print(f'0 after:\n{current_best_values}')
2329
- # print('\nAfter the first fit:')
2330
- # self._parameters.pretty_print()
2331
- # print(self._result.fit_report(show_correl=False))
2548
+ self._result = self._fit(
2549
+ 0, current_best_values, return_result=True, **kwargs)
2332
2550
 
2333
2551
  # Remove all irrelevant content from self._result
2334
- for attr in ('_abort', 'aborted', 'aic', 'best_fit', 'best_values', 'bic', 'calc_covar',
2335
- 'call_kws', 'chisqr', 'ci_out', 'col_deriv', 'covar', 'data', 'errorbars',
2336
- 'flatchain', 'ier', 'init_vals', 'init_fit', 'iter_cb', 'jacfcn', 'kws',
2337
- 'last_internal_values', 'lmdif_message', 'message', 'method', 'nan_policy',
2338
- 'ndata', 'nfev', 'nfree', 'params', 'redchi', 'reduce_fcn', 'residual', 'result',
2339
- 'scale_covar', 'show_candidates', 'calc_covar', 'success', 'userargs', 'userfcn',
2340
- 'userkws', 'values', 'var_names', 'weights', 'user_options'):
2552
+ for attr in (
2553
+ '_abort', 'aborted', 'aic', 'best_fit', 'best_values', 'bic',
2554
+ 'calc_covar', 'call_kws', 'chisqr', 'ci_out', 'col_deriv',
2555
+ 'covar', 'data', 'errorbars', 'flatchain', 'ier', 'init_vals',
2556
+ 'init_fit', 'iter_cb', 'jacfcn', 'kws', 'last_internal_values',
2557
+ 'lmdif_message', 'message', 'method', 'nan_policy', 'ndata',
2558
+ 'nfev', 'nfree', 'params', 'redchi', 'reduce_fcn', 'residual',
2559
+ 'result', 'scale_covar', 'show_candidates', 'calc_covar',
2560
+ 'success', 'userargs', 'userfcn', 'userkws', 'values',
2561
+ 'var_names', 'weights', 'user_options'):
2341
2562
  try:
2342
2563
  delattr(self._result, attr)
2343
2564
  except AttributeError:
2344
- # logging.warning(f'Unknown attribute {attr} in fit.FtMap._cleanup_result')
2345
2565
  pass
2346
2566
 
2347
- # t3 = time()
2348
2567
  if num_proc == 1:
2349
2568
  # Perform the remaining fits serially
2350
2569
  for n in range(1, self._map_dim):
2351
- # print(f'{n} before:\n{current_best_values}')
2352
2570
  self._fit(n, current_best_values, **kwargs)
2353
- # print(f'{n} after:\n{current_best_values}')
2354
2571
  else:
2355
2572
  # Perform the remaining fits in parallel
2356
2573
  num_fit = self._map_dim-1
2357
- # print(f'num_fit = {num_fit}')
2358
2574
  if num_proc > num_fit:
2359
- logging.warning(f'The requested number of processors ({num_proc}) exceeds the '+
2360
- f'number of fits, num_proc reduced to ({num_fit})')
2575
+ logger.warning(
2576
+ f'The requested number of processors ({num_proc}) exceeds '
2577
+ f'the number of fits, num_proc reduced to ({num_fit})')
2361
2578
  num_proc = num_fit
2362
2579
  num_fit_per_proc = 1
2363
2580
  else:
2364
2581
  num_fit_per_proc = round((num_fit)/num_proc)
2365
2582
  if num_proc*num_fit_per_proc < num_fit:
2366
- num_fit_per_proc +=1
2367
- # print(f'num_fit_per_proc = {num_fit_per_proc}')
2583
+ num_fit_per_proc += 1
2368
2584
  num_fit_batch = min(num_fit_per_proc, 40)
2369
- # print(f'num_fit_batch = {num_fit_batch}')
2370
2585
  with Parallel(n_jobs=num_proc) as parallel:
2371
- parallel(delayed(self._fit_parallel)(current_best_values, num_fit_batch,
2372
- n_start, **kwargs) for n_start in range(1, self._map_dim, num_fit_batch))
2373
- # t4 = time()
2586
+ parallel(
2587
+ delayed(self._fit_parallel)
2588
+ (current_best_values, num_fit_batch, n_start, **kwargs)
2589
+ for n_start in range(1, self._map_dim, num_fit_batch))
2374
2590
 
2375
2591
  # Renormalize the initial parameters for external use
2376
2592
  if self._norm is not None and self._normalized:
2377
2593
  init_values = {}
2378
2594
  for name, value in self._result.init_values.items():
2379
- if name not in self._parameter_norms or self._parameters[name].expr is not None:
2595
+ if (name not in self._parameter_norms
2596
+ or self._parameters[name].expr is not None):
2380
2597
  init_values[name] = value
2381
2598
  elif self._parameter_norms[name]:
2382
2599
  init_values[name] = value*self._norm[1]
@@ -2386,36 +2603,40 @@ class FitMap(Fit):
2386
2603
  _min = par.min
2387
2604
  _max = par.max
2388
2605
  value = par.value*self._norm[1]
2389
- if not np.isinf(_min) and abs(_min) != float_min:
2606
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
2390
2607
  _min *= self._norm[1]
2391
- if not np.isinf(_max) and abs(_max) != float_min:
2608
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
2392
2609
  _max *= self._norm[1]
2393
2610
  par.set(value=value, min=_min, max=_max)
2394
2611
  par.init_value = par.value
2395
2612
 
2396
2613
  # Remap the best results
2397
- # t5 = time()
2398
- self._out_of_bounds = np.copy(np.reshape(self._out_of_bounds_flat, self._map_shape))
2399
- self._max_nfev = np.copy(np.reshape(self._max_nfev_flat, self._map_shape))
2614
+ self._out_of_bounds = np.copy(np.reshape(
2615
+ self._out_of_bounds_flat, self._map_shape))
2616
+ self._max_nfev = np.copy(np.reshape(
2617
+ self._max_nfev_flat, self._map_shape))
2400
2618
  self._redchi = np.copy(np.reshape(self._redchi_flat, self._map_shape))
2401
- self._success = np.copy(np.reshape(self._success_flat, self._map_shape))
2402
- self._best_fit = np.copy(np.reshape(self._best_fit_flat,
2403
- list(self._map_shape)+[x_size]))
2404
- self._best_values = np.asarray([np.reshape(par, list(self._map_shape))
2405
- for par in self._best_values_flat])
2406
- self._best_errors = np.asarray([np.reshape(par, list(self._map_shape))
2407
- for par in self._best_errors_flat])
2619
+ self._success = np.copy(np.reshape(
2620
+ self._success_flat, self._map_shape))
2621
+ self._best_fit = np.copy(np.reshape(
2622
+ self._best_fit_flat, list(self._map_shape)+[x_size]))
2623
+ self._best_values = np.asarray([np.reshape(
2624
+ par, list(self._map_shape)) for par in self._best_values_flat])
2625
+ self._best_errors = np.asarray([np.reshape(
2626
+ par, list(self._map_shape)) for par in self._best_errors_flat])
2408
2627
  if self._inv_transpose is not None:
2409
- self._out_of_bounds = np.transpose(self._out_of_bounds, self._inv_transpose)
2628
+ self._out_of_bounds = np.transpose(
2629
+ self._out_of_bounds, self._inv_transpose)
2410
2630
  self._max_nfev = np.transpose(self._max_nfev, self._inv_transpose)
2411
2631
  self._redchi = np.transpose(self._redchi, self._inv_transpose)
2412
2632
  self._success = np.transpose(self._success, self._inv_transpose)
2413
- self._best_fit = np.transpose(self._best_fit,
2414
- list(self._inv_transpose)+[len(self._inv_transpose)])
2415
- self._best_values = np.transpose(self._best_values,
2416
- [0]+[i+1 for i in self._inv_transpose])
2417
- self._best_errors = np.transpose(self._best_errors,
2418
- [0]+[i+1 for i in self._inv_transpose])
2633
+ self._best_fit = np.transpose(
2634
+ self._best_fit,
2635
+ list(self._inv_transpose) + [len(self._inv_transpose)])
2636
+ self._best_values = np.transpose(
2637
+ self._best_values, [0] + [i+1 for i in self._inv_transpose])
2638
+ self._best_errors = np.transpose(
2639
+ self._best_errors, [0] + [i+1 for i in self._inv_transpose])
2419
2640
  del self._out_of_bounds_flat
2420
2641
  del self._max_nfev_flat
2421
2642
  del self._redchi_flat
@@ -2423,7 +2644,6 @@ class FitMap(Fit):
2423
2644
  del self._best_fit_flat
2424
2645
  del self._best_values_flat
2425
2646
  del self._best_errors_flat
2426
- # t6 = time()
2427
2647
 
2428
2648
  # Restore parameter bounds and renormalize the parameters
2429
2649
  for name, par in self._parameter_bounds.items():
@@ -2436,20 +2656,11 @@ class FitMap(Fit):
2436
2656
  value = par.value*self._norm[1]
2437
2657
  _min = par.min
2438
2658
  _max = par.max
2439
- if not np.isinf(_min) and abs(_min) != float_min:
2659
+ if not np.isinf(_min) and abs(_min) != FLOAT_MIN:
2440
2660
  _min *= self._norm[1]
2441
- if not np.isinf(_max) and abs(_max) != float_min:
2661
+ if not np.isinf(_max) and abs(_max) != FLOAT_MIN:
2442
2662
  _max *= self._norm[1]
2443
2663
  par.set(value=value, min=_min, max=_max)
2444
- # t7 = time()
2445
- # print(f'total run time in fit: {t7-t0:.2f} seconds')
2446
- # print(f'run time first fit: {t2-t1:.2f} seconds')
2447
- # print(f'run time remaining fits: {t4-t3:.2f} seconds')
2448
- # print(f'run time remapping results: {t6-t5:.2f} seconds')
2449
-
2450
- # print('\n\nAt end fit:')
2451
- # self._parameters.pretty_print()
2452
- # print(f'self._best_values:\n{self._best_values}\n\n')
2453
2664
 
2454
2665
  # Free the shared memory
2455
2666
  self.freemem()
@@ -2457,17 +2668,11 @@ class FitMap(Fit):
2457
2668
  def _fit_parallel(self, current_best_values, num, n_start, **kwargs):
2458
2669
  num = min(num, self._map_dim-n_start)
2459
2670
  for n in range(num):
2460
- # print(f'{n_start+n} before:\n{current_best_values}')
2461
2671
  self._fit(n_start+n, current_best_values, **kwargs)
2462
- # print(f'{n_start+n} after:\n{current_best_values}')
2463
2672
 
2464
2673
  def _fit(self, n, current_best_values, return_result=False, **kwargs):
2465
- #RV print(f'\n\nstart FitMap._fit {n}\n')
2466
- #RV print(f'current_best_values = {current_best_values}')
2467
- #RV print(f'self._best_parameters = {self._best_parameters}')
2468
- #RV print(f'self._new_parameters = {self._new_parameters}\n\n')
2469
- # self._parameters.pretty_print()
2470
- # Set parameters to current best values, but prevent them from sitting at boundaries
2674
+ # Set parameters to current best values, but prevent them from
2675
+ # sitting at boundaries
2471
2676
  if self._new_parameters is None:
2472
2677
  # Initial fit
2473
2678
  for name, value in current_best_values.items():
@@ -2479,19 +2684,17 @@ class FitMap(Fit):
2479
2684
  par = self._parameters[name]
2480
2685
  if name in self._new_parameters:
2481
2686
  if name in current_best_values:
2482
- par.set(value=self._reset_par_at_boundary(par, current_best_values[name]))
2687
+ par.set(value=self._reset_par_at_boundary(
2688
+ par, current_best_values[name]))
2483
2689
  elif par.expr is None:
2484
2690
  par.set(value=self._best_values[i][n])
2485
- #RV print(f'\nbefore fit {n}')
2486
- #RV self._parameters.pretty_print()
2487
2691
  if self._mask is None:
2488
- result = self._model.fit(self._ymap_norm[n], self._parameters, x=self._x, **kwargs)
2692
+ result = self._model.fit(
2693
+ self._ymap_norm[n], self._parameters, x=self._x, **kwargs)
2489
2694
  else:
2490
- result = self._model.fit(self._ymap_norm[n][~self._mask], self._parameters,
2491
- x=self._x[~self._mask], **kwargs)
2492
- # print(f'\nafter fit {n}')
2493
- # self._parameters.pretty_print()
2494
- # print(result.fit_report(show_correl=False))
2695
+ result = self._model.fit(
2696
+ self._ymap_norm[n][~self._mask], self._parameters,
2697
+ x=self._x[~self._mask], **kwargs)
2495
2698
  out_of_bounds = False
2496
2699
  for name, par in self._parameter_bounds.items():
2497
2700
  value = result.params[name].value
@@ -2506,7 +2709,8 @@ class FitMap(Fit):
2506
2709
  # Rerun fit with parameter bounds in place
2507
2710
  for name, par in self._parameter_bounds.items():
2508
2711
  self._parameters[name].set(min=par['min'], max=par['max'])
2509
- # Set parameters to current best values, but prevent them from sitting at boundaries
2712
+ # Set parameters to current best values, but prevent them
2713
+ # from sitting at boundaries
2510
2714
  if self._new_parameters is None:
2511
2715
  # Initial fit
2512
2716
  for name, value in current_best_values.items():
@@ -2522,17 +2726,13 @@ class FitMap(Fit):
2522
2726
  current_best_values[name]))
2523
2727
  elif par.expr is None:
2524
2728
  par.set(value=self._best_values[i][n])
2525
- # print('\nbefore fit')
2526
- # self._parameters.pretty_print()
2527
- # print(result.fit_report(show_correl=False))
2528
2729
  if self._mask is None:
2529
- result = self._model.fit(self._ymap_norm[n], self._parameters, x=self._x, **kwargs)
2730
+ result = self._model.fit(
2731
+ self._ymap_norm[n], self._parameters, x=self._x, **kwargs)
2530
2732
  else:
2531
- result = self._model.fit(self._ymap_norm[n][~self._mask], self._parameters,
2733
+ result = self._model.fit(
2734
+ self._ymap_norm[n][~self._mask], self._parameters,
2532
2735
  x=self._x[~self._mask], **kwargs)
2533
- # print(f'\nafter fit {n}')
2534
- # self._parameters.pretty_print()
2535
- # print(result.fit_report(show_correl=False))
2536
2736
  out_of_bounds = False
2537
2737
  for name, par in self._parameter_bounds.items():
2538
2738
  value = result.params[name].value
@@ -2542,26 +2742,25 @@ class FitMap(Fit):
2542
2742
  if not np.isinf(par['max']) and value > par['max']:
2543
2743
  out_of_bounds = True
2544
2744
  break
2545
- # print(f'{n} redchi < redchi_cutoff = {result.redchi < self._redchi_cutoff} success = {result.success} out_of_bounds = {out_of_bounds}')
2546
2745
  # Reset parameters back to unbound
2547
2746
  for name in self._parameter_bounds.keys():
2548
2747
  self._parameters[name].set(min=-np.inf, max=np.inf)
2549
- assert(not out_of_bounds)
2748
+ assert not out_of_bounds
2550
2749
  if result.redchi >= self._redchi_cutoff:
2551
2750
  result.success = False
2552
2751
  if result.nfev == result.max_nfev:
2553
- # print(f'Maximum number of function evaluations reached for n = {n}')
2554
- # logging.warning(f'Maximum number of function evaluations reached for n = {n}')
2555
2752
  if result.redchi < self._redchi_cutoff:
2556
2753
  result.success = True
2557
2754
  self._max_nfev_flat[n] = True
2558
2755
  if result.success:
2559
- assert(all(True for par in current_best_values if par in result.params.values()))
2756
+ assert all(
2757
+ True for par in current_best_values
2758
+ if par in result.params.values())
2560
2759
  for par in result.params.values():
2561
2760
  if par.vary:
2562
2761
  current_best_values[par.name] = par.value
2563
2762
  else:
2564
- logging.warning(f'Fit for n = {n} failed: {result.lmdif_message}')
2763
+ logger.warning(f'Fit for n = {n} failed: {result.lmdif_message}')
2565
2764
  # Renormalize the data and results
2566
2765
  self._renormalize(n, result)
2567
2766
  if self._print_report:
@@ -2569,16 +2768,15 @@ class FitMap(Fit):
2569
2768
  if self._plot:
2570
2769
  dims = np.unravel_index(n, self._map_shape)
2571
2770
  if self._inv_transpose is not None:
2572
- dims= tuple(dims[self._inv_transpose[i]] for i in range(len(dims)))
2573
- super().plot(result=result, y=np.asarray(self._ymap[dims]), plot_comp_legends=True,
2574
- skip_init=self._skip_init, title=str(dims))
2575
- #RV print(f'\n\nend FitMap._fit {n}\n')
2576
- #RV print(f'current_best_values = {current_best_values}')
2577
- # self._parameters.pretty_print()
2578
- # print(result.fit_report(show_correl=False))
2579
- #RV print(f'\nself._best_values_flat:\n{self._best_values_flat}\n\n')
2771
+ dims = tuple(
2772
+ dims[self._inv_transpose[i]] for i in range(len(dims)))
2773
+ super().plot(
2774
+ result=result, y=np.asarray(self._ymap[dims]),
2775
+ plot_comp_legends=True, skip_init=self._skip_init,
2776
+ title=str(dims))
2580
2777
  if return_result:
2581
- return(result)
2778
+ return result
2779
+ return None
2582
2780
 
2583
2781
  def _renormalize(self, n, result):
2584
2782
  self._redchi_flat[n] = np.float64(result.redchi)
@@ -2586,8 +2784,10 @@ class FitMap(Fit):
2586
2784
  if self._norm is None or not self._normalized:
2587
2785
  self._best_fit_flat[n] = result.best_fit
2588
2786
  for i, name in enumerate(self._best_parameters):
2589
- self._best_values_flat[i][n] = np.float64(result.params[name].value)
2590
- self._best_errors_flat[i][n] = np.float64(result.params[name].stderr)
2787
+ self._best_values_flat[i][n] = np.float64(
2788
+ result.params[name].value)
2789
+ self._best_errors_flat[i][n] = np.float64(
2790
+ result.params[name].stderr)
2591
2791
  else:
2592
2792
  pars = set(self._parameter_norms) & set(self._best_parameters)
2593
2793
  for name, par in result.params.items():
@@ -2599,15 +2799,21 @@ class FitMap(Fit):
2599
2799
  if self._print_report:
2600
2800
  if par.init_value is not None:
2601
2801
  par.init_value *= self._norm[1]
2602
- if not np.isinf(par.min) and abs(par.min) != float_min:
2802
+ if (not np.isinf(par.min)
2803
+ and abs(par.min) != FLOAT_MIN):
2603
2804
  par.min *= self._norm[1]
2604
- if not np.isinf(par.max) and abs(par.max) != float_min:
2805
+ if (not np.isinf(par.max)
2806
+ and abs(par.max) != FLOAT_MIN):
2605
2807
  par.max *= self._norm[1]
2606
- self._best_fit_flat[n] = result.best_fit*self._norm[1]+self._norm[0]
2808
+ self._best_fit_flat[n] = (
2809
+ result.best_fit*self._norm[1] + self._norm[0])
2607
2810
  for i, name in enumerate(self._best_parameters):
2608
- self._best_values_flat[i][n] = np.float64(result.params[name].value)
2609
- self._best_errors_flat[i][n] = np.float64(result.params[name].stderr)
2811
+ self._best_values_flat[i][n] = np.float64(
2812
+ result.params[name].value)
2813
+ self._best_errors_flat[i][n] = np.float64(
2814
+ result.params[name].stderr)
2610
2815
  if self._plot:
2611
2816
  if not self._skip_init:
2612
- result.init_fit = result.init_fit*self._norm[1]+self._norm[0]
2817
+ result.init_fit = (
2818
+ result.init_fit*self._norm[1] + self._norm[0])
2613
2819
  result.best_fit = np.copy(self._best_fit_flat[n])