def set_parameters_for(self, device_name, region_name): from ds import set_parameter props = [ p for p in dir(self) if not p.startswith('_') and isinstance(getattr(self, p), MaterialProperty) ] for pname in props: set_parameter(device=device_name, region=region_name, name=pname, value=getattr(self, pname))
def set_parameters_for(self, device_name, region_name): props = [ p for p in dir(self) if not p.startswith('_') and isinstance(getattr(self, p), MaterialProperty) ] for pname in props: set_parameter( device=device_name, region=region_name, name=pname, value=getattr(self, pname).value ) log.debug('set_parameter(device={}, region={}, name={}, value={})'.format( device_name, region_name, pname, getattr(self, pname).value))
def set_parameters_for(self, device, region): """ Use this function to register the parameters of this class into the simulation context. Internally uses ds.set_parameters() """ props = [ pname for pname in dir(self) if not pname.startswith('_') and not callable(getattr(self, pname)) ] for propname in props: set_parameter(device=device, region=region, name=propname, value=getattr(self, propname))
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 set_dd_parameters(device, region, permittivity=11.1, n_i=1E10, T=300, mu_n=400, mu_p=200, taun=1E-5, taup=1E-5): names = [ 'Permittivity', 'ElectronCharge', 'n_i', 'T', 'kT', 'V_t', 'mu_n', 'mu_p', 'n1', 'p1', 'taun', 'taup' ] values = [ permittivity * eps_0, q, n_i, T, k * T, k * T / q, mu_n, mu_p, n_i, n_i, taun, taup ] for name, value in zip(names, values): ds.set_parameter(device=device, region=region, name=name, value=value)
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 initial_solution(self): self.setup_context() for region in self.mesh.regions: # Create Potential, Potential@n0, Potential@n1 CreateSolution(self.name, region, "Potential") # Create potential only physical models # TODO: move to materials, relate region with material CreateSiliconPotentialOnly(self.name, region) # Set up the contacts applying a bias # TODO: Try to use self.contacts instead it is more correct for # the bias to be 0, and it looks like there are side effects for c in self.mesh.contacts: set_parameter(device=self.name, name=self._contact_bias_name(c), value=0.0) # TODO: move to models module CreateSiliconPotentialOnlyContact(self.name, region, c)
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 set_parameters(device=None, region=None, **kwargs): # TODO check if set_parameters works wihtout setting a device? Docs says it does... # if device is None: # device = ds.get_device_list()[0] if device is None and region is None: for name, value in kwargs.items(): ds.set_parameter(name=name, value=value) elif region is None: for name, value in kwargs.items(): ds.set_parameter(device=device, name=name, value=value) else: for name, value in kwargs.items(): ds.set_parameter(device=device, region=region, name=name, value=value)
def setup_context(self): """ Initialize the context for the equations. Basically sets some variables. Region is an instance of the mesh.Region class """ # Region context from pydevsim import T, kb, q for region in self.mesh.regions: for n, v in region.material.parameters.items(): set_parameter(device=self.name, region=region.name, name=n, value=v) # General set_parameter(device=self.name, region=region.name, name="ElectronCharge", value=q) set_parameter(device=self.name, region=region.name, name="q", value=q) set_parameter(device=self.name, region=region.name, name="T", value=T) set_parameter(device=self.name, region=region.name, name="kT", value=kb * T) set_parameter(device=self.name, region=region.name, name="V_t", value=kb * T / q)
relative_error=1e-10, maximum_iterations=30) # 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", value=float(volt)) ds.solve(type="dc", absolute_error=1e1, relative_error=1e-10, maximum_iterations=30) e_current = ds.get_contact_current(device=device, contact=bias_contact, equation="ElectronContinuityEquation") h_current = ds.get_contact_current(device=device, contact=bias_contact, equation="HoleContinuityEquation") current = e_current + h_current currs.append(current) print(volts, currs) plt.plot(volts, currs)
def SetSiliconParameters(device, region, T=300, esp_si=11.1, n_i=1E10, mu_n=400, mu_p=200, tau_n=1E-5, tau_p=1E-5): ''' Sets physical parameters assuming constants ''' #### TODO: make T a free parameter and T dependent parameters as models ds.set_parameter(device=device, region=region, name="Permittivity", value=esp_si * eps_0) ds.set_parameter(device=device, region=region, name="ElectronCharge", value=q) ds.set_parameter(device=device, region=region, name="n_i", value=n_i) ds.set_parameter(device=device, region=region, name="T", value=T) ds.set_parameter(device=device, region=region, name="kT", value=k * T) ds.set_parameter(device=device, region=region, name="V_t", value=k * T / q) ds.set_parameter(device=device, region=region, name="mu_n", value=mu_n) ds.set_parameter(device=device, region=region, name="mu_p", value=mu_p) #default SRH parameters ds.set_parameter(device=device, region=region, name="n1", value=n_i) ds.set_parameter(device=device, region=region, name="p1", value=n_i) ds.set_parameter(device=device, region=region, name="taun", value=tau_n) ds.set_parameter(device=device, region=region, name="taup", value=tau_p)
ds.add_1d_interface(mesh=meshname, tag=interface_tag, name=f"{CdS_region}_to_{CdTe_region}_interface") ds.add_1d_contact(material='metal', mesh=meshname, 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 = []
def set_dd_parameters(device, region, permittivity=11.1, n_i=1E10, T=300, mu_n=400, mu_p=200, taun=1E-5, taup=1E-5): names = [ 'permittivity', 'q', 'n_i', 'T', 'k', 'kT', 'V_t', 'mobility_n', 'mobility_p', 'n1', 'p1', 'taun', 'taup' ] values = [ permittivity * eps_0, q, n_i, T, k, k * T, k * T / q, mu_n, mu_p, n_i, n_i, taun, taup ] for name, value in zip(names, values): ds.set_parameter(device=device, region=region, name=name, value=value) # Setup the solutions for the potential, electron and hole densities pot = 'potential' electron_density = 'electron_density' hole_density = 'hole_density' for var in [pot, electron_density, hole_density]: create_solution(device=device, region=region, name=var) # Now for some poisson's equation # Create some nodemodels n_ie = 'n_ie', f"n_i*exp(q*{pot}/k*T)" # Intrinsic electron density n_ih = 'n_ih', f'n_i^2/{n_ie[0]}' # Intrinsic hole density net_n_i = 'net_n_i', f'kahan4(-{n_ie[0]}, {n_ih[0]}, p_doping, -n_doping)' # Net intrinsic charge net_n_i_charge = 'net_n_i_charge', f'q*{net_n_i[0]}' #PotentialIntrinsicCharge for name, eq in [n_ie, n_ih, net_n_i, net_n_i_charge]: ds.node_model(device=device, region=region, name=name, equation=eq) create_derivatives(device, region, name, eq, pot) E_field = 'E_field', f'({pot}@n0-{pot}@n1)*EdgeInverseLength' D_field = 'D_field', 'E_field * permittivity' #PotentialEdgeFlux ?? wtf # Initialize the electron and hole densities for carrier, init in zip([electron_density, hole_density], [n_ie, n_ih]): ds.set_node_values(device=device, region=region, name=carrier, init_from=init[0]) # setup edge nodes for edge_name, eq in [E_field, D_field]: ds.edge_model(device=device, region=region, name=edge_name, equation=eq) create_derivatives(device, region, edge_name, eq, pot) # Create PE poisson_RHS = 'PoissonRHS', f'({hole_density} - {electron_density} + p_doping - n_doping)' #*q/(permittivity)' # AKA pne ds.node_model(device=device, region=region, name=poisson_RHS[0], equation=poisson_RHS[1]) create_derivatives(device, region, poisson_RHS[0], poisson_RHS[1], hole_density, electron_density) ds.equation(device=device, region=region, name="PoissonEquation", variable_name=pot, node_model=poisson_RHS[0], edge_model=D_field[0]) # Use stupid bernouli formalism # Check if exists? ds.edge_from_node_model(device=device, region=region, node_model=pot) beta_vdiff = 'beta_vdiff', f'({pot}@n0 - {pot}@n1)*q/kT' ds.edge_model(device=device, region=region, name=beta_vdiff[0], equation=beta_vdiff[1]) bernouli = 'bern', f'B({beta_vdiff[0]})' ds.edge_model(device=device, region=region, name=bernouli[0], equation=bernouli[1]) # Create continuity equations E_qf_n = ( 'quasi_fermi_n', f'q*({pot}-electron_affinity) + k*T*log({electron_density}/N_cond)') E_qf_p = ( 'quasi_fermi_p', f'q*({pot}-electron_affinity) - k*T*log({hole_density}/N_val) - band_gap' ) J_e = 'e_current', f'q*mobility_n*{electron_density}*EdgeInverseLength*({E_qf_n[0]}@n0-{E_qf_n[0]}@n1)' J_h = 'h_current', f'q*mobility_p*{hole_density}*EdgeInverseLength*({E_qf_p[0]}@n0-{E_qf_p[0]}@n1)' for J in [E_qf_n, E_qf_p, J_e, J_h]: ds.edge_model(device=device, region=region, name=J[0], equation=J[1]) ds.node_model(device=device, region=region, name=J[0], equation=J[1]) for node_model in [pot, electron_density, hole_density]: # Check if exists?! ds.edge_from_node_model(device=device, region=region, node_model=node_model) create_derivatives(device, region, J[0], J[1], node_model) for node_model in [n_ie, n_ih, net_n_i, net_n_i_charge]: ds.print_node_values(device=device, region=region, name=node_model[0]) for edge_model in [E_qf_n, E_qf_p, J_e, J_h]: ds.print_edge_values(device=device, region=region, name=edge_model[0])
material="InAs", region=n2_region, tag1="bot_b", tag2="bot_n2") ds.add_1d_interface(mesh=meshname, tag="bot_b", name=f"{b_region}_to_{n2_region}") ds.add_1d_contact(mesh=meshname, name="bot_n2", tag="bot_n2", material="metal") ds.finalize_mesh(mesh=meshname) ds.create_device(mesh=meshname, device=device) #### #### Set parameters for 300 K #### ds.set_parameter(name="T", value=300) for region in regions: if region == b_region: affinity = b_affinity bandgap = b_band_gap doping = b_doping else: affinity = n_affinity bandgap = n_band_gap doping = 1E11 SetSiliconParameters(device, region, Affinity=affinity, EG300=bandgap) set_parameter(device=device, region=region, name="taun", value=1e-8) set_parameter(device=device, region=region, name="taup", value=1e-8) set_parameter(device=device, region=region, name="n1", value=1e10) set_parameter(device=device, region=region, name="p1", value=1e10)
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 set_bias(self, tag, bias_volt): set_parameter(device=self.name, name=f"{tag}_bias", value=float(bias_volt))
from adaptusim import setup_logger logger = setup_logger(__name__) # Fundamental Constants # Permitivity of free space eps_0 = 8.854187817e-14 # F/cm^2 # The electron charge quanta q = 1.60217662e-19 # Couloumbs # Boltzmann's constant kb = 1.3806503e-23 # J/K # Planck's constant planck_h = 6.626E-34 #J*s # Default Ambient temperature T = 300 # K # Thermal energy kT = kb * T # Thermal voltage Vt = kb * T / q # Set them globally for name in ['eps_0', 'q', 'kb', 'planck_h', 'T', 'kT', 'Vt']: value = eval(name) logger.info(f"Now setting global variable {name} to {value:.3g}") set_parameter(name=name, value=value) DS_DATADIR = abspath(join(dirname(__file__), 'data')) ECE_NAME = "ElectronContinuityEquation" HCE_NAME = "HoleContinuityEquation" CELEC_MODEL = "(1e-10 + 0.5*abs(NetDoping+(NetDoping^2 + 4 * n_i^2)^(0.5)))" CHOLE_MODEL = "(1e-10 + 0.5*abs(-NetDoping+(NetDoping^2 + 4 * n_i^2)^(0.5)))"
ds.create_device(mesh=meshname, device=device) setup_physical_constants(device, region) setup_poisson_parameters(device, region, p_doping=1e18) setup_poisson(device, region) get_ds_status() ds.solve(type="dc", absolute_error=10, relative_error=10, maximum_iterations=30) get_ds_status() print("Post solve") setup_continuity(device, region) ds.solve(type="dc", absolute_error=10, relative_error=1e1, maximum_iterations=30) for volt in [-1, 0, 1]: ds.set_parameter(device=device, name='top_contact_bias', value=float(volt)) chrg = ds.get_node_model_values(device=device, region=region, name=total_charge) pot = ds.get_edge_model_values(device=device, region=region, name=e_field) for data in [chrg, pot]: plt.figure() plt.plot(data) plt.show() # Ei = (Ec + Ev) / 2