pyerrors.fits
1import gc 2from collections.abc import Sequence 3import warnings 4import numpy as np 5import autograd.numpy as anp 6import scipy.optimize 7import scipy.stats 8import matplotlib.pyplot as plt 9from matplotlib import gridspec 10from scipy.odr import ODR, Model, RealData 11import iminuit 12from autograd import jacobian 13from autograd import elementwise_grad as egrad 14from .obs import Obs, derived_observable, covariance, cov_Obs 15 16 17class Fit_result(Sequence): 18 """Represents fit results. 19 20 Attributes 21 ---------- 22 fit_parameters : list 23 results for the individual fit parameters, 24 also accessible via indices. 25 """ 26 27 def __init__(self): 28 self.fit_parameters = None 29 30 def __getitem__(self, idx): 31 return self.fit_parameters[idx] 32 33 def __len__(self): 34 return len(self.fit_parameters) 35 36 def gamma_method(self, **kwargs): 37 """Apply the gamma method to all fit parameters""" 38 [o.gamma_method(**kwargs) for o in self.fit_parameters] 39 40 def __str__(self): 41 my_str = 'Goodness of fit:\n' 42 if hasattr(self, 'chisquare_by_dof'): 43 my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' 44 elif hasattr(self, 'residual_variance'): 45 my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n' 46 if hasattr(self, 'chisquare_by_expected_chisquare'): 47 my_str += '\u03C7\u00b2/\u03C7\u00b2exp = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n' 48 if hasattr(self, 'p_value'): 49 my_str += 'p-value = ' + f'{self.p_value:2.4f}' + '\n' 50 my_str += 'Fit parameters:\n' 51 for i_par, par in enumerate(self.fit_parameters): 52 my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' 53 return my_str 54 55 def __repr__(self): 56 m = max(map(len, list(self.__dict__.keys()))) + 1 57 return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())]) 58 59 60def least_squares(x, y, func, priors=None, silent=False, **kwargs): 61 r'''Performs a non-linear fit to y = func(x). 62 63 Parameters 64 ---------- 65 x : list 66 list of floats. 67 y : list 68 list of Obs. 69 func : object 70 fit function, has to be of the form 71 72 ```python 73 import autograd.numpy as anp 74 75 def func(a, x): 76 return a[0] + a[1] * x + a[2] * anp.sinh(x) 77 ``` 78 79 For multiple x values func can be of the form 80 81 ```python 82 def func(a, x): 83 (x1, x2) = x 84 return a[0] * x1 ** 2 + a[1] * x2 85 ``` 86 87 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 88 will not work. 89 priors : list, optional 90 priors has to be a list with an entry for every parameter in the fit. The entries can either be 91 Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 92 0.548(23), 500(40) or 0.5(0.4) 93 silent : bool, optional 94 If true all output to the console is omitted (default False). 95 initial_guess : list 96 can provide an initial guess for the input parameters. Relevant for 97 non-linear fits with many parameters. In case of correlated fits the guess is used to perform 98 an uncorrelated fit which then serves as guess for the correlated fit. 99 method : str, optional 100 can be used to choose an alternative method for the minimization of chisquare. 101 The possible methods are the ones which can be used for scipy.optimize.minimize and 102 migrad of iminuit. If no method is specified, Levenberg-Marquard is used. 103 Reliable alternatives are migrad, Powell and Nelder-Mead. 104 correlated_fit : bool 105 If True, use the full inverse covariance matrix in the definition of the chisquare cost function. 106 For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. 107 In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix). 108 This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning). 109 At the moment this option only works for `prior==None` and when no `method` is given. 110 expected_chisquare : bool 111 If True estimates the expected chisquare which is 112 corrected by effects caused by correlated input data (default False). 113 resplot : bool 114 If True, a plot which displays fit, data and residuals is generated (default False). 115 qqplot : bool 116 If True, a quantile-quantile plot of the fit result is generated (default False). 117 ''' 118 if priors is not None: 119 return _prior_fit(x, y, func, priors, silent=silent, **kwargs) 120 else: 121 return _standard_fit(x, y, func, silent=silent, **kwargs) 122 123 124def total_least_squares(x, y, func, silent=False, **kwargs): 125 r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. 126 127 Parameters 128 ---------- 129 x : list 130 list of Obs, or a tuple of lists of Obs 131 y : list 132 list of Obs. The dvalues of the Obs are used as x- and yerror for the fit. 133 func : object 134 func has to be of the form 135 136 ```python 137 import autograd.numpy as anp 138 139 def func(a, x): 140 return a[0] + a[1] * x + a[2] * anp.sinh(x) 141 ``` 142 143 For multiple x values func can be of the form 144 145 ```python 146 def func(a, x): 147 (x1, x2) = x 148 return a[0] * x1 ** 2 + a[1] * x2 149 ``` 150 151 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 152 will not work. 153 silent : bool, optional 154 If true all output to the console is omitted (default False). 155 initial_guess : list 156 can provide an initial guess for the input parameters. Relevant for non-linear 157 fits with many parameters. 158 expected_chisquare : bool 159 If true prints the expected chisquare which is 160 corrected by effects caused by correlated input data. 161 This can take a while as the full correlation matrix 162 has to be calculated (default False). 163 164 Notes 165 ----- 166 Based on the orthogonal distance regression module of scipy 167 ''' 168 169 output = Fit_result() 170 171 output.fit_function = func 172 173 x = np.array(x) 174 175 x_shape = x.shape 176 177 if not callable(func): 178 raise TypeError('func has to be a function.') 179 180 for i in range(42): 181 try: 182 func(np.arange(i), x.T[0]) 183 except Exception: 184 continue 185 else: 186 break 187 else: 188 raise RuntimeError("Fit function is not valid.") 189 190 n_parms = i 191 if not silent: 192 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 193 194 x_f = np.vectorize(lambda o: o.value)(x) 195 dx_f = np.vectorize(lambda o: o.dvalue)(x) 196 y_f = np.array([o.value for o in y]) 197 dy_f = np.array([o.dvalue for o in y]) 198 199 if np.any(np.asarray(dx_f) <= 0.0): 200 raise Exception('No x errors available, run the gamma method first.') 201 202 if np.any(np.asarray(dy_f) <= 0.0): 203 raise Exception('No y errors available, run the gamma method first.') 204 205 if 'initial_guess' in kwargs: 206 x0 = kwargs.get('initial_guess') 207 if len(x0) != n_parms: 208 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 209 else: 210 x0 = [1] * n_parms 211 212 data = RealData(x_f, y_f, sx=dx_f, sy=dy_f) 213 model = Model(func) 214 odr = ODR(data, model, x0, partol=np.finfo(np.float64).eps) 215 odr.set_job(fit_type=0, deriv=1) 216 out = odr.run() 217 218 output.residual_variance = out.res_var 219 220 output.method = 'ODR' 221 222 output.message = out.stopreason 223 224 output.xplus = out.xplus 225 226 if not silent: 227 print('Method: ODR') 228 print(*out.stopreason) 229 print('Residual variance:', output.residual_variance) 230 231 if out.info > 3: 232 raise Exception('The minimization procedure did not converge.') 233 234 m = x_f.size 235 236 def odr_chisquare(p): 237 model = func(p[:n_parms], p[n_parms:].reshape(x_shape)) 238 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2) 239 return chisq 240 241 if kwargs.get('expected_chisquare') is True: 242 W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel())))) 243 244 if kwargs.get('covariance') is not None: 245 cov = kwargs.get('covariance') 246 else: 247 cov = covariance(np.concatenate((y, x.ravel()))) 248 249 number_of_x_parameters = int(m / x_f.shape[-1]) 250 251 old_jac = jacobian(func)(out.beta, out.xplus) 252 fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0))) 253 fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplus, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0]))) 254 new_jac = np.concatenate((fused_row1, fused_row2), axis=1) 255 256 A = W @ new_jac 257 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 258 expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W) 259 if expected_chisquare <= 0.0: 260 warnings.warn("Negative expected_chisquare.", RuntimeWarning) 261 expected_chisquare = np.abs(expected_chisquare) 262 output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) / expected_chisquare 263 if not silent: 264 print('chisquare/expected_chisquare:', 265 output.chisquare_by_expected_chisquare) 266 267 fitp = out.beta 268 try: 269 hess = jacobian(jacobian(odr_chisquare))(np.concatenate((fitp, out.xplus.ravel()))) 270 except TypeError: 271 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 272 273 def odr_chisquare_compact_x(d): 274 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 275 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 276 return chisq 277 278 jac_jac_x = jacobian(jacobian(odr_chisquare_compact_x))(np.concatenate((fitp, out.xplus.ravel(), x_f.ravel()))) 279 280 # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv 281 try: 282 deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:]) 283 except np.linalg.LinAlgError: 284 raise Exception("Cannot invert hessian matrix.") 285 286 def odr_chisquare_compact_y(d): 287 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 288 chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 289 return chisq 290 291 jac_jac_y = jacobian(jacobian(odr_chisquare_compact_y))(np.concatenate((fitp, out.xplus.ravel(), y_f))) 292 293 # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv 294 try: 295 deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:]) 296 except np.linalg.LinAlgError: 297 raise Exception("Cannot invert hessian matrix.") 298 299 result = [] 300 for i in range(n_parms): 301 result.append(derived_observable(lambda my_var, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i]))) 302 303 output.fit_parameters = result 304 305 output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) 306 output.dof = x.shape[-1] - n_parms 307 output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof) 308 309 return output 310 311 312def _prior_fit(x, y, func, priors, silent=False, **kwargs): 313 output = Fit_result() 314 315 output.fit_function = func 316 317 x = np.asarray(x) 318 319 if not callable(func): 320 raise TypeError('func has to be a function.') 321 322 for i in range(100): 323 try: 324 func(np.arange(i), 0) 325 except Exception: 326 continue 327 else: 328 break 329 else: 330 raise RuntimeError("Fit function is not valid.") 331 332 n_parms = i 333 334 if n_parms != len(priors): 335 raise Exception('Priors does not have the correct length.') 336 337 def extract_val_and_dval(string): 338 split_string = string.split('(') 339 if '.' in split_string[0] and '.' not in split_string[1][:-1]: 340 factor = 10 ** -len(split_string[0].partition('.')[2]) 341 else: 342 factor = 1 343 return float(split_string[0]), float(split_string[1][:-1]) * factor 344 345 loc_priors = [] 346 for i_n, i_prior in enumerate(priors): 347 if isinstance(i_prior, Obs): 348 loc_priors.append(i_prior) 349 else: 350 loc_val, loc_dval = extract_val_and_dval(i_prior) 351 loc_priors.append(cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}")) 352 353 output.priors = loc_priors 354 355 if not silent: 356 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 357 358 y_f = [o.value for o in y] 359 dy_f = [o.dvalue for o in y] 360 361 if np.any(np.asarray(dy_f) <= 0.0): 362 raise Exception('No y errors available, run the gamma method first.') 363 364 p_f = [o.value for o in loc_priors] 365 dp_f = [o.dvalue for o in loc_priors] 366 367 if np.any(np.asarray(dp_f) <= 0.0): 368 raise Exception('No prior errors available, run the gamma method first.') 369 370 if 'initial_guess' in kwargs: 371 x0 = kwargs.get('initial_guess') 372 if len(x0) != n_parms: 373 raise Exception('Initial guess does not have the correct length.') 374 else: 375 x0 = p_f 376 377 def chisqfunc(p): 378 model = func(p, x) 379 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((p_f - p) / dp_f) ** 2) 380 return chisq 381 382 if not silent: 383 print('Method: migrad') 384 385 m = iminuit.Minuit(chisqfunc, x0) 386 m.errordef = 1 387 m.print_level = 0 388 if 'tol' in kwargs: 389 m.tol = kwargs.get('tol') 390 else: 391 m.tol = 1e-4 392 m.migrad() 393 params = np.asarray(m.values) 394 395 output.chisquare_by_dof = m.fval / len(x) 396 397 output.method = 'migrad' 398 399 if not silent: 400 print('chisquare/d.o.f.:', output.chisquare_by_dof) 401 402 if not m.fmin.is_valid: 403 raise Exception('The minimization procedure did not converge.') 404 405 hess_inv = np.linalg.pinv(jacobian(jacobian(chisqfunc))(params)) 406 407 def chisqfunc_compact(d): 408 model = func(d[:n_parms], x) 409 chisq = anp.sum(((d[n_parms: n_parms + len(x)] - model) / dy_f) ** 2) + anp.sum(((d[n_parms + len(x):] - d[:n_parms]) / dp_f) ** 2) 410 return chisq 411 412 jac_jac = jacobian(jacobian(chisqfunc_compact))(np.concatenate((params, y_f, p_f))) 413 414 deriv = -hess_inv @ jac_jac[:n_parms, n_parms:] 415 416 result = [] 417 for i in range(n_parms): 418 result.append(derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (y[0].value + np.finfo(np.float64).eps) * params[i], list(y) + list(loc_priors), man_grad=list(deriv[i]))) 419 420 output.fit_parameters = result 421 output.chisquare = chisqfunc(np.asarray(params)) 422 423 if kwargs.get('resplot') is True: 424 residual_plot(x, y, func, result) 425 426 if kwargs.get('qqplot') is True: 427 qqplot(x, y, func, result) 428 429 return output 430 431 432def _standard_fit(x, y, func, silent=False, **kwargs): 433 434 output = Fit_result() 435 436 output.fit_function = func 437 438 x = np.asarray(x) 439 440 if x.shape[-1] != len(y): 441 raise Exception('x and y input have to have the same length') 442 443 if len(x.shape) > 2: 444 raise Exception('Unknown format for x values') 445 446 if not callable(func): 447 raise TypeError('func has to be a function.') 448 449 for i in range(42): 450 try: 451 func(np.arange(i), x.T[0]) 452 except Exception: 453 continue 454 else: 455 break 456 else: 457 raise RuntimeError("Fit function is not valid.") 458 459 n_parms = i 460 461 if not silent: 462 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 463 464 y_f = [o.value for o in y] 465 dy_f = [o.dvalue for o in y] 466 467 if np.any(np.asarray(dy_f) <= 0.0): 468 raise Exception('No y errors available, run the gamma method first.') 469 470 if 'initial_guess' in kwargs: 471 x0 = kwargs.get('initial_guess') 472 if len(x0) != n_parms: 473 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 474 else: 475 x0 = [0.1] * n_parms 476 477 if kwargs.get('correlated_fit') is True: 478 corr = covariance(y, correlation=True, **kwargs) 479 covdiag = np.diag(1 / np.asarray(dy_f)) 480 condn = np.linalg.cond(corr) 481 if condn > 0.1 / np.finfo(float).eps: 482 raise Exception(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})") 483 if condn > 1 / np.sqrt(np.finfo(float).eps): 484 warnings.warn("Correlation matrix may be ill-conditioned, condition number: {%1.2e}" % (condn), RuntimeWarning) 485 chol = np.linalg.cholesky(corr) 486 chol_inv = scipy.linalg.solve_triangular(chol, covdiag, lower=True) 487 488 def chisqfunc_corr(p): 489 model = func(p, x) 490 chisq = anp.sum(anp.dot(chol_inv, (y_f - model)) ** 2) 491 return chisq 492 493 def chisqfunc(p): 494 model = func(p, x) 495 chisq = anp.sum(((y_f - model) / dy_f) ** 2) 496 return chisq 497 498 output.method = kwargs.get('method', 'Levenberg-Marquardt') 499 if not silent: 500 print('Method:', output.method) 501 502 if output.method != 'Levenberg-Marquardt': 503 if output.method == 'migrad': 504 fit_result = iminuit.minimize(chisqfunc, x0, tol=1e-4) # Stopping criterion 0.002 * tol * errordef 505 if kwargs.get('correlated_fit') is True: 506 fit_result = iminuit.minimize(chisqfunc_corr, fit_result.x, tol=1e-4) # Stopping criterion 0.002 * tol * errordef 507 output.iterations = fit_result.nfev 508 else: 509 fit_result = scipy.optimize.minimize(chisqfunc, x0, method=kwargs.get('method'), tol=1e-12) 510 if kwargs.get('correlated_fit') is True: 511 fit_result = scipy.optimize.minimize(chisqfunc_corr, fit_result.x, method=kwargs.get('method'), tol=1e-12) 512 output.iterations = fit_result.nit 513 514 chisquare = fit_result.fun 515 516 else: 517 if kwargs.get('correlated_fit') is True: 518 def chisqfunc_residuals_corr(p): 519 model = func(p, x) 520 chisq = anp.dot(chol_inv, (y_f - model)) 521 return chisq 522 523 def chisqfunc_residuals(p): 524 model = func(p, x) 525 chisq = ((y_f - model) / dy_f) 526 return chisq 527 528 fit_result = scipy.optimize.least_squares(chisqfunc_residuals, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15) 529 if kwargs.get('correlated_fit') is True: 530 fit_result = scipy.optimize.least_squares(chisqfunc_residuals_corr, fit_result.x, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15) 531 532 chisquare = np.sum(fit_result.fun ** 2) 533 if kwargs.get('correlated_fit') is True: 534 assert np.isclose(chisquare, chisqfunc_corr(fit_result.x), atol=1e-14) 535 else: 536 assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14) 537 538 output.iterations = fit_result.nfev 539 540 if not fit_result.success: 541 raise Exception('The minimization procedure did not converge.') 542 543 if x.shape[-1] - n_parms > 0: 544 output.chisquare_by_dof = chisquare / (x.shape[-1] - n_parms) 545 else: 546 output.chisquare_by_dof = float('nan') 547 548 output.message = fit_result.message 549 if not silent: 550 print(fit_result.message) 551 print('chisquare/d.o.f.:', output.chisquare_by_dof) 552 553 if kwargs.get('expected_chisquare') is True: 554 if kwargs.get('correlated_fit') is not True: 555 W = np.diag(1 / np.asarray(dy_f)) 556 cov = covariance(y) 557 A = W @ jacobian(func)(fit_result.x, x) 558 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 559 expected_chisquare = np.trace((np.identity(x.shape[-1]) - P_phi) @ W @ cov @ W) 560 output.chisquare_by_expected_chisquare = chisquare / expected_chisquare 561 if not silent: 562 print('chisquare/expected_chisquare:', 563 output.chisquare_by_expected_chisquare) 564 565 fitp = fit_result.x 566 try: 567 if kwargs.get('correlated_fit') is True: 568 hess = jacobian(jacobian(chisqfunc_corr))(fitp) 569 else: 570 hess = jacobian(jacobian(chisqfunc))(fitp) 571 except TypeError: 572 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 573 574 if kwargs.get('correlated_fit') is True: 575 def chisqfunc_compact(d): 576 model = func(d[:n_parms], x) 577 chisq = anp.sum(anp.dot(chol_inv, (d[n_parms:] - model)) ** 2) 578 return chisq 579 580 else: 581 def chisqfunc_compact(d): 582 model = func(d[:n_parms], x) 583 chisq = anp.sum(((d[n_parms:] - model) / dy_f) ** 2) 584 return chisq 585 586 jac_jac = jacobian(jacobian(chisqfunc_compact))(np.concatenate((fitp, y_f))) 587 588 # Compute hess^{-1} @ jac_jac[:n_parms, n_parms:] using LAPACK dgesv 589 try: 590 deriv = -scipy.linalg.solve(hess, jac_jac[:n_parms, n_parms:]) 591 except np.linalg.LinAlgError: 592 raise Exception("Cannot invert hessian matrix.") 593 594 result = [] 595 for i in range(n_parms): 596 result.append(derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (y[0].value + np.finfo(np.float64).eps) * fit_result.x[i], list(y), man_grad=list(deriv[i]))) 597 598 output.fit_parameters = result 599 600 output.chisquare = chisquare 601 output.dof = x.shape[-1] - n_parms 602 output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof) 603 604 if kwargs.get('resplot') is True: 605 residual_plot(x, y, func, result) 606 607 if kwargs.get('qqplot') is True: 608 qqplot(x, y, func, result) 609 610 return output 611 612 613def fit_lin(x, y, **kwargs): 614 """Performs a linear fit to y = n + m * x and returns two Obs n, m. 615 616 Parameters 617 ---------- 618 x : list 619 Can either be a list of floats in which case no xerror is assumed, or 620 a list of Obs, where the dvalues of the Obs are used as xerror for the fit. 621 y : list 622 List of Obs, the dvalues of the Obs are used as yerror for the fit. 623 """ 624 625 def f(a, x): 626 y = a[0] + a[1] * x 627 return y 628 629 if all(isinstance(n, Obs) for n in x): 630 out = total_least_squares(x, y, f, **kwargs) 631 return out.fit_parameters 632 elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray): 633 out = least_squares(x, y, f, **kwargs) 634 return out.fit_parameters 635 else: 636 raise Exception('Unsupported types for x') 637 638 639def qqplot(x, o_y, func, p): 640 """Generates a quantile-quantile plot of the fit result which can be used to 641 check if the residuals of the fit are gaussian distributed. 642 """ 643 644 residuals = [] 645 for i_x, i_y in zip(x, o_y): 646 residuals.append((i_y - func(p, i_x)) / i_y.dvalue) 647 residuals = sorted(residuals) 648 my_y = [o.value for o in residuals] 649 probplot = scipy.stats.probplot(my_y) 650 my_x = probplot[0][0] 651 plt.figure(figsize=(8, 8 / 1.618)) 652 plt.errorbar(my_x, my_y, fmt='o') 653 fit_start = my_x[0] 654 fit_stop = my_x[-1] 655 samples = np.arange(fit_start, fit_stop, 0.01) 656 plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution') 657 plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-') 658 659 plt.xlabel('Theoretical quantiles') 660 plt.ylabel('Ordered Values') 661 plt.legend() 662 plt.draw() 663 664 665def residual_plot(x, y, func, fit_res): 666 """ Generates a plot which compares the fit to the data and displays the corresponding residuals""" 667 sorted_x = sorted(x) 668 xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0]) 669 xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2]) 670 x_samples = np.arange(xstart, xstop + 0.01, 0.01) 671 672 plt.figure(figsize=(8, 8 / 1.618)) 673 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) 674 ax0 = plt.subplot(gs[0]) 675 ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data') 676 ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0) 677 ax0.set_xticklabels([]) 678 ax0.set_xlim([xstart, xstop]) 679 ax0.set_xticklabels([]) 680 ax0.legend() 681 682 residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], x)) / np.asarray([o.dvalue for o in y]) 683 ax1 = plt.subplot(gs[1]) 684 ax1.plot(x, residuals, 'ko', ls='none', markersize=5) 685 ax1.tick_params(direction='out') 686 ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True) 687 ax1.axhline(y=0.0, ls='--', color='k', marker=" ") 688 ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k') 689 ax1.set_xlim([xstart, xstop]) 690 ax1.set_ylabel('Residuals') 691 plt.subplots_adjust(wspace=None, hspace=None) 692 plt.draw() 693 694 695def error_band(x, func, beta): 696 """Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.""" 697 cov = covariance(beta) 698 if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): 699 warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) 700 701 deriv = [] 702 for i, item in enumerate(x): 703 deriv.append(np.array(egrad(func)([o.value for o in beta], item))) 704 705 err = [] 706 for i, item in enumerate(x): 707 err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) 708 err = np.array(err) 709 710 return err 711 712 713def ks_test(objects=None): 714 """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. 715 716 Parameters 717 ---------- 718 objects : list 719 List of fit results to include in the analysis (optional). 720 """ 721 722 if objects is None: 723 obs_list = [] 724 for obj in gc.get_objects(): 725 if isinstance(obj, Fit_result): 726 obs_list.append(obj) 727 else: 728 obs_list = objects 729 730 p_values = [o.p_value for o in obs_list] 731 732 bins = len(p_values) 733 x = np.arange(0, 1.001, 0.001) 734 plt.plot(x, x, 'k', zorder=1) 735 plt.xlim(0, 1) 736 plt.ylim(0, 1) 737 plt.xlabel('p-value') 738 plt.ylabel('Cumulative probability') 739 plt.title(str(bins) + ' p-values') 740 741 n = np.arange(1, bins + 1) / np.float64(bins) 742 Xs = np.sort(p_values) 743 plt.step(Xs, n) 744 diffs = n - Xs 745 loc_max_diff = np.argmax(np.abs(diffs)) 746 loc = Xs[loc_max_diff] 747 plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0)) 748 plt.draw() 749 750 print(scipy.stats.kstest(p_values, 'uniform'))
18class Fit_result(Sequence): 19 """Represents fit results. 20 21 Attributes 22 ---------- 23 fit_parameters : list 24 results for the individual fit parameters, 25 also accessible via indices. 26 """ 27 28 def __init__(self): 29 self.fit_parameters = None 30 31 def __getitem__(self, idx): 32 return self.fit_parameters[idx] 33 34 def __len__(self): 35 return len(self.fit_parameters) 36 37 def gamma_method(self, **kwargs): 38 """Apply the gamma method to all fit parameters""" 39 [o.gamma_method(**kwargs) for o in self.fit_parameters] 40 41 def __str__(self): 42 my_str = 'Goodness of fit:\n' 43 if hasattr(self, 'chisquare_by_dof'): 44 my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' 45 elif hasattr(self, 'residual_variance'): 46 my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n' 47 if hasattr(self, 'chisquare_by_expected_chisquare'): 48 my_str += '\u03C7\u00b2/\u03C7\u00b2exp = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n' 49 if hasattr(self, 'p_value'): 50 my_str += 'p-value = ' + f'{self.p_value:2.4f}' + '\n' 51 my_str += 'Fit parameters:\n' 52 for i_par, par in enumerate(self.fit_parameters): 53 my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' 54 return my_str 55 56 def __repr__(self): 57 m = max(map(len, list(self.__dict__.keys()))) + 1 58 return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())])
Represents fit results.
Attributes
- fit_parameters (list): results for the individual fit parameters, also accessible via indices.
37 def gamma_method(self, **kwargs): 38 """Apply the gamma method to all fit parameters""" 39 [o.gamma_method(**kwargs) for o in self.fit_parameters]
Apply the gamma method to all fit parameters
Inherited Members
- collections.abc.Sequence
- index
- count
61def least_squares(x, y, func, priors=None, silent=False, **kwargs): 62 r'''Performs a non-linear fit to y = func(x). 63 64 Parameters 65 ---------- 66 x : list 67 list of floats. 68 y : list 69 list of Obs. 70 func : object 71 fit function, has to be of the form 72 73 ```python 74 import autograd.numpy as anp 75 76 def func(a, x): 77 return a[0] + a[1] * x + a[2] * anp.sinh(x) 78 ``` 79 80 For multiple x values func can be of the form 81 82 ```python 83 def func(a, x): 84 (x1, x2) = x 85 return a[0] * x1 ** 2 + a[1] * x2 86 ``` 87 88 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 89 will not work. 90 priors : list, optional 91 priors has to be a list with an entry for every parameter in the fit. The entries can either be 92 Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 93 0.548(23), 500(40) or 0.5(0.4) 94 silent : bool, optional 95 If true all output to the console is omitted (default False). 96 initial_guess : list 97 can provide an initial guess for the input parameters. Relevant for 98 non-linear fits with many parameters. In case of correlated fits the guess is used to perform 99 an uncorrelated fit which then serves as guess for the correlated fit. 100 method : str, optional 101 can be used to choose an alternative method for the minimization of chisquare. 102 The possible methods are the ones which can be used for scipy.optimize.minimize and 103 migrad of iminuit. If no method is specified, Levenberg-Marquard is used. 104 Reliable alternatives are migrad, Powell and Nelder-Mead. 105 correlated_fit : bool 106 If True, use the full inverse covariance matrix in the definition of the chisquare cost function. 107 For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. 108 In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix). 109 This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning). 110 At the moment this option only works for `prior==None` and when no `method` is given. 111 expected_chisquare : bool 112 If True estimates the expected chisquare which is 113 corrected by effects caused by correlated input data (default False). 114 resplot : bool 115 If True, a plot which displays fit, data and residuals is generated (default False). 116 qqplot : bool 117 If True, a quantile-quantile plot of the fit result is generated (default False). 118 ''' 119 if priors is not None: 120 return _prior_fit(x, y, func, priors, silent=silent, **kwargs) 121 else: 122 return _standard_fit(x, y, func, silent=silent, **kwargs)
Performs a non-linear fit to y = func(x).
Parameters
- x (list): list of floats.
- y (list): list of Obs.
func (object): fit function, has to be of the form
import autograd.numpy as anp def func(a, x): return a[0] + a[1] * x + a[2] * anp.sinh(x)
For multiple x values func can be of the form
def func(a, x): (x1, x2) = x return a[0] * x1 ** 2 + a[1] * x2
It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation will not work.
- priors (list, optional): priors has to be a list with an entry for every parameter in the fit. The entries can either be Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 0.548(23), 500(40) or 0.5(0.4)
- silent (bool, optional): If true all output to the console is omitted (default False).
- initial_guess (list): can provide an initial guess for the input parameters. Relevant for non-linear fits with many parameters. In case of correlated fits the guess is used to perform an uncorrelated fit which then serves as guess for the correlated fit.
- method (str, optional): can be used to choose an alternative method for the minimization of chisquare. The possible methods are the ones which can be used for scipy.optimize.minimize and migrad of iminuit. If no method is specified, Levenberg-Marquard is used. Reliable alternatives are migrad, Powell and Nelder-Mead.
- correlated_fit (bool):
If True, use the full inverse covariance matrix in the definition of the chisquare cost function.
For details about how the covariance matrix is estimated see
pyerrors.obs.covariance
. In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix). This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning). At the moment this option only works forprior==None
and when nomethod
is given. - expected_chisquare (bool): If True estimates the expected chisquare which is corrected by effects caused by correlated input data (default False).
- resplot (bool): If True, a plot which displays fit, data and residuals is generated (default False).
- qqplot (bool): If True, a quantile-quantile plot of the fit result is generated (default False).
125def total_least_squares(x, y, func, silent=False, **kwargs): 126 r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. 127 128 Parameters 129 ---------- 130 x : list 131 list of Obs, or a tuple of lists of Obs 132 y : list 133 list of Obs. The dvalues of the Obs are used as x- and yerror for the fit. 134 func : object 135 func has to be of the form 136 137 ```python 138 import autograd.numpy as anp 139 140 def func(a, x): 141 return a[0] + a[1] * x + a[2] * anp.sinh(x) 142 ``` 143 144 For multiple x values func can be of the form 145 146 ```python 147 def func(a, x): 148 (x1, x2) = x 149 return a[0] * x1 ** 2 + a[1] * x2 150 ``` 151 152 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 153 will not work. 154 silent : bool, optional 155 If true all output to the console is omitted (default False). 156 initial_guess : list 157 can provide an initial guess for the input parameters. Relevant for non-linear 158 fits with many parameters. 159 expected_chisquare : bool 160 If true prints the expected chisquare which is 161 corrected by effects caused by correlated input data. 162 This can take a while as the full correlation matrix 163 has to be calculated (default False). 164 165 Notes 166 ----- 167 Based on the orthogonal distance regression module of scipy 168 ''' 169 170 output = Fit_result() 171 172 output.fit_function = func 173 174 x = np.array(x) 175 176 x_shape = x.shape 177 178 if not callable(func): 179 raise TypeError('func has to be a function.') 180 181 for i in range(42): 182 try: 183 func(np.arange(i), x.T[0]) 184 except Exception: 185 continue 186 else: 187 break 188 else: 189 raise RuntimeError("Fit function is not valid.") 190 191 n_parms = i 192 if not silent: 193 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 194 195 x_f = np.vectorize(lambda o: o.value)(x) 196 dx_f = np.vectorize(lambda o: o.dvalue)(x) 197 y_f = np.array([o.value for o in y]) 198 dy_f = np.array([o.dvalue for o in y]) 199 200 if np.any(np.asarray(dx_f) <= 0.0): 201 raise Exception('No x errors available, run the gamma method first.') 202 203 if np.any(np.asarray(dy_f) <= 0.0): 204 raise Exception('No y errors available, run the gamma method first.') 205 206 if 'initial_guess' in kwargs: 207 x0 = kwargs.get('initial_guess') 208 if len(x0) != n_parms: 209 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 210 else: 211 x0 = [1] * n_parms 212 213 data = RealData(x_f, y_f, sx=dx_f, sy=dy_f) 214 model = Model(func) 215 odr = ODR(data, model, x0, partol=np.finfo(np.float64).eps) 216 odr.set_job(fit_type=0, deriv=1) 217 out = odr.run() 218 219 output.residual_variance = out.res_var 220 221 output.method = 'ODR' 222 223 output.message = out.stopreason 224 225 output.xplus = out.xplus 226 227 if not silent: 228 print('Method: ODR') 229 print(*out.stopreason) 230 print('Residual variance:', output.residual_variance) 231 232 if out.info > 3: 233 raise Exception('The minimization procedure did not converge.') 234 235 m = x_f.size 236 237 def odr_chisquare(p): 238 model = func(p[:n_parms], p[n_parms:].reshape(x_shape)) 239 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2) 240 return chisq 241 242 if kwargs.get('expected_chisquare') is True: 243 W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel())))) 244 245 if kwargs.get('covariance') is not None: 246 cov = kwargs.get('covariance') 247 else: 248 cov = covariance(np.concatenate((y, x.ravel()))) 249 250 number_of_x_parameters = int(m / x_f.shape[-1]) 251 252 old_jac = jacobian(func)(out.beta, out.xplus) 253 fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0))) 254 fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplus, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0]))) 255 new_jac = np.concatenate((fused_row1, fused_row2), axis=1) 256 257 A = W @ new_jac 258 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 259 expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W) 260 if expected_chisquare <= 0.0: 261 warnings.warn("Negative expected_chisquare.", RuntimeWarning) 262 expected_chisquare = np.abs(expected_chisquare) 263 output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) / expected_chisquare 264 if not silent: 265 print('chisquare/expected_chisquare:', 266 output.chisquare_by_expected_chisquare) 267 268 fitp = out.beta 269 try: 270 hess = jacobian(jacobian(odr_chisquare))(np.concatenate((fitp, out.xplus.ravel()))) 271 except TypeError: 272 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 273 274 def odr_chisquare_compact_x(d): 275 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 276 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 277 return chisq 278 279 jac_jac_x = jacobian(jacobian(odr_chisquare_compact_x))(np.concatenate((fitp, out.xplus.ravel(), x_f.ravel()))) 280 281 # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv 282 try: 283 deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:]) 284 except np.linalg.LinAlgError: 285 raise Exception("Cannot invert hessian matrix.") 286 287 def odr_chisquare_compact_y(d): 288 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 289 chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 290 return chisq 291 292 jac_jac_y = jacobian(jacobian(odr_chisquare_compact_y))(np.concatenate((fitp, out.xplus.ravel(), y_f))) 293 294 # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv 295 try: 296 deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:]) 297 except np.linalg.LinAlgError: 298 raise Exception("Cannot invert hessian matrix.") 299 300 result = [] 301 for i in range(n_parms): 302 result.append(derived_observable(lambda my_var, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i]))) 303 304 output.fit_parameters = result 305 306 output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) 307 output.dof = x.shape[-1] - n_parms 308 output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof) 309 310 return output
Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters.
Parameters
- x (list): list of Obs, or a tuple of lists of Obs
- y (list): list of Obs. The dvalues of the Obs are used as x- and yerror for the fit.
func (object): func has to be of the form
import autograd.numpy as anp def func(a, x): return a[0] + a[1] * x + a[2] * anp.sinh(x)
For multiple x values func can be of the form
def func(a, x): (x1, x2) = x return a[0] * x1 ** 2 + a[1] * x2
It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation will not work.
- silent (bool, optional): If true all output to the console is omitted (default False).
- initial_guess (list): can provide an initial guess for the input parameters. Relevant for non-linear fits with many parameters.
- expected_chisquare (bool): If true prints the expected chisquare which is corrected by effects caused by correlated input data. This can take a while as the full correlation matrix has to be calculated (default False).
Notes
Based on the orthogonal distance regression module of scipy
614def fit_lin(x, y, **kwargs): 615 """Performs a linear fit to y = n + m * x and returns two Obs n, m. 616 617 Parameters 618 ---------- 619 x : list 620 Can either be a list of floats in which case no xerror is assumed, or 621 a list of Obs, where the dvalues of the Obs are used as xerror for the fit. 622 y : list 623 List of Obs, the dvalues of the Obs are used as yerror for the fit. 624 """ 625 626 def f(a, x): 627 y = a[0] + a[1] * x 628 return y 629 630 if all(isinstance(n, Obs) for n in x): 631 out = total_least_squares(x, y, f, **kwargs) 632 return out.fit_parameters 633 elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray): 634 out = least_squares(x, y, f, **kwargs) 635 return out.fit_parameters 636 else: 637 raise Exception('Unsupported types for x')
Performs a linear fit to y = n + m * x and returns two Obs n, m.
Parameters
- x (list): Can either be a list of floats in which case no xerror is assumed, or a list of Obs, where the dvalues of the Obs are used as xerror for the fit.
- y (list): List of Obs, the dvalues of the Obs are used as yerror for the fit.
640def qqplot(x, o_y, func, p): 641 """Generates a quantile-quantile plot of the fit result which can be used to 642 check if the residuals of the fit are gaussian distributed. 643 """ 644 645 residuals = [] 646 for i_x, i_y in zip(x, o_y): 647 residuals.append((i_y - func(p, i_x)) / i_y.dvalue) 648 residuals = sorted(residuals) 649 my_y = [o.value for o in residuals] 650 probplot = scipy.stats.probplot(my_y) 651 my_x = probplot[0][0] 652 plt.figure(figsize=(8, 8 / 1.618)) 653 plt.errorbar(my_x, my_y, fmt='o') 654 fit_start = my_x[0] 655 fit_stop = my_x[-1] 656 samples = np.arange(fit_start, fit_stop, 0.01) 657 plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution') 658 plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-') 659 660 plt.xlabel('Theoretical quantiles') 661 plt.ylabel('Ordered Values') 662 plt.legend() 663 plt.draw()
Generates a quantile-quantile plot of the fit result which can be used to check if the residuals of the fit are gaussian distributed.
666def residual_plot(x, y, func, fit_res): 667 """ Generates a plot which compares the fit to the data and displays the corresponding residuals""" 668 sorted_x = sorted(x) 669 xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0]) 670 xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2]) 671 x_samples = np.arange(xstart, xstop + 0.01, 0.01) 672 673 plt.figure(figsize=(8, 8 / 1.618)) 674 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) 675 ax0 = plt.subplot(gs[0]) 676 ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data') 677 ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0) 678 ax0.set_xticklabels([]) 679 ax0.set_xlim([xstart, xstop]) 680 ax0.set_xticklabels([]) 681 ax0.legend() 682 683 residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], x)) / np.asarray([o.dvalue for o in y]) 684 ax1 = plt.subplot(gs[1]) 685 ax1.plot(x, residuals, 'ko', ls='none', markersize=5) 686 ax1.tick_params(direction='out') 687 ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True) 688 ax1.axhline(y=0.0, ls='--', color='k', marker=" ") 689 ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k') 690 ax1.set_xlim([xstart, xstop]) 691 ax1.set_ylabel('Residuals') 692 plt.subplots_adjust(wspace=None, hspace=None) 693 plt.draw()
Generates a plot which compares the fit to the data and displays the corresponding residuals
696def error_band(x, func, beta): 697 """Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.""" 698 cov = covariance(beta) 699 if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): 700 warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) 701 702 deriv = [] 703 for i, item in enumerate(x): 704 deriv.append(np.array(egrad(func)([o.value for o in beta], item))) 705 706 err = [] 707 for i, item in enumerate(x): 708 err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) 709 err = np.array(err) 710 711 return err
Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.
714def ks_test(objects=None): 715 """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. 716 717 Parameters 718 ---------- 719 objects : list 720 List of fit results to include in the analysis (optional). 721 """ 722 723 if objects is None: 724 obs_list = [] 725 for obj in gc.get_objects(): 726 if isinstance(obj, Fit_result): 727 obs_list.append(obj) 728 else: 729 obs_list = objects 730 731 p_values = [o.p_value for o in obs_list] 732 733 bins = len(p_values) 734 x = np.arange(0, 1.001, 0.001) 735 plt.plot(x, x, 'k', zorder=1) 736 plt.xlim(0, 1) 737 plt.ylim(0, 1) 738 plt.xlabel('p-value') 739 plt.ylabel('Cumulative probability') 740 plt.title(str(bins) + ' p-values') 741 742 n = np.arange(1, bins + 1) / np.float64(bins) 743 Xs = np.sort(p_values) 744 plt.step(Xs, n) 745 diffs = n - Xs 746 loc_max_diff = np.argmax(np.abs(diffs)) 747 loc = Xs[loc_max_diff] 748 plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0)) 749 plt.draw() 750 751 print(scipy.stats.kstest(p_values, 'uniform'))
Performs a Kolmogorov–Smirnov test for the p-values of all fit object.
Parameters
- objects (list): List of fit results to include in the analysis (optional).