simudo.physics package

Submodules

simudo.physics.electro_optical_process module

class simudo.physics.electro_optical_process.AbsorptionAndRadiativeRecombinationEOPMixin[source]

Bases: object

enable_radiative_recombination = True
get_generation_user(band)[source]

Call get_generation_optical() and get_radiative_recombination() and add together their results accordingly.

Only include the radiative recombination process if enable_radiative_recombination is true.

class simudo.physics.electro_optical_process.BeerLambert(**kwargs)[source]

Bases: AbsorptionAndRadiativeRecombinationEOPMixin, TwoBandEOPMixin, ElectroOpticalProcess

Beer-Lambert absorption with energy-dependent, per-region alpha(E).

Alpha is registered via add_callable_rule() with key <name>/alpha_function, where <name> is the instance name of this EOP. Multiple BeerLambert instances can share the same optical fields (overlapping absorption is physically correct).

Radiative recombination is disabled in v1 (enable_radiative_recombination = False).

Generation is pre-accumulated into a dolfin.Function before each Newton iteration so that Newton assembly sees a single function rather than an N-term UFL sum over optical fields, where N is the number of optical fields.

Example usage:

spatial.add_callable_rule(
    'opt_cv/alpha_function', R.absorber,
    make_alpha_from_table(energies_eV, alphas))
pdd.easy_add_electro_optical_process(
    BeerLambert, dst_band=CB, src_band=VB, name='opt_cv')
dst_band_is_trap = False
enable_radiative_recombination = True
get_alpha(photon_energy)[source]

This method is called to get Beer-Lambert absorption coefficient alpha at a particular photon energy. Must return a UFL quantity on the PDD mesh, or None.

User class must implement this.

get_generation_optical(band)[source]

Compute carrier generation due to optical absorption, using absorption coefficient given by get_alpha_by_optical_field() and quantum yield given by get_quantum_yield().

get_radiative_recombination()[source]
get_refractive_index()[source]

Return the spatially-varying refractive index from the spatial registry.

Raises a clear error (instead of the low-level “no value rules for key” AssertionError) if refractive_index has not been set anywhere, since the Shockley-van Roosbroeck radiative-recombination integral cannot run without it.

gui_hint = "Beer-Lambert absorption with tabulated or analytic alpha(E). Register alpha per region with spatial.add_callable_rule('<name>/alpha_function', region, func). Overlapping absorption across multiple instances is supported."
name = 'beer_lambert'
pre_first_iteration_hook(solver)
pre_iteration_hook(solver)
required_spatial_callables: dict = {'alpha_function': {'arg_units': 'eV', 'description': 'optical absorption coefficient α(E)', 'return_units': '1/cm'}}
required_spatial_params: dict = {}
src_band_is_trap = False
svr_E_max = None
svr_E_min = None
svr_integration_bins = 100
svr_solid_angle = 12.566370614359172
svr_static_alpha = True
class simudo.physics.electro_optical_process.DarkEOPMixin[source]

Bases: object

get_alpha(photon_energy)[source]
class simudo.physics.electro_optical_process.ElectroOpticalProcess(**kwargs)[source]

Bases: SetattrInitMixin

This class exists to represent both electro-optical process and purely electronic (dark) processes, such as nonradiative recombination.

Variables:
  • pdd (PoissonDriftDiffusion) – Instance of PoissonDriftDiffusion.

  • optical (Optical) – Instance of Optical.

  • pre_iteration_hook (callable) – Implement this method in order to run code before each PDD Newton iteration. Useful for processes that need procedural code (cannot be expressed as UFL form).

  • pre_first_iteration_hook (callable) – This method is called before the first PDD Newton iteration.

  • post_iteration_hook (callable) – This method is called after each PDD Newton iteration.

check_band_types()[source]

Warn if a band is not of the type this process is derived for.

Bands whose is_trap_band is None are left alone: the class never declared one, so there is nothing to check against.

dst_band_is_trap = None
get_alpha(photon_energy)[source]

This method is called to get Beer-Lambert absorption coefficient alpha at a particular photon energy. Must return a UFL quantity on the PDD mesh, or None.

User class must implement this.

get_alpha_by_optical_field(optical_field)[source]

Exists as a separate method to make anisotropy possible.

By default just calls self.get_alpha.

get_band_generation_sign(band)[source]

As the generation process intensifies (e.g., light intensity increases), does band lose or gain more carriers?

The default implementation always returns None. User classes are expected to override this method.

Note: If you are implementing a two-band process, you almost certainly want to inherit from TwoBandEOPMixin.

Returns:

sign – If the band participates in the process, returns +1 or -1 if it gains or loses carriers, respectively. Otherwise, return None.

Return type:

object

get_generation(band)[source]
get_generation_optical(band)[source]

Compute carrier generation due to optical absorption, using absorption coefficient given by get_alpha_by_optical_field() and quantum yield given by get_quantum_yield().

get_generation_user(band)[source]

This method is called to get the generation contribution to band band. Must return a UFL quantity on the PDD mesh.

User class may override this. By default, this method just calls get_generation_optical().

Sign convention: The returned value is positive or negative if the band gains or loses carriers respectively.

This method will be called for every band in the system. You are strongly encouraged to use get_band_generation_sign() to determine whether the process causes a gain or a loss of carriers in band band, and whether the band participates in the process at all.

get_optical_generation(photon_energy)[source]

Override for luminescent coupling. By default returns zero.

get_optical_generation_by_optical_field(optical_field)[source]

Exists as a separate method to make anisotropy possible.

By default just calls self.get_optical_generation, and multiplies by solid_angle.

get_photon_flux_on_pdd_mesh(optical_field)[source]

Returns optical photon flux, adapted onto PDD mesh. Use this accessor instead of reaching inside optical.OpticalField yourself.

Parameters:

optical_field (optical.OpticalField) – Optical field whose photon flux (clipped to be nonnegative) to get.

get_quantum_yield(photon_energy)[source]

Default quantum_yield=1.

get_quantum_yield_by_optical_field(optical_field)[source]

Exists as a separate method to make anisotropy possible.

By default just calls self.get_quantum_yield.

logger
property mesh_util
post_iteration_hook = None
pre_first_iteration_hook = None
pre_iteration_hook = None
required_spatial_callables: dict = {}
required_spatial_params: dict = {}
src_band_is_trap = None
property unit_registry
class simudo.physics.electro_optical_process.IBBeerLambert(**kwargs)[source]

