use crate::constants::{GAMMA, MU_0};
use crate::error::{self, Result};
use crate::material::Ferromagnet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpinWaveMode {
DamonEshbach,
BackwardVolume,
ForwardVolume,
}
impl std::fmt::Display for SpinWaveMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SpinWaveMode::DamonEshbach => write!(f, "Damon-Eshbach (DE) surface mode"),
SpinWaveMode::BackwardVolume => write!(f, "Backward Volume MSW (BVMSW)"),
SpinWaveMode::ForwardVolume => write!(f, "Forward Volume MSW (FVMSW)"),
}
}
}
#[derive(Debug, Clone)]
pub struct SpinWaveModeCalculator {
ms: f64,
exchange_a: f64,
thickness: f64,
omega_m_per_field: f64,
}
impl SpinWaveModeCalculator {
pub fn new(material: &Ferromagnet, thickness: f64) -> Result<Self> {
if material.ms <= 0.0 {
return Err(error::invalid_param(
"ms",
"saturation magnetization must be positive",
));
}
if thickness <= 0.0 {
return Err(error::invalid_param(
"thickness",
"film thickness must be positive",
));
}
Ok(Self {
ms: material.ms,
exchange_a: material.exchange_a,
thickness,
omega_m_per_field: GAMMA * MU_0 * material.ms,
})
}
pub fn frequency(&self, mode: SpinWaveMode, h_ext: f64, k: f64) -> Result<f64> {
match mode {
SpinWaveMode::DamonEshbach => self.damon_eshbach_frequency(h_ext, k),
SpinWaveMode::BackwardVolume => self.bvmsw_frequency(h_ext, k),
SpinWaveMode::ForwardVolume => self.fvmsw_frequency(h_ext, k),
}
}
pub fn damon_eshbach_frequency(&self, h_ext: f64, k: f64) -> Result<f64> {
if h_ext < 0.0 {
return Err(error::invalid_param(
"h_ext",
"external field must be non-negative",
));
}
let omega_h = GAMMA * h_ext;
let omega_m = self.omega_m_per_field;
let d = self.thickness;
let lambda = 2.0 * self.exchange_a / (MU_0 * self.ms);
let exchange_term = omega_m * lambda * k * k;
let kd = k.abs() * d;
let dipolar_factor = 1.0 - (-2.0 * kd).exp();
let omega_sq = (omega_h + exchange_term) * (omega_h + exchange_term + omega_m)
+ omega_m * omega_m / 4.0 * dipolar_factor;
if omega_sq < 0.0 {
return Err(error::numerical_error(
"negative frequency squared in DE mode",
));
}
Ok(omega_sq.sqrt())
}
pub fn bvmsw_frequency(&self, h_ext: f64, k: f64) -> Result<f64> {
if h_ext < 0.0 {
return Err(error::invalid_param(
"h_ext",
"external field must be non-negative",
));
}
let omega_h = GAMMA * h_ext;
let omega_m = self.omega_m_per_field;
let d = self.thickness;
let lambda = 2.0 * self.exchange_a / (MU_0 * self.ms);
let kd = k.abs() * d;
let p = if kd < 1e-12 {
1.0 - kd / 2.0
} else {
(1.0 - (-kd).exp()) / kd
};
let exchange_term = omega_m * lambda * k * k;
let omega_sq = (omega_h + exchange_term) * (omega_h + exchange_term + omega_m * (1.0 - p));
if omega_sq < 0.0 {
return Err(error::numerical_error(
"negative frequency squared in BVMSW mode",
));
}
Ok(omega_sq.sqrt())
}
pub fn fvmsw_frequency(&self, h_ext: f64, k: f64) -> Result<f64> {
if h_ext < 0.0 {
return Err(error::invalid_param(
"h_ext",
"external field must be non-negative",
));
}
let omega_h = GAMMA * h_ext;
let omega_m = self.omega_m_per_field;
let d = self.thickness;
let lambda = 2.0 * self.exchange_a / (MU_0 * self.ms);
let kd = k.abs() * d;
let p = if kd < 1e-12 {
1.0 - kd / 2.0
} else {
(1.0 - (-kd).exp()) / kd
};
let exchange_term = omega_m * lambda * k * k;
let term1 = omega_h - omega_m + exchange_term + omega_m * p;
let term2 = omega_h - omega_m + exchange_term + omega_m * (1.0 - p);
let omega_sq = term1 * term2;
if omega_sq < 0.0 {
return Err(error::numerical_error(
"negative frequency squared in FVMSW mode; \
ensure H_ext > mu_0*Ms for out-of-plane saturation",
));
}
Ok(omega_sq.sqrt())
}
pub fn mode_profile(
&self,
mode: SpinWaveMode,
k: f64,
n_points: usize,
) -> Result<Vec<(f64, f64)>> {
if n_points < 2 {
return Err(error::invalid_param(
"n_points",
"need at least 2 points for mode profile",
));
}
let d = self.thickness;
let dz = d / (n_points - 1) as f64;
let mut profile = Vec::with_capacity(n_points);
for i in 0..n_points {
let z = i as f64 * dz;
let amplitude = match mode {
SpinWaveMode::DamonEshbach => {
let kd = k.abs() * d;
if kd < 1e-12 {
1.0 } else {
let decay = (-k.abs() * z).exp();
let growth = (-k.abs() * (d - z)).exp();
(decay + growth) / (1.0 + (-kd).exp())
}
},
SpinWaveMode::BackwardVolume | SpinWaveMode::ForwardVolume => {
1.0
},
};
profile.push((z, amplitude));
}
Ok(profile)
}
pub fn compare_modes(&self, h_ext: f64, k: f64) -> Result<Vec<(SpinWaveMode, f64)>> {
let modes = [
SpinWaveMode::DamonEshbach,
SpinWaveMode::BackwardVolume,
SpinWaveMode::ForwardVolume,
];
let mut results = Vec::new();
for mode in &modes {
match self.frequency(*mode, h_ext, k) {
Ok(omega) => results.push((*mode, omega)),
Err(_) => {
continue;
},
}
}
results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
Ok(results)
}
pub fn thickness(&self) -> f64 {
self.thickness
}
pub fn saturation_magnetization(&self) -> f64 {
self.ms
}
}
#[cfg(test)]
mod tests {
use super::*;
fn yig_calculator() -> SpinWaveModeCalculator {
let yig = Ferromagnet::yig();
SpinWaveModeCalculator::new(&yig, 1e-6).expect("valid YIG parameters")
}
#[test]
fn test_de_mode_basic() {
let calc = yig_calculator();
let omega = calc
.damon_eshbach_frequency(0.1, 1e6)
.expect("valid parameters");
assert!(omega > 0.0, "DE frequency must be positive: {omega}");
}
#[test]
fn test_bvmsw_basic() {
let calc = yig_calculator();
let omega = calc.bvmsw_frequency(0.1, 1e6).expect("valid parameters");
assert!(omega > 0.0, "BVMSW frequency must be positive: {omega}");
}
#[test]
fn test_fvmsw_basic() {
let calc = yig_calculator();
let mu0_ms = MU_0 * calc.ms;
let h_ext = mu0_ms + 0.1; let omega = calc.fvmsw_frequency(h_ext, 1e6).expect("valid parameters");
assert!(omega > 0.0, "FVMSW frequency must be positive: {omega}");
}
#[test]
fn test_de_higher_than_bvmsw() {
let calc = yig_calculator();
let h_ext = 0.1;
let k = 1e6;
let omega_de = calc
.damon_eshbach_frequency(h_ext, k)
.expect("valid DE parameters");
let omega_bv = calc.bvmsw_frequency(h_ext, k).expect("valid BV parameters");
assert!(
omega_de > omega_bv,
"DE ({omega_de}) should be higher than BVMSW ({omega_bv})"
);
}
#[test]
fn test_mode_profile_de_surface_localized() {
let calc = yig_calculator();
let profile = calc
.mode_profile(SpinWaveMode::DamonEshbach, 1e7, 100)
.expect("valid profile");
assert_eq!(profile.len(), 100);
let surface_amp = profile[0].1;
let center_amp = profile[50].1;
assert!(
surface_amp >= center_amp,
"DE mode should be surface-localized: surface={surface_amp}, center={center_amp}"
);
}
#[test]
fn test_mode_profile_volume_uniform() {
let calc = yig_calculator();
let profile = calc
.mode_profile(SpinWaveMode::BackwardVolume, 1e6, 50)
.expect("valid profile");
let first = profile[0].1;
let mid = profile[25].1;
assert!(
(first - mid).abs() < 0.01,
"Volume mode should be uniform: first={first}, mid={mid}"
);
}
#[test]
fn test_compare_modes() {
let calc = yig_calculator();
let mu0_ms = MU_0 * calc.ms;
let results = calc
.compare_modes(mu0_ms + 0.1, 1e6)
.expect("valid parameters");
assert!(!results.is_empty(), "should have at least one valid mode");
}
#[test]
fn test_invalid_parameters() {
let mut bad = Ferromagnet::yig();
bad.ms = 0.0;
assert!(SpinWaveModeCalculator::new(&bad, 1e-6).is_err());
assert!(SpinWaveModeCalculator::new(&Ferromagnet::yig(), 0.0).is_err());
}
#[test]
fn test_mode_display() {
let de = SpinWaveMode::DamonEshbach;
let display = format!("{de}");
assert!(display.contains("Damon-Eshbach"));
}
#[test]
fn test_de_k_zero_limit() {
let calc = yig_calculator();
let h_ext = 0.1;
let omega_de = calc.damon_eshbach_frequency(h_ext, 0.0).expect("valid k=0");
let omega_kittel = GAMMA * (h_ext * (h_ext + MU_0 * calc.ms)).sqrt();
let rel_diff = (omega_de - omega_kittel).abs() / omega_kittel.max(1.0);
assert!(
rel_diff < 0.01,
"DE at k=0 should match Kittel: DE={omega_de}, Kittel={omega_kittel}"
);
}
#[test]
fn test_bvmsw_negative_group_velocity() {
let yig = Ferromagnet::yig(); let calc = SpinWaveModeCalculator::new(&yig, 10e-6).expect("valid");
let k_large_1 = 1e7;
let k_large_2 = 2e7;
let omega_l1 = calc.bvmsw_frequency(0.1, k_large_1).expect("valid");
let omega_l2 = calc.bvmsw_frequency(0.1, k_large_2).expect("valid");
assert!(
omega_l2 > omega_l1,
"BVMSW in exchange regime should have positive group velocity: \
omega(k1)={omega_l1}, omega(k2)={omega_l2}"
);
assert!(omega_l1 > 0.0, "omega must be positive");
assert!(omega_l2 > 0.0, "omega must be positive");
}
}