Surface recombination

Hello Juan and forum,

I am trying to implement surface SRH recombination to represent interface defects and traps.

I’ve found two ways:

(1) In some textbooks/manuals, it shows up identical to bulk SRH, except occuring exactly at the interface, and replacing the lifetimes by a “surface velocity” term.

(2) In 3.2.8 Shockley-Read-Hall Generation/Recombination, it instead appears as a “distance to the interface”-dependent term in the bulk SRH lifetime, which should reduce to a similar effect.

What would be the best way to add this to DEVSIM?

In thinking about how to do (1):

  • My first thought was making a continuous interface, but somehow modifying the continuity equation to add the sink (similar to ohmic contact definition). But that require both regions across the interface to have the solutions (here, holes/electrons), so would require my insulators to have electron/hole solutions that I guess don’t have an associated equation? Similarly, a fluxterm unterface would not act as a pure sink.
  • I tried creating a new region only at a gmsh interface and defining a new node model, but that seems to remove the ability to use the nodes as an interface.
  • Is there a way to add a node model to a specific node instead of going through regions/interfaces? Then maybe we could get the node indices at the interface, and add an extra node model there?

For (2):

Thank you for any help!


Please see this command:


which may be useful to giving you the distance to a specific interface. The distance is to the center of the edge, but you can your own node model which transfers the edge model to a node.

one way to do this would be to use the this command on the built in ‘node_index’ node model:

which would give you node_index@0 and node_index@n1 on each edge.

Another useful model is “SurfaceArea” which is specific to interfaces.
Node models defined on each region of a device.

You can also get interface node indexes from an interface using:

There are probably many more options available. For an example, there is a command to create a contact at the same nodes as an existing contact.

Thank you Juan. I chose to go with the formulation in Selberherr:

I attempt to approximate the delta function through some function of iname_distance (thank you for the heads up):

def create_generation_recombination(device, region, generation_recombination_physics, variables):
    Bulk Shockley Read hall recombination model in terms of generation.

    Differentiates between bulk and surfaces. There may be multiple different surfaces.

    if "traps_SRH" in generation_recombination_physics:

        # Create {interface}_distance edge models, which evaluates to distance from the interface within the region
        current_interfaces = [interface for interface in ds.get_interface_list(device=device) if region in interface]
        for interface in current_interfaces:
            ds.interface_normal_model(device=device, region=region, interface=interface)
        # Create node_index@0 and node_index@n1 on each edge
        ds.edge_from_node_model(device=device, region=region, node_model="node_index")

        # Modify SRH to have different lifetimes on surface nodes and in bulk:
        USRH_bulk = "(Electrons*Holes - NIE^2)/(taup*(Electrons + n1) + taun*(Holes + p1))"
        USRH_surface = ""
        for interface in current_interfaces:
            # If any node is at interface, make USRH_bulk = 0
            USRH_bulk += f" * (ifelse({interface}_distance > 0.0, 1, 0))"
            # and evaluate its specific surface term
            USRH_surface += f"+ (Electrons*Holes - NIE^2)/((Electrons + n1)/sp + (Holes + p1)/sn) * ifelse({interface}_distance > 0.0, 0, 1)"

        # Usual treatment
        USRH = USRH_bulk + " + " + USRH_surface
        Gn = "-q * USRH"
        Gp = "+q * USRH"

        print(create_node_model(device, region, "USRH", USRH))
        for i in ("Electrons", "Holes", "T"):
            if i in variables:
                create_node_model_derivative(device, region, "USRH", USRH, i)

        print(ds.get_edge_model_list(device=device, region=region))
        print(ds.get_node_model_list(device=device, region=region))


        Gn = 0
        Gp = 0

    create_node_model(device, region, "ElectronGeneration", Gn)
    create_node_model(device, region, "HoleGeneration", Gp)
    for i in ("Electrons", "Holes", "T"):
        if i in variables:
            create_node_model_derivative(device, region, "ElectronGeneration", Gn, i)
            create_node_model_derivative(device, region, "HoleGeneration", Gp, i)

The prints show that the models are present:

