From 87b10485baa5d66a65378c785cae273dab9ab534 Mon Sep 17 00:00:00 2001 From: Philipp Niedermayer <p.niedermayer@gsi.de> Date: Wed, 25 May 2022 11:53:59 +0200 Subject: [PATCH] Implement double peak fit --- fitting.py | 36 ++++++++++++++++++++++++++++++++++++ plotting.py | 22 ++++++++++++++++------ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/fitting.py b/fitting.py index 6406cab..35fbbd6 100644 --- a/fitting.py +++ b/fitting.py @@ -60,6 +60,42 @@ def fit_lorenzian(S, V, **kwargs): param, param_error, function, [X, _] = result param[3] = np.abs(param[3]) # return the positive sigma (could use bounds instead, but that's more likely to fail) return param, param_error, function, [X, function(X, *param)] + + +def fit_double_lorenzian(S, V, *, s0=None, dry_run=False, **kwargs): + """Fit a lorenzian function to the data + + :param S: data x values + :param V: data y values + :param xerr: data x uncertainties + :param yerr: data y uncertainties + :param s0: initial peak positions + :param p0: initial values of parameters + :param odr: Fit method to use: True means ortogonal distance regression (ODR), False means ordinary least square fit, None (default) uses ODR only if either xerr or yerr is not None + :param extend: fraction by which the returned fit trace extends beyond the first/last data point + """ + lorenzian = lambda s, v0, vp, s0, gamma, vp2, s02, gamma2: v0 + vp/(1+((s-s0)/gamma)**2) + vp2/(1+((s-s02)/gamma2)**2) + + # ! for double peak fit, initial conditions are very important + v0 = np.min(V) + vp = np.max(V)-v0 + if s0 is None: + s0 = S[np.argmax(V)] + sigma = np.sqrt(np.abs(np.sum((S - s0) ** 2 * (V-v0)) / np.sum(V-v0)))/10 + s0 = [s0-3*sigma, s0+3*sigma] + sigma0 = np.sqrt(np.abs(np.sum((S - s0[0]) ** 2 * (V-v0)) / np.sum(V-v0)))/10 + sigma1 = np.sqrt(np.abs(np.sum((S - s0[1]) ** 2 * (V-v0)) / np.sum(V-v0)))/10 + p0=(v0, vp, s0[0], sigma0, vp, s0[1], sigma1) + + if dry_run: + mi, ma = min(S), max(S) + X = np.linspace(mi, ma, 1000) + return p0, (0,0,0,0), 0, [X, lorenzian(X, *p0)] + + result = fit_function(lorenzian, S, V, p0=p0, **kwargs) + param, param_error, function, [X, _] = result + param[3] = np.abs(param[3]) # return the positive sigma (could use bounds instead, but that's more likely to fail) + return param, param_error, function, [X, function(X, *param)] def fit_function(function, x, y, *, xerr=None, yerr=None, p0=None, odr=None, extend=0): diff --git a/plotting.py b/plotting.py index 4f55bfd..e1dd308 100644 --- a/plotting.py +++ b/plotting.py @@ -236,14 +236,24 @@ def plot_tune_spectrum(ax, libera_data, xy, turn_range=None, time_range=None, tu q = None try: fitr = (fit if callable(fit) else fit_lorenzian)(freq, mag) - q, w = fitr[0][2], fitr[0][3] - if q<np.min(freq) or q>np.max(freq) or w > 0.1: - raise RuntimeError('Fit failed') + if fit in (fit_lorenzian, fit_gaussian): + q, w = fitr[0][2], fitr[0][3] + if q<np.min(freq) or q>np.max(freq) or w > 0.1: + raise RuntimeError('Fit failed') + q = SI.Measurement(fitr[0][2], (fitr[1][2]**2+fitr[0][3]**2+fitr[1][3]**2)**0.5, '') # conservative estimate of error including width of peak + ax.plot(*fitr[-1], '--', label=f'Fit $q_{xy}={q:~L}$', zorder=50) + + elif fit in (fit_double_lorenzian, ): + q1, w1, q2, w2 = fitr[0][2], fitr[0][3], fitr[0][5], fitr[0][6] + if q1<np.min(freq) or q1>np.max(freq) or w1 > 0.1 or q2<np.min(freq) or q2>np.max(freq) or w2 > 0.1: + raise RuntimeError('Fit failed') + q1 = SI.Measurement(fitr[0][2], (fitr[1][2]**2+fitr[0][3]**2+fitr[1][3]**2)**0.5, '') # conservative estimate of error including width of peak + q2 = SI.Measurement(fitr[0][5], (fitr[1][5]**2+fitr[0][6]**2+fitr[1][6]**2)**0.5, '') # conservative estimate of error including width of peak + ax.plot(*fitr[-1], '--', label=f'Fit $q_{{{xy},1}}={q1:~L}$\nFit $q_{{{xy},2}}={q2:~L}$', zorder=50) + q = (q1, q2) + except RuntimeError: print('Warning: fit failed') - else: - q = SI.Measurement(fitr[0][2], (fitr[1][2]**2+fitr[0][3]**2+fitr[1][3]**2)**0.5, '') # conservative estimate of error including width of peak - ax.plot(*fitr[-1], '--', label=f'Fit $q_{xy}={q:~L}$', zorder=50) if return_spectrum: return freq, mag, phase, q -- GitLab