You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

763 lines
27 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. import numpy as np
  2. from uncertainties import ufloat
  3. import lmfit
  4. from lmfit.models import (ConstantModel, ComplexConstantModel, LinearModel, QuadraticModel,
  5. PolynomialModel, SineModel, GaussianModel, Gaussian2dModel, LorentzianModel,
  6. SplitLorentzianModel, VoigtModel, PseudoVoigtModel, MoffatModel,
  7. Pearson7Model, StudentsTModel, BreitWignerModel, LognormalModel,
  8. DampedOscillatorModel, ExponentialGaussianModel, SkewedGaussianModel,
  9. SkewedVoigtModel, ThermalDistributionModel, DoniachModel, PowerLawModel,
  10. ExponentialModel, StepModel, RectangleModel, ExpressionModel, DampedHarmonicOscillatorModel)
  11. from lmfit.models import (guess_from_peak, guess_from_peak2d, fwhm_expr, height_expr,
  12. update_param_vals)
  13. from lmfit.lineshapes import (not_zero, breit_wigner, damped_oscillator, dho, doniach,
  14. expgaussian, exponential, gaussian, gaussian2d,
  15. linear, lognormal, lorentzian, moffat, parabolic,
  16. pearson7, powerlaw, pvoigt, rectangle, sine,
  17. skewed_gaussian, skewed_voigt, split_lorentzian, step,
  18. students_t, thermal_distribution, tiny, voigt)
  19. from lmfit import Model
  20. import numpy as np
  21. from numpy import (arctan, copysign, cos, exp, isclose, isnan, log, pi, real,
  22. sin, sqrt, where)
  23. from scipy.special import erf, erfc
  24. from scipy.special import gamma as gamfcn
  25. from scipy.special import wofz
  26. from scipy.optimize import curve_fit
  27. import xarray as xr
  28. log2 = log(2)
  29. s2pi = sqrt(2*pi)
  30. s2 = sqrt(2.0)
  31. def gaussianWithOffset(x, amplitude=1.0, center=0.0, sigma=1.0, offset=0.0):
  32. """Return a 1-dimensional Gaussian function with an offset.
  33. gaussian(x, amplitude, center, sigma) =
  34. (amplitude/(s2pi*sigma)) * exp(-(1.0*x-center)**2 / (2*sigma**2))
  35. """
  36. return ((amplitude/(max(tiny, s2pi*sigma)))
  37. * exp(-(1.0*x-center)**2 / max(tiny, (2*sigma**2))) + offset)
  38. def lorentzianWithOffset(x, amplitude=1.0, center=0.0, sigma=1.0, offset=0.0):
  39. return ((amplitude/(1 + ((1.0*x-center)/max(tiny, sigma))**2))
  40. / max(tiny, (pi*sigma)) + offset)
  41. def exponentialWithOffset(x, amplitude=1.0, decay=1.0, offset=0.0):
  42. decay = not_zero(decay)
  43. return amplitude * exp(-x/decay) + offset
  44. def expansion(x, amplitude=1.0, offset=0.0):
  45. return np.sqrt(amplitude*x*x + offset)
  46. def dampingOscillation(x, center=0, amplitude=1.0, frequency=1.0, decay=1.0, offset=0.0):
  47. return amplitude * np.exp(-decay*x)*np.sin(2*np.pi*frequency*(x-center)) + offset
  48. def two_gaussian2d(x, y=0.0, A_amplitude=1.0, A_centerx=0.0, A_centery=0.0, A_sigmax=1.0,
  49. A_sigmay=1.0, B_amplitude=1.0, B_centerx=0.0, B_centery=0.0, B_sigmax=1.0,
  50. B_sigmay=1.0):
  51. """Return a 2-dimensional Gaussian function.
  52. gaussian2d(x, y, amplitude, centerx, centery, sigmax, sigmay) =
  53. amplitude/(2*pi*sigmax*sigmay) * exp(-(x-centerx)**2/(2*sigmax**2)
  54. -(y-centery)**2/(2*sigmay**2))
  55. """
  56. z = A_amplitude*(gaussian(x, amplitude=1, center=A_centerx, sigma=A_sigmax) *
  57. gaussian(y, amplitude=1, center=A_centery, sigma=A_sigmay))
  58. z += B_amplitude*(gaussian(x, amplitude=1, center=B_centerx, sigma=B_sigmax) *
  59. gaussian(y, amplitude=1, center=B_centery, sigma=B_sigmay))
  60. return z
  61. class GaussianWithOffsetModel(Model):
  62. fwhm_factor = 2*np.sqrt(2*np.log(2))
  63. height_factor = 1./np.sqrt(2*np.pi)
  64. def __init__(self, independent_vars=['x'], nan_policy='raise', prefix='', name=None, **kwargs):
  65. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  66. 'independent_vars': independent_vars})
  67. super().__init__(gaussianWithOffset, **kwargs)
  68. self._set_paramhints_prefix()
  69. def _set_paramhints_prefix(self):
  70. self.set_param_hint('sigma', min=0)
  71. self.set_param_hint('fwhm', expr=fwhm_expr(self))
  72. self.set_param_hint('height', expr=height_expr(self))
  73. def guess(self, data, x, negative=False, **kwargs):
  74. offset = np.min(data)
  75. data = data - offset
  76. pars = guess_from_peak(self, data, x, negative)
  77. pars.add('offset', value=offset)
  78. return update_param_vals(pars, self.prefix, **kwargs)
  79. class LorentzianWithOffsetModel(Model):
  80. fwhm_factor = 2.0
  81. height_factor = 1./np.pi
  82. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  83. **kwargs):
  84. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  85. 'independent_vars': independent_vars})
  86. super().__init__(lorentzianWithOffset, **kwargs)
  87. self._set_paramhints_prefix()
  88. def _set_paramhints_prefix(self):
  89. self.set_param_hint('sigma', min=0)
  90. self.set_param_hint('fwhm', expr=fwhm_expr(self))
  91. self.set_param_hint('height', expr=height_expr(self))
  92. def guess(self, data, x, negative=False, **kwargs):
  93. """Estimate initial model parameter values from data."""
  94. offset = np.min(data)
  95. data = data - offset
  96. pars = guess_from_peak(self, data, x, negative, ampscale=1.25)
  97. pars.add('offset', value=offset)
  98. return update_param_vals(pars, self.prefix, **kwargs)
  99. class ExponentialWithOffsetModel(Model):
  100. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  101. **kwargs):
  102. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  103. 'independent_vars': independent_vars})
  104. super().__init__(exponentialWithOffset, **kwargs)
  105. def guess(self, data, x, **kwargs):
  106. """Estimate initial model parameter values from data."""
  107. offset = np.min(data)
  108. data = data - offset
  109. try:
  110. sval, oval = np.polyfit(x, np.log(abs(data)+1.e-15), 1)
  111. except TypeError:
  112. sval, oval = 1., np.log(abs(max(data)+1.e-9))
  113. pars = self.make_params(amplitude=np.exp(oval), decay=-1.0/sval)
  114. pars.add('offset', value=offset)
  115. return update_param_vals(pars, self.prefix, **kwargs)
  116. class ExpansionModel(Model):
  117. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  118. **kwargs):
  119. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  120. 'independent_vars': independent_vars})
  121. super().__init__(expansion, **kwargs)
  122. def guess(self, data, x, **kwargs):
  123. """Estimate initial model parameter values from data."""
  124. popt1, pcov1 = curve_fit(expansion, x, data)
  125. pars = self.make_params(amplitude=popt1[0], offset=popt1[1])
  126. return update_param_vals(pars, self.prefix, **kwargs)
  127. class DampingOscillationModel(Model):
  128. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  129. **kwargs):
  130. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  131. 'independent_vars': independent_vars})
  132. super().__init__(dampingOscillation, **kwargs)
  133. def guess(self, data, x, **kwargs):
  134. """Estimate initial model parameter values from data."""
  135. try:
  136. popt1, pcov1 = curve_fit(dampingOscillation, x, data, np.array(0, 5, 5e2, 1e3, 16))
  137. pars = self.make_params(center=popt1[0], amplitude=popt1[1], frequency=popt1[2], decay=popt1[3], offset=popt1[4])
  138. except:
  139. pars = self.make_params(center=0, amplitude=5.0, frequency=5e2, decay=1.0e3, offset=16.0)
  140. return update_param_vals(pars, self.prefix, **kwargs)
  141. class TwoGaussian2dModel(Model):
  142. fwhm_factor = 2*np.sqrt(2*np.log(2))
  143. height_factor = 1./2*np.pi
  144. def __init__(self, independent_vars=['x', 'y'], prefix='', nan_policy='raise',
  145. **kwargs):
  146. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  147. 'independent_vars': independent_vars})
  148. self.helperModel = Gaussian2dModel()
  149. super().__init__(two_gaussian2d, **kwargs)
  150. self._set_paramhints_prefix()
  151. def _set_paramhints_prefix(self):
  152. self.set_param_hint('delta', value=-1, max=0)
  153. self.set_param_hint('A_sigmax', expr=f'{self.prefix}delta + {self.prefix}B_sigmax')
  154. def guess(self, data, x, y, negative=False, **kwargs):
  155. pars_guess = guess_from_peak2d(self.helperModel, data, x, y, negative)
  156. pars = self.make_params(A_amplitude=pars_guess['amplitude'].value, A_centerx=pars_guess['centerx'].value, A_centery=pars_guess['centery'].value,
  157. A_sigmax=pars_guess['sigmax'].value, A_sigmay=pars_guess['sigmay'].value,
  158. B_amplitude=pars_guess['amplitude'].value, B_centerx=pars_guess['centerx'].value, B_centery=pars_guess['centery'].value,
  159. B_sigmax=pars_guess['sigmax'].value, B_sigmay=pars_guess['sigmay'].value)
  160. pars[f'{self.prefix}A_sigmax'].set(expr=f'delta + {self.prefix}B_sigmax')
  161. pars.add(f'{self.prefix}delta', value=-1, max=0, min=-np.inf, vary=True)
  162. pars[f'{self.prefix}A_sigmay'].set(min=0.0)
  163. pars[f'{self.prefix}B_sigmax'].set(min=0.0)
  164. pars[f'{self.prefix}B_sigmay'].set(min=0.0)
  165. return pars
  166. class NewFitModel(Model):
  167. def __init__(self, func, independent_vars=['x'], prefix='', nan_policy='raise',
  168. **kwargs):
  169. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  170. 'independent_vars': independent_vars})
  171. super().__init__(func, **kwargs)
  172. def guess(self, *args, **kwargs):
  173. return self.make_params()
  174. lmfit_models = {'Constant': ConstantModel,
  175. 'Complex Constant': ComplexConstantModel,
  176. 'Linear': LinearModel,
  177. 'Quadratic': QuadraticModel,
  178. 'Polynomial': PolynomialModel,
  179. 'Gaussian': GaussianModel,
  180. 'Gaussian-2D': Gaussian2dModel,
  181. 'Lorentzian': LorentzianModel,
  182. 'Split-Lorentzian': SplitLorentzianModel,
  183. 'Voigt': VoigtModel,
  184. 'PseudoVoigt': PseudoVoigtModel,
  185. 'Moffat': MoffatModel,
  186. 'Pearson7': Pearson7Model,
  187. 'StudentsT': StudentsTModel,
  188. 'Breit-Wigner': BreitWignerModel,
  189. 'Log-Normal': LognormalModel,
  190. 'Damped Oscillator': DampedOscillatorModel,
  191. 'Damped Harmonic Oscillator': DampedHarmonicOscillatorModel,
  192. 'Exponential Gaussian': ExponentialGaussianModel,
  193. 'Skewed Gaussian': SkewedGaussianModel,
  194. 'Skewed Voigt': SkewedVoigtModel,
  195. 'Thermal Distribution': ThermalDistributionModel,
  196. 'Doniach': DoniachModel,
  197. 'Power Law': PowerLawModel,
  198. 'Exponential': ExponentialModel,
  199. 'Step': StepModel,
  200. 'Rectangle': RectangleModel,
  201. 'Expression': ExpressionModel,
  202. 'Gaussian With Offset':GaussianWithOffsetModel,
  203. 'Lorentzian With Offset':LorentzianWithOffsetModel,
  204. 'Expansion':ExpansionModel,
  205. 'Damping Oscillation Model':DampingOscillationModel,
  206. 'Two Gaussian-2D':TwoGaussian2dModel,
  207. }
  208. class FitAnalyser():
  209. def __init__(self, fitModel, fitDim=1, **kwargs) -> None:
  210. if isinstance(fitModel, str):
  211. self.fitModel = lmfit_models[fitModel](**kwargs)
  212. else:
  213. self.fitModel = fitModel
  214. self.fitDim = fitDim
  215. def print_params_set_templat(self, params=None):
  216. if params is None:
  217. params = self.fitModel.make_params()
  218. for key in params:
  219. res = "params.add("
  220. res += "name=\"" + key + "\", "
  221. if not params[key].expr is None:
  222. res += "expr=" + params[key].expr +")"
  223. else:
  224. res += "value=" + f'{params[key].value:3g}' + ", "
  225. if str(params[key].max)=="inf":
  226. res += "max=np.inf, "
  227. else:
  228. res += "max=" + f'{params[key].max:3g}' + ", "
  229. if str(params[key].min)=="-inf":
  230. res += "min=-np.inf, "
  231. else:
  232. res += "min=" + f'{params[key].min:3g}' + ", "
  233. res += "vary=" + str(params[key].vary) + ")"
  234. print(res)
  235. def _guess_1D(self, data, x, **kwargs):
  236. return self.fitModel.guess(data=data, x=x, **kwargs)
  237. def _guess_2D(self, data, x, y, **kwargs):
  238. data = data.flatten(order='F')
  239. return self.fitModel.guess(data=data, x=x, y=y, **kwargs)
  240. def guess(self, dataArray, x=None, y=None, guess_kwargs={}, input_core_dims=None, dask='parallelized', vectorize=True, keep_attrs=True, daskKwargs=None, **kwargs):
  241. kwargs.update(
  242. {
  243. "dask": dask,
  244. "vectorize": vectorize,
  245. "input_core_dims": input_core_dims,
  246. 'keep_attrs': keep_attrs,
  247. }
  248. )
  249. if not daskKwargs is None:
  250. kwargs.update({"dask_gufunc_kwargs": daskKwargs})
  251. if input_core_dims is None:
  252. kwargs.update(
  253. {
  254. "input_core_dims": [['x']],
  255. }
  256. )
  257. if x is None:
  258. if 'x' in dataArray.dims:
  259. x = dataArray['x'].to_numpy()
  260. else:
  261. if isinstance(x, str):
  262. if input_core_dims is None:
  263. kwargs.update(
  264. {
  265. "input_core_dims": [[x]],
  266. }
  267. )
  268. x = dataArray[x].to_numpy()
  269. if self.fitDim == 1:
  270. guess_kwargs.update(
  271. {
  272. 'x':x,
  273. }
  274. )
  275. return xr.apply_ufunc(self._guess_1D, dataArray, kwargs=guess_kwargs,
  276. output_dtypes=[type(self.fitModel.make_params())],
  277. **kwargs
  278. )
  279. if self.fitDim == 2:
  280. if y is None:
  281. if 'y' in dataArray.dims:
  282. y = dataArray['y'].to_numpy()
  283. if input_core_dims is None:
  284. kwargs.update(
  285. {
  286. "input_core_dims": [['x', 'y']],
  287. }
  288. )
  289. else:
  290. if isinstance(y, str):
  291. kwargs["input_core_dims"][0] = np.append(kwargs["input_core_dims"][0], y)
  292. y = dataArray[y].to_numpy()
  293. elif input_core_dims is None:
  294. kwargs.update(
  295. {
  296. "input_core_dims": [['x', 'y']],
  297. }
  298. )
  299. _x, _y = np.meshgrid(x, y)
  300. _x = _x.flatten()
  301. _y = _y.flatten()
  302. # dataArray = dataArray.stack(_z=(kwargs["input_core_dims"][0][0], kwargs["input_core_dims"][0][1]))
  303. # kwargs["input_core_dims"][0] = ['_z']
  304. guess_kwargs.update(
  305. {
  306. 'x':_x,
  307. 'y':_y,
  308. }
  309. )
  310. return xr.apply_ufunc(self._guess_2D, dataArray, kwargs=guess_kwargs,
  311. output_dtypes=[type(self.fitModel.make_params())],
  312. **kwargs
  313. )
  314. def _fit_1D(self, data, params, x):
  315. return self.fitModel.fit(data=data, x=x, params=params, nan_policy='omit')
  316. def _fit_2D(self, data, params, x, y):
  317. data = data.flatten(order='F')
  318. return self.fitModel.fit(data=data, x=x, y=y, params=params, nan_policy='omit')
  319. def fit(self, dataArray, paramsArray, x=None, y=None, input_core_dims=None, dask='parallelized', vectorize=True, keep_attrs=True, daskKwargs=None, **kwargs):
  320. kwargs.update(
  321. {
  322. "dask": dask,
  323. "vectorize": vectorize,
  324. "input_core_dims": input_core_dims,
  325. 'keep_attrs': keep_attrs,
  326. }
  327. )
  328. if not daskKwargs is None:
  329. kwargs.update({"dask_gufunc_kwargs": daskKwargs})
  330. if isinstance(paramsArray, type(self.fitModel.make_params())):
  331. if input_core_dims is None:
  332. kwargs.update(
  333. {
  334. "input_core_dims": [['x']],
  335. }
  336. )
  337. if x is None:
  338. if 'x' in dataArray.dims:
  339. x = dataArray['x'].to_numpy()
  340. else:
  341. if isinstance(x, str):
  342. if input_core_dims is None:
  343. kwargs.update(
  344. {
  345. "input_core_dims": [[x]],
  346. }
  347. )
  348. x = dataArray[x].to_numpy()
  349. if self.fitDim == 1:
  350. return xr.apply_ufunc(self._fit_1D, dataArray, kwargs={'params':paramsArray,'x':x},
  351. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  352. **kwargs)
  353. if self.fitDim == 2:
  354. if y is None:
  355. if 'y' in dataArray.dims:
  356. y = dataArray['y'].to_numpy()
  357. if input_core_dims is None:
  358. kwargs.update(
  359. {
  360. "input_core_dims": [['x', 'y']],
  361. }
  362. )
  363. else:
  364. if isinstance(y, str):
  365. kwargs["input_core_dims"][0] = np.append(kwargs["input_core_dims"][0], y)
  366. y = dataArray[y].to_numpy()
  367. elif input_core_dims is None:
  368. kwargs.update(
  369. {
  370. "input_core_dims": [['x', 'y']],
  371. }
  372. )
  373. _x, _y = np.meshgrid(x, y)
  374. _x = _x.flatten()
  375. _y = _y.flatten()
  376. # dataArray = dataArray.stack(_z=(kwargs["input_core_dims"][0][0], kwargs["input_core_dims"][0][1]))
  377. # kwargs["input_core_dims"][0] = ['_z']
  378. return xr.apply_ufunc(self._fit_2D, dataArray, kwargs={'params':paramsArray,'x':_x, 'y':_y},
  379. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  380. **kwargs)
  381. else:
  382. if input_core_dims is None:
  383. kwargs.update(
  384. {
  385. "input_core_dims": [['x'], []],
  386. }
  387. )
  388. if x is None:
  389. if 'x' in dataArray.dims:
  390. x = dataArray['x'].to_numpy()
  391. else:
  392. if isinstance(x, str):
  393. if input_core_dims is None:
  394. kwargs.update(
  395. {
  396. "input_core_dims": [[x], []],
  397. }
  398. )
  399. x = dataArray[x].to_numpy()
  400. if self.fitDim == 1:
  401. return xr.apply_ufunc(self._fit_1D, dataArray, paramsArray, kwargs={'x':x},
  402. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  403. **kwargs)
  404. if self.fitDim == 2:
  405. if input_core_dims is None:
  406. kwargs.update(
  407. {
  408. "input_core_dims": [['x', 'y'], []],
  409. }
  410. )
  411. if y is None:
  412. if 'y' in dataArray.dims:
  413. y = dataArray['y'].to_numpy()
  414. else:
  415. if isinstance(y, str):
  416. y = dataArray[y].to_numpy()
  417. kwargs["input_core_dims"][0] = np.append(kwargs["input_core_dims"][0], y)
  418. _x, _y = np.meshgrid(x, y)
  419. _x = _x.flatten()
  420. _y = _y.flatten()
  421. # dataArray = dataArray.stack(_z=(kwargs["input_core_dims"][0][0], kwargs["input_core_dims"][0][1]))
  422. # kwargs["input_core_dims"][0] = ['_z']
  423. return xr.apply_ufunc(self._fit_2D, dataArray, paramsArray, kwargs={'x':_x, 'y':_y},
  424. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  425. **kwargs)
  426. def _eval_1D(self, fitResult, x):
  427. return self.fitModel.eval(x=x, **fitResult.best_values)
  428. def _eval_2D(self, fitResult, x, y, shape):
  429. res = self.fitModel.eval(x=x, y=y, **fitResult.best_values)
  430. return res.reshape(shape, order='F')
  431. def eval(self, fitResultArray, x=None, y=None, output_core_dims=None, prefix="", dask='parallelized', vectorize=True, daskKwargs=None, **kwargs):
  432. kwargs.update(
  433. {
  434. "dask": dask,
  435. "vectorize": vectorize,
  436. "output_core_dims": output_core_dims,
  437. }
  438. )
  439. if daskKwargs is None:
  440. daskKwargs = {}
  441. if self.fitDim == 1:
  442. if output_core_dims is None:
  443. kwargs.update(
  444. {
  445. "output_core_dims": prefix+'x',
  446. }
  447. )
  448. output_core_dims = [prefix+'x']
  449. daskKwargs.update(
  450. {
  451. 'output_sizes': {
  452. output_core_dims[0]: np.size(x),
  453. },
  454. 'meta': np.ndarray((0,0), dtype=float)
  455. }
  456. )
  457. kwargs.update(
  458. {
  459. "dask_gufunc_kwargs": daskKwargs,
  460. }
  461. )
  462. res = xr.apply_ufunc(self._eval_1D, fitResultArray, kwargs={"x":x}, **kwargs)
  463. return res.assign_coords({prefix+'x':np.array(x)})
  464. if self.fitDim == 2:
  465. if output_core_dims is None:
  466. kwargs.update(
  467. {
  468. "output_core_dims": [[prefix+'x', prefix+'y']],
  469. }
  470. )
  471. output_core_dims = [prefix+'x', prefix+'y']
  472. daskKwargs.update(
  473. {
  474. 'output_sizes': {
  475. output_core_dims[0]: np.size(x),
  476. output_core_dims[1]: np.size(y),
  477. },
  478. 'meta': np.ndarray((0,0), dtype=float)
  479. },
  480. )
  481. kwargs.update(
  482. {
  483. "dask_gufunc_kwargs": daskKwargs,
  484. }
  485. )
  486. _x, _y = np.meshgrid(x, y)
  487. _x = _x.flatten()
  488. _y = _y.flatten()
  489. res = xr.apply_ufunc(self._eval_2D, fitResultArray, kwargs={"x":_x, "y":_y, "shape":(len(x), len(y))}, **kwargs)
  490. return res.assign_coords({prefix+'x':np.array(x), prefix+'y':np.array(y)})
  491. def _get_fit_value_single(self, fitResult, key):
  492. return fitResult.params[key].value
  493. def _get_fit_value(self, fitResult, params):
  494. func = np.vectorize(self._get_fit_value_single)
  495. res = tuple(
  496. func(fitResult, key)
  497. for key in params
  498. )
  499. return res
  500. def get_fit_value(self, fitResult, dask='parallelized', **kwargs):
  501. firstIndex = {
  502. key: fitResult[key][0]
  503. for key in fitResult.dims
  504. }
  505. firstFitResult = fitResult.sel(firstIndex).item()
  506. params = list(firstFitResult.params.keys())
  507. output_core_dims=[ [] for _ in range(len(params))]
  508. kwargs.update(
  509. {
  510. "dask": dask,
  511. "output_core_dims": output_core_dims,
  512. }
  513. )
  514. value = xr.apply_ufunc(self._get_fit_value, fitResult, kwargs=dict(params=params), **kwargs)
  515. value = xr.Dataset(
  516. data_vars={
  517. params[i]: value[i]
  518. for i in range(len(params))
  519. },
  520. attrs=fitResult.attrs
  521. )
  522. return value
  523. def _get_fit_std_single(self, fitResult, key):
  524. return fitResult.params[key].stderr
  525. def _get_fit_std(self, fitResult, params):
  526. func = np.vectorize(self._get_fit_std_single)
  527. res = tuple(
  528. func(fitResult, key)
  529. for key in params
  530. )
  531. return res
  532. def get_fit_std(self, fitResult, dask='parallelized', **kwargs):
  533. firstIndex = {
  534. key: fitResult[key][0]
  535. for key in fitResult.dims
  536. }
  537. firstFitResult = fitResult.sel(firstIndex).item()
  538. params = list(firstFitResult.params.keys())
  539. output_core_dims=[ [] for _ in range(len(params))]
  540. kwargs.update(
  541. {
  542. "dask": dask,
  543. "output_core_dims": output_core_dims,
  544. }
  545. )
  546. value = xr.apply_ufunc(self._get_fit_std, fitResult, kwargs=dict(params=params), **kwargs)
  547. value = xr.Dataset(
  548. data_vars={
  549. params[i]: value[i]
  550. for i in range(len(params))
  551. },
  552. attrs=fitResult.attrs
  553. )
  554. return value
  555. def _get_fit_full_result_single(self, fitResult, key):
  556. if not fitResult.params[key].value is None:
  557. value = fitResult.params[key].value
  558. else:
  559. value = np.nan
  560. if not fitResult.params[key].stderr is None:
  561. std = fitResult.params[key].stderr
  562. else:
  563. std = np.nan
  564. return ufloat(value, std)
  565. def _get_fit_full_result(self, fitResult, params):
  566. func = np.vectorize(self._get_fit_full_result_single)
  567. res = tuple(
  568. func(fitResult, key)
  569. for key in params
  570. )
  571. return res
  572. def get_fit_full_result(self, fitResult, dask='parallelized', **kwargs):
  573. firstIndex = {
  574. key: fitResult[key][0]
  575. for key in fitResult.dims
  576. }
  577. firstFitResult = fitResult.sel(firstIndex).item()
  578. params = list(firstFitResult.params.keys())
  579. output_core_dims=[ [] for _ in range(len(params))]
  580. kwargs.update(
  581. {
  582. "dask": dask,
  583. "output_core_dims": output_core_dims,
  584. }
  585. )
  586. value = xr.apply_ufunc(self._get_fit_full_result, fitResult, kwargs=dict(params=params), **kwargs)
  587. value = xr.Dataset(
  588. data_vars={
  589. params[i]: value[i]
  590. for i in range(len(params))
  591. },
  592. attrs=fitResult.attrs
  593. )
  594. return value