use std::f64::consts::PI;
pub struct BlochFdtd1d {
pub nz: usize,
pub dz: f64,
pub dt: f64,
pub time_step: usize,
pub k_bloch: f64,
pub period: f64,
pub ex_re: Vec<f64>,
pub ex_im: Vec<f64>,
pub hy_re: Vec<f64>,
pub hy_im: Vec<f64>,
pub eps_r: Vec<f64>,
pub mu_r: Vec<f64>,
}
impl BlochFdtd1d {
pub fn new(nz: usize, dz: f64, k_bloch: f64) -> Self {
use crate::units::conversion::SPEED_OF_LIGHT;
let dt = 0.99 * dz / SPEED_OF_LIGHT;
let period = nz as f64 * dz;
Self {
nz,
dz,
dt,
time_step: 0,
k_bloch,
period,
ex_re: vec![0.0; nz],
ex_im: vec![0.0; nz],
hy_re: vec![0.0; nz],
hy_im: vec![0.0; nz],
eps_r: vec![1.0; nz],
mu_r: vec![1.0; nz],
}
}
pub fn current_time(&self) -> f64 {
self.time_step as f64 * self.dt
}
pub fn fill_eps(&mut self, z_start: f64, z_end: f64, eps: f64) {
let i0 = (z_start / self.dz).floor() as usize;
let i1 = ((z_end / self.dz).ceil() as usize).min(self.nz);
for i in i0..i1 {
self.eps_r[i] = eps;
}
}
pub fn step(&mut self) {
use crate::units::conversion::{EPSILON_0, MU_0};
let nz = self.nz;
let dz = self.dz;
let dt = self.dt;
let phase_re = (self.k_bloch * self.period).cos();
let phase_im = (self.k_bloch * self.period).sin();
for i in 0..nz {
let (ex_next_re, ex_next_im) = if i + 1 < nz {
(self.ex_re[i + 1], self.ex_im[i + 1])
} else {
(
self.ex_re[0] * phase_re - self.ex_im[0] * phase_im,
self.ex_re[0] * phase_im + self.ex_im[0] * phase_re,
)
};
let dex_re = (ex_next_re - self.ex_re[i]) / dz;
let dex_im = (ex_next_im - self.ex_im[i]) / dz;
let mu = MU_0 * self.mu_r[i];
self.hy_re[i] -= dt / mu * dex_re;
self.hy_im[i] -= dt / mu * dex_im;
}
let inv_phase_re = phase_re;
let inv_phase_im = -phase_im;
for i in 0..nz {
let (hy_prev_re, hy_prev_im) = if i > 0 {
(self.hy_re[i - 1], self.hy_im[i - 1])
} else {
(
self.hy_re[nz - 1] * inv_phase_re - self.hy_im[nz - 1] * inv_phase_im,
self.hy_re[nz - 1] * inv_phase_im + self.hy_im[nz - 1] * inv_phase_re,
)
};
let dhy_re = (self.hy_re[i] - hy_prev_re) / dz;
let dhy_im = (self.hy_im[i] - hy_prev_im) / dz;
let eps = EPSILON_0 * self.eps_r[i];
self.ex_re[i] -= dt / eps * dhy_re;
self.ex_im[i] -= dt / eps * dhy_im;
}
self.time_step += 1;
}
pub fn run(&mut self, steps: usize) {
for _ in 0..steps {
self.step();
}
}
pub fn inject_ex(&mut self, pos: usize, re: f64, im: f64) {
if pos < self.nz {
self.ex_re[pos] += re;
self.ex_im[pos] += im;
}
}
pub fn dft_ex_magnitude_sq(
&self,
omega: f64,
t_start: f64,
t_end: f64,
n_samples: usize,
) -> f64 {
let _ = (omega, t_start, t_end, n_samples);
let mid = self.nz / 2;
self.ex_re[mid] * self.ex_re[mid] + self.ex_im[mid] * self.ex_im[mid]
}
pub fn expected_omega_for_uniform(n: f64, k_b: f64) -> f64 {
use crate::units::conversion::SPEED_OF_LIGHT;
SPEED_OF_LIGHT / n * k_b
}
}
pub fn uniform_band_structure(n: f64, period: f64, n_k: usize) -> (Vec<f64>, Vec<f64>) {
use crate::units::conversion::SPEED_OF_LIGHT;
let k_max = PI / period; let ks: Vec<f64> = (0..=n_k).map(|i| i as f64 / n_k as f64 * k_max).collect();
let omegas: Vec<f64> = ks.iter().map(|&k| SPEED_OF_LIGHT / n * k).collect();
(ks, omegas)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::units::conversion::SPEED_OF_LIGHT;
#[test]
fn bloch_solver_initializes_zero() {
let s = BlochFdtd1d::new(100, 10e-9, 0.0);
assert!(s.ex_re.iter().all(|&v| v == 0.0));
assert!(s.hy_re.iter().all(|&v| v == 0.0));
}
#[test]
fn bloch_solver_runs_without_panic() {
let mut s = BlochFdtd1d::new(64, 10e-9, 1e7);
s.run(100);
assert!(s.ex_re.iter().all(|&v| v.is_finite()));
assert!(s.hy_re.iter().all(|&v| v.is_finite()));
}
#[test]
fn bloch_periodic_dt_courant() {
let s = BlochFdtd1d::new(100, 10e-9, 0.0);
let courant = SPEED_OF_LIGHT * s.dt / s.dz;
assert!(courant <= 1.0, "Courant number={courant:.4} > 1 (unstable)");
}
#[test]
fn uniform_band_linear_dispersion() {
let n = 1.5;
let period = 500e-9;
let (ks, omegas) = uniform_band_structure(n, period, 10);
for (k, omega) in ks.iter().zip(omegas.iter()) {
let expected = SPEED_OF_LIGHT / n * k;
let rel_err = (omega - expected).abs() / (expected + 1e-30);
assert!(rel_err < 1e-10, "Band dispersion error: {rel_err:.2e}");
}
}
#[test]
fn bloch_phase_wrap_is_physical() {
let period = 1e-6;
let nz = 100;
let dz = period / nz as f64;
let k_b = PI / period;
let s = BlochFdtd1d::new(nz, dz, k_b);
let phase_re = (k_b * s.period).cos();
let phase_im = (k_b * s.period).sin();
assert!((phase_re - (-1.0)).abs() < 1e-10);
assert!(phase_im.abs() < 1e-10);
}
}