print(ds.get_edge_model_list(device=device, region=region)) (e.g. we now have 'core___box_distance'

('DField', 'DField:Potential@n0', 'DField:Potential@n1', 'EField', 'EField:Potential@n0', 'EField:Potential@n1', 'EdgeCouple', 'EdgeInverseLength', 'EdgeLength', 'EdgeNodeVolume', 'Electrons@n0', 'Electrons@n1', 'Epar_n', 'Epar_n:Potential@n0', 'Epar_n:Potential@n1', 'Epar_p', 'Epar_p:Potential@n0', 'Epar_p:Potential@n1', 'Holes@n0', 'Holes@n1', 'Jn', 'Jn:Electrons@n0', 'Jn:Electrons@n1', 'Jn:Holes@n0', 'Jn:Holes@n1', 'Jn:Potential@n0', 'Jn:Potential@n1', 'Jn_arora_lf', 'Jn_arora_lf:Electrons@n0', 'Jn_arora_lf:Electrons@n1', 'Jn_arora_lf:Holes@n0', 'Jn_arora_lf:Holes@n1', 'Jn_arora_lf:Potential@n0', 'Jn_arora_lf:Potential@n1', 'Jp', 'Jp:Electrons@n0', 'Jp:Electrons@n1', 'Jp:Holes@n0', 'Jp:Holes@n1', 'Jp:Potential@n0', 'Jp:Potential@n1', 'Jp_arora_lf', 'Jp_arora_lf:Electrons@n0', 'Jp_arora_lf:Electrons@n1', 'Jp_arora_lf:Holes@n0', 'Jp_arora_lf:Holes@n1', 'Jp_arora_lf:Potential@n0', 'Jp_arora_lf:Potential@n1', 'Potential@n0', 'Potential@n1', 'V_t_edge', 'beta_n', 'beta_p', 'core___box_distance', 'core___box_normal_x', 'core___box_normal_y', 'core___clad_distance', 'core___clad_normal_x', 'core___clad_normal_y', 'core___slab_distance', 'core___slab_normal_x', 'core___slab_normal_y', 'edge_index', 'mu_arora_n_lf', 'mu_arora_p_lf', 'mu_n', 'mu_n:Electrons@n0', 'mu_n:Electrons@n1', 'mu_n:Holes@n0', 'mu_n:Holes@n1', 'mu_n:Potential@n0', 'mu_n:Potential@n1', 'mu_p', 'mu_p:Electrons@n0', 'mu_p:Electrons@n1', 'mu_p:Holes@n0', 'mu_p:Holes@n1', 'mu_p:Potential@n0', 'mu_p:Potential@n1', 'node_index@n0', 'node_index@n1', 'unitx', 'unity', 'vsat_n', 'vsat_p')

print(ds.get_node_model_list(device=device, region=region))

('Acceptors', 'AtContactNode', 'ContactSurfaceArea', 'DEG', 'Donors', 'EC', 'EC:Potential', 'EFN', 'EFN:Electrons', 'EFN:Potential', 'EFP', 'EFP:Holes', 'EFP:Potential', 'EG', 'EI', 'EI:Potential', 'EV', 'EV:Potential', 'Electrons', 'Holes', 'IntrinsicCharge', 'IntrinsicCharge:Potential', 'IntrinsicElectrons', 'IntrinsicElectrons:Potential', 'IntrinsicHoles', 'IntrinsicHoles:Potential', 'NC', 'NIE', 'NSurfaceNormal_x', 'NSurfaceNormal_y', 'NTOT', 'NV', 'NetDoping', 'NodeVolume', 'Potential', 'PotentialIntrinsicCharge', 'PotentialIntrinsicCharge:Potential', 'PotentialNodeCharge', 'PotentialNodeCharge:Electrons', 'PotentialNodeCharge:Holes', 'SurfaceArea', 'Tn', 'USRH', 'USRH:Electrons', 'USRH:Holes', 'V_t', 'coordinate_index', 'mu_arora_n_node', 'mu_arora_p_node', 'node_index', 'x', 'y', 'z')

This is evaluating to invalid right now, presumably because USRH is a node model, but the inname_distances are edge models

How would I use node_index@0 and node_index@n1 to transfer the edge quantities to the nodes?



Since you do not appear to be using the actual distance.

The SurfaceArea node model is the surface area for nodes at any interface.

If you are concerned about a specific interface, you could do something like this:

# this is my node model with a 1 at each node of the surface I care about.
node_solution(name='atinterface' . . . )
# get all surface elements for the interface in one of its regions
elist = get_element_node_list(interface=interface, region=region . . . )
nset = set([])
for e in elist:
for n in nset:
   set_node_value(name='at_interface', index=n, value=1.0)

You then have a node model indicating nodes belonging to a specific interface.

Alternatively you could iterate through the interface distance edge model, and use that to index into node_index@n0 and node_index@n1. You can then set 1.0 using these index on an atinterface nodel.

The indexing seems to work! I should be able to multiply my models by the indexing functions to set different recombination rates at specific interfaces vs bulk

Some silicon waveguide cross-section:

Tagging the volume of my waveguide “clad” region:

And interfaces:

Thank you!

Thanks for the update. That looks really cool.