Bases: TrapEOPMixin, BeerLambert

Beer-Lambert absorption / radiative recombination for a transition involving an intermediate band (IB), where alpha is state-dependent:

α(E, x) = σ(E, x) · u_active(x) = σ(E, x) · N_IB(x) · f_IB(x)

where σ(E, x) is the optical cross-section (cm²), registered via add_callable_rule() with key <name>/sigma_function, and f_IB = u_IB / N_IB.

Which states are active depends on the direction of the transition (live UFL):

  • u_active = IB.u when IB and the regular band carry

    the same carrier sign (eg, IB and CB both electron-like): the occupied IB states are the ones a photon can excite out of

  • u_active = IB.number_of_states - IB.u otherwise (eg, IB electron-like and

    VB hole-like): the empty IB states are the ones that can receive a carrier

Both generation and radiative recombination are Picard-frozen: σΦ and u_active are each projected into a dolfin.Function and treated as constant by UFL, so the Jacobian sees no contribution from these terms. That is a fixed-point rather than a Newton linearisation — convergence is linear rather than quadratic — but it is robust across the full intensity range, whereas a live-UFL formulation gives a negative diagonal entry ∂G/∂(u_IB) = −σΦ that destabilises Newton once σΦ grows large enough to overwhelm the stabilising terms.

trap_band identifies the intermediate band; dst_band / src_band follow the usual BeerLambert convention (higher-energy band = dst_band).

Example usage:

spatial.add_callable_rule(
    'opt_ib_cv/sigma_function', R.absorber,
    make_sigma_top_hat(0.7*U('eV'), 1.1*U('eV'), 1e-15*U('cm^2')))
pdd.easy_add_electro_optical_process(
    IBBeerLambert,
    dst_band=CB, src_band=IB, trap_band=IB,
    name='opt_ib_cv')
enable_radiative_recombination = True
get_alpha(photon_energy)[source]

This method is called to get Beer-Lambert absorption coefficient alpha at a particular photon energy. Must return a UFL quantity on the PDD mesh, or None.

User class must implement this.

get_generation_optical(band)[source]

Picard-frozen generation: _u_active_function · _SigmaPhi_function.

Both _SigmaPhi = Σ_k σ(E_k)·Φ_k and u_active are projected once before the first Newton iteration and held frozen during the solve. The Jacobian therefore sees zero contribution from this term (Picard / fixed-point linearisation), which prevents the negative diagonal entry ∂G/∂(u_IB) = -_SigmaPhi from destabilising Newton when _SigmaPhi grows large enough to overwhelm the NR/relaxation stabilising contributions. Convergence is slower (linear rather than quadratic) but robust across the full intensity range.

get_radiative_recombination()[source]

SVR recombination, Picard-frozen: _u_active_function · _B0norm · expm1(ΔqFL/kT).

u_active is frozen (Picard) to remove the destabilising ∂u_active/∂u_IB Jacobian contribution. UFL still differentiates through expm1(x), giving the full QFL-split Jacobian for the radiative term.

get_sigma(photon_energy)[source]

Return σ(E) from the spatial callable registry, or None.

gui_hint = "Beer-Lambert absorption / radiative recombination for a transition involving a sharp IB, with a state-dependent alpha = sigma(E) * N_IB * f_IB that tracks the band's occupancy. Lower E band and Higher E band are the two bands of the transition, one of which is the IB; Trap band = the IB. Requires an optical cross-section '<name>/sigma_function' per region: a top hat set below, a tabulated sigma(E) in the project file, or a callable provided by the material."
name = 'beer_lambert_ib'
pre_first_iteration_hook(solver)

Project Σ σ·Φ and Σ σ·φ_bb into frozen Functions before each Newton step.

pre_iteration_hook = None
required_spatial_callables: dict = {'sigma_function': {'arg_units': 'eV', 'description': 'IB optical cross-section σ(E)', 'return_units': 'cm^2'}}
required_spatial_params: dict = {}
svr_static_alpha = False
class simudo.physics.electro_optical_process.NonOverlappingTopHatBeerLambert(**kwargs)[source]

Bases: AbsorptionAndRadiativeRecombinationEOPMixin, RadiativeEOPMixin, TwoBandEOPMixin, ElectroOpticalProcess

Absorption or radiative recombination between src_band and dst_band. For absorption, src_band=VB and dst_band=CB, and vice versa for recombination.

Includes radiative recombination by default.

dst_band_is_trap = False
get_alpha(photon_energy)[source]

Return constant alpha, clipped by absorption bounds.

get_constant_alpha()[source]
get_radiative_recombination()[source]
gui_hint = 'Photon absorption / radiative recombination between two standard bands (e.g. VB CB). Lower E band = valence-like band (e.g. VB); Higher E band = conduction-like band (e.g. CB). Requires spatial parameter ‘alpha’ per region.'
name = 'beer_lambert'
required_spatial_params: dict = {'alpha': '1/cm'}
src_band_is_trap = False
class simudo.physics.electro_optical_process.NonOverlappingTopHatBeerLambertIB(**kwargs)[source]

Bases: AbsorptionAndRadiativeRecombinationEOPMixin, RadiativeEOPMixin, TrapEOPMixin, TwoBandEOPMixin, ElectroOpticalProcess

Absorption between intermediate band and regular band.

Includes radiative recombination by default.

Based on [Strandberg2011].

get_alpha(photon_energy)[source]

This method is called to get Beer-Lambert absorption coefficient alpha at a particular photon energy. Must return a UFL quantity on the PDD mesh, or None.

User class must implement this.

get_constant_sigma_opt()[source]
get_radiative_pseudo_capture_coefficient()[source]

See eq:rad-u-form in doc/ib.lyx.

get_radiative_recombination()[source]
get_sigma_opt(photon_energy)[source]

Return constant sigma_opt, clipped by absorption bounds.

gui_hint = "Photon absorption / radiative recombination between a standard band and a sharp IB. Lower E band and Higher E band are the regular bands on each side (e.g. VB and CB); Trap band = the IB. Requires spatial parameter 'sigma_opt' per region."
name = 'beer_lambert_IB'
required_spatial_params: dict = {'sigma_opt': 'cm^2'}
class simudo.physics.electro_optical_process.NonRadiativeTrap(**kwargs)[source]

