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.

1209 lines
47 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 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 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 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. import copy
  29. log2 = log(2)
  30. s2pi = sqrt(2*pi)
  31. s2 = sqrt(2.0)
  32. def gaussianWithOffset(x, amplitude=1.0, center=0.0, sigma=1.0, offset=0.0):
  33. """Return a 1-dimensional Gaussian function with an offset.
  34. gaussian(x, amplitude, center, sigma) =
  35. (amplitude/(s2pi*sigma)) * exp(-(1.0*x-center)**2 / (2*sigma**2))
  36. """
  37. return ((amplitude/(max(tiny, s2pi*sigma)))
  38. * exp(-(1.0*x-center)**2 / max(tiny, (2*sigma**2))) + offset)
  39. def lorentzianWithOffset(x, amplitude=1.0, center=0.0, sigma=1.0, offset=0.0):
  40. return ((amplitude/(1 + ((1.0*x-center)/max(tiny, sigma))**2))
  41. / max(tiny, (pi*sigma)) + offset)
  42. def exponentialWithOffset(x, amplitude=1.0, decay=1.0, offset=0.0):
  43. decay = not_zero(decay)
  44. return amplitude * exp(-x/decay) + offset
  45. def expansion(x, amplitude=1.0, offset=0.0):
  46. return np.sqrt(amplitude*x*x + offset)
  47. def dampingOscillation(x, center=0, amplitude=1.0, frequency=1.0, decay=1.0, offset=0.0):
  48. return amplitude * np.exp(-decay*x)*np.sin(2*np.pi*frequency*(x-center)) + offset
  49. def two_gaussian2d(x, y=0.0, A_amplitude=1.0, A_centerx=0.0, A_centery=0.0, A_sigmax=1.0,
  50. A_sigmay=1.0, B_amplitude=1.0, B_centerx=0.0, B_centery=0.0, B_sigmax=1.0,
  51. B_sigmay=1.0):
  52. """Return a 2-dimensional Gaussian function.
  53. gaussian2d(x, y, amplitude, centerx, centery, sigmax, sigmay) =
  54. amplitude/(2*pi*sigmax*sigmay) * exp(-(x-centerx)**2/(2*sigmax**2)
  55. -(y-centery)**2/(2*sigmay**2))
  56. """
  57. z = A_amplitude*(gaussian(x, amplitude=1, center=A_centerx, sigma=A_sigmax) *
  58. gaussian(y, amplitude=1, center=A_centery, sigma=A_sigmay))
  59. z += B_amplitude*(gaussian(x, amplitude=1, center=B_centerx, sigma=B_sigmax) *
  60. gaussian(y, amplitude=1, center=B_centery, sigma=B_sigmay))
  61. return z
  62. def ThomasFermi_2d(x, y=0.0, centerx=0.0, centery=0.0, amplitude=1.0, sigmax=1.0, sigmay=1.0):
  63. res = (1- ((x-centerx)/(sigmax))**2 - ((y-centery)/(sigmay))**2)**(3 / 2)
  64. return amplitude * 5 / 2 / np.pi / max(tiny, sigmax * sigmay) * np.where(res > 0, res, 0)
  65. def polylog(power, numerator):
  66. order = 20
  67. dataShape = numerator.shape
  68. numerator = np.tile(numerator, (order, 1))
  69. numerator = np.power(numerator.T, np.arange(1, order+1)).T
  70. denominator = np.arange(1, order+1)
  71. denominator = np.tile(denominator, (dataShape[0], 1))
  72. denominator = denominator.T
  73. data = numerator/ np.power(denominator, power)
  74. return np.sum(data, axis=0)
  75. def polylog2_2d(x, y=0.0, centerx=0.0, centery=0.0, amplitude=1.0, sigmax=1.0, sigmay=1.0):
  76. ## Approximation of the polylog function with 2D gaussian as argument. -> discribes the thermal part of the cloud
  77. return amplitude / 2 / np.pi / 1.20206 / max(tiny, sigmax * sigmay) * polylog(2, np.exp( -((x-centerx)**2/(2 * (sigmax)**2))-((y-centery)**2/( 2 * (sigmay)**2)) ))
  78. def density_profile_BEC_2d(x, y=0.0, BEC_amplitude=1.0, thermal_amplitude=1.0, BEC_centerx=0.0, BEC_centery=0.0, thermal_centerx=0.0, thermal_centery=0.0,
  79. BEC_sigmax=1.0, BEC_sigmay=1.0, thermal_sigmax=1.0, thermal_sigmay=1.0):
  80. return ThomasFermi_2d(x=x, y=y, centerx=BEC_centerx, centery=BEC_centery,
  81. amplitude=BEC_amplitude, sigmax=BEC_sigmax, sigmay=BEC_sigmay
  82. ) + polylog2_2d(x=x, y=y, centerx=thermal_centerx, centery=thermal_centery,
  83. amplitude=thermal_amplitude, sigmax=thermal_sigmax, sigmay=thermal_sigmay)
  84. class GaussianWithOffsetModel(Model):
  85. fwhm_factor = 2*np.sqrt(2*np.log(2))
  86. height_factor = 1./np.sqrt(2*np.pi)
  87. def __init__(self, independent_vars=['x'], nan_policy='raise', prefix='', name=None, **kwargs):
  88. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  89. 'independent_vars': independent_vars})
  90. super().__init__(gaussianWithOffset, **kwargs)
  91. self._set_paramhints_prefix()
  92. def _set_paramhints_prefix(self):
  93. self.set_param_hint('sigma', min=0)
  94. self.set_param_hint('fwhm', expr=fwhm_expr(self))
  95. self.set_param_hint('height', expr=height_expr(self))
  96. def guess(self, data, x, negative=False, **kwargs):
  97. offset = np.min(data)
  98. data = data - offset
  99. pars = guess_from_peak(self, data, x, negative)
  100. pars.add('offset', value=offset)
  101. return update_param_vals(pars, self.prefix, **kwargs)
  102. class LorentzianWithOffsetModel(Model):
  103. fwhm_factor = 2.0
  104. height_factor = 1./np.pi
  105. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  106. **kwargs):
  107. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  108. 'independent_vars': independent_vars})
  109. super().__init__(lorentzianWithOffset, **kwargs)
  110. self._set_paramhints_prefix()
  111. def _set_paramhints_prefix(self):
  112. self.set_param_hint('sigma', min=0)
  113. self.set_param_hint('fwhm', expr=fwhm_expr(self))
  114. self.set_param_hint('height', expr=height_expr(self))
  115. def guess(self, data, x, negative=False, **kwargs):
  116. """Estimate initial model parameter values from data."""
  117. offset = np.min(data)
  118. data = data - offset
  119. pars = guess_from_peak(self, data, x, negative, ampscale=1.25)
  120. pars.add('offset', value=offset)
  121. return update_param_vals(pars, self.prefix, **kwargs)
  122. class ExponentialWithOffsetModel(Model):
  123. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  124. **kwargs):
  125. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  126. 'independent_vars': independent_vars})
  127. super().__init__(exponentialWithOffset, **kwargs)
  128. def guess(self, data, x, **kwargs):
  129. """Estimate initial model parameter values from data."""
  130. offset = np.min(data)
  131. data = data - offset
  132. try:
  133. sval, oval = np.polyfit(x, np.log(abs(data)+1.e-15), 1)
  134. except TypeError:
  135. sval, oval = 1., np.log(abs(max(data)+1.e-9))
  136. pars = self.make_params(amplitude=np.exp(oval), decay=-1.0/sval)
  137. pars.add('offset', value=offset)
  138. return update_param_vals(pars, self.prefix, **kwargs)
  139. class ExpansionModel(Model):
  140. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  141. **kwargs):
  142. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  143. 'independent_vars': independent_vars})
  144. super().__init__(expansion, **kwargs)
  145. def guess(self, data, x, **kwargs):
  146. """Estimate initial model parameter values from data."""
  147. popt1, pcov1 = curve_fit(expansion, x, data)
  148. pars = self.make_params(amplitude=popt1[0], offset=popt1[1])
  149. return update_param_vals(pars, self.prefix, **kwargs)
  150. class DampingOscillationModel(Model):
  151. def __init__(self, independent_vars=['x'], prefix='', nan_policy='raise',
  152. **kwargs):
  153. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  154. 'independent_vars': independent_vars})
  155. super().__init__(dampingOscillation, **kwargs)
  156. def guess(self, data, x, **kwargs):
  157. """Estimate initial model parameter values from data."""
  158. try:
  159. popt1, pcov1 = curve_fit(dampingOscillation, x, data, np.array(0, 5, 5e2, 1e3, 16))
  160. pars = self.make_params(center=popt1[0], amplitude=popt1[1], frequency=popt1[2], decay=popt1[3], offset=popt1[4])
  161. except:
  162. pars = self.make_params(center=0, amplitude=5.0, frequency=5e2, decay=1.0e3, offset=16.0)
  163. return update_param_vals(pars, self.prefix, **kwargs)
  164. class TwoGaussian2dModel(Model):
  165. fwhm_factor = 2*np.sqrt(2*np.log(2))
  166. height_factor = 1./2*np.pi
  167. def __init__(self, independent_vars=['x', 'y'], prefix='', nan_policy='raise',
  168. **kwargs):
  169. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  170. 'independent_vars': independent_vars})
  171. self.helperModel = Gaussian2dModel()
  172. super().__init__(two_gaussian2d, **kwargs)
  173. self._set_paramhints_prefix()
  174. def _set_paramhints_prefix(self):
  175. self.set_param_hint('delta', value=-1, max=0)
  176. self.set_param_hint('A_sigmax', expr=f'{self.prefix}delta + {self.prefix}B_sigmax')
  177. def guess(self, data, x, y, negative=False, **kwargs):
  178. pars_guess = guess_from_peak2d(self.helperModel, data, x, y, negative)
  179. pars = self.make_params(A_amplitude=pars_guess['amplitude'].value, A_centerx=pars_guess['centerx'].value, A_centery=pars_guess['centery'].value,
  180. A_sigmax=pars_guess['sigmax'].value, A_sigmay=pars_guess['sigmay'].value,
  181. B_amplitude=pars_guess['amplitude'].value, B_centerx=pars_guess['centerx'].value, B_centery=pars_guess['centery'].value,
  182. B_sigmax=pars_guess['sigmax'].value, B_sigmay=pars_guess['sigmay'].value)
  183. pars[f'{self.prefix}A_sigmax'].set(expr=f'delta + {self.prefix}B_sigmax')
  184. pars.add(f'{self.prefix}delta', value=-1, max=0, min=-np.inf, vary=True)
  185. pars[f'{self.prefix}A_sigmay'].set(min=0.0)
  186. pars[f'{self.prefix}B_sigmax'].set(min=0.0)
  187. pars[f'{self.prefix}B_sigmay'].set(min=0.0)
  188. return pars
  189. class Polylog22dModel(Model):
  190. fwhm_factor = 2*np.sqrt(2*np.log(2))
  191. height_factor = 1./2*np.pi
  192. def __init__(self, independent_vars=['x', 'y'], prefix='', nan_policy='raise',
  193. **kwargs):
  194. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  195. 'independent_vars': independent_vars})
  196. super().__init__(polylog2_2d, **kwargs)
  197. self._set_paramhints_prefix()
  198. def _set_paramhints_prefix(self):
  199. self.set_param_hint('Rx', min=0)
  200. self.set_param_hint('Ry', min=0)
  201. def guess(self, data, x, y, negative=False, **kwargs):
  202. """Estimate initial model parameter values from data."""
  203. pars = guess_from_peak2d(self, data, x, y, negative)
  204. return update_param_vals(pars, self.prefix, **kwargs)
  205. class ThomasFermi2dModel(Model):
  206. fwhm_factor = 1
  207. height_factor = 0.5
  208. def __init__(self, independent_vars=['x', 'y'], prefix='', nan_policy='raise',
  209. **kwargs):
  210. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  211. 'independent_vars': independent_vars})
  212. super().__init__(ThomasFermi_2d, **kwargs)
  213. self._set_paramhints_prefix()
  214. def _set_paramhints_prefix(self):
  215. self.set_param_hint('Rx', min=0)
  216. self.set_param_hint('Ry', min=0)
  217. def guess(self, data, x, y, negative=False, **kwargs):
  218. """Estimate initial model parameter values from data."""
  219. pars = guess_from_peak2d(self, data, x, y, negative)
  220. # amplitude = pars['amplitude'].value
  221. # simgax = pars['sigmax'].value
  222. # sigmay = pars['sigmay'].value
  223. # pars['amplitude'].set(value=amplitude/s2pi/simgax/sigmay)
  224. simgax = pars['sigmax'].value
  225. sigmay = pars['sigmay'].value
  226. pars['simgax'].set(value=simgax / 2.355)
  227. pars['sigmay'].set(value=sigmay / 2.355)
  228. return update_param_vals(pars, self.prefix, **kwargs)
  229. class DensityProfileBEC2dModel(Model):
  230. fwhm_factor = 2*np.sqrt(2*np.log(2))
  231. height_factor = 1./2*np.pi
  232. def __init__(self, independent_vars=['x', 'y'], prefix='', nan_policy='raise',
  233. **kwargs):
  234. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  235. 'independent_vars': independent_vars})
  236. super().__init__(density_profile_BEC_2d, **kwargs)
  237. self._set_paramhints_prefix()
  238. def _set_paramhints_prefix(self):
  239. # self.set_param_hint('BEC_sigmax', min=0)
  240. self.set_param_hint('deltax', min=0)
  241. self.set_param_hint('BEC_sigmax', expr=f'3 * {self.prefix}thermal_sigmax - {self.prefix}deltax')
  242. self.set_param_hint('BEC_sigmay', min=0)
  243. self.set_param_hint('thermal_sigmax', min=0)
  244. # self.set_param_hint('thermal_sigmay', min=0)
  245. self.set_param_hint('BEC_amplitude', min=0)
  246. self.set_param_hint('thermal_amplitude', min=0)
  247. self.set_param_hint('thermalAspectRatio', min=0.8, max=1.2)
  248. self.set_param_hint('thermal_sigmay', expr=f'{self.prefix}thermalAspectRatio * {self.prefix}thermal_sigmax')
  249. # self.set_param_hint('betax', value=0)
  250. # self.set_param_hint('BEC_centerx', expr=f'{self.prefix}thermal_sigmax - {self.prefix}betax')
  251. self.set_param_hint('condensate_fraction', expr=f'{self.prefix}BEC_amplitude / ({self.prefix}BEC_amplitude + {self.prefix}thermal_amplitude)')
  252. def guess(self, data, x, y, negative=False, pureBECThreshold=0.5, noBECThThreshold=0.0, **kwargs):
  253. """Estimate initial model parameter values from data."""
  254. fitModel = TwoGaussian2dModel()
  255. pars = fitModel.guess(data, x=x, y=y, negative=negative)
  256. pars['A_amplitude'].set(min=0)
  257. pars['B_amplitude'].set(min=0)
  258. pars['A_centerx'].set(min=pars['A_centerx'].value - 3 * pars['A_sigmax'],
  259. max=pars['A_centerx'].value + 3 * pars['A_sigmax'],)
  260. pars['A_centery'].set(min=pars['A_centery'].value - 3 * pars['A_sigmay'],
  261. max=pars['A_centery'].value + 3 * pars['A_sigmay'],)
  262. pars['B_centerx'].set(min=pars['B_centerx'].value - 3 * pars['B_sigmax'],
  263. max=pars['B_centerx'].value + 3 * pars['B_sigmax'],)
  264. pars['B_centery'].set(min=pars['B_centery'].value - 3 * pars['B_sigmay'],
  265. max=pars['B_centery'].value + 3 * pars['B_sigmay'],)
  266. fitResult = fitModel.fit(data, x=x, y=y, params=pars, **kwargs)
  267. pars_guess = fitResult.params
  268. BEC_amplitude = pars_guess['A_amplitude'].value
  269. thermal_amplitude = pars_guess['B_amplitude'].value
  270. pars = self.make_params(BEC_amplitude=BEC_amplitude,
  271. thermal_amplitude=thermal_amplitude,
  272. BEC_centerx=pars_guess['A_centerx'].value, BEC_centery=pars_guess['A_centery'].value,
  273. # BEC_sigmax=(pars_guess['A_sigmax'].value / 2.355),
  274. deltax = 3 * (pars_guess['B_sigmax'].value * s2) - (pars_guess['A_sigmax'].value / 2.355),
  275. BEC_sigmay=(pars_guess['A_sigmay'].value / 2.355),
  276. thermal_centerx=pars_guess['B_centerx'].value, thermal_centery=pars_guess['B_centery'].value,
  277. thermal_sigmax=(pars_guess['B_sigmax'].value * s2),
  278. thermalAspectRatio=(pars_guess['B_sigmax'].value * s2) / (pars_guess['B_sigmay'].value * s2)
  279. # thermal_sigmay=(pars_guess['B_sigmay'].value * s2)
  280. )
  281. nBEC = pars[f'{self.prefix}BEC_amplitude'] / 2 / np.pi / 5.546 / pars[f'{self.prefix}BEC_sigmay'] / pars[f'{self.prefix}BEC_sigmax']
  282. if (pars[f'{self.prefix}condensate_fraction']>0.95) and (np.max(data) > 1.05 * nBEC):
  283. temp = ((np.max(data) - nBEC) * s2pi * pars[f'{self.prefix}thermal_sigmay'] / pars[f'{self.prefix}thermal_sigmax'])
  284. if temp > pars[f'{self.prefix}BEC_amplitude']:
  285. pars[f'{self.prefix}thermal_amplitude'].set(value=pars[f'{self.prefix}BEC_amplitude'] / 2)
  286. else:
  287. pars[f'{self.prefix}thermal_amplitude'].set(value=temp * 10)
  288. if BEC_amplitude / (thermal_amplitude + BEC_amplitude) > pureBECThreshold:
  289. pars[f'{self.prefix}thermal_amplitude'].set(value=0)
  290. pars[f'{self.prefix}BEC_amplitude'].set(value=(thermal_amplitude + BEC_amplitude))
  291. if BEC_amplitude / (thermal_amplitude + BEC_amplitude) < noBECThThreshold:
  292. pars[f'{self.prefix}BEC_amplitude'].set(value=0)
  293. pars[f'{self.prefix}thermal_amplitude'].set(value=(thermal_amplitude + BEC_amplitude))
  294. pars[f'{self.prefix}BEC_centerx'].set(
  295. min=pars[f'{self.prefix}BEC_centerx'].value - 10 * pars[f'{self.prefix}BEC_sigmax'].value,
  296. max=pars[f'{self.prefix}BEC_centerx'].value + 10 * pars[f'{self.prefix}BEC_sigmax'].value,
  297. )
  298. pars[f'{self.prefix}thermal_centerx'].set(
  299. min=pars[f'{self.prefix}thermal_centerx'].value - 3 * pars[f'{self.prefix}thermal_sigmax'].value,
  300. max=pars[f'{self.prefix}thermal_centerx'].value + 3 * pars[f'{self.prefix}thermal_sigmax'].value,
  301. )
  302. pars[f'{self.prefix}BEC_centery'].set(
  303. min=pars[f'{self.prefix}BEC_centery'].value - 10 * pars[f'{self.prefix}BEC_sigmay'].value,
  304. max=pars[f'{self.prefix}BEC_centery'].value + 10 * pars[f'{self.prefix}BEC_sigmay'].value,
  305. )
  306. pars[f'{self.prefix}thermal_centery'].set(
  307. min=pars[f'{self.prefix}thermal_centery'].value - 3 * pars[f'{self.prefix}thermal_sigmay'].value,
  308. max=pars[f'{self.prefix}thermal_centery'].value + 3 * pars[f'{self.prefix}thermal_sigmay'].value,
  309. )
  310. pars[f'{self.prefix}BEC_sigmay'].set(
  311. max=5 * pars[f'{self.prefix}BEC_sigmay'].value,
  312. )
  313. pars[f'{self.prefix}thermal_sigmax'].set(
  314. max=5 * pars[f'{self.prefix}thermal_sigmax'].value,
  315. )
  316. return update_param_vals(pars, self.prefix, **kwargs)
  317. class NewFitModel(Model):
  318. def __init__(self, func, independent_vars=['x'], prefix='', nan_policy='raise',
  319. **kwargs):
  320. kwargs.update({'prefix': prefix, 'nan_policy': nan_policy,
  321. 'independent_vars': independent_vars})
  322. super().__init__(func, **kwargs)
  323. def guess(self, *args, **kwargs):
  324. return self.make_params()
  325. lmfit_models = {'Constant': ConstantModel,
  326. 'Complex Constant': ComplexConstantModel,
  327. 'Linear': LinearModel,
  328. 'Quadratic': QuadraticModel,
  329. 'Polynomial': PolynomialModel,
  330. 'Gaussian': GaussianModel,
  331. 'Gaussian-2D': Gaussian2dModel,
  332. 'Lorentzian': LorentzianModel,
  333. 'Split-Lorentzian': SplitLorentzianModel,
  334. 'Voigt': VoigtModel,
  335. 'PseudoVoigt': PseudoVoigtModel,
  336. 'Moffat': MoffatModel,
  337. 'Pearson7': Pearson7Model,
  338. 'StudentsT': StudentsTModel,
  339. 'Breit-Wigner': BreitWignerModel,
  340. 'Log-Normal': LognormalModel,
  341. 'Damped Oscillator': DampedOscillatorModel,
  342. 'Damped Harmonic Oscillator': DampedHarmonicOscillatorModel,
  343. 'Exponential Gaussian': ExponentialGaussianModel,
  344. 'Skewed Gaussian': SkewedGaussianModel,
  345. 'Skewed Voigt': SkewedVoigtModel,
  346. 'Thermal Distribution': ThermalDistributionModel,
  347. 'Doniach': DoniachModel,
  348. 'Power Law': PowerLawModel,
  349. 'Exponential': ExponentialModel,
  350. 'Step': StepModel,
  351. 'Rectangle': RectangleModel,
  352. 'Expression': ExpressionModel,
  353. 'Gaussian With Offset':GaussianWithOffsetModel,
  354. 'Lorentzian With Offset':LorentzianWithOffsetModel,
  355. 'Expansion':ExpansionModel,
  356. 'Damping Oscillation Model':DampingOscillationModel,
  357. 'Two Gaussian-2D':TwoGaussian2dModel,
  358. 'Thomas Fermi-2D': ThomasFermi2dModel,
  359. 'Density Profile of BEC-2D': DensityProfileBEC2dModel,
  360. 'Polylog2-2D': polylog2_2d,
  361. }
  362. class FitAnalyser():
  363. """This is a class integrated all the functions to do a fit.
  364. """
  365. def __init__(self, fitModel, fitDim=1, **kwargs) -> None:
  366. """Initialize function
  367. :param fitModel: The fitting model of fit function
  368. :type fitModel: lmfit Model
  369. :param fitDim: The dimension of the fit, defaults to 1
  370. :type fitDim: int, optional
  371. """
  372. if isinstance(fitModel, str):
  373. self.fitModel = lmfit_models[fitModel](**kwargs)
  374. else:
  375. self.fitModel = fitModel
  376. self.fitDim = fitDim
  377. def print_params_set_template(self, params=None):
  378. """Print a template to manually set the initial parameters of the fit
  379. :param params: An object of Parameters class to print, defaults to None
  380. :type params: lmfit Paraemters, optional
  381. """
  382. if params is None:
  383. params = self.fitModel.make_params()
  384. for key in params:
  385. res = "params.add("
  386. res += "name=\"" + key + "\", "
  387. if not params[key].expr is None:
  388. res += "expr=\"" + params[key].expr +"\")"
  389. else:
  390. res += "value=" + f'{params[key].value:3g}' + ", "
  391. if str(params[key].max)=="inf":
  392. res += "max=np.inf, "
  393. else:
  394. res += "max=" + f'{params[key].max:3g}' + ", "
  395. if str(params[key].min)=="-inf":
  396. res += "min=-np.inf, "
  397. else:
  398. res += "min=" + f'{params[key].min:3g}' + ", "
  399. res += "vary=" + str(params[key].vary) + ")"
  400. print(res)
  401. def _guess_1D(self, data, x, **kwargs):
  402. """Call the guess function of the 1D fit model to guess the initial value.
  403. :param data: The data to fit
  404. :type data: 1D numpy array
  405. :param x: The data of x axis
  406. :type x: 1D numpy array
  407. :return: The guessed initial parameters for the fit
  408. :rtype: lmfit Parameters
  409. """
  410. return self.fitModel.guess(data=data, x=x, **kwargs)
  411. def _guess_2D(self, data, x, y, **kwargs):
  412. """Call the guess function of the 2D fit model to guess the initial value.
  413. :param data: The flattened data to fit
  414. :type data: 1D numpy array
  415. :param x: The flattened data of x axis
  416. :type x: 1D numpy array
  417. :param y: The flattened data of y axis
  418. :type y: 1D numpy array
  419. :return: The guessed initial parameters for the fit
  420. :rtype: lmfit Parameters
  421. """
  422. data = data.flatten(order='F')
  423. return self.fitModel.guess(data=data, x=x, y=y, **kwargs)
  424. def guess(self, dataArray, x=None, y=None, guess_kwargs={}, input_core_dims=None, dask='parallelized', vectorize=True, keep_attrs=True, daskKwargs=None, **kwargs):
  425. """Call the guess function of the fit model to guess the initial value.
  426. :param dataArray: The data for the fit
  427. :type dataArray: xarray DataArray
  428. :param x: The name of x axis in data or the data of x axis, defaults to None
  429. :type x: str or numpy array, optional
  430. :param y: The name of y axis in data or the data of y axis, defaults to None
  431. :type y: str or numpy array, optional
  432. :param guess_kwargs: the keyworded arguments to send to the guess function, defaults to {}
  433. :type guess_kwargs: dict, optional
  434. :param input_core_dims: over write of the same argument in xarray.apply_ufunc, defaults to None
  435. :type input_core_dims: list or array like, optional
  436. :param dask: over write of the same argument in xarray.apply_ufunc,, defaults to 'parallelized'
  437. :type dask: str, optional
  438. :param vectorize: over write of the same argument in xarray.apply_ufunc, defaults to True
  439. :type vectorize: bool, optional
  440. :param keep_attrs: over write of the same argument in xarray.apply_ufunc, defaults to True
  441. :type keep_attrs: bool, optional
  442. :param daskKwargs: over write of the same argument in xarray.apply_ufunc, defaults to None
  443. :type daskKwargs: dict, optional
  444. :return: The guessed initial parameters for the fit
  445. :rtype: xarray DataArray
  446. """
  447. kwargs.update(
  448. {
  449. "dask": dask,
  450. "vectorize": vectorize,
  451. "input_core_dims": input_core_dims,
  452. 'keep_attrs': keep_attrs,
  453. }
  454. )
  455. if not daskKwargs is None:
  456. kwargs.update({"dask_gufunc_kwargs": daskKwargs})
  457. if input_core_dims is None:
  458. kwargs.update(
  459. {
  460. "input_core_dims": [['x']],
  461. }
  462. )
  463. if x is None:
  464. if 'x' in dataArray.dims:
  465. x = dataArray['x'].to_numpy()
  466. else:
  467. if isinstance(x, str):
  468. if input_core_dims is None:
  469. kwargs.update(
  470. {
  471. "input_core_dims": [[x]],
  472. }
  473. )
  474. x = dataArray[x].to_numpy()
  475. if self.fitDim == 1:
  476. guess_kwargs.update(
  477. {
  478. 'x':x,
  479. }
  480. )
  481. return xr.apply_ufunc(self._guess_1D, dataArray, kwargs=guess_kwargs,
  482. output_dtypes=[type(self.fitModel.make_params())],
  483. **kwargs
  484. )
  485. if self.fitDim == 2:
  486. if y is None:
  487. if 'y' in dataArray.dims:
  488. y = dataArray['y'].to_numpy()
  489. if input_core_dims is None:
  490. kwargs.update(
  491. {
  492. "input_core_dims": [['x', 'y']],
  493. }
  494. )
  495. else:
  496. if isinstance(y, str):
  497. kwargs["input_core_dims"][0] = np.append(kwargs["input_core_dims"][0], y)
  498. y = dataArray[y].to_numpy()
  499. elif input_core_dims is None:
  500. kwargs.update(
  501. {
  502. "input_core_dims": [['x', 'y']],
  503. }
  504. )
  505. _x, _y = np.meshgrid(x, y)
  506. _x = _x.flatten()
  507. _y = _y.flatten()
  508. # dataArray = dataArray.stack(_z=(kwargs["input_core_dims"][0][0], kwargs["input_core_dims"][0][1]))
  509. # kwargs["input_core_dims"][0] = ['_z']
  510. guess_kwargs.update(
  511. {
  512. 'x':_x,
  513. 'y':_y,
  514. }
  515. )
  516. return xr.apply_ufunc(self._guess_2D, dataArray, kwargs=guess_kwargs,
  517. output_dtypes=[type(self.fitModel.make_params())],
  518. **kwargs
  519. )
  520. def _fit_1D(self, data, params, x):
  521. """Call the fit function of the 1D fit model to do the fit.
  522. :param data: The data to fit
  523. :type data: 1D numpy array
  524. :param params: The initial paramters of the fit
  525. :type params: lmfit Parameters
  526. :param x: The data of x axis
  527. :type x: 1D numpy array
  528. :return: The result of the fit
  529. :rtype: lmfit FitResult
  530. """
  531. return self.fitModel.fit(data=data, x=x, params=params, nan_policy='omit')
  532. def _fit_2D(self, data, params, x, y):
  533. """Call the fit function of the 2D fit model to do the fit.
  534. :param data: The flattened data to fit
  535. :type data: 1D numpy array
  536. :param params: The flattened initial paramters of the fit
  537. :type params: lmfit Parameters
  538. :param x: The flattened data of x axis
  539. :type x: 1D numpy array
  540. :param y: The flattened data of y axis
  541. :type y: 1D numpy array
  542. :return: The result of the fit
  543. :rtype: lmfit FitResult
  544. """
  545. data = data.flatten(order='F')
  546. return self.fitModel.fit(data=data, x=x, y=y, params=params, nan_policy='omit')
  547. def fit(self, dataArray, paramsArray, x=None, y=None, input_core_dims=None, dask='parallelized', vectorize=True, keep_attrs=True, daskKwargs=None, **kwargs):
  548. """Call the fit function of the fit model to do the fit.
  549. :param dataArray: The data for the fit
  550. :type dataArray: xarray DataArray
  551. :param paramsArray: The flattened initial paramters of the fit
  552. :type paramsArray: xarray DataArray or lmfit Parameters
  553. :param x: The name of x axis in data or the data of x axis, defaults to None
  554. :type x: str or numpy array, optional
  555. :param y: The name of y axis in data or the data of y axis, defaults to None
  556. :type y: str or numpy array, optional
  557. :param input_core_dims: over write of the same argument in xarray.apply_ufunc, defaults to None, defaults to None
  558. :type input_core_dims: list or array like, optional
  559. :param dask: over write of the same argument in xarray.apply_ufunc, defaults to None, defaults to 'parallelized'
  560. :type dask: str, optional
  561. :param vectorize: over write of the same argument in xarray.apply_ufunc, defaults to None, defaults to True
  562. :type vectorize: bool, optional
  563. :param keep_attrs: over write of the same argument in xarray.apply_ufunc, defaults to None, defaults to True
  564. :type keep_attrs: bool, optional
  565. :param daskKwargs: over write of the same argument in xarray.apply_ufunc, defaults to None, defaults to None
  566. :type daskKwargs: dict, optional
  567. :return: The fit result
  568. :rtype: xarray DataArray
  569. """
  570. kwargs.update(
  571. {
  572. "dask": dask,
  573. "vectorize": vectorize,
  574. "input_core_dims": input_core_dims,
  575. 'keep_attrs': keep_attrs,
  576. }
  577. )
  578. if not daskKwargs is None:
  579. kwargs.update({"dask_gufunc_kwargs": daskKwargs})
  580. if isinstance(paramsArray, type(self.fitModel.make_params())):
  581. if input_core_dims is None:
  582. kwargs.update(
  583. {
  584. "input_core_dims": [['x']],
  585. }
  586. )
  587. if x is None:
  588. if 'x' in dataArray.dims:
  589. x = dataArray['x'].to_numpy()
  590. else:
  591. if isinstance(x, str):
  592. if input_core_dims is None:
  593. kwargs.update(
  594. {
  595. "input_core_dims": [[x]],
  596. }
  597. )
  598. x = dataArray[x].to_numpy()
  599. if self.fitDim == 1:
  600. res = xr.apply_ufunc(self._fit_1D, dataArray, kwargs={'params':paramsArray,'x':x},
  601. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  602. **kwargs)
  603. if keep_attrs:
  604. res.attrs = copy.copy(dataArray.attrs)
  605. return res
  606. if self.fitDim == 2:
  607. if y is None:
  608. if 'y' in dataArray.dims:
  609. y = dataArray['y'].to_numpy()
  610. if input_core_dims is None:
  611. kwargs.update(
  612. {
  613. "input_core_dims": [['x', 'y']],
  614. }
  615. )
  616. else:
  617. if isinstance(y, str):
  618. kwargs["input_core_dims"][0] = np.append(kwargs["input_core_dims"][0], y)
  619. y = dataArray[y].to_numpy()
  620. elif input_core_dims is None:
  621. kwargs.update(
  622. {
  623. "input_core_dims": [['x', 'y']],
  624. }
  625. )
  626. _x, _y = np.meshgrid(x, y)
  627. _x = _x.flatten()
  628. _y = _y.flatten()
  629. # dataArray = dataArray.stack(_z=(kwargs["input_core_dims"][0][0], kwargs["input_core_dims"][0][1]))
  630. # kwargs["input_core_dims"][0] = ['_z']
  631. res = xr.apply_ufunc(self._fit_2D, dataArray, kwargs={'params':paramsArray,'x':_x, 'y':_y},
  632. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  633. **kwargs)
  634. if keep_attrs:
  635. res.attrs = copy.copy(dataArray.attrs)
  636. return res
  637. else:
  638. if input_core_dims is None:
  639. kwargs.update(
  640. {
  641. "input_core_dims": [['x'], []],
  642. }
  643. )
  644. if x is None:
  645. if 'x' in dataArray.dims:
  646. x = dataArray['x'].to_numpy()
  647. else:
  648. if isinstance(x, str):
  649. if input_core_dims is None:
  650. kwargs.update(
  651. {
  652. "input_core_dims": [[x], []],
  653. }
  654. )
  655. x = dataArray[x].to_numpy()
  656. if self.fitDim == 1:
  657. return xr.apply_ufunc(self._fit_1D, dataArray, paramsArray, kwargs={'x':x},
  658. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  659. **kwargs)
  660. if self.fitDim == 2:
  661. if input_core_dims is None:
  662. kwargs.update(
  663. {
  664. "input_core_dims": [['x', 'y'], []],
  665. }
  666. )
  667. if y is None:
  668. if 'y' in dataArray.dims:
  669. y = dataArray['y'].to_numpy()
  670. else:
  671. if isinstance(y, str):
  672. y = dataArray[y].to_numpy()
  673. kwargs["input_core_dims"][0] = np.append(kwargs["input_core_dims"][0], y)
  674. _x, _y = np.meshgrid(x, y)
  675. _x = _x.flatten()
  676. _y = _y.flatten()
  677. # dataArray = dataArray.stack(_z=(kwargs["input_core_dims"][0][0], kwargs["input_core_dims"][0][1]))
  678. # kwargs["input_core_dims"][0] = ['_z']
  679. return xr.apply_ufunc(self._fit_2D, dataArray, paramsArray, kwargs={'x':_x, 'y':_y},
  680. output_dtypes=[type(lmfit.model.ModelResult(self.fitModel, self.fitModel.make_params()))],
  681. **kwargs)
  682. def _eval_1D(self, fitResult, x):
  683. """Call the eval function of the 1D fit model to calculate the curve.
  684. :param fitResult: The result of fit
  685. :type fitResult: lmfit FitResult
  686. :param x: The data of x axis
  687. :type x: 1D numpy array
  688. :return: The curve based on the fit result
  689. :rtype: 1D numpy array
  690. """
  691. return self.fitModel.eval(x=x, **fitResult.best_values)
  692. def _eval_2D(self, fitResult, x, y, shape):
  693. """Call the eval function of the 2D fit model to calculate the curve.
  694. :param fitResult: The result of fit
  695. :type fitResult: lmfit FitResult
  696. :param x: The data of x axis
  697. :type x: 1D numpy array
  698. :param y: The flattened data of y axis
  699. :type y: 1D numpy array
  700. :param shape: The desired shape for output
  701. :type shape: list, tuple or array like
  702. :return: The curve based on the fit result
  703. :rtype: 2D numpy array
  704. """
  705. res = self.fitModel.eval(x=x, y=y, **fitResult.best_values)
  706. return res.reshape(shape, order='F')
  707. def eval(self, fitResultArray, x=None, y=None, output_core_dims=None, prefix="", dask='parallelized', vectorize=True, daskKwargs=None, **kwargs):
  708. """Call the eval function of the fit model to calculate the curve.
  709. :param fitResultArray: The result of fit
  710. :type fitResultArray: xarray DataArray
  711. :param x: The name of x axis in data or the data of x axis, defaults to None
  712. :type x: str or numpy array, optional
  713. :param y: The name of y axis in data or the data of y axis, defaults to None
  714. :type y: str or numpy array, optional
  715. :param output_core_dims: over write of the same argument in xarray.apply_ufunc, defaults to None
  716. :type output_core_dims: list, optional
  717. :param prefix: prefix str of the fit model, defaults to ""
  718. :type prefix: str, optional
  719. :param dask: over write of the same argument in xarray.apply_ufunc, defaults to 'parallelized'
  720. :type dask: str, optional
  721. :param vectorize: over write of the same argument in xarray.apply_ufunc, defaults to True
  722. :type vectorize: bool, optional
  723. :param daskKwargs: over write of the same argument in xarray.apply_ufunc, defaults to None
  724. :type daskKwargs: dict, optional
  725. :return: The curve based on the fit result
  726. :rtype: xarray
  727. """
  728. kwargs.update(
  729. {
  730. "dask": dask,
  731. "vectorize": vectorize,
  732. "output_core_dims": output_core_dims,
  733. }
  734. )
  735. if daskKwargs is None:
  736. daskKwargs = {}
  737. if self.fitDim == 1:
  738. if output_core_dims is None:
  739. kwargs.update(
  740. {
  741. "output_core_dims": prefix+'x',
  742. }
  743. )
  744. output_core_dims = [prefix+'x']
  745. daskKwargs.update(
  746. {
  747. 'output_sizes': {
  748. output_core_dims[0]: np.size(x),
  749. },
  750. 'meta': np.ndarray((0,0), dtype=float)
  751. }
  752. )
  753. kwargs.update(
  754. {
  755. "dask_gufunc_kwargs": daskKwargs,
  756. }
  757. )
  758. res = xr.apply_ufunc(self._eval_1D, fitResultArray, kwargs={"x":x}, **kwargs)
  759. return res.assign_coords({prefix+'x':np.array(x)})
  760. if self.fitDim == 2:
  761. if output_core_dims is None:
  762. kwargs.update(
  763. {
  764. "output_core_dims": [[prefix+'x', prefix+'y']],
  765. }
  766. )
  767. output_core_dims = [prefix+'x', prefix+'y']
  768. daskKwargs.update(
  769. {
  770. 'output_sizes': {
  771. output_core_dims[0]: np.size(x),
  772. output_core_dims[1]: np.size(y),
  773. },
  774. 'meta': np.ndarray((0,0), dtype=float)
  775. },
  776. )
  777. kwargs.update(
  778. {
  779. "dask_gufunc_kwargs": daskKwargs,
  780. }
  781. )
  782. _x, _y = np.meshgrid(x, y)
  783. _x = _x.flatten()
  784. _y = _y.flatten()
  785. res = xr.apply_ufunc(self._eval_2D, fitResultArray, kwargs={"x":_x, "y":_y, "shape":(len(x), len(y))}, **kwargs)
  786. return res.assign_coords({prefix+'x':np.array(x), prefix+'y':np.array(y)})
  787. def _get_fit_value_single(self, fitResult, key):
  788. """get value of one single parameter from fit result
  789. :param fitResult: The result of the fit
  790. :type fitResult: lmfit FitResult
  791. :param key: The name of the parameter
  792. :type key: str
  793. :return: The vaule of the parameter
  794. :rtype: float
  795. """
  796. return fitResult.params[key].value
  797. def _get_fit_value(self, fitResult, params):
  798. """get value of parameters from fit result
  799. :param fitResult: The result of the fit
  800. :type fitResult: lmfit FitResult
  801. :param params: A list of the name of paramerters to read
  802. :type params: list or array like
  803. :return: The vaule of the parameter
  804. :rtype: 1D numpy array
  805. """
  806. func = np.vectorize(self._get_fit_value_single)
  807. res = tuple(
  808. func(fitResult, key)
  809. for key in params
  810. )
  811. return res
  812. def get_fit_value(self, fitResult, dask='parallelized', **kwargs):
  813. """get value of parameters from fit result
  814. :param fitResult: The result of the fit
  815. :type fitResult: lmfit FitResult
  816. :param dask: over write of the same argument in xarray.apply_ufunc, defaults to 'parallelized'
  817. :type dask: str, optional
  818. :return: The vaule of the parameter
  819. :rtype: xarray DataArray
  820. """
  821. firstIndex = {
  822. key: fitResult[key][0]
  823. for key in fitResult.dims
  824. }
  825. firstFitResult = fitResult.sel(firstIndex).item()
  826. params = list(firstFitResult.params.keys())
  827. output_core_dims=[ [] for _ in range(len(params))]
  828. kwargs.update(
  829. {
  830. "dask": dask,
  831. "output_core_dims": output_core_dims,
  832. }
  833. )
  834. value = xr.apply_ufunc(self._get_fit_value, fitResult, kwargs=dict(params=params), **kwargs)
  835. value = xr.Dataset(
  836. data_vars={
  837. params[i]: value[i]
  838. for i in range(len(params))
  839. },
  840. attrs=fitResult.attrs
  841. )
  842. return value
  843. def _get_fit_std_single(self, fitResult, key):
  844. """get standard deviation of one single parameter from fit result
  845. :param fitResult: The result of the fit
  846. :type fitResult: lmfit FitResult
  847. :param key: The name of the parameter
  848. :type key: str
  849. :return: The vaule of the parameter
  850. :rtype: float
  851. """
  852. return fitResult.params[key].stderr
  853. def _get_fit_std(self, fitResult, params):
  854. """get standard deviation of parameters from fit result
  855. :param fitResult: The result of the fit
  856. :type fitResult: lmfit FitResult
  857. :param params: A list of the name of paramerters to read
  858. :type params: list or array like
  859. :return: The vaule of the parameter
  860. :rtype: 1D numpy array
  861. """
  862. func = np.vectorize(self._get_fit_std_single)
  863. res = tuple(
  864. func(fitResult, key)
  865. for key in params
  866. )
  867. return res
  868. def get_fit_std(self, fitResult, dask='parallelized', **kwargs):
  869. """get standard deviation of parameters from fit result
  870. :param fitResult: The result of the fit
  871. :type fitResult: lmfit FitResult
  872. :param dask: over write of the same argument in xarray.apply_ufunc, defaults to 'parallelized'
  873. :type dask: str, optional
  874. :return: The vaule of the parameter
  875. :rtype: xarray DataArray
  876. """
  877. firstIndex = {
  878. key: fitResult[key][0]
  879. for key in fitResult.dims
  880. }
  881. firstFitResult = fitResult.sel(firstIndex).item()
  882. params = list(firstFitResult.params.keys())
  883. output_core_dims=[ [] for _ in range(len(params))]
  884. kwargs.update(
  885. {
  886. "dask": dask,
  887. "output_core_dims": output_core_dims,
  888. }
  889. )
  890. value = xr.apply_ufunc(self._get_fit_std, fitResult, kwargs=dict(params=params), **kwargs)
  891. value = xr.Dataset(
  892. data_vars={
  893. params[i]: value[i]
  894. for i in range(len(params))
  895. },
  896. attrs=fitResult.attrs
  897. )
  898. return value
  899. def _get_fit_full_result_single(self, fitResult, key):
  900. """get the full result of one single parameter from fit result
  901. :param fitResult: The result of the fit
  902. :type fitResult: lmfit FitResult
  903. :param key: The name of the parameter
  904. :type key: str
  905. :return: The vaule of the parameter
  906. :rtype: float
  907. """
  908. if not fitResult.params[key].value is None:
  909. value = fitResult.params[key].value
  910. else:
  911. value = np.nan
  912. if not fitResult.params[key].stderr is None:
  913. std = fitResult.params[key].stderr
  914. else:
  915. std = np.nan
  916. return ufloat(value, std)
  917. def _get_fit_full_result(self, fitResult, params):
  918. """get the full result of parameters from fit result
  919. :param fitResult: The result of the fit
  920. :type fitResult: lmfit FitResult
  921. :param params: A list of the name of paramerters to read
  922. :type params: list or array like
  923. :return: The vaule of the parameter
  924. :rtype: 1D numpy array
  925. """
  926. func = np.vectorize(self._get_fit_full_result_single)
  927. res = tuple(
  928. func(fitResult, key)
  929. for key in params
  930. )
  931. return res
  932. def get_fit_full_result(self, fitResult, dask='parallelized', **kwargs):
  933. """get the full result of parameters from fit result
  934. :param fitResult: The result of the fit
  935. :type fitResult: lmfit FitResult
  936. :param dask: over write of the same argument in xarray.apply_ufunc, defaults to 'parallelized'
  937. :type dask: str, optional
  938. :return: The vaule of the parameter
  939. :rtype: xarray DataArray
  940. """
  941. firstIndex = {
  942. key: fitResult[key][0]
  943. for key in fitResult.dims
  944. }
  945. firstFitResult = fitResult.sel(firstIndex).item()
  946. params = list(firstFitResult.params.keys())
  947. output_core_dims=[ [] for _ in range(len(params))]
  948. kwargs.update(
  949. {
  950. "dask": dask,
  951. "output_core_dims": output_core_dims,
  952. }
  953. )
  954. value = xr.apply_ufunc(self._get_fit_full_result, fitResult, kwargs=dict(params=params), **kwargs)
  955. value = xr.Dataset(
  956. data_vars={
  957. params[i]: value[i]
  958. for i in range(len(params))
  959. },
  960. attrs=fitResult.attrs
  961. )
  962. return value