from scan_framework.models.model import *
from scan_framework.analysis.curvefits import *
import numpy as np
from math import *
[docs]class BadFit(Exception):
pass
[docs]class CantFit(Exception):
pass
[docs]class FitModel(Model):
mirror = True
validation_errors = {}
valid = True
validators = {}
fit_map = {}
[docs] def fit_data(self, x, y, fit_function, hold=None, guess={}, yerr=None, man_bounds={}, man_scale={}):
"""Fit data in x and y to self.fit_function"""
# make sure x & y are numpy arrays
x = np.array(x)
y = np.array(y)
# -- fit the data
guess = guess or {} # if guess is None
self.fit = Fit(x, y, fit_function, yerr)
self.fit.fit_data(hold=hold, man_guess=guess, man_bounds=man_bounds, man_scale=man_scale)
# fitline in original order of x
self.fit.fitline_orig = self.fit.func.value(x, *self.fit.popt)
# -- map params
FitModel.map(self)
# -- regression analysis
"""Regression analysis"""
self.fit.reg_err = round(FitModel.reg_err(y, self.fit.fitline), 3)
self.fit.r2 = round(FitModel.r2(y, self.fit.fitline), 3)
self.fit.fitresults['analysis'] = {
'reg_err': self.fit.reg_err,
'r2': self.fit.r2
}
[docs] def map(self):
"""Map fit function parameter names to more descriptive names (e.g. for dataset names)"""
if self.fit_map:
for name in self.fit_map:
mapped = self.fit_map[name]
self.fit.fitresults[mapped] = self.fit.fitresults[name]
self.fit.fitresults[mapped + "_err"] = self.fit.fitresults[name + "_err"]
[docs] def pre_validate(self, series, validators=None):
"""Validate data is acceptable to fit"""
self.validation_errors = {}
self.valid = True
if validators is None:
return True
for field, rules in validators.items():
for method, args in rules.items():
value = series[field]
self.valid = self._call_validation_method(method, field, value, args)
if not self.valid:
raise CantFit(self.validation_errors[field])
return True
[docs] def validate(self, validators=None):
"""Validate fit was successful"""
self.validation_errors = {}
self.valid = True
if validators is None:
return True
for field, rules in validators.items():
fields = field.split(".")
for method, args in rules.items():
value = self.fit.fitresults
ok = True
for f in fields:
if f in value:
value = value[f]
else:
self.logger.warning("Validation skipped, {0} is not in fitresults".format(field))
ok = False
break
if ok:
self.valid = self._call_validation_method(method, field, value, args)
if not self.valid:
raise BadFit(self.validation_errors[field])
return True
[docs] def simulate(self, x, noise_level=0, simulation_args = None):
if simulation_args is None:
try:
simulation_args = self.simulation_args
except(NotImplementedError):
simulation_args = self.fit_function.simulation_args()
value = self.fit_function.value(x, **simulation_args)
# convert expectation value to quantized value
f = floor(value)
c = ceil(value)
if np.random.random() > (value - f):
value = f
else:
value = c
noise = (2.0 * np.random.random() - 1.0) * noise_level
return int(abs(value + noise))
[docs] @staticmethod
def reg_err(y, fitline):
"""Calculate the standard error in the regression"""
y = y[~np.isnan(y)]
N = len(y)
ss_res = np.sum((y - fitline)**2) # residual sum of squares
ss_tot = np.sum((y - np.mean(y))**2) # total sum of squares
S = np.sqrt(ss_res/N)
return S
[docs] @staticmethod
def r2(y, fitline):
"""Calculate the coefficient of determination (R^2)"""
y = y[~np.isnan(y)]
ss_res = np.sum((y - fitline)**2) # residual sum of squares
ss_tot = np.sum((y - np.mean(y))**2) # total sum of squares
r2 = 1 - ss_res / ss_tot
return r2