Bases: DarkEOPMixin, TrapEOPMixin, TwoBandEOPMixin, ElectroOpticalProcess

Shockley Read trapping process. Typically, trap_band = IB. If you do not have an explicit Band object for the trap concentration, use SRHRecombination instead.

See doc/ib.lyx.

get_generation_user(band)[source]

This method is called to get the generation contribution to band band. Must return a UFL quantity on the PDD mesh.

User class may override this. By default, this method just calls get_generation_optical().

Sign convention: The returned value is positive or negative if the band gains or loses carriers respectively.

This method will be called for every band in the system. You are strongly encouraged to use get_band_generation_sign() to determine whether the process causes a gain or a loss of carriers in band band, and whether the band participates in the process at all.

gui_hint = "Non-radiative trapping between a standard band (SB) and a sharp IB. Use when you have an explicit Band object for the IB (use SRHRecombination otherwise). Trap band = IB. One of lower or higher E band must also be the IB. Requires spatial parameter 'sigma_th' per region; thermal velocity comes from the band (CB/vth, VB/vth), usually from the material. To implement a full recombination, you need two NonRadiativeTrap processes (eg, for CB-IB and IB-VB) "
name = 'nonradiative'
required_spatial_params: dict = {'sigma_th': 'cm^2'}
class simudo.physics.electro_optical_process.RadiativeEOPMixin[source]

Bases: object

Calculates properties involved in radiative processes, such as trapping or recombination.

Only works in non-degenerate condition exp((E_photon_min - mu_fi)/kT) >> 1. See [Strandberg2011], page 3, under Eq. 6.

get_absorption_bounds()[source]
get_strandberg_I()[source]
inside_absorption_bounds_conditional(E, value)[source]
class simudo.physics.electro_optical_process.SRHRecombination(**kwargs)[source]

Bases: DarkEOPMixin, TrapEnergyLevelMixin, TwoBandEOPMixin, ElectroOpticalProcess

SRH recombination. The destination band is the one losing carriers through recombination.

Typically, dst_band=CB and src_band=VB.

Uses TrapEnergyLevelMixin (not TrapEOPMixin) — SRH has no explicit trap band object; the trap energy level and lifetime tau come from spatial parameters. The GUI therefore does not show a “Trap band” selector for this class.

dst_band_is_trap = False
get_degeneracy_factor(band)[source]

Degeneracy factor gamma = u / u_nondegenerate for band.

Returns 1 for a nondegenerate band.

Guarded against underflow: the Boltzmann density underflows to exactly zero in double precision once (E_F - E_C)/kT falls below roughly -745, which is reachable for a wide-gap band under reverse bias. Gamma tends to 1 in that limit – Fermi-Dirac and Boltzmann statistics coincide when carriers are sparse – so fall back to 1 rather than dividing by zero.

get_generation_user(band)[source]

This method is called to get the generation contribution to band band. Must return a UFL quantity on the PDD mesh.

User class may override this. By default, this method just calls get_generation_optical().

Sign convention: The returned value is positive or negative if the band gains or loses carriers respectively.

This method will be called for every band in the system. You are strongly encouraged to use get_band_generation_sign() to determine whether the process causes a gain or a loss of carriers in band band, and whether the band participates in the process at all.

get_tau(band)[source]
gui_hint = "Shockley-Read-Hall recombination through midgap traps with no explicit trap band. Lower E band = valence-like band (e.g. VB); Higher E band = conduction-like band (e.g. CB). Trap band should be left as (none) SRH uses per-band lifetime parameters instead. Requires spatial parameters '{dst_band}/tau' and '{src_band}/tau' per region."
name = 'SRH'
required_spatial_params: dict = {'energy_level': 'eV', '{dst_band}/tau': 's', '{src_band}/tau': 's'}
src_band_is_trap = False
trap_band = None
class simudo.physics.electro_optical_process.ShockleyReadBand2BandTrap(**kwargs)[source]

Bases: TwoBandEOPMixin, DarkEOPMixin, ElectroOpticalProcess

Shockley-Read-like trapping process between the receiving (R) and sending (D) bands. Similar to NonRadiativeTrap (which is Shockley-Read trapping from a band to trap states with a single energy).

If the signs of both bands are the same, s_D = s_R, then the rate is

U = [1 - exp(s_D (w_D - w_R)/kT)] u_D/tau

w_k is the qfl of band k u_D is the carrier concentration in band D where we approximate that the number of receiving states is independent of w_R Derivation assumes that the receiving band (R) is essentially entirely empty, so only the population of the sending band (D) enters the rate.

The energy-averaged (usually phenomenological) capture time tau must be defined. Convention: only the value of tau in the dst_band will be used. If the instance of this EOP has name SR_B2B and the D band name is DB tau is found from spatial[“SR_B2B/DB/tau”]

If s_D = -s_R, then the rate is

U = [1 - exp(s_D (w_D - w_R)/kT)] u_D u_R <c>

<c> is the average capture rate [volume^-1 time^-1] for the process. If the instance of this EOP has name SR_B2B and the D band name is DB <c> is found from spatial[“SR_B2B/DB/capture_rate”]

(In the previous case, 1/tau = <c> u_pR where u_pR is the concentration of carriers of opposite sign in band R)

Following the standard sign convention, dst_band = D, src_band = R

dst_band_is_trap = False
get_SR_generation_diff_sign()[source]
get_SR_generation_same_sign()[source]
get_generation_user(band)[source]

This method is called to get the generation contribution to band band. Must return a UFL quantity on the PDD mesh.

User class may override this. By default, this method just calls get_generation_optical().

Sign convention: The returned value is positive or negative if the band gains or loses carriers respectively.

This method will be called for every band in the system. You are strongly encouraged to use get_band_generation_sign() to determine whether the process causes a gain or a loss of carriers in band band, and whether the band participates in the process at all.

gui_hint = "Shockley-Read-like interband transfer between two standard bands (no explicit or implicit trap band). Lower E band = lower-energy band Higher E band = higher-energy band Requires '{dst_band}/tau' (for same-sign bands) or '{dst_band}/capture_rate' (for opposite-sign bands)."
name = 'SR_B2B'
required_spatial_params: dict = {'{dst_band}/capture_rate': 'cm^3/s', '{dst_band}/tau': 's'}
src_band_is_trap = False
class simudo.physics.electro_optical_process.ShockleyReadTrap2Trap(**kwargs)[source]

