ChessAnalysisPipeline 0.0.14__py3-none-any.whl → 0.0.15__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/utils/models.py ADDED
@@ -0,0 +1,567 @@
1
+ """Utils Pydantic model classes."""
2
+
3
+ # Third party imports
4
+ import numpy as np
5
+ from pydantic import (
6
+ BaseModel,
7
+ PrivateAttr,
8
+ StrictBool,
9
+ conint,
10
+ conlist,
11
+ confloat,
12
+ constr,
13
+ validator,
14
+ )
15
+ from typing import (
16
+ Literal,
17
+ Optional,
18
+ Union,
19
+ )
20
+
21
+ # Local modules
22
+ from CHAP.utils.general import not_zero, tiny
23
+
24
+ tiny = np.finfo(np.float64).resolution
25
+ s2pi = np.sqrt(2*np.pi)
26
+
27
+ #def constant(x, c=0.5):
28
+ def constant(x, c=0.0):
29
+ """Return a linear function.
30
+
31
+ constant(x, c) = c
32
+
33
+ """
34
+ return c*np.ones((x.size))
35
+
36
+
37
+ #def linear(x, slope=0.9, intercept=0.1):
38
+ def linear(x, slope=1.0, intercept=0.0):
39
+ """Return a linear function.
40
+
41
+ linear(x, slope, intercept) = slope * x + intercept
42
+
43
+ """
44
+ return slope * x + intercept
45
+
46
+
47
+ #def quadratic(x, a=0.5, b=0.4, c=0.1):
48
+ def quadratic(x, a=0.0, b=0.0, c=0.0):
49
+ """Return a parabolic function.
50
+
51
+ parabolic(x, a, b, c) = a * x**2 + b * x + c
52
+
53
+ """
54
+ return (a*x + b) * x + c
55
+
56
+
57
+ #def exponential(x, amplitude=1.0, decay=0.3):
58
+ def exponential(x, amplitude=1.0, decay=1.0):
59
+ """Return an exponential function.
60
+
61
+ exponential(x, amplitude, decay) = amplitude * exp(-x/decay)
62
+
63
+ """
64
+ return amplitude * np.exp(-x/not_zero(decay))
65
+
66
+
67
+ #def gaussian(x, amplitude=0.25, center=0.5, sigma=0.1):
68
+ def gaussian(x, amplitude=1.0, center=0.0, sigma=1.0):
69
+ """Return a 1-dimensional Gaussian function.
70
+
71
+ gaussian(x, amplitude, center, sigma) =
72
+ (amplitude/(s2pi*sigma)) * exp(-(x-center)**2 / (2*sigma**2))
73
+
74
+ """
75
+ return ((amplitude/(max(tiny, s2pi*sigma)))
76
+ * np.exp(-(x-center)**2 / max(tiny, (2*sigma**2))))
77
+
78
+
79
+ #def lorentzian(x, amplitude=0.3, center=0.5, sigma=0.1):
80
+ def lorentzian(x, amplitude=1.0, center=0.0, sigma=1.0):
81
+ """Return a 1-dimensional Lorentzian function.
82
+
83
+ lorentzian(x, amplitude, center, sigma) =
84
+ (amplitude/(1 + ((1.0*x-center)/sigma)**2)) / (pi*sigma)
85
+
86
+ """
87
+ return ((amplitude/(1 + ((x-center)/max(tiny, sigma))**2))
88
+ / max(tiny, (pi*sigma)))
89
+
90
+
91
+ def rectangle(
92
+ x, amplitude=1.0, center1=0.0, sigma1=1.0, center2=1.0,
93
+ sigma2=1.0, form='linear'):
94
+ """Return a rectangle function.
95
+
96
+ Starts at 0.0, rises to `amplitude` (at `center1` with width `sigma1`),
97
+ then drops to 0.0 (at `center2` with width `sigma2`) with `form`:
98
+ - `'linear'` (default) = ramp_up + ramp_down
99
+ - `'atan'`, `'arctan`' = amplitude*(atan(arg1) + atan(arg2))/pi
100
+ - `'erf'` = amplitude*(erf(arg1) + erf(arg2))/2.
101
+ - `'logisitic'` = amplitude*[1 - 1/(1 + exp(arg1)) - 1/(1+exp(arg2))]
102
+
103
+ where ``arg1 = (x - center1)/sigma1`` and
104
+ ``arg2 = -(x - center2)/sigma2``.
105
+
106
+ """
107
+ arg1 = (x - center1)/max(tiny, sigma1)
108
+ arg2 = (center2 - x)/max(tiny, sigma2)
109
+
110
+ if form == 'erf':
111
+ # Third party modules
112
+ from scipy.special import erf
113
+
114
+ rect = 0.5*(erf(arg1) + erf(arg2))
115
+ elif form == 'logistic':
116
+ rect = 1. - 1./(1. + np.exp(arg1)) - 1./(1. + np.exp(arg2))
117
+ elif form in ('atan', 'arctan'):
118
+ rect = (np.arctan(arg1) + np.arctan(arg2))/pi
119
+ elif form == 'linear':
120
+ rect = 0.5*(np.minimum(1, np.maximum(-1, arg1))
121
+ + np.minimum(1, np.maximum(-1, arg2)))
122
+ else:
123
+ raise ValueError(f'Invalid parameter form ({form})')
124
+
125
+ return amplitude*rect
126
+
127
+
128
+ def validate_parameters(parameters, values):
129
+ """Validate the parameters
130
+
131
+ :param parameters: Fit model parameters.
132
+ :type parameters: list[FitParameter]
133
+ :return: List of fit model parameters.
134
+ :rtype: list[FitParameter]
135
+ """
136
+ # System imports
137
+ import inspect
138
+ from copy import deepcopy
139
+
140
+ model = values.get('model', None)
141
+ if model is None or model == 'expression':
142
+ return parameters
143
+ sig = {
144
+ name:par
145
+ for name, par in inspect.signature(models[model]).parameters.items()}
146
+ sig.pop('x')
147
+
148
+ # Check input model parameter validity
149
+ for par in parameters:
150
+ if par.name not in sig:
151
+ raise ValueError('Invalid parameter {par.name} in {model} model')
152
+
153
+ # Set model parameters
154
+ output_parameters = []
155
+ for sig_name, sig_par in sig.items():
156
+ if model == 'rectangle' and sig_name == 'form':
157
+ continue
158
+ for par in parameters:
159
+ if sig_name == par.name:
160
+ break
161
+ else:
162
+ par = FitParameter(name=sig_name)
163
+ if sig_par.default != sig_par.empty:
164
+ par._default = sig_par.default
165
+ output_parameters.append(par)
166
+
167
+ return output_parameters
168
+
169
+
170
+ class FitParameter(BaseModel):
171
+ """
172
+ Class representing a specific fit parameter for the fit processor.
173
+
174
+ """
175
+ name: constr(strip_whitespace=True, min_length=1)
176
+ value: Optional[confloat(allow_inf_nan=False)]
177
+ min: Optional[confloat()] = -np.inf
178
+ max: Optional[confloat()] = np.inf
179
+ vary: StrictBool = True
180
+ expr: Optional[constr(strip_whitespace=True, min_length=1)]
181
+ _default: float = PrivateAttr()
182
+ _init_value: float = PrivateAttr()
183
+ _prefix: str = PrivateAttr()
184
+ _stderr: float = PrivateAttr()
185
+
186
+ @validator('min', always=True)
187
+ def validate_min(cls, value):
188
+ """Validate the specified min.
189
+
190
+ :param value: Field value to validate (`min`).
191
+ :type value: Union[float, None]
192
+ :return: Lower bound of fit parameter.
193
+ :rtype: float
194
+ """
195
+ if value is None:
196
+ return -np.inf
197
+ return value
198
+
199
+ @validator('max', always=True)
200
+ def validate_max(cls, value):
201
+ """Validate the specified max.
202
+
203
+ :param value: Field value to validate (`max`).
204
+ :type value: Union[float, None]
205
+ :return: Upper bound of fit parameter.
206
+ :rtype: float
207
+ """
208
+ if value is None:
209
+ return np.inf
210
+ return value
211
+
212
+ @property
213
+ def default(self):
214
+ """Return the _default attribute."""
215
+ if hasattr(self, '_default'):
216
+ return self._default
217
+ else:
218
+ return None
219
+
220
+ @property
221
+ def init_value(self):
222
+ """Return the _init_value attribute."""
223
+ if hasattr(self, '_init_value'):
224
+ return self._init_value
225
+ else:
226
+ return None
227
+
228
+ @property
229
+ def prefix(self):
230
+ """Return the _prefix attribute."""
231
+ if hasattr(self, '_prefix'):
232
+ return self._prefix
233
+ else:
234
+ return None
235
+
236
+ @property
237
+ def stderr(self):
238
+ """Return the _stderr attribute."""
239
+ if hasattr(self, '_stderr'):
240
+ return self._stderr
241
+ else:
242
+ return None
243
+
244
+ def set(self, value=None, min=None, max=None, vary=None, expr=None):
245
+ """
246
+ Set or update FitParameter attributes.
247
+
248
+ :param value: Parameter value.
249
+ :type value: float, optional
250
+ :param min: Lower Parameter value bound. To remove the lower
251
+ bound you must set min to `numpy.inf`.
252
+ :type min: bool, optional
253
+ :param max: Upper Parameter value bound. To remove the lower
254
+ bound you must set max to `numpy.inf`.
255
+ :type max: bool, optional
256
+ :param vary: Whether the Parameter is varied during a fit.
257
+ :type vary: bool, optional
258
+ :param expr: Mathematical expression used to constrain the
259
+ value during the fit. To remove a constraint you must
260
+ supply an empty string.
261
+ :type expr: str, optional
262
+ """
263
+ if expr is not None:
264
+ if not isinstance(expr, str):
265
+ raise ValueError(f'Invalid parameter expr ({expr})')
266
+ if expr == '':
267
+ expr = None
268
+ self.expr = expr
269
+ if expr is not None:
270
+ self.value = None
271
+ self.min = -np.inf
272
+ self.max = np.inf
273
+ self.vary = False
274
+ return
275
+ if min is not None:
276
+ if not isinstance(min, (int, float)):
277
+ raise ValueError(f'Invalid parameter min ({min})')
278
+ self.min = min
279
+ if max is not None:
280
+ if not isinstance(max, (int, float)):
281
+ raise ValueError(f'Invalid parameter max ({max})')
282
+ self.max = max
283
+ if vary is not None:
284
+ if not isinstance(vary, bool):
285
+ raise ValueError(f'Invalid parameter vary ({vary})')
286
+ self.vary = vary
287
+ if value is not None:
288
+ if not isinstance(value, (int, float)):
289
+ raise ValueError(f'Invalid parameter value ({value})')
290
+ self.value = value
291
+ if self.value > self.max:
292
+ self.value = self.max
293
+ elif self.value < self.min:
294
+ self.value = self.min
295
+ self.expr = None
296
+
297
+ class Constant(BaseModel):
298
+ """
299
+ Class representing a Constant model component.
300
+
301
+ :ivar model: The model component base name (a prefix will be added
302
+ if multiple identical model components are added).
303
+ :type model: Literal['constant']
304
+ :ivar parameters: Function parameters, defaults to those auto
305
+ generated from the function signature (excluding the
306
+ independent variable), defaults to `[]`.
307
+ :type parameters: list[FitParameter], optional
308
+ :ivar prefix: The model prefix, defaults to `''`.
309
+ :type prefix: str, optional
310
+ """
311
+ model: Literal['constant']
312
+ parameters: conlist(item_type=FitParameter) = []
313
+ prefix: Optional[str] = ''
314
+
315
+ _validate_parameters_parameters = validator(
316
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
317
+
318
+
319
+ class Linear(BaseModel):
320
+ """
321
+ Class representing a Linear model component.
322
+
323
+ :ivar model: The model component base name (a prefix will be added
324
+ if multiple identical model components are added).
325
+ :type model: Literal['linear']
326
+ :ivar parameters: Function parameters, defaults to those auto
327
+ generated from the function signature (excluding the
328
+ independent variable), defaults to `[]`.
329
+ :type parameters: list[FitParameter], optional
330
+ :ivar prefix: The model prefix, defaults to `''`.
331
+ :type prefix: str, optional
332
+ """
333
+ model: Literal['linear']
334
+ parameters: conlist(item_type=FitParameter) = []
335
+ prefix: Optional[str] = ''
336
+
337
+ _validate_parameters_parameters = validator(
338
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
339
+
340
+
341
+ class Quadratic(BaseModel):
342
+ """
343
+ Class representing a Quadratic model component.
344
+
345
+ :ivar model: The model component base name (a prefix will be added
346
+ if multiple identical model components are added).
347
+ :type model: Literal['quadratic']
348
+ :ivar parameters: Function parameters, defaults to those auto
349
+ generated from the function signature (excluding the
350
+ independent variable), defaults to `[]`.
351
+ :type parameters: list[FitParameter], optional
352
+ :ivar prefix: The model prefix, defaults to `''`.
353
+ :type prefix: str, optional
354
+ """
355
+ model: Literal['quadratic']
356
+ parameters: conlist(item_type=FitParameter) = []
357
+ prefix: Optional[str] = ''
358
+
359
+ _validate_parameters_parameters = validator(
360
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
361
+
362
+
363
+ class Exponential(BaseModel):
364
+ """
365
+ Class representing an Exponential model component.
366
+
367
+ :ivar model: The model component base name (a prefix will be added
368
+ if multiple identical model components are added).
369
+ :type model: Literal['exponential']
370
+ :ivar parameters: Function parameters, defaults to those auto
371
+ generated from the function signature (excluding the
372
+ independent variable), defaults to `[]`.
373
+ :type parameters: list[FitParameter], optional
374
+ :ivar prefix: The model prefix, defaults to `''`.
375
+ :type prefix: str, optional
376
+ """
377
+ model: Literal['exponential']
378
+ parameters: conlist(item_type=FitParameter) = []
379
+ prefix: Optional[str] = ''
380
+
381
+ _validate_parameters_parameters = validator(
382
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
383
+
384
+
385
+ class Gaussian(BaseModel):
386
+ """
387
+ Class representing a Gaussian model component.
388
+
389
+ :ivar model: The model component base name (a prefix will be added
390
+ if multiple identical model components are added).
391
+ :type model: Literal['gaussian']
392
+ :ivar parameters: Function parameters, defaults to those auto
393
+ generated from the function signature (excluding the
394
+ independent variable), defaults to `[]`.
395
+ :type parameters: list[FitParameter], optional
396
+ :ivar prefix: The model prefix, defaults to `''`.
397
+ :type prefix: str, optional
398
+ """
399
+ model: Literal['gaussian']
400
+ parameters: conlist(item_type=FitParameter) = []
401
+ prefix: Optional[str] = ''
402
+
403
+ _validate_parameters_parameters = validator(
404
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
405
+
406
+
407
+ class Lorentzian(BaseModel):
408
+ """
409
+ Class representing a Lorentzian model component.
410
+
411
+ :ivar model: The model component base name (a prefix will be added
412
+ if multiple identical model components are added).
413
+ :type model: Literal['lorentzian']
414
+ :ivar parameters: Function parameters, defaults to those auto
415
+ generated from the function signature (excluding the
416
+ independent variable), defaults to `[]`.
417
+ :type parameters: list[FitParameter], optional
418
+ :ivar prefix: The model prefix, defaults to `''`.
419
+ :type prefix: str, optional
420
+ """
421
+ model: Literal['lorentzian']
422
+ parameters: conlist(item_type=FitParameter) = []
423
+ prefix: Optional[str] = ''
424
+
425
+ _validate_parameters_parameters = validator(
426
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
427
+
428
+
429
+ class Rectangle(BaseModel):
430
+ """
431
+ Class representing a Rectangle model component.
432
+
433
+ :ivar model: The model component base name (a prefix will be added
434
+ if multiple identical model components are added).
435
+ :type model: Literal['rectangle']
436
+ :ivar parameters: Function parameters, defaults to those auto
437
+ generated from the function signature (excluding the
438
+ independent variable), defaults to `[]`.
439
+ :type parameters: list[FitParameter], optional
440
+ :ivar prefix: The model prefix, defaults to `''`.
441
+ :type prefix: str, optional
442
+ """
443
+ model: Literal['rectangle']
444
+ parameters: conlist(item_type=FitParameter) = []
445
+ prefix: Optional[str] = ''
446
+
447
+ _validate_parameters_parameters = validator(
448
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
449
+
450
+
451
+ class Expression(BaseModel):
452
+ """
453
+ Class representing an Expression model component.
454
+
455
+ :ivar model: The model component base name (a prefix will be added
456
+ if multiple identical model components are added).
457
+ :type model: Literal['expression']
458
+ :ivar expr: Mathematical expression to represent the model
459
+ component.
460
+ :type expr: str
461
+ :ivar parameters: Function parameters, defaults to those auto
462
+ generated from the model expression (excluding the
463
+ independent variable), defaults to `[]`.
464
+ :type parameters: list[FitParameter], optional
465
+ :ivar prefix: The model prefix, defaults to `''`.
466
+ :type prefix: str, optional
467
+ """
468
+ model: Literal['expression']
469
+ expr: constr(strip_whitespace=True, min_length=1)
470
+ parameters: conlist(item_type=FitParameter) = []
471
+ prefix: Optional[str] = ''
472
+
473
+ _validate_parameters_parameters = validator(
474
+ 'parameters', always=True, allow_reuse=True)(validate_parameters)
475
+
476
+
477
+ class Multipeak(BaseModel):
478
+ model: Literal['multipeak']
479
+ centers: conlist(item_type=confloat(allow_inf_nan=False), min_items=1)
480
+ fit_type: Optional[Literal['uniform', 'unconstrained']] = 'unconstrained'
481
+ centers_range: Optional[confloat(allow_inf_nan=False)]
482
+ fwhm_min: Optional[confloat(allow_inf_nan=False)]
483
+ fwhm_max: Optional[confloat(allow_inf_nan=False)]
484
+ peak_models: Literal['gaussian', 'lorentzian'] = 'gaussian'
485
+
486
+
487
+ models = {
488
+ 'constant': constant,
489
+ 'linear': linear,
490
+ 'quadratic': quadratic,
491
+ 'exponential': exponential,
492
+ 'gaussian': gaussian,
493
+ 'lorentzian': lorentzian,
494
+ 'rectangle': rectangle,
495
+ }
496
+
497
+ model_classes = (
498
+ Constant,
499
+ Linear,
500
+ Quadratic,
501
+ Exponential,
502
+ Gaussian,
503
+ Lorentzian,
504
+ Rectangle,
505
+ )
506
+
507
+
508
+ class FitConfig(BaseModel):
509
+ """
510
+ Class representing the configuration for the fit processor.
511
+
512
+ :ivar code: Specifies is lmfit is used to perform the fit or if
513
+ the scipy fit method is called directly, default is `'lmfit'`.
514
+ :type code: Literal['lmfit', 'scipy'], optional
515
+ :ivar parameters: Fit model parameters in addition to those
516
+ implicitly defined through the build-in model functions,
517
+ defaults to `[]`'
518
+ :type parameters: list[FitParameter], optional
519
+ :ivar models: The component(s) of the (composite) fit model.
520
+ :type models: Union[Constant, Linear, Quadratic, Exponential,
521
+ Gaussian, Lorentzian, Rectangle, Expression, Multipeak]
522
+ :ivar rel_height_cutoff: Relative peak height cutoff for
523
+ peak fitting (any peak with a height smaller than
524
+ `rel_height_cutoff` times the maximum height of all peaks
525
+ gets removed from the fit model), defaults to `None`.
526
+ :type rel_height_cutoff: float, optional
527
+ :ivar num_proc: The number of processors used in fitting a map
528
+ of data, defaults to `1`.
529
+ :type num_proc: int, optional
530
+ :ivar plot: Weather a plot of the fit result is generated,
531
+ defaults to `False`.
532
+ :type plot: bool, optional.
533
+ :ivar print_report: Weather to generate a fit result printout,
534
+ defaults to `False`.
535
+ :type print_report: bool, optional.
536
+ """
537
+ code: Literal['lmfit', 'scipy'] = 'scipy'
538
+ parameters: conlist(item_type=FitParameter) = []
539
+ models: conlist(item_type=Union[
540
+ Constant, Linear, Quadratic, Exponential, Gaussian, Lorentzian,
541
+ Rectangle, Expression, Multipeak], min_items=1)
542
+ method: Literal[
543
+ 'leastsq', 'trf', 'dogbox', 'lm', 'least_squares'] = 'leastsq'
544
+ rel_height_cutoff: Optional[confloat(gt=0, lt=1.0, allow_inf_nan=False)]
545
+ num_proc: conint(gt=0) = 1
546
+ plot: StrictBool = False
547
+ print_report: StrictBool = False
548
+
549
+ @validator('method', always=True)
550
+ def validate_method(cls, value, values):
551
+ """Validate the specified method.
552
+
553
+ :param value: Field value to validate (`method`).
554
+ :type value: str
555
+ :param values: Dictionary of validated class field values.
556
+ :type values: dict
557
+ :return: Fit method.
558
+ :rtype: str
559
+ """
560
+ code = values['code']
561
+ if code == 'lmfit':
562
+ if value not in ('leastsq', 'least_squares'):
563
+ value = 'leastsq'
564
+ elif value == 'least_squares':
565
+ value = 'leastsq'
566
+
567
+ return value