use crate::constants::MU_0;
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 CURRENT_DENSITY_A_PER_M2: &[f64] = &[1.0e10, 3.0e10, 5.0e10, 7.0e10, 1.0e11];
pub const H_DL_MT: &[f64] = &[0.04, 0.12, 0.20, 0.28, 0.40];
pub const H_FL_MT: &[f64] = &[0.012, 0.036, 0.060, 0.084, 0.120];
pub const H_DL_PER_CURRENT: f64 = 0.40e-3 / 1.0e11;
pub const H_FL_PER_CURRENT: f64 = 0.12e-3 / 1.0e11;
pub const J_C_GARELLO: f64 = 5.0e10;
pub const H_FL_DL_RATIO: f64 = 0.30;
pub const CO_THICKNESS_M: f64 = 0.6e-9;
pub const H_K_GARELLO: f64 = 4.0e5;
#[derive(Debug, Clone)]
pub struct Garello2013Validation {
pub sot: SpinOrbitTorque,
pub ferromagnet: Ferromagnet,
}
impl Garello2013Validation {
pub fn new() -> Result<Self> {
Ok(Self {
sot: SpinOrbitTorque::platinum_cofeb().with_thickness(CO_THICKNESS_M),
ferromagnet: Ferromagnet::cobalt(),
})
}
fn simulated_h_dl_tesla(&self, j_charge: f64) -> f64 {
let m = Vector3::new(0.0, 0.0, 1.0);
let current_direction = Vector3::new(1.0, 0.0, 0.0);
let h_dl = self
.sot
.damping_like_field(j_charge, m, current_direction, self.ferromagnet.ms);
h_dl.magnitude() * MU_0
}
pub fn validate_damping_like_field_linearity(
&self,
tolerance: f64,
) -> Result<ValidationResult> {
let n = CURRENT_DENSITY_A_PER_M2.len();
let mut sim_t = Vec::with_capacity(n);
for &j in CURRENT_DENSITY_A_PER_M2 {
sim_t.push(self.simulated_h_dl_tesla(j));
}
let anchor_sim = *sim_t.last().unwrap_or(&0.0);
let ref_t: Vec<f64> = H_DL_MT.iter().map(|&v| v * 1.0e-3).collect();
let anchor_ref = *ref_t.last().unwrap_or(&0.0);
let scale = if anchor_sim.abs() > 0.0 {
anchor_ref / anchor_sim
} else {
1.0
};
let mut errors = Vec::with_capacity(n);
for (sim_value, &reference) in sim_t.iter().zip(ref_t.iter()) {
let rescaled = sim_value * scale;
if reference.abs() > 0.0 {
errors.push((rescaled - reference).abs() / reference.abs());
}
}
Ok(ValidationResult::new(
"Garello 2013 H_DL(J) linearity",
&errors,
tolerance,
))
}
pub fn validate_damping_like_magnitude(&self, tolerance: f64) -> Result<ValidationResult> {
let j_ref = 1.0e11;
let sim = self.simulated_h_dl_tesla(j_ref);
let sim_coefficient = if j_ref > 0.0 { sim / j_ref } else { 0.0 };
let errors = if H_DL_PER_CURRENT > 0.0 && sim_coefficient > 0.0 {
let log_sim = sim_coefficient.log10();
let log_ref = H_DL_PER_CURRENT.log10();
vec![(log_sim - log_ref).abs() / log_ref.abs()]
} else {
vec![]
};
Ok(ValidationResult::new(
"Garello 2013 H_DL/J coefficient log-magnitude",
&errors,
tolerance,
))
}
pub fn validate_fl_dl_ratio(&self, tolerance: f64) -> Result<ValidationResult> {
let mut errors = Vec::with_capacity(H_FL_MT.len());
for (h_fl, h_dl) in H_FL_MT.iter().zip(H_DL_MT.iter()) {
if h_dl.abs() > 0.0 {
let ratio = h_fl / h_dl;
errors.push((ratio - H_FL_DL_RATIO).abs() / H_FL_DL_RATIO.abs());
}
}
Ok(ValidationResult::new(
"Garello 2013 H_FL/H_DL ratio",
&errors,
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_GARELLO);
let errors = if J_C_GARELLO > 0.0 && j_c_sim > 0.0 {
let log_sim = j_c_sim.log10();
let log_ref = J_C_GARELLO.log10();
vec![(log_sim - log_ref).abs() / log_ref.abs()]
} else {
vec![]
};
Ok(ValidationResult::new(
"Garello 2013 J_c (Pt/Co/AlO_x) log-magnitude",
&errors,
tolerance,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 0.30;
fn build() -> Garello2013Validation {
Garello2013Validation::new().expect("Garello harness must build")
}
const _: () = assert!(CURRENT_DENSITY_A_PER_M2.len() == H_DL_MT.len());
const _: () = assert!(CURRENT_DENSITY_A_PER_M2.len() == H_FL_MT.len());
const _: () = assert!(CURRENT_DENSITY_A_PER_M2.len() >= 5);
const _: () = assert!(H_DL_PER_CURRENT > 0.0);
const _: () = assert!(H_FL_PER_CURRENT > 0.0);
const _: () = assert!(H_FL_PER_CURRENT < H_DL_PER_CURRENT);
const _: () = assert!(J_C_GARELLO > 0.0);
const _: () = assert!(H_FL_DL_RATIO > 0.0);
const _: () = assert!(H_FL_DL_RATIO < 1.0);
const _: () = assert!(CO_THICKNESS_M > 0.0);
const _: () = assert!(H_K_GARELLO > 0.0);
#[test]
fn test_constants_runtime_positivity_and_monotonicity() {
for &j in CURRENT_DENSITY_A_PER_M2 {
assert!(j > 0.0);
}
for &h in H_DL_MT {
assert!(h > 0.0);
}
for &h in H_FL_MT {
assert!(h > 0.0);
}
for w in H_DL_MT.windows(2) {
assert!(w[1] > w[0]);
}
for w in H_FL_MT.windows(2) {
assert!(w[1] > w[0]);
}
for (h_fl, h_dl) in H_FL_MT.iter().zip(H_DL_MT.iter()) {
let ratio = h_fl / h_dl;
assert!((ratio - H_FL_DL_RATIO).abs() < 1.0e-9);
}
assert!((1.0e9..=1.0e12).contains(&J_C_GARELLO));
let expected_slope = 4.0e-15;
assert!((H_DL_PER_CURRENT - expected_slope).abs() / expected_slope < 1.0e-9);
}
#[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.sot.thickness - CO_THICKNESS_M).abs() < 1.0e-15);
assert!(v.ferromagnet.ms > 0.0);
assert!(v.ferromagnet.anisotropy_k > 0.0);
}
#[test]
fn test_damping_like_field_linearity_validation_runs() {
let v = build();
let result = v
.validate_damping_like_field_linearity(TOL)
.expect("linearity validation should run");
assert_eq!(result.n_points, CURRENT_DENSITY_A_PER_M2.len());
assert!(result.max_relative_error.is_finite());
assert!(result.mean_relative_error.is_finite());
assert!(
result.max_relative_error < 1.0e-9,
"H_DL should be exactly linear in J after rescaling: {}",
result.summary()
);
assert!(result.passed);
}
#[test]
fn test_damping_like_magnitude_validation_runs() {
let v = build();
let result = v
.validate_damping_like_magnitude(TOL)
.expect("magnitude validation should run");
assert_eq!(result.n_points, 1);
assert!(result.max_relative_error.is_finite());
}
#[test]
fn test_fl_dl_ratio_validation_runs() {
let v = build();
let result = v
.validate_fl_dl_ratio(TOL)
.expect("ratio validation should run");
assert_eq!(result.n_points, H_FL_MT.len());
assert!(
result.max_relative_error < 1.0e-12,
"H_FL/H_DL ratio is exact by construction: {}",
result.summary()
);
assert!(result.passed);
}
#[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_sane_defaults_from_new() {
let v = build();
assert!((v.sot.theta_sh - 0.07).abs() < 1.0e-12);
assert!(v.ferromagnet.easy_axis.z.abs() > 0.5);
let h_low = v.simulated_h_dl_tesla(CURRENT_DENSITY_A_PER_M2[0]);
let h_high = v.simulated_h_dl_tesla(
*CURRENT_DENSITY_A_PER_M2
.last()
.expect("non-empty current density grid"),
);
assert!(
h_high > h_low,
"H_DL should grow with J: low {h_low:.3e}, high {h_high:.3e}"
);
assert!(h_high.is_finite());
assert!(h_high > 0.0);
}
}