Bases: TwoBandEOPMixin, DarkEOPMixin, ElectroOpticalProcess

Shockley-Read-like trapping process between the receiving (R) and sending (D) bands where both are trap (ie, sharp) bands. Similar to NonRadiativeTrap (which is Shockley-Read trapping from a dispersing band to trap states with a single energy).

Regardless of the signs of the band, the net transfer rate from D to R is

U = [1 - exp( -(w_D - w_R)/kT)] f_D (1-f_R) n_R N_D c

where

c is the capture coefficient [vol/time] for the trapping process N_D is the number of states [1/vol] in the D band N_R is the number of states [1/vol] in the R band

If the instance of this EOP has name SR_T2T and the D band name is DB c is found from spatial[“SR_T2T/DB/capture_rate”]

Following the standard sign convention, dst_band = D, src_band = R

dst_band_is_trap = True
get_SR_generation()[source]
get_generation_user(band)[source]

This method is called to get the generation contribution to band band. Must return a UFL quantity on the PDD mesh.

User class may override this. By default, this method just calls get_generation_optical().

Sign convention: The returned value is positive or negative if the band gains or loses carriers respectively.

This method will be called for every band in the system. You are strongly encouraged to use get_band_generation_sign() to determine whether the process causes a gain or a loss of carriers in band band, and whether the band participates in the process at all.

gui_hint = "Shockley-Read-like inter-trap transfer between two sharp IB states. Both Lower E band and Higher E band must be sharp (IB-type) bands. Lower E band = source / receiving (R) band; Higher E band = destination / sending (D) band. Requires '{dst_band}/capture_rate' per region."
name = 'SR_T2T'
required_spatial_params: dict = {'{dst_band}/capture_rate': 'cm^3/s'}
src_band_is_trap = True
class simudo.physics.electro_optical_process.StaticGeneration(**kwargs)[source]

Bases: TwoBandEOPMixin, ElectroOpticalProcess

Static generation process. Typically, dst_band=CB and src_band=VB. Uses a generation rate that does not change during the simulation.

Generation rate is defined in pdd.spatial[‘name/generation’], where the default name is static.

get_generation_user(band)[source]

This method is called to get the generation contribution to band band. Must return a UFL quantity on the PDD mesh.

User class may override this. By default, this method just calls get_generation_optical().

Sign convention: The returned value is positive or negative if the band gains or loses carriers respectively.

This method will be called for every band in the system. You are strongly encouraged to use get_band_generation_sign() to determine whether the process causes a gain or a loss of carriers in band band, and whether the band participates in the process at all.

gui_hint = 'Not usable in the GUI. Consider using BeerLambert or IBBeerLambert. If using Python, you can apply arbitrary static absorption profiles using UniformGenerationAdaptiveStepper'
name = 'static'
class simudo.physics.electro_optical_process.TrapEOPMixin[source]

Bases: TrapEnergyLevelMixin

Calculates properties involved in trapping between a trap band and dispersing band, such as u1, tau, etc.

Requires an explicit trap_band Band object.

GUI cross-reference: any class that directly inherits this mixin will have the “Trap band” selector shown for it in code/gui/panels/processes.py. (SRHRecombination inherits TrapEnergyLevelMixin instead — it does not use an explicit trap band and should NOT show the trap band selector.)

Variables:
  • trap_band (Band) – Band doing the trapping.

  • reg_band (Band) – Non-trap band. e.g. CB or VB.

band_spatial_get(name, band)[source]
check_band_types()[source]

These processes need one trap band and one dispersing band, and reg_band identifies the dispersing one by elimination. Anything leaving it undefined raises rather than warns: the process cannot be assembled at all.

classmethod easy_add_two_traps_to_pdd(pdd, name_prefix, top_band, bottom_band, trap_band, **kwargs)[source]
get_capture_coefficient(band=None)[source]

See [Shockley1952a] (3.5).

get_shockley_read_trap_generation(capture_coeff)[source]
get_sigma_th(band=None)[source]
get_tau(band=None)[source]
get_trap_concentration(band=None)[source]
get_trap_process_name(band)[source]
get_v_th(band=None)[source]
reg_band
trap_spatial_get(name, band=None)[source]
class simudo.physics.electro_optical_process.TrapEnergyLevelMixin[source]

Bases: object

Minimal mixin providing trap energy level and SRH u1 statistics.

Shared by TrapEOPMixin (explicit trap band) and SRHRecombination (implicit trap from spatial parameters, no Band object).

get_trap_energy_level():

Returns the trap energy level, either from the explicit trap_band (if set) or from the spatial parameter <name>/energy_level.

get_u1(band):

Quasi-Fermi level equilibrium carrier concentration in band evaluated at the trap energy level. Used in the SRH denominator.

get_trap_energy_level()[source]
get_u1(band)[source]
class simudo.physics.electro_optical_process.TwoBandEOPMixin[source]

Bases: object

Convenience mixin representing a generation process across two bands.

Sign convention: As the generation process intensifies, the destination band gains more carriers. The source band may gain or lose carriers depending on its carrier type.

Variables:
  • src_band (Band) – Source band (which may gain or lose a carrier depending on the sign).

  • dst_band (Band) – Destination band (which always gains a carrier).

get_band_generation_sign(band)[source]

Provides a non-trivial implementation of ElectroOpticalProcess.get_band_generation_sign() in the case of two active bands.

See TwoBandEOPMixin for the sign convention.

simudo.physics.heterojunction module

class simudo.physics.heterojunction.ThermionicHeterojunction(band: Band, boundary: FacetRegion, interface_quadrature_degree: int = 20, HJBC_enhancement: Constant = _Nothing.NOTHING, use_alternative_BC: bool = True, use_nondegen: bool = False)[source]

Bases: object

Thermionic emission heterojunction BC valid with parabolic bands. Treats both nondegenerate (Boltzmann) and degenerate bands

Parameters:
  • band (Band) – Semiconductor band on which the BC is applied. Only MixedQflBand is currently supported.

  • boundary (FacetRegion) – Boundary on which to implement heterojunction boundary condition.

