Source code for mlfompy.extraction_methods

"""
=========================
Extraction methods module
=========================

Functions to import and parser data to MLFoMpyDataset"""
import numpy as np
from scipy import interpolate
import matplotlib.pyplot as plt

from . import auxiliar as aux


[docs] def threshold_voltage_sd_method(fds, interpolation_points=1000): """Extracts the threshold voltage using the second derivative (SD) method: https://doi.org/10.1016/j.microrel.2012.09.015 Parameters ---------- fds: MLFoMpyDataset interpolation_points: int, fixed to 1000 Number of interpolation points required """ if fds.iv_curve_dd: vth = [] for i in range(len(fds.iv_curve_dd)): try: if len(fds.iv_curve_dd) == 1: v_gate, i_drain = fds.iv_curve_dd[i][:,0], fds.iv_curve_dd[i][:,1] fds.iv_dd_sanity.append(True) else: v_gate, i_drain = aux.iv_curve_dd_filter(fds, i) if fds.iv_dd_sanity[i] and len(i_drain) > 4: # 4 is min number of points needed to the spline quartic interpolation quartic_interpol, x_interp, delta_x_interp = aux.iv_interpolation(fds, v_gate, i_drain, interpolation_points) fd = quartic_interpol.derivative(n=1) sd = quartic_interpol.derivative(n=2) # n is the derivative order upper_limit = x_interp[np.argmax(fd(x_interp))] x_filter = x_interp[np.where(x_interp<upper_limit)] vth_sd = round(x_interp[np.argmax(sd(x_filter))],4) ### Plotting extraction method for debugging # plt.plot(x_interp, quartic_interpol(x_interp)) # plt.plot(x_interp, fd(x_interp)/5) # plt.plot(x_interp[np.argmax(fd(x_interp))],np.max(fd(x_interp)/5),'yo') # plt.plot(x_interp,sd(x_interp)/10) # plt.plot(x_filter, sd(x_filter)/10,'o--') # plt.plot(vth_sd, sd(vth_sd)/10,'d',ms=15) vth.append(vth_sd) # plt.legend(['I-V','FD','FD max','SD','SD filter','SD filter max']) # plt.show() else: vth.append(np.nan) except Exception as e: vth.append(np.nan) aux.print_error(f'Simulation {i+1}:\n{e}') # Storing into fds.figure_of_merit if not 'vth' in fds.figure_of_merit: fds.figure_of_merit['vth'] = {} fds.figure_of_merit['vth']['SD'] = { 'method':f'SD', 'units':'V', 'values':vth} if len(fds.iv_curve_dd) > 1: fds.figure_of_merit['vth']['SD']['is_anomalous'] = aux.check_anomalous_data(fom=vth) std_vth, mean_vth = round(np.nanstd(fds.figure_of_merit['vth']['SD']['values']),4), round(np.nanmean(fds.figure_of_merit['vth']['SD']['values']),4) fds.figure_of_merit['vth']['SD']['stats'] = { 'stdev':std_vth, 'mean':mean_vth, 'units':'V'} else: aux.print_warning(f'[{__name__}.threshold_voltage_sd_method] No Drift-diffusion data ')
[docs] def threshold_voltage_le_method(fds, interpolation_points=10000): """Extracts the threshold voltage using the linear extraction method https://doi.org/10.1016/j.microrel.2012.09.015 Parameters ---------- fds: MLFoMpyDataset interpolation_points: int, fixed to 1000 Number of interpolation points required """ if fds.iv_curve_dd: vth = [] for i in range(len(fds.iv_curve_dd)): try: if len(fds.iv_curve_dd) == 1: # For one curve problem with sanity condition v_gate, i_drain = fds.iv_curve_dd[i][:,0], fds.iv_curve_dd[i][:,1] fds.iv_dd_sanity.append(True) else: v_gate, i_drain = aux.iv_curve_dd_filter(fds, i) if fds.iv_dd_sanity[i] and len(i_drain) > 4: # 4 is min number of points needed to the spline quartic interpolation quartic_interpol, x_interp, delta_x_interp = aux.iv_interpolation(fds, v_gate, i_drain, interpolation_points) first_derivative = quartic_interpol.derivative(n=1) # n is the derivative order idx = x_interp[np.argmax(first_derivative(x_interp))] m_tan = first_derivative(idx) vth_le = idx-quartic_interpol(idx)/m_tan ### Plotting extraction method for debugging # plt.plot(x_interp, quartic_interpol(x_interp), '-') # plt.plot(x_interp, first_derivative(x_interp)/2, 'g-') # plt.plot(x_interp, m_tan*(x_interp-vth_le), 'r-') # plt.plot(vth_le,0,'o') # plt.legend(['I-V','FD','LE','vth_le']) # plt.axhline(0) # plt.show() icc = quartic_interpol(vth_le) if fds.drain_bias_value < 0.5: vth.append(round(vth_le+fds.drain_bias_value/2,4)) else: vth.append(round(vth_le, 4)) else: vth.append(np.nan) # aux.print_warning(f'[{__name__}.threshold_voltage_le_method] Simulation nº{i+1}: Not enough points to make a quartic interpolation for simulation nº{i+1}') except Exception as e: vth.append(np.nan) aux.print_error(f'Simulation nº{i+1}: {e}') # Storing into fds.figure_of_merit if not 'vth' in fds.figure_of_merit: fds.figure_of_merit['vth'] = {} fds.figure_of_merit['vth']['LE'] = { 'method':f'LE', 'units':'V', 'values':vth} if len(fds.iv_curve_dd) > 1: fds.figure_of_merit['vth']['LE']['is_anomalous'] = aux.check_anomalous_data(fom=vth) std_vth, mean_vth = round(np.nanstd(fds.figure_of_merit['vth']['LE']['values']),4), round(np.nanmean(fds.figure_of_merit['vth']['LE']['values']),4) fds.figure_of_merit['vth']['LE']['stats'] = { 'stdev':std_vth, 'mean':mean_vth, 'units':'V'} else: aux.print_warning(f'[{__name__}.threshold_voltage_le_method] No Drift-diffusion data ')
[docs] def threshold_voltage_cc_method(fds, cc_criteria, interpolation_points=10000): """Extracts the threshold voltage using the constant current (CC) method: https://doi.org/10.1016/j.microrel.2012.09.015 Parameters ---------- fds: MLFoMpyDataset cc_criteria: float Consant current criteria chosen in [A] interpolation_points: int, fixed to 1000 Number of interpolation points required """ if fds.iv_curve_dd: vth = [] for i in range(len(fds.iv_curve_dd)): try: if len(fds.iv_curve_dd) == 1: # For one curve problem with sanity condition v_gate, i_drain = fds.iv_curve_dd[i][:,0], fds.iv_curve_dd[i][:,1] fds.iv_dd_sanity.append(True) else: v_gate, i_drain = aux.iv_curve_dd_filter(fds, i) if fds.iv_dd_sanity[i] and len(i_drain) > 3: # 3 is min number of points needed to the spline cubic interpolation x_interp, delta_x_interp = np.linspace(v_gate[0], v_gate[-1], interpolation_points, retstep=True) cubic_interpol = interpolate.UnivariateSpline(v_gate, i_drain-cc_criteria, s=0, k=3) vth_cc = cubic_interpol.roots() if vth_cc: vth.append(round(vth_cc[0],4)) else: vth.append(np.nan) aux.print_warning(f'[{__name__}.threshold_voltage_cc_method] Simulation nº{i+1}: No intersection between constant current and I-V curve\n\t i) Bad choice of constant current criteria\n\t ii) The complete I-V curve wasn simulated') else: vth.append(np.nan) except Exception as e: aux.print_error(f'Simulation nº{fds+1}: {e}') aux.print_error(f'Simulation nº{fds+1}: {e}') # Storing into fds.figure_of_merit if not 'vth' in fds.figure_of_merit: fds.figure_of_merit['vth'] = {} fds.figure_of_merit['vth']['CC'] = { 'method':f'CC Icc={cc_criteria} A', 'units':'V', 'values': vth} if len(fds.iv_curve_dd) > 1: fds.figure_of_merit['vth']['CC']['is_anomalous'] = aux.check_anomalous_data(fom=vth) std_vth, mean_vth = round(np.nanstd(fds.figure_of_merit['vth']['CC']['values']),4), round(np.nanmean(fds.figure_of_merit['vth']['CC']['values']),4) fds.figure_of_merit['vth']['CC']['stats'] = { 'stdev':std_vth, 'mean':mean_vth, 'units':'V'} else: aux.print_warning(f'[{__name__}.threshold_voltage_cc_method] No Drift-diffusion data ')
[docs] def threshold_voltage(fds, method=None, cc_criteria=None): """Extracts the threshold voltage using the desired method: https://doi.org/10.1016/j.microrel.2012.09.015 Parameters ---------- fds: MLFoMpyDataset method: str Method accepted values: ['SD','LE','CC'] cc_criteria: float Consant current criteria chosen in [A] """ if method == 'SD' or method is None: aux.print_title('Extracting Vth with SD method') threshold_voltage_sd_method(fds) elif method == 'LE': aux.print_title('Extracting Vth with LE method') threshold_voltage_le_method(fds) elif method == 'CC': aux.print_title('Extracting Vth with CC method') threshold_voltage_cc_method(fds, cc_criteria)
[docs] def off_current(fds, vg_ext): """Extracts the off current at a fixed gate potential (vg_ext) Parameters ---------- fds: MLFoMpyDataset vg_ext: float Gate potential chosen to extract the off current """ aux.print_title('Extracting Ioff') if fds.iv_curve_dd: ioff = [] for i in range(len(fds.iv_curve_dd)): try: if len(fds.iv_curve_dd) == 1: # For one curve problem with sanity condition v_gate, i_drain = fds.iv_curve_dd[i][:,0], fds.iv_curve_dd[i][:,1] fds.iv_dd_sanity.append(True) else: v_gate, i_drain = aux.iv_curve_dd_filter(fds, i) if vg_ext == 0.0: t_ioff = i_drain[0] ioff.append(t_ioff) else: if len(i_drain) > 3: # 3 is min number of points needed to the spline cubic interpolation cubic_interpol = interpolate.UnivariateSpline(v_gate, i_drain, s=0, k=3) ioff.append(float(cubic_interpol(vg_ext))) else: ioff.append(np.nan) except Exception as e: ioff.append(np.nan) aux.print_error(f'Simulation {i+1}: {e}') # Storing into fds.figure_of_merit fds.figure_of_merit['ioff'] = {} fds.figure_of_merit['ioff']['VG'] = { 'method':f'VG Vg={vg_ext} V', 'units': 'A', 'values':ioff} if len(fds.iv_curve_dd) > 1: fds.figure_of_merit['ioff']['VG']['is_anomalous'] = aux.check_anomalous_data(fom=ioff) std_ioff, mean_ioff = round(np.nanstd(np.log10(fds.figure_of_merit['ioff']['VG']['values'])),2), round(np.nanmean(np.log10(fds.figure_of_merit['ioff']['VG']['values'])),2) fds.figure_of_merit['ioff']['VG']['stats'] = { 'stdev':std_ioff, 'mean':mean_ioff, 'units': 'log10A',} else: aux.print_warning(f'[{__name__}.off_current] No Drift-diffusion data ')
[docs] def subthreshold_slope(fds, vg_start=None, vg_end=None): """Extracts the subthreshold slope in the linear region defined between vg_start and vg_end. By default vg_start is fixed to the first gate potential and vg_end to vth_sd/2 Parameters ---------- fds: MLFoMpyDataset vg_start: float First gate potential chosen to extract the subthreshold slope vg_end: float Last gate potential chosen to extract the subthreshold slope """ aux.print_title('Extracting SS') if fds.iv_curve_dd: ss = [] threshold_voltage_sd_method(fds) for i in range(len(fds.iv_curve_dd)): try: if len(fds.iv_curve_dd) == 1: # For one curve problem with sanity condition v_gate, i_drain = fds.iv_curve_dd[i][:,0], fds.iv_curve_dd[i][:,1] fds.iv_dd_sanity.append(True) else: v_gate, i_drain = aux.iv_curve_dd_filter(fds, i) if fds.iv_dd_sanity[i]: cubic_interpol = interpolate.UnivariateSpline(v_gate, i_drain, s=0, k=3) start = vg_start if vg_start else v_gate[0] end = vg_end if vg_end else fds.figure_of_merit['vth']['SD']['values'][i]/2 t_ss = (end-start)*1000/(np.log10(cubic_interpol(end))-np.log10(cubic_interpol(start))) ss.append(round(t_ss,2)) else: ss.append(np.nan) except Exception as e: ss.append(np.nan) aux.print_error(f'Simulation {i+1}:{e}') # Storing into fds.figure_of_merit fds.figure_of_merit['ss'] = {} fds.figure_of_merit['ss']['VGI'] = { 'method':f'VG interval Vg_start={start}, Vg_end={end} V', 'units': 'mV/dec', 'values': ss} if len(fds.iv_curve_dd) > 1: fds.figure_of_merit['ss']['VGI']['is_anomalous'] = aux.check_anomalous_data(fom=ss) std_ss, mean_ss = round(np.nanstd(fds.figure_of_merit['ss']['VGI']['values']),2), round(np.nanmean(fds.figure_of_merit['ss']['VGI']['values']),2) fds.figure_of_merit['ss']['VGI']['stats'] = { 'stdev':std_ss, 'mean':mean_ss, 'units': 'mV/dec',} else: aux.print_warning(f'[{__name__}.subthreshold_slope] No Drift-diffusion data ')
[docs] def dd_on_current(fds, vg_ext): """Extracts the on current at a fixed gate potential (vg_ext) Parameters ---------- fds: MLFoMpyDataset vg_ext: float Gate potential chosen to extract the on current """ aux.print_title('Extracting DD Ion') if fds.iv_curve_dd: ion = [] for i in range(len(fds.iv_curve_dd)): try: if len(fds.iv_curve_dd) == 1: # For one curve problem with sanity condition v_gate, i_drain = fds.iv_curve_dd[i][:,0], fds.iv_curve_dd[i][:,1] fds.iv_dd_sanity.append(True) else: v_gate, i_drain = aux.iv_curve_dd_filter(fds, i) if fds.iv_dd_sanity[i] and vg_ext <= v_gate[-1]: cubic_interpol = interpolate.UnivariateSpline(v_gate, i_drain, s=0, k=3) ion.append(float(cubic_interpol(vg_ext))) else: ion.append(np.nan) aux.print_warning(f'[{__name__}.dd_on_current] Simulation nº{i+1}: No I value at vg_ext as it is higher than last vg of the simulated I-V curve') except Exception as e: ion.append(np.nan) aux.print_error(f'Simulation {i+1}: {e}') # Storing into fds.figure_of_merit fds.figure_of_merit['ion_dd'] = {} fds.figure_of_merit['ion_dd']['VG'] = { 'method':f'VG Vg={vg_ext} V', 'units': 'A', 'values':ion} if len(fds.iv_curve_dd) > 1: fds.figure_of_merit['ion_dd']['VG']['is_anomalous'] = aux.check_anomalous_data(fom=ion) std_ion, mean_ion = np.nanstd(fds.figure_of_merit['ion_dd']['VG']['values']), np.nanmean(fds.figure_of_merit['ion_dd']['VG']['values']) fds.figure_of_merit['ion_dd']['VG']['stats'] = { 'stdev':std_ion, 'mean':mean_ion, 'units': 'A',} else: aux.print_warning(f'[{__name__}.dd_on_current] No Drift-diffusion data ')
[docs] def mc_on_current(fds): """Extracts the on current from a MC output Parameters ---------- fds: MLFoMpyDataset """ aux.print_title('Extracting MC Ion') if fds.iv_point_mc: ion = [] for i in range(len(fds.iv_point_mc)): try: if fds.iv_mc_sanity[i]: ion.append(fds.iv_point_mc[i][1]) else: ion.append(np.nan) except Exception as e: ion.append(np.nan) aux.print_error(f'Simulation {i+1}:{e}') # Storing into fds.figure_of_merit fds.figure_of_merit['ion_mc'] = {} fds.figure_of_merit['ion_mc']['VG'] = { 'method':f'VG Vg={fds.iv_point_mc[0][0]} V', 'units': 'A', 'values': ion} fds.figure_of_merit['ion_mc']['VG']['is_anomalous'] = aux.check_anomalous_data(fom=ion) std_ion, mean_ion = np.nanstd(fds.figure_of_merit['ion_mc']['VG']['values']), np.nanmean(fds.figure_of_merit['ion_mc']['VG']['values']) fds.figure_of_merit['ion_mc']['VG']['stats'] = { 'stdev':std_ion, 'mean':mean_ion, 'units': 'A'} else: aux.print_warning(f'[{__name__}.mc_on_current] No Monte Carlo data ')