getelec.band_structure

Band Structure Calculation Module.

This module provides a suite of classes to model and generate energy grids and band structures for metals and semiconductors: fixed grids, grids trimmed automatically to the energies that carry current ('smart'), and grids supplied by the user ('custom').

class BandStructure(abc.ABC):

Abstract base class representing a material's energy band structure.

Parameters

energy_resolution : float, default 0.01 The step size or resolution for the energy grid discretization, in electron-volts (eV)

Attributes

energy_resolution : float The step size or resolution for the energy grid discretization.

BandStructure(energy_resolution: float = 0.01)

Initialize the base BandStructure instance.

energy_resolution
@abstractmethod
def generate_band_structure( self, fermi_level: float, work_function: float, electric_field: float, temperature: float) -> numpy.ndarray:

Generate the energy grid or band structure for the material.

Parameters

fermi_level : float The Fermi energy level of the system, in electron-volts (eV) work_function : float The work function of the material, in electron-volts (eV) electric_field : float The applied external electric field, in volts per nanometre (V/nm) temperature : float The thermodynamic temperature of the system, in Kelvin (K)

Returns

numpy.ndarray An array representing the computed energy states or grid.

Raises

NotImplementedError If the subclass does not implement this abstract method, in electron-voltd (eV)

Examples

>>> # Subclasses must implement this method.
>>> # band_structure_instance.generate_band_structure(9.5, 4.5, 0.0, 3.0, 300.0)
def get_state_weights(self, energy_array: numpy.ndarray) -> numpy.ndarray:

How many states each total energy carries, relative to a free electron gas.

The current integral of the paper counts the states available at total energy E through the inner integral over normal energy, integral of D dE_z across the window, which for a parabolic band is the area of the k-parallel disc. A band structure that knows better than the free electron model returns the ratio here, and the emitter multiplies the total energy distribution by it; the normal energy distribution is then rebuilt from the same double integral, so the two still integrate to the same current density.

The base class returns ones, which is the free electron model and the behaviour of every band structure that does not override this.

Parameters

energy_array : numpy.ndarray Total energies, in eV.

Returns

numpy.ndarray Dimensionless weights, the same shape as energy_array.

class Metal(BandStructure):

Represents a basic uniform metallic band structure grid.

Parameters

lower_energy_limit : float, default 0.001 The minimum boundary of the uniform energy grid. It represents the bottom of the conduction band and it is the zero for our energy reference system. upper_energy_limit : float, default 30.0 The maximum boundary of the uniform energy grid. It represents the upper limit of the energy grid and it can be above the top of the potential barrier. energy_resolution : float, default 0.01 The discretization step size for the grid.

Attributes

lower_energy_lim : float The minimum boundary of the uniform energy grid. upper_energy_lim : float The maximum boundary of the uniform energy grid. energy_resolution : float The discretization step size for the grid.

Metal( lower_energy_limit: float = 0.001, upper_energy_limit: float = 30.0, energy_resolution: float = 0.01)

Initialize the Metal instance with grid boundaries and resolution.

lower_energy_lim
upper_energy_lim
energy_resolution
def generate_band_structure(self, **kwargs) -> numpy.ndarray:

Generate a uniform energy grid from lower to upper energy limits.

Parameters

**kwargs : dict Arbitrary keyword arguments. Included for compatibility with the base class interface but ignored during calculation.

Returns

numpy.ndarray A 1D array of evenly spaced energy values.

Examples

>>> metal = Metal(lower_energy_limit=0.0, upper_energy_limit=2.0, energy_resolution=0.5)
>>> metal.generate_band_structure()
array([0. , 0.5, 1. , 1.5, 2. ])
Inherited Members
BandStructure
get_state_weights
class Semiconductor(BandStructure):

Represents a semiconductor band structure with conduction and valence bands.

Parameters