Notes

See V. Palankovski (2004), eq. 3.72 and K. Yang, J. R. East, G. I. Haddad, Solid State Electronics v.36 (3) p.321-330 (1993) K. Horio, H. Yanai, IEEE Trans. Elec. Devices v.37(4) p.1093-1098 (1990)

For degenerate conditions, see Sentaurus sdevice manual

For a conduction band BC, band.spatial must have attribute “CB/vth” in the barrier region. Similarly for other bands.

Unlimited carrier flow from low to barrier region can be resolved, but due to precision issues, cannot resolve Delta_w producing carrier flows from barrier to low region with Delta_w larger than |ln(1e-16)| * kT in double precision

DX 20260813: make use_alternative_BC = True the default. Alternative BC is using the exponential of delta_w, or lambda, instead of delta_w as the HJBC requirement.

HJBC_enhancement: Constant
property N_eff
band: Band
boundary: FacetRegion
property emission_velocity

Richardson emission velocity, \sqrt{k_B T / 2\pi m^*}.

The material files export <band>/vth as the mean speed \langle v\rangle = \sqrt{8k_BT/\pi m^*}, which is the average an SRH capture coefficient needs. Thermionic emission is a flux across the interface, so it needs the one-directional flux average instead – only the carriers moving toward the barrier, weighted by how fast they cross:

v_R = \langle v_z \rangle_{v_z>0}
    = \sqrt{\frac{k_B T}{2\pi m^*}}
    = \frac{\langle v \rangle}{4}

The factor is exactly 4, since \sqrt{8/\pi} \big/ \sqrt{1/2\pi} = \sqrt{16}. Equivalently v_R = A^* T^2 / (q N_C) with A^* = 4\pi q m^* k_B^2 / h^3.

Applying the conversion here, rather than exporting a second velocity from every material file, keeps one vth per band and puts the choice of average next to the physics that fixes it.

interface_quadrature_degree: int
register(debug_output=False)[source]

Apply boundary condition onto the band.

use_alternative_BC: bool
use_nondegen: bool
property vth

simudo.physics.material module

class simudo.physics.material.BadSimpleSiliconMaterial(**kwargs)[source]

Bases: SimpleSiliconMaterial

Just an example of how to use inheritance to selectively modify properties of the superclass

get_dict()[source]

Construct dictionary of material parameters.

Returns:

Dictionary where keys are add_rule() keys, and values are the values (expressions).

Return type:

dict

name = 'badSi'
class simudo.physics.material.Material(**kwargs)[source]

Bases: SetattrInitMixin

Base material class.

Parameters:
  • problem_data (problem_data.ProblemData, optional) – ProblemData instance (used to pull, for example, the temperature variable).

  • unit_registry (pint.UnitRegistry, optional) – Unit registry.

Variables:
  • name (str, optional) – Material name.

  • required_spatial_params (list of str) – Spatial rule keys that must be registered in spatial before get_dict() can evaluate correctly (e.g. ['MoleFractionX'] for alloy materials). The GUI uses this list to prompt the user to set these values per layer. Subclasses should override as needed.

dict
get_dict()[source]

Construct dictionary of material parameters.

Returns:

Dictionary where keys are add_rule() keys, and values are the values (expressions).

Return type:

dict

name = None
property pdd
register(name=None)[source]
required_spatial_params: list = []
property temperature
property unit_registry
class simudo.physics.material.SimpleSiliconMaterial(**kwargs)[source]

Bases: Material

Very simple silicon material. No temperature dependences.

get_dict()[source]

Construct dictionary of material parameters.

Returns:

Dictionary where keys are add_rule() keys, and values are the values (expressions).

Return type:

dict

name = 'Si'

simudo.physics.optical module

class simudo.physics.optical.AbsorptionRangesHelper(**kwargs)[source]

Bases: SetattrInitMixin

Parameters:
  • problem_data – Problem_data object.

  • inf_upper_bound – Upper bound on absorption to use instead of literal ‘inf’. By default 100 eV.

  • energy_unit – By default, eV.

property bands
property energy_unit
get_transition_bounds()[source]

Returns dictionary of (lower, upper) bounds for energy transitions. See get_transition_lower_bounds() for more.

get_transition_lower_bounds()[source]

Returns a dictionary of minimum transition lower bounds. The keys in the dictionary are frozensets {source_band_key, destination_band_key}.

property inf_upper_bound
property unit_registry
class simudo.physics.optical.Optical(**kwargs)[source]

Bases: DefaultProblemDataChildMixin, GetToSaveMixin, SetattrInitMixin

Contains optical part of the problem. Note that unlike PDD, the optical fields are solved independent (self-consistently), so we do not have a big mixed space made up of all the photon flux fields.

Parameters:
  • problem_data – See problem_data.

  • mesh_data (optional) – See mesh_data.

Variables:
  • problem_data (ProblemData) – Parent problem.

  • mesh_data (MeshData) – Mesh data. Taken from problem_data if not specified.

  • fields (NameDict) – Dictionary of OpticalField objects.

  • spatial (spatial.Spatial) – Object managing boundary conditions and spatial quantities.

  • Phi_scale (dolfin.Constant) – This controls the scaling factor applied to light photon flux boundary conditions. This is particularly useful when doing a light ramp-up.

Phi_pddproj_space
Phi_scale
easy_add_field(key, photon_energy, direction, solid_angle=None)[source]
fields
get_to_save()[source]

Get dict of functions needed to restore a solution from file, or to save in a backtracking/adaptive solver.

The default implementation returns an empty NameDict.

Returns:

NameDict of SaveItem.

Return type:

NameDict

initialize_from(other_optical)[source]

Copy solved optical state (Phi_scale and each field’s solved photon flux) from other_optical into this instance.

Unlike initialize_from(), which only carries over PDD (Poisson + band) state, this must be called separately since Optical is a sibling of pdd on ProblemData, not one of its children. Skipping this leaves the copied-in carrier state (fully illuminated, via pdd.initialize_from) inconsistent with a not-yet-solved (effectively zero) photon flux, which reliably fails to converge on the first solve.

spatial
class simudo.physics.optical.OpticalField(**kwargs)[source]

Bases: SetattrInitMixin

A single optical field subproblem. This represents a scalar photon flux at a single wavelength and direction of propagation.

