use crate::effect::ishe::InverseSpinHall;
use crate::error::Result;
use crate::material::interface::SpinInterface;
use crate::validation::experimental::ValidationResult;
use crate::vector3::Vector3;
pub const THETA_SH_LITERATURE_MIN: f64 = 0.0037;
pub const THETA_SH_LITERATURE_MAX: f64 = 0.15;
#[derive(Debug, Clone)]
pub struct Saitoh2006Validation {
pub ishe: InverseSpinHall,
pub interface: SpinInterface,
}
impl Saitoh2006Validation {
pub fn new() -> Result<Self> {
Ok(Self {
ishe: InverseSpinHall::platinum(),
interface: SpinInterface::yig_pt(),
})
}
pub fn validate_spin_hall_angle(&self, tolerance: f64) -> Result<ValidationResult> {
let theta = self.ishe.theta_sh;
let mid = 0.5 * (THETA_SH_LITERATURE_MIN + THETA_SH_LITERATURE_MAX);
let half = 0.5 * (THETA_SH_LITERATURE_MAX - THETA_SH_LITERATURE_MIN);
let dist = (theta - mid).abs();
let rel_err = if dist <= half {
0.0
} else {
(dist - half) / half
};
Ok(ValidationResult::new(
"Saitoh 2006 Pt spin Hall angle",
&[rel_err],
tolerance,
))
}
pub fn validate_ishe_voltage_polarity(&self) -> Result<bool> {
let js_flow = Vector3::new(0.0, 0.0, 1.0); let sigma = Vector3::new(1.0, 0.0, 0.0); let e_field = self.ishe.convert(js_flow, sigma);
let y_component = e_field.dot(&Vector3::new(0.0, 1.0, 0.0));
Ok(y_component > 0.0)
}
pub fn validate_ishe_scaling(
&self,
js_values: &[f64],
tolerance: f64,
) -> Result<ValidationResult> {
let mut errors = Vec::with_capacity(js_values.len());
let js_flow = Vector3::new(0.0, 0.0, 1.0);
for &j in js_values {
if j == 0.0 {
continue;
}
let sigma = Vector3::new(j, 0.0, 0.0);
let e_field = self.ishe.convert(js_flow, sigma);
let mag_sim = e_field.magnitude();
let mag_expected = self.ishe.rho * self.ishe.theta_sh.abs() * j.abs();
let rel_err = (mag_sim - mag_expected).abs() / mag_expected.abs();
errors.push(rel_err);
}
Ok(ValidationResult::new(
"Saitoh 2006 ISHE linearity in J_s",
&errors,
tolerance,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 0.30;
fn build() -> Saitoh2006Validation {
Saitoh2006Validation::new().expect("Saitoh harness must build")
}
#[test]
fn test_constants_consistent() {
const _: () = {
assert!(THETA_SH_LITERATURE_MIN > 0.0);
assert!(THETA_SH_LITERATURE_MAX > THETA_SH_LITERATURE_MIN);
assert!(THETA_SH_LITERATURE_MAX < 1.0);
};
}
#[test]
fn test_build_succeeds() {
let v = build();
assert!(v.ishe.theta_sh > 0.0);
assert!(v.ishe.rho > 0.0);
assert!(v.interface.g_r > 0.0);
}
#[test]
fn test_modern_preset_in_window() {
let v = build();
let result = v
.validate_spin_hall_angle(TOL)
.expect("spin Hall angle validation should run");
assert!(
result.passed,
"Pt preset θ_SH = {} should lie in literature window: {}",
v.ishe.theta_sh,
result.summary()
);
}
#[test]
fn test_spin_hall_angle_outside_window_fails() {
let v = Saitoh2006Validation {
ishe: InverseSpinHall::platinum().with_theta_sh(0.50),
interface: SpinInterface::yig_pt(),
};
let result = v
.validate_spin_hall_angle(0.01)
.expect("validation should run");
assert!(
!result.passed,
"θ_SH = 0.50 should be outside literature window"
);
assert!(result.max_relative_error > 0.0);
}
#[test]
fn test_voltage_polarity_correct() {
let v = build();
let ok = v
.validate_ishe_voltage_polarity()
.expect("polarity check should run");
assert!(
ok,
"Pt has positive θ_SH; E_ISHE should align with +(ẑ × x̂)"
);
}
#[test]
fn test_voltage_polarity_reversed_for_negative_theta() {
let v = Saitoh2006Validation {
ishe: InverseSpinHall::tungsten(),
interface: SpinInterface::yig_pt(),
};
let ok = v
.validate_ishe_voltage_polarity()
.expect("polarity check should run");
assert!(!ok, "Negative θ_SH should flip the ISHE field polarity");
}
#[test]
fn test_ishe_scaling_linear() {
let v = build();
let js_values = [1.0e7, 1.0e8, 1.0e9, 1.0e10];
let result = v
.validate_ishe_scaling(&js_values, 1e-6)
.expect("scaling validation should run");
assert!(
result.max_relative_error < 1e-9,
"ISHE should be exactly linear: {}",
result.summary()
);
assert!(result.passed);
}
#[test]
fn test_ishe_scaling_skips_zero() {
let v = build();
let js_values = [0.0, 1.0e8];
let result = v
.validate_ishe_scaling(&js_values, 1e-6)
.expect("scaling validation should run");
assert_eq!(
result.n_points, 1,
"the zero-current point should be skipped"
);
}
#[test]
fn test_summary_human_readable() {
let v = build();
let result = v.validate_spin_hall_angle(TOL).unwrap();
let s = result.summary();
assert!(s.contains("Saitoh 2006"));
}
}