use std::f64::consts::FRAC_PI_2;
use crate::error::Result;
use crate::spinwave::damon_eshbach::DamonEshbachDetailed;
use crate::validation::experimental::ValidationResult;
pub const K_VALUES: &[f64] = &[5.0e4, 1.0e5, 1.5e5, 2.0e5, 2.5e5, 3.0e5, 3.5e5, 4.0e5];
pub const OMEGA_GHZ: &[f64] = &[5.8, 6.3, 6.9, 7.5, 8.1, 8.7, 9.3, 9.9];
pub const NONRECIP_PCT: &[f64] = &[3.5, 4.8, 5.5, 6.0, 6.2, 6.2, 6.0, 5.7];
#[derive(Debug, Clone)]
pub struct Demidov2006Validation {
pub model: DamonEshbachDetailed,
}
impl Demidov2006Validation {
pub fn new() -> Result<Self> {
let thickness = 5.0e-6; let ms = 1.4e5; let a_ex = 3.5e-12; let h_ext = 79_577.0; let alpha = 3.0e-5;
let model = DamonEshbachDetailed::new(thickness, h_ext, ms, a_ex, alpha)?;
Ok(Self { model })
}
pub fn k_values(&self) -> &'static [f64] {
K_VALUES
}
pub fn validate_dispersion(&self, tolerance: f64) -> Result<ValidationResult> {
let two_pi_giga = 2.0 * std::f64::consts::PI * 1.0e9;
let mut errors = Vec::with_capacity(K_VALUES.len());
for (k, &f_ref_ghz) in K_VALUES.iter().zip(OMEGA_GHZ.iter()) {
let omega_sim = self.model.dispersion_omega(*k, FRAC_PI_2);
let f_sim_ghz = omega_sim / two_pi_giga;
let rel_err = (f_sim_ghz - f_ref_ghz).abs() / f_ref_ghz.abs();
errors.push(rel_err);
}
Ok(ValidationResult::new(
"Demidov 2006 DE dispersion",
&errors,
tolerance,
))
}
pub fn validate_nonreciprocity(&self, tolerance: f64) -> Result<ValidationResult> {
let mut errors = Vec::with_capacity(K_VALUES.len());
for (k, &pct_ref) in K_VALUES.iter().zip(NONRECIP_PCT.iter()) {
let omega = self.model.dispersion_omega(*k, FRAC_PI_2);
if omega <= 0.0 {
continue; }
let delta_omega = self.model.nonreciprocity(*k).abs();
let pct_sim = 100.0 * delta_omega / omega;
let rel_err = (pct_sim - pct_ref).abs() / pct_ref.abs();
errors.push(rel_err);
}
Ok(ValidationResult::new(
"Demidov 2006 DE non-reciprocity",
&errors,
tolerance,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const LOOSE_TOL: f64 = 0.50; const VERY_LOOSE_TOL: f64 = 5.0;
fn build() -> Demidov2006Validation {
Demidov2006Validation::new().expect("YIG/Pt validation harness must build")
}
#[test]
fn test_constants_lengths_match() {
assert_eq!(K_VALUES.len(), OMEGA_GHZ.len());
assert_eq!(K_VALUES.len(), NONRECIP_PCT.len());
assert!(K_VALUES.len() >= 5, "need at least 5 reference points");
}
#[test]
fn test_constants_monotonic_dispersion() {
for w in OMEGA_GHZ.windows(2) {
assert!(
w[1] > w[0],
"OMEGA_GHZ should be monotonic increasing: {:?}",
w
);
}
}
#[test]
fn test_build_succeeds() {
let v = build();
assert!(v.model.ms > 0.0);
assert!(v.model.thickness > 0.0);
assert!(v.model.h_ext > 0.0);
}
#[test]
fn test_k_values_accessor() {
let v = build();
assert_eq!(v.k_values(), K_VALUES);
}
#[test]
fn test_dispersion_validation_runs() {
let v = build();
let result = v
.validate_dispersion(LOOSE_TOL)
.expect("dispersion validation should run");
assert_eq!(result.n_points, K_VALUES.len());
assert!(result.max_relative_error.is_finite());
assert!(result.mean_relative_error.is_finite());
println!("{}", result.summary());
}
#[test]
fn test_nonreciprocity_validation_runs() {
let v = build();
let result = v
.validate_nonreciprocity(VERY_LOOSE_TOL)
.expect("non-reciprocity validation should run");
assert!(result.n_points > 0);
assert!(result.max_relative_error.is_finite());
assert!(result.mean_relative_error.is_finite());
println!("{}", result.summary());
}
#[test]
fn test_dispersion_positive_and_increasing() {
let v = build();
let mut last = 0.0;
for &k in K_VALUES {
let omega = v.model.dispersion_omega(k, FRAC_PI_2);
assert!(omega > 0.0, "dispersion must be positive at k = {k:.2e}");
assert!(
omega > last,
"dispersion should increase with k: {omega:.4e} after {last:.4e}"
);
last = omega;
}
}
}