Parameters:
  • optical (Optical) – Parent object.

  • key (str) – Name of optical field. Must be unique.

  • photon_energy (pint.Quantity) – Photon energy. Magnitude must be floating point constant.

  • direction (numpy.ndarray) – Direction of propagation. Must be a normalized vector.

  • solid_angle (float) – Solid angle spanned by this optical field. The sum of all solid angles at a given wavelength must sum up to 4\pi.

Variables:
  • Phi (pint.Quantity wrapping dolfin.Function) – Photon flux field. This function is on the optical mesh.

  • alpha (pint.Quantity wrapping expression) – Absorption/extinction coefficient. This quantity is on the optical mesh (and may be a result of a projection/interpolation from the PDD mesh).

  • g (pint.Quantity wrapping expression) – Additional generation (excluding -alpha*Phi). This quantity is on the optical mesh (and may be a result of a projection/interpolation from the PDD mesh).

  • Phi_pddproj (pint.Quantity of dolfin.Function) – Projected/interpolated version of Phi on the PDD mesh. To update the projection, call update_output().

  • Phi_pddproj_clipped (pint.Quantity wrapping expression) – Clipped version of Phi_pddproj that is always nonnegative.

Notes

TODO: write down radiative transfer equation

Phi_pddproj
property Phi_pddproj_clipped
alpha
property function_subspace_registry
g
get_alpha_pdd_expr()[source]

This represents the extinction coefficient on the PDD mesh, as extracted from the poisson_drift_diffusion.ElectroOpticalProcess instances in electro_optical_processes.

get_g_pdd_expr()[source]

This represents the optical generation on the PDD mesh (excluding the loss through the extinction coefficient), as extracted from the poisson_drift_diffusion.ElectroOpticalProcess instances in electro_optical_processes.

get_subspace_descriptors_for_solution_space()[source]
property mesh_direction

Propagation direction, fitted to the mesh’s geometric dimension.

direction is kept as the user gave it; this is what the weak form uses. See adapt_direction_to_gdim() for which directions are accepted.

property mesh_util
mixed_function_helper
property mixed_function_solution_object
property name
property pdd
property spatial
property unit_registry
update_input()[source]

Update input quantities (e.g. by projection onto optical mesh).

This updates alpha and g.

update_output()[source]

Update output quantities (e.g. by projecting onto PDD mesh).

This updates Phi_pddproj.

property vacuum_wavelength

Computes vacuum wavelength based on photon_energy. Can also be used to initialize that property, with the conversion being done automatically.

class simudo.physics.optical.OpticalFieldMSORTE(**kwargs)[source]

Bases: OpticalFieldMSORTE, InitializeFromByAttributesMixin, WithSubfunctionsMixin, OpticalField, GetToSaveMixin

get_essential_bcs()[source]
get_solution_function()[source]
get_to_save()[source]

Get dict of functions needed to restore a solution from file, or to save in a backtracking/adaptive solver.

The default implementation returns an empty NameDict.

Returns:

NameDict of SaveItem.

Return type:

NameDict

get_weak_form()[source]
initialize_from_attributes = ('Phi',)
simudo.physics.optical.adapt_direction_to_gdim(direction, gdim, name=None)[source]

Fit a propagation direction to a mesh’s geometric dimension.

A direction with more components than the mesh has dimensions is accepted when the extra components are zero, and is truncated. That is what lets a script written for the 2D strip – where (1, 0) and (-1, 0) are the only directions a layered device can use – run unchanged on a true-1D interval mesh, which wants (1,) and (-1,).

Anything else raises ValueError: a transverse component has no meaning on an interval mesh, and a direction with too few components cannot be completed without guessing.

Parameters:
  • direction – Direction of propagation, as given by the user.

  • gdim (int) – Geometric dimension of the mesh the field lives on.

  • name (str, optional) – Field name, used in the error message.

Returns:

Direction with exactly gdim components.

Return type:

numpy.ndarray

simudo.physics.poisson_drift_diffusion module

class simudo.physics.poisson_drift_diffusion.Band(**kwargs)[source]

Bases: Band, WithSubfunctionsMixin, GetToSaveMixin, TypicalFromPDDMixin, SetattrInitMixin

Represents a band where its carriers are at thermal equilibrium

with each other (such that a quasifermi level is well defined).

Variables:
  • name (str) – Name of this band. Must be unique among PoissonDriftDiffusion.bands as it is used as a key in that NameDict. By default simply an alias for key, so you do not need to set it.

  • key (str) – Unique key used to prefix subfunctions.

  • pdd (PoissonDriftDiffusion) – Parent object.

  • u – Carrier density in this band.

  • qfl – Quasi-Fermi level, aka imref, of carriers in this band.

  • j – Current density through this band.

  • sign – Sign of the charge carriers; -1 for electrons, and +1 for holes.

  • mobility – Band mobility. By default taken from .spatial.

  • subdomain – Region name on which the band exists.

property g
get_essential_bcs()[source]
get_weak_form()[source]
is_degenerate_band = False
is_trap_band = None
property name
property phiqfl
phiqfl_to_u(phi_plus_qfl)[source]
qfl_to_u(qfl)[source]
subfunctions_info = ()
u_to_phiqfl(u)[source]
u_to_qfl(u)[source]
class simudo.physics.poisson_drift_diffusion.GetToSaveMixin[source]

Bases: object

get_to_save()[source]

Get dict of functions needed to restore a solution from file, or to save in a backtracking/adaptive solver.

The default implementation returns an empty NameDict.

Returns:

NameDict of SaveItem.

Return type:

NameDict

class simudo.physics.poisson_drift_diffusion.InitializeFromByAttributesMixin[source]

Bases: object

initialize_from(other, function_subspace_registry, **kwargs)[source]
class simudo.physics.poisson_drift_diffusion.IntermediateBand(**kwargs)[source]

Bases: DegeneracyMixin, IntermediateBand, Band

This represents an intermediate band with an energetically sharp density of states, where all number_of_states (N_I) states are concentrated at an energy level energy_level (E_I).

The number of carriers obeys Fermi-Dirac statistics. The defining relationship is therefore

u = N_I f_1\Big(s\cdot\big((w + q\phi) - E_I\big)/kT\Big)

where

