def create_electron_current(device, region, mu_n): """ Sets up the electron current :param device: :param region: :param mu_n: mobility :return: """ ensure_edge_from_node_model_exists(device, region, "Potential") ensure_edge_from_node_model_exists(device, region, "Electrons") ensure_edge_from_node_model_exists(device, region, "Holes") # Make sure the bernoulli functions exist if "Bern01" not in get_edge_model_list(device=device, region=region): create_bernoulli(device, region) # test for requisite models here # Jn = "ElectronCharge*{0}*EdgeInverseLength*V_t*(Electrons@n1*Bern10 - Electrons@n0*Bern01)".format(mu_n) Jn = "ElectronCharge*{0}*EdgeInverseLength*V_t*kahan3(Electrons@n1*Bern01, Electrons@n1*vdiff, -Electrons@n0*Bern01)".format( mu_n) # Jn = "ElectronCharge*{0}*EdgeInverseLength*V_t*((Electrons@n1-Electrons@n0)*Bern01 + Electrons@n1*vdiff)".format(mu_n) edge_model(device=device, region=region, name="ElectronCurrent", equation=Jn) for i in ("Electrons", "Potential", "Holes"): create_edge_model_derivatives(device, region, "ElectronCurrent", Jn, i)
def create_edge_and_derivatives(device, region, model, equation, deriv_list=None): """ Creates derivatives with respect to all the variables of the equation named model in the device and region. These equations can ONLY contain the following things: Constants like 1E-2 Parameters that are set in that device or region EdgeModel strings that are set in that area These equations CANNOT use NodeModel strings. If you want NodeModel strings, you need to call ds.edge_model_from_node :param device: :param region: :param model: :param equation: :param deriv_list: :return: """ ds.edge_model(device=device, region=region, name=model, equation=equation) if deriv_list is not None: for var in deriv_list: for node in ['n0', 'n1']: ds.edge_model(device=device, region=region, name=f'{model}:{var}@{node}', equation=f'diff({equation}, {var}@{node})')
def create_edge_model_derivatives(device, region, model, expression, variable): """ Creates edge model derivatives """ for num in ['n0', 'n1']: edge_model(device=device, region=region, name=f"{model}:{variable}@{num}", equation=f"simplify(diff({expression}, {variable}@{num}))")
def setup_continuity(device, region, **kwargs): """ :param device: :param region: :param kwargs: :return: """ logger.debug("Setting up continuity equation") # These are variables that are to be solved during the simulation # # TODO check if these already exist? # for sol_variable in [hole_density, electron_density, potential]: # create_solution(device, region, sol_variable) # These are variables that exist per each node and may be spatially dependant # Generation rate # U_SRH_eq = f'{q}*({electron_density}*{hole_density} - {intrinsic_charge}^2)/(tau_p*({electron_density}+{intrinsic_charge}) + tau_n*({hole_density}+{intrinsic_charge}))' # U_SRH_eq = f'({electron_density}*{hole_density} - {intrinsic_charge}^2)/(tau_p*({electron_density}+{intrinsic_charge}) + tau_n*({hole_density}+{intrinsic_charge}))' # for name, eq in zip([U_SRH, e_gen, h_gen], [U_SRH_eq, f'-{q}*{U_SRH_eq}', f"{q}*{U_SRH_eq}"]) : # # TODO can we move SRH to its own function/method/module? yes, yes we can. # create_node_and_derivatives(device, region, name, eq, [electron_density, hole_density]) # Bernouli setup # ds.node_model(device=device, region=region, name='dp', equation=f'{delta_pot}*{Vt}_inv*0.5') # Begin finite difs of phi # e_current_eq = f"{ q}*mobility_n*EdgeInverseLength*k*T/{q}*kahan3({electron_density}@n1*Bern01, {electron_density}@n1*{delta_pot}, -{electron_density}@n0*Bern01)" for name, eq in [(f'{delta_pot}', f"q/(k*T)*({potential}@n1 - {potential}@n0)"), (f'Bern01', f'B({delta_pot})'), (f'Bern00', f'B(-{delta_pot})'), (f'{delta_pot}:{potential}@n0', f'-q/(k*T)'), (f'{delta_pot}:{potential}@n1', f'-{delta_pot}:{potential}@n0'), (f'Bern01:{potential}@n0', f'dBdx({delta_pot})*{delta_pot}:{potential}@n0'), (f'Bern01:{potential}@n1', f'-Bern01:{potential}@n0'), (f'Bern00:{potential}@n0', f'-dBdx(-{delta_pot})*{delta_pot}:{potential}@n0'), (f'Bern00:{potential}@n1', f'-Bern00:{potential}@n0')]: ds.edge_model(device=device, region=region, name=name, equation=eq) # electron continuity e_current_eq = f"-mobility_n*k*T*EdgeInverseLength*(Bern00*{electron_density}@n0 - Bern01*{electron_density}@n1)" create_edge_and_derivatives(device, region, e_current_name, e_current_eq, [electron_density, hole_density, potential]) ds.equation(device=device, region=region, name='ECE', variable_name=electron_density, time_node_model=electron_density, edge_model=e_current_name, node_model=e_gen) # hole continuity h_current_eq =f"mobility_p*k*T*EdgeInverseLength*(-Bern00*{hole_density}@n1 + Bern01*{hole_density}@n0)" create_edge_and_derivatives(device, region, h_current_name, h_current_eq, [electron_density, hole_density, potential]) ds.equation(device=device, region=region, name='HCE', variable_name=hole_density, time_node_model=hole_density, edge_model=h_current_name, node_model=h_gen)
def create_hole_current(device, region, mu_p): """ Hole current """ ensure_edge_from_node_model_exists(device, region, "Potential") ensure_edge_from_node_model_exists(device, region, "Holes") # Make sure the bernoulli functions exist if "Bern01" not in get_edge_model_list(device=device, region=region): create_bernoulli(device, region) # test for requisite models here # Jp ="-ElectronCharge*{0}*EdgeInverseLength*V_t*(Holes@n1*Bern01 - Holes@n0*Bern10)".format(mu_p) Jp = "-ElectronCharge*{0}*EdgeInverseLength*V_t*kahan3(Holes@n1*Bern01, -Holes@n0*Bern01, -Holes@n0*vdiff)".format( mu_p) # Jp ="-ElectronCharge*{0}*EdgeInverseLength*V_t*((Holes@n1 - Holes@n0)*Bern01 - Holes@n0*vdiff)".format(mu_p) edge_model(device=device, region=region, name="HoleCurrent", equation=Jp) for i in ("Holes", "Potential", "Electrons"): create_edge_model_derivatives(device, region, "HoleCurrent", Jp, i)
def CreateEdgeModel(device, region, model, expression): """ Creates an edge model """ result = edge_model(device=device, region=region, name=model, equation=expression) log.debug("EDGEMODEL {d} {r} {m} \"{re}\"".format(d=device, r=region, m=model, re=result))
def create_bernoulli(device, region): """ Creates the Bernoulli function for Scharfetter Gummel """ # test for requisite models here ensure_edge_from_node_model_exists(device, region, "Potential") vdiffstr = "(Potential@n0 - Potential@n1)/V_t" for model, expression in [("vdiff", "(Potential@n0 - Potential@n1)/V_t"), ("vdiff:Potential@n0", "V_t^(-1)"), ("vdiff:Potential@n1", "-vdiff:Potential@n0"), ( "Bern01", "B(vdiff)", ), ("Bern01:Potential@n0", "dBdx(vdiff) * vdiff:Potential@n0"), ("Bern01:Potential@n1", "-Bern01:Potential@n0")]: result = edge_model(device=device, region=region, name=model, equation=expression) logger.debug(f"New edgemodel {device} {region} {model} -> '{result}'" ) #.format(d=device, r=region, m=model, re=result))
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])
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 setup_continuity(device, region, **kwargs): """ :param device: :param region: :param kwargs: :return: """ # These are variables that are to be solved during the simulation # TODO check if these already exist? for sol_variable in [hole_density, electron_density, potential]: create_solution(device, region, sol_variable) # These are variables that exist per each node and may be spatially dependant # Generation rate U_SRH_eq = f'{q}*({electron_density}*{hole_density} - {intrinsic_charge}^2)/(tau_p*({electron_density}+{intrinsic_electrons}) + tau_n*({hole_density}+{intrinsic_holes}))' for name, eq in zip([U_SRH, e_gen, h_gen], [U_SRH_eq, '-' + U_SRH_eq, U_SRH_eq]): # TODO can we move SRH to its own function/method/module? yes, yes we can. create_node_and_derivatives(device, region, name, eq, [electron_density, hole_density]) # Bernouli setup ds.node_model(device=device, region=region, name=Vt, equation='k*T/q') ds.node_model(device=device, region=region, name=Vt + "_inv", equation='q/(k*T)') for name, eq in [ (f'{delta_pot}', f"q/(k*T)*({potential}@n0 - {potential}@n1)"), (f'Bern01', f'B({delta_pot})'), (f'{delta_pot}:{potential}@n0', f'q/(k*T)'), (f'{delta_pot}:{potential}@n1', f'-{delta_pot}:{potential}@n0'), (f'Bern01:{potential}@n0', f'dBdx({delta_pot})*{delta_pot}:{potential}@n0'), (f'Bern01:{potential}@n1', f'-Bern01:{potential}@n0') ]: ds.edge_model(device=device, region=region, name=name, equation=eq) # electron continuity e_current = f"{q}*mobility_n*EdgeInverseLength*k*T/{q}*kahan3({electron_density}@n1*Bern01, {electron_density}@n1*{delta_pot}, -{electron_density}@n0*Bern01)" create_edge_and_derivatives(device, region, e_current_name, e_current, [electron_density, hole_density, potential]) ds.equation(device=device, region=region, name='ECE', variable_name=electron_density, time_node_model=electron_density, edge_model=e_current_name, node_model=e_gen) # hole continuity h_current = f"-{q}*mobility_p*EdgeInverseLength*k*T/{q}*kahan3({hole_density}@n1*Bern01, -{hole_density}@n0*vdiff, -{hole_density}@n0*Bern01)" create_edge_and_derivatives(device, region, h_current_name, h_current, [electron_density, hole_density, potential]) ds.equation(device=device, region=region, name='HCE', variable_name=hole_density, time_node_model=hole_density, edge_model=h_current_name, node_model=h_gen)