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.
- CHAP/TaskManager.py +214 -0
- CHAP/common/models/integration.py +392 -249
- CHAP/common/models/map.py +350 -198
- CHAP/common/processor.py +227 -189
- CHAP/common/reader.py +52 -39
- CHAP/common/utils/fit.py +1197 -991
- CHAP/common/utils/general.py +629 -372
- CHAP/common/utils/material.py +158 -121
- CHAP/common/utils/scanparsers.py +735 -339
- CHAP/common/writer.py +31 -25
- CHAP/edd/models.py +63 -49
- CHAP/edd/processor.py +130 -109
- CHAP/edd/reader.py +1 -1
- CHAP/edd/writer.py +1 -1
- CHAP/inference/processor.py +35 -28
- CHAP/inference/reader.py +1 -1
- CHAP/inference/writer.py +1 -1
- CHAP/pipeline.py +14 -28
- CHAP/processor.py +44 -75
- CHAP/reader.py +49 -40
- CHAP/runner.py +73 -32
- CHAP/saxswaxs/processor.py +1 -1
- CHAP/saxswaxs/reader.py +1 -1
- CHAP/saxswaxs/writer.py +1 -1
- CHAP/server.py +130 -0
- CHAP/sin2psi/processor.py +1 -1
- CHAP/sin2psi/reader.py +1 -1
- CHAP/sin2psi/writer.py +1 -1
- CHAP/tomo/__init__.py +1 -4
- CHAP/tomo/models.py +53 -31
- CHAP/tomo/processor.py +1326 -900
- CHAP/tomo/reader.py +4 -2
- CHAP/tomo/writer.py +4 -2
- CHAP/writer.py +47 -41
- {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/METADATA +1 -1
- ChessAnalysisPipeline-0.0.6.dist-info/RECORD +52 -0
- ChessAnalysisPipeline-0.0.5.dist-info/RECORD +0 -50
- {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/WHEEL +0 -0
- {ChessAnalysisPipeline-0.0.5.dist-info → ChessAnalysisPipeline-0.0.6.dist-info}/entry_points.txt +0 -0
- {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
|
|
2
|
-
|
|
3
|
-
#
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
#pylint: disable=
|
|
4
4
|
"""
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
File : fit.py
|
|
6
|
+
Author : Rolf Verberg <rolfverberg AT gmail dot com>
|
|
7
|
+
Description: General curve fitting module
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
except:
|
|
24
|
-
|
|
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
|
|
31
|
-
|
|
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
|
-
|
|
41
|
-
except:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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':
|
|
64
|
-
'lorentzian':
|
|
65
|
-
'splitlorentzian':
|
|
66
|
-
'voight':
|
|
67
|
-
'pseudovoight':
|
|
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':
|
|
72
|
-
'lorentzian':
|
|
73
|
-
'splitlorentzian':
|
|
74
|
-
'voight':
|
|
75
|
-
'pseudovoight':
|
|
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
|
-
"""
|
|
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
|
-
|
|
99
|
-
if not isinstance(
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
131
|
+
elif HAVE_XARRAY and isinstance(y, xr.DataArray):
|
|
107
132
|
if x is not None:
|
|
108
|
-
|
|
133
|
+
logger.warning('Ignoring superfluous input x ({x})')
|
|
109
134
|
if y.ndim != 1:
|
|
110
|
-
|
|
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
|
-
|
|
141
|
+
raise ValueError(f'Invalid parameter y ({y})')
|
|
115
142
|
if self._x.ndim != 1:
|
|
116
|
-
raise ValueError(
|
|
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(
|
|
119
|
-
|
|
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(
|
|
131
|
-
|
|
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
|
-
|
|
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
|
|
154
|
-
return
|
|
155
|
-
|
|
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
|
|
161
|
-
return
|
|
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
|
|
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] = {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
"""
|
|
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
|
|
182
|
-
if not
|
|
183
|
-
|
|
184
|
-
|
|
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',
|
|
199
|
-
best_results['best_values'] =
|
|
200
|
-
|
|
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
|
|
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
|
|
208
|
-
return
|
|
209
|
-
|
|
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
|
|
215
|
-
return
|
|
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
|
-
|
|
222
|
-
return
|
|
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] = {
|
|
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
|
|
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] = {
|
|
298
|
+
components[name] = {
|
|
299
|
+
'parameters': parameters,
|
|
300
|
+
}
|
|
248
301
|
else:
|
|
249
|
-
components[name] = {
|
|
250
|
-
|
|
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
|
|
256
|
-
return
|
|
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
|
|
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] = {
|
|
267
|
-
|
|
268
|
-
|
|
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
|
|
274
|
-
return
|
|
275
|
-
sorted(self._result.init_params)
|
|
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
|
|
346
|
+
return None
|
|
281
347
|
if self._norm is None:
|
|
282
|
-
return
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
294
|
-
return
|
|
363
|
+
return None
|
|
364
|
+
return self._result.nfev
|
|
295
365
|
|
|
296
366
|
@property
|
|
297
367
|
def parameters(self):
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
305
|
-
return
|
|
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
|
|
311
|
-
return
|
|
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
|
|
391
|
+
return None
|
|
317
392
|
if not self._result.success:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
"""
|
|
401
|
+
"""
|
|
402
|
+
Return the variable names for the covarience matrix property.
|
|
336
403
|
"""
|
|
337
404
|
if self._result is None:
|
|
338
|
-
return
|
|
339
|
-
return
|
|
405
|
+
return None
|
|
406
|
+
return getattr(self._result, 'var_names', None)
|
|
340
407
|
|
|
341
408
|
@property
|
|
342
409
|
def x(self):
|
|
343
|
-
|
|
410
|
+
"""Return the input x-array."""
|
|
411
|
+
return self._x
|
|
344
412
|
|
|
345
413
|
@property
|
|
346
414
|
def y(self):
|
|
347
|
-
|
|
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(
|
|
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
|
-
|
|
369
|
-
|
|
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(
|
|
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(
|
|
453
|
+
raise ValueError(
|
|
454
|
+
f'Invalid "vary" value ({vary}) in parameter {parameter}')
|
|
379
455
|
if not vary:
|
|
380
456
|
if 'min' in parameter:
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
386
|
-
|
|
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(
|
|
467
|
+
raise ValueError(
|
|
468
|
+
f'Missing parameter normalization type for parameter {name}')
|
|
390
469
|
self._parameters.add(**parameter)
|
|
391
470
|
|
|
392
|
-
def add_model(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
#
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
427
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
450
|
-
|
|
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(
|
|
454
|
-
|
|
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':
|
|
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':
|
|
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':
|
|
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':
|
|
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(
|
|
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':
|
|
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
|
|
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':
|
|
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
|
|
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':
|
|
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':
|
|
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
|
|
518
|
-
|
|
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':
|
|
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
|
|
531
|
-
|
|
532
|
-
|
|
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':
|
|
656
|
+
elif model == 'expression':
|
|
657
|
+
# Par: by expression
|
|
545
658
|
expr = kwargs['expr']
|
|
546
659
|
if not isinstance(expr, str):
|
|
547
|
-
raise ValueError(
|
|
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
|
-
|
|
551
|
-
|
|
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(
|
|
556
|
-
|
|
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
|
-
|
|
559
|
-
'
|
|
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(
|
|
680
|
+
raise ValueError(
|
|
681
|
+
f'Invalid "name" value ({name}) in input '
|
|
682
|
+
'parameters')
|
|
564
683
|
ast = Interpreter()
|
|
565
|
-
expr_parameters = [
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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 = [
|
|
576
|
-
|
|
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
|
-
|
|
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
|
|
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) !=
|
|
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) !=
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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
|
-
|
|
673
|
-
|
|
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
|
-
|
|
677
|
-
|
|
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
|
-
|
|
681
|
-
|
|
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
|
-
|
|
685
|
-
|
|
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
|
-
|
|
690
|
-
|
|
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
|
-
|
|
694
|
-
|
|
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
|
-
|
|
698
|
-
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
#
|
|
725
|
-
#
|
|
726
|
-
|
|
727
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
851
|
+
return None
|
|
852
|
+
return result.eval(x=np.asarray(x))-self.normalization_offset
|
|
750
853
|
|
|
751
|
-
def fit(self,
|
|
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
|
-
|
|
755
|
-
return
|
|
756
|
-
if
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
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
|
-
|
|
766
|
-
|
|
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
|
-
|
|
889
|
+
logger.warning(
|
|
890
|
+
'Ignoring input parameter guess during refitting')
|
|
776
891
|
guess = False
|
|
777
892
|
|
|
778
893
|
# Check for circular expressions
|
|
779
|
-
#
|
|
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(
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
#
|
|
794
|
-
#
|
|
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(
|
|
802
|
-
|
|
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(
|
|
809
|
-
|
|
810
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
825
|
-
|
|
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(
|
|
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
|
-
|
|
844
|
-
|
|
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(
|
|
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
|
|
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(
|
|
887
|
-
|
|
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 = {
|
|
894
|
-
|
|
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(
|
|
906
|
-
#
|
|
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(
|
|
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(
|
|
911
|
-
|
|
912
|
-
|
|
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
|
-
|
|
932
|
-
|
|
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
|
-
|
|
1059
|
+
logger.warning('Ignorint invalid parameter y ({y}')
|
|
947
1060
|
if len(y) != len(self._x):
|
|
948
|
-
|
|
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,
|
|
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(
|
|
984
|
-
|
|
985
|
-
plots += [(self._x[~mask],
|
|
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
|
-
|
|
992
|
-
|
|
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(
|
|
998
|
-
|
|
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
|
-
|
|
1008
|
-
|
|
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
|
|
1011
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1029
|
-
|
|
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
|
-
|
|
1058
|
-
|
|
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
|
-
|
|
1061
|
-
|
|
1169
|
+
low = index_nearest(
|
|
1170
|
+
x, (center_guesses[n-1]+center_guesses[n]) / 2)
|
|
1171
|
+
upp = len(x)
|
|
1062
1172
|
else:
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
|
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
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1312
|
+
delta_y_const = \
|
|
1313
|
+
self._parameters[name] * np.ones(len(x))
|
|
1229
1314
|
else:
|
|
1230
|
-
delta_y_const =
|
|
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 =
|
|
1241
|
-
|
|
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
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
#
|
|
1251
|
-
#
|
|
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
|
-
|
|
1259
|
-
|
|
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
|
-
|
|
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 = [
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if not
|
|
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
|
-
|
|
1272
|
-
|
|
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[
|
|
1275
|
-
|
|
1276
|
-
|
|
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[
|
|
1279
|
-
|
|
1358
|
+
y_const += self._parameters[name].value \
|
|
1359
|
+
* np.ones(len(x))
|
|
1280
1360
|
elif isinstance(component, QuadraticModel):
|
|
1281
|
-
|
|
1282
|
-
|
|
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[
|
|
1285
|
-
|
|
1286
|
-
|
|
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[
|
|
1289
|
-
|
|
1290
|
-
|
|
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[
|
|
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
|
|
1295
|
-
#
|
|
1296
|
-
#
|
|
1297
|
-
|
|
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
|
|
1300
|
-
assert
|
|
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
|
-
|
|
1307
|
-
|
|
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 =
|
|
1400
|
+
dexpr_dnname = diff(expr, nname)
|
|
1317
1401
|
if dexpr_dnname:
|
|
1318
|
-
assert
|
|
1319
|
-
|
|
1320
|
-
|
|
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
|
-
|
|
1406
|
+
mat_a[:,free_parameters.index(nname)] += \
|
|
1407
|
+
y_expr
|
|
1326
1408
|
else:
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
delta_y_const = np.multiply(
|
|
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
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
#
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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) !=
|
|
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) !=
|
|
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) !=
|
|
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) !=
|
|
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 =
|
|
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) !=
|
|
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) !=
|
|
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 =
|
|
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
|
|
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) !=
|
|
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) !=
|
|
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
|
|
1459
|
-
#
|
|
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
|
|
1469
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1503
|
-
|
|
1504
|
-
return
|
|
1505
|
-
return
|
|
1583
|
+
return low
|
|
1584
|
+
if value >= upp:
|
|
1585
|
+
return upp
|
|
1586
|
+
return value
|
|
1506
1587
|
|
|
1507
1588
|
|
|
1508
1589
|
class FitMultipeak(Fit):
|
|
1509
|
-
"""
|
|
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(
|
|
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
|
-
"""
|
|
1521
|
-
|
|
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
|
|
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(
|
|
1527
|
-
|
|
1528
|
-
|
|
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(
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
def fit(
|
|
1540
|
-
|
|
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
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
1657
|
+
logger.warning(
|
|
1658
|
+
' -> Should not happen with param_constraint set, '
|
|
1659
|
+
'fail the fit')
|
|
1568
1660
|
success = False
|
|
1569
1661
|
else:
|
|
1570
|
-
|
|
1571
|
-
self.fit(
|
|
1572
|
-
|
|
1573
|
-
|
|
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(
|
|
1589
|
-
|
|
1673
|
+
self.plot(
|
|
1674
|
+
skip_init=True, plot_comp=True, plot_comp_legends=True,
|
|
1675
|
+
plot_residual=True)
|
|
1590
1676
|
|
|
1591
|
-
return
|
|
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)
|
|
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
|
|
1695
|
+
raise ValueError(f'Invalid parameter peak model ({peak_models})')
|
|
1606
1696
|
if len(peak_models) != num_peaks:
|
|
1607
|
-
raise ValueError(
|
|
1608
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1622
|
-
|
|
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
|
-
|
|
1718
|
+
logger.warning(
|
|
1719
|
+
'Ignoring center_exprs input for unconstrained fit')
|
|
1626
1720
|
center_exprs = None
|
|
1627
1721
|
else:
|
|
1628
|
-
raise ValueError(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
1682
|
-
|
|
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(
|
|
1696
|
-
|
|
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,
|
|
1699
|
-
|
|
1806
|
+
{'name': 'sigma', 'value': sig_init,
|
|
1807
|
+
'min': min_value, 'max': sig_max},
|
|
1808
|
+
))
|
|
1700
1809
|
else:
|
|
1701
|
-
self.add_model(
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
'
|
|
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 =
|
|
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
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
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'))
|
|
1721
|
-
par['value'] <= 0.0)
|
|
1722
|
-
((name.endswith('sigma') or name.endswith('fwhm'))
|
|
1723
|
-
|
|
1724
|
-
(name
|
|
1725
|
-
|
|
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')
|
|
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[
|
|
1846
|
+
sigma_max = self._sigma_max[
|
|
1847
|
+
int(index.search(name).group())-1]
|
|
1732
1848
|
if par['value'] > sigma_max:
|
|
1733
|
-
|
|
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
|
-
|
|
1737
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1743
|
-
|
|
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
|
-
|
|
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
|
|
1770
|
-
#
|
|
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
|
|
1899
|
+
elif HAVE_XARRAY and isinstance(ymap, xr.DataArray):
|
|
1774
1900
|
if x is not None:
|
|
1775
|
-
|
|
1901
|
+
logger.warning('Ignoring superfluous input x ({x})')
|
|
1776
1902
|
self._x = np.asarray(ymap[ymap.dims[-1]])
|
|
1777
1903
|
else:
|
|
1778
|
-
|
|
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(
|
|
1786
|
-
|
|
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(
|
|
1789
|
-
|
|
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
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
1798
|
-
|
|
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)
|
|
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
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
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(
|
|
1815
|
-
|
|
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
|
|
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(
|
|
1821
|
-
|
|
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(
|
|
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(
|
|
1838
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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 = {
|
|
1887
|
-
|
|
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'] =
|
|
1898
|
-
|
|
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
|
|
2049
|
+
return best_results
|
|
1901
2050
|
|
|
1902
2051
|
@property
|
|
1903
2052
|
def best_values(self):
|
|
1904
|
-
|
|
2053
|
+
"""Return values of the best fit parameters."""
|
|
2054
|
+
return self._best_values
|
|
1905
2055
|
|
|
1906
2056
|
@property
|
|
1907
2057
|
def chisqr(self):
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
-
|
|
1916
|
-
|
|
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] = {
|
|
2077
|
+
parameters[name] = {
|
|
2078
|
+
'free': False,
|
|
2079
|
+
'expr': self._parameters[name].expr,
|
|
2080
|
+
}
|
|
1926
2081
|
else:
|
|
1927
|
-
parameters[name] = {
|
|
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
|
|
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
|
|
2104
|
+
return components
|
|
1947
2105
|
|
|
1948
2106
|
@property
|
|
1949
2107
|
def covar(self):
|
|
1950
|
-
|
|
1951
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1960
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2141
|
+
return None
|
|
1974
2142
|
if self._mask is None:
|
|
1975
|
-
|
|
2143
|
+
residual = np.asarray(self._ymap)-self.best_fit
|
|
1976
2144
|
else:
|
|
1977
|
-
ymap_flat = np.reshape(
|
|
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(
|
|
1980
|
-
|
|
1981
|
-
|
|
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
|
-
|
|
2156
|
+
"""Return the success value for each fit."""
|
|
2157
|
+
return self._success
|
|
1986
2158
|
|
|
1987
2159
|
@property
|
|
1988
2160
|
def var_names(self):
|
|
1989
|
-
|
|
1990
|
-
|
|
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
|
-
|
|
1995
|
-
|
|
2168
|
+
"""Return the input y-array."""
|
|
2169
|
+
logger.warning('Undefined property y')
|
|
1996
2170
|
|
|
1997
2171
|
@property
|
|
1998
2172
|
def ymap(self):
|
|
1999
|
-
|
|
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
|
|
2004
|
-
if not isinstance(dims, (list, tuple))
|
|
2005
|
-
|
|
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
|
-
|
|
2008
|
-
|
|
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] = {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
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
|
-
|
|
2033
|
-
|
|
2034
|
-
def plot(
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
if
|
|
2039
|
-
|
|
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 +=
|
|
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(
|
|
2063
|
-
|
|
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
|
|
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(
|
|
2281
|
+
quick_plot(
|
|
2282
|
+
tuple(plots), legend=legend, title=str(dims), block=True, **kwargs)
|
|
2089
2283
|
|
|
2090
2284
|
def fit(self, **kwargs):
|
|
2091
|
-
|
|
2285
|
+
"""Fit the model to the input data."""
|
|
2092
2286
|
# Check input parameters
|
|
2093
2287
|
if self._model is None:
|
|
2094
|
-
|
|
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
|
-
|
|
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
|
|
2102
|
-
|
|
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
|
-
|
|
2106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
2136
|
-
|
|
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(
|
|
2141
|
-
|
|
2142
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
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(
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
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
|
|
2193
|
-
#
|
|
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
|
|
2197
|
-
self._best_parameters = [
|
|
2198
|
-
|
|
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
|
|
2203
|
-
self._new_parameters = [
|
|
2204
|
-
|
|
2205
|
-
|
|
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,
|
|
2210
|
-
#
|
|
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
|
|
2221
|
-
assert
|
|
2222
|
-
assert
|
|
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(
|
|
2225
|
-
|
|
2226
|
-
self._best_values = [
|
|
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
|
|
2236
|
-
#
|
|
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 = {
|
|
2246
|
-
|
|
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
|
|
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(
|
|
2269
|
-
|
|
2270
|
-
self._best_errors_flat = [
|
|
2271
|
-
|
|
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 = [
|
|
2274
|
-
|
|
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 += [
|
|
2278
|
-
|
|
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(
|
|
2286
|
-
|
|
2287
|
-
|
|
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(
|
|
2290
|
-
|
|
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(
|
|
2293
|
-
|
|
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(
|
|
2296
|
-
|
|
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(
|
|
2299
|
-
|
|
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(
|
|
2303
|
-
|
|
2304
|
-
|
|
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(
|
|
2308
|
-
|
|
2309
|
-
|
|
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(
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
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
|
|
2545
|
+
# Perform the first fit to get model component info and
|
|
2546
|
+
# initial parameters
|
|
2323
2547
|
current_best_values = {}
|
|
2324
|
-
|
|
2325
|
-
|
|
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 (
|
|
2335
|
-
'
|
|
2336
|
-
'
|
|
2337
|
-
'
|
|
2338
|
-
'
|
|
2339
|
-
'
|
|
2340
|
-
'
|
|
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
|
-
|
|
2360
|
-
|
|
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(
|
|
2372
|
-
|
|
2373
|
-
|
|
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
|
|
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) !=
|
|
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) !=
|
|
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
|
-
|
|
2398
|
-
|
|
2399
|
-
self._max_nfev = np.copy(np.reshape(
|
|
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(
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
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(
|
|
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(
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
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) !=
|
|
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) !=
|
|
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
|
-
#
|
|
2466
|
-
#
|
|
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(
|
|
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(
|
|
2692
|
+
result = self._model.fit(
|
|
2693
|
+
self._ymap_norm[n], self._parameters, x=self._x, **kwargs)
|
|
2489
2694
|
else:
|
|
2490
|
-
result = self._model.fit(
|
|
2491
|
-
|
|
2492
|
-
|
|
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
|
|
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(
|
|
2730
|
+
result = self._model.fit(
|
|
2731
|
+
self._ymap_norm[n], self._parameters, x=self._x, **kwargs)
|
|
2530
2732
|
else:
|
|
2531
|
-
result = self._model.fit(
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
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
|
|
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(
|
|
2590
|
-
|
|
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)
|
|
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)
|
|
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] =
|
|
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(
|
|
2609
|
-
|
|
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 =
|
|
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])
|