Note that state degeneracy is handled through DegeneracyMixin.

Variables:
  • number_of_states – Number of states in the intermediate band. The carrier concentration in this band can never be higher than this number. By default taken from spatial.

  • energy_level – Band energy level. By default inherited from DegeneracyMixin.energy_level.

  • use_constant_mobility (bool) – Use spatial variable "${band_name}/mobility0" as a constant mobility in the IB, neglecting the influence of filling fraction on the mobility. (default: False)

is_degenerate_band = True
is_trap_band = True
use_constant_mobility = False
class simudo.physics.poisson_drift_diffusion.MixedDensityNondegenerateBand(**kwargs)[source]

Bases: InitializeFromByAttributesMixin, MixedDensityBandMixin, NondegenerateBand

class simudo.physics.poisson_drift_diffusion.MixedPoisson(**kwargs)[source]

Bases: InitializeFromByAttributesMixin, MixedMethodPoissonMixin, Poisson

class simudo.physics.poisson_drift_diffusion.MixedQflBandMixin[source]

Bases: MixedQflBand

Mixed method for the drift-diffusion and continuity equations using quasi-fermi level and current density as the dynamical variables.

get_essential_bcs()[source]
get_to_save()[source]
get_weak_form()[source]
initialize_from_attributes = ('qfl', 'j')
mixedqfl_base_w
mixedqfl_debug_fill_from_boundary = True
mixedqfl_debug_fill_thresholds = (0.0, 0.0)
mixedqfl_debug_fill_with_zero_except_bc = False
mixedqfl_debug_quad_degree_g = 8
mixedqfl_debug_quad_degree_super = 20
mixedqfl_debug_use_bchack = False
mixedqfl_drift_diffusion_heterojunction_bc_term
mixedqfl_drift_diffusion_heterojunction_facet_region
property mixedqfl_drift_diffusion_jump_dS
mixedqfl_surface_recombination_term
class simudo.physics.poisson_drift_diffusion.MixedQflIntermediateBand(**kwargs)[source]

Bases: InitializeFromByAttributesMixin, MixedQflBandMixin, IntermediateBand

class simudo.physics.poisson_drift_diffusion.MixedQflNondegenerateBand(**kwargs)[source]

Bases: InitializeFromByAttributesMixin, MixedQflBandMixin, NondegenerateBand

class simudo.physics.poisson_drift_diffusion.NondegenerateBand(**kwargs)[source]

Bases: NondegenerateBand, Band

This represents a nondegenerate band obeying Boltzmann statistics (instead of Fermi-Dirac as it would be for a proper degenerate band), with an effective density of states effective_density_of_states (N_0) at an energy level energy_level (E_0).

The defining relationship is

u = N_0 \exp\Big[s\cdot\big(E_0 - (w + q\phi)\big) / kT\Big]

where

Variables:
  • effective_density_of_states – Band effective density of states. By default taken from spatial.

  • energy_level – Band effective energy level. By default taken from spatial.

property effective_energy_level

In a nondegenerate band, by default the energy_level is assumed to include degeneracy effects, so this just returns that attribute.

is_trap_band = False
class simudo.physics.poisson_drift_diffusion.Poisson(**kwargs)[source]

Bases: Poisson, WithSubfunctionsMixin, GetToSaveMixin, SetattrInitMixin, TypicalFromPDDMixin

Base class for Poisson part of the problem. You should instead look at one of the subclasses, such as MixedPoisson.

Variables:
  • key (str) – Unique key used to prefix subfunctions.

  • pdd (PoissonDriftDiffusion) – Parent object.

  • phi (pint.Quantity) – Electrostatic potential.

  • E (pint.Quantity) – Electric field.

  • rho (pint.Quantity) – Charge density.

  • thermal_equilibrium_phi (pint.Quantity) – Electrostatic potential at thermal equilibrium (i.e. when all qfls are equal to zero).

property at_thermal_equilibrium
get_to_save()[source]

Get dict of functions needed to restore a solution from file, or to save in a backtracking/adaptive solver.

The default implementation returns an empty NameDict.

Returns:

NameDict of SaveItem.

Return type:

NameDict

key = 'poisson'
property permittivity
thermal_equilibrium_phi
class simudo.physics.poisson_drift_diffusion.PoissonDriftDiffusion(**kwargs)[source]

Bases: DefaultProblemDataChildMixin, GetToSaveMixin, SetattrInitMixin

Parameters:
  • problem_data – See problem_data.

  • mesh_data (optional) – See mesh_data.

Variables:
  • problem_data (ProblemData) – Parent problem.

  • mesh_data (MeshData) – Mesh data. Taken from problem_data if not specified.

  • bands (NameDict) – Dictionary of band objects.

  • poisson (Poisson) – Poisson part of the problem.

  • electro_optical_processes (NameDict) – Dictionary of ElectroOpticalProcess instances, including dark generation/recombination mechanisms like SRH.

  • mesh_util (MeshUtil) – PDE utilities, many of which are (needlessly) mesh-specific.

  • mixed_function_helper (MixedFunctionHelper) – Mixed function and related registry.

add_band(cls, kwargs)[source]

Instantiate and add band. You probably want to use easy_add_band() instead.

bands
easy_add_band(name, band_type=None, sign='auto', subdomain=None)[source]

Shortcut for instantiating and adding a band.

Parameters:
  • name (str) – Name of the band.

  • band_type (str or class or None) –

    Can be a string, or a band class (inheriting from Band). If it is a string, it serves as an alias as defined below:

    If None, the type will be deduced from the band name:

    • ”CB”, “VB” -> “nondegenerate”

    • ”IB” -> “intermediate”

  • sign (+1, -1, None, or "auto") –

    The sign of the charge carrier for this band (negative for electrons, positive for holes).

    • If “auto”, the sign will be deduced from the name of the band.

    • If None, the sign keyword argument will not be passed to be band object constructor.

  • subdomain (topology.CellRegion) – Subset of the domain where the band exists.

Returns:

band – Band object.

Return type:

Band

easy_add_electro_optical_process(cls, **kwargs)[source]
easy_add_electrostatic_potential_BC(facet_region, value)[source]
easy_auto_pre_solve(parameters=None)[source]
easy_create_newton_solver()[source]
electro_optical_processes
get_essential_bcs()[source]
get_solution_function()[source]
get_subproblem_children()[source]
get_subspace_descriptors_for_solution_space()[source]

