def substractBase(x, y, peakInterval, baseFitInterval, model, usePositiveConstrains, extrapolate=None, useStartParams=None): """ Fit base by Cauchy function and substract from y. :param x: argument :param y: function values :param peakInterval: interval of peak search (do not included in base fitting) :param baseFitInterval: interval of base fit. Usually it includes peakInterval :param model: 'cauchy' or 'bezier' or 'arctan' :param usePositiveConstrains: add constrain y_base <= y :param extrapolate: {'left':percent_dx_left, 'right':percent_dx_right} :return: x_peak, y_sub - peak with substracted base (on interval peakInterval); x_base, y_base - base on baseFitInterval; y_peak - peak part of original func; y_sub_full - y_sub expanded to baseFitInterval """ assert model in ['cauchy', 'bezier', 'arctan'] assert len(x) == len(y) if extrapolate is None: extrapolate = {} ind_peak = (x >= peakInterval[0]) & (x <= peakInterval[1]) ind_base_full = (x >= baseFitInterval[0]) & (x <= baseFitInterval[1]) ind_base = ind_base_full & ~ind_peak x_peak = x[ind_peak] y_peak = y[ind_peak] x_base = x[ind_base] y_base = y[ind_base] x_base_full = x[ind_base_full] y_base_full = y[ind_base_full] # make x_fit y_fit by extrapolating base inside peak interval (linear extrapolation from both ends) if usePositiveConstrains: ind_base_left = (x < peakInterval[0]) & ind_base_full b1, a1 = linearReg(x[ind_base_left], y[ind_base_left]) ind_base_right = (x > peakInterval[1]) & ind_base_full b2, a2 = linearReg(x[ind_base_right], y[ind_base_right]) y_gap = np.max([a1 * x_peak + b1, a2 * x_peak + b2], axis=0).reshape(-1) assert len(y_gap) == len(x_peak) x_fit = x_base_full y_fit = np.concatenate((y[ind_base_left], y_gap, y[ind_base_right])) assert len(x_fit) == len(y_fit), str(len(x_fit)) + " " + str( len(y_fit)) else: x_fit = x_base y_fit = y_base x1 = x_base[0] x2 = x_base[-1] y1 = y_base[0] y2 = y_base[-1] if 'left' in extrapolate: n = np.where(x_base <= x1 + (x2 - x1) / 10)[0][-1] + 1 if n < 2: n = 2 slope, intercept, _, _, _ = scipy.stats.linregress( x_base[:n], y_base[:n]) percent = extrapolate['left'] count = np.round(len(x_base) * percent) first = x1 - (x2 - x1) * percent last = x1 - (x2 - x1) / count new_x = np.linspace(first, last, count) x_base = np.insert(x_base, 0, new_x) y_base = np.insert(y_base, 0, new_x * slope + intercept) if 'right' in extrapolate: n = np.where(x_base >= x2 - (x2 - x1) / 10)[0][-1] + 1 if n < 2: n = 2 slope, intercept, _, _, _ = scipy.stats.linregress( x_base[-n:], y_base[-n:]) percent = extrapolate['right'] count = np.round(len(x_base) * percent) last = x2 + (x2 - x1) * percent first = x2 + (x2 - x1) / count new_x = np.linspace(first, last, count) x_base = np.append(x_base, new_x) y_base = np.append(y_base, new_x * slope + intercept) assert (len(x_peak) >= 2) and (len(x_base) >= 2), 'len(x_peak) = ' + str( len(x_peak)) + ' len(x_base) = ' + str(len(x_base)) minx = np.min(x) maxx = np.max(x) maxy = np.max(y) if model == 'cauchy': fff = lambda x, a, b, g, d: a / ((x - b)**2 + g) + d mod = ExpressionModel('a/((x-b)**2+g) + d') b0 = x2 + x2 - x1 g0 = 1 a0 = (y2 - y1) / (1 / ((x2 - b0)**2 + g0) - 1 / ((x1 - b0)**2 + g0)) d0 = y1 - a0 / ((x1 - b0)**2 + g0) params = mod.make_params(a=a0, b=b0, g=g0, d=d0) param_order = {'a': 0, 'b': 1, 'g': 2, 'd': 3} start0 = [ params['a'].value, params['b'].value, params['g'].value, params['d'].value ] result = mod.fit(y_fit, params, x=x_fit) start = [ result.params['a'].value, result.params['b'].value, result.params['g'].value, result.params['d'].value ] bounds = [[0, 1e3 * maxy], [minx, maxx + (maxx - minx) * 10], [0, (maxx - minx) * 10], [-maxy, maxy]] elif model == 'arctan': fff = lambda x, a, b, c, x0, d: b / (1 + np.exp(-a * (x - x0)) ) + c + d * (x - x_base[0]) mod = ExpressionModel('b/(1+exp(-a*(x - x0)))+c+d*(x-' + str(x_base[0]) + ')') efermi0, _, _ = findExpEfermi(x, y, 0.5 * np.mean(y[-5:])) if efermi0 < x_peak[0]: efermi0 = x_peak[0] a0 = 1 b0 = y[-1] - y[0] c0 = y[0] x00 = efermi0 d0 = (y_peak[0] - y_base[0]) / (x_peak[0] - x_base[0]) params = mod.make_params(a=a0, b=b0, c=c0, x0=x00, d=d0) param_order = {'a': 0, 'b': 1, 'c': 2, 'x0': 3, 'd': 4} start0 = [ params['a'].value, params['b'].value, params['c'].value, params['x0'].value, params['d'].value ] assert np.all(x[1:] - x[:-1] > 0), str(x) max_dy = np.max((y[1:] - y[:-1]) / (x[1:] - x[:-1])) params['a'].set(min=0) params['a'].set(max=max_dy / (np.max(y) - np.min(y)) * 10) params['b'].set(min=0) params['x0'].set(min=x_peak[0]) params['d'].set(min=0) params['d'].set(max=3 * (y_peak[0] - y_base[0]) / (x_peak[0] - x_base[0])) dist = np.max([abs(x00 - minx), abs(x00 - maxx), maxx - minx]) bounds = [[0, a0 * 100], [0, maxy * 10], [-maxy, maxy], [minx - dist, maxx + dist * 10], [0, 3 * (y_peak[0] - y_base[0]) / (x_peak[0] - x_base[0])]] # TODO: remove lmfit, because scipy.optimize.minimize works better if useStartParams is None: result = mod.fit(y_fit, params, x=x_fit) # result.plot() # plt.show() # print(result.fit_report()) start = [ result.params['a'].value, result.params['b'].value, result.params['c'].value, result.params['x0'].value, result.params['d'].value ] else: start = useStartParams else: Mtk = lambda n, t, k: t**k * (1 - t)**(n - k) * scipy.misc.comb(n, k) BezierCoeff = lambda ts: [[Mtk(3, t, k) for k in range(4)] for t in ts] t = np.linspace(0, 1, len(x_base)) Pseudoinverse = np.linalg.pinv(BezierCoeff(t)) data = np.column_stack((x_base, y_base)) control_points = Pseudoinverse.dot(data) Bezier = np.array(BezierCoeff(tPlot)).dot(control_points) assert not usePositiveConstrains return x_peak, y_peak - app_y_base_inside_peak, x_base_full, app_y_base_full, y_peak, y_base_full - app_y_base_full def func(params): y_app = fff(x_base, *params) return np.linalg.norm(y_app - y_base) if useStartParams is None: res = scipy.optimize.minimize(func, start0, bounds=bounds) # print(func(start), res.fun) if res.fun < func(start): for name in result.params: # print(f'Setting {name} = ',res.x[param_order[name]]) result.params[name].set(res.x[param_order[name]]) # print(result.params) start = res.x info = {'optimParam': start, 'optimVal': func(start)} if usePositiveConstrains: #while True: #if np.all(fff(x_peak,*start)<=y_peak): break #dx = np.max(x_peak)-np.min(x_peak) #dy = np.max(y_peak)-np.min(y_peak) #start[1] += dx*0.01 #start[3] -= dy*0.01 constrains = tuple() for i in range(len(x_peak)): cons_fun = lambda params, i=i: fff(x_peak[i], *params) constrains += (scipy.optimize.NonlinearConstraint( cons_fun, -maxy, y_peak[i]), ) # print(bounds) res = scipy.optimize.minimize(func, start, bounds=bounds, constraints=constrains) params = res.x app_y_base_inside_peak = fff(x_peak, *params) app_y_base_full = fff(x_base_full, *params) info = {'optimParam': params, 'optimVal': res.fun} else: app_y_base_inside_peak = mod.eval(result.params, x=x_peak) app_y_base_full = mod.eval(result.params, x=x_base_full) return x_peak, y_peak - app_y_base_inside_peak, x_base_full, app_y_base_full, y_peak, y_base_full - app_y_base_full, info
import matplotlib.pyplot as plt import numpy as np from lmfit.models import ExpressionModel # Number of frames nf = [2000, 1500, 1000, 500] time = [6534.59, 4871.85, 3282.48, 1682.1186] # Fitting gmod = ExpressionModel("a*x +b") result = gmod.fit(time, x=nf, a=10, b=0) # Prediction x = np.arange(0, 20000, 500) y = gmod.eval(result.params, x=x) print(result.params) print(result.best_fit) print(result.best_values) print(result.fit_report()) test_nf = 200000 test_time = gmod.eval(result.params, x=test_nf) print('{0:d} frames taks {1:f} s ({2:f} hours to finish)'.format( test_nf, test_time, test_time / 3600.0)) plt.plot(x, y, '.-') plt.plot(nf, time, '*') plt.show()