def setup_poisson(device, region, variable_update='log_damp', **kwargs): """ Sets up poisson equation solution Requires: setup physical constants and setup poisson parameters :param kwargs: :return: """ logger.debug("Setting up Poisson Equation") # These are variables that are to be solved during the simulation # for sol_variable in [hole_density, electron_density, potential, qfn, qfp]: for sol_variable in [potential, electron_density, hole_density]: create_solution(device, region, sol_variable) total_charge_eq = f'kahan3({hole_density},-{electron_density},{p_doping})' #f'{hole_density}-{electron_density}+{p_doping}-{n_doping}' #f'-{q}*kahan4({hole_density}, -{electron_density}, {p_doping}, -{n_doping})' int_chg_eq = '1e11'# f'({Nc}*{Nv})^(1/2)*exp(-{bandgap}/(2*k*T))' # Order matters! for name, eq in zip([intrinsic_charge, total_charge, ], [int_chg_eq, total_charge_eq,]): create_node_and_derivatives(device, region, name, eq, [potential, electron_density, hole_density]) ds.edge_from_node_model(device=device, region=region,node_model=electron_density) ds.edge_from_node_model(device=device, region=region, node_model=hole_density) e_field_eq = f"({potential}@n0-{potential}@n1)*EdgeInverseLength" d_field_eq = f'{e_field}*{permittivity}*{eps_0}' for name, eq in zip([e_field, d_field],[e_field_eq, d_field_eq]): create_edge_and_derivatives(device, region, name, eq, [potential]) # Lastly, setup the equation to solve for 'potential' # TODO check initial values ds.set_node_values(device=device, region=region, name=electron_density, init_from=intrinsic_charge) ds.set_node_values(device=device, region=region, name=hole_density, init_from=intrinsic_charge) ds.equation(device=device, region=region, name='PoissonEq', variable_name=potential, node_model=total_charge, edge_model=d_field, variable_update=variable_update)
def CreateSiliconPotentialOnly(device, region): """ Creates the physical models for a Silicon region """ if not InNodeModelList(device, region, "Potential"): log("Creating Node Solution Potential") CreateSolution(device, region, "Potential") # require NetDoping intrinsics = (("IntrinsicElectrons", "n_i*exp(Potential/V_t)"), ("IntrinsicHoles", "n_i^2/IntrinsicElectrons"), ("IntrinsicCharge", "kahan3(IntrinsicHoles, -IntrinsicElectrons, NetDoping)"), ("PotentialIntrinsicCharge", "-ElectronCharge * IntrinsicCharge")) for name, eq in intrinsics: CreateNodeModel(device, region, name, eq) CreateNodeModelDerivative(device, region, name, eq, "Potential") # TODO: Edge Average Model electrics = (("ElectricField", "(Potential@n0-Potential@n1)*EdgeInverseLength"), ("PotentialEdgeFlux", "Permittivity * ElectricField")) for name, eq in electrics: CreateEdgeModel(device, region, name, eq) CreateEdgeModelDerivatives(device, region, name, eq, "Potential") equation(device=device, region=region, name="PotentialEquation", variable_name="Potential", node_model="PotentialIntrinsicCharge", edge_model="PotentialEdgeFlux", variable_update="log_damp")
def setup_poisson(device, region, variable_update='log_damp', **kwargs): """ Sets up poisson equation solution Requires: setup physical constants and setup poisson parameters :param kwargs: :return: """ # These are variables that are to be solved during the simulation # for sol_variable in [hole_density, electron_density, potential, qfn, qfp]: for sol_variable in [potential, qfn, qfp]: create_solution(device, region, sol_variable) hole_density_eq = f'n_i*exp( ({qfp}-{potential})*k*T/{q})' electron_density_eq = f'n_i*exp(({potential}-{qfn})*k*T/{q})' # ds.equation(device, region, name='HoleQFL', variable_name=qfn, # node_model=hole_density, edge_model=) total_charge_eq = f'-{q}*kahan4({hole_density}, -{electron_density}, {p_doping}, -{n_doping})' for name, eq in zip( [electron_density, hole_density, total_charge], [electron_density_eq, hole_density_eq, total_charge_eq]): create_node_and_derivatives(device, region, name, eq, [potential]) # # These are variables that exist per each node and may be spatially dependant # intrinsic_charge_eq = f'-{q}*kahan4({intrinsic_holes}, -{intrinsic_electrons}, {p_doping}, -{n_doping})' # create_node_and_derivatives(device, region, total_charge, total_charge_eq, [hole_density, electron_density, potential]) # # intrinsic_electrons_eq = f'n_i*exp(({potential}-{qfn})/(k*T/q))' # TODO this is not intrinsic, thsi is any! # # intrinsic_holes_eq = f'n_i*exp(({qfp}-{potential})/(k*T/q))' # # Homojunction # intrinsic_holes_eq = f'{10E10}^2/{intrinsic_electrons}'# f'{intrinsic_charge}^2/{intrinsic_electrons}' # intrinsic_electrons_eq = f"{Vt}*exp({potential}*{Vt})"# f"{intrinsic_charge}*exp({potential}*q/(k*T))" # # for name, eq in zip([intrinsic_electrons, intrinsic_holes, intrinsic_charge], # [intrinsic_electrons_eq, intrinsic_holes_eq, intrinsic_charge_eq]): # create_node_and_derivatives(device, region, name, eq, [potential]) # for name, eq in zip([qfn, qfp], []) # These are the variables for each edge, and connect nodes together e_field_eq = f"({potential}@n0-{potential}@n1)*EdgeInverseLength" d_field_eq = f'{e_field}*{permittivity}*{eps_0}' for name, eq in zip([e_field, d_field], [e_field_eq, d_field_eq]): create_edge_and_derivatives(device, region, name, eq, [potential]) # Lastly, setup the equation to solve for 'potential' # ds.set_node_values(device=device, region=region, name=qfp, init_from=intrinsic_holes) # ds.set_node_values(device=device, region=region, name=qfn, init_from=intrinsic_electrons) ds.equation(device=device, region=region, name='PoissonEq', variable_name=potential, node_model=total_charge, edge_model=d_field, variable_update=variable_update)
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 CreateHCE(device, region, mu_p): CreateHoleCurrent(device, region, mu_p) PCharge = "-ElectronCharge * Holes" CreateNodeModel(device, region, "PCharge", PCharge) CreateNodeModelDerivative(device, region, "PCharge", PCharge, "Holes") equation(device=device, region=region, name="HoleContinuityEquation", variable_name="Holes", time_node_model="PCharge", edge_model="HoleCurrent", variable_update="positive", node_model="HoleGeneration")
def CreateECE(device, region, mu_n): CreateElectronCurrent(device, region, mu_n) NCharge = "ElectronCharge * Electrons" CreateNodeModel(device, region, "NCharge", NCharge) CreateNodeModelDerivative(device, region, "NCharge", NCharge, "Electrons") equation(device=device, region=region, name="ElectronContinuityEquation", variable_name="Electrons", time_node_model="NCharge", edge_model="ElectronCurrent", variable_update="positive", node_model="ElectronGeneration")
def CreatePE(device, region): pne = "-ElectronCharge*kahan3(Holes, -Electrons, NetDoping)" CreateNodeModel(device, region, "PotentialNodeCharge", pne) CreateNodeModelDerivative(device, region, "PotentialNodeCharge", pne, "Electrons") CreateNodeModelDerivative(device, region, "PotentialNodeCharge", pne, "Holes") equation(device=device, region=region, name="PotentialEquation", variable_name="Potential", node_model="PotentialNodeCharge", edge_model="PotentialEdgeFlux", time_node_model="", variable_update="log_damp")
def create_hole_continuity_eq(device, region, mu_p): """ Creates the hole continuity equation :param device: :param region: :param mu_p: mobility of holes :return: """ create_hole_current(device, region, mu_p) PCharge = "-ElectronCharge * Holes" create_node_model(device, region, "PCharge", PCharge) CreateNodeModelDerivative(device, region, "PCharge", PCharge, "Holes") equation(device=device, region=region, name="HoleContinuityEquation", variable_name="Holes", time_node_model="PCharge", edge_model="HoleCurrent", variable_update="positive", node_model="HoleGeneration")
def create_electron_continuity_eq(device, region, mu_n): """ Creates the electron continuity equation to be solved :param device: :param region: :param mu_n: mobility of electrons :return: """ create_electron_current(device, region, mu_n) NCharge = "ElectronCharge * Electrons" create_node_model(device, region, "NCharge", NCharge) CreateNodeModelDerivative(device, region, "NCharge", NCharge, "Electrons") equation(device=device, region=region, name="ElectronContinuityEquation", variable_name="Electrons", time_node_model="NCharge", edge_model="ElectronCurrent", variable_update="positive", node_model="ElectronGeneration")
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)