Пример #1
0
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)
Пример #2
0
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})')
Пример #3
0
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}))")
Пример #4
0
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)
Пример #5
0
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)
Пример #6
0
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))
Пример #7
0
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))
Пример #8
0
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])
Пример #9
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)
Пример #10
0
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)