From 6550c2a2bd5f9c7090920bf381ec17559fe615a7 Mon Sep 17 00:00:00 2001 From: Jan Gruis <gruis@cst.rwth-aachen.de> Date: Tue, 19 Sep 2023 15:46:03 +0200 Subject: [PATCH] added evaluation module which is currently only a copy of tlmlib.py --- .gitignore | 2 + evaluation.py | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 evaluation.py diff --git a/.gitignore b/.gitignore index 3dff88e..63831f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea/ .ipynb_checkpoints/ __pycache__/ + +jan_tests.ipynb diff --git a/evaluation.py b/evaluation.py new file mode 100644 index 0000000..71ab0f3 --- /dev/null +++ b/evaluation.py @@ -0,0 +1,320 @@ +"""Contains all evaluation classes and functions. + +class LinearRegressionFitting: Represents a Linear Regression Fitting of a 2D x,y plot +""" + +import os +import pandas as pd +import numpy as np +import altair as alt +import datetime + + + +# Colors +rwthcolors= {'blau' : '#00549F', + 'schwarz' : '#000000', + 'magenta' : '#E30066', + 'gelb' : '#FFED00', + 'petrol' : '#006165', + 'türkis' : '#0098A1', + 'grün' : '#57AB27', + 'maigrün' : '#BDCD00', + 'orange' : '#F6A800', + 'rot' : '#CC071E', + 'bordeaux' : '#A11035', + 'violett' : '#612158', + 'lila' : '#7A6FAC', + } +colorlist = [rwthcolors['blau'], + rwthcolors['türkis'], + rwthcolors['grün'], + rwthcolors['orange'], + rwthcolors['violett'], + ] + + + + + + +# Class Definitions +class LinearRegressionFitting: + """Represents a Linear Regression Fitting of a 2D x,y plot""" + + def __init__(self, PandaDataFrame): + """Initializes the instance with a PandaDataFrame + + Attributes: + x + y + slope + intercept + residuals + diagnostics + model + R2 + df + chart + """ + self.input = PandaDataFrame + self.x = PandaDataFrame.to_numpy()[:, 0] + self.y = PandaDataFrame.to_numpy()[:, 1] + + # https://numpy.org/doc/stable/reference/generated/numpy.polyfit.html + [self.slope, self.intercept], self.residuals, *self.diagnostics = np.polyfit(self.x, self.y, 1, full = True) + + # model = expression of polynom from polyfit, here basicly + # y = model(x) <=> same as writing y = slope * x + intercept + self.model = np.poly1d([self.slope, self.intercept]) + + # Bestimmtheitsmaß / Coefficient of determination / R² + # 1 - ( residual sum of squares / SUM( (yi - y_mean)² ) ) + self.R2 = 1 - (self.residuals[0] / + np.sum( pow( self.y - np.average( self.y ), 2) ) + ) + + self.df = pd.DataFrame({'x': self.x,'f(x)': self.model(self.x)}) + self.chart = alt.Chart(self.df).mark_line().encode(x="x", y='f(x)') + +class RT_Evaluation(LinearRegressionFitting): + def __init__(self, PandaDataFrame, contactlenght): + super().__init__(PandaDataFrame) + #self.input + #self.x + #self.y + #self.slope + #self.intercept + #self.residuals + #self.diagnostics + #self.model + #self.R2 + #self.df + model_df_x = np.linspace(0, self.x[-1]) + self.df = pd.DataFrame({'x': model_df_x,'f(x)': self.model(model_df_x)}) + #self.chart + self.chart = alt.Chart(self.df).mark_line().encode(x="x", y='f(x)') + + self.contactlenght = contactlenght + + # Kontaktwiderstand Rc [Ohm*mm] + # = RT(d=0)/2 [Ohm] * Contactlenght [µm/1000] + self.Rc = (self.intercept/2) * (contactlenght/1000) + + # Schichtwiderstand [Ohm/sq = Ohm] + # = slope [Ohm/µm] * Contactlenght [µm] + self.Rsh = self.slope * contactlenght + + # Transferlänge LT [mm] RT(d) = 0 + # = slope [Ohm/µm] / RT(d=0) [Ohm] / 1000 + #self.LT = self.intercept / self.slope / 1000 + + # Transferlänge LT [µm] RT(d) = 0 + # = slope [Ohm/µm] / RT(d=0) [Ohm] + self.LT = self.intercept / self.slope / 2 + + # LT = sqrt(rhoc/Rsh). + # "Semiconductor Material and Device Characterization Third Edition", + # D. Schroder, p. 140, Eq. 3.21 + # Spezifischer Kontaktwiderstand rhoc = LT² * Rsh + # = Ohm cm² | µm²*0.00000001 = cm² + self.rhoc = self.LT*self.LT * self.Rsh * 1E-4 * 1E-4 + + +class TlmMeasurement(object): + def __init__(self, filelist, distances = (5, 10, 15, 20, 50), contactlenght = 50): + """filelist as tuple + distances as tuple ## Abstände der TLM in µm + contactlenght # Kontaktweite der TLM Strukturen in µm + """ + self._creation_date = datetime.datetime.now() + self.filelist = filelist + self.path, self.files = self.importfiles(filelist) + self.distances = distances + self.contactlenght = contactlenght + self.df = self.construct_dataframes(self.filelist) + #self.df_org = self.df + self.R, self.lin_reg_charts, self.R_statistics = self.R_from_lin_reg() + self.RT0 = pd.DataFrame({'d/µm':self.distances, 'R_T/Ohm':self.R}) + + self.eval0 = RT_Evaluation(self.RT0, self.contactlenght) + self.eval1, self.eval2 = self.find_RT1_RT2() + self.refined = False + #self.results = self.results() + + def importfiles(self, filelist): + if not len(filelist) == 5: + raise Exception("Files Missing - I need 5 files for TLM or CTLM") + filenames = [] + name = "" + measurement = [] + for i in range(len(filelist)): + filenames.append(os.path.split(filelist[i])[1]) + name, end = filenames[i].rsplit(sep="_",maxsplit=1) + #print(name, "_", end) + if i == 0: + firstname = name + elif not name == firstname: + print("Files:", filenames) + raise Exception("Filenames differ") + measurement.append(end.split(sep=".")[0]) + #print(measurement) + #print(i, len(files)-1) + if (i == len(filelist)-1) and (not measurement == ['1', '2', '3', '4', '5']): + print("Files:", filenames) + raise Exception("Not Measurement _1 to _5?") + path = os.path.split(filelist[0])[0] + return (path, filenames) + + def construct_dataframes(self, filelist = None): + if filelist is None: + filelist = self.filelist + df = [] + for i in range(len(filelist)): + df.append(pd.read_csv(filelist[i], sep=" ", skip_blank_lines=True, header=3, index_col=0, usecols=[0,1,2,3], names=["#","U/V","I/A","R/Ohm"])) + return df + + def construct_ui_charts(self, PandaDataFrames = None): + if PandaDataFrames is None: + PandaDataFrames = self.df + uicharts = [] + for i in range(len(PandaDataFrames)): + uicharts.append( alt.Chart(self.df[i]).mark_point(color=colorlist[i]).encode(x="U/V", y="I/A").interactive() ) + return uicharts + + def R_from_lin_reg(self, PandaDataFrames = None): + if PandaDataFrames is None: + PandaDataFrames = self.df + lin_reg_results = [] # R + lin_reg_charts = [] + statistics_string = f"Statistics for Fitting of R Values \n d / µm | R² | RT / Ohm" + #print(f"Statistics for Fitting of R Values \n {'d / µm' : >9} | {'R²' : <6} | RT / Ohm ") + for i in range(len(PandaDataFrames)): + fit = LinearRegressionFitting(PandaDataFrames[i]) + lin_reg_results.append( 1 / (fit.slope) ) + lin_reg_charts.append( alt.Chart(fit.df).mark_line(color=colorlist[i]).encode(x="x", y='f(x)') ) + #print(f"{self.distances[i] : 10d} | {fit.R2 : >5.4f} | {lin_reg_results[i] : 10.4f}") + statistics_string += "\n" + f"{self.distances[i] : 10d} | {fit.R2 : >5.4f} | {lin_reg_results[i] : 10.4f}" + #print(statistics_string) + return lin_reg_results, lin_reg_charts, statistics_string + + def contruct_lin_reg_fit_charts(self): + return self.lin_reg_charts + + def find_RT1_RT2(self, R = None): + if R is None: + R = self.RT0 + + # Remove One Measurement + # Find maximal Bestimmtheitsmaß / Coefficient of determination / R² + save_RT1 = None + max = 0 + for i in range(len(R)): + Ri = R.drop(labels=i, axis=0) + evali = RT_Evaluation(Ri, self.contactlenght) + if evali.R2 >= max: + max = evali.R2 + save_RT1 = evali + + # Remove Two Measurements + list_of_rows_to_remove = [[0,1], + [0,2], + [0,3], + [0,4], + [1,2], + [1,3], + [1,4], + [2,3], + [2,4], + [3,4], + ] + save_RT2 = None + max = 0 + for rows in list_of_rows_to_remove: + Ri = R.drop(labels=rows, axis=0) + evali = RT_Evaluation(Ri, self.contactlenght) + if evali.R2 >= max: + max = evali.R2 + save_RT2 = evali + + return save_RT1, save_RT2 + + def refine_evaluated_range(self, newrange = None): + if not type(newrange) is tuple: + raise Exception("Wrong format for Newrange! Should be tuple (float, float)") + if not len(newrange) == 2: + raise Exception("Two few or many values in newrange! Should be tuple (float, float)") + minI, maxI = newrange + refined_df = self.df + for i in range(len(refined_df)): + # First vector where values for I/A are bigger than min and smaller than max = True + # (dataframe.iloc[:,1] > min) & (dataframe.iloc[:,1] < max ) + # then this boolean vector is used as indexer for the dame dataframe + # every value with False is sliced + work = refined_df[i] + #print(work) + #print((dataframe.iloc[:,1] > minI) & (dataframe.iloc[:,1] < maxI )) + work = work [ (work.iloc[:,1] > minI) & (work.iloc[:,1] < maxI ) ] + #print(work) + refined_df[i] = work + self.df = refined_df + self.R, self.lin_reg_charts, statistics_string = self.R_from_lin_reg() + self.RT0 = pd.DataFrame({'d/µm':self.distances, 'R_T/Ohm':self.R}) + self.eval0 = RT_Evaluation(self.RT0, self.contactlenght) + self.eval1, self.eval2 = self.find_RT1_RT2() + self.refined = True + + def results(self, comment=""): + #print("Path:\n", ctlm.path, "\n", sep = "") + results_string = "Path: " + self.path + "\n" + #print("Files: {}, {}, {}, {}, {} \n".format(*ctlm.files)) + results_string += "Files: {}, {}, {}, {}, {} \n".format(*self.files) + #header = "Feld Rsh R² Rc LT rhoc min I max I # removed values" + #header = "Feld Rsh R² Rc LT rhoc # removed values" + results_string += comment + " \n" + results_string += "Feld Rsh R² Rc LT rhoc # removed values \n" + #units = "- [Ohm/sq] - [Ohm mm] [µm] [Ohm cm²] [A] [A] -" + #units = "- [Ohm/sq] - [Ohm mm] [µm] [Ohm cm²] -" + results_string += "- [Ohm/sq] - [Ohm mm] [µm] [Ohm cm²] - \n" + format_string = "{:<4}{:7.2f} {:8.4f} {:6.2f} {:8.2f} {:8.2e} {} \n" + results_string += format_string.format(self.files[0].rsplit("_")[0], + self.eval0.Rsh, + self.eval0.R2, + self.eval0.Rc, + self.eval0.LT, + self.eval0.rhoc, + #minI, + #maxI, + None, + ) + results_string += format_string.format(self.files[0].rsplit("_")[0], + self.eval1.Rsh, + self.eval1.R2, + self.eval1.Rc, + self.eval1.LT, + self.eval1.rhoc, + #minI, + #maxI, + 1, + ) + results_string += format_string.format(self.files[0].rsplit("_")[0], + self.eval2.Rsh, + self.eval2.R2, + self.eval2.Rc, + self.eval2.LT, + self.eval2.rhoc, + #minI, + #maxI, + 2, + ) + + return results_string + + def construct_rt_charts(self): + rt_charts = [] + rt_charts.append( alt.Chart(self.RT0).mark_point(color=rwthcolors['blau']).encode(x='d/µm', y='R_T/Ohm').interactive() ) + rt_charts.append( alt.Chart(self.eval0.df).mark_line(color=rwthcolors['bordeaux']).encode(x='x', y='f(x)') ) + rt_charts.append( alt.Chart(self.eval1.df).mark_line(color=rwthcolors['violett']).encode(x='x', y='f(x)') ) + rt_charts.append( alt.Chart(self.eval2.df).mark_line(color=rwthcolors['lila']).encode(x='x', y='f(x)') ) + return rt_charts + -- GitLab