#![allow(dead_code)]
#![allow(clippy::too_many_arguments)]
use std::f64::consts::PI;
pub const K_B_EV: f64 = 8.617_333_262e-5;
pub const K_B_J: f64 = 1.380_649e-23;
pub const E_CHARGE: f64 = 1.602_176_634e-19;
pub const M_ELECTRON: f64 = 9.109_383_701_5e-31;
pub const HBAR: f64 = 1.054_571_817e-34;
#[derive(Debug, Clone)]
pub struct QuantumMaterial {
pub band_gap: f64,
pub fermi_energy: f64,
pub effective_mass: f64,
pub mobility_300k: f64,
pub scattering_time: f64,
pub name: String,
}
impl QuantumMaterial {
pub fn new(
band_gap: f64,
fermi_energy: f64,
effective_mass: f64,
mobility_300k: f64,
scattering_time: f64,
name: impl Into<String>,
) -> Self {
Self {
band_gap,
fermi_energy,
effective_mass,
mobility_300k,
scattering_time,
name: name.into(),
}
}
pub fn silicon() -> Self {
Self::new(1.12, 0.56, 0.26, 0.135, 3.0e-13, "Silicon")
}
pub fn gaas() -> Self {
Self::new(1.42, 0.71, 0.063, 0.85, 1.0e-13, "GaAs")
}
pub fn copper() -> Self {
Self::new(0.0, 7.04, 1.0, 3.2e-3, 2.5e-14, "Copper")
}
}
pub fn fermi_dirac(energy: f64, fermi_energy: f64, temp_k: f64) -> f64 {
if temp_k <= 0.0 {
if energy < fermi_energy {
1.0
} else if (energy - fermi_energy).abs() < 1e-12 {
0.5
} else {
0.0
}
} else {
let kbt = K_B_EV * temp_k;
let x = (energy - fermi_energy) / kbt;
if x > 500.0 {
0.0
} else if x < -500.0 {
1.0
} else {
1.0 / (1.0 + x.exp())
}
}
}
pub fn density_of_states(energy: f64, effective_mass_ratio: f64) -> f64 {
if energy <= 0.0 {
return 0.0;
}
let m_star = effective_mass_ratio * M_ELECTRON;
let energy_j = energy * E_CHARGE; let prefactor = (1.0 / (2.0 * PI * PI)) * (2.0 * m_star / (HBAR * HBAR)).powf(1.5);
let dos_per_j = prefactor * energy_j.sqrt();
dos_per_j * E_CHARGE
}
pub fn carrier_concentration(
band_gap: f64,
fermi_energy: f64,
effective_mass_ratio: f64,
temp_k: f64,
) -> f64 {
if temp_k <= 0.0 {
return 0.0;
}
let m_star = effective_mass_ratio * M_ELECTRON;
let kbt_j = K_B_J * temp_k;
let kbt_ev = K_B_EV * temp_k;
let h = 2.0 * PI * HBAR;
let n_c = 2.0 * (2.0 * PI * m_star * kbt_j / (h * h)).powf(1.5);
let e_c = band_gap;
let delta = (e_c - fermi_energy) / kbt_ev;
if delta > 500.0 {
return 0.0;
}
n_c * (-delta).exp()
}
pub fn mobility_temperature(mobility_300k: f64, temp_k: f64, alpha: f64) -> f64 {
if temp_k <= 0.0 {
return 0.0;
}
mobility_300k * (300.0 / temp_k).powf(alpha)
}
pub fn seebeck_coefficient(band_gap: f64, fermi_energy: f64, temp_k: f64, r_scatter: f64) -> f64 {
if temp_k <= 0.0 {
return 0.0;
}
let kbt_ev = K_B_EV * temp_k;
let e_c = band_gap;
let eta = (e_c - fermi_energy) / kbt_ev;
-(K_B_EV / 1.0) * (eta + r_scatter + 2.5) }
pub fn hall_coefficient(carrier_density: f64, hall_factor: f64) -> f64 {
if carrier_density <= 0.0 {
return 0.0;
}
-hall_factor / (carrier_density * E_CHARGE)
}
pub fn optical_conductivity(
carrier_density: f64,
effective_mass_ratio: f64,
scattering_time: f64,
omega: f64,
) -> f64 {
let m_star = effective_mass_ratio * M_ELECTRON;
let sigma0 = carrier_density * E_CHARGE * E_CHARGE * scattering_time / m_star;
let omega_tau = omega * scattering_time;
sigma0 / (1.0 + omega_tau * omega_tau)
}
pub fn plasma_frequency(carrier_density: f64, effective_mass_ratio: f64, epsilon_r: f64) -> f64 {
let epsilon_0 = 8.854_187_817e-12_f64; let m_star = effective_mass_ratio * M_ELECTRON;
(carrier_density * E_CHARGE * E_CHARGE / (epsilon_0 * epsilon_r * m_star)).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: f64 = 1e-10;
#[test]
fn test_fermi_dirac_t0_below() {
let f = fermi_dirac(0.5, 1.0, 0.0);
assert!(
(f - 1.0).abs() < EPS,
"FD at T=0, E<E_F should be 1, got {f}"
);
}
#[test]
fn test_fermi_dirac_t0_above() {
let f = fermi_dirac(1.5, 1.0, 0.0);
assert!(f.abs() < EPS, "FD at T=0, E>E_F should be 0, got {f}");
}
#[test]
fn test_fermi_dirac_t0_at_ef() {
let f = fermi_dirac(1.0, 1.0, 0.0);
assert!(
(f - 0.5).abs() < EPS,
"FD at T=0, E=E_F should be 0.5, got {f}"
);
}
#[test]
fn test_fermi_dirac_at_fermi_level_finite_t() {
let f = fermi_dirac(1.0, 1.0, 300.0);
assert!(
(f - 0.5).abs() < EPS,
"FD at E=E_F for any T should be 0.5, got {f}"
);
}
#[test]
fn test_fermi_dirac_thermal_tail() {
let kbt = K_B_EV * 1000.0;
let ef = 1.0;
let e = ef + 5.0 * kbt;
let f = fermi_dirac(e, ef, 1000.0);
let expected = 1.0 / (1.0 + 5.0_f64.exp());
assert!(
(f - expected).abs() < 1e-12,
"FD thermal tail mismatch: {f} vs {expected}"
);
}
#[test]
fn test_fermi_dirac_large_negative() {
let f = fermi_dirac(-10.0, 1.0, 0.01); assert!(f > 0.99, "FD way below E_F at low T should be ~1, got {f}");
}
#[test]
fn test_dos_zero_at_band_edge() {
let d = density_of_states(0.0, 0.26);
assert!(d.abs() < EPS, "DOS at band edge should be 0, got {d}");
}
#[test]
fn test_dos_increases_with_energy() {
let d1 = density_of_states(0.1, 0.26);
let d2 = density_of_states(0.5, 0.26);
assert!(
d2 > d1,
"DOS should increase with energy in parabolic band, d1={d1}, d2={d2}"
);
}
#[test]
fn test_dos_scales_with_mass() {
let d_light = density_of_states(0.3, 0.1);
let d_heavy = density_of_states(0.3, 1.0);
assert!(
d_heavy > d_light,
"Heavier effective mass should give larger DOS"
);
}
#[test]
fn test_carrier_concentration_increases_with_temp() {
let n_low = carrier_concentration(1.12, 0.56, 0.26, 200.0);
let n_high = carrier_concentration(1.12, 0.56, 0.26, 400.0);
assert!(
n_high > n_low,
"Carrier conc should increase with T: n_low={n_low}, n_high={n_high}"
);
}
#[test]
fn test_carrier_concentration_t0() {
let n = carrier_concentration(1.12, 0.56, 0.26, 0.0);
assert!(n.abs() < EPS, "Carrier conc at T=0 should be 0, got {n}");
}
#[test]
fn test_carrier_concentration_band_gap_effect() {
let n_small_gap = carrier_concentration(0.5, 0.25, 0.26, 300.0);
let n_large_gap = carrier_concentration(2.0, 1.0, 0.26, 300.0);
assert!(
n_small_gap > n_large_gap,
"Smaller band gap should yield higher carrier density"
);
}
#[test]
fn test_mobility_decreases_with_temp() {
let mu_low = mobility_temperature(0.135, 200.0, 1.5);
let mu_high = mobility_temperature(0.135, 400.0, 1.5);
assert!(
mu_high < mu_low,
"Mobility should decrease with temperature"
);
}
#[test]
fn test_mobility_at_300k() {
let mu = mobility_temperature(0.135, 300.0, 1.5);
assert!(
(mu - 0.135).abs() < 1e-12,
"Mobility at 300K should be 0.135, got {mu}"
);
}
#[test]
fn test_mobility_t0() {
let mu = mobility_temperature(0.135, 0.0, 1.5);
assert!(mu.abs() < EPS, "Mobility at T=0 should be 0, got {mu}");
}
#[test]
fn test_seebeck_nonzero() {
let s = seebeck_coefficient(1.12, 0.56, 300.0, -0.5);
assert!(
s.abs() > 1e-6,
"Seebeck coefficient should be non-zero, got {s}"
);
}
#[test]
fn test_seebeck_t0() {
let s = seebeck_coefficient(1.12, 0.56, 0.0, -0.5);
assert!(s.abs() < EPS, "Seebeck at T=0 should be 0, got {s}");
}
#[test]
fn test_seebeck_n_type_negative() {
let s = seebeck_coefficient(1.12, 0.56, 300.0, -0.5);
assert!(s < 0.0, "n-type Seebeck should be negative, got {s}");
}
#[test]
fn test_hall_coefficient_negative_for_electrons() {
let rh = hall_coefficient(1e22, 1.0);
assert!(
rh < 0.0,
"Hall coefficient for n-type should be negative, got {rh}"
);
}
#[test]
fn test_hall_coefficient_magnitude() {
let n = 1e22_f64;
let rh = hall_coefficient(n, 1.0);
let expected = -1.0 / (n * E_CHARGE);
assert!(
(rh - expected).abs() < EPS.abs(),
"Hall coefficient mismatch: {rh} vs {expected}"
);
}
#[test]
fn test_hall_coefficient_zero_density() {
let rh = hall_coefficient(0.0, 1.0);
assert!(
rh.abs() < EPS,
"Hall coefficient for zero density should be 0, got {rh}"
);
}
#[test]
fn test_optical_conductivity_dc() {
let n = 8.49e28_f64; let m_ratio = 1.0;
let tau = 2.5e-14;
let m_star = m_ratio * M_ELECTRON;
let sigma0 = n * E_CHARGE * E_CHARGE * tau / m_star;
let sigma = optical_conductivity(n, m_ratio, tau, 0.0);
assert!(
(sigma - sigma0).abs() / sigma0 < 1e-10,
"DC conductivity mismatch"
);
}
#[test]
fn test_optical_conductivity_decreases_at_high_freq() {
let n = 8.49e28_f64;
let sigma_dc = optical_conductivity(n, 1.0, 2.5e-14, 0.0);
let sigma_hf = optical_conductivity(n, 1.0, 2.5e-14, 1e15);
assert!(
sigma_hf < sigma_dc,
"Conductivity should decrease at high frequency"
);
}
#[test]
fn test_plasma_frequency_positive() {
let wp = plasma_frequency(8.49e28, 1.0, 1.0);
assert!(wp > 0.0, "Plasma frequency should be positive, got {wp}");
}
#[test]
fn test_presets_construction() {
let si = QuantumMaterial::silicon();
let gaas = QuantumMaterial::gaas();
let cu = QuantumMaterial::copper();
assert!(si.band_gap > 0.0);
assert!(gaas.band_gap > 0.0);
assert!(cu.band_gap == 0.0);
}
#[test]
fn test_carrier_concentration_mass_effect() {
let n_light = carrier_concentration(1.12, 0.56, 0.1, 300.0);
let n_heavy = carrier_concentration(1.12, 0.56, 1.0, 300.0);
assert!(
n_heavy > n_light,
"Heavier effective mass should increase N_c and hence n"
);
}
#[test]
fn test_fermi_dirac_monotone() {
let ef = 1.0;
let t = 300.0;
let f1 = fermi_dirac(0.5, ef, t);
let f2 = fermi_dirac(1.0, ef, t);
let f3 = fermi_dirac(1.5, ef, t);
assert!(
f1 > f2 && f2 > f3,
"FD must be decreasing in energy: f1={f1} f2={f2} f3={f3}"
);
}
#[test]
fn test_dos_sqrt_scaling() {
let d1 = density_of_states(1.0, 0.26);
let d4 = density_of_states(4.0, 0.26);
let ratio = d4 / d1;
assert!(
(ratio - 2.0).abs() < 1e-6,
"DOS should scale as sqrt(E): ratio={ratio}"
);
}
#[test]
fn test_optical_conductivity_half_power() {
let n = 1e28_f64;
let tau = 1e-14;
let omega = 1.0 / tau; let sigma = optical_conductivity(n, 1.0, tau, omega);
let sigma0 = optical_conductivity(n, 1.0, tau, 0.0);
assert!(
(sigma - sigma0 / 2.0).abs() / sigma0 < 1e-12,
"At ωτ=1, σ should be σ₀/2"
);
}
#[test]
fn test_seebeck_silicon_reasonable() {
let s = seebeck_coefficient(1.12, 0.56, 300.0, -0.5);
assert!(
s.abs() > 1e-4,
"Silicon Seebeck should be > 100 µV/K, got {s} V/K"
);
}
}