lower_energy_limit : float, default 0.001 The absolute lowest energy limit for the valence band generation. It represents the bottom of the valence band and it is the zero for our energy reference system. top_valence : float, default 12.5 The energy level corresponding to the top of the valence band. fermi_level : float, default 13.0 The Fermi energy level of the semiconductor system. band_gap : float, default 1.12 The energy band gap separating the valence and conduction bands. electron_eff_mass : float, default 1.64 The effective mass of electrons in the conduction band. It is a float that represents the relative effective mass of the electron when compared with the free electron mass. hole_eff_mass : float, default 0.68 The effective mass of holes in the valence band. It is a float that represents the relative effective mass of the hole when compared with the free electron mass. upper_energy_limit : float, default 30.0 The absolute maximum energy limit for the conduction band generation. energy_resolution : float, default 0.002 The discretization step size for the base grids. Finer than a metal's default because each band's grid ends at its band edge, where the TED has a finite slope: the trapezoid error in the current is then ~(h / k_B T)^2 / 12, about 1.1% at 0.01 eV and 300 K.

Attributes

lower_energy_lim : float The absolute lowest energy limit. upper_energy_lim : float The absolute maximum energy limit. energy_resolution : float The discretization step size. top_valence : float The top of the valence band. fermi_level : float The Fermi energy level. band_gap : float The semiconductor energy band gap. electron_eff_mass : float The effective mass of conduction electrons. hole_eff_mass : float The effective mass of valence holes.

Semiconductor( lower_energy_limit: float = 0.001, top_valence: float = 12.5, fermi_level: float = 13.0, band_gap: float = 1.12, electron_eff_mass=1.64, hole_eff_mass=0.68, upper_energy_limit: float = 30.0, energy_resolution: float = 0.002)

Initialize the Semiconductor instance with full band parameters.

lower_energy_lim
upper_energy_lim
energy_resolution
top_valence
fermi_level
band_gap
electron_eff_mass
hole_eff_mass
def generate_band_structure(self, **kwargs) -> numpy.ndarray:

Generate unscaled and scaled dispersion grids for conduction and valence bands.

Parameters

**kwargs : dict Arbitrary keyword arguments. Included for compatibility with the base class interface but ignored during calculation.

Returns

tuple of numpy.ndarray A 4-element tuple containing: - energies_conduction_band1 : Conduction band unscaled energy grid. - energies_conduction_band2 : Conduction band scaled by electron effective mass. - energies_valence_band1 : Valence band unscaled energy grid. - energies_valence_band2 : Valence band scaled by hole effective mass.

Examples

>>> semi = Semiconductor(lower_energy_limit=10.0, top_valence=12.0, band_gap=1.0, upper_energy_limit=14.0, energy_resolution=1.0)
>>> ec1, ec2, ev1, ev2 = semi.generate_band_structure()
Inherited Members
BandStructure
get_state_weights
class SmartMetal(BandStructure):

Represents an adaptive metal grid system optimizing energy boundaries dynamically.

Notes

Use only with SN barrier. The rest of the barriers need their own optimisation protocol. If not SN barrier, use Metal or Custom.

The defaults are built for the currents an emitter actually produces. They hold the current density and the Nottingham heat within 1% of a far wider grid wherever the current density reaches 1e-12 A/cm^2, which is 1 pA from 1 cm^2 -- checked for the planar, sharp-tip and triangular barriers, work functions of 2.5 to 6 eV, fields of 0.2 to 12 V/nm and 300 to 3000 K. Below 1 pA/cm^2 the cuts start to show, and the smaller the current the more it costs to reach: the same 1% at arbitrarily small currents would need a supply_threshold around 1e-40 and the energies to match. If those currents are what you are after, pass a smaller supply_threshold and a larger barrier_width, or set the limits yourself with Metal or CustomMetal.

Parameters

barrier_width : float, default 3.0 The spatial width of the potential barrier, beyond which there is effectivively no tunneling. supply_threshold : float, default 5e-24 The numerical threshold constraint used to cap maximum grid energies, beyond which the emitted electrons do not substatially contribute to the total emitted current. The cut sits k_B T * ln(1 / supply_threshold) above the Fermi level, 53.7 k_B T at the default. Thermionic emission comes from the top of the barrier, so the grid has to reach it: at 32.2 k_B T (a threshold of 1e-14) the cut falls below the top once the top lies more than about 25 k_B T above the Fermi level, and the current is then underestimated -- by 7% for a 4.5 eV work function at 0.5 V/nm and 1500 K. energy_resolution : float, default 0.01 The structural resolution step size of the resulting mesh.

