def V_bi(self, eV=False): measurement_id = self.Project.add_measurement(self.measurements_types['Built-in voltage measurement'], 'Vbi', 'Built-in voltage', measurement_datetime=None, create_new=False) point_name_long = 'Vbi' Vbi_array, _, measurement_date_array, unit_name_array = self.Project.get_data_points(measurement_id, point_name_long) for i in range(len(Vbi_array)): T, _ = self.Project.get_data_point_at_datetime(self.T_measurement_id, 'Temperature', measurement_date_array[i], interpolation_type='step') if T == self.T: Vbi = Vbi_array[i] if eV: if unit_name_array[i] == 'J': Vbi /= to_numeric(q) else: if unit_name_array[i] == 'V': Vbi *= to_numeric(q) return np.float(Vbi) point_name_short = 'Vbi' point_unit_name = 'V' if eV else 'J' point_order = self.Project.get_next_data_point_order(measurement_id, point_name_long) if eV: Vbi = to_numeric(self.Metal.WF / q) - self.Semiconductor.WF(self.T, z=1e3, eV=eV) else: Vbi = to_numeric(self.Metal.WF) - self.Semiconductor.WF(self.T, z=1e3, eV=eV) self.Project.add_datapoint(point_order, measurement_id, self.tool_id, point_name_long, point_name_short, point_unit_name, Vbi, datetime.datetime.now()) return np.float(Vbi)
def BandsBending_prepare_data(SchDiode, Psi, Vd, z, eV, SchottkyEffect, draw_metal): dz = np.gradient(z) Psi_points = Psi(z) E_points = -np.gradient(Psi_points, dz, edge_order=2) ''' if SchDiode.Semiconductor.dop_type == 'n': idx = np.where(E_points > 0)[0] else: idx = np.where(E_points < 0)[0] if idx.size > 0: split = np.where(idx[1:]-idx[:-1] > 1)[0]+1 if split.size > 0: idx = np.split(idx, split)[0] #print 'Inversion pt', z[idx][-1]*1e6 FieldInversionPoint = z[idx[-1]] FieldInversionPsi = Psi_points[idx[-1]] else: FieldInversionPoint = 0 FieldInversionPsi = 0 ''' FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn(Psi, Vd, SchottkyEffect=False) FieldInversionPsi = Psi(FieldInversionPoint) idx = np.where(z <= FieldInversionPoint)[0] coeff = 1 if eV else to_numeric(q) Eg = SchDiode.Semiconductor.band_gap(SchDiode.T, symbolic=False, electron_volts=eV) Ec = SchDiode.Ec(Psi, Vd, z, eV=eV) type_sign = 1 if SchDiode.Semiconductor.dop_type == 'n' else -1 Ef = np.zeros_like(z) + coeff * type_sign * Vd if FieldInversionPoint > 0: # xi = np.float(SchDiode.Semiconductor.Ech_pot(T=SchDiode.T, z=1e3, eV=eV, debug=False)) Ef[0:idx[-1]] = coeff * FieldInversionPsi * type_sign # print idx[-1] # Ef[0:idx[-1]] = Ec[0:idx[-1]] - Ef[idx[-1]] Ev = Ec - Eg if draw_metal: Z = np.append(0, z) Ec = np.append(0, Ec) Ev = np.append(0, Ev) Ef = np.append(Ef[0], Ef) MirrEnergy = np.zeros_like(Ec) if SchottkyEffect: for ii, zz in enumerate(Z): if zz != 0: MirrEnergy[ii] = to_numeric( q ** 2 / (16 * np.pi * epsilon0 * SchDiode.Semiconductor.reference['epsilon'] * zz)) if eV: MirrEnergy[ii] /= to_numeric(q) else: MirrEnergy[ii] = Ec[ii] return Z, Ec, Ev, Ef, Eg, MirrEnergy, FieldInversionPoint
def BandsBending_prepare_data(SchDiode, Psi, Vd, z, eV, SchottkyEffect, draw_metal): Psi_points = Psi(z) E_points = -np.gradient(Psi_points, z, edge_order=2) ''' if SchDiode.Semiconductor.dop_type == 'n': idx = np.where(E_points > 0)[0] else: idx = np.where(E_points < 0)[0] if idx.size > 0: split = np.where(idx[1:]-idx[:-1] > 1)[0]+1 if split.size > 0: idx = np.split(idx, split)[0] #print 'Inversion pt', z[idx][-1]*1e6 FieldInversionPoint = z[idx[-1]] FieldInversionPsi = Psi_points[idx[-1]] else: FieldInversionPoint = 0 FieldInversionPsi = 0 ''' FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn(Psi, Vd, SchottkyEffect=False) FieldInversionPsi = Psi(FieldInversionPoint) idx = np.where(z <= FieldInversionPoint)[0] coeff = 1 if eV else to_numeric(q) Eg = SchDiode.Semiconductor.band_gap(SchDiode.T, symbolic=False, electron_volts=eV) Ec = SchDiode.Ec(Psi, Vd, z, eV=eV) type_sign = 1 if SchDiode.Semiconductor.dop_type == 'n' else -1 Ef = np.zeros_like(z) + coeff * type_sign * Vd if FieldInversionPoint > 0: # xi = np.float(SchDiode.Semiconductor.Ech_pot(T=SchDiode.T, z=1e3, eV=eV, debug=False)) Ef[0:idx[-1]] = coeff * FieldInversionPsi * type_sign # print idx[-1] # Ef[0:idx[-1]] = Ec[0:idx[-1]] - Ef[idx[-1]] Ev = Ec - Eg if draw_metal: Z = np.append(0, z) Ec = np.append(0, Ec) Ev = np.append(0, Ev) Ef = np.append(Ef[0], Ef) MirrEnergy = np.zeros_like(Ec) if SchottkyEffect: for ii, zz in enumerate(Z): if zz != 0: MirrEnergy[ii] = to_numeric( q ** 2 / (16 * np.pi * epsilon0 * SchDiode.Semiconductor.reference['epsilon'] * zz)) if eV: MirrEnergy[ii] /= to_numeric(q) else: MirrEnergy[ii] = Ec[ii] return Z, Ec, Ev, Ef, Eg, MirrEnergy, FieldInversionPoint
def ThermionicEmissionCurrent(self, Va, phi_bn, debug=False): kT = to_numeric(k * self.T) q_n = to_numeric(q) A = self.Area Rs = self.Rs if self.Semiconductor.dop_type == 'n': Ar = self.Semiconductor.reference['A_R_coeff_n'] * constants['A_R'] else: Ar = self.Semiconductor.reference['A_R_coeff_p'] * constants['A_R'] Js = Ar * (self.T ** 2) * mp.exp(-q_n * phi_bn / kT) if debug: print 'Js, Is =', Js, A * Js J = -Js + kT / (q_n * A * Rs) * mp.lambertw((q_n * A * Rs * Js / kT) * mp.exp(q_n * (Va + A * Js * Rs) / kT)) if debug: print 'J, I =', J, A * J Vd = Va - A * J * Rs return np.float(Vd), np.float(J)
def RhoDiagram(ax, SchDiode, Psi=Psi_zero, z=0): print 'Rho Diagram Start' rho_semi_num = SchDiode.build_rho_z_Psi(Psi, carriers_charge=True) rho = rho_semi_num(z, Psi) / to_numeric(q) n, p = SchDiode.n_carriers_theory(Psi, z) dopants_rho = [] for dopant in SchDiode.Semiconductor.dopants: Nd = dopant.concentration(z) dopants_rho.append( Nd * ((dopant.charge_states[1][0] - dopant.charge_states[0][0]) * dopant.F(z) + dopant.charge_states[0][0])) ax.plot(z * 1e6, abs(rho * 1e-6), linewidth=2, color='black', linestyle='-') if SchDiode.Semiconductor.dop_type == 'n': ax.plot(z * 1e6, n * 1e-6, linewidth=2, color='blue', linestyle='-') elif SchDiode.Semiconductor.dop_type == 'p': ax.plot(z * 1e6, p * 1e-6, linewidth=2, color='red', linestyle='-') for dopant_rho in dopants_rho: ax.plot(z * 1e6, dopant_rho * 1e-6, linewidth=2, color='green', linestyle='-') ax.set_yscale('log') # ax.ticklabel_format(axis='y', style='sci', scilimits=(-7,7)) ax.set_title('Net density of charge') ax.set_ylabel('Concentration, cm$^{-3}$') ax.set_xlabel(r'Coordinate (z), um') ax.grid(True) print 'Rho Diagram Stop'
def Ec(self, Psi=Psi_zero, Va=0, z=0, eV=False): coeff = 1.0 if eV else to_numeric(q) Psi_nodes = Psi(z) xi = np.float(self.Semiconductor.Ech_pot(T=self.T, z=1e3, eV=eV, debug=False)) type_sign = -1 if self.Semiconductor.dop_type == 'p' else 1 Ec = -coeff * Psi_nodes + xi + coeff * type_sign * Va return Ec
def init(): Eg = SchDiode.Semiconductor.band_gap(SchDiode.T, symbolic=False, electron_volts=eV) bands_lines['Ec'].set_data([], []) bands_lines['Ev'].set_data([], []) bands_lines['Ef'].set_data([], []) for dopant in SchDiode.Semiconductor.dopants: threshold_dopant = 0.05 if eV else to_numeric(q) * 0.05 level = dopant.energy_level(SchDiode.T, SchDiode.Semiconductor, electron_volts=eV) if level > threshold_dopant and level < (Eg - threshold_dopant): dopant_lines[dopant.name].set_data([], []) if label_bands: bands_labels['Ec'].set_text('') bands_labels['Ev'].set_text('') bands_labels['Ef'].set_text('') V_label.set_text('') T_label.set_text('') for key in BI_levels.keys(): BI_levels[key].set_offsets(np.array([])) BI_levels[key].set_array(np.array([])) fig_list = bands_lines.values() fig_list.extend(bands_labels.values()) fig_list.extend(dopant_lines.values()) fig_list.extend([V_label, T_label]) fig_list.extend(BI_levels.values()) return fig_list
def n_carriers_theory(self, Psi=Psi_zero, z=0): ''' Charge carrier concentration in the main band ''' k_n = to_numeric(k) Ef = self.EfEc(Psi, z, eV=False) Eg = self.Semiconductor.band_gap(self.T, symbolic=False, electron_volts=False) F_n = np.exp(-Ef / (k_n * self.T)) F_p = np.exp((Ef - Eg) / (k_n * self.T)) n = np.float(self.Semiconductor.Nc(self.T, symbolic=False)) * F_n p = np.float(self.Semiconductor.Nv(self.T, symbolic=False)) * F_p if isinstance(z, np.ndarray): idx = np.where(z < self.DeadLayer)[0] # print idx if idx.size > 0: n[idx] = 0.0 p[idx] = 0.0 idx = np.where(z < self.FieldInversionPoint)[0] if idx.size > 0: n[idx] = 0.0 p[idx] = 0.0 elif z < self.DeadLayer or z < self.FieldInversionPoint: n = 0 p = 0 return n, p
def set_electrode(self, Metal=None): measurement_type = self.Project.add_measurement_type('Metal work function', measurement_type_description='Metal work function measurement') try: metal_label = self.Project.get_project_info('Schottky electrode') measurement_id = self.Project.add_measurement(measurement_type, 'Electrode work function', 'Work function of metal electrode', measurement_datetime=None, create_new=False) WF_arr, _, _, _ = self.Project.get_data_points(measurement_id, 'Metal electrode work function') if len(WF_arr) > 0: WF = WF_arr[-1] except: metal_label = None WF = 0 if Metal is None: if metal_label is None: raise Exception('Metal electriode is needed for Schottky diode') else: self.Metal = MetalElectrode(metal_label, WF) else: if Metal.label != metal_label: self.Project.add_project_info_if_changed('Schottky electrode', Metal.label) _, WF = self.Project.add_monitored_datapoint(measurement_type, 'Electrode work function', 'Work function of metal electrode', self.tool_id, 'Metal electrode work function', 'Metal WF', 'J', None, to_numeric(Metal.WF)) self.Metal = Metal
def energy_level(self, temperature, semiconductor, charge_state_idx=0, electron_volts=False): """ Trap energy level measured from Ec (Conduction band) towards Ev (Valence band) """ if isinstance(temperature, (tuple, list, np.ndarray)): energy_level_wrap = partial(self.energy_level, semiconductor=semiconductor, charge_state_idx=charge_state_idx, electron_volts=electron_volts) return np.array(map(energy_level_wrap, temperature)) band_gap = semiconductor.band_gap(temperature, symbolic=False, electron_volts=False) try: level = self.charge_states[charge_state_idx][1].subs( 'Eg', band_gap) except AttributeError: level = self.charge_states[charge_state_idx][1] if electron_volts: level /= q return to_numeric(level)
def capture_cross_sections(self, temperature): """ Calculates temperature dependent capture cross section :param temperature: Temperature in K :return: Temperature dependent capture cross sections """ energy_scale = to_numeric(k * temperature) exp_term_e = np.exp(-self.electron_capture_cross_section_activation_energy / energy_scale) exp_term_h = np.exp(-self.hole_capture_cross_section_activation_energy / energy_scale) return self.electron_capture_cross_section * exp_term_e, self.hole_capture_cross_section * exp_term_h
def dn_carriers_dEf_theory(self, Psi=Psi_zero, z=0): k_n = to_numeric(k) Ef = self.EfEc(Psi, z, eV=False) Eg = self.Semiconductor.band_gap(self.T, symbolic=False, electron_volts=False) F_n = np.exp(-Ef / (k_n * self.T)) F_p = np.exp((Ef - Eg) / (k_n * self.T)) dn = np.float(self.Semiconductor.Nc(self.T, symbolic=False)) / (k_n * self.T) * F_n dp = np.float(-self.Semiconductor.Nv(self.T, symbolic=False)) / to_numeric(k * self.T) * F_p if isinstance(z, np.ndarray): idx = np.where(z < self.DeadLayer)[0] if idx.size > 0: dn[idx] = 0.0 dp[idx] = 0.0 idx = np.where(z < self.FieldInversionPoint)[0] if idx.size > 0: dn[idx] = 0.0 dp[idx] = 0.0 elif z < self.DeadLayer or z < self.FieldInversionPoint: dn = 0 dp = 0 return dn, dp
def capture_cross_sections(self, temperature): """ Calculates temperature dependent capture cross section :param temperature: Temperature in K :return: Temperature dependent capture cross sections """ energy_scale = to_numeric(k * temperature) exp_term_e = np.exp( -self.electron_capture_cross_section_activation_energy / energy_scale) exp_term_h = np.exp( -self.hole_capture_cross_section_activation_energy / energy_scale) return self.electron_capture_cross_section * exp_term_e, self.hole_capture_cross_section * exp_term_h
def annotate_energies(ax, SchDiode, arrow_z_pos, Psi, Vd, SchottkyEffect, metal_size, draw_metal, eV, draw_energies): annot_fig = {} if draw_energies: barrier_style = [1, 'black', '--'] z_barrier, phi_bn, _, phi_b = SchDiode.get_phi_bn(Psi, Vd, SchottkyEffect=SchottkyEffect) if not eV: phi_bn *= to_numeric(q) phi_b *= to_numeric(q) z_barrier *= 1e6 phi_bn_z = -metal_size[0] / 2 phi_b_z = arrow_z_pos * 1e6 allowed_band = SchDiode.Ec(Psi, Vd, np.float(phi_b_z * 1e-6), eV=eV) if SchDiode.Semiconductor.dop_type == 'p': phi_bn *= -1 allowed_band = SchDiode.Ev(Psi, Vd, np.float(phi_b_z * 1e-6), eV=eV) if draw_metal: phi_bn_z = -metal_size[0] - 0.1 annot_fig['zero_line'] = ax.hlines(0, phi_bn_z, 0, linewidth=barrier_style[0], color=barrier_style[1], linestyle=barrier_style[2]) annot_fig['phi_bn_line'] = ax.hlines(phi_bn, phi_bn_z, z_barrier, linewidth=barrier_style[0], color=barrier_style[1], linestyle=barrier_style[2]) annot_fig['phi_bn_arrows'] = ax.annotate('', xy=(phi_bn_z, phi_bn), xycoords='data', xytext=(phi_bn_z, 0), textcoords='data', arrowprops={'arrowstyle': '<->'}) annot_fig['phi_bn_annot'] = ax.annotate('$q\phi_{bn}$', xy=(phi_bn_z, phi_bn / 2), xycoords='data', xytext=(-5, 0), textcoords='offset points', horizontalalignment='right', verticalalignment='top') annot_fig['phi_b_line'] = ax.hlines(phi_bn, z_barrier, phi_b_z, linewidth=barrier_style[0], color=barrier_style[1], linestyle=barrier_style[2]) annot_fig['phi_b_arrows'] = ax.annotate('', xy=(phi_b_z, phi_bn), xycoords='data', xytext=(phi_b_z, allowed_band), textcoords='data', arrowprops={'arrowstyle': '<->'}) annot_fig['phi_b_annot'] = ax.annotate('$q\phi_{b}$', xy=(phi_b_z, (phi_bn + allowed_band) / 2), xycoords='data', xytext=(5, 0), textcoords='offset points', horizontalalignment='left', verticalalignment='top') return annot_fig
def d_rho_dDPsi(z, Psi): dn_nodes, dp_nodes = SchDiode.dn_carriers_dEf_theory(Psi, z) if SchDiode.Semiconductor.dop_type == 'n': dp_nodes = np.zeros_like(z) elif SchDiode.Semiconductor.dop_type == 'p': dn_nodes = np.zeros_like(z) d_carriers_nodes = dn_nodes - dp_nodes dN_dopants_nodes = np.zeros_like(z) for dopant in SchDiode.Semiconductor.dopants: dN_dopants_nodes += dopant.concentration(z) * ( dopant.charge_states[1][0] - dopant.charge_states[0][0]) * dopant.dF(z) dN_BI_nodes = np.zeros_like(z) for BI in SchDiode.Semiconductor.bonding_interfaces: dN_BI_nodes += smooth_dd(z - BI.depth, BI.smooth_dirac_epsilon) * BI.d_density_of_charge return (d_carriers_nodes - dN_dopants_nodes - dN_BI_nodes) * to_numeric(q ** 2) / Eps0Eps
def energy_level(self, temperature, semiconductor, charge_state_idx=0, electron_volts=False): """ Trap energy level measured from Ec (Conduction band) towards Ev (Valence band) """ if isinstance(temperature, (tuple, list, np.ndarray)): energy_level_wrap = partial(self.energy_level, semiconductor=semiconductor, charge_state_idx=charge_state_idx, electron_volts=electron_volts) return np.array(map(energy_level_wrap, temperature)) band_gap = semiconductor.band_gap(temperature, symbolic=False, electron_volts=False) try: level = self.charge_states[charge_state_idx][1].subs('Eg', band_gap) except AttributeError: level = self.charge_states[charge_state_idx][1] if electron_volts: level /= q return to_numeric(level)
def get_phi_bn(self, Psi=Psi_zero, Va=0, SchottkyEffect=False): chg_sign = 1 if self.Semiconductor.dop_type == 'n' else -1 F = to_numeric(q / (16 * np.pi * epsilon0 * self.Semiconductor.reference['epsilon'])) if not SchottkyEffect: F = 0 def f(z): if F != 0: if not isinstance(z, np.ndarray): return chg_sign * (Psi(z) + chg_sign * F / z) else: return chg_sign * np.array([Psi(zz) + chg_sign * F / zz for zz in z]) else: if not isinstance(z, np.ndarray): return chg_sign * Psi(z) else: #return chg_sign * np.array([Psi(zz) for zz in z]) return chg_sign * Psi(z) z_0 = np.linspace(1e-10, np.float(self.L), num=50) extrema_z = np.zeros_like(z_0) warnings.filterwarnings("ignore", category=RuntimeWarning) for i in range(z_0.size): extrema_z[i] = fmin(f, z_0[i], disp=False) # print extrema_z[i]*1e6 warnings.resetwarnings() extrema = f(extrema_z) # _, (ax1, ax2, ax3) = plt.subplots(3) # ax1.plot(z_0*1e6, f(z_0)) # ax2.plot(z_0*1e6, extrema_z*1e6) # ax3.plot(extrema_z*1e6, extrema) # plt.show() z_max = extrema_z[np.argmin(extrema)] if z_max < 0: z_max = 1e-15 xi = self.Semiconductor.Ech_pot(T=self.T, z=1e3, eV=True, debug=False) Eg = self.Semiconductor.band_gap(self.T, symbolic=False, electron_volts=True) if self.Semiconductor.dop_type == 'n': phi_bn = abs(-f(z_max) + xi + Va) delta_phi = abs(self.Ec(Psi, Va, z_max, eV=True) - phi_bn) phi_b = phi_bn - xi else: phi_bn = abs(f(z_max) + xi - Va - Eg) delta_phi = abs(self.Ev(Psi, Va, z_max, eV=True) - phi_bn) phi_b = phi_bn - Eg + xi # print 'Z_max =', z_max*1e6, 'um' return np.float(z_max), np.float(phi_bn), np.float(delta_phi), np.float(phi_b)
def rho_z_Psi(z, Psi): rho = 0 if carriers_charge: if Psi != Psi_zero: n, p = self.n_carriers_theory(Psi, z) if self.Semiconductor.dop_type == 'n': p = 0 elif self.Semiconductor.dop_type == 'p': n = 0 rho += p - n for dopant in self.Semiconductor.dopants: Nd = dopant.concentration(z) rho += Nd * ( (dopant.charge_states[1][0] - dopant.charge_states[0][0]) * dopant.F(z) + dopant.charge_states[0][ 0]) for BI in self.Semiconductor.bonding_interfaces: rho += smooth_dd(z - BI.depth, BI.smooth_dirac_epsilon) * BI.density_of_charge return to_numeric(q) * rho
def d_rho_dDPsi(z, Psi): dn_nodes, dp_nodes = SchDiode.dn_carriers_dEf_theory(Psi, z) if SchDiode.Semiconductor.dop_type == 'n': dp_nodes = np.zeros_like(z) elif SchDiode.Semiconductor.dop_type == 'p': dn_nodes = np.zeros_like(z) d_carriers_nodes = dn_nodes - dp_nodes dN_dopants_nodes = np.zeros_like(z) for dopant in SchDiode.Semiconductor.dopants: dN_dopants_nodes += dopant.concentration(z) * ( dopant.charge_states[1][0] - dopant.charge_states[0][0]) * dopant.dF(z) dN_BI_nodes = np.zeros_like(z) for BI in SchDiode.Semiconductor.bonding_interfaces: dN_BI_nodes += smooth_dd( z - BI.depth, BI.smooth_dirac_epsilon) * BI.d_density_of_charge return (d_carriers_nodes - dN_dopants_nodes - dN_BI_nodes) * to_numeric(q**2) / Eps0Eps
def Reccurent_Poisson_solver(SchDiode, Psi=Psi_zero, Vd_guess=None, Vd_error=1e-6, equilibrium_filling=True, fast_traps=None, t=mp.inf, initial_condition_id=-1, rho_rel_err=1e-3, max_iter=100, debug=False): recurrent_solver_start_time = time.time() Va = SchDiode.Va kT_eV = to_numeric(k * SchDiode.T / q) measurement_id = add_Electric_Field_Measurement(SchDiode, Va, equilibrium_filling, t, initial_condition_id) Solution_found, Psi_found, E, z_nodes, rho_rel_err_points, Vd, Vd_err, J, J_err, BI_F, dopants_F, measurement_id = load_diode_state( SchDiode, measurement_id, initial_condition_id, debug) if Solution_found: Psi = Psi_found Vd_guess = Vd[-1] J_tmp = J[-1] if max(abs(rho_rel_err_points)) <= rho_rel_err: if debug: print '==> Solution satisfy rel_error condition' return Psi, E, z_nodes, rho_rel_err_points, Vd[0], Vd_err[0], J[0], J_err[ 0], BI_F, dopants_F, measurement_id if debug or 1: print '==> Solution found does not satisfy rel_error condition' print '==> Recalculating...' SchDiode.FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn(Psi, Vd_guess, SchottkyEffect=False) else: if debug: print '==> No solutions found' if Vd_guess is None: Vd_guess = Va PHI_b = abs(SchDiode.V_bi(eV=True)) SchDiode.FieldInversionPoint = 0 else: SchDiode.FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn(Psi, Vd_guess, SchottkyEffect=False) if Va < PHI_b: Vd_guess = Va J_tmp = 0 converged = False current_iter = 0 Vd_tmp_corr = 0 Vd_guess_monitor_length = 5 Vd_guess_monitor = np.arange(Vd_guess_monitor_length, dtype=np.float) Vd_guess_monitor_count = 0 stalled = False while not converged and not stalled and current_iter < max_iter: if debug: print '\nIteration:', current_iter, '**' if debug or 1: print 'Vd_tmp =', Vd_guess if debug: print 'PHI_b =', PHI_b if Vd_guess >= PHI_b: Vd_guess = PHI_b - 1 * kT_eV if debug: print 'Vd_tmp =', Vd_guess Vd_guess_monitor[Vd_guess_monitor_count] = Vd_guess Vd_guess_monitor_count += 1 if Vd_guess_monitor_count == Vd_guess_monitor_length: Vd_guess_monitor_count = 0 if np.unique(Vd_guess_monitor).size == 1: stalled = True print 'Solution process stalled on iteration', current_iter, '!!!' continue nodes_num = int(np.floor(SchDiode.L * 1e6 + 1) * 100) nodes, _ = np.linspace(0, SchDiode.L, num=nodes_num + 1, endpoint=True, retstep=True) Meshes = Poisson_eq_num_solver_amr(SchDiode, nodes, Psi, Vd_guess, equilibrium_filling, fast_traps, max_iterations=50, residual_threshold=rho_rel_err, int_residual_threshold=5e-14, max_level=5, mesh_refinement_threshold=1e-19, debug=debug) z_nodes, Psi_points, rho_rel_err_points = Meshes.flatten(debug=False) dz = np.gradient(z_nodes) E_points = -np.gradient(Psi_points, dz, edge_order=2) Psi = interp_Fn(z_nodes, Psi_points, interp_type='last') E = interp_Fn(z_nodes, E_points, interp_type='last') SchDiode.FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn(Psi, Vd_guess, SchottkyEffect=False) print '*** !!! Inversion pt (calc) !!!', SchDiode.FieldInversionPoint if debug: print 'PHI_b, PHI_bn =', PHI_b, PHI_bn Vd_tmp_corr, J_tmp_corr = SchDiode.ThermionicEmissionCurrent(Va, PHI_bn, debug=True) if debug: print 'V_corr, J =', Vd_tmp_corr, J_tmp_corr Vd_err = abs(Vd_guess - Vd_tmp_corr) J_err = abs(J_tmp - J_tmp_corr) if debug or 1: print 'Vd err =', Vd_err if debug or 1: print 'J err =', J_err, '\n' if Vd_err < Vd_error: converged = True else: Vd_guess = Vd_tmp_corr J_tmp = J_tmp_corr current_iter += 1 Vd = Vd_tmp_corr J = J_tmp if debug: print 'Calculation converged' recurrent_solver_elapsed_time = time.time() - recurrent_solver_start_time if debug: print 'Total recurrent solver execution time =', recurrent_solver_elapsed_time, 's\n' save_diode_state(SchDiode, measurement_id, initial_condition_id, z_nodes, Psi_points, E_points, rho_rel_err_points, Vd, Vd_err, J, J_err, debug) BI_F = {} for BI in SchDiode.Semiconductor.bonding_interfaces: for i, trap in enumerate(BI.dsl_tilt.traps): BI_F[BI.label + '_tilt_' + trap[0].name + '_F'] = BI.dsl_tilt_f[i] for i, trap in enumerate(BI.dsl_twist.traps): BI_F[BI.label + '_twist_' + trap[0].name + '_F'] = BI.dsl_twist_f[i] dopants_F = {} for dopant in SchDiode.Semiconductor.dopants: dopants_F[dopant.name + '_F'] = dopant.F(z_nodes) recurrent_solver_elapsed_time = time.time() - recurrent_solver_start_time if debug: print 'Total recurrent solver execution time =', recurrent_solver_elapsed_time, 's' return Psi, E, z_nodes, rho_rel_err_points, Vd, Vd_err, J, J_err, BI_F, dopants_F, measurement_id
def equilibrium_f(self, temperature, semiconductor, fermi_level_from_conduction_band, electron_volts=False, use_mpmath=False, debug=False): """ Calculates trap equilibrium filling for given Fermi level position :param temperature: Temperature, K :param semiconductor: Semiconductor object :param fermi_level_from_conduction_band: distance from Conduction band to Fermi level :param electron_volts: if True assume all energy values to be in eV :param use_mpmath: if True integration is done using mpmath.quad function instead of numpy.trapz (default) :param debug: if True prints out some debug information :return: equilibrium f between 0 and 1 """ if electron_volts: energy_coefficient = to_numeric(q) energy_unit = 'eV' else: energy_coefficient = 1 energy_unit = 'J' energy_scale = to_numeric(k * temperature) / energy_coefficient conduction_band = 0 fermi_level = conduction_band - fermi_level_from_conduction_band trap_energy_level = self.energy_level(temperature, semiconductor, charge_state_idx=0, electron_volts=electron_volts) if debug: print 'Et = %2.2g ' % ((conduction_band - trap_energy_level)) + energy_unit print 'Ef =', fermi_level, energy_unit g_ratio = self.charge_states[0][2] / self.charge_states[1][2] if self.energy_distribution_function in energy_distribution_functions['Single Level']: fermi_level_grid, = np.meshgrid(fermi_level) exp_arg = (np.float(conduction_band - trap_energy_level) - fermi_level_grid) / energy_scale exp_term = np.exp(exp_arg) f = 1 / (1 + g_ratio * exp_term) else: energy = sym.symbols('E') energy_distribution = self.energy_distribution.subs([('Et', trap_energy_level * energy_coefficient), ('Ecb', conduction_band * energy_coefficient)]) energy_distribution = energy_distribution.subs(q, to_numeric(q)) if not use_mpmath: if debug: print 'Numeric integration (numpy.trapz)' energy_range = centered_linspace(conduction_band - trap_energy_level, 10 * to_numeric(self.energy_spread) / energy_coefficient, to_numeric(self.energy_spread) / energy_coefficient / 1000) energy_range_grid, fermi_level_grid = np.meshgrid(energy_range, fermi_level) fermi_function = 1 / (1 + g_ratio * np.exp((energy_range_grid - fermi_level_grid) / energy_scale)) energy_distribution_function = sym.lambdify(energy, energy_distribution, 'numpy') integrand_array = energy_distribution_function(energy_range_grid * energy_coefficient) * fermi_function f = np.trapz(integrand_array, energy_range_grid * energy_coefficient, axis=1) else: if debug: print 'Numeric integration (mpmath.quad)' fermi_level_grid, = np.meshgrid(fermi_level) f = np.zeros_like(fermi_level_grid) for i, fermi_level_i in enumerate(fermi_level_grid): fermi_function = fermi(energy, fermi_level_i * energy_coefficient, temperature, g_ratio) fermi_function = fermi_function.subs(k, to_numeric(k)) integrand = sym.lambdify(energy, energy_distribution * fermi_function) f[i] = mp.quad(integrand, [to_numeric(energy_coefficient * (conduction_band - trap_energy_level) - 10 * self.energy_spread), to_numeric(energy_coefficient * (conduction_band - trap_energy_level) - 0.5 * self.energy_spread), to_numeric(energy_coefficient * (conduction_band - trap_energy_level) + 0.5 * self.energy_spread), to_numeric(energy_coefficient * (conduction_band - trap_energy_level) + 10 * self.energy_spread)]) if debug: print 'F =', f if f.size == 1: f = f[0] return f
def emission_rate(self, temperature, semiconductor, f, poole_frenkel_e=None, poole_frenkel_h=None, barrier_lowering_e=None, barrier_lowering_h=None, use_mpmath=False, debug=False): """ Calculate carriers emission rate for bot electrons and holes :param temperature: Temperature, K :param semiconductor: Semiconductor object :param f: Trap occupation from 0.0 to 1.0 :param poole_frenkel_e: emission rate boost due to Poole-Frenkel effect for electron :param poole_frenkel_h: emission rate boost due to Poole-Frenkel effect for electron :param barrier_lowering_e: lowering of activation energy for electrons :param barrier_lowering_h: lowering of activation energy for holes :param use_mpmath: if True integration is done using mpmath.quad function instead of numpy.trapz (default) :param debug: if True prints out some debug information :return: emission_e, emission_h """ if barrier_lowering_e is None: barrier_lowering_e = np.zeros_like(f, dtype=np.float) if barrier_lowering_h is None: barrier_lowering_h = np.zeros_like(f, dtype=np.float) if poole_frenkel_e is None: poole_frenkel_e = np.ones_like(f, dtype=np.float) if poole_frenkel_h is None: poole_frenkel_h = np.ones_like(f, dtype=np.float) energy_scale = to_numeric(k * temperature) conduction_band = 0 band_gap = semiconductor.band_gap(temperature, symbolic=False, electron_volts=False) valence_band = conduction_band - band_gap trap_energy_level_e = np.float( self.energy_level(temperature, semiconductor, charge_state_idx=1, electron_volts=False)) trap_energy_level_h = np.float( self.energy_level(temperature, semiconductor, charge_state_idx=0, electron_volts=False)) trap_energy_level_e_positive = conduction_band - trap_energy_level_e trap_energy_level_h_positive = conduction_band - trap_energy_level_h g_ratio_e = self.charge_states[0][2] / self.charge_states[1][2] g_ratio_h = self.charge_states[1][2] / self.charge_states[0][2] v_e = np.float(semiconductor.v_T('e', temperature, symbolic=False)) v_h = np.float(semiconductor.v_T('h', temperature, symbolic=False)) if debug: print '<v_e> =', v_e, 'm/s' print '<v_h> =', v_h, 'm/s' sigma_n, sigma_p = self.capture_cross_sections(temperature) fore_factor_n = np.float( sigma_n * v_e * semiconductor.Nc(temperature, symbolic=False) * g_ratio_e) fore_factor_p = np.float( sigma_p * v_h * semiconductor.Nv(temperature, symbolic=False) * g_ratio_h) fore_factor_n *= poole_frenkel_e fore_factor_p *= poole_frenkel_h if debug: print 'factor_n =', fore_factor_n print 'factor_p =', fore_factor_p if self.energy_distribution_function in energy_distribution_functions[ 'Single Level']: activation_energy_e = conduction_band - trap_energy_level_e_positive - barrier_lowering_e activation_energy_h = trap_energy_level_h_positive - valence_band - barrier_lowering_h emission_rate_e = fore_factor_n * np.exp( -activation_energy_e / energy_scale) emission_rate_h = fore_factor_p * np.exp( -activation_energy_h / energy_scale) emission_time_constant_e = 1 / emission_rate_e emission_time_constant_h = 1 / emission_rate_h emission_e = emission_rate_e * f emission_h = emission_rate_h * (1 - f) else: quasi_fermi_level = self.f_to_equilibrium_fermi_level( temperature, semiconductor, f, electron_volts=False, use_mpmath=use_mpmath, debug=False) quasi_fermi_level = conduction_band - quasi_fermi_level if debug: print 'Eqf =', quasi_fermi_level / to_numeric(q), 'eV' energy = sym.symbols('E') energy_distribution_e = self.energy_distribution.subs([ ('Et', trap_energy_level_e), ('Ecb', conduction_band) ]) energy_distribution_e = energy_distribution_e.subs( q, to_numeric(q)) energy_distribution_h = self.energy_distribution.subs([ ('Et', trap_energy_level_h), ('Ecb', conduction_band) ]) energy_distribution_h = energy_distribution_h.subs( q, to_numeric(q)) energy_range_e = centered_linspace( conduction_band - trap_energy_level_e, 10 * to_numeric(self.energy_spread), to_numeric(self.energy_spread) / 1000) energy_range_h = centered_linspace( conduction_band - trap_energy_level_h, 10 * to_numeric(self.energy_spread), to_numeric(self.energy_spread) / 1000) energy_distribution_function_e = sym.lambdify( energy, energy_distribution_e, 'numpy') energy_distribution_function_h = sym.lambdify( energy, energy_distribution_h, 'numpy') energy_range_grid_e, barrier_lowering_grid_e = np.meshgrid( energy_range_e, barrier_lowering_e) energy_range_grid_h, barrier_lowering_grid_h = np.meshgrid( energy_range_h, barrier_lowering_h) exp_term_e = np.exp(-(conduction_band - energy_range_grid_e + barrier_lowering_grid_e) / energy_scale) exp_term_h = np.exp( -(energy_range_grid_h - barrier_lowering_grid_h - valence_band) / energy_scale) emission_rate_e = energy_distribution_function_e( energy_range_e) * exp_term_e * fore_factor_n emission_rate_h = energy_distribution_function_h( energy_range_h) * exp_term_h * fore_factor_p emission_rate_e_max = np.max(emission_rate_e, axis=1) emission_rate_h_max = np.max(emission_rate_h, axis=1) emission_time_constant_e = 1 / emission_rate_e_max emission_time_constant_h = 1 / emission_rate_h_max if not use_mpmath: if debug: print 'Numeric integration (numpy.trapz)' energy_range_grid_e, fermi_level_grid_e = np.meshgrid( energy_range_e, quasi_fermi_level) energy_range_grid_h, fermi_level_grid_h = np.meshgrid( energy_range_h, quasi_fermi_level) fermi_function_e = 1 / (1 + g_ratio_e * np.exp( (energy_range_grid_e - fermi_level_grid_e) / energy_scale)) fermi_function_h = 1 - 1 / (1 + g_ratio_h * np.exp( (energy_range_grid_h - fermi_level_grid_h) / energy_scale)) exp_term_e = np.exp(-(conduction_band - energy_range_grid_e + barrier_lowering_e) / energy_scale) exp_term_h = np.exp( -(energy_range_grid_h - barrier_lowering_h - valence_band) / energy_scale) emission_rate_e = energy_distribution_function_e( energy_range_grid_e) * exp_term_e emission_rate_h = energy_distribution_function_h( energy_range_grid_h) * exp_term_h integrand_array_e = emission_rate_e * fermi_function_e integrand_array_h = emission_rate_h * fermi_function_h emission_e = np.trapz(integrand_array_e, energy_range_grid_e, axis=1) * fore_factor_n emission_h = np.trapz(integrand_array_h, energy_range_grid_h, axis=1) * fore_factor_p else: if debug: print 'Numeric integration (mpmath.quad)' exp_term_e = sym.exp( -(conduction_band - energy + barrier_lowering_e) / energy_scale) exp_term_h = sym.exp( -(energy - barrier_lowering_h - valence_band) / energy_scale) quasi_fermi_level_grid, = np.meshgrid(quasi_fermi_level) emission_e = np.zeros_like(quasi_fermi_level_grid) emission_h = np.zeros_like(quasi_fermi_level_grid) for i, quasi_fermi_level_i in enumerate( quasi_fermi_level_grid): fermi_function_e = fermi(energy, quasi_fermi_level_i, temperature, g_ratio_e) fermi_function_e = fermi_function_e.subs(k, to_numeric(k)) fermi_function_h = fermi(quasi_fermi_level_i, energy, temperature, g_ratio_h) fermi_function_h = fermi_function_h.subs(k, to_numeric(k)) integrand_e = sym.lambdify( energy, energy_distribution_e * fermi_function_e * exp_term_e) integrand_h = sym.lambdify( energy, energy_distribution_h * fermi_function_h * exp_term_h) emission_integral_e = mp.quad(integrand_e, [ to_numeric(trap_energy_level_e_positive - 10 * self.energy_spread), to_numeric(trap_energy_level_e_positive - 0.5 * self.energy_spread), to_numeric(trap_energy_level_e_positive + 0.5 * self.energy_spread), to_numeric(trap_energy_level_e_positive + 10 * self.energy_spread) ]) emission_integral_h = mp.quad(integrand_h, [ to_numeric(trap_energy_level_h_positive - 10 * self.energy_spread), to_numeric(trap_energy_level_h_positive - 0.5 * self.energy_spread), to_numeric(trap_energy_level_h_positive + 0.5 * self.energy_spread), to_numeric(trap_energy_level_h_positive + 10 * self.energy_spread) ]) emission_e[i] = np.float(emission_integral_e * fore_factor_n) emission_h[i] = np.float(emission_integral_h * fore_factor_p) if isinstance(emission_e, np.ndarray): if emission_e.size == 1: emission_e = emission_e[0] emission_h = emission_h[0] emission_time_constant_e = emission_time_constant_e[0] emission_time_constant_h = emission_time_constant_h[0] if debug: print 'emission_e =', emission_e if debug: print 'emission_h =', emission_h if debug: print 'emission_tau_e =', emission_time_constant_e if debug: print 'emission_tau_h =', emission_time_constant_e return emission_e, emission_h, emission_time_constant_e, emission_time_constant_h
def energy_distribution_diagram(self, ax, temperature, semiconductor, trap_concentration=1, trap_concentration_units='', fermi_level_from_conduction_band=None, electron_volts=True, fancy_labels=False): if electron_volts: energy_coefficient = to_numeric(q) energy_unit = 'eV' else: energy_coefficient = 1 energy_unit = 'J' energy_scale = to_numeric(k * temperature) / energy_coefficient conduction_band = 0 g_ratio = self.charge_states[0][2] / self.charge_states[1][2] band_gap = semiconductor.band_gap(temperature, symbolic=False, electron_volts=electron_volts) energy_range = np.linspace(-np.float(band_gap), 0, num=1001, endpoint=True) charge_state_idx = 0 trap_energy_level = self.energy_level(temperature, semiconductor, charge_state_idx, electron_volts) if fermi_level_from_conduction_band is None: fermi_level_from_conduction_band = trap_energy_level fermi_level = conduction_band - fermi_level_from_conduction_band if self.energy_distribution_function in energy_distribution_functions[ 'Single Level']: ax.plot(np.zeros_like(energy_range), energy_range, linewidth=2, color='black', linestyle='-') ax_max = ax.get_xlim()[1] if trap_concentration > ax_max: head_length = 0.03 * trap_concentration else: head_length = 0.03 * ax_max ax.arrow(0, conduction_band - trap_energy_level, trap_concentration - head_length, 0, linewidth=2, head_width=head_length, head_length=head_length, fc='black', ec='black') f = self.equilibrium_f(temperature, semiconductor, fermi_level_from_conduction_band, electron_volts) ax.arrow(0, conduction_band - trap_energy_level, trap_concentration * f, 0, linewidth=2, head_width=0, head_length=0, fc='red', ec='red') else: energy = sym.symbols('E') energy_distribution = self.energy_distribution.subs([ ('Et', trap_energy_level * energy_coefficient), ('Ecb', conduction_band * energy_coefficient) ]) energy_distribution = energy_distribution.subs(q, to_numeric(q)) energy_distribution_function = sym.lambdify( energy, energy_distribution, 'numpy') fermi_function = 1 / (1 + g_ratio * np.exp( (energy_range - fermi_level) / energy_scale)) energy_states_distribution = energy_distribution_function( energy_range * energy_coefficient) energy_states_distribution *= trap_concentration * energy_coefficient trapped_carriers_distribution = energy_states_distribution * fermi_function ax.plot(energy_states_distribution, energy_range, linewidth=2, color='black', linestyle='-') # ax.plot(trapped_carriers_distribution, energy_range, linewidth=2, color='red', linestyle='-') ax.plot(fermi_function * max(energy_states_distribution), energy_range, linewidth=2, color='black', linestyle='--') ax.fill_between(trapped_carriers_distribution, 0, energy_range, color='blue', alpha=0.5) ax.arrow(0, fermi_level, ax.get_xlim()[1], 0, linewidth=2, head_width=0, head_length=0, fc='black', ec='black') ax.set_title('Traps distribution in the Band Gap') ax.set_ylabel('Energy, ' + energy_unit) ax.set_ylim([-np.float(band_gap), 0]) x_label = 'Traps distribution, 1/' + energy_unit if trap_concentration_units != 1 and trap_concentration_units != '': x_label += ' * ' + trap_concentration_units ax.set_xlabel(x_label) ax.set_xlim([0, max([ax.get_xlim()[1], trap_concentration])]) if fancy_labels: ticks = ax.get_yticks() labels = np.array([lbl.get_text() for lbl in ax.get_yticklabels()]) # print ticks # print labels if u'Ev' in labels: ticks = np.append(ticks, band_gap - trap_energy_level) labels = np.append(labels, 'Ec-%2.2g' % trap_energy_level) else: #ticks = np.array([0, band_gap, band_gap - trap_energy_level]) ticks = np.array([-band_gap, -trap_energy_level, 0]) labels = np.array(['Ev', 'Ec-%2.2g' % trap_energy_level, 'Ec']) ax.set_yticks(ticks) ax.set_yticklabels(labels)
def __str__(self, *args, **kwargs): return 'Metal: %s, Workfunction: %2.2f eV (%2.2g J)' % (str(self.label), to_numeric(self.WF / q), to_numeric(self.WF))
def Reccurent_Poisson_solver(SchDiode, Psi=Psi_zero, Vd_guess=None, Vd_error=1e-6, equilibrium_filling=True, fast_traps=None, t=mp.inf, initial_condition_id=-1, rho_rel_err=1e-3, max_iter=100, save_to_db=True, debug=False): recurrent_solver_start_time = time.time() Va = SchDiode.Va kT_eV = to_numeric(k * SchDiode.T / q) if save_to_db: measurement_id = add_Electric_Field_Measurement( SchDiode, Va, equilibrium_filling, t, initial_condition_id) Solution_found, Psi_found, E, z_nodes, rho_rel_err_points, Vd, Vd_err, J, J_err, BI_F, dopants_F, measurement_id = load_diode_state( SchDiode, measurement_id, initial_condition_id, debug) else: Solution_found = False measurement_id = -1 if Solution_found: Psi = Psi_found Vd_guess = Vd[-1] J_tmp = J[-1] if max(abs(rho_rel_err_points)) <= rho_rel_err: if debug: print '==> Solution satisfy rel_error condition' return Psi, E, z_nodes, rho_rel_err_points, Vd[0], Vd_err[0], J[ 0], J_err[0], BI_F, dopants_F, measurement_id if debug or 1: print '==> Solution found does not satisfy rel_error condition' print '==> Recalculating...' SchDiode.FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn( Psi, Vd_guess, SchottkyEffect=False) else: if debug: print '==> No solutions found' if Vd_guess is None: Vd_guess = Va PHI_b = abs(SchDiode.V_bi(eV=True)) SchDiode.FieldInversionPoint = 0 else: SchDiode.FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn( Psi, Vd_guess, SchottkyEffect=False) if Va < PHI_b: Vd_guess = Va J_tmp = 0 converged = False current_iter = 0 Vd_tmp_corr = 0 Vd_guess_monitor_length = 5 Vd_guess_monitor = np.arange(Vd_guess_monitor_length, dtype=np.float) Vd_guess_monitor_count = 0 stalled = False while not converged and not stalled and current_iter < max_iter: if debug: print '\nIteration:', current_iter, '**' if debug or 1: print 'Vd_tmp =', Vd_guess if debug: print 'PHI_b =', PHI_b if Vd_guess >= PHI_b: Vd_guess = PHI_b - 1 * kT_eV if debug: print 'Vd_tmp =', Vd_guess Vd_guess_monitor[Vd_guess_monitor_count] = Vd_guess Vd_guess_monitor_count += 1 if Vd_guess_monitor_count == Vd_guess_monitor_length: Vd_guess_monitor_count = 0 if np.unique(Vd_guess_monitor).size == 1: stalled = True print 'Solution process stalled on iteration', current_iter, '!!!' continue nodes_num = int(np.floor(SchDiode.L * 1e6 + 1) * 100) nodes, _ = np.linspace(0, SchDiode.L, num=nodes_num + 1, endpoint=True, retstep=True) Meshes = Poisson_eq_num_solver_amr(SchDiode, nodes, Psi, Vd_guess, equilibrium_filling, fast_traps, max_iterations=50, residual_threshold=rho_rel_err, int_residual_threshold=5e-14, max_level=5, mesh_refinement_threshold=1e-19, debug=debug) z_nodes, Psi_points, rho_rel_err_points = Meshes.flatten(debug=False) E_points = -np.gradient(Psi_points, z_nodes, edge_order=2) Psi = interp_Fn(z_nodes, Psi_points, interp_type='last') E = interp_Fn(z_nodes, E_points, interp_type='last') SchDiode.FieldInversionPoint, PHI_bn, _, PHI_b = SchDiode.get_phi_bn( Psi, Vd_guess, SchottkyEffect=False) print '*** !!! Inversion pt (calc) !!!', SchDiode.FieldInversionPoint if debug: print 'PHI_b, PHI_bn =', PHI_b, PHI_bn Vd_tmp_corr, J_tmp_corr = SchDiode.ThermionicEmissionCurrent( Va, PHI_bn, debug=True) if debug: print 'V_corr, J =', Vd_tmp_corr, J_tmp_corr Vd_err = abs(Vd_guess - Vd_tmp_corr) J_err = abs(J_tmp - J_tmp_corr) if debug or 1: print 'Vd err =', Vd_err if debug or 1: print 'J err =', J_err, '\n' if Vd_err < Vd_error: converged = True else: Vd_guess = Vd_tmp_corr J_tmp = J_tmp_corr current_iter += 1 Vd = Vd_tmp_corr J = J_tmp if debug: print 'Calculation converged' recurrent_solver_elapsed_time = time.time() - recurrent_solver_start_time if debug: print 'Total recurrent solver execution time =', recurrent_solver_elapsed_time, 's\n' if save_to_db: save_diode_state(SchDiode, measurement_id, initial_condition_id, z_nodes, Psi_points, E_points, rho_rel_err_points, Vd, Vd_err, J, J_err, debug) BI_F = {} for BI in SchDiode.Semiconductor.bonding_interfaces: for i, trap in enumerate(BI.dsl_tilt.traps): BI_F[BI.label + '_tilt_' + trap[0].name + '_F'] = BI.dsl_tilt_f[i] for i, trap in enumerate(BI.dsl_twist.traps): BI_F[BI.label + '_twist_' + trap[0].name + '_F'] = BI.dsl_twist_f[i] dopants_F = {} for dopant in SchDiode.Semiconductor.dopants: dopants_F[dopant.name + '_F'] = dopant.F(z_nodes) recurrent_solver_elapsed_time = time.time() - recurrent_solver_start_time if debug: print 'Total recurrent solver execution time =', recurrent_solver_elapsed_time, 's' return Psi, E, z_nodes, rho_rel_err_points, Vd, Vd_err, J, J_err, BI_F, dopants_F, measurement_id
def BandsBendingDiagram(ax, SchDiode, Psi=Psi_zero, Vd=0, z=0, BI_F={}, dopants_F={}, eV=True, draw_metal=True, label_bands=True, fancy_ticks=True, SchottkyEffect=False, draw_energies=False): print 'BB Diagram Start' Z, Ec, Ev, Ef, Eg, MirrEnergy, FieldInversionPoint = BandsBending_prepare_data(SchDiode, Psi, Vd, z, eV, SchottkyEffect, draw_metal) bands_style = [2, 'black', '-'] Ef_style = [1, 'black', ':'] lbl_style = [12, 'black'] inv = ax.transData.inverted() dxdy = inv.transform(ax.transData.transform((0, 0)) + np.array([bands_style[0], bands_style[0]])) metal_size = [0.2, 1] # um x eV BandsBending_draw_metal(ax, SchDiode, metal_size, bands_style, lbl_style, draw_energies, draw_metal) arrow_z_pos = np.max(Z) / 2 annot_fig = annotate_energies(ax, SchDiode, arrow_z_pos, Psi, Vd, SchottkyEffect, metal_size, draw_metal, eV, draw_energies) bands_lines = {} if SchDiode.Semiconductor.dop_type == 'n': bands_lines['Ec'], = ax.plot(Z * 1e6, Ec - MirrEnergy, linewidth=bands_style[0], color=bands_style[1], linestyle=bands_style[2]) bands_lines['Ev'], = ax.plot(Z * 1e6, Ev, linewidth=bands_style[0], color=bands_style[1], linestyle=bands_style[2]) else: bands_lines['Ec'], = ax.plot(Z * 1e6, Ec, linewidth=bands_style[0], color=bands_style[1], linestyle=bands_style[2]) bands_lines['Ev'], = ax.plot(Z * 1e6, Ev + MirrEnergy, linewidth=bands_style[0], color=bands_style[1], linestyle=bands_style[2]) Ef_idx = np.where(Z > FieldInversionPoint) # Ef_idx = np.where(Z >= 0) bands_lines['Ef'], = ax.plot(Z[Ef_idx] * 1e6, Ef[Ef_idx], linewidth=Ef_style[0], color=Ef_style[1], linestyle=Ef_style[2]) bands_labels = {} if label_bands: trans = transforms.blended_transform_factory(ax.transAxes, ax.transData) if SchDiode.Semiconductor.dop_type == 'n': lbl_pos_y = [Ec[-1] + 3 * dxdy[1], Ev[-1] - 3 * dxdy[1], Ef[-1] - 3 * dxdy[1]] lbl_algn = ['bottom', 'top', 'top'] else: lbl_pos_y = [Ec[-1] + 3 * dxdy[1], Ev[-1] - 3 * dxdy[1], Ef[-1] + 3 * dxdy[1]] lbl_algn = ['bottom', 'top', 'bottom'] bands_labels['Ec'] = ax.text(0.9, lbl_pos_y[0], 'Ec', verticalalignment=lbl_algn[0], horizontalalignment='left', transform=trans, color=lbl_style[1], fontsize=lbl_style[0]) bands_labels['Ev'] = ax.text(0.9, lbl_pos_y[1], 'Ev', verticalalignment=lbl_algn[1], horizontalalignment='left', transform=trans, color=lbl_style[1], fontsize=lbl_style[0]) bands_labels['Ef'] = ax.text(0.9, lbl_pos_y[2], 'Ef', verticalalignment=lbl_algn[2], horizontalalignment='left', transform=trans, color=lbl_style[1], fontsize=lbl_style[0]) dopant_lines = {} for dopant in SchDiode.Semiconductor.dopants: dopant_style = [1, 'black', '--'] threshold_dopant = 0.05 if eV else to_numeric(q) * 0.05 level = dopant.energy_level(SchDiode.T, SchDiode.Semiconductor, electron_volts=eV) if level > threshold_dopant and level < (Eg - threshold_dopant): if draw_metal: dopant_lines[dopant.name], = ax.plot(Z * 1e6, np.append(Ec[1], Ec[1:]) - level, linewidth=dopant_style[0], color=dopant_style[1], linestyle=dopant_style[2]) else: dopant_lines[dopant.name], = ax.plot(Z * 1e6, Ec - level, linewidth=dopant_style[0], color=dopant_style[1], linestyle=dopant_style[2]) BI_levels = {} for j, BI in enumerate(SchDiode.Semiconductor.bonding_interfaces): for i, trap in enumerate(BI.dsl_tilt.traps): charge_state_idx = 0 level = trap[0].energy_level(SchDiode.T, SchDiode.Semiconductor, charge_state_idx, electron_volts=eV) print 'Et =', level, 'eV' F_i = BI_F[BI.label + '_tilt_' + trap[0].name + '_F'] gray = (1 - np.float(F_i), 1 - np.float(F_i), 1 - np.float(F_i)) BI_levels[str(j) + '-tilt-' + trap[0].name] = ax.scatter(BI.depth * 1e6, SchDiode.Ec(Psi, Vd, BI.depth, eV=True) - level, s=40, edgecolors='black', c=gray[0], vmin=0, vmax=1, cmap=cm.gray) for i, trap in enumerate(BI.dsl_twist.traps): charge_state_idx = 0 level = trap[0].energy_level(SchDiode.T, SchDiode.Semiconductor, charge_state_idx, electron_volts=eV) print 'Et =', level, 'eV' F_i = BI_F[BI.label + '_twist_' + trap[0].name + '_F'] gray = (1 - np.float(F_i), 1 - np.float(F_i), 1 - np.float(F_i)) BI_levels[str(j) + '-twist-' + trap[0].name] = ax.scatter(BI.depth * 1e6, SchDiode.Ec(Psi, Vd, BI.depth, eV=True) - level, s=40, edgecolors='black', c=gray[0], vmin=0, vmax=1, cmap=cm.gray) BandsBending_prepare_axes(ax, eV) Z_coordinate_ticks(ax, SchDiode, fancy_ticks) print 'BB Diagram Stop' return bands_lines, bands_labels, annot_fig, dopant_lines, BI_levels
def update_frame(i): try: V_label.set_text('Va = %2.2f V, Vd = %2.2f V' % (Va[i], Vd[i])) T_label.set_text('T = %3.2f K' % T[i]) Z_coordinate_ticks(ax, SchDiode, fancy_ticks) Z, Ec, Ev, Ef, Eg, MirrEnergy, FieldInversionPoint = BandsBending_prepare_data(SchDiode, Psi[i], Vd[i], z, eV, SchottkyEffect, draw_metal) if SchDiode.Semiconductor.dop_type == 'n': bands_lines['Ec'].set_data(Z * 1e6, (Ec - MirrEnergy)) bands_lines['Ev'].set_data(Z * 1e6, Ev) else: bands_lines['Ec'].set_data(Z * 1e6, Ec) bands_lines['Ev'].set_data(Z * 1e6, Ev + MirrEnergy) Ef_idx = np.where(Z > FieldInversionPoint) # Ef_idx = np.where(Z >= 0) bands_lines['Ef'].set_data(Z[Ef_idx] * 1e6, Ef[Ef_idx]) if label_bands: inv = ax.transData.inverted() dxdy = inv.transform(ax.transData.transform((0, 0)) + np.array([2, 2])) # trans = transforms.blended_transform_factory(ax.transAxes, ax.transData) if SchDiode.Semiconductor.dop_type == 'n': lbl_pos_y = [Ec[-1] + 3 * dxdy[1], Ev[-1] - 3 * dxdy[1], Ef[-1] - 3 * dxdy[1]] else: lbl_pos_y = [Ec[-1] + 3 * dxdy[1], Ev[-1] - 3 * dxdy[1], Ef[-1] + 3 * dxdy[1]] bands_labels['Ec'].set_y(lbl_pos_y[0]) bands_labels['Ev'].set_y(lbl_pos_y[1]) bands_labels['Ef'].set_y(lbl_pos_y[2]) bands_labels['Ec'].set_text('Ec') bands_labels['Ev'].set_text('Ev') bands_labels['Ef'].set_text('Ef') for dopant in SchDiode.Semiconductor.dopants: threshold_dopant = 0.05 if eV else to_numeric(q) * 0.05 level = dopant.energy_level(SchDiode.T, SchDiode.Semiconductor, electron_volts=eV) if level > threshold_dopant and level < (Eg - threshold_dopant): if draw_metal: dopant_lines[dopant.name].set_data(Z * 1e6, np.append(Ec[1], Ec[1:]) - level) else: dopant_lines[dopant.name].set_data(Z * 1e6, Ec - level) for j, BI in enumerate(SchDiode.Semiconductor.bonding_interfaces): for l, trap in enumerate(BI.dsl_tilt.traps): charge_state_idx = 0 level = trap[0].energy_level(SchDiode.T, SchDiode.Semiconductor, charge_state_idx, electron_volts=eV) # print 'Et =', level, 'eV' F_l = BI_F[i][BI.label + '_tilt_' + trap[0].name + '_F'] gray = np.array([1 - np.float(F_l), 1 - np.float(F_l), 1 - np.float(F_l), 1.0]) # print gray # gray = np.array([(1 - np.float(BI.dsl_tilt_f[l]), 1 - np.float(BI.dsl_tilt_f[l]), 1 - np.float(BI.dsl_tilt_f[l]))]) BI_levels[str(j) + '-tilt-' + trap[0].name].set_offsets( np.array([BI.depth * 1e6, SchDiode.Ec(Psi[i], Vd[i], BI.depth, eV=True) - level])) BI_levels[str(j) + '-tilt-' + trap[0].name].set_array(gray) for l, trap in enumerate(BI.dsl_twist.traps): charge_state_idx = 0 level = trap[0].energy_level(SchDiode.T, SchDiode.Semiconductor, charge_state_idx, electron_volts=eV) # print 'Et =', level, 'eV' F_l = BI_F[i][BI.label + '_twist_' + trap[0].name + '_F'] gray = (1 - np.float(F_l), 1 - np.float(F_l), 1 - np.float(F_l)) # gray = np.array([(1 - np.float(BI.dsl_twist_f[l]), 1 - np.float(BI.dsl_twist_f[l]), 1 - np.float(BI.dsl_twist_f[l]))]) BI_levels[str(j) + '-twist-' + trap[0].name].set_offsets( np.array([BI.depth * 1e6, SchDiode.Ec(Psi[i], Vd[i], BI.depth, eV=True) - level])) # BI_levels[str(j)+'-twist-'+trap[0].name].set_array(gray) except Exception as e: print '!!! ====>', e pass fig_list = bands_lines.values() fig_list.extend(dopant_lines.values()) fig_list.extend(bands_labels.values()) fig_list.extend([V_label, T_label]) fig_list.extend(BI_levels.values()) return fig_list
def f_to_equilibrium_fermi_level(self, temperature, semiconductor, f, electron_volts=False, use_mpmath=False, parallel=False, debug=False): """ Calculates equilibrium Fermi level position for given occupation of the Trap F :param temperature: Temperature, K :param semiconductor: Semiconductor object :param f: Trap occupation from 0.0 to 1.0 :param electron_volts: if True assume all energy values to be in eV :param use_mpmath: if True integration is done using mpmath.quad function instead of numpy.trapz (default) :param debug: if True prints out some debug information :return: Fermi level as distance from Conduction band to Fermi level In calculation we use eV since solver has much better stability in smaller order numbers """ energy_unit = 'eV' energy_coefficient = to_numeric(q) trap_energy_level = self.energy_level(temperature, semiconductor, charge_state_idx=0, electron_volts=True) f_grid, = np.meshgrid(f) #print f_grid def equation(fermi_level_from_conduction_band, f): test_f = self.equilibrium_f(temperature, semiconductor, fermi_level_from_conduction_band, electron_volts=True, use_mpmath=use_mpmath, debug=debug) residual = f - test_f if debug: print 'Fermi level type:', type( fermi_level_from_conduction_band) print 'Test F =', test_f print 'F residual =', residual return residual def solver(args): equation, lower_boundary, upper_boundary, initial_guess, f, use_mpmath = args if not use_mpmath: warnings.filterwarnings('ignore') solution = root(equation, initial_guess, args=f, method='hybr') solution = solution.x[0] warnings.resetwarnings() else: equation_wrap = partial(equation, f=f) try: solution = mp.findroot(equation_wrap, (lower_boundary, upper_boundary), maxsteps=1000, solver='anderson', tol=5e-16) except ValueError as err: print err print 'Lowering tolerance to 5e-6' solution = mp.findroot(equation_wrap, (lower_boundary, upper_boundary), maxsteps=1000, solver='anderson', tol=5e-6) solution = np.float(solution) return solution fermi_level_lower_boundary = abs( to_numeric(trap_energy_level - 2 * self.energy_spread / energy_coefficient)) fermi_level_upper_boundary = abs( to_numeric(trap_energy_level + 2 * self.energy_spread / energy_coefficient)) if debug: print 'Fermi level lower boundary = %2.2g ' % fermi_level_lower_boundary + energy_unit print 'Fermi level upper boundary = %2.2g ' % fermi_level_upper_boundary + energy_unit args = np.empty((len(f_grid), 6), dtype=object) args[:, 0] = equation args[:, 1] = fermi_level_lower_boundary args[:, 2] = fermi_level_upper_boundary args[:, 3] = (fermi_level_lower_boundary + fermi_level_upper_boundary) / 2 args[:, 4] = f_grid args[:, 5] = use_mpmath if parallel: try: from pathos.pools import ProcessPool as Pool pool = Pool() solutions = np.array(pool.map(solver, args)) except ImportError: print 'Parallel calculation needs pathos! Using standard map() instead.' solutions = np.array(map(solver, args)) else: solutions = np.array(map(solver, args)) if not electron_volts: solutions *= energy_coefficient return solutions
def f_to_equilibrium_fermi_level(self, temperature, semiconductor, f, electron_volts=False, use_mpmath=False, parallel=False, debug=False): """ Calculates equilibrium Fermi level position for given occupation of the Trap F :param temperature: Temperature, K :param semiconductor: Semiconductor object :param f: Trap occupation from 0.0 to 1.0 :param electron_volts: if True assume all energy values to be in eV :param use_mpmath: if True integration is done using mpmath.quad function instead of numpy.trapz (default) :param debug: if True prints out some debug information :return: Fermi level as distance from Conduction band to Fermi level In calculation we use eV since solver has much better stability in smaller order numbers """ energy_unit = 'eV' energy_coefficient = to_numeric(q) trap_energy_level = self.energy_level(temperature, semiconductor, charge_state_idx=0, electron_volts=True) f_grid, = np.meshgrid(f) #print f_grid def equation(fermi_level_from_conduction_band, f): test_f = self.equilibrium_f(temperature, semiconductor, fermi_level_from_conduction_band, electron_volts=True, use_mpmath=use_mpmath, debug=debug) residual = f - test_f if debug: print 'Fermi level type:', type(fermi_level_from_conduction_band) print 'Test F =', test_f print 'F residual =', residual return residual def solver(args): equation, lower_boundary, upper_boundary, initial_guess, f, use_mpmath = args if not use_mpmath: warnings.filterwarnings('ignore') solution = root(equation, initial_guess, args=f, method='hybr') solution = solution.x[0] warnings.resetwarnings() else: equation_wrap = partial(equation, f=f) try: solution = mp.findroot(equation_wrap, (lower_boundary, upper_boundary), maxsteps=1000, solver='anderson', tol=5e-16) except ValueError as err: print err print 'Lowering tolerance to 5e-6' solution = mp.findroot(equation_wrap, (lower_boundary, upper_boundary), maxsteps=1000, solver='anderson', tol=5e-6) solution = np.float(solution) return solution fermi_level_lower_boundary = abs(to_numeric(trap_energy_level - 2 * self.energy_spread / energy_coefficient)) fermi_level_upper_boundary = abs(to_numeric(trap_energy_level + 2 * self.energy_spread / energy_coefficient)) if debug: print 'Fermi level lower boundary = %2.2g ' % fermi_level_lower_boundary + energy_unit print 'Fermi level upper boundary = %2.2g ' % fermi_level_upper_boundary + energy_unit args = np.empty((len(f_grid), 6), dtype=object) args[:, 0] = equation args[:, 1] = fermi_level_lower_boundary args[:, 2] = fermi_level_upper_boundary args[:, 3] = (fermi_level_lower_boundary + fermi_level_upper_boundary) / 2 args[:, 4] = f_grid args[:, 5] = use_mpmath if parallel: try: from pathos.pools import ProcessPool as Pool pool = Pool() solutions = np.array(pool.map(solver, args)) except ImportError: print 'Parallel calculation needs pathos! Using standard map() instead.' solutions = np.array(map(solver, args)) else: solutions = np.array(map(solver, args)) if not electron_volts: solutions *= energy_coefficient return solutions
def traps_kinetics(schottky_diode, initial_condition_id, delta_t_min, delta_t_max, t_stop, fast_traps=None, rho_rel_err=1e-1, df_threshold=1e-3, debug=False, debug_plot=False): t_points = [] potential_t = [] field_d = [] z_t = [] diode_voltage_drop_t = [] current_density_t = [] bonding_interfaces_f_t = [] dopants_f_t = [] ic_found, potential, z_nodes, _, _, _, _, _, _, _, _, _ = Poisson.load_diode_state(schottky_diode, initial_condition_id, debug=True) if not ic_found: raise(Exception('Initial condition not found. Please check everything')) if debug_plot: plt.ion() axes = Visual.prepare_debug_axes(['Potential', 'Dopants', 'Localized traps']) potential_lines = Visual.create_lines(axes['Potential'], ['Start potential', 'Current potential']) dopants_lines_names = [dopant.name + '_F' for dopant in schottky_diode.Semiconductor.dopants] localized_traps_lines_names = [] for bi in schottky_diode.Semiconductor.bonding_interfaces: localized_traps_lines_names += [bi.label + '_tilt_' + trap[0].name + '_F' for trap in bi.dsl_tilt.traps] localized_traps_lines_names += [bi.label + '_twist_' + trap[0].name + '_F' for trap in bi.dsl_twist.traps] dopants_lines = Visual.create_lines(axes['Dopants'], dopants_lines_names) localized_traps_lines = Visual.create_lines(axes['Localized traps'], localized_traps_lines_names) t = 0 last_state_id = initial_condition_id if fast_traps is None: fast_traps = [] while t <= t_stop: print 't =', t potential, field, z_nodes, _, \ diode_voltage_drop, _, current_density, _, \ bonding_interface_f, dopants_f, \ last_state_id = Poisson.Reccurent_Poisson_solver(schottky_diode, potential, equilibrium_filling=False, fast_traps=fast_traps, t=t, initial_condition_id=last_state_id, rho_rel_err=rho_rel_err, max_iter=100, debug=False) t_points.append(t) potential_t.append(potential) field_d.append(field) z_t.append(z_nodes) diode_voltage_drop_t.append(diode_voltage_drop) current_density_t.append(current_density) bonding_interfaces_f_t.append(bonding_interface_f) dopants_f_t.append(dopants_f) fermi_level = schottky_diode.EfEc(potential, z_nodes, eV=False) if debug_plot: if t == 0: potential_lines['Start potential'].set_data(z_nodes * 1e6, -potential(z_nodes)) potential_lines['Current potential'].set_data(z_nodes * 1e6, -potential(z_nodes)) for dopant_line_name in dopants_lines_names: dopants_lines[dopant_line_name].set_data(z_nodes * 1e6, dopants_f[dopant_line_name]) for localized_trap_line_name in localized_traps_lines_names: localized_trap_f_t = [] for bonding_interfaces_f_t_i in bonding_interfaces_f_t: localized_trap_f_t.append(bonding_interfaces_f_t_i[localized_trap_line_name]) localized_traps_lines[localized_trap_line_name].set_data(t_points, localized_trap_f_t) Visual.autoscale_axes(axes) plt.draw() dt = delta_t_max if t_stop - t > delta_t_max else t_stop - t if dt == 0: break if debug: print '\n\nT = %2.2f K, t = %2.2g s, dt = %2.2g s' % (schottky_diode.T, t, dt) n, p = schottky_diode.n_carriers_theory(potential, z_nodes) if schottky_diode.Semiconductor.dop_type == 'n': p = np.zeros_like(p) elif schottky_diode.Semiconductor.dop_type == 'p': n = np.zeros_like(n) #fast_traps = [] dopants_skip_list = [] df_dopants = {} for dopant in schottky_diode.Semiconductor.dopants: dopant_key = dopant.name + '_F' if dopant.name in fast_traps: if debug: print 'This dopant is in a fast-traps list, skipping.' # dopant_f = dopant.equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level, # electron_volts=False, debug=False) # dopant.set_F_interp(z_nodes, dopant_f) # dopant.set_dF_interp(z_nodes, np.zeros_like(dopant_f)) # fast_traps.append(dopant.name) dopants_skip_list.append(dopant_key) continue poole_frenkel_e = 1.0 poole_frenkel_h = 1.0 barrier_lowering_e = np.zeros_like(n, dtype=np.float) barrier_lowering_h = np.zeros_like(p, dtype=np.float) df_dt, tau = dopant.df_dt(schottky_diode.T, schottky_diode.Semiconductor, dopants_f[dopant_key], n, p, poole_frenkel_e=poole_frenkel_e, poole_frenkel_h=poole_frenkel_h, barrier_lowering_e=barrier_lowering_e, barrier_lowering_h=barrier_lowering_h, use_mpmath=False, debug=False) df_dopants[dopant_key] = df_dt max_dt = df_threshold / np.max(np.abs(df_dt)) if debug: print '\nDopant:', dopant.name print 'time constant %2.2g s' % tau print 'Max dF:', np.max(np.abs(df_dt)), 'th:', df_threshold print 'Max dt:', max_dt, 'dt:', dt if dt > max_dt > delta_t_min: if debug: print 'Setting dt to', max_dt dt = max_dt elif max_dt < delta_t_min: if debug: print 'Traps are too fast. Setting dopant occupation to equilibrium value' dopant_f = dopant.equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level, electron_volts=False, debug=False) dopant.set_F_interp(z_nodes, dopant_f) dopant.set_dF_interp(z_nodes, np.zeros_like(dopant_f)) # fast_traps.append(dopant.name) dopants_skip_list.append(dopant_key) localized_traps_skip_list = [] df_bonding_interfaces = {} for bi in schottky_diode.Semiconductor.bonding_interfaces: fermi_level_at_bi = schottky_diode.EfEc(potential, bi.depth, eV=False) electric_field_at_bi = field(bi.depth) n_bi, p_bi = schottky_diode.n_carriers_theory(potential, bi.depth) if schottky_diode.Semiconductor.dop_type == 'n': p_bi = 0.0 elif schottky_diode.Semiconductor.dop_type == 'p': n_bi = 0.0 if debug: print '\nBI:', bi.label print 'n_BI = %2.4g' % n_bi print 'p_BI = %2.4g' % p_bi bi_tilt_f = [] for trap_idx, trap in enumerate(bi.dsl_tilt.traps): localized_trap_key = bi.label + '_tilt_' + trap[0].name + '_F' print 'BI Density of Charge = %2.2g cm-2' % (bi.density_of_charge / 1e4) print 'TILT F = %2.2f' % bi.dsl_tilt_f[trap_idx] dsl_charge_density = bi.dsl_tilt_f[trap_idx] * trap[1] print 'TILT Density of Charge = %2.2g cm-1' % (dsl_charge_density / 1e2) print 'EXT FIELD = %2.2g V*cm' % (electric_field_at_bi / 1e2) electric_field_at_bi_r = abs(electric_field_at_bi) electric_field_at_bi_theta = 0 if electric_field_at_bi >= 0 else np.pi electric_field_at_bi_3d = (electric_field_at_bi_r, electric_field_at_bi_theta, 0.0) trap[0].trap_potential.get_potential_by_name('Charged Dislocation').set_linear_charge_density(dsl_charge_density) trap[0].trap_potential.get_potential_by_name('External Field').external_field = electric_field_at_bi_3d kT = to_numeric(k * schottky_diode.T / q) theta = np.linspace(0, np.pi, num=100, endpoint=True) barrier_lowering = np.array([trap[0].trap_potential.barrier_lowering(theta_i) for theta_i in theta]) poole_frenkel = 0.5 * np.trapz(np.sin(theta) * np.exp(abs(barrier_lowering[:, 0]) / kT), theta) poole_frenkel_e = 1.0 poole_frenkel_h = 1.0 if np.sum(barrier_lowering[:, 0]) < 0: poole_frenkel_e = poole_frenkel print 'emission boost e: %2.4g' % poole_frenkel elif np.sum(barrier_lowering[:, 0]) > 0: poole_frenkel_h = poole_frenkel print 'emission boost h: %2.4g' % poole_frenkel barrier_lowering_e = 0.0 barrier_lowering_h = 0.0 df_dt, tau = trap[0].df_dt(schottky_diode.T, schottky_diode.Semiconductor, bonding_interface_f[localized_trap_key], n_bi, p_bi, poole_frenkel_e=poole_frenkel_e, poole_frenkel_h=poole_frenkel_h, barrier_lowering_e=barrier_lowering_e, barrier_lowering_h=barrier_lowering_h, use_mpmath=False, debug=False) df_bonding_interfaces[localized_trap_key] = df_dt try: max_dt = df_threshold / abs(df_dt) except ZeroDivisionError: max_dt = 1e250 if debug: print '\nTrap:', trap[0].name print 'time constant %2.2g s' % tau print 'dF: %2.4g, th: %2.2f'% (df_dt, df_threshold) print 'Max dt:', max_dt, 'dt:', dt if dt > max_dt > delta_t_min: if debug: print 'Setting dt to', max_dt dt = max_dt elif max_dt < delta_t_min: if dt > 10 * max_dt: if debug: print 'Trap is too fast. Setting trap occupation to equilibrium value' bonding_interface_f[localized_trap_key] = trap[0].equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level_at_bi, electron_volts=False, debug=False) localized_traps_skip_list.append(localized_trap_key) else: if debug: print 'Setting dt to', max_dt dt = max_dt # fast_traps.append(bi.label + '_tilt_' + trap[0].name) bi_tilt_f.append(bonding_interface_f[localized_trap_key]) bi_twist_f = [] for trap in bi.dsl_twist.traps: localized_trap_key = bi.label + '_twist_' + trap[0].name + '_F' trap[0].trap_potential.get_potential_by_name('External Field').external_field = electric_field_at_bi print 'EXT FIELD = %2.2g V*cm' % (electric_field_at_bi / 1e2) print trap[0].trap_potential.barrier_lowering() barrier_lowering_e = 0.0 barrier_lowering_h = 0.0 df_dt, tau = trap[0].df_dt(schottky_diode.T, schottky_diode.Semiconductor, bonding_interface_f[localized_trap_key], n_bi, p_bi, barrier_lowering_e=barrier_lowering_e, barrier_lowering_h=barrier_lowering_h, use_mpmath=False, debug=False) df_bonding_interfaces[localized_trap_key] = df_dt try: max_dt = df_threshold / abs(df_dt) except ZeroDivisionError: max_dt = 1e250 if debug: print '\nTrap:', trap[0].name print 'time constant %2.2g s' % tau print 'dF: %2.4g, th: %2.2f'% (df_dt, df_threshold) print 'Max dt:', max_dt, 'dt:', dt if dt > max_dt > delta_t_min: if debug: print 'Setting dt to', max_dt dt = max_dt elif max_dt < delta_t_min: if dt > 10 * max_dt: if debug: print 'Trap is too fast. Setting trap occupation to equilibrium value' bonding_interface_f[localized_trap_key] = trap[0].equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level, electron_volts=False, debug=False) localized_traps_skip_list.append(localized_trap_key) else: if debug: print 'Setting dt to', max_dt dt = max_dt # fast_traps.append(bi.label + '_twist_' + trap[0].name) bi_twist_f.append(bonding_interface_f[localized_trap_key]) bi.set_traps_f(np.array(bi_tilt_f), np.array(bi_twist_f)) bi.set_traps_df(np.zeros(len(bi_tilt_f)), np.zeros(len(bi_twist_f))) dt = np.float(dt) for dopant in schottky_diode.Semiconductor.dopants: dopant_key = dopant.name + '_F' if dopant_key in dopants_skip_list: if debug: print 'Dopant', dopant_key, 'does not need an update' continue dopants_f[dopant_key] += df_dopants[dopant_key] * dt dopants_f[dopant_key][np.where(dopants_f[dopant_key] > 1.0)] = 1.0 dopants_f[dopant_key][np.where(dopants_f[dopant_key] < 0.0)] = 0.0 dopant.set_F_interp(z_nodes, dopants_f[dopant_key]) dopant.set_dF_interp(z_nodes, np.zeros_like(dopants_f[dopant_key])) for bi in schottky_diode.Semiconductor.bonding_interfaces: bi_tilt_f = [] for trap in bi.dsl_tilt.traps: localized_trap_key = bi.label + '_tilt_' + trap[0].name + '_F' if localized_trap_key not in localized_traps_skip_list: bonding_interface_f[localized_trap_key] += df_bonding_interfaces[localized_trap_key] * dt if bonding_interface_f[localized_trap_key] > 1: bonding_interface_f[localized_trap_key] = 1 elif bonding_interface_f[localized_trap_key] < 0: bonding_interface_f[localized_trap_key] = 0 if debug: print 'F:', bonding_interface_f[localized_trap_key] bi_tilt_f.append(bonding_interface_f[localized_trap_key]) bi_twist_f = [] for trap in bi.dsl_twist.traps: localized_trap_key = bi.label + '_twist_' + trap[0].name + '_F' if localized_trap_key not in localized_traps_skip_list: bonding_interface_f[localized_trap_key] += df_bonding_interfaces[localized_trap_key] * dt if bonding_interface_f[localized_trap_key] > 1: bonding_interface_f[localized_trap_key] = 1 elif bonding_interface_f[localized_trap_key] < 0: bonding_interface_f[localized_trap_key] = 0 if debug: print 'F:', bonding_interface_f[localized_trap_key] bi_twist_f.append(bonding_interface_f[localized_trap_key]) bi.set_traps_f(np.array(bi_tilt_f), np.array(bi_twist_f)) bi.set_traps_df(np.zeros(len(bi_tilt_f)), np.zeros(len(bi_twist_f))) t += dt if debug_plot: plt.ioff() return t_points, potential_t, field_d, z_t, diode_voltage_drop_t, current_density_t, \ bonding_interfaces_f_t, dopants_f_t, last_state_id
def traps_kinetics(schottky_diode, initial_condition_id, delta_t_min, delta_t_max, t_stop, fast_traps=None, rho_rel_err=1e-1, df_threshold=1e-3, min_t_points=50, dopants_deriv_threshold=5.0e-5, dopants_deriv_window=9, dopants_deriv_z_limit=None, save_to_db=True, potential=None, debug=False, debug_plot=False): if dopants_deriv_z_limit is None: dopants_deriv_z_limit = 1e8 z_limit_f = 1e8 dopants_f_total = {} t_points = [] potential_t = [] field_d = [] z_t = [] diode_voltage_drop_t = [] current_density_t = [] bonding_interfaces_f_t = [] dopants_f_t = [] n_t = [] p_t = [] if save_to_db: ic_found, potential, _, z_nodes, _, _, _, _, _, _, _, _ = Poisson.load_diode_state( schottky_diode, initial_condition_id, debug=True) if not ic_found: raise (Exception( 'Initial condition not found. Please check everything')) if debug_plot: plt.ion() axes = Visual.prepare_debug_axes( ['Potential', 'Dopants', 'Localized traps']) potential_lines = Visual.create_lines( axes['Potential'], ['Start potential', 'Current potential']) dopants_lines_names = [ dopant.name + '_F' for dopant in schottky_diode.Semiconductor.dopants ] localized_traps_lines_names = [] for bi in schottky_diode.Semiconductor.bonding_interfaces: localized_traps_lines_names += [ bi.label + '_tilt_' + trap[0].name + '_F' for trap in bi.dsl_tilt.traps ] localized_traps_lines_names += [ bi.label + '_twist_' + trap[0].name + '_F' for trap in bi.dsl_twist.traps ] dopants_lines = Visual.create_lines(axes['Dopants'], dopants_lines_names) localized_traps_lines = Visual.create_lines( axes['Localized traps'], localized_traps_lines_names) t = 0 last_state_id = initial_condition_id if fast_traps is None: fast_traps = [] slow_traps = {} while t <= t_stop: print '\n\nt =', t potential, field, z_nodes, _, \ diode_voltage_drop, _, current_density, _, \ bonding_interface_f, dopants_f, \ last_state_id = Poisson.Reccurent_Poisson_solver(schottky_diode, potential, equilibrium_filling=False, fast_traps=fast_traps, t=t, initial_condition_id=last_state_id, rho_rel_err=rho_rel_err, max_iter=100, save_to_db=save_to_db, debug=False) t_points.append(t) potential_t.append(potential) field_d.append(field) z_t.append(z_nodes) diode_voltage_drop_t.append(diode_voltage_drop) current_density_t.append(current_density) bonding_interfaces_f_t.append(bonding_interface_f.copy()) dopants_f_t.append(dopants_f.copy()) fermi_level = schottky_diode.EfEc(potential, z_nodes, eV=False) n, p = schottky_diode.n_carriers_theory(potential, z_nodes) if schottky_diode.Semiconductor.dop_type == 'n': p = np.zeros_like(p) elif schottky_diode.Semiconductor.dop_type == 'p': n = np.zeros_like(n) n_t.append(n.copy()) p_t.append(p.copy()) if debug_plot: if t == 0: potential_lines['Start potential'].set_data( z_nodes * 1e6, -potential(z_nodes)) potential_lines['Current potential'].set_data( z_nodes * 1e6, -potential(z_nodes)) for dopant_line_name in dopants_lines_names: dopants_lines[dopant_line_name].set_data( z_nodes * 1e6, dopants_f[dopant_line_name]) for localized_trap_line_name in localized_traps_lines_names: localized_trap_f_t = [] for bonding_interfaces_f_t_i in bonding_interfaces_f_t: localized_trap_f_t.append( bonding_interfaces_f_t_i[localized_trap_line_name]) localized_traps_lines[localized_trap_line_name].set_data( t_points, localized_trap_f_t) Visual.autoscale_axes(axes) plt.draw() dt = delta_t_max if t_stop - t > delta_t_max else t_stop - t if dt == 0: for dopant in schottky_diode.Semiconductor.dopants: dopant_key = dopant.name + '_F' dopants_f_t[-1].update( {dopant_key + '_pf': np.ones_like(z_nodes)}) dopants_f_t[-1].update( {dopant_key + '_df': np.zeros_like(z_nodes)}) break if debug: print '\n\nT = %2.2f K, t = %2.2g s, dt = %2.2g s' % ( schottky_diode.T, t, dt) #fast_traps = [] dopants_skip_list = [] df_dopants = {} for dopant in schottky_diode.Semiconductor.dopants: dopant_key = dopant.name + '_F' dopants_deriv_z_limit_idx = np.where( z_nodes < dopants_deriv_z_limit) if t == 0: dopants_f_total[dopant_key] = [ np.trapz( dopants_f_t[0][dopant_key][dopants_deriv_z_limit_idx], x=z_nodes[dopants_deriv_z_limit_idx]) ] else: dopants_f_total[dopant_key].append( np.trapz( dopants_f_t[-1][dopant_key][dopants_deriv_z_limit_idx], x=z_nodes[dopants_deriv_z_limit_idx])) print 'Total Donor charge %2.2g' % dopants_f_total[dopant_key][-1] if t > 0: loc_der = (dopants_f_total[dopant_key][-1] - dopants_f_total[dopant_key][-2]) \ / (t_points[-1] - t_points[-2]) / dopants_f_total[dopant_key][0] print 'local derivative %2.2g%%' % (loc_der * 100) if dopant.name in fast_traps: if debug: print '\nDopant:', dopant.name print 'This dopant is in a fast-traps list, skipping.' dopants_skip_list.append(dopant_key) continue if dopant.name in slow_traps.keys(): if debug: print '\nDopant:', dopant.name print 'This dopant is in a slow-traps list, skipping.' df_dt = np.zeros_like(z_nodes) for wp in range(dopants_deriv_window): df_dt[dopants_deriv_z_limit_idx] += dopants_f_t[-wp - 2][ dopant_key + '_df'][dopants_deriv_z_limit_idx] df_dt /= dopants_deriv_window dopants_f_t[-1].update({dopant_key + '_df': df_dt}) df_dopants[dopant_key] = df_dt #dopants_skip_list.append(dopant_key) continue poole_frenkel_e = np.ones_like(z_nodes, dtype=np.float) poole_frenkel_h = np.ones_like(z_nodes, dtype=np.float) barrier_lowering_e = np.zeros_like(n, dtype=np.float) barrier_lowering_h = np.zeros_like(p, dtype=np.float) field_z = field(z_nodes) if dopant.trap_potential.get_potential_by_name( 'Charged Dislocation') is not None: kT = to_numeric(k * schottky_diode.T / q) theta_points = 90 theta = np.linspace(0, np.pi, num=theta_points, endpoint=True) #theta = np.linspace(0, np.pi / 2, num=theta_points, endpoint=True) barrier_lowering = np.zeros((theta_points, len(z_nodes)), dtype=np.float) max_N_l = dopant.trap_potential.get_potential_by_name('Charged Dislocation')\ .max_linear_charge_density dsl_charge_density = max_N_l * dopant.F(z_nodes) for z_num, local_electric_field in enumerate(field_z): #print z_num, 'of', len(z_nodes) #dsl_charge_density = max_N_l * dopant.F(z_nodes[z_num]) local_electric_field_r = abs(local_electric_field) local_electric_field_theta = 0 if local_electric_field >= 0 else np.pi local_electric_field_3d = (local_electric_field_r, local_electric_field_theta, 0.0) dopant.trap_potential.get_potential_by_name('Charged Dislocation')\ .set_linear_charge_density(dsl_charge_density[z_num]) dopant.trap_potential.get_potential_by_name('External Field')\ .external_field = local_electric_field_3d loc_f = local_electric_field_r loc_a = dopant.trap_potential.get_potential_by_name( 'Charged Dislocation').a loc_b = -dopant.trap_potential.get_potential_by_name( 'Deformation').a r0 = np.zeros_like(theta) if loc_f < 1.0e-5: if z_nodes[z_num] < z_limit_f: z_limit_f = z_nodes[z_num] #print 'here', loc_f, z_nodes[z_num]*1e6 try: r0[:] = loc_b / loc_a except FloatingPointError: pass #print len(r0), r0 else: determinant = loc_a**2 + 4 * loc_b * loc_f * np.cos( theta) idx = np.where(determinant >= 0) sqrt = np.sqrt(loc_a**2 + 4 * loc_b * loc_f * np.cos(theta[idx])) sol1 = (-loc_a - sqrt) / (2 * loc_f * np.cos(theta[idx])) sol2 = (-loc_a + sqrt) / (2 * loc_f * np.cos(theta[idx])) sol1[np.where(sol1 < 0.0)] = 0.0 #sol2[np.where(sol2 < 0.0)] = 0.0 sol = sol1.copy() #sol2_selection = np.where(sol2 > 0) sol2_selection = np.where((sol2 > 0) & (sol2 < sol1)) sol[sol2_selection] = sol2[sol2_selection] sol2_selection = np.where((sol2 > 0) & (sol1 <= 0)) sol[sol2_selection] = sol2[sol2_selection] r0[idx] = sol #idx = np.where(r0 < loc_b / loc_a / 3) #r0[idx] = loc_b / loc_a zero_theta_idx = np.where( abs(loc_f * np.cos(theta)) < 1.0e-5) try: r0[zero_theta_idx] = loc_b / loc_a except FloatingPointError: r0[zero_theta_idx] = 0.0 non_zero_r_idx = np.where(r0 > 0.0) bl_grid = dopant.trap_potential.potential( r0[non_zero_r_idx], theta[non_zero_r_idx], 0) #np.save('./bl_grid_'+str(z_nodes[z_num]*1e6)+'_'+str(t), bl_grid) #print np.rad2deg(theta[non_zero_r_idx]) #print bl_grid[0,:,0].shape, theta.shape #print bl_grid[0,:,0] bl_flat = np.zeros_like(theta) try: bl_flat[non_zero_r_idx] = bl_grid[0, :, 0] except IndexError: pass #print kT #print bl_flat #barrier_lowering[:,z_num] = np.array([dopant.trap_potential.barrier_lowering(theta_i)[0] for theta_i in theta]) barrier_lowering[:, z_num] = bl_flat #print barrier_lowering[:, z_num] #print bl_flat - barrier_lowering[:, z_num] #poole_frenkel = 0.5 * np.trapz(np.sin(theta) * np.exp(abs(barrier_lowering[:, 0]) / kT), theta) #poole_frenkel = 0.5 * np.trapz(np.exp(abs(barrier_lowering) / kT), theta, axis=0) #poole_frenkel = 0.5 + np.trapz(np.exp(abs(barrier_lowering) / kT), theta, axis=0) / np.pi poole_frenkel = np.trapz( np.exp(abs(barrier_lowering) / kT), theta, axis=0) / np.pi #print poole_frenkel if np.sum(barrier_lowering[:, 0]) < 0: poole_frenkel_e = poole_frenkel #print 'emission boost e:', poole_frenkel elif np.sum(barrier_lowering[:, 0]) > 0: poole_frenkel_h = poole_frenkel #print 'emission boost h:', poole_frenkel try: dopants_f_t[-1].update({dopant_key + '_pf': poole_frenkel}) except: dopants_f_t[-1].update( {dopant_key + '_pf': np.ones_like(z_nodes)}) df_dt, tau = dopant.df_dt(schottky_diode.T, schottky_diode.Semiconductor, dopants_f[dopant_key], n, p, poole_frenkel_e=poole_frenkel_e, poole_frenkel_h=poole_frenkel_h, barrier_lowering_e=barrier_lowering_e, barrier_lowering_h=barrier_lowering_h, use_mpmath=False, debug=False) z_limit_f_idx = np.where(z_nodes < z_limit_f) z_limit_f_idx0 = np.where(z_t[0] < z_limit_f) dopants_f_t[-1].update({dopant_key + '_df': df_dt}) df_dopants[dopant_key] = df_dt #print df_dt df_total = np.sum(df_dt[z_limit_f_idx]) / np.sum( dopants_f_t[0][dopant_key][z_limit_f_idx0]) max_dt_local = df_threshold / np.max(np.abs(df_dt)) max_dt_total = df_threshold / np.max(np.abs(df_total)) max_dt = min(max_dt_local, max_dt_total) if debug: print '\nDopant:', dopant.name print 'Z limit of %2.2g m: left %d points of %d' % ( z_limit_f, len(z_nodes[z_limit_f_idx]), len(z_nodes)) print 'Min time constant %2.2g s' % tau print 'Max dF local:', np.max( np.abs(df_dt)), 'th:', df_threshold print 'Max dF total:', np.max( np.abs(df_total)), 'th:', df_threshold print 'Max dt:', max_dt, 'dt:', dt if len(t_points) > dopants_deriv_window: deriv = np.array(dopants_f_total[dopant_key][-dopants_deriv_window + 1:]) \ - np.array(dopants_f_total[dopant_key][-dopants_deriv_window:-1]) deriv /= dopants_f_total[dopant_key][0] deriv /= np.array( t_points[-dopants_deriv_window + 1:]) - np.array( t_points[-dopants_deriv_window:-1]) deriv = np.average(deriv) if debug: print 'Dopants derivative: %2.2g%%' % (deriv * 100) print 'Derivative threshold: %2.2g%%' % ( dopants_deriv_threshold * 100) else: deriv = 1e8 if abs(deriv) < dopants_deriv_threshold and len( t_points) >= min_t_points: if debug: print 'Traps are all set. Adding dopant to slow traps.' slow_traps[dopant.name] = deriv if dt > max_dt > delta_t_min: if debug: print 'Setting dt to', max_dt dt = max_dt elif max_dt < delta_t_min: if debug: print 'Traps are too fast. Setting dopant occupation to equilibrium value' dopant_f = dopant.equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level, electron_volts=False, debug=False) dopant.set_F_interp(z_nodes, dopant_f) dopant.set_dF_interp(z_nodes, np.zeros_like(dopant_f)) fast_traps.append(dopant.name) dopants_skip_list.append(dopant_key) localized_traps_skip_list = [] df_bonding_interfaces = {} for bi in schottky_diode.Semiconductor.bonding_interfaces: fermi_level_at_bi = schottky_diode.EfEc(potential, bi.depth, eV=False) electric_field_at_bi = field(bi.depth) n_bi, p_bi = schottky_diode.n_carriers_theory(potential, bi.depth) if schottky_diode.Semiconductor.dop_type == 'n': p_bi = 0.0 elif schottky_diode.Semiconductor.dop_type == 'p': n_bi = 0.0 if debug: print '\nBI:', bi.label print 'n_BI = %2.4g' % n_bi print 'p_BI = %2.4g' % p_bi bi_tilt_f = [] for trap_idx, trap in enumerate(bi.dsl_tilt.traps): localized_trap_key = bi.label + '_tilt_' + trap[0].name + '_F' print 'BI Density of Charge = %2.2g cm-2' % ( bi.density_of_charge / 1e4) print 'TILT F = %2.2f' % bi.dsl_tilt_f[trap_idx] dsl_charge_density = bi.dsl_tilt_f[trap_idx] * trap[1] print 'TILT Density of Charge = %2.2g cm-1' % ( dsl_charge_density / 1e2) print 'EXT FIELD = %2.2g V*cm' % (electric_field_at_bi / 1e2) electric_field_at_bi_r = abs(electric_field_at_bi) electric_field_at_bi_theta = 0 if electric_field_at_bi >= 0 else np.pi electric_field_at_bi_3d = (electric_field_at_bi_r, electric_field_at_bi_theta, 0.0) trap[0].trap_potential.get_potential_by_name( 'Charged Dislocation').set_linear_charge_density( dsl_charge_density) trap[0].trap_potential.get_potential_by_name( 'External Field').external_field = electric_field_at_bi_3d kT = to_numeric(k * schottky_diode.T / q) theta = np.linspace(0, np.pi, num=100, endpoint=True) barrier_lowering = np.array([ trap[0].trap_potential.barrier_lowering(theta_i) for theta_i in theta ]) poole_frenkel = 0.5 * np.trapz( np.sin(theta) * np.exp(abs(barrier_lowering[:, 0]) / kT), theta) poole_frenkel_e = 1.0 poole_frenkel_h = 1.0 if np.sum(barrier_lowering[:, 0]) < 0: poole_frenkel_e = poole_frenkel print 'emission boost e: %2.4g' % poole_frenkel elif np.sum(barrier_lowering[:, 0]) > 0: poole_frenkel_h = poole_frenkel print 'emission boost h: %2.4g' % poole_frenkel barrier_lowering_e = 0.0 barrier_lowering_h = 0.0 df_dt, tau = trap[0].df_dt( schottky_diode.T, schottky_diode.Semiconductor, bonding_interface_f[localized_trap_key], n_bi, p_bi, poole_frenkel_e=poole_frenkel_e, poole_frenkel_h=poole_frenkel_h, barrier_lowering_e=barrier_lowering_e, barrier_lowering_h=barrier_lowering_h, use_mpmath=False, debug=False) df_bonding_interfaces[localized_trap_key] = df_dt try: max_dt = df_threshold / abs(df_dt) except ZeroDivisionError: max_dt = 1e250 if debug: print '\nTrap:', trap[0].name print 'time constant %2.2g s' % tau print 'dF: %2.4g, th: %2.2f' % (df_dt, df_threshold) print 'Max dt:', max_dt, 'dt:', dt if dt > max_dt > delta_t_min: if debug: print 'Setting dt to', max_dt dt = max_dt elif max_dt < delta_t_min: if dt > 10 * max_dt: if debug: print 'Trap is too fast. Setting trap occupation to equilibrium value' bonding_interface_f[localized_trap_key] = trap[ 0].equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level_at_bi, electron_volts=False, debug=False) localized_traps_skip_list.append(localized_trap_key) else: if debug: print 'Setting dt to', max_dt dt = max_dt # fast_traps.append(bi.label + '_tilt_' + trap[0].name) bi_tilt_f.append(bonding_interface_f[localized_trap_key]) bi_twist_f = [] for trap in bi.dsl_twist.traps: localized_trap_key = bi.label + '_twist_' + trap[0].name + '_F' trap[0].trap_potential.get_potential_by_name( 'External Field').external_field = electric_field_at_bi print 'EXT FIELD = %2.2g V*cm' % (electric_field_at_bi / 1e2) print trap[0].trap_potential.barrier_lowering() barrier_lowering_e = 0.0 barrier_lowering_h = 0.0 df_dt, tau = trap[0].df_dt( schottky_diode.T, schottky_diode.Semiconductor, bonding_interface_f[localized_trap_key], n_bi, p_bi, barrier_lowering_e=barrier_lowering_e, barrier_lowering_h=barrier_lowering_h, use_mpmath=False, debug=False) df_bonding_interfaces[localized_trap_key] = df_dt try: max_dt = df_threshold / abs(df_dt) except ZeroDivisionError: max_dt = 1e250 if debug: print '\nTrap:', trap[0].name print 'time constant %2.2g s' % tau print 'dF: %2.4g, th: %2.2f' % (df_dt, df_threshold) print 'Max dt:', max_dt, 'dt:', dt if dt > max_dt > delta_t_min: if debug: print 'Setting dt to', max_dt dt = max_dt elif max_dt < delta_t_min: if dt > 10 * max_dt: if debug: print 'Trap is too fast. Setting trap occupation to equilibrium value' bonding_interface_f[localized_trap_key] = trap[ 0].equilibrium_f(schottky_diode.T, schottky_diode.Semiconductor, fermi_level, electron_volts=False, debug=False) localized_traps_skip_list.append(localized_trap_key) else: if debug: print 'Setting dt to', max_dt dt = max_dt # fast_traps.append(bi.label + '_twist_' + trap[0].name) bi_twist_f.append(bonding_interface_f[localized_trap_key]) bi.set_traps_f(np.array(bi_tilt_f), np.array(bi_twist_f)) bi.set_traps_df(np.zeros(len(bi_tilt_f)), np.zeros(len(bi_twist_f))) dt = np.float(dt) for dopant in schottky_diode.Semiconductor.dopants: dopant_key = dopant.name + '_F' if dopant_key in dopants_skip_list: if debug: print '\nDopant', dopant.name, 'does not need an update' continue dopants_f_corr = dopants_f[dopant_key] + df_dopants[dopant_key] * dt dopants_f_corr[np.where(dopants_f_corr > 1.0)] = 1.0 dopants_f_corr[np.where(dopants_f_corr < 0.0)] = 0.0 dopants_f_corr[np.where( z_nodes >= z_limit_f)] = dopants_f_corr[z_limit_f_idx][-1] dopant.set_F_interp(z_nodes, dopants_f_corr) dopant.set_dF_interp(z_nodes, np.zeros_like(dopants_f_corr)) for bi in schottky_diode.Semiconductor.bonding_interfaces: bi_tilt_f = [] for trap in bi.dsl_tilt.traps: localized_trap_key = bi.label + '_tilt_' + trap[0].name + '_F' if localized_trap_key not in localized_traps_skip_list: bonding_interface_f[ localized_trap_key] += df_bonding_interfaces[ localized_trap_key] * dt if bonding_interface_f[localized_trap_key] > 1: bonding_interface_f[localized_trap_key] = 1 elif bonding_interface_f[localized_trap_key] < 0: bonding_interface_f[localized_trap_key] = 0 if debug: print 'F:', bonding_interface_f[localized_trap_key] bi_tilt_f.append(bonding_interface_f[localized_trap_key]) bi_twist_f = [] for trap in bi.dsl_twist.traps: localized_trap_key = bi.label + '_twist_' + trap[0].name + '_F' if localized_trap_key not in localized_traps_skip_list: bonding_interface_f[ localized_trap_key] += df_bonding_interfaces[ localized_trap_key] * dt if bonding_interface_f[localized_trap_key] > 1: bonding_interface_f[localized_trap_key] = 1 elif bonding_interface_f[localized_trap_key] < 0: bonding_interface_f[localized_trap_key] = 0 if debug: print 'F:', bonding_interface_f[localized_trap_key] bi_twist_f.append(bonding_interface_f[localized_trap_key]) bi.set_traps_f(np.array(bi_tilt_f), np.array(bi_twist_f)) bi.set_traps_df(np.zeros(len(bi_tilt_f)), np.zeros(len(bi_twist_f))) t += dt if debug_plot: plt.ioff() return t_points, potential_t, field_d, z_t, diode_voltage_drop_t, current_density_t, \ bonding_interfaces_f_t, dopants_f_t, n_t, p_t, last_state_id
def emission_rate(self, temperature, semiconductor, f, poole_frenkel_e=1.0, poole_frenkel_h=1.0, barrier_lowering_e=None, barrier_lowering_h=None, use_mpmath=False, debug=False): """ Calculate carriers emission rate for bot electrons and holes :param temperature: Temperature, K :param semiconductor: Semiconductor object :param f: Trap occupation from 0.0 to 1.0 :param poole_frenkel_e: emission rate boost due to Poole-Frenkel effect for electron :param poole_frenkel_h: emission rate boost due to Poole-Frenkel effect for electron :param barrier_lowering_e: lowering of activation energy for electrons :param barrier_lowering_h: lowering of activation energy for holes :param use_mpmath: if True integration is done using mpmath.quad function instead of numpy.trapz (default) :param debug: if True prints out some debug information :return: emission_e, emission_h """ if barrier_lowering_e is None: barrier_lowering_e = np.zeros_like(f, dtype=np.float) if barrier_lowering_h is None: barrier_lowering_h = np.zeros_like(f, dtype=np.float) energy_scale = to_numeric(k * temperature) conduction_band = 0 band_gap = semiconductor.band_gap(temperature, symbolic=False, electron_volts=False) valence_band = conduction_band - band_gap trap_energy_level_e = np.float(self.energy_level(temperature, semiconductor, charge_state_idx=1, electron_volts=False)) trap_energy_level_h = np.float(self.energy_level(temperature, semiconductor, charge_state_idx=0, electron_volts=False)) trap_energy_level_e_positive = conduction_band - trap_energy_level_e trap_energy_level_h_positive = conduction_band - trap_energy_level_h g_ratio_e = self.charge_states[0][2] / self.charge_states[1][2] g_ratio_h = self.charge_states[1][2] / self.charge_states[0][2] v_e = np.float(semiconductor.v_T('e', temperature, symbolic=False)) v_h = np.float(semiconductor.v_T('h', temperature, symbolic=False)) if debug: print '<v_e> =', v_e, 'm/s' print '<v_h> =', v_h, 'm/s' sigma_n, sigma_p = self.capture_cross_sections(temperature) fore_factor_n = np.float(sigma_n * v_e * semiconductor.Nc(temperature, symbolic=False) * g_ratio_e) fore_factor_p = np.float(sigma_p * v_h * semiconductor.Nv(temperature, symbolic=False) * g_ratio_h) fore_factor_n *= poole_frenkel_e fore_factor_p *= poole_frenkel_h if debug: print 'factor_n =', fore_factor_n print 'factor_p =', fore_factor_p if self.energy_distribution_function in energy_distribution_functions['Single Level']: activation_energy_e = conduction_band - trap_energy_level_e_positive - barrier_lowering_e activation_energy_h = trap_energy_level_h_positive - valence_band - barrier_lowering_h emission_rate_e = fore_factor_n * np.exp(-activation_energy_e / energy_scale) emission_rate_h = fore_factor_p * np.exp(-activation_energy_h / energy_scale) emission_time_constant_e = 1 / emission_rate_e emission_time_constant_h = 1 / emission_rate_h emission_e = emission_rate_e * f emission_h = emission_rate_h * (1 - f) else: quasi_fermi_level = self.f_to_equilibrium_fermi_level(temperature, semiconductor, f, electron_volts=False, use_mpmath=use_mpmath, debug=False) quasi_fermi_level = conduction_band - quasi_fermi_level if debug: print 'Eqf =', quasi_fermi_level / to_numeric(q), 'eV' energy = sym.symbols('E') energy_distribution_e = self.energy_distribution.subs([('Et', trap_energy_level_e), ('Ecb', conduction_band)]) energy_distribution_e = energy_distribution_e.subs(q, to_numeric(q)) energy_distribution_h = self.energy_distribution.subs([('Et', trap_energy_level_h), ('Ecb', conduction_band)]) energy_distribution_h = energy_distribution_h.subs(q, to_numeric(q)) energy_range_e = centered_linspace(conduction_band - trap_energy_level_e, 10 * to_numeric(self.energy_spread), to_numeric(self.energy_spread) / 1000) energy_range_h = centered_linspace(conduction_band - trap_energy_level_h, 10 * to_numeric(self.energy_spread), to_numeric(self.energy_spread) / 1000) energy_distribution_function_e = sym.lambdify(energy, energy_distribution_e, 'numpy') energy_distribution_function_h = sym.lambdify(energy, energy_distribution_h, 'numpy') energy_range_grid_e, barrier_lowering_grid_e = np.meshgrid(energy_range_e, barrier_lowering_e) energy_range_grid_h, barrier_lowering_grid_h = np.meshgrid(energy_range_h, barrier_lowering_h) exp_term_e = np.exp(-(conduction_band - energy_range_grid_e + barrier_lowering_grid_e) / energy_scale) exp_term_h = np.exp(-(energy_range_grid_h - barrier_lowering_grid_h - valence_band) / energy_scale) emission_rate_e = energy_distribution_function_e(energy_range_e) * exp_term_e * fore_factor_n emission_rate_h = energy_distribution_function_h(energy_range_h) * exp_term_h * fore_factor_p emission_rate_e_max = np.max(emission_rate_e, axis=1) emission_rate_h_max = np.max(emission_rate_h, axis=1) emission_time_constant_e = 1 / emission_rate_e_max emission_time_constant_h = 1 / emission_rate_h_max if not use_mpmath: if debug: print 'Numeric integration (numpy.trapz)' energy_range_grid_e, fermi_level_grid_e = np.meshgrid(energy_range_e, quasi_fermi_level) energy_range_grid_h, fermi_level_grid_h = np.meshgrid(energy_range_h, quasi_fermi_level) fermi_function_e = 1 / (1 + g_ratio_e * np.exp((energy_range_grid_e - fermi_level_grid_e) / energy_scale)) fermi_function_h = 1 - 1 / (1 + g_ratio_h * np.exp((energy_range_grid_h - fermi_level_grid_h) / energy_scale)) exp_term_e = np.exp(-(conduction_band - energy_range_grid_e + barrier_lowering_e) / energy_scale) exp_term_h = np.exp(-(energy_range_grid_h - barrier_lowering_h - valence_band) / energy_scale) emission_rate_e = energy_distribution_function_e(energy_range_grid_e) * exp_term_e emission_rate_h = energy_distribution_function_h(energy_range_grid_h) * exp_term_h integrand_array_e = emission_rate_e * fermi_function_e integrand_array_h = emission_rate_h * fermi_function_h emission_e = np.trapz(integrand_array_e, energy_range_grid_e, axis=1) * fore_factor_n emission_h = np.trapz(integrand_array_h, energy_range_grid_h, axis=1) * fore_factor_p else: if debug: print 'Numeric integration (mpmath.quad)' exp_term_e = sym.exp(-(conduction_band - energy + barrier_lowering_e) / energy_scale) exp_term_h = sym.exp(-(energy - barrier_lowering_h - valence_band) / energy_scale) quasi_fermi_level_grid, = np.meshgrid(quasi_fermi_level) emission_e = np.zeros_like(quasi_fermi_level_grid) emission_h = np.zeros_like(quasi_fermi_level_grid) for i, quasi_fermi_level_i in enumerate(quasi_fermi_level_grid): fermi_function_e = fermi(energy, quasi_fermi_level_i, temperature, g_ratio_e) fermi_function_e = fermi_function_e.subs(k, to_numeric(k)) fermi_function_h = fermi(quasi_fermi_level_i, energy, temperature, g_ratio_h) fermi_function_h = fermi_function_h.subs(k, to_numeric(k)) integrand_e = sym.lambdify(energy, energy_distribution_e * fermi_function_e * exp_term_e) integrand_h = sym.lambdify(energy, energy_distribution_h * fermi_function_h * exp_term_h) emission_integral_e = mp.quad(integrand_e, [to_numeric(trap_energy_level_e_positive - 10 * self.energy_spread), to_numeric(trap_energy_level_e_positive - 0.5 * self.energy_spread), to_numeric(trap_energy_level_e_positive + 0.5 * self.energy_spread), to_numeric(trap_energy_level_e_positive + 10 * self.energy_spread)]) emission_integral_h = mp.quad(integrand_h, [to_numeric(trap_energy_level_h_positive - 10 * self.energy_spread), to_numeric(trap_energy_level_h_positive - 0.5 * self.energy_spread), to_numeric(trap_energy_level_h_positive + 0.5 * self.energy_spread), to_numeric(trap_energy_level_h_positive + 10 * self.energy_spread)]) emission_e[i] = np.float(emission_integral_e * fore_factor_n) emission_h[i] = np.float(emission_integral_h * fore_factor_p) if isinstance(emission_e, np.ndarray): if emission_e.size == 1: emission_e = emission_e[0] emission_h = emission_h[0] emission_time_constant_e = emission_time_constant_e[0] emission_time_constant_h = emission_time_constant_h[0] if debug: print 'emission_e =', emission_e if debug: print 'emission_h =', emission_h if debug: print 'emission_tau_e =', emission_time_constant_e if debug: print 'emission_tau_h =', emission_time_constant_e return emission_e, emission_h, emission_time_constant_e, emission_time_constant_h
def energy_distribution_diagram(self, ax, temperature, semiconductor, trap_concentration=1, trap_concentration_units='', fermi_level_from_conduction_band = None, electron_volts=True, fancy_labels=False): if electron_volts: energy_coefficient = to_numeric(q) energy_unit = 'eV' else: energy_coefficient = 1 energy_unit = 'J' energy_scale = to_numeric(k * temperature) / energy_coefficient conduction_band = 0 g_ratio = self.charge_states[0][2] / self.charge_states[1][2] band_gap = semiconductor.band_gap(temperature, symbolic=False, electron_volts=electron_volts) energy_range = np.linspace(-np.float(band_gap), 0, num=1001, endpoint=True) charge_state_idx = 0 trap_energy_level = self.energy_level(temperature, semiconductor, charge_state_idx, electron_volts) if fermi_level_from_conduction_band is None: fermi_level_from_conduction_band = trap_energy_level fermi_level = conduction_band - fermi_level_from_conduction_band if self.energy_distribution_function in energy_distribution_functions['Single Level']: ax.plot(np.zeros_like(energy_range), energy_range, linewidth=2, color='black', linestyle='-') ax_max = ax.get_xlim()[1] if trap_concentration > ax_max: head_length = 0.03 * trap_concentration else: head_length = 0.03 * ax_max ax.arrow(0, conduction_band - trap_energy_level, trap_concentration - head_length, 0, linewidth=2, head_width=head_length, head_length=head_length, fc='black', ec='black') f = self.equilibrium_f(temperature, semiconductor, fermi_level_from_conduction_band, electron_volts) ax.arrow(0, conduction_band - trap_energy_level, trap_concentration * f, 0, linewidth=2, head_width=0, head_length=0, fc='red', ec='red') else: energy = sym.symbols('E') energy_distribution = self.energy_distribution.subs([('Et', trap_energy_level * energy_coefficient), ('Ecb', conduction_band * energy_coefficient)]) energy_distribution = energy_distribution.subs(q, to_numeric(q)) energy_distribution_function = sym.lambdify(energy, energy_distribution, 'numpy') fermi_function = 1 / (1 + g_ratio * np.exp((energy_range - fermi_level) / energy_scale)) energy_states_distribution = energy_distribution_function(energy_range * energy_coefficient) energy_states_distribution *= trap_concentration * energy_coefficient trapped_carriers_distribution = energy_states_distribution * fermi_function ax.plot(energy_states_distribution, energy_range, linewidth=2, color='black', linestyle='-') # ax.plot(trapped_carriers_distribution, energy_range, linewidth=2, color='red', linestyle='-') ax.plot(fermi_function * max(energy_states_distribution), energy_range, linewidth=2, color='black', linestyle='--') ax.fill_between(trapped_carriers_distribution, 0, energy_range, color='blue', alpha=0.5) ax.arrow(0, fermi_level, ax.get_xlim()[1], 0, linewidth=2, head_width=0, head_length=0, fc='black', ec='black') ax.set_title('Traps distribution in the Band Gap') ax.set_ylabel('Energy, ' + energy_unit) ax.set_ylim([-np.float(band_gap), 0]) x_label = 'Traps distribution, 1/' + energy_unit if trap_concentration_units != 1 and trap_concentration_units != '': x_label += ' * ' + trap_concentration_units ax.set_xlabel(x_label) ax.set_xlim([0, max([ax.get_xlim()[1], trap_concentration])]) if fancy_labels: ticks = ax.get_yticks() labels = np.array([lbl.get_text() for lbl in ax.get_yticklabels()]) # print ticks # print labels if u'Ev' in labels: ticks = np.append(ticks, band_gap - trap_energy_level) labels = np.append(labels, 'Ec-%2.2g' % trap_energy_level) else: #ticks = np.array([0, band_gap, band_gap - trap_energy_level]) ticks = np.array([-band_gap, -trap_energy_level, 0]) labels = np.array(['Ev', 'Ec-%2.2g' % trap_energy_level, 'Ec']) ax.set_yticks(ticks) ax.set_yticklabels(labels)
def d_equilibrium_f_d_fermi_energy(self, temperature, semiconductor, fermi_level_from_conduction_band, electron_volts=False, use_mpmath=False, debug=False): """ Calculates trap equilibrium filling derivative for given Fermi level position on small Fermi level change :param temperature: Temperature, K :param semiconductor: Semiconductor object :param fermi_level_from_conduction_band: distance from Conduction band to Fermi level :param electron_volts: if True assume all energy values to be in eV :param use_mpmath: if True integration is done using mpmath.quad function instead of numpy.trapz (default) :param debug: if True prints out some debug information :return: equilibrium f between 0 and 1 """ if electron_volts: energy_coefficient = to_numeric(q) energy_unit = 'eV' else: energy_coefficient = 1 energy_unit = 'J' energy_scale = to_numeric(k * temperature) / energy_coefficient conduction_band = 0 fermi_level = conduction_band - fermi_level_from_conduction_band trap_energy_level = self.energy_level(temperature, semiconductor, charge_state_idx=0, electron_volts=electron_volts) if debug: print 'Et = %2.2g ' % ( (conduction_band - trap_energy_level)) + energy_unit print 'Ef =,', fermi_level, energy_unit g_ratio = self.charge_states[0][2] / self.charge_states[1][2] if self.energy_distribution_function in energy_distribution_functions[ 'Single Level']: fermi_level_grid, = np.meshgrid(fermi_level) exp_arg = (np.float(conduction_band - trap_energy_level) - fermi_level_grid) / energy_scale exp_term = np.exp(exp_arg) exp_term = np.array(map(mp.exp, exp_arg)) a = g_ratio / (energy_coefficient * energy_scale) #print exp_term #/ (1 + g_ratio * exp_term) b = exp_term / (1 + g_ratio * exp_term)**2 d_f = a * b # d_f = g_ratio / (energy_coefficient * energy_scale) * exp_term / (1 + g_ratio * exp_term) ** 2 d_f[np.where(d_f == np.nan)] = 0 else: energy = sym.symbols('E') energy_distribution = self.energy_distribution.subs([ ('Et', trap_energy_level * energy_coefficient), ('Ecb', conduction_band * energy_coefficient) ]) energy_distribution = energy_distribution.subs(q, to_numeric(q)) if not use_mpmath: if debug: print 'Numeric integration (numpy.trapz)' energy_range = centered_linspace( conduction_band - trap_energy_level, 10 * to_numeric(self.energy_spread) / energy_coefficient, to_numeric(self.energy_spread) / energy_coefficient / 1000) energy_range_grid, fermi_level_grid = np.meshgrid( energy_range, fermi_level) exp_term = np.exp( (energy_range_grid - fermi_level_grid) / energy_scale) fore_factor = g_ratio / (energy_coefficient * energy_scale) d_fermi_function = fore_factor * exp_term / ( 1 + g_ratio * exp_term)**2 d_fermi_function[np.where(d_fermi_function == np.nan)] = 0 energy_distribution_function = sym.lambdify( energy, energy_distribution, 'numpy') integrand_array = energy_distribution_function( energy_range_grid * energy_coefficient) * d_fermi_function d_f = np.trapz(integrand_array, energy_range_grid * energy_coefficient, axis=1) else: if debug: print 'Numeric integration (mpmath.quad)' fermi_level_grid, = np.meshgrid(fermi_level) d_f = np.zeros_like(fermi_level_grid) for i, fermi_level_i in enumerate(fermi_level_grid): d_fermi_function = d_fermi_d_delta_fermi_energy( energy, fermi_level_i * energy_coefficient, temperature, g_ratio) d_fermi_function = d_fermi_function.subs(k, to_numeric(k)) integrand = sym.lambdify( energy, energy_distribution * d_fermi_function) d_f[i] = mp.quad(integrand, [ to_numeric(energy_coefficient * (conduction_band - trap_energy_level) - 10 * self.energy_spread), to_numeric(energy_coefficient * (conduction_band - trap_energy_level) - 0.5 * self.energy_spread), to_numeric(energy_coefficient * (conduction_band - trap_energy_level) + 0.5 * self.energy_spread), to_numeric(energy_coefficient * (conduction_band - trap_energy_level) + 10 * self.energy_spread) ]) if debug: print 'dF =', d_f if d_f.size == 1: d_f = d_f[0] return d_f
def EfEc(self, Psi=Psi_zero, z=0, eV=False): coeff = 1 if eV else to_numeric(q) Psi_nodes = Psi(z) xi = np.float(self.Semiconductor.Ech_pot(T=self.T, z=1e3, eV=eV, debug=False)) return -coeff * Psi_nodes + xi