1#![allow(dead_code)]
11#![allow(clippy::too_many_arguments)]
12
13use std::f64::consts::PI;
14
15pub const K_B_EV: f64 = 8.617_333_262e-5;
17pub const K_B_J: f64 = 1.380_649e-23;
19pub const E_CHARGE: f64 = 1.602_176_634e-19;
21pub const M_ELECTRON: f64 = 9.109_383_701_5e-31;
23pub const HBAR: f64 = 1.054_571_817e-34;
25
26#[derive(Debug, Clone)]
34pub struct QuantumMaterial {
35 pub band_gap: f64,
37 pub fermi_energy: f64,
39 pub effective_mass: f64,
41 pub mobility_300k: f64,
43 pub scattering_time: f64,
45 pub name: String,
47}
48
49impl QuantumMaterial {
50 pub fn new(
60 band_gap: f64,
61 fermi_energy: f64,
62 effective_mass: f64,
63 mobility_300k: f64,
64 scattering_time: f64,
65 name: impl Into<String>,
66 ) -> Self {
67 Self {
68 band_gap,
69 fermi_energy,
70 effective_mass,
71 mobility_300k,
72 scattering_time,
73 name: name.into(),
74 }
75 }
76
77 pub fn silicon() -> Self {
79 Self::new(1.12, 0.56, 0.26, 0.135, 3.0e-13, "Silicon")
80 }
81
82 pub fn gaas() -> Self {
84 Self::new(1.42, 0.71, 0.063, 0.85, 1.0e-13, "GaAs")
85 }
86
87 pub fn copper() -> Self {
89 Self::new(0.0, 7.04, 1.0, 3.2e-3, 2.5e-14, "Copper")
90 }
91}
92
93pub fn fermi_dirac(energy: f64, fermi_energy: f64, temp_k: f64) -> f64 {
104 if temp_k <= 0.0 {
105 if energy < fermi_energy {
106 1.0
107 } else if (energy - fermi_energy).abs() < 1e-12 {
108 0.5
109 } else {
110 0.0
111 }
112 } else {
113 let kbt = K_B_EV * temp_k;
114 let x = (energy - fermi_energy) / kbt;
115 if x > 500.0 {
117 0.0
118 } else if x < -500.0 {
119 1.0
120 } else {
121 1.0 / (1.0 + x.exp())
122 }
123 }
124}
125
126pub fn density_of_states(energy: f64, effective_mass_ratio: f64) -> f64 {
136 if energy <= 0.0 {
137 return 0.0;
138 }
139 let m_star = effective_mass_ratio * M_ELECTRON;
140 let energy_j = energy * E_CHARGE; let prefactor = (1.0 / (2.0 * PI * PI)) * (2.0 * m_star / (HBAR * HBAR)).powf(1.5);
143 let dos_per_j = prefactor * energy_j.sqrt();
144 dos_per_j * E_CHARGE
146}
147
148pub fn carrier_concentration(
161 band_gap: f64,
162 fermi_energy: f64,
163 effective_mass_ratio: f64,
164 temp_k: f64,
165) -> f64 {
166 if temp_k <= 0.0 {
167 return 0.0;
168 }
169 let m_star = effective_mass_ratio * M_ELECTRON;
170 let kbt_j = K_B_J * temp_k;
171 let kbt_ev = K_B_EV * temp_k;
172 let h = 2.0 * PI * HBAR;
174 let n_c = 2.0 * (2.0 * PI * m_star * kbt_j / (h * h)).powf(1.5);
175 let e_c = band_gap;
177 let delta = (e_c - fermi_energy) / kbt_ev;
179 if delta > 500.0 {
180 return 0.0;
181 }
182 n_c * (-delta).exp()
183}
184
185pub fn mobility_temperature(mobility_300k: f64, temp_k: f64, alpha: f64) -> f64 {
201 if temp_k <= 0.0 {
202 return 0.0;
203 }
204 mobility_300k * (300.0 / temp_k).powf(alpha)
205}
206
207pub fn seebeck_coefficient(band_gap: f64, fermi_energy: f64, temp_k: f64, r_scatter: f64) -> f64 {
219 if temp_k <= 0.0 {
220 return 0.0;
221 }
222 let kbt_ev = K_B_EV * temp_k;
223 let e_c = band_gap;
224 let eta = (e_c - fermi_energy) / kbt_ev; -(K_B_EV / 1.0) * (eta + r_scatter + 2.5) }
229
230pub fn hall_coefficient(carrier_density: f64, hall_factor: f64) -> f64 {
243 if carrier_density <= 0.0 {
244 return 0.0;
245 }
246 -hall_factor / (carrier_density * E_CHARGE)
247}
248
249pub fn optical_conductivity(
267 carrier_density: f64,
268 effective_mass_ratio: f64,
269 scattering_time: f64,
270 omega: f64,
271) -> f64 {
272 let m_star = effective_mass_ratio * M_ELECTRON;
273 let sigma0 = carrier_density * E_CHARGE * E_CHARGE * scattering_time / m_star;
274 let omega_tau = omega * scattering_time;
275 sigma0 / (1.0 + omega_tau * omega_tau)
276}
277
278pub fn plasma_frequency(carrier_density: f64, effective_mass_ratio: f64, epsilon_r: f64) -> f64 {
287 let epsilon_0 = 8.854_187_817e-12_f64; let m_star = effective_mass_ratio * M_ELECTRON;
289 (carrier_density * E_CHARGE * E_CHARGE / (epsilon_0 * epsilon_r * m_star)).sqrt()
290}
291
292#[cfg(test)]
297mod tests {
298 use super::*;
299
300 const EPS: f64 = 1e-10;
301
302 #[test]
304 fn test_fermi_dirac_t0_below() {
305 let f = fermi_dirac(0.5, 1.0, 0.0);
306 assert!(
307 (f - 1.0).abs() < EPS,
308 "FD at T=0, E<E_F should be 1, got {f}"
309 );
310 }
311
312 #[test]
314 fn test_fermi_dirac_t0_above() {
315 let f = fermi_dirac(1.5, 1.0, 0.0);
316 assert!(f.abs() < EPS, "FD at T=0, E>E_F should be 0, got {f}");
317 }
318
319 #[test]
321 fn test_fermi_dirac_t0_at_ef() {
322 let f = fermi_dirac(1.0, 1.0, 0.0);
323 assert!(
324 (f - 0.5).abs() < EPS,
325 "FD at T=0, E=E_F should be 0.5, got {f}"
326 );
327 }
328
329 #[test]
331 fn test_fermi_dirac_at_fermi_level_finite_t() {
332 let f = fermi_dirac(1.0, 1.0, 300.0);
333 assert!(
334 (f - 0.5).abs() < EPS,
335 "FD at E=E_F for any T should be 0.5, got {f}"
336 );
337 }
338
339 #[test]
341 fn test_fermi_dirac_thermal_tail() {
342 let kbt = K_B_EV * 1000.0;
343 let ef = 1.0;
344 let e = ef + 5.0 * kbt;
345 let f = fermi_dirac(e, ef, 1000.0);
346 let expected = 1.0 / (1.0 + 5.0_f64.exp());
347 assert!(
348 (f - expected).abs() < 1e-12,
349 "FD thermal tail mismatch: {f} vs {expected}"
350 );
351 }
352
353 #[test]
355 fn test_fermi_dirac_large_negative() {
356 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}");
358 }
359
360 #[test]
362 fn test_dos_zero_at_band_edge() {
363 let d = density_of_states(0.0, 0.26);
364 assert!(d.abs() < EPS, "DOS at band edge should be 0, got {d}");
365 }
366
367 #[test]
369 fn test_dos_increases_with_energy() {
370 let d1 = density_of_states(0.1, 0.26);
371 let d2 = density_of_states(0.5, 0.26);
372 assert!(
373 d2 > d1,
374 "DOS should increase with energy in parabolic band, d1={d1}, d2={d2}"
375 );
376 }
377
378 #[test]
380 fn test_dos_scales_with_mass() {
381 let d_light = density_of_states(0.3, 0.1);
382 let d_heavy = density_of_states(0.3, 1.0);
383 assert!(
384 d_heavy > d_light,
385 "Heavier effective mass should give larger DOS"
386 );
387 }
388
389 #[test]
391 fn test_carrier_concentration_increases_with_temp() {
392 let n_low = carrier_concentration(1.12, 0.56, 0.26, 200.0);
393 let n_high = carrier_concentration(1.12, 0.56, 0.26, 400.0);
394 assert!(
395 n_high > n_low,
396 "Carrier conc should increase with T: n_low={n_low}, n_high={n_high}"
397 );
398 }
399
400 #[test]
402 fn test_carrier_concentration_t0() {
403 let n = carrier_concentration(1.12, 0.56, 0.26, 0.0);
404 assert!(n.abs() < EPS, "Carrier conc at T=0 should be 0, got {n}");
405 }
406
407 #[test]
409 fn test_carrier_concentration_band_gap_effect() {
410 let n_small_gap = carrier_concentration(0.5, 0.25, 0.26, 300.0);
411 let n_large_gap = carrier_concentration(2.0, 1.0, 0.26, 300.0);
412 assert!(
413 n_small_gap > n_large_gap,
414 "Smaller band gap should yield higher carrier density"
415 );
416 }
417
418 #[test]
420 fn test_mobility_decreases_with_temp() {
421 let mu_low = mobility_temperature(0.135, 200.0, 1.5);
422 let mu_high = mobility_temperature(0.135, 400.0, 1.5);
423 assert!(
424 mu_high < mu_low,
425 "Mobility should decrease with temperature"
426 );
427 }
428
429 #[test]
431 fn test_mobility_at_300k() {
432 let mu = mobility_temperature(0.135, 300.0, 1.5);
433 assert!(
434 (mu - 0.135).abs() < 1e-12,
435 "Mobility at 300K should be 0.135, got {mu}"
436 );
437 }
438
439 #[test]
441 fn test_mobility_t0() {
442 let mu = mobility_temperature(0.135, 0.0, 1.5);
443 assert!(mu.abs() < EPS, "Mobility at T=0 should be 0, got {mu}");
444 }
445
446 #[test]
448 fn test_seebeck_nonzero() {
449 let s = seebeck_coefficient(1.12, 0.56, 300.0, -0.5);
450 assert!(
451 s.abs() > 1e-6,
452 "Seebeck coefficient should be non-zero, got {s}"
453 );
454 }
455
456 #[test]
458 fn test_seebeck_t0() {
459 let s = seebeck_coefficient(1.12, 0.56, 0.0, -0.5);
460 assert!(s.abs() < EPS, "Seebeck at T=0 should be 0, got {s}");
461 }
462
463 #[test]
465 fn test_seebeck_n_type_negative() {
466 let s = seebeck_coefficient(1.12, 0.56, 300.0, -0.5);
468 assert!(s < 0.0, "n-type Seebeck should be negative, got {s}");
469 }
470
471 #[test]
473 fn test_hall_coefficient_negative_for_electrons() {
474 let rh = hall_coefficient(1e22, 1.0);
475 assert!(
476 rh < 0.0,
477 "Hall coefficient for n-type should be negative, got {rh}"
478 );
479 }
480
481 #[test]
483 fn test_hall_coefficient_magnitude() {
484 let n = 1e22_f64;
485 let rh = hall_coefficient(n, 1.0);
486 let expected = -1.0 / (n * E_CHARGE);
487 assert!(
488 (rh - expected).abs() < EPS.abs(),
489 "Hall coefficient mismatch: {rh} vs {expected}"
490 );
491 }
492
493 #[test]
495 fn test_hall_coefficient_zero_density() {
496 let rh = hall_coefficient(0.0, 1.0);
497 assert!(
498 rh.abs() < EPS,
499 "Hall coefficient for zero density should be 0, got {rh}"
500 );
501 }
502
503 #[test]
505 fn test_optical_conductivity_dc() {
506 let n = 8.49e28_f64; let m_ratio = 1.0;
508 let tau = 2.5e-14;
509 let m_star = m_ratio * M_ELECTRON;
510 let sigma0 = n * E_CHARGE * E_CHARGE * tau / m_star;
511 let sigma = optical_conductivity(n, m_ratio, tau, 0.0);
512 assert!(
513 (sigma - sigma0).abs() / sigma0 < 1e-10,
514 "DC conductivity mismatch"
515 );
516 }
517
518 #[test]
520 fn test_optical_conductivity_decreases_at_high_freq() {
521 let n = 8.49e28_f64;
522 let sigma_dc = optical_conductivity(n, 1.0, 2.5e-14, 0.0);
523 let sigma_hf = optical_conductivity(n, 1.0, 2.5e-14, 1e15);
524 assert!(
525 sigma_hf < sigma_dc,
526 "Conductivity should decrease at high frequency"
527 );
528 }
529
530 #[test]
532 fn test_plasma_frequency_positive() {
533 let wp = plasma_frequency(8.49e28, 1.0, 1.0);
534 assert!(wp > 0.0, "Plasma frequency should be positive, got {wp}");
535 }
536
537 #[test]
539 fn test_presets_construction() {
540 let si = QuantumMaterial::silicon();
541 let gaas = QuantumMaterial::gaas();
542 let cu = QuantumMaterial::copper();
543 assert!(si.band_gap > 0.0);
544 assert!(gaas.band_gap > 0.0);
545 assert!(cu.band_gap == 0.0);
546 }
547
548 #[test]
550 fn test_carrier_concentration_mass_effect() {
551 let n_light = carrier_concentration(1.12, 0.56, 0.1, 300.0);
552 let n_heavy = carrier_concentration(1.12, 0.56, 1.0, 300.0);
553 assert!(
554 n_heavy > n_light,
555 "Heavier effective mass should increase N_c and hence n"
556 );
557 }
558
559 #[test]
561 fn test_fermi_dirac_monotone() {
562 let ef = 1.0;
563 let t = 300.0;
564 let f1 = fermi_dirac(0.5, ef, t);
565 let f2 = fermi_dirac(1.0, ef, t);
566 let f3 = fermi_dirac(1.5, ef, t);
567 assert!(
568 f1 > f2 && f2 > f3,
569 "FD must be decreasing in energy: f1={f1} f2={f2} f3={f3}"
570 );
571 }
572
573 #[test]
575 fn test_dos_sqrt_scaling() {
576 let d1 = density_of_states(1.0, 0.26);
577 let d4 = density_of_states(4.0, 0.26);
578 let ratio = d4 / d1;
579 assert!(
581 (ratio - 2.0).abs() < 1e-6,
582 "DOS should scale as sqrt(E): ratio={ratio}"
583 );
584 }
585
586 #[test]
588 fn test_optical_conductivity_half_power() {
589 let n = 1e28_f64;
590 let tau = 1e-14;
591 let omega = 1.0 / tau; let sigma = optical_conductivity(n, 1.0, tau, omega);
593 let sigma0 = optical_conductivity(n, 1.0, tau, 0.0);
594 assert!(
595 (sigma - sigma0 / 2.0).abs() / sigma0 < 1e-12,
596 "At ωτ=1, σ should be σ₀/2"
597 );
598 }
599
600 #[test]
602 fn test_seebeck_silicon_reasonable() {
603 let s = seebeck_coefficient(1.12, 0.56, 300.0, -0.5);
605 assert!(
606 s.abs() > 1e-4,
607 "Silicon Seebeck should be > 100 µV/K, got {s} V/K"
608 );
609 }
610}