def fit_temperature_stable(run_num, df_info, df_raw, ycol, ystd, ystd_sf, remove_data=True, plotfile=None, loss='linear'): # loss should be 'linear' or 'huber' # query raw data to get run df_i = df_info.iloc[run_num] df_ = df_raw.query(f'"{df_i.t0}" < index < "{df_i.tf}"').copy() df_['run_hours'] = (df_.index - df_.index[0]).total_seconds() / 60**2 # if run is long enough, only fit first 100 hours if df_['run_hours'].max() > 100.: df_2 = df_.query('run_hours < 100') else: df_2 = df_ # TESTING # df_2_ = df_2.copy() # df_2 = df_2.iloc[120:] # results = None; fig = None; ax1 = None; ax2 = None # df_ = df_.iloc[120:].copy() # df_['run_num'] = run_num # return results, df_, fig, ax1, ax2 # END TESTING # create an array for weights if float supplied if type(ystd) != np.ndarray: # ystd = ystd*np.ones(len(df_2)) ystd = ystd * df_2[ycol] # fractional stddev. from manufacturer # setup lmfit model # y = A + B * np.exp(- x / C) model = lm.Model(mod_exp, independent_vars=['x']) params = lm.Parameters() params.add('A', value=0, vary=True) params.add('B', value=0, vary=True) params.add('C', value=1, min=0, vary=True) # fit result = model.fit(df_2[ycol].values, x=df_2.run_hours.values, params=params, weights=1 / ystd, scale_covar=False, method='least_squares', fit_kws={'loss': loss}) # plot # label for fit # label= (r'$\underline{y = A + B e^{-x / C}}$'+'\n\n'+ # rf'$A = {result.params["A"].value:0.2f}$'+'\n'+ # rf'$B = {result.params["B"].value:0.2f}$'+'\n'+ # rf'$C = {result.params["C"].value:0.2f}$'+'\n'+ # rf'$\chi^2_\mathrm{{red.}} = {result.redchi:0.2f}$'+'\n\n') label = (r'$\underline{y = A + B e^{-x / C}}$' + '\n\n' + rf'$A = {result.params["A"].value:0.3f}$' + rf'$\pm{result.params["A"].stderr:0.3f}$' + '\n' + rf'$B = {result.params["B"].value:0.3f}$' + rf'$\pm{result.params["B"].stderr:0.3f}$' + '\n' + rf'$C = {result.params["C"].value:0.3f}$' + rf'$\pm{result.params["C"].stderr:0.3f}$' + '\n' + rf'$\chi^2_\mathrm{{red.}} = {result.redchi:0.2f}$' + '\n\n') # set up figure with two axes config_plots() fig = plt.figure() ax1 = fig.add_axes((0.12, 0.33, 0.8, 0.58)) ax2 = fig.add_axes((0.12, 0.13, 0.8, 0.2)) # plot data and fit # data if ystd_sf == 1: label_data = 'Data' else: label_data = r'Data (error $\times$' + f'{ystd_sf})' ax1.errorbar(df_2.index.values, df_2[ycol].values, yerr=ystd_sf * ystd, fmt='o', ls='none', ms=2, zorder=100, label=label_data) # fit ax1.plot(df_2.index.values, result.best_fit, linewidth=2, color='red', zorder=99, label=label) # time constant x_ = df_2.index[0] + timedelta(hours=result.params["C"].value) ymin = np.min(df_2[ycol]) * 0.95 ymax = np.max(df_2[ycol]) * 1.02 ax1.plot([x_, x_], [ymin, ymax], '--', color='gray', zorder=101, label=rf'$C = {result.params["C"].value:0.3f}$ [Hours]') # calculate residual (data - fit) res = df_2[ycol].values - result.best_fit res_full = (df_[ycol].values - mod_exp(df_['run_hours'].values, **result.params)) df_['stable_temp_res'] = res_full # quick histogram for residuals #_ = res_full _ = df_['NMR [T]'].values mean_full = np.mean(_) std_full = np.std(_) nstd = 2 fig_h, ax_h = plt.subplots() n, bins, patches = ax_h.hist(_, bins=25, histtype='step', label='Measurements') #label='Fit Residuals') nm = np.max(n) ax_h.plot([mean_full - nstd * std_full, mean_full - nstd * std_full], [0, nm], 'r--', label=f'{nstd} RMS from mean') ax_h.plot([mean_full + nstd * std_full, mean_full + nstd * std_full], [0, nm], 'r--') # ax_h.set_xlabel(r'Fit Residuals [$^\circ$C]') ax_h.set_xlabel(r'NMR [T]') ax_h.set_ylabel('Count') ax_h.set_yscale('log') ax_h.legend() fig_h.suptitle( f'Temperature Stability Fit Residuals: Exponential Decay\n' + f'Run Index {run_num}, {loss.capitalize()} Loss') if not plotfile is None: fig_h.savefig(plotfile + '_res_hist.pdf') fig_h.savefig(plotfile + '_res_hist.png') # calculate ylimit for ax2 yl = 1.1 * (np.max(np.abs(res)) + ystd_sf * ystd[0]) # plot residual # zero-line xmin = np.min(df_2.index.values) xmax = np.max(df_2.index.values) ax2.plot([xmin, xmax], [0, 0], 'k--', linewidth=2, zorder=98) # residual ax2.errorbar(df_2.index.values, res, yerr=ystd_sf * ystd, fmt='o', ls='none', ms=2, zorder=99) # formatting # set ylimit ax2 ax2.set_ylim([-yl, yl]) # remove ticklabels for ax1 xaxis ax1.set_xticklabels([]) # axis labels ax2.set_xlabel('Datetime [2021 MM-DD hh:mm]') ax2.set_ylabel(r'(Data - Fit) [$^{\circ}$C]') ax1.set_ylabel(r'Yoke (center magnet) Temp. [$^{\circ}$C]') # force consistent x axis range for ax1 and ax2 #tmin = np.min(df_2.index.values) #tmax = np.max(df_2.index.values) #ax1.set_xlim([tmin, tmax]) #ax2.set_xlim([tmin, tmax]) # turn on legend ax1.legend().set_zorder(102) # add title fig.suptitle(f'Temperature Stability Fit: Exponential Decay\n' + f'Run Index {run_num}, {loss.capitalize()} Loss') # minor ticks ax1.yaxis.set_minor_locator(AutoMinorLocator()) ax2.yaxis.set_minor_locator(AutoMinorLocator()) # inward ticks and ticks on right and top ax1.tick_params(which='both', direction='in', top=True, right=True, bottom=True) # ax1.ticklabel_format(axis='y', useOffset=True) ax2.tick_params(which='both', direction='in', top=True, right=True) # tick label format consistent formatter = DateFormatter('%m-%d %H:%M') ax2.xaxis.set_major_formatter(formatter) # rotate label for dates ax2.xaxis.set_tick_params(rotation=15) # save figure #fig.tight_layout() if not plotfile is None: fig.savefig(plotfile + '.pdf') fig.savefig(plotfile + '.png') if remove_data: # remove first "time constant" of data hmax = df_.run_hours.max() tau = result.params["C"].value # special case (water chiller failed) -- only remove 1/2 time constant # if hmax/tau < 1: if hmax / tau < 1.5: # df_ = df_.query(f'run_hours > {tau/2}').copy() df_ = df_.query(f'run_hours > {tau/8}').copy() # else normal case -- remove one time constant else: df_ = df_.query(f'run_hours > {tau}').copy() # add run number to df df_['run_num'] = run_num return result, df_, fig, ax1, ax2
import numpy as np import pandas as pd import lmfit as lm from scipy.interpolate import interp1d import matplotlib.pyplot as plt from plotting import config_plots config_plots() # pickle file ddir = '/home/ckampa/data/hallprobecalib_extras/datafiles/magnet_ramp/' # FEMM data df_f0 = pd.read_csv(ddir+'gap75_B_vs_I_r0z0_0-300_results.txt', skiprows=8, names=['I','B'], delimiter=',') df_fp = pd.read_csv(ddir+'gap75_B_vs_I_r0z37.5_results.txt', skiprows=8, names=['I', 'B'], delimiter=',') df_f0 = df_f0.query('I <= 140').copy() df_f0.eval('I = I*2', inplace=True) df_fp = df_fp.query('I <= 140').copy() df_fp.eval('I = I*2', inplace=True) df_f0['B_ratio'] = df_f0.B / df_fp.B # FEMM df df_f = pd.read_csv(ddir+'gap75_B_vs_I_r0z0_0-300_results.txt', skiprows=8, names=['I','B'], delimiter=',') df_f = df_f.query('I <= 140').copy() df_f.eval('I = I*2', inplace=True) # df = pd.read_pickle('/home/ckampa/Desktop/slow_controls_current.pkl') df = pd.read_pickle(ddir+'ramp_2021-02-24_processed.pkl') probe = '6A0000000D61333A' # Is_set = np.array([224., 240.]) # testing equipment
def linear_temperature_regression(run_num, df, plotfile, xcol, ycol, ystd, ystd_sf, force_decreasing): # query preprocessed data to get run df_ = df.query(f'run_num == {run_num}').copy() # create an array for weights if float supplied if type(ystd) != np.ndarray: ystd = ystd*np.ones(len(df_)) # setup lmfit model # y = A + B * x model = lm.Model(mod_lin, independent_vars=['x']) params = lm.Parameters() params.add('A', value=0, vary=True) params.add('B', value=0, vary=True) params.add('X0', value=T0, vary=False) # fit result = model.fit(df_[ycol].values, x=df_[xcol].values, params=params, weights=1/ystd, scale_covar=False) # check if not decreasing and rerun if force_decreasing and (result.params['B'].value > 0.): params['B'].vary = False result = model.fit(df_[ycol].values, x=df_[xcol].values, params=params, weights=1/ystd, scale_covar=False) # label for fit bv_str = f'{result.params["B"].value:0.3E}' ind = bv_str.find('E') b0 = bv_str[:ind] bsf = int(bv_str[ind+1:]) be = f'{result.params["B"].stderr/10**bsf:0.3f}' label= (rf'$\underline{{y = A + B (x - {T0})}}$'+'\n'+ rf'$A = {result.params["A"].value:0.7f}$'+'\n'+ rf'$\pm {result.params["A"].stderr:0.7f}$'+'\n'+ rf'$B =$'+'\n'+rf'$({b0}\pm{be})$'+ rf'$\times 10^{{ {bsf} }}$'+' (fixed)\n'+ # rf'$B = {result.params["B"].value:0.1f}$'+ # rf'$\pm {result.params["B"].stderr:0.1f}$'+' (fixed)\n'+ rf'$\chi^2_\mathrm{{red.}} = {result.redchi:0.2f}$'+'\n') else: # label for fit bv_str = f'{result.params["B"].value:0.3E}' ind = bv_str.find('E') b0 = bv_str[:ind] bsf = int(bv_str[ind+1:]) be = f'{result.params["B"].stderr/10**bsf:0.3f}' label= (rf'$\underline{{y = A + B (x - {T0})}}$'+'\n'+ rf'$A = {result.params["A"].value:0.7f}$'+'\n'+ rf'$\pm {result.params["A"].stderr:0.7f}$'+'\n'+ rf'$B =$'+'\n'+rf'$({b0}\pm{be})$'+ rf'$\times 10^{{ {bsf} }}$'+'\n'+ # rf'$B = {result.params["B"].value:0.3E}$'+ # rf'$\pm {result.params["B"].stderr:0.3E}$'+'\n'+ rf'$\chi^2_\mathrm{{red.}} = {result.redchi:0.2f}$'+'\n') # plot # set up figure with two axes config_plots() fig = plt.figure() ax1 = fig.add_axes((0.12, 0.3, 0.7, 0.6)) ax2 = fig.add_axes((0.12, 0.08, 0.7, 0.185)) # colorbar axis cb_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7]) # plot data and fit # data # with errorbars if ystd_sf == 1: label_data = 'Data' else: label_data = r'Data (error $\times$'+f'{ystd_sf})' ax1.errorbar(df_[xcol].values, df_[ycol].values, yerr=ystd_sf*ystd, fmt='o', ls='none', ms=2, zorder=100, label=label_data) # scatter with color #sc = ax1.scatter(df_[xcol].values, df_[ycol].values, c=df_.index, s=1, # zorder=101) sc = ax1.scatter(df_[xcol].values, df_[ycol].values, c=df_.run_hours, s=1, zorder=101) # fit ax1.plot(df_[xcol].values, result.best_fit, linewidth=2, color='red', zorder=99, label=label) # calculate residual (data - fit) res = df_[ycol].values - result.best_fit # calculate ylimit for ax2 yl = 1.1*(np.max(np.abs(res)) + ystd_sf*ystd[0]) # plot residual # zero-line xmin = np.min(df_[xcol].values) xmax = np.max(df_[xcol].values) ax2.plot([xmin, xmax], [0, 0], 'k--', linewidth=2, zorder=98) # residual ax2.errorbar(df_[xcol].values, res, yerr=ystd_sf*ystd, fmt='o', ls='none', ms=2, zorder=99) #ax2.scatter(df_[xcol].values, res, c=df_.index, s=1, zorder=101) ax2.scatter(df_[xcol].values, res, c=df_.run_hours, s=1, zorder=101) # colorbar for ax1 cb = fig.colorbar(sc, cax=cb_ax) cb.set_label('Time [Hours]') ## WITH DATETIME #cb.set_t # print(f'vmin = {sc.colorbar.vmin}') # print(f'vmax = {sc.colorbar.vmax}') # # change colobar ticks labels and locators # cb.set_ticks([sc.colorbar.vmin + t*(sc.colorbar.vmax-sc.colorbar.vmin) # for t in cb.ax.get_yticks()]) # cbtls = [mdates.datetime.datetime.fromtimestamp((sc.colorbar.vmin + # t*(sc.colorbar.vmax-sc.colorbar.vmin))*1e-9).strftime('%c') # for t in cb.ax.get_yticks()] # cb.set_ticklabels(cbtls) #cb.ax.set_yticklabels(df_.index.strftime('%m-%d %H:%M')) # formatting # kludge for NMR or Hall if ycol == 'NMR [T]': ylabel1 = r'$|B|_\mathrm{NMR}$ [T]' title_prefix = 'NMR' else: ylabel1 = r'$|B|_\mathrm{Hall}$ [T]' title_prefix = 'Hall Probe' # set ylimit ax2 ax2.set_ylim([-yl, yl]) # remove ticklabels for ax1 xaxis ax1.set_xticklabels([]) # axis labels ax2.set_xlabel(r'Yoke (center magnet) Temp. [$^{\circ}$C]') ax2.set_ylabel('(Data - Fit) [T]') ax1.set_ylabel(ylabel1) # force consistent x axis range for ax1 and ax2 tmin = np.min(df_[xcol].values) tmax = np.max(df_[xcol].values) range_t = tmax-tmin ax1.set_xlim([tmin-0.1*range_t, tmax+0.1*range_t]) ax2.set_xlim([tmin-0.1*range_t, tmax+0.1*range_t]) # turn on legend ax1.legend().set_zorder(102) # add title fig.suptitle(f'{title_prefix} vs. Temperature Regression: Linear Model\n'+ f'Run Index {run_num}') # minor ticks ax1.xaxis.set_minor_locator(AutoMinorLocator()) ax2.xaxis.set_minor_locator(AutoMinorLocator()) ax1.yaxis.set_minor_locator(AutoMinorLocator()) ax2.yaxis.set_minor_locator(AutoMinorLocator()) # inward ticks and ticks on right and top ax1.tick_params(which='both', direction='in', top=True, right=True, bottom=True) ax2.tick_params(which='both', direction='in', top=True, right=True) # save figure #fig.tight_layout() fig.savefig(plotfile+'.pdf') fig.savefig(plotfile+'.png') return result, fig, ax1, ax2