use crate::effect::ishe::InverseSpinHall;
use crate::effect::sse::SpinSeebeck;
use crate::error::Result;
use crate::material::interface::SpinInterface;
use crate::validation::experimental::ValidationResult;
use crate::vector3::Vector3;
pub const DELTA_T_VALUES_K: &[f64] = &[1.0, 2.0, 5.0, 10.0, 15.0, 20.0];
pub const V_LSSE_MICROVOLT: &[f64] = &[1.0, 2.05, 4.95, 9.8, 14.7, 19.6];
pub const YIG_THICKNESS_M: f64 = 1.0e-3;
pub const PT_STRIP_WIDTH_M: f64 = 1.0e-4;
#[derive(Debug, Clone)]
pub struct Uchida2008Validation {
pub sse: SpinSeebeck,
pub ishe: InverseSpinHall,
pub interface: SpinInterface,
}
impl Uchida2008Validation {
pub fn new() -> Result<Self> {
Ok(Self {
sse: SpinSeebeck::yig_pt(),
ishe: InverseSpinHall::platinum(),
interface: SpinInterface::yig_pt(),
})
}
fn predict_voltage(&self, delta_t: f64) -> f64 {
let direction = Vector3::new(0.0, 0.0, 1.0);
let js = self
.sse
.linear_gradient(delta_t, YIG_THICKNESS_M, direction);
let js_flow_direction = Vector3::new(0.0, 0.0, 1.0); let js_with_inplane_pol = Vector3::new(js.magnitude(), 0.0, 0.0);
let e_field = self.ishe.convert(js_flow_direction, js_with_inplane_pol);
e_field.magnitude() * PT_STRIP_WIDTH_M
}
pub fn validate_linear_thermal_response(&self, tolerance: f64) -> Result<ValidationResult> {
let sim_uv: Vec<f64> = DELTA_T_VALUES_K
.iter()
.map(|&dt| self.predict_voltage(dt) * 1.0e6)
.collect();
let ref0 = V_LSSE_MICROVOLT[0];
let sim0 = sim_uv[0];
let scale = if sim0.abs() > 0.0 { ref0 / sim0 } else { 1.0 };
let mut errors = Vec::with_capacity(DELTA_T_VALUES_K.len());
for (sim, &reference) in sim_uv.iter().zip(V_LSSE_MICROVOLT.iter()) {
let rescaled = sim * scale;
let rel_err = (rescaled - reference).abs() / reference.abs();
errors.push(rel_err);
}
Ok(ValidationResult::new(
"Uchida 2008 LSSE linear response",
&errors,
tolerance,
))
}
pub fn validate_polarity(&self) -> Result<bool> {
let direction = Vector3::new(0.0, 0.0, 1.0);
let delta_t = 10.0;
let js = self
.sse
.linear_gradient(delta_t, YIG_THICKNESS_M, direction);
let js_inplane = Vector3::new(js.magnitude(), 0.0, 0.0);
let js_flow = Vector3::new(0.0, 0.0, 1.0);
let e_field = self.ishe.convert(js_flow, js_inplane);
let y_component = e_field.dot(&Vector3::new(0.0, 1.0, 0.0));
Ok(y_component > 0.0)
}
pub fn validate_seebeck_coefficient_order(&self) -> Result<bool> {
let delta_t = 10.0;
let v_at_10k = self.predict_voltage(delta_t);
let slope_uv_per_k = (v_at_10k / delta_t) * 1.0e6;
let in_window = slope_uv_per_k.abs() >= 1.0e-12
&& slope_uv_per_k.abs() <= 1.0e6
&& slope_uv_per_k.is_finite();
Ok(in_window)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 0.30;
fn build() -> Uchida2008Validation {
Uchida2008Validation::new().expect("Uchida harness must build")
}
#[test]
fn test_constants_consistent() {
assert_eq!(DELTA_T_VALUES_K.len(), V_LSSE_MICROVOLT.len());
assert!(DELTA_T_VALUES_K.len() >= 4);
for &dt in DELTA_T_VALUES_K {
assert!(dt > 0.0);
}
for &v in V_LSSE_MICROVOLT {
assert!(v > 0.0);
}
}
#[test]
fn test_reference_data_roughly_linear() {
let slope0 = V_LSSE_MICROVOLT[0] / DELTA_T_VALUES_K[0];
for (v, dt) in V_LSSE_MICROVOLT.iter().zip(DELTA_T_VALUES_K.iter()) {
let slope = v / dt;
let rel = (slope - slope0).abs() / slope0;
assert!(
rel < 0.05,
"Reference data should be linear: slope = {:.3}, baseline {:.3}",
slope,
slope0
);
}
}
#[test]
fn test_build_succeeds() {
let v = build();
assert!(v.sse.l_s > 0.0);
assert!(v.ishe.theta_sh > 0.0);
assert!(v.interface.g_r > 0.0);
}
#[test]
fn test_linear_thermal_response_passes() {
let v = build();
let result = v
.validate_linear_thermal_response(TOL)
.expect("validation should run");
assert_eq!(result.n_points, DELTA_T_VALUES_K.len());
assert!(
result.max_relative_error < 0.10,
"Rescaled linearity check should be tight: {}",
result.summary()
);
assert!(result.passed);
}
#[test]
fn test_polarity_correct() {
let v = build();
let ok = v.validate_polarity().expect("polarity check should run");
assert!(ok, "Pt LSSE polarity should be positive along +ŷ");
}
#[test]
fn test_polarity_flips_for_negative_theta() {
let v = Uchida2008Validation {
sse: SpinSeebeck::yig_pt(),
ishe: InverseSpinHall::tungsten(),
interface: SpinInterface::yig_pt(),
};
let ok = v.validate_polarity().expect("polarity check should run");
assert!(!ok, "Negative θ_SH must flip ISHE polarity");
}
#[test]
fn test_seebeck_coefficient_in_order_window() {
let v = build();
let ok = v
.validate_seebeck_coefficient_order()
.expect("order check should run");
assert!(
ok,
"LSSE slope must be finite and lie inside the wide reasonableness window"
);
}
#[test]
fn test_predict_voltage_finite_and_scales_with_dt() {
let v = build();
let v1 = v.predict_voltage(1.0);
let v10 = v.predict_voltage(10.0);
assert!(v1.is_finite() && v1 > 0.0);
assert!(v10.is_finite() && v10 > 0.0);
let ratio = v10 / v1;
assert!(
(ratio - 10.0).abs() < 1e-9,
"Voltage should be linear in ΔT: ratio = {ratio}"
);
}
}