use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct NanopostLibrary {
pub pitch: f64,
pub height: f64,
pub n_post: f64,
pub n_sub: f64,
pub n_sup: f64,
pub wavelength: f64,
pub diameters: Vec<f64>,
pub transmittance: Vec<f64>,
pub phase: Vec<f64>,
}
impl NanopostLibrary {
pub fn new(pitch: f64, height: f64, n_post: f64, n_sub: f64, wavelength: f64) -> Self {
let n_pts = 50;
let d_min = 0.05 * pitch;
let d_max = 0.85 * pitch;
let k0 = 2.0 * PI / wavelength;
let diameters: Vec<f64> = (0..n_pts)
.map(|i| d_min + (d_max - d_min) * i as f64 / (n_pts - 1) as f64)
.collect();
let phase: Vec<f64> = diameters
.iter()
.map(|&d| {
let f = PI * (d / 2.0).powi(2) / (pitch * pitch);
let f = f.clamp(0.0, 0.95);
let n_eff = (f * n_post * n_post + (1.0 - f) * n_sub * n_sub).sqrt();
let phi = k0 * (n_eff - n_sub) * height;
phi.rem_euclid(2.0 * PI)
})
.collect();
let transmittance = vec![0.95; n_pts];
Self {
pitch,
height,
n_post,
n_sub,
n_sup: 1.0,
wavelength,
diameters,
transmittance,
phase,
}
}
pub fn tio2_532nm() -> Self {
Self::new(350e-9, 600e-9, 2.35, 1.46, 532e-9)
}
pub fn gan_405nm() -> Self {
Self::new(280e-9, 500e-9, 2.55, 1.46, 405e-9)
}
pub fn si_1550nm() -> Self {
Self::new(700e-9, 600e-9, 3.48, 1.46, 1550e-9)
}
pub fn phase_range(&self) -> f64 {
let max = self.phase.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let min = self.phase.iter().cloned().fold(f64::INFINITY, f64::min);
max - min
}
pub fn has_full_phase_coverage(&self) -> bool {
self.phase_range() >= 1.8 * PI }
pub fn diameter_for_phase(&self, target_phase: f64) -> Option<f64> {
let target = target_phase.rem_euclid(2.0 * PI);
for i in 0..self.phase.len().saturating_sub(1) {
let p0 = self.phase[i];
let p1 = self.phase[i + 1];
if (p0 <= target && target <= p1) || (p1 <= target && target <= p0) {
let t = if (p1 - p0).abs() < 1e-10 {
0.0
} else {
(target - p0) / (p1 - p0)
};
let d = self.diameters[i] + t * (self.diameters[i + 1] - self.diameters[i]);
return Some(d);
}
}
None
}
pub fn average_transmittance(&self) -> f64 {
self.transmittance.iter().sum::<f64>() / self.transmittance.len() as f64
}
pub fn n_diameters(&self) -> usize {
self.diameters.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nanopost_library_tio2_created() {
let lib = NanopostLibrary::tio2_532nm();
assert!(lib.n_diameters() > 0);
}
#[test]
fn nanopost_phase_range_positive() {
let lib = NanopostLibrary::si_1550nm();
assert!(
lib.phase_range() > 0.0,
"phase range={:.2}",
lib.phase_range()
);
}
#[test]
fn nanopost_diameters_sorted() {
let lib = NanopostLibrary::tio2_532nm();
for i in 1..lib.diameters.len() {
assert!(lib.diameters[i] > lib.diameters[i - 1]);
}
}
#[test]
fn nanopost_average_transmittance_in_range() {
let lib = NanopostLibrary::tio2_532nm();
let t = lib.average_transmittance();
assert!(t > 0.0 && t <= 1.0, "T_avg={t:.3}");
}
#[test]
fn nanopost_phase_in_0_2pi() {
let lib = NanopostLibrary::tio2_532nm();
for &p in &lib.phase {
assert!((0.0..=2.0 * PI + 1e-10).contains(&p), "phase={p:.3}");
}
}
}