def multifit_std_err_scale_analysis(folder, file): # fig, axes = plt.subplots(2, 1, sharex='all', sharey='all') magopter = Magopter(folder, file, ts_filename=ts_file) magopter.prepare(down_sampling_rate=1, roi_b_plasma=True, crit_freq=4000, crit_ampl=None) index = int(0.5 * len(magopter.iv_arrs[0])) scales = np.arange(0.5, 1.2, 0.05) chis = np.zeros_like(scales) plt.figure() for i, fitter in enumerate([f.SimpleIVFitter(), f.FullIVFitter()]): raw_iv_data = magopter.iv_arrs[0][index].copy() sigma = raw_iv_data[c.SIGMA] for j, scaler in enumerate(scales): raw_iv_data[c.SIGMA] = sigma * scaler fitdata = raw_iv_data.multi_fit(iv_fitter=fitter) print('Sigma * {}'.format(scaler)) fitdata.print_fit_params() chis[j] = fitdata.reduced_chi2 plt.plot(scales, chis, label=fitter.name) plt.axhline(y=1.0, color='black', linewidth=1) plt.xlabel('$\delta I$ ($\sigma$)') plt.ylabel(r'Reduced $\chi^2$') plt.legend()
def analyse_small_probe(ds_full, probe_designation, output_tag, sweep_range): # Select the small probe ds_full = ds_full.sel(probe=probe_designation) manual_start = sweep_range[0] manual_end = sweep_range[1] plt.figure() ds_full.max(dim='time').mean('direction')['current'].plot.line(x='sweep') ds_full.max(dim='time').mean('direction').isel(sweep=slice(manual_start, manual_end))['current'].plot.line( x='sweep') plt.savefig(f'{output_tag}_shot.png', bbox_inches='tight') # Choose only the IVs in the static section ds_full = ds_full.isel(sweep=slice(manual_start, manual_end)) # Average across each sweep direction sweep_avg_up = ds_full.sel(direction='up').mean('sweep') sweep_avg_dn = ds_full.sel(direction='down').mean('sweep') # Add in standard deviation of each bin as a new data variable sweep_avg_up = sweep_avg_up.assign({'d_current': ds_full.sel(direction='up').std('sweep')['current']}) sweep_avg_dn = sweep_avg_dn.assign({'d_current': ds_full.sel(direction='down').std('sweep')['current']}) print(ds_full) sweep_avg_updn = ds_full.mean('direction').mean('sweep').assign( {'d_current': ds_full.std('direction').std('sweep')['current']}) sweep_avg_updn = sweep_avg_updn.where(sweep_avg_updn.current <= 0, drop=True) print(sweep_avg_updn) # sweep_avg_updn['current'].plot.line() # concatenate the up and down sweeps together to cancel the (small) capacitance effect iv_data = ivd.IVData(sweep_avg_updn['voltage'].data, -sweep_avg_updn['current'].data, sweep_avg_updn['time'].data, sigma=sweep_avg_updn['d_current'].data, estimate_error_fl=False) starting_params = [0.69, 0.009, 1.12, 1] full_iv_fitter = fts.FullIVFitter() fit_data = full_iv_fitter.fit_iv_data(iv_data, initial_vals=starting_params) fig = plt.figure() fit_data.plot(fig=fig, show_fl=False) # plt.errorbar(fit_data.raw_x, fit_data.raw_y, yerr=iv_data['sigma'], ecolor='silver') # plt.plot(fit_data.raw_x, fit_data.fit_y, color='orange', label=r'') plt.plot(iv_data['V'], full_iv_fitter.fit_function(iv_data['V'], *starting_params), label='Start-param IV') plt.legend() plt.ylim([-.25, 2.0]) plt.tight_layout() plt.savefig(f'{output_tag}_fit.png', bbox_inches='tight') del sweep_avg_up, sweep_avg_dn, sweep_avg_updn import gc gc.collect()
def fit_by_upper_index(iv_data_ds, upper_index, ax=None, multi_fit_fl=False, plot_fl=True): """ This has been superceded by the IVData method fit_to_minimum() """ import flopter.core.fitters as f # Determine which way round the sweep goes if iv_data_ds['voltage'].values[0] < iv_data_ds['voltage'].values[-1]: iv_data_trimmed_ds = iv_data_ds.isel(time=slice(0, upper_index)) else: iv_data_trimmed_ds = iv_data_ds.isel(time=slice(iv_data_ds.time.size - upper_index, -1)) if plot_fl: if ax is None: fig, ax = plt.subplots() else: fig = ax.figure fig.suptitle(f'upper_index = {upper_index}') ax.errorbar(iv_data_trimmed_ds['voltage'].values, -iv_data_trimmed_ds['current'].values, yerr=iv_data_trimmed_ds['d_current'].values, linestyle='none', color='k', ecolor='k', label='Sweep-averaged IV', zorder=2) # Plot the whole IV in an inset axis inner_ax = plt.axes([0.2, 0.35, .2, .2]) (-iv_data_ds.set_coords('voltage')['current']).plot(x='voltage', ax=inner_ax) inner_ax.axvline(x=iv_data_trimmed_ds.max('time')['voltage'].values, **c.AX_LINE_DEFAULTS) inner_ax.set_title('Whole IV') inner_ax.set_xlabel('V') inner_ax.set_ylabel('I') inner_ax.set_xticks([]) inner_ax.set_yticks([]) shot_iv = iv.IVData(iv_data_trimmed_ds['voltage'].values, -iv_data_trimmed_ds['current'].values, iv_data_trimmed_ds['shot_time'].values, sigma=iv_data_trimmed_ds['stderr_current'].values) # shot_iv = iv.IVData(iv_data_ds['voltage'].values, # -iv_data_ds['current'].values, # iv_data_ds['shot_time'].values, # sigma=iv_data_ds['stderr_current'].values) if multi_fit_fl: shot_fit = shot_iv.multi_fit(sat_region=-52) else: fitter = f.FullIVFitter() shot_fit = fitter.fit_iv_data(shot_iv, sigma=shot_iv['sigma']) if plot_fl: chi_2_str = r"$\chi^2_{red}$" ax.plot(*shot_fit.get_fit_plottables(), label=f'Fit - T_e={shot_fit.get_temp():.3g}, {chi_2_str} = {shot_fit.reduced_chi2:.3g}') ax.legend() return shot_fit
def straight_iv_fit(iv_data, cutoff=-2, all_initial_values=None): if all_initial_values is None: all_initial_values = DEFAULT_INITIAL_PARAMS fitter = fts.FullIVFitter() if cutoff is None: cutoff = fts.IVFitter.find_floating_pot(iv_data['V'][:-1], iv_data['I'][:-1]) iv_region = np.where(iv_data['V'] <= cutoff) fit_iv_data = iv.IVData.non_contiguous_trim(iv_data, iv_region) initial_vals = fitter.generate_guess(**all_initial_values) try: fit_data = fitter.fit_iv_data(fit_iv_data, sigma=fit_iv_data['sigma'], initial_vals=initial_vals) except ValueError: print('CAUGHT: straight fit error') fit_data = get_dummy_fit(fit_iv_data, fitter) return fit_data
def multi_iv_fit(iv_data, sat_region=-6, show_err_fl=True, all_initial_values=None, **kwargs): if all_initial_values is None: all_initial_values = DEFAULT_INITIAL_PARAMS if 'iv_fitter' in kwargs: fitter = kwargs['iv_fitter'] else: fitter = fts.FullIVFitter() if 'stage_2_guess' not in kwargs: initial_vals = fitter.generate_guess(**all_initial_values) else: initial_vals = kwargs.pop('stage_2_guess') try: fit_data = iv_data.multi_fit(sat_region=sat_region, stage_2_guess=initial_vals, **kwargs) except ValueError as e: print('CAUGHT: multi fit error') if show_err_fl: print(e) fit_data = get_dummy_fit(iv_data, fitter) return fit_data
def averaged_iv_analysis(filename=None, ts_temp=None, ts_dens=None, shunt_resistance=10.0, theta_perp=10.0, probe_designations=PROBE_DESIGNATIONS, sweep_range=SWEEP_RANGE, downsamplnig_factor=1): if filename is None: # folders = ['2019-05-28_Leland/', '2019-05-29_Leland/'] mg.Magoptoffline._FOLDER_STRUCTURE = '/Data/external/magnum/' files = [] file_folders = [] for folder1 in FOLDERS: os.chdir(mg.Magoptoffline.get_data_path() + folder1) files.extend(glob.glob('*.adc')) file_folders.extend([folder1] * len(glob.glob('*.adc'))) files.sort() # for i, f in enumerate(files): # print(i, f) # file = files[286] # adc_file = files[285] # ts_file = files[284] adc_file = files[-1] folder = FOLDERS[-1] else: # If using the tkinter file chooser adc_file = filename.split('/')[-1] folder = filename.split('/')[-2] + '/' mg.Magoptoffline._FOLDER_STRUCTURE = '/Data/external/magnum/' print('"{}" \t\t "{}"'.format(folder, adc_file)) mp = lp.MagnumProbes() probe_S = mp.probe_s probe_B = mp.probe_b dsr = downsamplnig_factor # Create magopter object print('Creating magopter object') magopter = mg.Magoptoffline(folder, adc_file, shunt_resistor=shunt_resistance, cabling_resistance=2) magopter._VOLTAGE_CHANNEL = 3 magopter._PROBE_CHANNEL_3 = 4 magopter._PROBE_CHANNEL_4 = 5 magopter.prepare(down_sampling_rate=dsr, roi_b_plasma=True, filter_arcs_fl=False, crit_freq=None, crit_ampl=None) print('0: {}, 1: {}'.format(len(magopter.iv_arrs[0]), len(magopter.iv_arrs[1]))) if ts_dens is not None and ts_temp is not None: T_e_ts = ts_temp d_T_e_ts = ts_temp * 0.01 n_e_ts = ts_dens d_n_e_ts = ts_dens * 0.01 else: T_e_ts = 1.12 d_T_e_ts = 0.01 n_e_ts = 4.44e20 d_n_e_ts = 0.01e20 # print length of the 0th probes (probe S) number of sweeps print(len(magopter.iv_arrs[1])) # Create relative t array by subtracting the first timestep value from the first time array first_time_arr = magopter.iv_arrs[1][0]['t'] relative_t = np.zeros(len(first_time_arr)) sweep_length = np.shape(relative_t)[0] // 2 print('Sweep length is {}'.format(sweep_length)) relative_t = first_time_arr - first_time_arr[0] # create a list of datasets for each sweep ds_probes = [] for i in range(len(magopter.iv_arrs)): ds_list = [] for j, iv in enumerate(magopter.iv_arrs[i]): if j % 2 == 0: ds = xr.Dataset( { 'voltage': (['time'], iv['V'][:sweep_length]), 'current': (['time'], iv['I'][:sweep_length]), 'shot_time': (['time'], iv['t'][:sweep_length]), 'start_time': iv['t'][0] }, coords={ 'time': relative_t[:sweep_length], 'direction': 'up', 'probe': probe_designations[i] }) else: ds = xr.Dataset( { 'voltage': (['time'], np.flip(iv['V'][:sweep_length])), 'current': (['time'], np.flip(iv['I'][:sweep_length])), 'shot_time': (['time'], np.flip(iv['t'][:sweep_length])), 'start_time': iv['t'][0] }, coords={ 'time': relative_t[:sweep_length], 'direction': 'down', 'probe': probe_designations[i] }) ds_list.append(ds) # # Separate into up and down sweeps then concat along sweep direction as an axis print('Before equalisation: ', len(ds_list), len(ds_list[::2]), len(ds_list[1::2])) if len(ds_list[::2]) == len(ds_list[1::2]) + 1: ds_ups = xr.concat(ds_list[:-2:2], 'sweep') else: ds_ups = xr.concat(ds_list[::2], 'sweep') ds_downs = xr.concat(ds_list[1::2], 'sweep') print('After equalisation: ', len(ds_ups['sweep']), len(ds_downs['sweep'])) direction = xr.DataArray(np.array(['up', 'down']), dims=['direction'], name='direction') ds_probes.append(xr.concat([ds_ups, ds_downs], dim=direction)) probe = xr.DataArray(np.array(probe_designations), dims=['probe'], name='probe') ds_full = xr.concat(ds_probes, dim=probe) # Select the small probe ds_full = ds_full.sel(probe=probe_designations[0]) manual_start = sweep_range[0] manual_end = sweep_range[1] plt.figure() ds_full.max(dim='time').mean('direction')['current'].plot.line(x='sweep') ds_full.max(dim='time').mean('direction').isel( sweep=slice(manual_start, manual_end))['current'].plot.line(x='sweep') # Choose only the IVs in the static section ds_full = ds_full.isel(sweep=slice(manual_start, manual_end)) # Average across each sweep direction sweep_avg_up = ds_full.sel(direction='up').mean('sweep') sweep_avg_dn = ds_full.sel(direction='down').mean('sweep') # Add in standard deviation of each bin as a new data variable sweep_avg_up = sweep_avg_up.assign( {'d_current': ds_full.sel(direction='up').std('sweep')['current']}) sweep_avg_dn = sweep_avg_dn.assign( {'d_current': ds_full.sel(direction='down').std('sweep')['current']}) print(ds_full) sweep_avg_updn = ds_full.mean('direction').mean('sweep').assign( {'d_current': ds_full.std('direction').std('sweep')['current']}) sweep_avg_updn = sweep_avg_updn.where(sweep_avg_updn.current <= 0, drop=True) print(sweep_avg_updn) # sweep_avg_updn['current'].plot.line() # concatenate the up and down sweeps together to cancel the (small) capacitance effect iv_data = ivd.IVData(sweep_avg_updn['voltage'].data, -sweep_avg_updn['current'].data, sweep_avg_updn['time'].data, sigma=sweep_avg_updn['d_current'].data, estimate_error_fl=False) starting_params = [0.69, 0.009, 1.12, +1] full_iv_fitter = fts.FullIVFitter() fit_data = full_iv_fitter.fit_iv_data(iv_data, initial_vals=starting_params) fig = plt.figure() fit_data.plot(fig=fig, show_fl=False) # plt.errorbar(fit_data.raw_x, fit_data.raw_y, yerr=iv_data['sigma'], ecolor='silver') # plt.plot(fit_data.raw_x, fit_data.fit_y, color='orange', label=r'') plt.plot(iv_data['V'], full_iv_fitter.fit_function(iv_data['V'], *starting_params), label='Start-param IV') plt.legend() plt.ylim([-.25, 1]) # Create new averaged iv figure theta_perp = np.radians(theta_perp) # probe_selected = probe_L A_coll_0 = probe_S.get_collection_area(theta_perp) d_A_coll = np.abs( probe_S.get_collection_area(theta_perp + np.radians(0.8)) - A_coll_0) v_f_fitted = fit_data.get_param('V_f') d_v_f_fitted = fit_data.get_param('V_f', errors_fl=True).error v_f_approx = -3 * fit_data.get_temp() d_v_f_approx = 0.05 * v_f_approx v_f_approx_ts = -3 * T_e_ts d_v_f_approx_ts = 0.05 * v_f_approx_ts c_s_fitted = lp.sound_speed(fit_data.get_temp(), gamma_i=1) d_c_s_fitted = lp.d_sound_speed(c_s_fitted, fit_data.get_temp(), fit_data.get_temp(errors_fl=True).error) n_e_fitted = lp.electron_density(fit_data.get_isat(), c_s_fitted, A_coll_0) d_n_e_fitted = lp.d_electron_density( n_e_fitted, c_s_fitted, d_c_s_fitted, A_coll_0, d_A_coll, fit_data.get_isat(), fit_data.get_isat(errors_fl=True).error) print("iv = averaged: \n" "\t v_f = {:.3g} +- {:.1g} \n" "\t T_e = {:.3g} +- {:.1g} \n" "\t I_sat = {:.3g} +- {:.1g} \n" "\t n_e = {:.3g} +- {:.1g} \n" "\t a = {:.3g} +- {:.1g} \n" "\t c_s = {:.3g} +- {:.1g} \n" "\t A_coll = {:.3g} +- {:.1g} \n".format( v_f_fitted, d_v_f_fitted, fit_data.get_temp(), fit_data.get_temp(errors_fl=True).error, fit_data.get_isat(), fit_data.get_isat(errors_fl=True).error, n_e_fitted, d_n_e_fitted, fit_data.get_sheath_exp(), fit_data.get_sheath_exp(errors_fl=True).error, c_s_fitted, d_c_s_fitted, A_coll_0, d_A_coll)) I_f = probe_S.get_analytical_iv(fit_data.raw_x, v_f_fitted, theta_perp, fit_data.get_temp(), n_e_fitted, print_fl=True) I_ts = probe_S.get_analytical_iv(fit_data.raw_x, v_f_approx_ts, theta_perp, T_e_ts, n_e_ts, print_fl=True) plt.figure() plt.errorbar(fit_data.raw_x, fit_data.raw_y, yerr=fit_data.sigma, label='Raw IV', ecolor='silver', color='gray', zorder=-1) # plt.plot(iv_data[c.RAW_X].tolist()[0], I_f, label='Analytical - measured', linestyle='dashed', linewidth=1, color='r') plt.plot(fit_data.raw_x, fit_data.fit_y, color='blue', linewidth=1.2, label='Fit - ({:.2g}eV, {:.2g}m'.format(fit_data.get_temp(), n_e_fitted) + r'$^{-3}$)') plt.plot(fit_data.raw_x, I_ts, linestyle='dashed', color='red', label='Analytical from TS - ({:.2g}eV, {:.2g}m'.format( T_e_ts, n_e_ts) + '$^{-3}$)') plt.legend() # plt.title('Comparison of analytical to measured IV curves for the small area probe') plt.xlabel(r'$V_p$ / V') plt.ylabel(r'$I$ / A') # plt.ylim([-0.01, 3.2]) plt.show()
def fit_magnum_ds(magnum_subset_ds, probes=('L', 'S', 'B', 'R'), ax=True, scan_param='tilt', scan_selection=None, threshold='auto', fitter=None, print_fl=False, temp_from_phi_fl=True, multi_fit_fl=True, mass=1, Z=1, **kwargs): if fitter is None: fitter = fts.FullIVFitter() if scan_selection is not None: magnum_subset_ds = magnum_subset_ds.sel(scan_selection) metadata_labels = [ scan_param, 'probe', 'B', 'ts_temp', 'ts_dens', 'fit_success_fl', ] fit_param_labels = [ 'temp', 'd_temp', 'isat', 'd_isat', 'a', 'd_a', 'v_f', 'd_v_f', 'dens', 'd_dens', 'chi2', 'reduced_chi2' ] phi_labels = [ 'phi', 'temp_phi', 'temp_fit_phi' ] if temp_from_phi_fl: fit_param_labels += phi_labels all_labels = metadata_labels + fit_param_labels fit_df = pd.DataFrame(columns=all_labels) for scan_param_value in magnum_subset_ds[scan_param].values: scan_param_ds = magnum_subset_ds.sel(**{scan_param: scan_param_value}) if print_fl: print(f'\n\n -- ({scan_param_ds.shot_number.values}) Running fit on {scan_param} with {scan_param_value} -- \n') for probe in probes: probe_paramscan_ds = scan_param_ds.sel(probe=probe) probe_paramscan_ds = probe_paramscan_ds.where(np.isfinite(probe_paramscan_ds['voltage']), drop=True) probe_paramscan_ds = probe_paramscan_ds.where(np.isfinite(probe_paramscan_ds['current']), drop=True) if threshold == 'auto': # Auto option finds the first point the other side of the floating potential and then selects up to that threshold = 1 if isinstance(threshold, int): iv_indices = np.where(probe_paramscan_ds.current < 0)[0] if iv_indices[0] == 0: extreme_index = max(iv_indices) extension = [extreme_index + (i + 1) for i in range(threshold)] ext_iv_indices = np.concatenate((iv_indices, extension)) else: extreme_index = min(iv_indices) extension = [extreme_index - (threshold - i) for i in range(threshold)] ext_iv_indices = np.concatenate((extension, iv_indices)) probe_paramscan_ds = probe_paramscan_ds.isel(time=ext_iv_indices) elif isinstance(threshold, float): probe_paramscan_ds = probe_paramscan_ds.where(probe_paramscan_ds.current < threshold, drop=True) elif print_fl: print('No threshold set, continuing with full sweep.') if scan_param == 'tilt': tilt = scan_param_value else: tilt = np.array(probe_paramscan_ds['tilt'].values, ndmin=1)[0] alpha = np.radians(tilt) if len(probe_paramscan_ds.time) == 0: if print_fl: print('Time has no length, continuing...') continue shot_iv = iv.IVData(probe_paramscan_ds['voltage'].values, -probe_paramscan_ds['current'].values, probe_paramscan_ds['shot_time'].values, sigma=probe_paramscan_ds['d_current'].values) # Generate a guess for a 4-parameter IV characteristic temp_guess = probe_paramscan_ds['ts_temperature'].mean().values dens_guess = probe_paramscan_ds['ts_density'].mean().values a_guess = magnum_probes[probe].get_sheath_exp_param(temp_guess, dens_guess, alpha) isat_guess = probe_paramscan_ds['current'].min('time').values v_f_guess = fitter.find_floating_pot_iv_data(shot_iv) initial_params = fitter.generate_guess( temperature=temp_guess, floating_potential=v_f_guess, isat=isat_guess, sheath_exp_param=a_guess ) if print_fl: print(f"First guess initial_params: {[temp_guess, dens_guess, isat_guess, v_f_guess]}") print(f"Generated initial_params: {initial_params}") try: if multi_fit_fl: shot_fit = shot_iv.multi_fit(print_fl=print_fl, stage_2_guess=initial_params, iv_fitter=fitter, **kwargs) else: shot_fit = fitter.fit_iv_data(shot_iv, sigma=shot_iv['sigma'], initial_vals=initial_params) dens = magnum_probes[probe].get_density(shot_fit.get_isat(), shot_fit.get_temp(), alpha=alpha, mass=mass, Z=Z) d_dens = magnum_probes[probe].get_d_density( shot_fit.get_isat(), shot_fit.get_isat_err(), shot_fit.get_temp(), shot_fit.get_temp_err(), alpha=alpha, mass=mass, Z=Z, ) if isinstance(dens, collections.Iterable): dens = dens[0] d_dens = d_dens[0] fit_params = { 'fit_success_fl': True, 'temp': shot_fit.get_temp(), 'd_temp': shot_fit.get_temp_err(), 'isat': shot_fit.get_isat(), 'd_isat': shot_fit.get_isat_err(), 'a': shot_fit.get_sheath_exp(), 'd_a': shot_fit.get_sheath_exp_err(), 'v_f': shot_fit.get_floating_pot(), 'd_v_f': shot_fit.get_floating_pot_err(), 'dens': dens, 'd_dens': d_dens, 'chi2': shot_fit.chi2, 'reduced_chi2': shot_fit.reduced_chi2, } if temp_from_phi_fl: phi = shot_iv.estimate_phi() temp_phi = lpu.estimate_temperature(shot_iv.get_vf(), phi) temp_fit_phi = lpu.estimate_temperature(shot_fit.get_floating_pot(), phi) fit_params.update({ 'phi': phi, 'temp_phi': temp_phi, 'temp_fit_phi': temp_fit_phi }) if ax is not None: if ax is True: fig, ax = plt.subplots() ax.errorbar(shot_iv['V'], shot_iv['I'], yerr=shot_iv['sigma'], ecolor='silver', color='silver', marker='+', zorder=1) ax.plot(*shot_fit.get_fit_plottables()) except RuntimeError as e: print(f'WARNING: Failed on {scan_param}={scan_param_value} with probe {probe}') print(e) fit_params = {label: np.NaN for label in fit_param_labels} fit_params['fit_success_fl'] = False fit_df = fit_df.append({ scan_param: scan_param_value, 'probe': probe, 'B': np.around(probe_paramscan_ds['shot_b_field'].mean().values, decimals=1), 'ts_temp': probe_paramscan_ds['ts_temperature'].max().values, 'ts_dens': probe_paramscan_ds['ts_density'].max().values, **fit_params, }, ignore_index=True) return fit_df
def fit(self, fitter=None, initial_vals=None, bounds=None, load_fl=False, save_fl=False, print_fl=False): if load_fl and save_fl: print( 'WARNING: Unnecessary to save and load at the same time - loading will be prioritised if successful.' ) # Looks for csv files containing previously fitted data if asked for by the load_fl boolean flag. fit_files = [ self._FIT_FILE_STRING.format(i, self.timestamp) for i in range(self.coaxes) ] if load_fl: start_dir = os.getcwd() os.chdir('{}{}{}'.format(pth.Path.home(), self._FOLDER_STRUCTURE, self.directory)) directory_fit_files = glob.glob(self._FIT_FILE_GLOBSTR) if set(fit_files).issubset(directory_fit_files): return [pd.read_csv(filepath_or_buffer=ff) for ff in fit_files] else: print( 'Could not find fit files - they should be in {} with names of the format {}' .format(self.directory, fit_files[0])) print('Continuing with regular fit...') os.chdir(start_dir) # Fitting routine if not fitter: fitter = f.FullIVFitter() if all(iv_arr is None or len(iv_arr) == 0 for iv_arr in self.iv_arrs): raise ValueError('No iv_data found to fit in self.iv_arrs') # pool = mp.Pool() fit_arrs = [[] for dummy in range(self.coaxes)] fit_time = [[] for dummy in range(self.coaxes)] for i in range(self.coaxes): for iv_data in self.iv_arrs[i]: try: # Parallelised using multiprocessing.pool # TODO: Not currently working according to system monitor. fit_data = iv_data.multi_fit(plot_fl=False) # result = pool.apply_async(iv_data.multi_fit) # fit_data = result.get(timeout=10) except RuntimeError: if print_fl: print('Error encountered in fit, skipping timestep {}'. format(np.mean(iv_data.time))) continue if all(param.error >= (param.value * 0.5) for param in fit_data.fit_params): if print_fl: print( 'All fit parameters exceeded good fit voltage_threshold, skipping time step {}' .format(np.mean(iv_data[c.TIME]))) continue fit_arrs[i].append(fit_data) fit_time[i].append(np.mean(iv_data[c.TIME])) fit_dfs = [ pd.DataFrame([fit_data.to_dict() for fit_data in fit_arrs[i]], index=fit_time[i]) for i in range(self.coaxes) ] if save_fl: for i in range(self.coaxes): fit_dfs[i].to_csv( path_or_buf='{}{}{}{}'.format(pth.Path.home( ), self._FOLDER_STRUCTURE, self.directory, fit_files[i])) return fit_dfs
def multifit_trim_filter_analysis(probe_0, folder, file): # noinspection PyTypeChecker fig, ax = plt.subplots(2, 1, sharex=True, sharey=True) fitter = f.FullIVFitter() for i, freq in enumerate([None, 4000]): magopter = Magopter(folder, file, ts_filename=ts_file) magopter.prepare(down_sampling_rate=1, roi_b_plasma=True, crit_freq=freq, crit_ampl=None) index = int(0.5 * len(magopter.iv_arrs[0])) iv_data = magopter.iv_arrs[0][index] fitdata = iv_data.multi_fit(iv_fitter=fitter) iv_data.trim_beg = 0.01 iv_data.trim_end = 0.45 fitdata_1 = iv_data.multi_fit(iv_fitter=fitter) print('{}:{}'.format(iv_data.trim_beg, iv_data.trim_end)) fitdata_1.print_fit_params() # fitdata_1_fvf = iv_data.multi_fit(plot_fl=False, fix_vf_fl=True) untrimmed_x = iv_data[c.POTENTIAL] untrimmed_y = iv_data[c.CURRENT] custom_params = [1.08, 0.006, 4.30, -15.1] # Plot the raw signal and the untrimmed fit plt.sca(ax[i]) plt.title('Critical Frequency is {}'.format('{}Hz'.format(freq) if freq is not None else 'not set')) plt.errorbar(untrimmed_x, untrimmed_y, fmt='.', yerr=iv_data[c.SIGMA], label='Raw', color='silver', zorder=-1) plt.plot(untrimmed_x, fitdata.fit_function(untrimmed_x), label='Fit - No Trim', color='green', zorder=10) # Plot the comparion between fixed vf and free vf plt.plot(untrimmed_x, fitdata_1.fit_function(untrimmed_x), color='red', linewidth=1, label=r'T_e = {:.3g}, $\chi^2$ = {:.3g}'.format(fitdata_1.get_temp().value, fitdata_1.reduced_chi2)) plt.axvline(x=np.max(fitdata_1.raw_x), label='Trim Min/Max', color='red', linestyle='dashed', linewidth=1) plt.axvline(x=np.min(fitdata_1.raw_x), color='red', linestyle='dashed', linewidth=1) # plt.plot(untrimmed_x, fitdata_1_fvf.fit_function(untrimmed_x), label=r'Fixed $V_f$ Fit - {}:{}' # .format(iv_data.trim_end, iv_data.trim_beg), color='red', linestyle='-.') # # Plot the raw signal and the untrimmed fit # plt.figure() # plt.errorbar(untrimmed_x, untrimmed_y, fmt='.', yerr=iv_data[c.SIGMA], label='Raw', color='silver', zorder=-1) # plt.plot(untrimmed_x, fitdata.fit_function(untrimmed_x), label='Fit - No Trim', color='green') # Trim and plot again iv_data.trim_beg = -0.05 iv_data.trim_end = 0.45 fitdata_2 = iv_data.multi_fit(iv_fitter=fitter) print('{}:{}'.format(iv_data.trim_beg, iv_data.trim_end)) fitdata_2.print_fit_params() # fitdata_2_fvf = iv_data.multi_fit(fix_vf_fl=True) plt.plot(untrimmed_x, fitdata_2.fit_function(untrimmed_x), color='blue', linewidth=1, label=r'T_e = {:.3g}, $\chi^2$ = {:.3g}'.format(fitdata_2.get_temp().value, fitdata_2.reduced_chi2)) plt.axvline(x=np.max(fitdata_2.raw_x), label='Trim Min/Max', color='blue', linestyle='dashed', linewidth=1) plt.axvline(x=np.min(fitdata_2.raw_x), color='blue', linestyle='dashed', linewidth=1) # plt.plot(untrimmed_x, fitdata_0160_fvf.fit_function(untrimmed_x), label=r'Fixed $V_f$ Fit - {}:{}' # .format(iv_data.trim_end, iv_data.trim_beg), color='blue', linestyle='-.') # Trim and plot again iv_data.trim_beg = -0.1 iv_data.trim_end = 0.45 fitdata_3 = iv_data.multi_fit(iv_fitter=fitter) print('{}:{}'.format(iv_data.trim_beg, iv_data.trim_end)) fitdata_3.print_fit_params() # fitdata_3_fvf = iv_data.multi_fit(fix_vf_fl=True) plt.plot(untrimmed_x, fitdata_3.fit_function(untrimmed_x), color='orange', linewidth=1, label=r'T_e = {:.3g}, $\chi^2$ = {:.3g}'.format(fitdata_3.get_temp().value, fitdata_3.reduced_chi2)) plt.axvline(x=np.max(fitdata_3.raw_x), label='Trim Min/Max', color='orange', linestyle='dashed', linewidth=1) plt.axvline(x=np.min(fitdata_3.raw_x), color='orange', linestyle='dashed', linewidth=1) # Plot the custom fit and an axis line through 0 # plt.plot(untrimmed_x, f.FullIVFitter().fit_function(untrimmed_x, *custom_params), label='Custom Fit {}' # .format(', '.join([str(i) for i in custom_params]))) plt.axhline(color='black', linewidth=1) plt.ylim(-1.1, 1.6) plt.xlabel('Voltage (V)') plt.ylabel('Current (A)') plt.legend()
def fit_to_minimum(self, initial_vals=None, fitter=None, trimming_vals=None, mode=0, plot_fl=False, print_fl=False): """ A fitting function which minimises the fitted temperature of the IV characteristic by varying how many values around the floating potential are included in the fit. The default values for distance around the floating potential are 0.2 and 0.1 towards the plasma potential and ion saturation region respectively. As of now these are hard coded, but will be configurable in future updates. :param fitter: (IVFitter) Fitter object used to perform the fit. The default is FullIVFitter :param initial_vals: (tuple) The initial parameters for the fit. Default's to the fitter's preset starting params if left as None. :param trimming_vals: (tuple) The trimming fractions used to control the range of values minimised over. Each value should be the distance from the floating potential as a fraction of the total IV characteristic.The first value determines the upper limit (>V_f), the second value determines the lower limit (<V_f) and the third value determines how big the steps should be. Default values are (0.3, 0.3, 0.02), which for an IV of length 50 would give ~31 total points (15 above V_f, 15 points below V_f, and V_f itself). :param mode: (int) Choice of minimisation parameter. Choices currently implemented: > 0 = T_e · dT_e · (|chi² - 1| + 1) > 1 = T_e · dT_e · |chi² - 1| > 2 = |chi² - 1| + 1 > 3 = T_e · dT_e > 4 = T_e > 5 = |T_e - 1| Prototyping for these parameters was created in a jupyter notebook, see that for more details. :param plot_fl: (Boolean) Flag to control whether the process is plotted while running. Current configuration is to plot the full IV, overlaid with each of the trimmed IVs, and below that a separate plot of temperature as a function of upper trim voltage. :param print_fl: (Boolean) Flag to control whether information is printed to terminal during the fitting process. :return: (IVFitData) FitData object for lowest temperature fit performed. """ import flopter.core.fitters as f if fitter is None or not isinstance(fitter, f.IVFitter): fitter = f.FullIVFitter() vf_index = self.get_vf_index() v_f = self['V'][vf_index] # Select how far from the floating potential we want to iterate if trimming_vals is None: trimming_vals = self.MINFIT_TRIM_VALS upper_dist_frac, lower_dist_frac, step_frac = trimming_vals trim_range_updist = int(upper_dist_frac * len(self['t'])) trim_range_dndist = int(lower_dist_frac * len(self['t'])) trim_range_step = max(int(step_frac * len(self['t'])), 1) # Set trim range depending on what direction our data is in. if self['V'][0] < self['V'][-1]: trim_range = np.arange( max(vf_index - trim_range_dndist, 0), min(vf_index + trim_range_updist, len(self['t'])), trim_range_step) else: trim_range = np.arange( max(vf_index - trim_range_updist, 0), min(vf_index + trim_range_dndist, len(self['t'])), trim_range_step) ax_big = None if plot_fl: height_ratios = [2.5, 1, 1, 1, 1, 1] fig = plt.figure(constrained_layout=True) gs = fig.add_gridspec(ncols=2, nrows=6, height_ratios=height_ratios) ax = [] ax_big = fig.add_subplot(gs[0, :]) # ax_big.errorbar('voltage', 'current', yerr='stderr_current', data=sweep_avg_ds) self.plot(ax=ax_big) ax_big.axvline(x=v_f, **c.AX_LINE_DEFAULTS, label=r'$V_f$') ax_big.axvline(x=self['V'][max(trim_range)], linewidth=0.7, color='black', label='trim min/max') ax_big.axvline(x=self['V'][min(trim_range)], linewidth=0.7, color='black') ax_big.legend() for row in range(1, 6): ax_left = fig.add_subplot(gs[row, 0]) ax_right = fig.add_subplot(gs[row, 1]) ax.append([ax_left, ax_right]) # Fit for each value of trim around the floating potential trimmed_fits = [] temps = np.array([]) d_temps = np.array([]) isats = np.array([]) d_isats = np.array([]) sheathexps = np.array([]) d_sheathexps = np.array([]) chis = np.array([]) volts = np.array([]) for i in trim_range: # Trim is defined as: from the 0th element to the ith element, or from the ith element to the last element # depending on the direction of sweep. if self['V'][0] < self['V'][-1]: trim_iv = self.upper_trim(i) else: trim_iv = self.lower_trim(i) if plot_fl: trim_iv.plot(ax=ax_big, zorder=i) try: trim_fit = fitter.fit_iv_data(trim_iv, sigma=trim_iv['sigma'], initial_vals=initial_vals) for fp in trim_fit.fit_params: error_ratio = fp.error / fp.value if error_ratio > self.MINFIT_ERR_RATIO: raise RuntimeError( f'Fit produced a parameter with an unacceptable error ratio ({error_ratio})' ) trimmed_fits.append(trim_fit) # Append values relevant to minimisation param to their respective arrays volts = np.append(volts, np.max(trim_fit.raw_x)) temps = np.append(temps, trim_fit.get_temp()) d_temps = np.append(d_temps, trim_fit.get_temp_err()) isats = np.append(isats, trim_fit.get_isat()) d_isats = np.append(d_isats, trim_fit.get_isat_err()) chis = np.append(chis, trim_fit.reduced_chi2) # Check if sheath expansion parameter is present and append zeros if not. This would happen if using a # 3-parameter fit. if c.SHEATH_EXP in trim_fit: sheathexps = np.append(sheathexps, trim_fit.get_sheath_exp()) d_sheathexps = np.append(d_sheathexps, trim_fit.get_sheath_exp_err()) else: sheathexps = np.append(sheathexps, 0.0) d_sheathexps = np.append(d_sheathexps, 0.0) except RuntimeError as e: if print_fl: print(f'Temp-minimisation fit failed on index {i}\n:' f'{e}') if len(temps) == 0: raise RuntimeError( 'All temperature minimisation fits failed. Try using different trimming_vals.' ) temp_param = temps * d_temps chi_param = np.abs(chis - 1) + 1 alt_chi_param = np.abs(chis - 1) goodness_param = temp_param * chi_param alt_goodness_param = temp_param * alt_chi_param alt_temp_param = np.abs(temps - 1) # Get the indices for the minimum values for each of the parameters minimisable_params = [ goodness_param, alt_goodness_param, chi_param, temp_param, temps, alt_temp_param, ] if plot_fl: t_minv = volts[np.argmin(temps)] t_dt_minv = volts[np.argmin(temp_param)] chi_param_minv = volts[np.argmin(chi_param)] alt_chi_param_minv = volts[np.argmin(alt_chi_param)] alt_goodness_minv = volts[np.argmin(alt_goodness_param)] goodness_minv = volts[np.argmin(goodness_param)] t_param_minv = volts[np.argmin(alt_temp_param)] chi2_str = r'$\chi^{2}_{\nu}$' temp_str = r'$T_e$' d_temp_str = r'$\Delta T_e$' isat_str = r'$I_{sat}$' a_str = r'$a$' chi_param_str = r'$|\chi^{2}_{\nu} - 1| + 1$' alt_chi_param_str = r'$|\chi^{2}_{\nu} - 1|$' d_temps_ratio_str = r'$\frac{\Delta T_e}{T_e}$' alt_temp_param_str = r'$|T_e - 1|$' ax[0][0].errorbar(volts, temps, yerr=d_temps) ax[0][0].plot(volts, alt_temp_param, label=alt_temp_param_str) ax[0][0].set_ylabel(temp_str) ax[0][0].axvline(x=t_minv, color='blue', linestyle='--', label='T min') ax[0][0].axvline( x=t_dt_minv, color='purple', linestyle='--', ) ax[0][0].axvline( x=chi_param_minv, color='green', linestyle='--', ) ax[0][0].axvline( x=goodness_minv, color='red', linestyle='--', ) ax[0][0].axvline(x=t_param_minv, color='pink', linestyle=':', label=alt_temp_param_str) ax[0][1].plot(volts, chis, label=chi2_str) ax[0][1].axhline(y=1, **c.AX_LINE_DEFAULTS) ax[0][1].set_yscale('log') ax[0][1].set_ylabel(chi2_str) ax[0][1].axvline( x=t_minv, color='blue', linestyle='--', ) ax[0][1].axvline( x=t_dt_minv, color='purple', linestyle='--', ) ax[0][1].axvline( x=chi_param_minv, color='green', linestyle='--', ) ax[0][1].axvline( x=goodness_minv, color='red', linestyle='--', ) ax[1][0].errorbar(volts, isats, yerr=d_isats, label=isat_str) ax[1][0].set_ylabel(isat_str) ax[1][1].errorbar(volts, sheathexps, yerr=d_sheathexps, label='a') ax[1][1].set_ylabel(a_str) ax[2][0].plot(volts, temp_param) ax[2][0].set_ylabel(temp_str + r'$\cdot$' + d_temp_str) ax[2][0].set_yscale('log') ax[2][0].axvline(x=t_dt_minv, color='purple', linestyle='--', label=r'$T \cdot \Delta T$') ax[2][1].plot(volts, d_temps / temps) ax[2][1].set_ylabel(d_temps_ratio_str) ax[2][1].set_yscale('log') ax[2][1].axhline(y=self.MINFIT_ERR_RATIO, color='black', linewidth=0.75, label='Threshold') ax[3][0].plot(volts, chi_param) ax[3][0].set_yscale('log') ax[3][0].set_ylabel(chi_param_str) ax[3][0].axvline(x=chi_param_minv, color='green', linestyle='--', label=chi_param_str) ax[3][1].plot(volts, alt_chi_param) ax[3][1].set_yscale('log') ax[3][1].set_ylabel(alt_chi_param_str) ax[3][1].axvline(x=alt_chi_param_minv, color='green', linestyle='--', label=alt_chi_param_str) ax[4][0].plot(volts, goodness_param) ax[4][0].set_yscale('log') ax[4][0].set_ylabel( r'$T_e \cdot \Delta T_e \cdot (|\chi^{2}_{\nu} - 1| + 1)$') ax[4][0].axvline(x=goodness_minv, color='red', linestyle='--', label='goodness') ax[4][1].plot(volts, alt_goodness_param) ax[4][1].set_yscale('log') ax[4][1].set_ylabel( r'$T_e \cdot \Delta T_e \cdot |\chi^{2}_{\nu} - 1|$') ax[4][1].axvline(x=alt_goodness_minv, color='gold', linestyle='--', label='alt_goodness') for col in ax: for axis in col: axis.axvline(x=v_f, **c.AX_LINE_DEFAULTS) axis.legend() minimised_param_index = int( np.argmin(minimisable_params[mode], axis=None)) if plot_fl: ax_big.axvline(x=volts[np.argmin(minimisable_params[mode])], linewidth=0.7, color='red', linestyle=':', label='Chosen') return trimmed_fits[minimised_param_index]
def multi_fit(self, sat_region=_DEFAULT_STRAIGHT_CUTOFF, stage_2_guess=None, iv_fitter=None, sat_fitter=None, fix_vf_fl=False, plot_fl=False, print_fl=False, minimise_temp_fl=True, trim_to_floating_fl=True, **kwargs): """ Multi-stage fitting method using an initial straight line fit to the saturation region of the IV curve (decided by the sat_region kwarg). The fitted I_sat is then left fixed while T_e and a are found with a 2-parameter fit, which gives the guess parameters for an unconstrained full IV fit. :param sat_region: (Integer) Threshold voltage value below which the 'Straight section' is defined. The straight section is fitted to get an initial value of saturation current for subsequent fits. :param sat_fitter: Fitter object to be used for the initial saturation region fit :param stage_2_guess: Tuple-like containing starting values for all parameters in iv_fitter, to be used as initial parameters in the second stage fit. Note that only the temperature (and optionally sheath expansion parameter) will be used, as the isat will already have a value (by design) and the floating potential is set at the interpolated value. These will be overwritten if present. Default behaviour (stage_2_guess=None) is to use the defaults on the iv_fitter object. :param iv_fitter: Fitter object to be used for the fixed-I_sat fit and the final, full, free fit. :param plot_fl: (Boolean) If true, plots the output of all 3 stages of fitting. Default is False. :param fix_vf_fl: (Boolean) If true, fixes the floating potential for all 3 stages of fitting. The value used is the interpolated value of Voltage where Current = 0. Default is False. :param print_fl: (Boolean) If true, prints fitter information to console. :param minimise_temp_fl: (Boolean) Flag to control whether multiple final fits are performed at a range of upper indices past the floating potential, with the fit producing the lowest temperature returned. :param trim_to_floating_fl: (Boolean) Flag to control whether to truncate the IV characteristic to values strictly below the floating potential before fitting. This is ignored by the minimisation routine, so the full IV will be used in that case. :return: (IVFitData) Full 4-parameter IVFitData object """ import flopter.core.fitters as f if iv_fitter is None or not isinstance(iv_fitter, f.IVFitter): iv_fitter = f.FullIVFitter() if not isinstance(sat_fitter, f.GenericCurveFitter): if sat_fitter is not None and print_fl: print( 'Provided sat_fitter is not a valid child of GenericCurveFitter, continuing with the default \n' 'straight line fitter.') sat_fitter = f.StraightLineFitter() if print_fl: print(f'Running saturation region fit with {sat_fitter.name}, \n' f'running subsequent IV fits with {iv_fitter.name}') # find floating potential and max potential v_f = f.IVFitter.find_floating_pot_iv_data(self) if trim_to_floating_fl: iv_data_trim = self.get_below_floating(v_f=v_f, print_fl=print_fl) else: iv_data_trim = self.copy() # Find and fit straight section str_sec = np.where(iv_data_trim[c.POTENTIAL] <= sat_region) iv_data_ss = IVData.non_contiguous_trim(iv_data_trim, str_sec) if fix_vf_fl and c.FLOAT_POT in sat_fitter: sat_fitter.set_fixed_values({c.FLOAT_POT: v_f}) # Attempt first stage fit try: stage1_f_data = sat_fitter.fit(iv_data_ss[c.POTENTIAL], iv_data_ss[c.CURRENT], sigma=iv_data_ss[c.SIGMA]) except RuntimeError as e: raise MultiFitError(f'RuntimeError occured in stage 1. \n' f'Original error: {e}') # Use I_sat value to fit a fixed_value 4-parameter IV fit if c.ION_SAT in sat_fitter: isat_guess = stage1_f_data.get_isat() else: isat_guess = stage1_f_data.fit_function(sat_region) if fix_vf_fl: iv_fitter.set_fixed_values({ c.FLOAT_POT: v_f, c.ION_SAT: isat_guess }) else: iv_fitter.set_fixed_values({c.ION_SAT: isat_guess}) if stage_2_guess is None: stage_2_guess = list(iv_fitter.default_values) elif len(stage_2_guess) != len(iv_fitter.default_values): raise ValueError( f'stage_2_guess is not of the appropriate length ({len(stage_2_guess)}) for use as initial ' f'parameters in the given iv_fitter (should be length {len(iv_fitter.default_values)}).' ) stage_2_guess[iv_fitter.get_isat_index()] = isat_guess stage_2_guess[iv_fitter.get_vf_index()] = v_f # Attempt second stage fit try: stage2_f_data = iv_fitter.fit_iv_data(iv_data_trim, sigma=iv_data_trim[c.SIGMA], initial_vals=stage_2_guess) except RuntimeError as e: raise MultiFitError(f'RuntimeError occured in stage 2. \n' f'Original error: {e}') # Do a full 4 parameter fit with initial guess params taken from previous fit params = stage2_f_data.fit_params.get_values() iv_fitter.unset_fixed_values() if fix_vf_fl: iv_fitter.set_fixed_values({c.FLOAT_POT: v_f}) # Attempt third stage fit. Option to fit to multiple values past the floating potential to minimise the # temperature of the fit or just to the floating potential. try: if minimise_temp_fl: stage3_f_data = self.fit_to_minimum(initial_vals=params, fitter=iv_fitter, plot_fl=plot_fl, **kwargs) else: stage3_f_data = iv_fitter.fit_iv_data( iv_data_trim, initial_vals=params, sigma=iv_data_trim[c.SIGMA]) except RuntimeError as e: raise MultiFitError(f'RuntimeError occured in stage 3. \n' f'Original error: {e}') if plot_fl: fig, ax = plt.subplots(3, sharex=True, sharey=True) stage1_f_data.plot(ax=ax[0]) ax[0].set_xlabel('') ax[0].set_ylabel('Current (A)') stage2_f_data.plot(ax=ax[1]) ax[1].set_xlabel('') ax[1].set_ylabel('Current (A)') stage3_f_data.plot(ax=ax[2]) ax[2].set_xlabel('Voltage (V)') ax[2].set_ylabel('Current (A)') fig.suptitle('lower_offset = {}, upper_offset = {}'.format( self.trim_beg, self.trim_end)) return stage3_f_data
(flopter.core.constants.ELEM_CHARGE * n_e)) c_s = np.sqrt( (flopter.core.constants.ELEM_CHARGE * (T_e + gamma_i * T_i)) / m_i) a = ((c_1 + (c_2 / np.tan(np.radians(alpha)))) / np.sqrt(np.sin(np.radians(alpha)))) * (lambda_D / (L + g)) I_0 = n_e * flopter.core.constants.ELEM_CHARGE * c_s * A_coll J_0 = I_0 / A_coll q_par = 8 * T_e * J_0 print('Heat flux: {}'.format(q_par)) # I_0 = n_e * nrm.ELEM_CHARGE * c_s * np.sin(np.radians(alpha)) * A_probe isats_def.append(I_0) I = I_0 * (1 + (a * np.float_power(np.abs(V), .75)) - np.exp(-V)) currents[alpha] = I fitter = f.FullIVFitter() temps_t = [] temps_err_t = [] isats_t = [] isats_err_t = [] count = 0 for i in range(num_samples): noisey = (0.09 * (np.random.rand(len(I)) - 0.5)) + I try: fit_data = fitter.fit(V_range, noisey) except RuntimeError: count += 1 continue if i == 0 and alpha == 10.5 and v_max == 100 and plot_fl: # plt.figure() # plt.plot(V_range, I, label='Analytical')