Attributes

barrier_width : float The spatial width of the potential barrier. supply_threshold : float The numerical threshold constraint.

SmartMetal( barrier_width: float = 3.0, supply_threshold: float = 5e-24, energy_resolution: float = 0.01)

Initialize the SmartMetal instance with physical boundary parameters.

barrier_width
supply_threshold
def generate_band_structure( self, fermi_level: float = 9.5, work_function: float = 4.5, electric_field: float = 3.0, temperature: float = 300.0) -> numpy.ndarray:

Generate an optimized dynamic array grid bounded by the calculated limits.

Parameters

fermi_level : float, default 9.5 The structural Fermi energy level. work_function : float, default 4.5 The target material work function. electric_field : float, default 3.0 External field strength impacting the profile. temperature : float, default 300.0 The thermal setting in Kelvin.

Returns

numpy.ndarray An array containing the structured smart mesh layout.

Examples

>>> sm = SmartMetal()
>>> sm.generate_band_structure(temperature=100.0)
class SmartSemiconductor(BandStructure):

Semiconductor band grids trimmed with the two criteria of SmartMetal.

The grids of Semiconductor, cut in each band:

  • bottom: where the Schottky-Nordheim barrier becomes wider than barrier_width. As in SmartMetal, a band's grid never starts higher than 1 eV below the top of its occupied states, min(E_F, band top): field emission comes from within a few decay widths of that energy, and at low field the barrier criterion alone would start above it. For the conduction band that is 1 eV below E_F; for the valence band 1 eV below min(E_F, E_V), so a valence band lying more than 1 eV below the Fermi level keeps the energies just below E_V it emits from.
  • top: where the supply ln(1 + exp(-(E - E_F)/k_B T)) falls below supply_threshold.

What survives is clipped to each band: the conduction grid never starts below E_C, and the valence grid never runs past the top of the Semiconductor valence grid. A band that lies entirely outside the window comes back empty and contributes nothing -- the conduction band of a semiconductor at 0 K with the Fermi level in the gap, for example. The points kept are exactly those of the Semiconductor grids with the same parameters, so the two agree wherever neither cut applies.

Parameters

lower_energy_limit : float, default 0.001 The bottom of the valence band, and the zero of the energy scale, in eV. top_valence : float, default 12.5 The top of the valence band, in eV. fermi_level : float, default 13.0 The Fermi level, in eV. Used when generate_band_structure is not given one; an emitter passes its barrier's. band_gap : float, default 1.12 The band gap, in eV. electron_eff_mass : float, default 1.64 Conduction band effective mass, relative to the free electron mass. hole_eff_mass : float, default 0.68 Valence band effective mass, relative to the free electron mass. upper_energy_limit : float, default 30.0 The top of the conduction band grid before trimming, in eV. energy_resolution : float, default 0.002 The grid step, in eV. See Semiconductor for why it is finer than a metal's. barrier_width : float, default 3.0 The barrier width, in nm, below whose energy the transmission is taken as negligible. supply_threshold : float, default 5e-24 The supply below which the electrons are taken as negligible. The cut sits k_B T * ln(1 / supply_threshold) above the Fermi level, 53.7 k_B T at the default, which keeps the barrier top inside the grid wherever the current reaches 1e-12 A/cm^2. See SmartMetal.

Attributes

lower_energy_lim, upper_energy_lim, energy_resolution : float As for Semiconductor. top_valence, fermi_level, band_gap : float As for Semiconductor. electron_eff_mass, hole_eff_mass : float As for Semiconductor. barrier_width, supply_threshold : float As for SmartMetal.

Notes

The barrier criterion uses the Schottky-Nordheim barrier, as SmartMetal does, whatever barrier the emitter holds.

