use crate::error::Result;
use crate::texture::dw_dynamics::{DwMaterial, DwSotDynamics};
use crate::validation::experimental::ValidationResult;
pub const CURRENT_DENSITY: [f64; 5] = [1.5e11, 2.0e11, 2.5e11, 3.0e11, 3.5e11];
pub const DW_VELOCITY: [f64; 5] = [5.0, 20.0, 50.0, 100.0, 160.0];
pub const J_THRESHOLD: f64 = 1.5e11;
pub const THETA_SH_EFF: f64 = 0.15;
pub const T_CO: f64 = 0.6e-9;
pub const MS_CO: f64 = 1.0e6;
const _: () = assert!(J_THRESHOLD > 0.0);
const _: () = assert!(THETA_SH_EFF > 0.0);
const _: () = assert!(THETA_SH_EFF < 1.0);
const _: () = assert!(T_CO > 0.0);
const _: () = assert!(MS_CO > 0.0);
const _: () = {
let mut i = 0usize;
while i < 5 {
assert!(CURRENT_DENSITY[i] > 0.0);
assert!(DW_VELOCITY[i] > 0.0);
i += 1;
}
};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Miron2011Validation {
pub sot: DwSotDynamics,
}
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
impl Miron2011Validation {
pub fn new() -> Self {
Self {
sot: DwSotDynamics::new(DwMaterial::pt_co_alox()),
}
}
pub fn validate_velocity_linearity(&self, tol: f64) -> Result<ValidationResult> {
let n = CURRENT_DENSITY.len() - 1; let mut sum_xy = 0.0_f64;
let mut sum_xx = 0.0_f64;
let mut v_mean = 0.0_f64;
for i in 1..CURRENT_DENSITY.len() {
let dj = CURRENT_DENSITY[i] - J_THRESHOLD;
let v = DW_VELOCITY[i];
sum_xy += dj * v;
sum_xx += dj * dj;
v_mean += v;
}
v_mean /= n as f64;
let slope = sum_xy / sum_xx;
let mut ss_res = 0.0_f64;
let mut ss_tot = 0.0_f64;
for i in 1..CURRENT_DENSITY.len() {
let dj = CURRENT_DENSITY[i] - J_THRESHOLD;
let v = DW_VELOCITY[i];
let v_fit = slope * dj;
ss_res += (v - v_fit) * (v - v_fit);
ss_tot += (v - v_mean) * (v - v_mean);
}
let r_squared = if ss_tot > 0.0 {
1.0 - ss_res / ss_tot
} else {
1.0
};
let linearity_error = (1.0 - r_squared).abs();
let errors = vec![linearity_error];
Ok(ValidationResult::new(
"Miron 2011 DW velocity linearity (R² check)",
&errors,
tol,
))
}
pub fn validate_velocity_magnitude(&self, tol: f64) -> Result<ValidationResult> {
let errors: Vec<f64> = CURRENT_DENSITY
.iter()
.zip(DW_VELOCITY.iter())
.map(|(&j, &v_exp)| {
let v_model = self.sot.velocity(j);
(v_model - v_exp).abs() / v_exp
})
.collect();
Ok(ValidationResult::new(
"Miron 2011 DW velocity magnitude (SOT model)",
&errors,
tol,
))
}
pub fn validate_polarity(&self) -> Result<bool> {
let h_dl = self.sot.dl_sot_field(1.0e11);
Ok(h_dl > 0.0)
}
}
impl Default for Miron2011Validation {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build() -> Miron2011Validation {
Miron2011Validation::new()
}
const _: () = assert!(J_THRESHOLD > 0.0);
const _: () = assert!(THETA_SH_EFF > 0.0 && THETA_SH_EFF < 1.0);
const _: () = assert!(MS_CO > 0.0);
const _: () = assert!(T_CO > 0.0);
#[test]
fn test_new_builds_without_panic() {
let val = build();
assert!(val.sot.material.ms > 0.0);
assert!(val.sot.material.theta_sh > 0.0);
assert!(val.sot.material.thickness > 0.0);
}
#[test]
fn test_default_equals_new() {
let val1 = Miron2011Validation::new();
let val2 = Miron2011Validation::default();
assert!(
(val1.sot.material.ms - val2.sot.material.ms).abs() < 1.0,
"default() and new() must use identical Ms"
);
assert!(
(val1.sot.material.theta_sh - val2.sot.material.theta_sh).abs() < 1.0e-15,
"default() and new() must use identical θ_SH"
);
}
#[test]
fn test_velocity_linearity_passes_at_5pct_tolerance() {
let val = build();
let result = val
.validate_velocity_linearity(0.10)
.expect("linearity validation must run");
assert!(
result.passed,
"Velocity linearity must pass at 10% tolerance: {}",
result.summary()
);
}
#[test]
fn test_velocity_linearity_result_has_correct_n_points() {
let val = build();
let result = val
.validate_velocity_linearity(0.30)
.expect("linearity validation must run");
assert_eq!(result.n_points, 1);
}
#[test]
fn test_velocity_linearity_summary_contains_miron() {
let val = build();
let result = val
.validate_velocity_linearity(0.30)
.expect("linearity validation must run");
assert!(
result.summary().contains("Miron"),
"Summary must mention 'Miron': {}",
result.summary()
);
}
#[test]
fn test_velocity_magnitude_result_has_5_points() {
let val = build();
let result = val
.validate_velocity_magnitude(0.30)
.expect("magnitude validation must run");
assert_eq!(result.n_points, 5, "Must compare 5 current density points");
}
#[test]
fn test_velocity_magnitude_model_velocity_positive_at_all_j() {
let val = build();
for &j in &CURRENT_DENSITY {
let v = val.sot.velocity(j);
assert!(
v > 0.0,
"Model velocity must be positive at J = {:.2e} A/m²",
j
);
}
}
#[test]
fn test_velocity_magnitude_model_increasing() {
let val = build();
let mut v_prev = 0.0_f64;
for &j in &CURRENT_DENSITY {
let v = val.sot.velocity(j);
assert!(
v > v_prev,
"Model velocity must increase with J: v({:.2e})={:.2} ≤ v_prev={:.2}",
j,
v,
v_prev
);
v_prev = v;
}
}
#[test]
fn test_velocity_magnitude_model_within_order_of_magnitude() {
let val = build();
let result = val
.validate_velocity_magnitude(0.30)
.expect("magnitude validation must run");
assert!(
result.max_relative_error < 20.0,
"Model velocity more than 20x off from experiment: max err = {:.2}",
result.max_relative_error
);
}
#[test]
fn test_velocity_magnitude_highest_j_within_30pct() {
let val = build();
let j_max = *CURRENT_DENSITY.iter().last().unwrap();
let v_exp_max = *DW_VELOCITY.iter().last().unwrap();
let v_model = val.sot.velocity(j_max);
let rel_err = (v_model - v_exp_max).abs() / v_exp_max;
assert!(
rel_err < 0.30,
"Model velocity at highest J should be within 30 % of experiment; \
v_model={:.2} m/s, v_exp={:.2} m/s, rel_err={:.3}",
v_model,
v_exp_max,
rel_err
);
}
#[test]
fn test_polarity_is_correct_direction() {
let val = build();
let polarity = val
.validate_polarity()
.expect("polarity validation must run");
assert!(
polarity,
"θ_SH_eff must be positive → DW moves in correct direction"
);
}
#[test]
fn test_dl_sot_field_positive_for_positive_current() {
let val = build();
let h_dl = val.sot.dl_sot_field(1.0e11);
assert!(
h_dl > 0.0,
"DL-SOT field must be positive for positive current; got {:.4e}",
h_dl
);
}
#[test]
fn test_reference_data_length_consistent() {
assert_eq!(
CURRENT_DENSITY.len(),
DW_VELOCITY.len(),
"CURRENT_DENSITY and DW_VELOCITY must have the same length"
);
}
#[test]
fn test_sot_material_parameters_match_miron_2011() {
let val = build();
let mat = &val.sot.material;
assert!(
(mat.theta_sh - THETA_SH_EFF).abs() < 1.0e-15,
"θ_SH must equal THETA_SH_EFF = {}, got {}",
THETA_SH_EFF,
mat.theta_sh
);
assert!(
(mat.thickness - T_CO).abs() < 1.0e-20,
"thickness must equal T_CO = {} m, got {}",
T_CO,
mat.thickness
);
assert!(
(mat.ms - MS_CO).abs() < 1.0,
"Ms must equal MS_CO = {} A/m, got {}",
MS_CO,
mat.ms
);
}
}