use crate::constants::{GAMMA, MU_0};
use crate::error::{self, Result};
#[derive(Debug, Clone)]
pub struct DamonEshbachDetailed {
pub thickness: f64,
pub h_ext: f64,
pub ms: f64,
pub a_ex: f64,
pub alpha: f64,
}
impl DamonEshbachDetailed {
pub fn new(thickness: f64, h_ext: f64, ms: f64, a_ex: f64, alpha: f64) -> Result<Self> {
if thickness <= 0.0 {
return Err(error::invalid_param(
"thickness",
"film thickness must be positive",
));
}
if h_ext < 0.0 {
return Err(error::invalid_param(
"h_ext",
"external field must be non-negative",
));
}
if ms <= 0.0 {
return Err(error::invalid_param(
"ms",
"saturation magnetization must be positive",
));
}
if a_ex <= 0.0 {
return Err(error::invalid_param(
"a_ex",
"exchange stiffness must be positive",
));
}
if alpha < 0.0 {
return Err(error::invalid_param(
"alpha",
"Gilbert damping must be non-negative",
));
}
Ok(Self {
thickness,
h_ext,
ms,
a_ex,
alpha,
})
}
pub fn yig_film_micron() -> Self {
let ms: f64 = 1.4e5;
let a_ex: f64 = 3.5e-12;
let alpha: f64 = 3e-5;
let thickness: f64 = 300e-9;
let h_ext: f64 = 62_460.0;
Self {
thickness,
h_ext,
ms,
a_ex,
alpha,
}
}
pub fn permalloy_thin() -> Self {
Self {
thickness: 20e-9,
h_ext: 0.0,
ms: 8.6e5,
a_ex: 1.3e-11,
alpha: 8e-3,
}
}
pub fn cofeb_strip() -> Self {
Self {
thickness: 3e-9,
h_ext: 0.0,
ms: 1.2e6,
a_ex: 1.5e-11,
alpha: 5e-3,
}
}
#[inline]
pub fn omega_h(&self) -> f64 {
GAMMA * MU_0 * self.h_ext
}
#[inline]
pub fn omega_m(&self) -> f64 {
GAMMA * MU_0 * self.ms
}
#[inline]
fn exchange_len_sq(&self) -> f64 {
2.0 * self.a_ex / (MU_0 * self.ms * self.ms)
}
#[inline]
fn f_kd(&self, k: f64) -> f64 {
let kd = k.abs() * self.thickness;
if kd < 1e-9 {
0.5 * kd
} else {
1.0 - (1.0 - (-kd).exp()) / kd
}
}
pub fn dispersion_omega(&self, k: f64, phi: f64) -> f64 {
let omega_h = self.omega_h();
let omega_m = self.omega_m();
let lambda_ex = self.exchange_len_sq();
let f_kd = self.f_kd(k);
let sin2_phi = phi.sin().powi(2);
let exch = omega_m * lambda_ex * k * k;
let term1 = omega_h + exch;
let term2 = omega_h + exch + omega_m * f_kd * sin2_phi + omega_m * (1.0 - f_kd);
let omega_sq = term1 * term2;
if omega_sq < 0.0 {
0.0
} else {
omega_sq.sqrt()
}
}
pub fn nonreciprocity(&self, k: f64) -> f64 {
use std::f64::consts::PI;
let omega_m = self.omega_m();
let kd = k.abs() * self.thickness;
let omega_pi2 = self.dispersion_omega(k, PI / 2.0);
let omega_0 = self.dispersion_omega(k, 0.0);
let angular_diff = omega_pi2 - omega_0;
let de_asymm = omega_m * 0.5 * (1.0 - (-2.0 * kd).exp());
if angular_diff.abs() > de_asymm.abs() {
angular_diff
} else {
de_asymm
}
}
pub fn surface_localization_length(&self, k: f64) -> f64 {
if k.abs() < 1e-10 {
self.thickness } else {
1.0 / k.abs()
}
}
pub fn group_velocity(&self, k: f64, phi: f64) -> f64 {
let dk = if k.abs() > 1.0 {
k.abs() * 1e-6
} else {
1.0 };
let k_plus = k + dk;
let k_minus = (k - dk).max(0.0);
let omega_plus = self.dispersion_omega(k_plus, phi);
let omega_minus = self.dispersion_omega(k_minus, phi);
let effective_dk = if k - dk < 0.0 {
k + dk } else {
2.0 * dk };
(omega_plus - omega_minus) / effective_dk
}
pub fn propagation_length(&self, k: f64) -> f64 {
use std::f64::consts::FRAC_PI_2;
let omega = self.dispersion_omega(k, FRAC_PI_2);
if omega <= 0.0 || self.alpha <= 0.0 {
return 0.0;
}
let vg = self.group_velocity(k, FRAC_PI_2).abs();
vg / (self.alpha * omega)
}
}
#[cfg(test)]
mod tests {
use std::f64::consts::{FRAC_PI_2, PI};
use super::*;
const TOL_REL: f64 = 0.05; const TOL_ABS: f64 = 1e6;
fn yig() -> DamonEshbachDetailed {
DamonEshbachDetailed::yig_film_micron()
}
fn permalloy() -> DamonEshbachDetailed {
DamonEshbachDetailed::permalloy_thin()
}
#[test]
fn test_yig_preset_valid() {
let de = yig();
assert!(de.thickness > 0.0);
assert!(de.ms > 0.0);
assert!(de.a_ex > 0.0);
assert!(de.alpha > 0.0);
}
#[test]
fn test_yig_fmr_near_5ghz() {
let de = yig();
let omega_h = de.omega_h();
let omega_m = de.omega_m();
let omega_fmr = (omega_h * (omega_h + omega_m)).sqrt();
let f_fmr_ghz = omega_fmr / (2.0 * PI * 1e9);
assert!(
(f_fmr_ghz - 5.0).abs() < 1.5,
"YIG FMR should be near 5 GHz, got {f_fmr_ghz:.2} GHz"
);
}
#[test]
fn test_permalloy_preset_valid() {
let de = permalloy();
assert!(de.thickness > 0.0);
assert!(de.ms > 8.0e5, "Py Ms should be ~8.6e5 A/m");
assert!(de.alpha > 1e-3, "Py damping should be ~8e-3");
}
#[test]
fn test_cofeb_preset_valid() {
let de = DamonEshbachDetailed::cofeb_strip();
assert!(de.thickness > 0.0);
assert!((de.ms - 1.2e6).abs() < 1e4);
}
#[test]
fn test_new_invalid_thickness() {
let result = DamonEshbachDetailed::new(0.0, 0.0, 1e5, 1e-12, 1e-4);
assert!(result.is_err());
let result = DamonEshbachDetailed::new(-1e-9, 0.0, 1e5, 1e-12, 1e-4);
assert!(result.is_err());
}
#[test]
fn test_new_invalid_ms() {
let result = DamonEshbachDetailed::new(100e-9, 0.0, 0.0, 1e-12, 1e-4);
assert!(result.is_err());
let result = DamonEshbachDetailed::new(100e-9, 0.0, -1.0, 1e-12, 1e-4);
assert!(result.is_err());
}
#[test]
fn test_new_invalid_a_ex() {
let result = DamonEshbachDetailed::new(100e-9, 0.0, 1e5, 0.0, 1e-4);
assert!(result.is_err());
let result = DamonEshbachDetailed::new(100e-9, 0.0, 1e5, -1e-12, 1e-4);
assert!(result.is_err());
}
#[test]
fn test_new_invalid_h_ext() {
let result = DamonEshbachDetailed::new(100e-9, -100.0, 1e5, 1e-12, 1e-4);
assert!(result.is_err());
}
#[test]
fn test_new_valid() {
let result = DamonEshbachDetailed::new(100e-9, 0.0, 1e5, 1e-12, 1e-4);
assert!(result.is_ok());
}
#[test]
fn test_dispersion_positive_frequency() {
let de = yig();
for &k in &[1e4_f64, 1e5, 1e6, 1e7, 1e8] {
let omega = de.dispersion_omega(k, FRAC_PI_2);
assert!(
omega > 0.0,
"omega must be positive at k={k:.2e}: got {omega}"
);
}
}
#[test]
fn test_de_phi_pi2_greater_than_phi_0() {
let de = yig();
let k = 1e6;
let omega_de = de.dispersion_omega(k, FRAC_PI_2);
let omega_bv = de.dispersion_omega(k, 0.0);
assert!(
omega_de > omega_bv,
"DE geometry (φ=π/2) should give higher freq than BV (φ=0): {omega_de:.4e} vs {omega_bv:.4e}"
);
}
#[test]
fn test_group_velocity_de_positive() {
let de = yig();
let vg = de.group_velocity(1e6, FRAC_PI_2);
assert!(
vg > 0.0,
"DE mode group velocity should be positive: {vg:.4e}"
);
}
#[test]
fn test_surface_localization_length() {
let de = yig();
let k = 1e7;
let l = de.surface_localization_length(k);
assert!(
(l - 1.0 / k).abs() < 1e-15,
"localization length should be 1/k: {l}"
);
}
#[test]
fn test_propagation_length_yig_micron_scale() {
let de = yig();
let k = 1e6;
let l = de.propagation_length(k);
assert!(
l > 1e-6, "YIG propagation length should be > 1 µm: {l:.4e} m"
);
assert!(
l < 1.0, "YIG propagation length should be < 1 m: {l:.4e} m"
);
}
#[test]
fn test_omega_h_omega_m() {
let de = yig();
let omega_h = de.omega_h();
let omega_m = de.omega_m();
assert!(omega_h >= 0.0, "omega_H must be non-negative");
assert!(omega_m > 0.0, "omega_M must be positive");
let expected_omega_m = GAMMA * MU_0 * de.ms;
assert!(
(omega_m - expected_omega_m).abs() < TOL_ABS,
"omega_M mismatch: {omega_m:.4e} vs {expected_omega_m:.4e}"
);
}
#[test]
fn test_nonreciprocity_positive() {
let de = yig();
let delta_omega = de.nonreciprocity(1e6);
assert!(
delta_omega > 0.0,
"Non-reciprocity Δω should be positive: {delta_omega:.4e}"
);
}
#[test]
fn test_nonreciprocity_increases_with_k() {
let de = yig();
let dnr_low = de.nonreciprocity(1e5);
let dnr_high = de.nonreciprocity(1e7);
assert!(
dnr_high > dnr_low,
"Non-reciprocity should increase with k: low={dnr_low:.4e}, high={dnr_high:.4e}"
);
}
#[test]
fn test_dispersion_increases_with_k_exchange_regime() {
let de = yig();
let k1 = 1e8;
let k2 = 2e8;
let omega1 = de.dispersion_omega(k1, FRAC_PI_2);
let omega2 = de.dispersion_omega(k2, FRAC_PI_2);
assert!(
omega2 > omega1,
"In exchange regime, ω should increase with k: ω(k1)={omega1:.4e}, ω(k2)={omega2:.4e}"
);
}
#[test]
fn test_dispersion_k_zero_limit() {
let de = yig();
let omega_ks = de.dispersion_omega(1e-3, FRAC_PI_2);
let omega_h = de.omega_h();
let omega_m = de.omega_m();
let omega_kittel = (omega_h * (omega_h + omega_m)).sqrt();
let rel_err = (omega_ks - omega_kittel).abs() / omega_kittel.max(1.0);
assert!(
rel_err < TOL_REL,
"At small k, ω should approach Kittel: K-S={omega_ks:.4e}, Kittel={omega_kittel:.4e}, rel_err={rel_err:.4}"
);
}
}