As for SmartMetal, the defaults are set for currents down to 1e-12 A/cm^2 (1 pA from 1 cm^2). For currents far below that, pass a smaller supply_threshold and a larger barrier_width, or give the grids yourself with CustomSemiconductor.

SmartSemiconductor( lower_energy_limit: float = 0.001, top_valence: float = 12.5, fermi_level: float = 13.0, band_gap: float = 1.12, electron_eff_mass=1.64, hole_eff_mass=0.68, upper_energy_limit: float = 30.0, energy_resolution: float = 0.002, barrier_width: float = 3.0, supply_threshold: float = 5e-24)

Initialize the SmartSemiconductor instance with band and trimming parameters.

lower_energy_lim
upper_energy_lim
top_valence
fermi_level
band_gap
electron_eff_mass
hole_eff_mass
barrier_width
supply_threshold
def generate_band_structure( self, fermi_level: float = None, work_function: float = 4.5, electric_field: float = 3.0, temperature: float = 300.0) -> tuple:

Generate the trimmed conduction and valence grids and their window limits.

Parameters

fermi_level : float, optional The Fermi level, in eV. Defaults to the instance's own. work_function : float, default 4.5 The work function, in eV. electric_field : float, default 3.0 The field, in V/nm. temperature : float, default 300.0 The temperature, in K.

Returns

tuple of numpy.ndarray The same four arrays as Semiconductor.generate_band_structure(): each band's energies and the lower limit of its E_z window. Either band may be empty.

Examples

>>> smart = SmartSemiconductor(energy_resolution=0.01)
>>> ec1, ec2, ev1, ev2 = smart.generate_band_structure(work_function=4.5, electric_field=5.0, temperature=300.0)
class CustomMetal(BandStructure):

Represents a static user-provided pre-defined metal band structure system. It can be used to set any arbitrary metallic band structure - the user should be ware of checking for consistency with the other modules

Parameters

custom_energy_array : numpy.ndarray The pre-computed custom structured array representation.

Attributes

custom_energy_array : numpy.ndarray The pre-computed custom structured array representation.

CustomMetal(custom_energy_array: numpy.ndarray)

Initialize the CustomMetal tracking containing custom array arrays.

custom_energy_array
def generate_band_structure( self, fermi_level: float = 9.5, work_function: float = 4.5, electric_field: float = 3.0, temperature: float = 300.0) -> numpy.ndarray:

Return the stored custom energy matrix array without transformation.

Parameters

fermi_level : float, default 9.5 Ignored parameters included for subclass compatibility. work_function : float, default 4.5 Ignored parameters included for subclass compatibility. electric_field : float, default 3.0 Ignored parameters included for subclass compatibility. temperature : float, default 300.0 Ignored parameters included for subclass compatibility.

Returns

numpy.ndarray The original initialization array reference map context.

Examples

>>> array = np.array([1, 2, 3])
>>> cm = CustomMetal(array)
>>> cm.generate_band_structure()
array([1, 2, 3])
class CustomSemiconductor(BandStructure):

A semiconductor band structure on energy grids the user supplies, one per band.

The counterpart of CustomMetal: the energies are returned as given, for a range, a resolution or a spacing that Semiconductor does not offer. What a semiconductor needs beyond the energies -- the lower limit of each energy's E_z window, Eq. (8) of the paper -- follows from the band edges and effective masses, exactly as in Semiconductor, so the physics does not depend on which of the two built the grid.

The grids are checked when the band structure is generated, because points outside a band have no states and would make the distributions inconsistent: each must be a finite, strictly increasing 1-D array, the conduction energies must not lie below E_C = top_valence + band_gap, and the valence energies must not lie above top_valence. Either band may be empty, and then contributes nothing.

Parameters

custom_conduction_array : array_like Conduction band energies, in eV. custom_valence_array : array_like Valence band energies, in eV. top_valence : float, default 12.5 The top of the valence band, in eV. band_gap : float, default 1.12 The band gap, in eV. electron_eff_mass : float, default 1.64 Conduction band effective mass, relative to the free electron mass. hole_eff_mass : float, default 0.68 Valence band effective mass, relative to the free electron mass.

