Source code for boar.SIMsalabim_utils.aux_func

###################################################
############### Useful function ###################
###################################################
# Author: Vincent M. Le Corre
# Github: https://github.com/VMLC-PV

# Import libraries
import numpy as np
import pandas as pd
from scipy import stats,constants

## Physics constants
q = constants.value(u'elementary charge')
eps_0 = constants.value(u'electric constant')
kb = constants.value(u'Boltzmann constant in eV/K')

[docs] def sci_notation(number, sig_fig=2): """Make proper scientific notation for graphs Parameters ---------- number : float Number to put in scientific notation. sig_fig : int, optional Number of significant digits (Defaults = 2). Returns ------- output : str String containing the number in scientific notation """ if sig_fig != -1: if number == 0: output = '0' else: ret_string = "{0:.{1:d}e}".format(number, sig_fig) a,b = ret_string.split("e") if int(b) >= 0: b = int(b) #removed leading "+" and strips leading zeros too. c = '' else: b = abs(int(b)) c = u"\u207B" # superscript minus sign SUP = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹") b = str(b).translate(SUP) output =a + ' x 10' + c + b else: if number == 0: output = '0' else: ret_string = "{0:.{1:d}e}".format(number, 0) a,b = ret_string.split("e") b = int(b) #removed leading "+" and strips leading zeros too. if int(b) >= 0: b = int(b) #removed leading "+" and strips leading zeros too. c = '' else: b = abs(int(b)) c = u"\u207B" # superscript minus sign SUP = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹") #SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") b = str(b).translate(SUP) output = '10' + c + b return output
[docs] def LinearFunc(t,a,b): """Linear function f(t) = a\*t + b Parameters ---------- t : 1-D sequence of floats time a : float slope b : float offset Returns ------- 1-D sequence of floats f(t) """ return a*t + b
[docs] def MonoExpDecay(t, tau, f0 , finf): """ Monoexponential decay function f(t) = (f0-finf) \* np.exp(-(t/tau) ) + finf Parameters ---------- t : 1-D sequence of floats time k : float lifetime f0 : float initial quantity finf : float offset Returns ------- 1-D sequence of floats f(t) """ return (f0-finf) * np.exp(-(t/tau) ) + finf
[docs] def MonoExpInc(t, tau, f0, finf): """ Monoexponential Inc function f(t) = (finf-f0) \*(1-np.exp(-(t/tau))) + f0) Parameters ---------- t : 1-D sequence of floats time k : float lifetime f0 : float initial quantity finf : float offset Returns ------- 1-D sequence of floats f(t) """ return (finf-f0) *(1-np.exp(-(t/tau))) + f0
[docs] def StretchedExp(t, tau, h, A, B): """ Stretched decay function f(t) = A \* np.exp(- (t/tau)^h ) + B Parameters ---------- t : 1-D sequence of floats time tau : float lifetime h : float heterogeneity parameter A : float initial quantity B : float offset Returns ------- 1-D sequence of floats f(t) """ return A * np.exp(- (t/tau)**h ) + B
[docs] def get_Jsc(Volt,Curr): """Get the short-circuit current (Jsc) from solar cell JV-curve by interpolating the current at 0 V Parameters ---------- Volt : 1-D sequence of floats Array containing the voltages. Curr : 1-D sequence of floats Array containing the current-densities. Returns ------- Jsc : float Short-circuit current value """ Jsc_dumb = np.interp(0, Volt, Curr) return Jsc_dumb
[docs] def get_Voc(Volt,Curr): """Get the Open-circuit voltage (Voc) from solar cell JV-curve by interpolating the Voltage when the current is 0 Parameters ---------- Volt : 1-D sequence of floats Array containing the voltages. Curr : 1-D sequence of floats Array containing the current-densities. Returns ------- Voc : float Open-circuit voltage value """ Voc_dumb = np.interp(0, Curr, Volt) return Voc_dumb
[docs] def get_FF(Volt,Curr): """Get the fill factor (FF) from solar cell JV-curve by calculating the maximum power point Parameters ---------- Volt : 1-D sequence of floats Array containing the voltages. Curr : 1-D sequence of floats Array containing the current-densities. Returns ------- FF : float Fill factor value """ power = [] Volt_oc = get_Voc(Volt,Curr) Curr_sc = get_Jsc(Volt,Curr) for i,j in zip(Volt,Curr): if (i < Volt_oc and j > Curr_sc): power.append(i*j) power_max = min(power) FF_dumb = power_max/(Volt_oc*Curr_sc) return abs(FF_dumb)
[docs] def get_PCE(Volt,Curr,suns=1): """Get the power conversion efficiency (PCE) from solar cell JV-curve Parameters ---------- Volt : 1-D sequence of floats Array containing the voltages. Curr : 1-D sequence of floats Array containing the current-densities. Returns ------- PCE : float Power conversion efficiency value. """ Voc_dumb = get_Voc(Volt,Curr) Jsc_dumb = get_Jsc(Volt, Curr) FF_dumb = get_FF(Volt, Curr) PCE_dumb = Voc_dumb*Jsc_dumb*FF_dumb/(10*suns) # to get it in % when Jsc is in A/m2 and Voc in V return abs(PCE_dumb)
[docs] def get_ideality_factor(suns,Vocs,T=295): """Returns ideality factor from suns-Voc data linear fit of Voc = (nIF/Vt)\*log(suns) + intercept Parameters ---------- suns : 1-D sequence of floats Array containing the intensity in sun. Vocs : 1-D sequence of floats Array containing the open-circuit voltages. T : float optional Temperature in Kelvin (Default = 295 K). Returns ------- nIF : float Ideality factor value. intercept : float Intercept of the regression line. rvalue : float Correlation coefficient. pvalue : float Two-sided p-value for a hypothesis test whose null hypothesis is that the slope is zero, using Wald Test with t-distribution of the test statistic. stderr : float Standard error of the estimated gradient. """ Vt = kb*T suns = np.log(suns) slope_d, intercept_d, r_value_d, p_value_d, std_err_d = stats.linregress(suns,Vocs) nIF = slope_d/Vt return nIF,intercept_d, r_value_d**2, p_value_d, std_err_d
[docs] def get_alpha_factor(suns,Jscs): """Returns alpha from suns-Jsc data linear fit of log(Jsc) = alpha\*log(suns) + b Parameters ---------- suns : 1-D sequence of floats Array containing the intensity in sun. Vocs : 1-D sequence of floats Array containing the open-circuit voltages. Returns ------- alpha : float Alpha value. intercept : float Intercept of the regression line. rvalue : float Correlation coefficient. pvalue : float Two-sided p-value for a hypothesis test whose null hypothesis is that the slope is zero, using Wald Test with t-distribution of the test statistic. stderr : float Standard error of the estimated gradient. """ suns = np.log(suns) Jscs = np.log(Jscs) alpha, intercept_d, r_value_d, p_value_d, std_err_d = stats.linregress(suns,Jscs) return alpha,intercept_d, r_value_d**2, p_value_d, std_err_d
[docs] def get_random_value(val_min,val_max,scale='lin'): """Get random value between two boundaries Parameters ---------- val_min : float min value val_max : float max value scale : str, optional scale type, by default 'lin' Returns ------- float random value """ if val_min > val_max: dum_min = min(val_min,val_max) dum_max = max(val_min,val_max) val_min = dum_min val_max = dum_max print('Careful, the val_min > val_max, check the input for get_random_value') random_val = random.uniform(0, 1) if scale == 'lin': val = (val_max - val_min) * random_val + val_min elif scale == 'log': val = np.sign(val_max) * np.exp( random_val * ( np.log(abs(val_max)) - np.log(abs(val_min)) ) +np.log(abs(val_min)) ) elif scale == 'int': val = int(round((val_max - val_min) * random_val + val_min)) else: print('The program will stop') sys.exit('Wrong scale for the input parameters') return val
[docs] def valence_urbach(CB,VB,Eu,num_points): """ Creates a Urbach tail for the valence band (from VB to VB-(VB-CB)/2) to be used as a BulkTrapFile or IntTrapFile for SIMsalabim Fomula: frac = exp(-(VB-E)/Eu) Parameters ---------- CB : float Conduction band edge value in eV VB : float Valence band edge value in eV Eu : float Urbach energy in eV num_points : int Number of points to be generated Returns ------- E : 1-D sequence of floats Array containing the energy values in eV frac : 1-D sequence of floats Array containing the fraction of traps at each energy value """ E = np.linspace(VB-(VB-CB)/2,VB,num_points) Erela = VB - E frac = np.ones(len(Erela)) for i in range(len(Erela)): frac[i] = np.exp(-Erela[i]/Eu) # drop last point E = E[:-1] frac = frac[:-1] #normalize the fraction frac = frac/np.sum(frac) return E,frac
[docs] def conduction_urbach(CB,VB,Eu,num_points): """ Creates a Urbach tail for the conduction band (from CB to CB+(VB-CB)/2) to be used as a BulkTrapFile or IntTrapFile for SIMsalabim Parameters ---------- CB : float Conduction band edge value in eV VB : float Valence band edge value in eV Eu : float Urbach energy in eV num_points : int Number of points to be generated Returns ------- E : 1-D sequence of floats Array containing the energy values in eV frac : 1-D sequence of floats Array containing the fraction of traps at each energy value """ E = np.linspace(CB,CB+(VB-CB)/2,num_points) Erela = E - CB frac = np.ones(len(Erela)) for i in range(len(Erela)): frac[i] = np.exp(-Erela[i]/Eu) # drop first point E = E[1:] frac = frac[1:] #normalize the fraction frac = frac/np.sum(frac) return E,frac
[docs] def double_urbach(CB,VB,Eu,num_points): """ Creates a Urbach tail on both sides of the bandgap to be used as a BulkTrapFile or IntTrapFile for SIMsalabim Parameters ---------- CB : float Conduction band edge value in eV VB : float Valence band edge value in eV Eu : float Urbach energy in eV num_points : int Number of points to be generated Returns ------- E : 1-D sequence of floats Array containing the energy values in eV frac : 1-D sequence of floats Array containing the fraction of traps at each energy value """ num_points = int(num_points/2) E1,frac1 = conduction_urbach(CB,VB,Eu,num_points) E2,frac2 = valence_urbach(CB,VB,Eu,num_points) E = np.concatenate((E1,E2)) frac = np.concatenate((frac1,frac2)) # check for duplicates in E and remove E, idx = np.unique(E, return_index=True) # drop the points with duplicates frac = frac[idx] #normalize the fraction frac = frac/np.sum(frac) return E,frac
[docs] def double_urbach_midgap(CB,VB,Eu,fracmid,num_points): """ Creates a Urbach tail on both sides of the bandgap and add a state mid-gap to be used as a BulkTrapFile or IntTrapFile for SIMsalabim Parameters ---------- CB : float Conduction band edge value in eV VB : float Valence band edge value in eV Eu : float Urbach energy in eV fracmid : float Fraction of traps at mid-gap num_points : int Number of points to be generated Returns ------- E : 1-D sequence of floats Array containing the energy values in eV frac : 1-D sequence of floats Array containing the fraction of traps at each energy value """ E,Ntraps = double_urbach(CB,VB,Eu,num_points) Emid = CB+(VB-CB)/2 Ntrap_mid = fracmid Norm_Ntraps = Ntraps/np.sum(Ntraps) Ntraps = Norm_Ntraps*(1-Ntrap_mid) if Emid in E: idx = np.where(E==Emid) Ntraps[idx] = Ntraps[idx] + Ntrap_mid else: # add Emid to E amd add Ntrap_mid to Ntraps sorted with respect to E E = np.append(E,Emid) Ntraps = np.append(Ntraps,Ntrap_mid) idx = np.argsort(E) E = E[idx] Ntraps = Ntraps[idx] # check for duplicates in E and remove E, idx = np.unique(E, return_index=True) # drop the points with duplicates Ntraps = Ntraps[idx] #normalize the fraction Ntraps = Ntraps/np.sum(Ntraps) return E,Ntraps