#[derive(Debug, Clone)]
pub struct MaxwellGarnett {
pub eps_host: f64,
pub eps_inclusion: f64,
pub fill_fraction: f64,
}
impl MaxwellGarnett {
pub fn effective_permittivity(&self) -> f64 {
let eh = self.eps_host;
let ei = self.eps_inclusion;
let f = self.fill_fraction.clamp(0.0, 1.0);
let delta = ei - eh;
let denom = ei + 2.0 * eh - f * delta;
if denom.abs() < 1e-30 {
return eh * (1.0 + 3.0 * f * delta.signum() * 1e15);
}
eh * (1.0 + 3.0 * f * delta / denom)
}
pub fn effective_index(&self) -> f64 {
self.effective_permittivity().max(0.0).sqrt()
}
pub fn is_valid(&self) -> bool {
self.fill_fraction < 0.3
}
pub fn clausius_mossotti_factor(&self) -> f64 {
let delta = self.eps_inclusion - self.eps_host;
let denom = self.eps_inclusion + 2.0 * self.eps_host;
if denom.abs() < 1e-30 {
return f64::INFINITY;
}
delta / denom
}
}
#[derive(Debug, Clone)]
pub struct BruggemanEmt {
pub eps1: f64,
pub eps2: f64,
pub f1: f64,
}
impl BruggemanEmt {
fn f2(&self) -> f64 {
(1.0 - self.f1).clamp(0.0, 1.0)
}
fn residual(&self, eps_eff: f64) -> f64 {
let t1 = {
let d = self.eps1 + 2.0 * eps_eff;
if d.abs() < 1e-60 {
0.0
} else {
self.f1 * (self.eps1 - eps_eff) / d
}
};
let t2 = {
let d = self.eps2 + 2.0 * eps_eff;
if d.abs() < 1e-60 {
0.0
} else {
self.f2() * (self.eps2 - eps_eff) / d
}
};
t1 + t2
}
pub fn effective_permittivity(&self) -> f64 {
let f1 = self.f1.clamp(0.0, 1.0);
let f2 = 1.0 - f1;
let e1 = self.eps1;
let e2 = self.eps2;
let b = (3.0 * f1 - 1.0) * e1 + (3.0 * f2 - 1.0) * e2;
let disc = b * b + 8.0 * e1 * e2;
if disc < 0.0 {
return self.effective_permittivity_iterative();
}
let eps_plus = (b + disc.sqrt()) / 4.0;
let eps_minus = (b - disc.sqrt()) / 4.0;
let r_plus = self.residual(eps_plus).abs();
let r_minus = self.residual(eps_minus).abs();
if r_plus <= r_minus {
eps_plus
} else {
eps_minus
}
}
fn effective_permittivity_iterative(&self) -> f64 {
let lo = self.eps1.min(self.eps2);
let hi = self.eps1.max(self.eps2);
let mut a = lo;
let mut b = hi;
let mut mid = (a + b) / 2.0;
for _ in 0..200 {
mid = (a + b) / 2.0;
let r_mid = self.residual(mid);
let r_a = self.residual(a);
if r_mid.abs() < 1e-12 {
break;
}
if r_a * r_mid < 0.0 {
b = mid;
} else {
a = mid;
}
}
mid
}
pub fn effective_index(&self) -> f64 {
self.effective_permittivity().max(0.0).sqrt()
}
pub fn percolation_threshold(&self) -> f64 {
1.0 / 3.0
}
pub fn is_percolated(&self) -> bool {
self.f1 > self.percolation_threshold()
}
}
#[derive(Debug, Clone)]
pub struct MultilayerEmt {
pub eps_a: f64,
pub eps_b: f64,
pub f_a: f64,
}
impl MultilayerEmt {
pub fn eps_parallel(&self) -> f64 {
let f_b = 1.0 - self.f_a;
self.f_a * self.eps_a + f_b * self.eps_b
}
pub fn eps_perpendicular(&self) -> f64 {
let f_b = 1.0 - self.f_a;
let inv_a = if self.eps_a.abs() < 1e-30 {
self.eps_a.signum() * 1e30
} else {
1.0 / self.eps_a
};
let inv_b = if self.eps_b.abs() < 1e-30 {
self.eps_b.signum() * 1e30
} else {
1.0 / self.eps_b
};
let inv = self.f_a * inv_a + f_b * inv_b;
if inv.abs() < 1e-30 {
return f64::INFINITY;
}
1.0 / inv
}
pub fn is_hyperbolic(&self) -> bool {
self.eps_parallel() * self.eps_perpendicular() < 0.0
}
pub fn is_type_i_hyperbolic(&self) -> bool {
self.eps_perpendicular() < 0.0 && self.eps_parallel() > 0.0
}
pub fn is_type_ii_hyperbolic(&self) -> bool {
self.eps_parallel() < 0.0 && self.eps_perpendicular() > 0.0
}
pub fn is_uniaxial_normal(&self) -> bool {
(self.eps_a - self.eps_b).abs() > 1e-10
}
pub fn birefringence(&self) -> f64 {
let n_par = self.eps_parallel().max(0.0).sqrt();
let n_perp = self.eps_perpendicular().max(0.0).sqrt();
(n_par - n_perp).abs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maxwell_garnett_dilute_limit() {
let mg = MaxwellGarnett {
eps_host: 2.25,
eps_inclusion: 12.0,
fill_fraction: 0.001,
};
let eps = mg.effective_permittivity();
assert!(
(eps - 2.25).abs() < 0.05,
"ε_eff should approach ε_host in dilute limit, got {}",
eps
);
}
#[test]
fn maxwell_garnett_full_inclusion() {
let mg = MaxwellGarnett {
eps_host: 1.0,
eps_inclusion: 5.0,
fill_fraction: 0.999,
};
let eps = mg.effective_permittivity();
assert!(
(eps - 5.0).abs() < 1.0,
"ε_eff should approach ε_inclusion at high f, got {}",
eps
);
}
#[test]
fn bruggeman_symmetric() {
let bg = BruggemanEmt {
eps1: 1.0,
eps2: 9.0,
f1: 0.5,
};
let eps = bg.effective_permittivity();
let residual = bg.residual(eps);
assert!(
residual.abs() < 1e-8,
"Bruggeman residual should be ~0, got {}",
residual
);
}
#[test]
fn multilayer_hyperbolic_condition() {
let ml = MultilayerEmt {
eps_a: -10.0,
eps_b: 7.0,
f_a: 0.5,
};
assert!(ml.is_hyperbolic(), "Should be hyperbolic");
assert!(ml.is_type_ii_hyperbolic(), "Should be Type-II hyperbolic");
}
#[test]
fn multilayer_isotropic_same_material() {
let ml = MultilayerEmt {
eps_a: 4.0,
eps_b: 4.0,
f_a: 0.3,
};
assert!((ml.eps_parallel() - 4.0).abs() < 1e-10);
assert!((ml.eps_perpendicular() - 4.0).abs() < 1e-10);
assert!(!ml.is_hyperbolic());
}
#[test]
fn multilayer_type_i_hyperbolic() {
let ml = MultilayerEmt {
eps_a: 10.0,
eps_b: -1.0,
f_a: 0.9,
};
assert!(ml.is_type_i_hyperbolic(), "Should be Type-I hyperbolic");
}
#[test]
fn bruggeman_pure_material_limit() {
let bg = BruggemanEmt {
eps1: 4.0,
eps2: 9.0,
f1: 1.0,
};
let eps = bg.effective_permittivity();
assert!(
(eps - 4.0).abs() < 0.1,
"f1=1 should give ε_eff ≈ ε1, got {}",
eps
);
}
#[test]
fn clausius_mossotti_factor() {
let mg = MaxwellGarnett {
eps_host: 1.0,
eps_inclusion: 4.0,
fill_fraction: 0.1,
};
let cm = mg.clausius_mossotti_factor();
assert!(
(cm - 0.5).abs() < 1e-10,
"CM factor should be 0.5, got {}",
cm
);
}
}