Attributes

custom_conduction_array, custom_valence_array : numpy.ndarray The energies of each band. top_valence, band_gap, electron_eff_mass, hole_eff_mass : float As for Semiconductor.

Notes

The distributions are verified on uniform grids, which is what the other band structures produce. A non-uniform grid is integrated correctly, but the transmission over each window is sampled at the grid's first step.

CustomSemiconductor( custom_conduction_array: numpy.ndarray, custom_valence_array: numpy.ndarray, top_valence: float = 12.5, band_gap: float = 1.12, electron_eff_mass: float = 1.64, hole_eff_mass: float = 0.68)

Initialize the CustomSemiconductor instance with its band grids and parameters.

custom_conduction_array
custom_valence_array
top_valence
band_gap
electron_eff_mass
hole_eff_mass
def generate_band_structure( self, fermi_level: float = 9.5, work_function: float = 4.5, electric_field: float = 3.0, temperature: float = 300.0) -> tuple:

Return the stored band grids with the lower limits of their E_z windows.

Parameters

fermi_level, work_function, electric_field, temperature : float Ignored parameters included for subclass compatibility.

Returns

tuple of numpy.ndarray The same four arrays as Semiconductor.generate_band_structure().

Raises

ValueError If a grid is not a finite, strictly increasing 1-D array, or lies outside its band.

Examples

>>> custom = CustomSemiconductor(np.linspace(13.62, 15.0, 139), np.linspace(11.0, 12.5, 151))
>>> ec1, ec2, ev1, ev2 = custom.generate_band_structure()
class DensityOfStatesMetal(BandStructure):

A metal whose energy grid and state count both come from a tabulated density of states.

The other metal band structures assume a free electron gas, whose density of states goes as sqrt(E) above the bottom of the conduction band. This one takes g(E) from a table -- a DFT calculation, say -- and weights every total energy by how many states it actually holds relative to that free electron reference,

R(E) = [g(E) / sqrt(E)] / [g(E_F) / sqrt(E_F)] ,

normalised so that R(E_F) = 1. The emitter multiplies the total energy distribution by R and rebuilds the normal energy distribution from the same double integral, so the current density, the Nottingham heat and both distributions stay consistent with each other.

The energies are the table's own, so the table sets both the range and the resolution of the grid. Energies at or below zero are dropped: zero is the bottom of the conduction band and the reference for every energy in this package, and 1 / sqrt(E) has no meaning below it.

Parameters

dos_energies : array_like Energies of the tabulated density of states, in eV, on the same scale as fermi_level -- that is, measured from the bottom of the conduction band, not from the Fermi level. Must be finite and strictly increasing. dos_values : array_like The density of states at those energies. Any units: only the shape survives the normalisation, which is why absolute currents remain comparable to the free electron result even for a projected density of states with no absolute normalisation of its own. fermi_level : float The Fermi level, in eV, measured from the bottom of the conduction band. Must lie inside the tabulated range, where the table must have states. work_function : float The work function, in eV. Used only to check that the grid reaches over the top of the barrier; the barrier itself holds its own copy.

Attributes

dos_energies, dos_values : numpy.ndarray The table, cropped to positive energies. fermi_level, work_function : float As above. energy_resolution : float The table's own step, taken as the median spacing of its energies.

Notes

What the weight is, and what it is not. In the supply integral the group velocity cancels the Jacobian, v_z dk_z = dE / hbar, so the quantity that belongs here is the number of forward-propagating channels at energy E -- the area of the constant energy surface projected on the surface plane, counted with multiplicity -- and not the density of states, which weights states by one over the same velocity. Writing R = g / sqrt(E) assumes the two are related as they are for free electrons. Where flat bands dominate g they have small normal velocity and small projected area, so their contribution is overestimated; for a d band metal that is a real effect, not a rounding one. Treat this as a model of the band structure's influence on emission rather than as a first-principles calculation of it. If the number of channels N(E) is known instead, it belongs in the same slot against its own free electron reference, which goes as E and not as sqrt(E): get_state_weights() is the only place the ratio is formed.

