use crate::effect::smr::SpinHallMagnetoresistance;
use crate::error::Result;
use crate::validation::experimental::ValidationResult;
use crate::vector3::Vector3;
pub const SMR_RATIO_REFERENCE: f64 = 4.0e-3;
pub const DEFAULT_TOLERANCE: f64 = 0.30_f64;
pub const N_ANGLE_POINTS: usize = 8;
const _: () = assert!(SMR_RATIO_REFERENCE > 0.0);
const _: () = assert!(SMR_RATIO_REFERENCE < 1.0);
const _: () = assert!(DEFAULT_TOLERANCE > 0.0);
const _: () = assert!(DEFAULT_TOLERANCE < 1.0);
const _: () = assert!(N_ANGLE_POINTS >= 4);
#[derive(Debug, Clone)]
pub struct Nakayama2013Validation {
pub smr: SpinHallMagnetoresistance,
}
impl Nakayama2013Validation {
pub fn new() -> Result<Self> {
Ok(Self {
smr: SpinHallMagnetoresistance::platinum_yig(),
})
}
fn m_in_plane(alpha: f64) -> Vector3<f64> {
Vector3::new(alpha.cos(), alpha.sin(), 0.0)
}
pub fn validate_longitudinal_angular(&self, tolerance: f64) -> Result<ValidationResult> {
let rho_0 = self.smr.rho_0();
let rho_1 = self.smr.rho_1();
let mut errors = Vec::with_capacity(N_ANGLE_POINTS);
for k in 0..N_ANGLE_POINTS {
let alpha = 2.0 * std::f64::consts::PI * (k as f64) / (N_ANGLE_POINTS as f64);
let m = Self::m_in_plane(alpha);
let rho_l_sim = self.smr.longitudinal_resistivity(m);
let rho_l_ref = rho_0 + rho_1 * (1.0 - alpha.sin() * alpha.sin());
if rho_l_ref.abs() > 0.0 {
errors.push((rho_l_sim - rho_l_ref).abs() / rho_l_ref.abs());
}
}
Ok(ValidationResult::new(
"Nakayama 2013 longitudinal SMR angular dependence",
&errors,
tolerance,
))
}
pub fn validate_smr_ratio(&self, tolerance: f64) -> Result<ValidationResult> {
let ratio_sim = self.smr.smr_ratio();
let err = (ratio_sim - SMR_RATIO_REFERENCE).abs() / SMR_RATIO_REFERENCE.abs();
Ok(ValidationResult::new(
"Nakayama 2013 SMR ratio (ρ₁/ρ₀)",
&[err],
tolerance,
))
}
pub fn validate_hall_angular(&self, tolerance: f64) -> Result<ValidationResult> {
let rho_1 = self.smr.rho_1();
let eps = rho_1.abs() * 1e-9;
let mut errors = Vec::with_capacity(N_ANGLE_POINTS);
for k in 0..N_ANGLE_POINTS {
let alpha = 2.0 * std::f64::consts::PI * (k as f64) / (N_ANGLE_POINTS as f64);
let m = Self::m_in_plane(alpha);
let rho_h_sim = self.smr.hall_resistivity(m);
let rho_h_ref = rho_1 * (2.0 * alpha).sin() / 2.0;
if rho_h_ref.abs() > eps {
errors.push((rho_h_sim - rho_h_ref).abs() / rho_h_ref.abs());
}
}
Ok(ValidationResult::new(
"Nakayama 2013 Hall SMR angular dependence",
&errors,
tolerance,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 0.30_f64;
fn build() -> Nakayama2013Validation {
Nakayama2013Validation::new().expect("harness must build from platinum_yig preset")
}
const _: () = assert!(SMR_RATIO_REFERENCE > 0.0);
const _: () = assert!(SMR_RATIO_REFERENCE < 1.0);
const _: () = assert!(DEFAULT_TOLERANCE > 0.0);
const _: () = assert!(N_ANGLE_POINTS > 0);
#[test]
fn test_build_succeeds() {
let v = build();
assert!(v.smr.theta_sh > 0.0);
assert!(v.smr.resistivity_nm > 0.0);
assert!(v.smr.lambda_sf > 0.0);
assert!(v.smr.t_nm > 0.0);
assert!(v.smr.sigma_nm > 0.0);
assert!(v.smr.g_r > 0.0);
}
#[test]
fn test_smr_rho1_positive() {
let v = build();
assert!(v.smr.rho_1() > 0.0);
}
#[test]
fn test_longitudinal_angular_passes_at_30pct_tolerance() {
let v = build();
let result = v
.validate_longitudinal_angular(TOL)
.expect("validation should run");
assert_eq!(result.n_points, N_ANGLE_POINTS);
assert!(result.max_relative_error.is_finite());
assert!(
result.passed,
"Longitudinal angular validation failed at 30%: {}",
result.summary()
);
}
#[test]
fn test_longitudinal_angular_near_exact() {
let v = build();
let result = v.validate_longitudinal_angular(TOL).expect("should run");
assert!(
result.max_relative_error < 1e-12,
"Longitudinal SMR angular error should be near machine precision: {}",
result.summary()
);
}
#[test]
fn test_longitudinal_angular_summary_contains_name() {
let v = build();
let result = v.validate_longitudinal_angular(TOL).expect("should run");
let s = result.summary();
assert!(
s.contains("Nakayama"),
"Summary should contain 'Nakayama': {s}"
);
assert!(
s.contains("longitudinal") || s.contains("SMR"),
"Summary should mention longitudinal: {s}"
);
}
#[test]
fn test_smr_ratio_passes_at_30pct_tolerance() {
let v = build();
let result = v
.validate_smr_ratio(TOL)
.expect("SMR ratio validation should run");
assert_eq!(result.n_points, 1);
assert!(result.max_relative_error.is_finite());
assert!(
result.passed,
"SMR ratio failed at 30%: {}",
result.summary()
);
}
#[test]
fn test_smr_ratio_finite_and_positive() {
let v = build();
let ratio = v.smr.smr_ratio();
assert!(ratio.is_finite());
assert!(ratio > 0.0);
}
#[test]
fn test_smr_ratio_summary_contains_name() {
let v = build();
let result = v.validate_smr_ratio(TOL).expect("should run");
let s = result.summary();
assert!(
s.contains("Nakayama"),
"Summary should contain 'Nakayama': {s}"
);
}
#[test]
fn test_hall_angular_passes_at_30pct_tolerance() {
let v = build();
let result = v
.validate_hall_angular(TOL)
.expect("Hall angular validation should run");
assert!(result.max_relative_error.is_finite());
assert!(
result.passed,
"Hall angular validation failed at 30%: {}",
result.summary()
);
}
#[test]
fn test_hall_angular_near_exact() {
let v = build();
let result = v.validate_hall_angular(TOL).expect("should run");
assert!(
result.max_relative_error < 1e-12,
"Hall SMR angular error should be near machine precision: {}",
result.summary()
);
}
#[test]
fn test_hall_angular_summary_contains_name() {
let v = build();
let result = v.validate_hall_angular(TOL).expect("should run");
let s = result.summary();
assert!(
s.contains("Nakayama"),
"Summary should contain 'Nakayama': {s}"
);
}
#[test]
fn test_rho_l_maximum_at_alpha_zero() {
let v = build();
let rho_at_0 = v.smr.longitudinal_resistivity(Vector3::new(1.0, 0.0, 0.0));
let rho_at_pi2 = v.smr.longitudinal_resistivity(Vector3::new(0.0, 1.0, 0.0));
assert!(
rho_at_0 > rho_at_pi2,
"ρ_L should be maximum when m ⊥ ŷ: rho(0)={rho_at_0:.4e} vs rho(π/2)={rho_at_pi2:.4e}"
);
}
#[test]
fn test_hall_sign_at_pi_over_4() {
let v = build();
let rho_1 = v.smr.rho_1();
let alpha1 = std::f64::consts::PI / 4.0;
let alpha2 = 3.0 * std::f64::consts::PI / 4.0;
let m1 = Vector3::new(alpha1.cos(), alpha1.sin(), 0.0);
let m2 = Vector3::new(alpha2.cos(), alpha2.sin(), 0.0);
let h1 = v.smr.hall_resistivity(m1);
let h2 = v.smr.hall_resistivity(m2);
assert!(h1 > 0.0, "ρ_H(π/4) should be positive: {h1:.3e}");
assert!(h2 < 0.0, "ρ_H(3π/4) should be negative: {h2:.3e}");
let expected = rho_1 / 2.0;
assert!((h1 - expected).abs() < 1e-25, "ρ_H(π/4) should equal ρ₁/2");
assert!(
(h2 + expected).abs() < 1e-25,
"ρ_H(3π/4) should equal −ρ₁/2"
);
}
#[test]
fn test_full_validation_suite_runs_without_error() {
let v = build();
v.validate_longitudinal_angular(TOL)
.expect("longitudinal angular");
v.validate_smr_ratio(TOL).expect("smr ratio");
v.validate_hall_angular(TOL).expect("hall angular");
}
}