def rampvoltage(device, Vsource, begin_bias, end_bias, init_step_size, min_step, max_iter, rel_error, abs_error, callback): ''' Ramps bias with assignable callback function ''' start_bias = begin_bias if (start_bias < end_bias): step_sign = 1 else: step_sign = -1 num_successes = 0 last_bias = start_bias step_size = init_step_size while (abs(last_bias - end_bias) > min_step): print(("last end %e %e") % (last_bias, end_bias)) next_bias = last_bias + step_sign * step_size if next_bias < end_bias: next_step_sign = 1 else: next_step_sign = -1 if next_step_sign != step_sign: next_bias = end_bias print("setting to last bias %e" % (end_bias)) print("setting next bias %e" % (next_bias)) ds.circuit_alter(name=Vsource, value=next_bias) try: ds.solve(type="dc", absolute_error=abs_error, relative_error=rel_error, maximum_iterations=max_iter) except ds.error as msg: if msg[0].find("Convergence failure") != 0: raise ds.circuit_alter(name=Vsource, value=last_bias) step_size *= 0.5 print("setting new step size %e" % (step_size)) if step_size < min_step: raise NameError("Min step size too small") num_successes = 0 continue num_successes += 1 if (num_successes > 5) and (step_size < init_step_size): step_size *= 2 if step_size > init_step_size: step_size = init_step_size print("setting new step size %e" % (step_size)) num_successes = 0 print("Succeeded") last_bias = next_bias callback()
def solve(self, *args, **kwargs): for region in self.device.mesh.regions: log.info('Computing photogeneration for region: %s' % region) # Assume 1D for now nodes = ds.get_node_model_values(device=self.device.name, region=region, name='x') pg = np.zeros((len(nodes), len(self.light_source)), dtype=float) rfidx = RefractiveIndex(region.material) for idλ, λ in enumerate(self.light_source): # I know, using unicode names here is asking for trouble # ... but looks nice when using greek letters # TODO: compute the real reflection R = 0 # λ is nm, but irradiance's units are W⋅m–2⋅nm–1 P = self.light_source.irradiance(λ) Φ_0 = (P * λ * 1e-9 / PhysicalConstants.hc) * (1 - R) # α(λ)'s units are cm-1. But we use m α = rfidx.alpha(λ) * 1e+2 # TODO: do not assume conversion efficiency is 100% (1 photon = 1EHP by now) η_g = 1.0 # Debug print('\n' + '-' * 70) print('Photogeneration dump. Parameters:') print( 'λ={:.2f} nm; P(λ)={:.2f}; Φ_0(λ)={:8.2e} m-2 s-1; α(λ)={:8.2e} m-1' .format(λ, P, Φ_0, α)) print('-' * 24) print(' x(μm) | G_op(x, λ)') print('-' * 24) for idx, x in enumerate(nodes): pg[idx, idλ] = η_g * Φ_0 * exp(-α * x) print('{:8.2f} | {:8.2e}'.format(x * 1e6, pg[idx, idλ])) # Total photogeneration for each node # Conditions: # 100% quantum efficiency # Ignoring reflection pgen_by_node = np.add.reduce(pg, 1) for i, v in enumerate(pgen_by_node): ds.set_node_value(device=self.device.name, region=region, name='G_op', index=i, value=float(v)) if 'type' in kwargs: ds.solve(**kwargs) else: ds.solve(type='dc', **kwargs)
def rampvoltage(device, Vsource, begin_bias, end_bias, init_step_size, min_step, max_iter, rel_error, abs_error, callback): ''' Ramps bias with assignable callback function ''' start_bias=begin_bias if (start_bias < end_bias): step_sign=1 else: step_sign=-1 num_successes = 0 last_bias=start_bias step_size=init_step_size while(abs(last_bias - end_bias) > min_step): print("last end %e %e") % (last_bias, end_bias) next_bias=last_bias + step_sign * step_size if next_bias < end_bias: next_step_sign=1 else: next_step_sign=-1 if next_step_sign != step_sign: next_bias=end_bias print "setting to last bias %e" % (end_bias) print "setting next bias %e" % (next_bias) ds.circuit_alter(name=Vsource, value=next_bias) try: ds.solve(type="dc", absolute_error=abs_error, relative_error=rel_error, maximum_iterations=max_iter) except ds.error as msg: if msg[0].find("Convergence failure") != 0: raise ds.circuit_alter(name=Vsource, value=last_bias) step_size *= 0.5 print "setting new step size %e" % (step_size) if step_size < min_step: raise NameError("Min step size too small") num_successes = 0 continue num_successes += 1 if (num_successes > 5) and (step_size < init_step_size): step_size *= 2 if step_size > init_step_size: step_size = init_step_size print "setting new step size %e" % (step_size) num_successes = 0 print "Succeeded" last_bias=next_bias callback()
def rampbias(device, contact, end_bias, step_size, min_step, max_iter, rel_error, abs_error, callback): ''' Ramps bias with assignable callback function ''' start_bias = ds.get_parameter(device=device, name=GetContactBiasName(contact)) if (start_bias < end_bias): step_sign = 1 else: step_sign = -1 last_bias = start_bias while (abs(last_bias - end_bias) > min_step): print(("last end %e %e") % (last_bias, end_bias)) next_bias = last_bias + step_sign * step_size if next_bias < end_bias: next_step_sign = 1 else: next_step_sign = -1 if next_step_sign != step_sign: next_bias = end_bias print("setting to last bias %e" % (end_bias)) print("setting next bias %e" % (next_bias)) ds.set_parameter(device=device, name=GetContactBiasName(contact), value=next_bias) try: ds.solve(type="dc", absolute_error=abs_error, relative_error=rel_error, maximum_iterations=max_iter) except ds.error as msg: if msg[0].find("Convergence failure") != 0: raise ds.set_parameter(device=device, name=GetContactBiasName(contact), value=last_bias) step_size *= 0.5 print("setting new step size %e" % (step_size)) if step_size < min_step: raise "Min step size too small" continue print("Succeeded") last_bias = next_bias callback()
def solve(self, *args, **kwargs): if not args and not kwargs: self.initial_solution() elif kwargs.get('type', '') == 'ramp': kwargs['type'] = 'dc' start = kwargs.pop('start') stop = kwargs.pop('stop') step = kwargs.pop('step') contact = kwargs.pop('contact') for v in range(start, stop, step): set_parameter(device=self.name, name=self._contact_bias_name(contact), value=v) solve(**kwargs) self.print_currents() else: solve(*args, **kwargs) for mdl in self._models: mdl.solve(*args, **kwargs)
def rampbias(device, contact, end_bias, step_size, min_step, max_iter, rel_error, abs_error, callback): ''' Ramps bias with assignable callback function ''' start_bias=ds.get_parameter(device=device, name=GetContactBiasName(contact)) if (start_bias < end_bias): step_sign=1 else: step_sign=-1 last_bias=start_bias while(abs(last_bias - end_bias) > min_step): print("last end %e %e") % (last_bias, end_bias) next_bias=last_bias + step_sign * step_size if next_bias < end_bias: next_step_sign=1 else: next_step_sign=-1 if next_step_sign != step_sign: next_bias=end_bias print "setting to last bias %e" % (end_bias) print "setting next bias %e" % (next_bias) ds.set_parameter(device=device, name=GetContactBiasName(contact), value=next_bias) try: ds.solve(type="dc", absolute_error=abs_error, relative_error=rel_error, maximum_iterations=max_iter) except ds.error as msg: if msg[0].find("Convergence failure") != 0: raise ds.set_parameter(device=device, name=GetContactBiasName(contact), value=last_bias) step_size *= 0.5 print "setting new step size %e" % (step_size) if step_size < min_step: raise "Min step size too small" continue print "Succeeded" last_bias=next_bias callback()
CreateMesh(device=device, region=region) SetParameters(device=device, region=region) set_parameter(device=device, region=region, name="taun", value=1e-8) set_parameter(device=device, region=region, name="taup", value=1e-8) SetNetDoping(device=device, region=region) print_node_values(device=device, region=region, name="NetDoping") InitialSolution(device, region) # Initial DC solution solve(type="dc", absolute_error=1.0, relative_error=1e-10, maximum_iterations=30) DriftDiffusionInitialSolution(device, region) ### ### Drift diffusion simulation at equilibrium ### solve(type="dc", absolute_error=1e10, relative_error=1e-10, maximum_iterations=30) #### #### Ramp the bias to 0.5 Volts #### # bias_volt = 0.0
tag=bottom_tag, name=bottom_tag) ds.finalize_mesh(mesh=meshname) ds.create_device(mesh=meshname, device=device) # Add some physics for region, params in zip(regions, [CdS_params, CdTe_params]): for name, value in params.items(): ds.set_parameter(device=device, region=region, name=name, value=value) set_dd_parameters(device=device, region=region) # Initial DC solution ds.solve(type="dc", absolute_error=1.0e10, relative_error=1e10, maximum_iterations=150) # for region in regions: # DriftDiffusionInitialSolution(device, region) ### ### Drift diffusion simulation at equilibrium ### currs = [] volts = [] bias_contact = "top_n1" for volt in range(-20, 20, 1): volt = volt / 10 volts.append(volt) ds.set_parameter(device=device, name=f"{bias_contact}_bias",
def setup_drift_diffusion(self, dielectric_const=11.9, intrinsic_carriers=1E10, work_function=4.05, band_gap=1.124, Ncond=3E19, Nval=3E19, mobility_n=1107, mobility_p=424.6, Nacceptors=0, Ndonors=0): """ Sets up equations for a drift-diffusion style of dc current transport. kwargs here are device-level parameters, imbuing all regions with these properties If a specific region or material is to have a different set of parameters, they can be set through the Region constructor. :param dielectric_const: :param intrinsic_carriers: :param work_function: :param band_gap: :param Ncond: :param Nval: :param mobility_n: :param mobility_p: :param Nacceptors: :param Ndonors: :return: """ # Begin legacy copy-paste job # Set silicon parameters device = self.name region = self.regions[0].name eps_si = dielectric_const n_i = 1E10 k = kb mu_n = mobility_n mu_p = mobility_p set_parameter(device=device, region=region, name="Permittivity", value=eps_si * eps_0) set_parameter(device=device, region=region, name="ElectronCharge", value=q) set_parameter(device=device, region=region, name="n_i", value=n_i) set_parameter(device=device, region=region, name="T", value=T) set_parameter(device=device, region=region, name="kT", value=k * T) set_parameter(device=device, region=region, name="V_t", value=k * T / q) set_parameter(device=device, region=region, name="mu_n", value=mu_n) set_parameter(device=device, region=region, name="mu_p", value=mu_p) # default SRH parameters set_parameter(device=device, region=region, name="n1", value=n_i) set_parameter(device=device, region=region, name="p1", value=n_i) set_parameter(device=device, region=region, name="taun", value=1e-5) set_parameter(device=device, region=region, name="taup", value=1e-5) # CreateNodeModel 3 times for name, value in [('Acceptors', "1.0e18*step(0.5e-5-x)"), ('Donors', "1.0e18*step(x-0.5e-5)"), ('NetDoping', "Donors-Acceptors")]: result = node_model(device=device, region=region, name=name, equation=value) logger.debug(f"NODEMODEL {device} {region} {name} '{result}'") print_node_values(device=device, region=region, name="NetDoping") model_name = "Potential" node_solution(name=model_name, device=device, region=region) edge_from_node_model(node_model=model_name, device=device, region=region) # Create silicon potentialOnly if model_name not in get_node_model_list(device=device, region=region): logger.debug("Creating Node Solution Potential") node_solution(device=device, region=region, name=model_name) edge_from_node_model(node_model=model_name, device=device, region=region) # require NetDoping for name, eq in ( ("IntrinsicElectrons", "n_i*exp(Potential/V_t)"), ("IntrinsicHoles", "n_i^2/IntrinsicElectrons"), ("IntrinsicCharge", "kahan3(IntrinsicHoles, -IntrinsicElectrons, NetDoping)"), ("PotentialIntrinsicCharge", "-ElectronCharge * IntrinsicCharge")): node_model(device=device, region=region, name=name, equation=eq) node_model(device=device, region=region, name=f"{name}:{model_name}", equation=f"simplify(diff({eq},{model_name}))") # CreateNodeModelDerivative(device, region, name, eq, model_name) ### TODO: Edge Average Model for name, eq in (("ElectricField", "(Potential@n0-Potential@n1)*EdgeInverseLength"), ("PotentialEdgeFlux", "Permittivity * ElectricField")): edge_model(device=device, region=region, name=name, equation=eq) edge_model(device=device, region=region, name=f"{name}:{model_name}@n0", equation=f"simplify(diff({eq}, {model_name}@n0))") edge_model(device=device, region=region, name=f"{name}:{model_name}@n1", equation=f"simplify(diff({eq}, {model_name}@n1))") equation(device=device, region=region, name="PotentialEquation", variable_name=model_name, node_model="PotentialIntrinsicCharge", edge_model="PotentialEdgeFlux", variable_update="log_damp") # Set up the contacts applying a bias is_circuit = False for contact_name in get_contact_list(device=device): set_parameter(device=device, name=f"{contact_name}_bias", value=0.0) # CreateSiliconPotentialOnlyContact(device, region, contact_name) # Start # Means of determining contact charge # Same for all contacts if not InNodeModelList(device, region, "contactcharge_node"): create_node_model(device, region, "contactcharge_node", "ElectronCharge*IntrinsicCharge") #### TODO: This is the same as D-Field if not InEdgeModelList(device, region, "contactcharge_edge"): CreateEdgeModel(device, region, "contactcharge_edge", "Permittivity*ElectricField") create_edge_model_derivatives(device, region, "contactcharge_edge", "Permittivity*ElectricField", "Potential") # set_parameter(device=device, region=region, name=GetContactBiasName(contact), value=0.0) contact_bias_name = f"{contact_name}_bias" contact_model_name = f"{contact_name}nodemodel" contact_model = f"Potential -{contact_bias_name} + ifelse(NetDoping > 0, -V_t*log({CELEC_MODEL!s}/n_i), V_t*log({CHOLE_MODEL!s}/n_i))"\ CreateContactNodeModel(device, contact_name, contact_model_name, contact_model) # Simplify it too complicated CreateContactNodeModel( device, contact_name, "{0}:{1}".format(contact_model_name, "Potential"), "1") if is_circuit: CreateContactNodeModel( device, contact_name, "{0}:{1}".format(contact_model_name, contact_bias_name), "-1") if is_circuit: contact_equation(device=device, contact=contact_name, name="PotentialEquation", variable_name="Potential", node_model=contact_model_name, edge_model="", node_charge_model="contactcharge_node", edge_charge_model="contactcharge_edge", node_current_model="", edge_current_model="", circuit_node=contact_bias_name) else: contact_equation(device=device, contact=contact_name, name="PotentialEquation", variable_name="Potential", node_model=contact_model_name, edge_model="", node_charge_model="contactcharge_node", edge_charge_model="contactcharge_edge", node_current_model="", edge_current_model="") # Biggie # Initial DC solution solve(type="dc", absolute_error=1.0, relative_error=1e-10, maximum_iterations=30) drift_diffusion_initial_solution(device, region) solve(type="dc", absolute_error=1e10, relative_error=1e-10, maximum_iterations=30)
def solve(self, *args, **kwargs): if not args and not kwargs: self.initial_solution() else: solve(*args, **kwargs)