Range, and where this stops being physics. A table ends where the calculation that produced it ran out of bands, and its density of states falls to zero at that edge for that reason and not a physical one. Emission from the top of a table is therefore an artefact of the band count: the model reports no states where a real metal has a free electron-like continuum, and the current comes out too low.

Field emission is untouched by this, because it draws from within a fraction of an electronvolt of the Fermi level -- on a tabulated d band metal the share of the current density coming from the table's top 1 eV is around 1e-30 at 300 K, at every field from 1.5 to 7 V/nm. Thermionic emission is another matter: it draws from the top of the barrier, which sits close to the end of the table, and the same share reaches 0.99 at 1.5 V/nm and 2000 K, where the tabulated current density comes out 23 times below the free electron one for no physical reason.

So generate_band_structure warns when the top of the table clears the higher of the barrier maximum and the Fermi level by less than 8 k_B T. The barrier maximum is the Schottky-Nordheim one, E_F + phi - 2 sqrt(k_e F / 4), as in SmartMetal. Take the warning seriously: what it flags is not a discretisation error but a result set by where the band structure calculation stopped.

Examples

>>> band = DensityOfStatesMetal.from_file(          # doctest: +SKIP
...     "dos.txt", fermi_level=10.268, work_function=4.67)
>>> energies = band.generate_band_structure()       # doctest: +SKIP
>>> weights = band.get_state_weights(energies)      # doctest: +SKIP
DensityOfStatesMetal( dos_energies: numpy.ndarray, dos_values: numpy.ndarray, fermi_level: float, work_function: float)

Initialize the DensityOfStatesMetal instance from a tabulated density of states.

dos_energies
dos_values
fermi_level
work_function
@classmethod
def from_file( cls, path, fermi_level: float, work_function: float, columns=(0, 1)) -> DensityOfStatesMetal:

Build the band structure from a two-column text file.

Parameters

path : str or pathlib.Path A whitespace-separated text file. Header lines and # comments are skipped, so no row count is needed. fermi_level, work_function : float As for the constructor, in eV. columns : tuple of int, default (0, 1) Which columns hold the energies and the density of states. Files with several projections in them are common, so the pair is explicit.

Returns

DensityOfStatesMetal

Examples

>>> band = DensityOfStatesMetal.from_file(      # doctest: +SKIP
...     "examples/dos.txt", fermi_level=10.268, work_function=4.67)
def get_state_weights(self, energy_array: numpy.ndarray) -> numpy.ndarray:

The ratio R(E) of tabulated to free electron states, normalised to one at E_F.

Zero outside the tabulated range, where the table says there are no states. Evaluated by interpolation, which is exact on the grid generate_band_structure() returns, since that is the table's own.

Parameters

energy_array : numpy.ndarray Total energies, in eV.

Returns

numpy.ndarray

def generate_band_structure( self, fermi_level: float = None, work_function: float = None, electric_field: float = 3.0, temperature: float = 300.0) -> numpy.ndarray:

The tabulated energies, which set both the range and the resolution of the grid.

Parameters

fermi_level : float, optional The Fermi level, in eV. Defaults to the instance's own. work_function : float, optional The work function, in eV. Defaults to the instance's own. electric_field : float, default 3.0 The field, in V/nm. Used only for the range check. temperature : float, default 300.0 The temperature, in K. Used only for the range check.

Returns

numpy.ndarray The tabulated energies, cropped to positive values.

Warns

RuntimeWarning If the table ends too close to the top of the barrier or to the Fermi level for the emission to be complete. See the class notes.

Examples

>>> band = DensityOfStatesMetal(           # doctest: +SKIP
...     energies, dos, fermi_level=10.268, work_function=4.67)
>>> band.generate_band_structure(electric_field=5.0, temperature=300.0)
Inherited Members
BandStructure
energy_resolution