use std::f64::consts::PI;
use crate::constants::{GAMMA, HBAR};
use crate::effect::ishe::InverseSpinHall;
use crate::error::Result;
use crate::material::interface::SpinInterface;
use crate::validation::experimental::ValidationResult;
pub const PT_THICKNESS_M: &[f64] = &[1.0e-9, 2.0e-9, 3.0e-9, 5.0e-9, 7.0e-9, 10.0e-9, 15.0e-9];
pub const V_ISHE_UV: &[f64] = &[0.5, 1.4, 2.1, 2.8, 3.0, 3.05, 3.1];
pub const THETA_SH_MOSENDZ: f64 = 0.013;
pub const G_MIX_REFERENCE: f64 = 2.1e19;
pub const LAMBDA_SF_PT_M: f64 = 7.0e-9;
pub const PY_THICKNESS_M: f64 = 12.0e-9;
pub const MS_PERMALLOY: f64 = 8.0e5;
#[derive(Debug, Clone)]
pub struct Mosendz2010Validation {
pub ishe: InverseSpinHall,
pub interface: SpinInterface,
}
impl Mosendz2010Validation {
pub fn new() -> Result<Self> {
Ok(Self {
ishe: InverseSpinHall::platinum(),
interface: SpinInterface::py_pt().with_g_r(G_MIX_REFERENCE),
})
}
fn thickness_scaling_factor(t_pt: f64) -> f64 {
(t_pt / (2.0 * LAMBDA_SF_PT_M)).tanh()
}
pub fn validate_pt_thickness_scaling(&self, tolerance: f64) -> Result<ValidationResult> {
let n = PT_THICKNESS_M.len();
let mut model = Vec::with_capacity(n);
for &t in PT_THICKNESS_M {
model.push(Self::thickness_scaling_factor(t));
}
let anchor_model = *model.last().unwrap_or(&1.0);
let anchor_reference = *V_ISHE_UV.last().unwrap_or(&1.0);
let scale = if anchor_model > 0.0 {
anchor_reference / anchor_model
} else {
1.0
};
let mut errors = Vec::with_capacity(n);
for (sim_factor, &v_ref) in model.iter().zip(V_ISHE_UV.iter()) {
let v_sim = sim_factor * scale;
if v_ref.abs() > 0.0 {
errors.push((v_sim - v_ref).abs() / v_ref.abs());
}
}
Ok(ValidationResult::new(
"Mosendz 2010 V_ISHE(t_Pt) scaling",
&errors,
tolerance,
))
}
pub fn predicted_linewidth_enhancement(&self, ferromagnet_thickness: f64, ms: f64) -> f64 {
if ferromagnet_thickness <= 0.0 || ms <= 0.0 {
return 0.0;
}
(GAMMA * HBAR * self.interface.g_r) / (4.0 * PI * ms * ferromagnet_thickness)
}
pub fn reference_linewidth_enhancement(ferromagnet_thickness: f64) -> f64 {
if ferromagnet_thickness <= 0.0 {
return 0.0;
}
(GAMMA * HBAR * G_MIX_REFERENCE) / (4.0 * PI * MS_PERMALLOY * ferromagnet_thickness)
}
pub fn validate_linewidth_enhancement(
&self,
ferromagnet_thickness: f64,
tolerance: f64,
) -> Result<ValidationResult> {
let predicted = self.predicted_linewidth_enhancement(ferromagnet_thickness, MS_PERMALLOY);
let reference = Self::reference_linewidth_enhancement(ferromagnet_thickness);
let errors = if reference.abs() > 0.0 {
vec![(predicted - reference).abs() / reference.abs()]
} else {
vec![]
};
Ok(ValidationResult::new(
"Mosendz 2010 Δα_eff (spin pumping)",
&errors,
tolerance,
))
}
pub fn validate_spin_mixing_conductance(&self, tolerance: f64) -> Result<ValidationResult> {
let g_sim = self.interface.g_r;
let g_ref = G_MIX_REFERENCE;
let errors = if g_ref.abs() > 0.0 {
vec![(g_sim - g_ref).abs() / g_ref.abs()]
} else {
vec![]
};
Ok(ValidationResult::new(
"Mosendz 2010 g↑↓_eff (Py/Pt)",
&errors,
tolerance,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 0.30;
fn build() -> Mosendz2010Validation {
Mosendz2010Validation::new().expect("Mosendz harness must build")
}
const _: () = assert!(THETA_SH_MOSENDZ > 0.0);
const _: () = assert!(THETA_SH_MOSENDZ < 0.1);
const _: () = assert!(PT_THICKNESS_M.len() == V_ISHE_UV.len());
const _: () = assert!(PT_THICKNESS_M.len() >= 5);
#[test]
fn test_reference_data_monotonic_and_positive() {
for &t in PT_THICKNESS_M {
assert!(t > 0.0);
}
for &v in V_ISHE_UV {
assert!(v > 0.0);
}
for w in V_ISHE_UV.windows(2) {
assert!(
w[1] >= w[0],
"V_ISHE_UV should be monotonically non-decreasing"
);
}
assert!((1.0e18..1.0e21).contains(&G_MIX_REFERENCE));
}
#[test]
fn test_build_succeeds() {
let v = build();
assert!(v.ishe.theta_sh > 0.0);
assert!(v.ishe.rho > 0.0);
let rel = (v.interface.g_r - G_MIX_REFERENCE).abs() / G_MIX_REFERENCE.abs();
assert!(rel < 1.0e-12, "interface g_r should equal G_MIX_REFERENCE");
}
#[test]
fn test_thickness_scaling_validation_runs() {
let v = build();
let result = v
.validate_pt_thickness_scaling(TOL)
.expect("thickness-scaling validation should run");
assert_eq!(result.n_points, PT_THICKNESS_M.len());
assert!(result.max_relative_error.is_finite());
assert!(result.mean_relative_error.is_finite());
const SHAPE_TOL: f64 = 0.65;
assert!(
result.max_relative_error <= SHAPE_TOL,
"tanh thickness scaling should be within {} %: {}",
SHAPE_TOL * 100.0,
result.summary()
);
}
#[test]
fn test_linewidth_enhancement_positive_and_finite() {
let v = build();
let delta_alpha = v.predicted_linewidth_enhancement(PY_THICKNESS_M, MS_PERMALLOY);
assert!(delta_alpha > 0.0);
assert!(delta_alpha.is_finite());
assert!(
delta_alpha > 1.0e-5 && delta_alpha < 1.0e-1,
"Δα_eff = {delta_alpha:.3e} is outside the physical window for Py/Pt"
);
}
#[test]
fn test_linewidth_enhancement_validation_runs() {
let v = build();
let result = v
.validate_linewidth_enhancement(PY_THICKNESS_M, TOL)
.expect("linewidth-enhancement validation should run");
assert_eq!(result.n_points, 1);
assert!(
result.max_relative_error < 1.0e-9,
"linewidth check should be exact for reference-matched g_r: {}",
result.summary()
);
assert!(result.passed);
}
#[test]
fn test_spin_mixing_conductance_error_decreases_when_matched() {
let mismatched = Mosendz2010Validation {
ishe: InverseSpinHall::platinum(),
interface: SpinInterface::py_pt().with_g_r(G_MIX_REFERENCE * 5.0),
};
let result_bad = mismatched
.validate_spin_mixing_conductance(0.10)
.expect("g↑↓ validation should run");
assert!(!result_bad.passed);
assert!(result_bad.max_relative_error > 1.0);
let matched = build();
let result_good = matched
.validate_spin_mixing_conductance(1.0e-9)
.expect("g↑↓ validation should run");
assert!(result_good.passed);
assert!(result_good.max_relative_error < 1.0e-12);
}
#[test]
fn test_sane_defaults_from_new() {
let v = build();
assert!((v.ishe.theta_sh - 0.08).abs() < 1.0e-12);
assert!((v.interface.normal.magnitude() - 1.0).abs() < 1.0e-10);
let alpha_thin = v.predicted_linewidth_enhancement(5.0e-9, MS_PERMALLOY);
let alpha_thick = v.predicted_linewidth_enhancement(50.0e-9, MS_PERMALLOY);
assert!(
alpha_thin > alpha_thick,
"Δα_eff should decrease with increasing t_F: {alpha_thin:.3e} vs {alpha_thick:.3e}"
);
let f_thin = Mosendz2010Validation::thickness_scaling_factor(PT_THICKNESS_M[0]);
let f_thick = Mosendz2010Validation::thickness_scaling_factor(
*PT_THICKNESS_M.last().expect("non-empty thickness grid"),
);
assert!(f_thin < f_thick);
assert!(f_thick <= 1.0);
}
}