use crate::constants::{GAMMA, MU_0};
use crate::error::{self, Result};
#[derive(Debug, Clone)]
pub struct BackwardVolumeMSW {
pub thickness: f64,
pub h_ext_perp: f64,
pub ms: f64,
pub a_ex: f64,
pub alpha: f64,
}
impl BackwardVolumeMSW {
pub fn new(thickness: f64, h_ext_perp: 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 ms <= 0.0 {
return Err(error::invalid_param(
"ms",
"saturation magnetization must be positive",
));
}
if h_ext_perp <= ms {
return Err(error::invalid_param(
"h_ext_perp",
"perpendicular field must exceed Ms for out-of-plane saturation",
));
}
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_perp,
ms,
a_ex,
alpha,
})
}
pub fn yig_yagi(h_perp_a_per_m: f64) -> Result<Self> {
Self::new(
1e-6, h_perp_a_per_m,
1.4e5, 3.5e-12, 3e-5, )
}
pub fn permalloy_perp(h_perp: f64) -> Result<Self> {
Self::new(
30e-9, h_perp, 8.6e5, 1.3e-11, 8e-3, )
}
#[inline]
pub fn omega_h_eff(&self) -> f64 {
GAMMA * MU_0 * (self.h_ext_perp - self.ms)
}
#[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 {
1.0 - kd / 2.0
} else {
(1.0 - (-kd).exp()) / kd
}
}
pub fn dispersion_omega(&self, k: f64) -> f64 {
let omega_h = self.omega_h_eff();
let omega_m = self.omega_m();
let lambda_ex = self.exchange_len_sq();
let f_kd = self.f_kd(k);
let exch = omega_m * lambda_ex * k * k;
let term1 = omega_h + exch;
let term2 = omega_h + exch + omega_m * (f_kd - 1.0);
let omega_sq = term1 * term2;
if omega_sq < 0.0 {
0.0
} else {
omega_sq.sqrt()
}
}
pub fn group_velocity(&self, k: 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);
let omega_minus = self.dispersion_omega(k_minus);
let eff_dk = if k - dk < 0.0 { k + dk } else { 2.0 * dk };
(omega_plus - omega_minus) / eff_dk
}
pub fn crossover_wavevector(&self) -> f64 {
let mut k_lo = 1.0_f64;
let mut k_hi = 1e8_f64;
let vg_lo = self.group_velocity(k_lo);
let vg_hi = self.group_velocity(k_hi);
if vg_lo.signum() == vg_hi.signum() {
let omega_h = self.omega_h_eff();
let omega_m = self.omega_m();
let lambda_ex = self.exchange_len_sq();
return (omega_h / (omega_m * lambda_ex)).sqrt().max(1.0);
}
for _ in 0..60 {
let k_mid = 0.5 * (k_lo + k_hi);
let vg_mid = self.group_velocity(k_mid);
let vg_lo = self.group_velocity(k_lo);
if vg_lo.signum() == vg_mid.signum() {
k_lo = k_mid;
} else {
k_hi = k_mid;
}
if (k_hi - k_lo) / k_hi < 1e-8 {
break;
}
}
0.5 * (k_lo + k_hi)
}
pub fn mode_profile(&self, _k: f64, z: f64) -> f64 {
use std::f64::consts::PI;
let z_clamped = z.clamp(0.0, self.thickness);
(PI * z_clamped / self.thickness).cos()
}
pub fn quantized_thickness_modes(&self, n_max: usize) -> Vec<(f64, f64)> {
use std::f64::consts::PI;
let n_modes = n_max.max(1);
let mut modes = Vec::with_capacity(n_modes);
for n in 1..=n_modes {
let k_perp_n = n as f64 * PI / self.thickness;
let omega_n = self.dispersion_omega(k_perp_n);
modes.push((k_perp_n, omega_n));
}
modes
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL_REL: f64 = 0.05;
fn yig_bv() -> BackwardVolumeMSW {
BackwardVolumeMSW::yig_yagi(2.0 * 1.4e5).expect("valid field")
}
#[test]
fn test_yig_yagi_valid() {
let bv = yig_bv();
assert!(bv.ms > 0.0);
assert!(bv.h_ext_perp > bv.ms, "h_ext must exceed ms");
}
#[test]
fn test_yig_yagi_field_too_low() {
let result = BackwardVolumeMSW::yig_yagi(1.4e5);
assert!(result.is_err());
let result = BackwardVolumeMSW::yig_yagi(1.0e5);
assert!(result.is_err());
}
#[test]
fn test_permalloy_perp_valid() {
let bv = BackwardVolumeMSW::permalloy_perp(1.8e6).expect("valid");
assert!(bv.ms > 8.0e5);
assert!(bv.h_ext_perp > bv.ms);
}
#[test]
fn test_dispersion_positive_frequency() {
let bv = yig_bv();
for &k in &[1e4_f64, 1e5, 1e6, 1e7] {
let omega = bv.dispersion_omega(k);
assert!(
omega > 0.0,
"BVMSW frequency must be positive at k={k:.2e}: {omega}"
);
}
}
#[test]
fn test_omega_h_eff_positive() {
let bv = yig_bv();
let omega_h = bv.omega_h_eff();
assert!(
omega_h > 0.0,
"Effective Larmor frequency must be positive: {omega_h}"
);
}
#[test]
fn test_backward_group_velocity_small_k() {
let bv = yig_bv();
let _vg_small = bv.group_velocity(1e4);
let k_cross = bv.crossover_wavevector();
assert!(
k_cross > 0.0,
"Crossover wavevector must be positive: {k_cross:.4e}"
);
}
#[test]
fn test_forward_group_velocity_large_k() {
let bv = yig_bv();
let vg_large = bv.group_velocity(1e8);
assert!(
vg_large > 0.0,
"At large k, BVMSW group velocity should be positive: {vg_large:.4e}"
);
}
#[test]
fn test_crossover_wavevector_between_bounds() {
let bv = yig_bv();
let k_cross = bv.crossover_wavevector();
assert!(
k_cross > 0.0 && k_cross < 2e8,
"Crossover wavevector out of expected range: {k_cross:.4e}"
);
}
#[test]
fn test_mode_profile_cosine_shape() {
let bv = yig_bv();
let d = bv.thickness;
let k = 1e6;
let amp_surface = bv.mode_profile(k, 0.0);
assert!(
(amp_surface - 1.0).abs() < 1e-10,
"Mode profile at z=0 should be 1.0: {amp_surface}"
);
let amp_center = bv.mode_profile(k, d / 2.0);
assert!(
amp_center.abs() < 1e-10,
"Mode profile at z=d/2 should be 0.0: {amp_center}"
);
let amp_bottom = bv.mode_profile(k, d);
assert!(
(amp_bottom + 1.0).abs() < 1e-10,
"Mode profile at z=d should be -1.0: {amp_bottom}"
);
}
#[test]
fn test_quantized_modes_frequency_behavior() {
let bv = yig_bv();
let modes = bv.quantized_thickness_modes(5);
assert_eq!(modes.len(), 5);
for (i, &(k_n, omega_n)) in modes.iter().enumerate() {
assert!(
k_n > 0.0,
"Mode {}: k_perp must be positive: {k_n:.4e}",
i + 1
);
assert!(
omega_n >= 0.0,
"Mode {}: frequency must be non-negative: {omega_n:.4e}",
i + 1
);
}
let omega_first = modes.first().unwrap().1;
let omega_last = modes.last().unwrap().1;
assert!(
omega_first >= 0.0,
"First mode must be non-negative: {omega_first:.4e}"
);
assert!(
omega_last >= 0.0,
"Last mode must be non-negative: {omega_last:.4e}"
);
}
#[test]
fn test_quantized_modes_wavevectors() {
use std::f64::consts::PI;
let bv = yig_bv();
let d = bv.thickness;
let modes = bv.quantized_thickness_modes(3);
for (n, &(k_n, _)) in (1..=3_usize).zip(modes.iter()) {
let expected = n as f64 * PI / d;
let rel_err = (k_n - expected).abs() / expected;
assert!(
rel_err < TOL_REL,
"k_perp,{n} should be {expected:.4e}, got {k_n:.4e}"
);
}
}
#[test]
fn test_new_invalid_h_ext_eq_ms() {
let ms = 8.6e5;
let result = BackwardVolumeMSW::new(30e-9, ms, ms, 1.3e-11, 8e-3);
assert!(result.is_err());
}
#[test]
fn test_new_invalid_thickness() {
let result = BackwardVolumeMSW::new(0.0, 2e6, 8.6e5, 1.3e-11, 8e-3);
assert!(result.is_err());
}
}