Used by self.mixed_function_helper to build the mixed function space.

get_to_save()[source]

Get dict of functions needed to restore a solution from file, or to save in a backtracking/adaptive solver.

The default implementation returns an empty NameDict.

Returns:

NameDict of SaveItem.

Return type:

NameDict

get_weak_form()[source]
initialize_from(other_pdd)[source]
property kT

Shortcut for k_B T, where k_B is the Boltzmann constant, and T is the temperature (temperature).

mixed_function_helper
property mixed_function_space
poisson
spatial
property temperature

Temperature as taken from spatial.

class simudo.physics.poisson_drift_diffusion.SaveItem(**kwargs)[source]

Bases: SetattrInitMixin

file_save = True
name = None
solver_save = False
value = None

simudo.physics.problem_data module

class simudo.physics.problem_data.ProblemData(**kwargs)[source]

Bases: SetattrInitMixin

Parameters:
  • goal (str) – Represents the goal of this problem. Must be "local charge neutrality", "thermal equilibrium", or "full" (representing full coupled solution).

  • unit_registry (pint.UnitRegistry) – Unit registry to use.

  • mesh_data (MeshData) – Mesh data to use by default in PoissonDriftDiffusion and Optical.

Variables:
function_space_cache
function_subspace_registry
property goal_abbreviated

return abbreviated goal, for logging tag purposes

optical
pdd

simudo.physics.problem_data_child module

class simudo.physics.problem_data_child.DefaultProblemDataChildMixin[source]

Bases: object

property function_space_cache
property function_subspace_registry
property mesh_data
mesh_util
property unit_registry

simudo.physics.steppers module

exception simudo.physics.steppers.NewtonBailoutException[source]

Bases: Exception

class simudo.physics.steppers.NonequilibriumCoupledConstantStepper(**kwargs)[source]

Bases: ConstantStepperMixin, NonequilibriumCoupledStepper

class simudo.physics.steppers.NonequilibriumCoupledStepper(**kwargs)[source]

Bases: AdaptiveStepper

Adaptively solve a coupled poisson-drift diffusion-optical problem.

Extends AdaptiveStepper with PDD-specific logic. The linear solve at each Newton step uses NewtonSolver (or a subclass set via solver_class). The optical problem can be solved self-consistently with the drift-diffusion problem.

Concrete subclasses for common use cases:

Parameters:
  • solution (ProblemData) – Solution on which to operate. This solution object will be progressively re-solved at each of the target parameter values.

  • selfconsistent_optics (bool) – If True, the optical problem will be re-solved at every Newton iteration, allowing for problems where optical absorption and carrier concentrations are interdependent.

  • output_writer (OutputWriter) – If output_writer is a string, it is used as a filename to be passed to the default OutputWriter object. If output_writer is an object inherited from OutputWriter, this object will be used instead. If None, then no output is written. Output will be written after each target_value is solved.

solver_class

alias of NewtonSolver

solver_parameters = None
to_save_objects
user_make_solver(solution)[source]
user_solver(solution, parameter)[source]

This user-defined method must re-solve solution using parameter value parameter.

Must return boolean indicating whether the solver succeeded.

class simudo.physics.steppers.OpticalIntensityAdaptiveStepper(**kwargs)[source]

Bases: NonequilibriumCoupledConstantStepper

Adaptively increase the optical intensity by increasing optical.Optical.Phi_scale.

constants
parameter_name = 'I'
parameter_target_values
parameter_unit
run_optics = True
step_size = 1e-30
update_parameter_failure_factor = 0.2
update_parameter_success_factor = 3
class simudo.physics.steppers.OpticalIntensityLogDampStepper(**kwargs)[source]

Bases: OpticalIntensityAdaptiveStepper

Optical itensity stepper with log damping parameter updates

solver_class

alias of NewtonSolverLogDamping

class simudo.physics.steppers.UniformGenerationAdaptiveStepper(**kwargs)[source]

Bases: NonequilibriumCoupledConstantStepper

Adaptively ramp a prescribed generation rate, without solving optics.

Ramps pdd.uniform_generation_scale – a dimensionless dolfin.Constant – from its initial value up to 1.0. No optical problem is solved (selfconsistent_optics is False), which makes this much cheaper than OpticalIntensityAdaptiveStepper when the generation profile is known in advance and only its magnitude needs to be brought up from zero.

Two things the user must do, neither of which happens automatically:

  1. Set pdd.uniform_generation_scale before constructing the stepper. It does not exist otherwise, and its absence raises AttributeError when constants is first evaluated.

  2. Build the generation spatial rule out of the same dolfin.Constant object. The stepper only assigns to that Constant; if the generation rule does not reference it, ramping the parameter has no effect on the solution.

Example

generation_scale = dolfin.Constant(0)
pdd.uniform_generation_scale = generation_scale * U.dimensionless

# profile1 may be any fixed spatial profile; generation_scale is the
# same Constant object assigned above, and is what gets ramped.
spatial.add_rule("static_cv/generation", R.domain,
                 profile1 * generation_scale)
pdd.easy_add_electro_optical_process(
    StaticGeneration, name='static_cv', dst_band=CB, src_band=VB)

stepper = UniformGenerationAdaptiveStepper(solution=full_problem, ...)
stepper.do_loop()

The spatial key prefix must match the process name: StaticGeneration reads <name>/generation, so name='static_cv' pairs with the rule "static_cv/generation" above.

uniform in the class and attribute names is historical. The profile need not be uniform – it need only be fixed in shape while its magnitude is ramped.

constants
parameter_name = 'G'
parameter_target_values
parameter_unit
selfconsistent_optics = False
step_size = 1e-10
update_parameter_failure_factor = 0.5
update_parameter_success_factor = 3
class simudo.physics.steppers.VoltageStepper(**kwargs)[source]

Bases: NonequilibriumCoupledConstantStepper

Starting from an existing solution, which may be at thermal equilibrium or with previously applied bias of illumination, ramp the bias at one or more contacts.

parameter_name = 'V'
run_optics = False
step_size = 0.1
update_parameter_failure_factor = 0.5
update_parameter_success_factor = 1.5
user_apply_parameter_to_solution(solution, parameter_value)[source]

Module contents