def B(x): """ Bernoulli function B(x)=x/(exp(x)-1), implemented for double precision calculation """ # For small x, Taylor expansion should be used instead of direct # formula which will is not even defined for x=0. BP = 0.0031769106669272879 # Taylor series x2 = x**2. taylor = 1. - x / 2. + x2 / 12. - x2**2 / 720. # Direct calculation # 1e-20 is added to avoid NaN for small x. It only modifies # the result if abs(x)<BP. In such a case, the direct formula # is not used anyway. # In order to avoid overflow errors, maximum x is limited to 700 # to ensure that exp(xn) is always representable in double precision. # Anyway xn/(exp(xn)-1.) is almost 0. for large x xn = ad.where(x < 700., x, 700.) direct = xn / ((ad.exp(xn) - 1.) + 1e-20) # Breakpoint BP is chosen so that Taylor series and direct # calculation coincide in double precision. return ad.where(abs(x) < BP, taylor, direct)
def c(self, ctx, eq, Ef, numeric_parameters=False): Ef_raw = Ef if self.Ef_max is not None: Ef = where(Ef < self.Ef_max, Ef, self.Ef_max) if self.logger.isEnabledFor(logging.INFO): if np.any(nvalue(Ef) != nvalue(Ef_raw)): self.logger.info( 'c(%r): clipping Ef>%r, max(Ef)=%r' % (eq.prefix, self.Ef_max, np.amax( nvalue(Ef_raw)))) N0 = self.N0(ctx, eq) if numeric_parameters: N0 = nvalue(N0) Vt = ctx.varsOf(eq.thermal)['Vt'] if numeric_parameters: Vt = nvalue(Vt) v = Ef / Vt if self.c_limit: v_raw = v v = where(v <= 0., v, 0.) if self.logger.isEnabledFor(logging.INFO): if np.any(nvalue(v_raw) != nvalue(v)): self.logger.info( 'c(%r): clipping Ef/kT>0, max(Ef/kT)=%r' % (eq.prefix, np.amax( nvalue(v_raw)))) c = exp(v) * N0 return c
def mobility(self, parent, ctx, eq): mu0 = ctx.param(eq, 'mu0') gamma = ctx.param(eq, 'gamma') v = gamma * sqrt(ctx.varsOf(eq.poisson)['Ecellm'] + 1e-10) v_max = log(1e10) v_min = -v_max v = where(v < v_max, v, v_max) v = where(v > v_min, v, v_min) mu = mu0 * exp(v) mu_face = eq.mesh.faceaverage(mu) ctx.varsOf(eq)['mu_face'] = mu_face ctx.varsOf(eq)['mu_cell'] = mu
def species_v_D_limited(ctx, eq): "Species with concentration limitation" v, D = species_v_D_charged_from_params(ctx, eq) c = ctx.varsOf(eq)['c'] c = where(v > 0, c[eq.mesh.faces['+']], c[eq.mesh.faces['-']]) v = v * (1. - c / ctx.param(eq, 'N0')) return v, D
def Aux2(x): """ Auxiliary function Aux2(x) = 1./(exp(x)+1) Properties: Aux2(x)+Aux2(-x)=1 """ xn = ad.where(x < 700., x, 700.) return 1. / (ad.exp(xn) + 1.)
def image_correction(self, ctx, eq, ixto): if not self.image_force: return 0. n = eq.mesh.boundaries[self.name]['normal'] F = eq.z * eq.mesh.dotv(ctx.varsOf(eq.poisson)['Ecellv'][ixto], n) F = where(F > self.F_eps, F, self.F_eps) return -eq.z * \ functions.EmtageODwyerBarrierLowering( F, getitem(ctx.varsOf(eq.poisson)['epsilon'], ixto))
def Ef(self, ctx, eq): c = ctx.varsOf(eq)['c'] c_raw = c c = where(c > self.c_eps, c, self.c_eps) # avoid NaN if self.logger.isEnabledFor(logging.INFO): if np.any(nvalue(c_raw) != nvalue(c)): self.logger.info( 'Ef(%r): clipping c<%r, min(c)=%r' % (eq.prefix, self.c_eps, np.amin(nvalue(c_raw)))) N0 = self.N0(ctx, eq) return ctx.varsOf(eq.thermal)['Vt'] * np.log(c / N0)
def _load(self, ctx, eq): variables = ctx.varsOf(eq) if 'gdos_data' in variables: return variables['gdos_data'] info = self.disorder.disorder_parameters(self, ctx, eq) nsigma = info['nsigma'] nsigma = where(nsigma > self.nsigma_min, nsigma, self.nsigma_min) nsigma = where(nsigma < self.nsigma_max, nsigma, self.nsigma_max) if np.any(nsigma != info['nsigma']): self.logger.info( 'nsigma clipped to ensure numerical stability: probably not calculating what you want. This is OK in initial Newton iterations, but not in the final one') N0 = info['N0'] c = ctx.varsOf(eq)['c'] / N0 assert self.c_eps >= self.impl.I_min assert self.c_max <= self.impl.I_max c = where(c > self.c_eps, c, self.c_eps) c = where(c < self.c_max, c, self.c_max) b = self.impl.b(np.sqrt(2.) * nsigma, c) info.update(b=b, c=c, nsigma=nsigma) variables['gdos_data'] = info return info
def mobility(self, parent, ctx, eq): info = self._load(ctx, eq) a = _egdm.N0toa(info['N0']) nsigma = info['nsigma'] sigma = info['sigma'] mu_cell = _egdm.mu0t(nsigma, info['mu0']) c = info['c'] if self.use_g1: mu_cell = mu_cell * \ _egdm.g1(nsigma, where(c < self.g1_max_c, c, self.g1_max_c)) mu_face = eq.mesh.faceaverage(mu_cell) E = ctx.varsOf(eq.poisson)['E'] if self.use_g2: En = (E * a) / sigma En2 = En**2 En2_max = self.g2_max_En**2 mu_face = mu_face * \ _egdm.g2_En2(nsigma, where(En2 < En2_max, En2, En2_max)) ctx.varsOf(eq).update( mu_face=mu_face, mu_cell=eq.mesh.cellaveragev(mu_face))
def D_mu(self, parent, ctx, eq): info = self._load(ctx, eq) assert self.g3_max_c <= self.gdosImpl.I_max assert self.c_eps >= self.gdosImpl.I_min assert self.nsigma_max * np.sqrt(2.) <= self.gdosImpl.a_max c = info['c'] nsigma = info['nsigma'] inlimit_g3 = c < self.g3_max_c def branch_if_inlimit(ix): return getitem(info['b'], ix) def branch_cut_limit(ix): return self.impl.b( np.sqrt(2.) * getitem(nsigma, ix), self.g3_max_c) b_g3 = branch(inlimit_g3, branch_if_inlimit, branch_cut_limit) c_g3 = where(inlimit_g3, c, self.g3_max_c) g3 = _egdm.g3(nsigma, c_g3, impl=self.impl, b=b_g3) return eq.mesh.faceaverage(g3*info['Vt'])
def evaluate(self, ctx, eq): if ctx.solver.poissonOnly: return assert eq.electron_eq.poisson is eq.hole_eq.poisson assert eq.electron_eq.thermal is eq.hole_eq.thermal poissonvars = ctx.varsOf(eq.electron_eq.poisson) epsilon = poissonvars['epsilon'] Vt = ctx.varsOf(eq.electron_eq.thermal)['Vt'] nvars = ctx.varsOf(eq.electron_eq) pvars = ctx.varsOf(eq.hole_eq) evars = ctx.varsOf(eq.eq) if self.binding_energy_param: Eb = ctx.param(eq.eq, 'binding_energy') a = scipy.constants.elementary_charge / \ (Eb * 4 * np.pi * epsilon) else: a = ctx.param(eq.eq, 'distance') Eb = scipy.constants.elementary_charge / \ (4 * np.pi * epsilon * a) u = 3. / (4. * np.pi * a**3) # m^-3 v = exp(-Eb / Vt) # 1 b = (scipy.constants.elementary_charge / (8 * np.pi)) * \ poissonvars['Ecellm'] / (epsilon * Vt**2) # 1 # b=0. t = functions.OnsagerFunction( where(b < self.b_max, b, self.b_max) + self.b_eps) # 1 gamma = scipy.constants.elementary_charge * \ (nvars['mu_cell'] + pvars['mu_cell']) / \ epsilon # m^3 /s r = gamma * (nvars['c'] * pvars['c'] - ctx.common_param( [eq.electron_eq, eq.hole_eq], 'npi')) # 1/(m^3 s) # TODO d = gamma * evars['c'] * u * v * t ctx.outputCell([eq.eq, self.name, 'recombination'], r, unit=ctx.units.dconcentration_dt) ctx.output([eq.eq, self.name, 'dissociation'], d, unit=ctx.units.dconcentration_dt) f = r - d self.add(ctx, f, plus=[eq.eq], minus=[eq.hole_eq, eq.electron_eq])
def value(self, ctx, eq): if isinstance(ctx.solver, solver.RamoShockleyCalculation): return where(self.name == ctx.solver.boundary_name, 1, 0) return self.potential(ctx, eq)
def I(self, a, b): v = self._value(a, b) l_max = np.log(self.I_max) v = ad.where(v <= l_max, v, l_max) return ad.exp(v)