use crate::effect::sot::SpinOrbitTorque;
use crate::error::Result;
use crate::material::ferromagnet::Ferromagnet;
use crate::validation::experimental::ValidationResult;
use crate::vector3::Vector3;
pub const THETA_SH_TA: f64 = -0.12;
pub const THETA_SH_TA_MIN: f64 = -0.15;
pub const THETA_SH_TA_MAX: f64 = -0.10;
pub const J_C_REFERENCE: f64 = 1.0e10;
pub const COFEB_THICKNESS_M: &[f64] = &[0.8e-9, 1.0e-9, 1.2e-9, 1.5e-9, 1.8e-9, 2.0e-9];
pub const J_C_VS_T_A_PER_M2: &[f64] = &[5.0e9, 7.0e9, 9.0e9, 1.2e10, 1.6e10, 2.0e10];
pub const H_K_REFERENCE: f64 = 8.0e4;
#[derive(Debug, Clone)]
pub struct Liu2012Validation {
pub sot: SpinOrbitTorque,
pub ferromagnet: Ferromagnet,
}
impl Liu2012Validation {
pub fn new() -> Result<Self> {
Ok(Self {
sot: SpinOrbitTorque::tantalum_cofeb(),
ferromagnet: Ferromagnet::cofeb(),
})
}
pub fn validate_ta_spin_hall_angle(&self, tolerance: f64) -> Result<ValidationResult> {
let theta = self.sot.theta_sh;
let mid = 0.5 * (THETA_SH_TA_MIN + THETA_SH_TA_MAX);
let half = 0.5 * (THETA_SH_TA_MAX - THETA_SH_TA_MIN).abs();
let dist = (theta - mid).abs();
let rel_err = if dist <= half {
0.0
} else {
(dist - half) / half.abs()
};
Ok(ValidationResult::new(
"Liu 2012 β-Ta θ_SH window",
&[rel_err],
tolerance,
))
}
pub fn validate_critical_switching_current(&self, tolerance: f64) -> Result<ValidationResult> {
let j_c_sim = self
.sot
.critical_current_density(self.ferromagnet.ms, H_K_REFERENCE);
let errors = if J_C_REFERENCE > 0.0 && j_c_sim > 0.0 {
let log_sim = j_c_sim.log10();
let log_ref = J_C_REFERENCE.log10();
vec![(log_sim - log_ref).abs() / log_ref.abs()]
} else {
vec![]
};
Ok(ValidationResult::new(
"Liu 2012 j_c (Ta/CoFeB) log-magnitude",
&errors,
tolerance,
))
}
pub fn validate_sot_polarity(&self) -> Result<bool> {
let m = Vector3::new(0.0, 0.0, 1.0);
let current_direction = Vector3::new(1.0, 0.0, 0.0);
let j_charge = 1.0e10;
let ms = self.ferromagnet.ms;
let h_dl = self
.sot
.damping_like_field(j_charge, m, current_direction, ms);
let x_component = h_dl.dot(&Vector3::new(1.0, 0.0, 0.0));
Ok(x_component < 0.0)
}
pub fn validate_thickness_scaling(&self, tolerance: f64) -> Result<ValidationResult> {
let n = COFEB_THICKNESS_M.len();
let mut sim = Vec::with_capacity(n);
for &t in COFEB_THICKNESS_M {
let sot = self.sot.clone().with_thickness(t);
let j_c = sot.critical_current_density(self.ferromagnet.ms, H_K_REFERENCE);
sim.push(j_c);
}
let mid = n / 2;
let scale = if sim[mid] > 0.0 {
J_C_VS_T_A_PER_M2[mid] / sim[mid]
} else {
1.0
};
let mut errors = Vec::with_capacity(n);
for (sim_jc, &ref_jc) in sim.iter().zip(J_C_VS_T_A_PER_M2.iter()) {
let rescaled = sim_jc * scale;
if ref_jc.abs() > 0.0 {
errors.push((rescaled - ref_jc).abs() / ref_jc.abs());
}
}
Ok(ValidationResult::new(
"Liu 2012 j_c(t_CoFeB) linearity",
&errors,
tolerance,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 0.30;
fn build() -> Liu2012Validation {
Liu2012Validation::new().expect("Liu harness must build")
}
const _: () = assert!(THETA_SH_TA < 0.0);
const _: () = assert!(THETA_SH_TA_MIN < 0.0);
const _: () = assert!(THETA_SH_TA_MAX < 0.0);
const _: () = assert!(THETA_SH_TA_MIN < THETA_SH_TA_MAX);
#[test]
fn test_theta_sh_ta_in_reported_window() {
assert!((THETA_SH_TA_MIN..=THETA_SH_TA_MAX).contains(&THETA_SH_TA));
}
#[test]
fn test_constants_lengths_match() {
assert_eq!(COFEB_THICKNESS_M.len(), J_C_VS_T_A_PER_M2.len());
assert!(COFEB_THICKNESS_M.len() >= 5);
for &t in COFEB_THICKNESS_M {
assert!(t > 0.0);
}
for &j in J_C_VS_T_A_PER_M2 {
assert!(j > 0.0);
}
for w in J_C_VS_T_A_PER_M2.windows(2) {
assert!(w[1] >= w[0]);
}
assert!((1.0e9..=1.0e11).contains(&J_C_REFERENCE));
}
#[test]
fn test_build_succeeds() {
let v = build();
assert!(v.sot.theta_sh < 0.0);
assert!(v.sot.thickness > 0.0);
assert!(v.sot.lambda_sd > 0.0);
assert!(v.ferromagnet.ms > 0.0);
assert!(v.ferromagnet.anisotropy_k > 0.0);
}
#[test]
fn test_spin_hall_angle_validation_runs() {
let v = build();
let result = v
.validate_ta_spin_hall_angle(TOL)
.expect("θ_SH validation should run");
assert_eq!(result.n_points, 1);
assert!(result.max_relative_error.is_finite());
assert!(
result.max_relative_error < 1.0,
"Ta preset θ_SH = {} should be close to literature window: {}",
v.sot.theta_sh,
result.summary()
);
}
#[test]
fn test_critical_current_validation_runs() {
let v = build();
let result = v
.validate_critical_switching_current(TOL)
.expect("j_c validation should run");
assert_eq!(result.n_points, 1);
assert!(result.max_relative_error.is_finite());
}
#[test]
fn test_polarity_returns_boolean() {
let v = build();
let ok = v
.validate_sot_polarity()
.expect("polarity check should run");
assert!(ok, "Ta polarity should be opposite to Pt (negative θ_SH)");
let v_pt = Liu2012Validation {
sot: SpinOrbitTorque::platinum_cofeb(),
ferromagnet: Ferromagnet::cofeb(),
};
let ok_pt = v_pt
.validate_sot_polarity()
.expect("polarity check should run");
assert!(
!ok_pt,
"Pt has positive θ_SH; the polarity check should be false"
);
}
#[test]
fn test_thickness_scaling_validation_runs() {
let v = build();
let result = v
.validate_thickness_scaling(TOL)
.expect("thickness-scaling validation should run");
assert_eq!(result.n_points, COFEB_THICKNESS_M.len());
assert!(result.max_relative_error.is_finite());
assert!(result.mean_relative_error.is_finite());
const SHAPE_TOL: f64 = 0.90;
assert!(
result.max_relative_error <= SHAPE_TOL,
"Linear j_c(t) scaling should be within {} %: {}",
SHAPE_TOL * 100.0,
result.summary()
);
}
}