"""Provide the primary functions."""
from .util import *
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline as spline
from scipy.interpolate import PchipInterpolator as interpolator
from .accum import *
def band_gap(Eg_o: float, Ao: float, Bo: float, temp: np.ndarray = None) -> np.ndarray:
"""
This method uses Eg(T)=Eg(T=0)-Ao*T**2/(T+Bo) to approximate the temperature dependency of the dielectrics band gap.
A good reference is "Properties of Advanced Semiconductor Materials" by Michael E. Levinshtein.
Parameters
----------
Eg_o: float
The band gap at zero Kelvin
Ao: float
Experimentally fitted parameter
Bo: float
Experimentally fitted parameter
temp: np.ndarray
Temperature range
Returns
-------
Eg: np.ndarray
Temperature-dependent band gap
"""
if temp is None:
'''Use default temperature range (300K up to 1300K with step of 100K) '''
T = temperature()
else:
T = temp
Eg = Eg_o - Ao * np.divide(T ** 2, T + Bo)
return Eg
def analytical_dos(range_energy: np.ndarray, electron_eff_mass: float, nonparabolic_term: np.ndarray) -> dict:
"""
This function approximate the electron density of state for parabolic and non-parabolic bands
in case DFT calculation is not available.
Parameters
----------
range_energy: np.ndarray
Electron energy range
electron_eff_mass: float
Electron effective mass
nonparabolic_term: np.ndarray
Non-parabolic term (shows the mixture of S and P orbitals)
Returns
-------
DoS: dict
The DoS['DoS_nonparabolic'] is phonon density of states for non-parabolic band and
the DoS['DoS_parabolic'] is the phonon density of states for non-parabolic.
"""
hBar = 6.582119e-16 # Reduced Planck constant in eV.s
e2C = 1.6021765e-19 # e to Coulomb unit change
DoS_nonparabolic = 1 / np.pi ** 2 * np.sqrt(2 * range_energy * (1 + range_energy * nonparabolic_term.T)) \
* np.sqrt(electron_eff_mass / hBar ** 2) ** 3 \
* (1 + (2 * range_energy * nonparabolic_term.T)) / e2C ** (3.0 / 2)
DoS_parabolic = np.sqrt(range_energy) / np.pi ** 2 * np.sqrt(2) / hBar ** 3 \
* electron_eff_mass ** (3.0 / 2) / e2C ** (3.0 / 2)
print(np.shape(DoS_nonparabolic), np.shape(DoS_parabolic))
DoS = {'DoS_nonparabolic':DoS_nonparabolic, 'DoS_parabolic': DoS_parabolic}
return DoS
[docs]def fermi_level(carrier: np.ndarray, energy: np.ndarray, density: np.ndarray,
Nc: float = None, Ao: float = None, temp: np.ndarray = None) -> np.ndarray:
"""
This function uses Joice Dixon approximation to predict Ef and thereby the carrier concentration at each temperature
A good reference book is "Principles of Semiconductor Devices" by Sima Dimitrijev.
Parameters
----------
carrier: np.ndarray
Total carrier concentration
energy: np.ndarray
The electron energy level
density: np.ndarray
The electron density of states
Nc: float
The effective densities of states in the conduction band
Ao: float
Experimentally fitted parameter (Nc ~ Ao*T^(3/2))
temp: np.ndarray
Temperature range
Returns
-------
output: np.ndarray
The first row is the carrier concentration and the second one is the Fermi level
"""
k_bolt = 8.617330350e-5 # Boltzmann constant in eV/K
if temp is None:
T = temperature()
else:
T = temp
if Ao is None and Nc is None:
raise Exception("Either Ao or Nc should be defined")
if Nc is None:
Nc = Ao * temp ** (3.0 / 2)
JD_CC = np.log(carrier / Nc) + 1 / np.sqrt(8) * carrier / Nc - (3. / 16 - np.sqrt(3) / 9) * (carrier / Nc)**2
fermi_energy = k_bolt * (T * JD_CC) # Joice Dixon approximation of Ef
f, _ = fermi_distribution(energy, fermi_energy, temp=T) # Fermi distribution
n = np.trapz(np.multiply(density, f), energy, axis=1) # Carrier concentration
output = np.stack((n, fermi_energy[0]))
return output
[docs]def fermi_self_consistent(carrier: np.ndarray, temp: np.ndarray,
energy: np.ndarray, density: np.ndarray,
fermi_levels: np.ndarray) -> np.ndarray:
"""
Self-consistent calculation of the Fermi level from a given carrier concentration
using Joyce Dixon approximation as the initial guess for degenerate semiconductors.
As a default value of 4000 sampling points in energy range from Ef(JD)-0.4 eV up to
Ef(JD)+0.2 is considered.This looks reasonable in most cases. The index is printed out
if it reaches the extreme index of (0) or (4000), increase energy range.
Parameters
----------
carrier: np.ndarray
Total carrier concentration
energy: np.ndarray
The electron energy level
density: np.ndarray
The electron density of states
fermi_levels: np.ndarray
Joyce Dixon femi level approximation as the initial guess
temp: np.ndarray
Temperature range
Returns
-------
output: np.ndarray
The first row is the carrier concentration and the second one is the Fermi level
"""
fermi = np.linspace(fermi_levels[1] - 0.4, fermi_levels[1] + 0.2, 4000, endpoint=True).T
init_array = np.empty((np.shape(temp)[1], np.shape(fermi)[1]))
idx_j = 0
for j in temp[0]:
idx_i = 0
for i in fermi[idx_j]:
f, _ = fermi_distribution(energy, np.expand_dims(np.array([i]), axis=0),
np.expand_dims(np.array([j]), axis=0))
tmp = np.trapz(density * f, energy, axis=1)
init_array[idx_j, idx_i] = tmp
idx_i += 1
idx_j += 1
diff = np.tile(carrier.T, (1, np.shape(fermi)[1])) - abs(init_array)
min_idx = np.argmin(np.abs(diff), axis=1)
print("Fermi Level Self Consistent Index ", min_idx)
Ef = np.empty((1, np.shape(temp)[1]))
for Ef_idx in np.arange(len(min_idx)):
Ef[0, Ef_idx] = fermi[Ef_idx, min_idx[Ef_idx]]
elm = 0
n = np.empty((1, np.shape(temp)[1]))
for idx in min_idx:
n[0, elm] = init_array[elm, idx]
elm += 1
output = np.stack((n[0], Ef[0]))
return output
[docs]def group_velocity(kpoints: np.ndarray, energy_kp: np.ndarray, energy: np.ndarray) -> np.ndarray:
"""
Group velocity is the derivation of band structure from DFT. Linear BTE needs single band data.
Reciprocal lattice vector is required
Parameters
----------
kpoints: np.ndarray
Wave vectors
energy_kp: np.ndarray
Energy for each wave vector
energy: np.ndarray
Energy range
Returns
-------
velocity: np.ndarray
Group velocity
"""
h_bar = 6.582119e-16 # Reduced Planck constant in eV.s
dE = np.roll(energy_kp, -1, axis=0) - np.roll(energy_kp, 1, axis=0)
dk = np.roll(kpoints, -1, axis=0) - np.roll(kpoints, 1, axis=0)
dEdk = np.divide(dE, dk)
dEdk[0] = (energy_kp[1] - energy_kp[0]) / (kpoints[1] - kpoints[0])
dEdk[-1] = (energy_kp[-1] - energy_kp[-2]) / (kpoints[-1] - kpoints[-2])
dEdkSpline = spline(energy_kp, np.array(dEdk))
dEdkFunctionEnergy = dEdkSpline(energy)
group_vel = dEdkFunctionEnergy / h_bar
return group_vel
def analytical_group_velocity(energy: np.ndarray, lattice_parameter: np.ndarray, num_kpoints: np.ndarray,
m_eff: np.ndarray, valley: np.ndarray, dk_len: np.ndarray,
alpha_term: np.ndarray) -> np.ndarray:
"""
This method approximate the group velocity near the conduction band edge
in case no DFT calculation is available.
Parameters
----------
energy: np.ndarray
Energy range
lattice_parameter: np.ndarray
Lattice parameter
num_kpoints: np.ndarray
Number of wave vectors
m_eff: np.ndarray
Effective mass along different axis
valley: np.ndarray
Conduction valley
dk_len: np.ndarray
magnitude of wave vectors
alpha_term: np.ndarray
Non-parabolic term
Returns
-------
velocity: np.ndarray
Group velocity
"""
e2C = 1.6021765e-19 # e to Coulomb unit change
h_bar = 6.582119e-16 # Reduced Planck constant in eV.s
ko = 2 * np.pi / lattice_parameter * valley
del_k = 2 * np.pi / lattice_parameter * dk_len * np.array([1, 1, 1])
kx = np.linspace(ko[0], ko[0] + del_k[0], num_kpoints[0], endpoint=True) # kpoints mesh
ky = np.linspace(ko[1], ko[1] + del_k[1], num_kpoints[1], endpoint=True) # kpoints mesh
kz = np.linspace(ko[2], ko[2] + del_k[2], num_kpoints[2], endpoint=True) # kpoints mesh
[xk, yk, zk] = np.meshgrid(kx, ky, kz)
xk_ = np.reshape(xk, -1)
yk_ = np.reshape(yk, -1)
zk_ = np.reshape(zk, -1)
kpoint = np.array([xk_, yk_, zk_])
mass_cond = 3 / (1 / m_eff[0] + 1 / m_eff[1] + 1 / m_eff[2]) # Conduction band effective mass
E = h_bar ** 2 / 2 * \
((kpoint[0] - ko[0]) ** 2 / m_eff[0] +
(kpoint[1] - ko[1]) ** 2 / m_eff[1] +
(kpoint[2] - ko[2]) ** 2 / m_eff[2]) * e2C # Ellipsoidal energy band shape
vel = h_bar * np.sqrt((kpoint[0] - ko[0]) ** 2 + (kpoint[1] - ko[1]) ** 2
+ (kpoint[2] - ko[2]) ** 2) / mass_cond / (1 + 2 * alpha_term * E) * e2C
Ec, indices, return_indices = np.unique(E, return_index=True, return_inverse=True)
vg = accum(return_indices, vel, func=np.mean, dtype=float)
en_spline = interpolator(Ec, vg)
velocity = en_spline(energy)
return velocity
if __name__ == "__main__":
print('ThermoElectric')