def find_flatBand_voltage( self, plot, data, configs, indexMax, indexMin, df, cv_files, voltage_value_of_max_firstder, **addConfigs ): # cv is the list containing all the cvmos files """ Finds the full depletion voltage of all data samples and adds a vertical line for the full depletion in the plot. Vertical line is the mean of all measurements. Furthermore, adds a text with the statistics. :param plot: The plot object :param data: The data files :param configs: the configs :param **addConfigs: the configs special for the 1/C2 plot, it is recommended to pass the same options here again, like in the original plot! :return: The updated plot """ # global Right_stats, fit_stats self.log.info("Searching for flat band voltage in all files...") sample = deepcopy(data[df]) # Create a new data frame with just two columns try: df1 = pd.DataFrame( { "xaxis": sample["data"]["voltage"], "yaxis": sample["data"]["CapacityCopy"], } ) except Exception: df1 = pd.DataFrame( { "xaxis": sample["data"]["Voltage"], "yaxis": sample["data"]["CapacityCopy"], } ) df1 = df1.dropna() # Loop one time from the right side, to get the slope of the accumulation region, and then loop on the fit region to get the fit slope RR2 = 0 fitR2 = 0 for idx in range(5, len(df1) - 5): # Right slope_right, intercept_right, r_right, p_value, std_err_right = linregress( df1["xaxis"][idx:], df1["yaxis"][idx:] ) r2_right = r_right * r_right self.log.debug( "Right side fit: Slope {}, intercept: {}, r^2: {}, std: {}".format( slope_right, intercept_right, r2_right, std_err_right ) ) # See if the r2 value has increased and store it if r2_right >= RR2: RR2 = r2_right RightEndPoints = ( ( df1["xaxis"][idx], slope_right * df1["xaxis"][idx] + intercept_right, ), ( df1["xaxis"][len(df1["xaxis"]) - 1], slope_right * df1["xaxis"][len(df1["xaxis"]) - 1] + intercept_right, ), ) Right_stats = [ RightEndPoints, slope_right, intercept_right, r_right, p_value, std_err_right, ] # Fit central region for idx in range(indexMax, indexMin - 1): # Do central fit slope_fit, intercept_fit, r_fit, p_valuefit, std_err_fit = linregress( df1["xaxis"][idx : indexMin - 1], df1["yaxis"][idx : indexMin - 1] ) r2_fit = r_fit * r_fit self.log.debug( "central fit: Slope {}, intercept: {}, r^2: {}, std: {}".format( slope_fit, intercept_fit, r2_fit, std_err_fit ) ) # See if the r2 value has increased and store it if r2_fit >= fitR2: fitR2 = r2_fit fitEndPoints = ( ( df1["xaxis"][indexMax], slope_fit * df1["xaxis"][indexMax] + intercept_fit, ), ( df1["xaxis"][idx + 1], slope_fit * df1["xaxis"][idx + 1] + intercept_fit, ), # use idx +1 to avoid having the same end points ) fit_stats = [ fitEndPoints, slope_fit, intercept_fit, r_fit, p_valuefit, std_err_fit, ] # Add central slope xmax = df1["xaxis"][indexMin] fit_line = np.array( [ [ df1["xaxis"][indexMax - 3], fit_stats[1] * df1["xaxis"][indexMax - 3] + fit_stats[2], ], [xmax + 0.2, fit_stats[1] * (xmax + 0.2) + fit_stats[2]], ] ) # Add right slope xmax = df1["xaxis"][len(df1["yaxis"]) - 1] right_line = np.array( [ [ df1["xaxis"][indexMax - 3], Right_stats[1] * df1["xaxis"][indexMax - 3] + Right_stats[2], ], [xmax, Right_stats[1] * xmax + Right_stats[2]], ] ) # Compute the flatband voltage flatband_voltage = line_intersection(fit_stats[0], Right_stats[0]) self.log.info( "Flatband voltage to data file {} is {}".format(df, flatband_voltage[0]) ) # Find oxide thickness Tox in nm Accum_capacitance = np.max(df1["yaxis"]) * ( float(self.data[df]["header"][0].split(":")[1]) * (1e-8) ) # float(..) is the area. Accum_capacitance_table = "{:.2e}".format(Accum_capacitance) Accum_capacitance_normalized = np.max(df1["yaxis"]) # F/cm^2 Accum_capacitance_normalized_table = "{:.2e}".format( Accum_capacitance_normalized ) Tox = ( self.config["IV_PQC_parameter"]["epsilonNull"] * self.config["IV_PQC_parameter"]["epsilonSiliconOxide"] * 1e5 / Accum_capacitance_normalized ) Tox_table = "{:.2e}".format(Tox) # Find Fixed oxide charge Nox in cm^-2 phi_s = ( self.config["IV_PQC_parameter"]["electronAffinity"] + self.config["IV_PQC_parameter"]["bandGapEnergy"] / 2 + ( self.config["IV_PQC_parameter"]["boltzmannConstant"] * self.config["IV_PQC_parameter"]["Temperature"] * np.log( self.config["IV_PQC_parameter"]["SiliconDoping"] / self.config["IV_PQC_parameter"]["intrinsicDopingConcentration"] ) ) / self.config["IV_PQC_parameter"]["q"] ) phi_ms = self.config["IV_PQC_parameter"]["phi_m"] - phi_s Nox = (Accum_capacitance_normalized * (phi_ms + flatband_voltage[0])) / ( self.config["IV_PQC_parameter"]["q"] ) Nox_table = "{:.2e}".format( Nox ) # Value to insert later on in the results table # Append the values resulting from the analysis to the corresponding lists. fbvoltage.append(flatband_voltage[0]) Accum_capacitance_list.append(Accum_capacitance_table) Accum_capacitance_normalized_list.append(Accum_capacitance_normalized_table) Tox_list.append(Tox_table) Nox_list.append(Nox_table) # Add text text = hv.Text( 10, 0.00000000065, "Flat band voltage_fit_2nd derivative: {} V \n" "Flat band voltage first derivative: {} V \n" "C accumulation: {} F \n" "C accumulation/A: {} F/cm\N{SUPERSCRIPT TWO} \n" "Tox: {} nm \n" "Nox: {} cm\N{SUPERSCRIPT MINUS}\N{SUPERSCRIPT TWO}".format( np.round(np.median(flatband_voltage[0]), 2), np.round(voltage_value_of_max_firstder, 2), np.round(Accum_capacitance, 10), np.round(Accum_capacitance_normalized, 10), np.round(Tox, 2), np.format_float_scientific(Nox, 2), ), ).opts(style=dict(text_font_size="25pt")) # If more than one file do not do the derivates plots if not len(cv_files) == 1: returnPlot = plot returnPlot = customize_plot( returnPlot, "1C2", configs["IV_PQC"], **addConfigs ) return ( returnPlot, flatband_voltage[0], Accum_capacitance_table, Accum_capacitance_normalized_table, Tox_table, Nox_table, ) elif len(cv_files) == 1: # Plot a vertical line in the value of the fb voltage vline = hv.VLine(flatband_voltage[0]).opts(color="black", line_width=1.0) # Plots of the derivatives secondDerivativePlot = self.basePlots5.Curve.secondderivative # Plots of the fits right_line = hv.Curve(right_line).opts(color="blue", line_width=1.0) fit_line = hv.Curve(fit_line).opts(color="red", line_width=1.5) returnPlot = plot * right_line * fit_line * secondDerivativePlot * vline returnPlot = customize_plot( returnPlot, "1C2", configs["IV_PQC"], **addConfigs ) returnplot2 = plot * fit_line * right_line * vline * text return ( returnPlot, flatband_voltage[0], Accum_capacitance_table, Accum_capacitance_normalized_table, Tox_table, Nox_table, returnplot2, )
def find_flatBand_voltage(self, file): RR2 = 0 fitR2 = 0 df = self.data[file]["fit"]["dataframe"] for idx in range(5, len(df) - 5): # Right slope_right, intercept_right, r_right, p_value, std_err_right = linregress( df["x"][idx:], df["y"][idx:]) r2_right = r_right * r_right # See if the r2 value has increased and store it if r2_right >= RR2: RR2 = r2_right RightEndPoints = ((df["x"][idx], slope_right * df["x"][idx] + intercept_right), (df["x"][len(df["x"]) - 1], slope_right * df["x"][len(df["x"]) - 1] + intercept_right)) Right_stats = [ RightEndPoints, slope_right, intercept_right, r_right, p_value, std_err_right ] startIndex = df['y'].idxmin() endIndex = len(df) - 1 # Fit central region for idx in range(startIndex + 5, endIndex - 1): # Do central fit slope_fit, intercept_fit, r_fit, p_valuefit, std_err_fit = linregress( df["x"][startIndex:idx], df["y"][startIndex:idx]) r2_fit = r_fit * r_fit # See if the r2 value has increased and store it if r2_fit >= fitR2: fitR2 = r2_fit fitEndPoints = ((df["x"][startIndex], slope_fit * df["x"][startIndex] + intercept_fit), (df["x"][idx + 1], slope_fit * df["x"][idx + 1] + intercept_fit)) fit_stats = [ fitEndPoints, slope_fit, intercept_fit, r_fit, p_valuefit, std_err_fit ] # Add central slope, -3 on x value so the line doesnt end too soon, fit_line = [[start_x,start_x],[end_x,end_y]] xmax = df["x"][endIndex] fit_line = np.array([[ df["x"][startIndex - 3], fit_stats[1] * df["x"][startIndex - 3] + fit_stats[2] ], [xmax + 0.2, fit_stats[1] * (xmax + 0.2) + fit_stats[2]]]) fit_line = hv.Curve(fit_line).opts(color="red", line_width=1.5) self.data[file]["fit"]["lines"] = [fit_line, None] # Add right slope xmax = df["x"][len(df["y"]) - 1] right_line = np.array([[ df["x"][startIndex - 3], Right_stats[1] * df["x"][startIndex - 3] + Right_stats[2] ], [xmax, Right_stats[1] * xmax + Right_stats[2]]]) right_line = hv.Curve(right_line).opts(color="blue", line_width=1.0) self.data[file]["fit"]["lines"][1] = right_line # intersect lines and store only the voltage flatband_voltage = line_intersection(fit_stats[0], Right_stats[0]) self.data[file]["fit"]["flatband"] = round(flatband_voltage[0], 4)
def find_full_depletion_c2(self, plot, data, configs, diode_files, **addConfigs): """ Finds the full depletion voltage of all data samples and adds a vertical line for the full depletion in the plot. Vertical line is the mean of all measurements. Furthermore, adds a text with the statistics. :param plot: The plot object :param data: The data files :param configs: the configs :param **addConfigs: the configs special for the 1/C2 plot, it is recomended to pass the same options here again, like in the original plot! :return: The updated plot """ # global LeftEndPoints, df Left_stats = np.zeros((len(diode_files), 6), dtype=np.object) self.log.info("Searching for full depletion voltage in all files...") for samplekey in diode_files: if "1C2" not in data[samplekey]["data"]: self.log.warning( "Full depletion calculation could not be done for data set: {}".format( samplekey ) ) else: self.log.debug("Data: {}".format(samplekey)) sample = deepcopy(data[samplekey]) df = pd.DataFrame( {"xaxis": sample["data"]["Voltage"], "yaxis": sample["data"]["1C2"]} ) df = df.dropna() # Loop one time from the from the left side, to get the slope LR2 = 0 for idx in range(5, len(df) - 20): # Left ( slope_left, intercept_left, r_left, p_value, std_err_left, ) = linregress(df["xaxis"][:-idx], df["yaxis"][:-idx]) r2_left = r_left * r_left self.log.debug( "Left side fit: Slope {}, intercept: {}, r^2: {}, std: {}".format( slope_left, intercept_left, r2_left, std_err_left ) ) # See if the r2 value has increased and store end points if r2_left >= LR2: LR2 = r2_left LeftEndPoints = ( (df["xaxis"][0], intercept_left), ( df["xaxis"][idx], slope_left * df["xaxis"][idx] + intercept_left, ), ) # Find the right fit by averaging on the final 20 points average_right = np.mean(list(df["yaxis"][-20:])) RightEndPoints = [ (df["xaxis"][len(df["xaxis"]) - 20], average_right), (df["xaxis"][len(df["xaxis"]) - 1], average_right), ] # Find the line intersection full_depletion_voltages = line_intersection(LeftEndPoints, RightEndPoints) fdepvoltage.append(full_depletion_voltages[0]) self.log.info( "Full depletion voltage: {} V".format( np.round(full_depletion_voltages[0], 2) ) ) # Add vertical line for full depletion vline = hv.VLine(full_depletion_voltages[0]).opts(color="black", line_width=5.0) # Add slopes left_line = np.array( [ [0, np.median(Left_stats[:, 2])], [full_depletion_voltages[0], full_depletion_voltages[1]], ] ) left_line = hv.Curve(left_line).opts(color="grey") right_line = hv.HLine(average_right).opts(color="grey") # Add text text = hv.Text( 230, 5e21, "Depletion voltage: {} V".format(np.round(full_depletion_voltages[0], 2)), ).opts(style=dict(text_font_size="20pt")) # Update the plot specific options if need be returnPlot = plot * vline * right_line * left_line * text returnPlot = customize_plot(returnPlot, "1C2", configs["IV_PQC"], **addConfigs) return returnPlot, full_depletion_voltages[0]
def find_full_depletion(self, plot, data, configs, **addConfigs): """ Finds the full depletion voltage of all data samples and adds a vertical line for the full depletion in the plot. Vertical line is the mean of all measurements. Furthermore, adds a text with the statistics. :param plot: The plot object :param data: The data files :param configs: the configs :param **addConfigs: the configs special for the 1/C2 plot, it is recomended to pass the same options here again, like in the original plot! :return: The updated plot """ full_depletion_voltages = np.zeros((len(data["keys"]), 2)) Left_stats = np.zeros((len(data["keys"]), 6), dtype=np.object) Right_stats = np.zeros((len(data["keys"]), 6), dtype=np.object) self.log.info("Searching for full depletion voltage in all files...") for i, samplekey in enumerate(data["keys"]): if "1C2" not in data[samplekey]["data"]: self.log.warning( "Full depletion calculation could not be done for data set: {}" .format(samplekey)) else: self.log.debug("Data: {}".format(samplekey)) sample = deepcopy(data[samplekey]) try: df = sample["data"][["voltage", "1C2"]].rename(columns={ "voltage": "xaxis", "1C2": "yaxis" }) except: df = sample["data"][["Voltage", "1C2"]].rename(columns={ "Voltage": "xaxis", "1C2": "yaxis" }) df = df.dropna() # Loop one time from the right side and from the left, to get both slopes LR2, Left_stats[i], RR2, Right_stats[ i] = self.calculate_slopes(df, minSize=5, startidx=5) # Make the line intersection full_depletion_voltages[i] = line_intersection( Left_stats[i][0], Right_stats[i][0]) self.log.info( "Full depletion voltage to data file {} is {}, with LR^2={} and RR^2={}" .format(samplekey, full_depletion_voltages[i], LR2, RR2)) # Add vertical line for full depletion # Calculate the mean of all full depeltion voltages and draw a line there valid_indz = np.nonzero(full_depletion_voltages[:, 0]) vline = hv.VLine( np.mean(full_depletion_voltages[valid_indz], axis=0)[0]).opts(color="black", line_width=5.0) # Add slopes xmax = df["xaxis"][len(df["yaxis"]) - 1] left_line = np.array([ [0, np.median(Left_stats[:, 2])], [ xmax, np.median(Left_stats[:, 1]) * xmax + np.median(Left_stats[:, 2]), ], ]) left_line = hv.Curve(left_line).opts(color="grey") right_line = np.array([ [0, np.median(Right_stats[:, 2])], [ xmax, np.median(Right_stats[:, 1]) * xmax + np.median(Right_stats[:, 2]), ], ]) right_line = hv.Curve(right_line).opts(color="grey") # Add text self.log.info( "Full depletion voltage: {} V, " "Error: {} V".format( np.round(np.median(full_depletion_voltages[valid_indz, 0]), 2), np.round(np.std(full_depletion_voltages[valid_indz, 0]), 2), )) # text = hv.Text(np.mean(full_depletion_voltages[valid_indz], axis=0)[0]*2, np.mean(full_depletion_voltages[valid_indz], axis=0)[1]*1.2, 'Depletion voltage: {} V \n' # 'Error: {} V'.format(np.round(np.mean(full_depletion_voltages[:, 0]), 2), # np.round(np.std(full_depletion_voltages[valid_indz, 0]), 2)) # ).opts(fontsize=30) bounds = np.mean(full_depletion_voltages[valid_indz], axis=0) text = text_box( "Depletion voltage: {} V \n" "Error: {} V".format( np.round(np.mean(full_depletion_voltages[:, 0]), 2), np.round(np.std(full_depletion_voltages[valid_indz, 0]), 2), ), np.mean(full_depletion_voltages[valid_indz], axis=0)[0] * 2, np.mean(full_depletion_voltages[valid_indz], axis=0)[1] * 1.3, boxsize=(200, bounds[1] * 0.3), ) # Update the plot specific options if need be returnPlot = vline * right_line * left_line * text * plot # returnPlot = relabelPlot(returnPlot, "CV CURVES - Full depletion calculation") returnPlot = customize_plot(returnPlot, "1C2", configs[self.analysisName], **addConfigs) return returnPlot