#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::constants::{E_CHARGE, GAMMA, HBAR, MU_0, MU_B};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DwMaterial {
#[cfg_attr(feature = "serde", serde(skip, default = "DwMaterial::default_name"))]
pub name: &'static str,
pub exchange_a: f64,
pub anisotropy_k: f64,
pub ms: f64,
pub alpha: f64,
pub thickness: f64,
pub polarization: f64,
pub beta: f64,
pub theta_sh: f64,
}
impl DwMaterial {
pub fn permalloy() -> Self {
Self {
name: "Permalloy",
exchange_a: 1.3e-11,
anisotropy_k: 1.0e3,
ms: 800.0e3,
alpha: 0.01,
thickness: 20.0e-9,
polarization: 0.40,
beta: 0.01,
theta_sh: 0.0,
}
}
pub fn co_pt_pma() -> Self {
Self {
name: "Co/Pt PMA",
exchange_a: 1.5e-11,
anisotropy_k: 1.5e6,
ms: 1.1e6,
alpha: 0.10,
thickness: 1.0e-9,
polarization: 0.50,
beta: 0.04,
theta_sh: 0.08,
}
}
pub fn cofeb_mgo_pma() -> Self {
Self {
name: "CoFeB/MgO PMA",
exchange_a: 2.0e-11,
anisotropy_k: 1.0e6,
ms: 1.1e6,
alpha: 0.01,
thickness: 1.5e-9,
polarization: 0.65,
beta: 0.01,
theta_sh: 0.0,
}
}
pub fn pt_co_alox() -> Self {
Self {
name: "Pt/Co/AlOx",
exchange_a: 1.6e-11,
anisotropy_k: 1.3e6,
ms: 1.0e6,
alpha: 0.50,
thickness: 0.6e-9,
polarization: 0.50,
beta: 0.02,
theta_sh: 0.15,
}
}
pub fn k_eff(&self) -> f64 {
self.anisotropy_k - 0.5 * MU_0 * self.ms * self.ms
}
#[cfg(feature = "serde")]
fn default_name() -> &'static str {
"custom"
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct WalkerBreakdown {
pub material: DwMaterial,
}
impl WalkerBreakdown {
pub fn new(material: DwMaterial) -> Self {
Self { material }
}
pub fn dw_width(&self) -> f64 {
let k_eff = self.material.k_eff();
std::f64::consts::PI * (self.material.exchange_a / k_eff).sqrt()
}
pub fn walker_threshold_field(&self) -> f64 {
self.material.alpha * self.material.ms * 0.5
}
pub fn walker_threshold_tesla(&self) -> f64 {
MU_0 * self.walker_threshold_field()
}
pub fn walker_velocity(&self) -> f64 {
GAMMA * self.dw_width() * 0.5 * MU_0 * self.material.ms
}
pub fn velocity_below_walker(&self, h_field: f64) -> f64 {
GAMMA * self.dw_width() * MU_0 * h_field / self.material.alpha
}
pub fn velocity_above_walker(&self, h_field: f64) -> f64 {
GAMMA * self.dw_width() * MU_0 * h_field / (1.0 + self.material.alpha * self.material.alpha)
}
pub fn velocity(&self, h_field: f64) -> f64 {
let h_abs = h_field.abs();
let h_w = self.walker_threshold_field();
let sign = if h_field >= 0.0 { 1.0 } else { -1.0 };
if h_abs <= h_w {
sign * self.velocity_below_walker(h_abs)
} else {
sign * self.velocity_above_walker(h_abs)
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DwSttDynamics {
pub material: DwMaterial,
}
impl DwSttDynamics {
pub fn new(material: DwMaterial) -> Self {
Self { material }
}
pub fn dw_width(&self) -> f64 {
let k_eff = self.material.k_eff();
std::f64::consts::PI * (self.material.exchange_a / k_eff).sqrt()
}
pub fn walker_threshold_field(&self) -> f64 {
self.material.alpha * self.material.ms * 0.5
}
pub fn walker_velocity(&self) -> f64 {
GAMMA * self.dw_width() * 0.5 * MU_0 * self.material.ms
}
pub fn adiabatic_velocity(&self, current_density: f64) -> f64 {
self.material.polarization * current_density * MU_B / (E_CHARGE * self.material.ms)
}
pub fn velocity(&self, current_density: f64, h_applied: f64) -> f64 {
let alpha = self.material.alpha;
let beta = self.material.beta;
let v_s = self.adiabatic_velocity(current_density);
let v_h = GAMMA * self.dw_width() * MU_0 * h_applied;
((1.0 + alpha * beta) * v_s + (beta - alpha) * v_h) / (1.0 + alpha * alpha)
}
pub fn walker_current_density(&self) -> f64 {
let v_w = self.walker_velocity();
v_w * E_CHARGE * self.material.ms / (self.material.polarization * MU_B)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DwSotDynamics {
pub material: DwMaterial,
}
impl DwSotDynamics {
pub fn new(material: DwMaterial) -> Self {
Self { material }
}
pub fn dw_width(&self) -> f64 {
let k_eff = self.material.k_eff();
std::f64::consts::PI * (self.material.exchange_a / k_eff).sqrt()
}
pub fn walker_threshold_field(&self) -> f64 {
self.material.alpha * self.material.ms * 0.5
}
pub fn dl_sot_field(&self, current_density: f64) -> f64 {
self.material.theta_sh * HBAR * current_density
/ (2.0 * E_CHARGE * MU_0 * self.material.ms * self.material.thickness)
}
pub fn velocity(&self, current_density: f64) -> f64 {
let h_dl = self.dl_sot_field(current_density);
GAMMA * self.dw_width() * MU_0 * h_dl / self.material.alpha
}
pub fn velocity_with_field(&self, current_density: f64, h_applied: f64) -> f64 {
let h_dl = self.dl_sot_field(current_density);
GAMMA * self.dw_width() * MU_0 * (h_dl + h_applied) / self.material.alpha
}
pub fn walker_current_density(&self) -> f64 {
let h_w = self.walker_threshold_field();
2.0 * E_CHARGE
* MU_0
* self.material.ms
* self.material.thickness
* self.material.alpha
* h_w
/ (self.material.theta_sh * HBAR)
}
pub fn velocity_curve(&self, j_min: f64, j_max: f64, n_points: usize) -> Vec<(f64, f64)> {
let n = n_points.max(2);
let j_w = self.walker_current_density();
let v_w =
GAMMA * self.dw_width() * MU_0 * self.walker_threshold_field() / self.material.alpha;
(0..n)
.map(|i| {
let j = j_min + (j_max - j_min) * (i as f64) / ((n - 1) as f64);
let v = if j.abs() <= j_w {
self.velocity(j)
} else {
let sign = if j >= 0.0 { 1.0 } else { -1.0 };
sign * v_w
};
(j, v)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_material_permalloy_k_eff_positive() {
let mat = DwMaterial::permalloy();
let _ = mat.k_eff();
assert!(mat.ms > 0.0);
}
#[test]
fn test_material_co_pt_k_eff_positive() {
let mat = DwMaterial::co_pt_pma();
assert!(
mat.k_eff() > 0.0,
"Co/Pt PMA must have K_eff > 0 (got {})",
mat.k_eff()
);
}
#[test]
fn test_material_pt_co_alox_k_eff_positive() {
let mat = DwMaterial::pt_co_alox();
assert!(
mat.k_eff() > 0.0,
"Pt/Co/AlOx must have K_eff > 0 (got {})",
mat.k_eff()
);
}
#[test]
fn test_material_cofeb_k_eff_positive() {
let mat = DwMaterial::cofeb_mgo_pma();
assert!(
mat.k_eff() > 0.0,
"CoFeB/MgO must have K_eff > 0 (got {})",
mat.k_eff()
);
}
#[test]
fn test_walker_dw_width_co_pt_nm_scale() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let width_nm = wb.dw_width() * 1.0e9;
assert!(
width_nm > 1.0 && width_nm < 100.0,
"Co/Pt DW width should be 1–100 nm, got {:.2} nm",
width_nm
);
}
#[test]
fn test_walker_dw_width_pt_co_alox_around_15nm() {
let wb = WalkerBreakdown::new(DwMaterial::pt_co_alox());
let width_nm = wb.dw_width() * 1.0e9;
assert!(
width_nm > 10.0 && width_nm < 25.0,
"Pt/Co/AlOx DW width should be ~15 nm, got {:.2} nm",
width_nm
);
}
#[test]
fn test_walker_threshold_field_co_pt_mt_range() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let b_milli_tesla = wb.walker_threshold_tesla() * 1.0e3;
assert!(
b_milli_tesla > 10.0 && b_milli_tesla < 500.0,
"Walker threshold should be in mT–hundreds-of-mT range, got {:.2} mT",
b_milli_tesla
);
}
#[test]
fn test_walker_threshold_field_permalloy_milli_tesla_range() {
let wb = WalkerBreakdown::new(DwMaterial::permalloy());
let b_milli_tesla = wb.walker_threshold_tesla() * 1.0e3;
assert!(
b_milli_tesla > 0.5 && b_milli_tesla < 100.0,
"Permalloy Walker field should be in mT range, got {:.3} mT",
b_milli_tesla
);
}
#[test]
fn test_walker_velocity_co_pt_physical_range() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let v_w = wb.walker_velocity();
assert!(
v_w > 10.0 && v_w < 5000.0,
"Co/Pt Walker velocity should be 10–5000 m/s, got {:.2} m/s",
v_w
);
}
#[test]
fn test_walker_velocity_equal_to_velocity_at_threshold() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let h_w = wb.walker_threshold_field();
let v_at_threshold = wb.velocity_below_walker(h_w);
let v_w = wb.walker_velocity();
let rel_err = (v_at_threshold - v_w).abs() / v_w;
assert!(
rel_err < 1.0e-12,
"v_below_walker(H_W) must equal v_W, rel_err = {:.2e}",
rel_err
);
}
#[test]
fn test_walker_velocity_linear_below_threshold() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let h_w = wb.walker_threshold_field();
let v1 = wb.velocity_below_walker(h_w * 0.5);
let v2 = wb.velocity_below_walker(h_w * 1.0);
let ratio = v2 / v1;
assert!(
(ratio - 2.0).abs() < 1.0e-10,
"Below-Walker velocity must be linear in H, ratio = {:.6}",
ratio
);
}
#[test]
fn test_velocity_auto_regime_selection() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let h_w = wb.walker_threshold_field();
let h_sub = h_w * 0.9;
assert!(
(wb.velocity(h_sub) - wb.velocity_below_walker(h_sub)).abs() < 1.0e-30,
"velocity() must use below-Walker formula below threshold"
);
let h_sup = h_w * 1.5;
assert!(
(wb.velocity(h_sup) - wb.velocity_above_walker(h_sup)).abs() < 1.0e-30,
"velocity() must use above-Walker formula above threshold"
);
}
#[test]
fn test_velocity_sign_follows_field_sign() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let h = wb.walker_threshold_field() * 0.5;
assert!(wb.velocity(h) > 0.0, "positive field → positive velocity");
assert!(wb.velocity(-h) < 0.0, "negative field → negative velocity");
}
#[test]
fn test_above_walker_velocity_smaller_than_walker_velocity() {
let wb = WalkerBreakdown::new(DwMaterial::co_pt_pma());
let h_w = wb.walker_threshold_field();
let v_above = wb.velocity_above_walker(2.0 * h_w);
let v_w = wb.walker_velocity();
assert!(
v_above < v_w * 2.5,
"above-Walker velocity should not greatly exceed Walker velocity, v_above={:.2}, v_W={:.2}",
v_above, v_w
);
}
#[test]
fn test_stt_adiabatic_velocity_linear_in_j() {
let stt = DwSttDynamics::new(DwMaterial::co_pt_pma());
let j1 = 1.0e11;
let j2 = 2.0e11;
let v1 = stt.adiabatic_velocity(j1);
let v2 = stt.adiabatic_velocity(j2);
let ratio = v2 / v1;
assert!(
(ratio - 2.0).abs() < 1.0e-10,
"Adiabatic velocity must be linear in J, ratio = {:.6}",
ratio
);
}
#[test]
fn test_stt_adiabatic_velocity_physical_scale() {
let stt = DwSttDynamics::new(DwMaterial::co_pt_pma());
let v = stt.adiabatic_velocity(1.0e12);
assert!(
v > 1.0 && v < 1000.0,
"Adiabatic velocity at 1e12 A/m² should be 1–1000 m/s, got {:.2} m/s",
v
);
}
#[test]
fn test_stt_velocity_zero_current_zero_field() {
let stt = DwSttDynamics::new(DwMaterial::co_pt_pma());
let v = stt.velocity(0.0, 0.0);
assert!(
v.abs() < 1.0e-30,
"Zero current + zero field → zero velocity"
);
}
#[test]
fn test_stt_velocity_agrees_with_adiabatic_at_small_alpha_beta() {
let mat = DwMaterial {
name: "test",
exchange_a: 1.5e-11,
anisotropy_k: 1.5e6,
ms: 1.1e6,
alpha: 1.0e-4,
thickness: 1.0e-9,
polarization: 0.50,
beta: 1.0e-4,
theta_sh: 0.0,
};
let stt = DwSttDynamics::new(mat);
let j = 1.0e12;
let v_s = stt.adiabatic_velocity(j);
let v = stt.velocity(j, 0.0);
let rel_err = (v - v_s).abs() / v_s;
assert!(
rel_err < 1.0e-6,
"At α=β→0 full velocity must equal v_s; rel_err = {:.2e}",
rel_err
);
}
#[test]
fn test_stt_walker_current_density_positive() {
let stt = DwSttDynamics::new(DwMaterial::co_pt_pma());
let j_w = stt.walker_current_density();
assert!(j_w > 0.0, "Walker current density must be positive");
assert!(
j_w > 1.0e10 && j_w < 1.0e15,
"Walker current density out of expected range: {:.2e} A/m²",
j_w
);
}
#[test]
fn test_sot_dl_field_linear_in_j() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let j1 = 1.0e11;
let j2 = 3.0e11;
let h1 = sot.dl_sot_field(j1);
let h2 = sot.dl_sot_field(j2);
let ratio = h2 / h1;
assert!(
(ratio - 3.0).abs() < 1.0e-10,
"DL-SOT field must be linear in J, ratio = {:.6}",
ratio
);
}
#[test]
fn test_sot_dl_field_physical_scale() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let h_dl = sot.dl_sot_field(3.0e11);
assert!(
h_dl > 1.0e3 && h_dl < 1.0e8,
"DL-SOT field should be in kA/m–MA/m range, got {:.2e} A/m",
h_dl
);
}
#[test]
fn test_sot_velocity_linear_in_j_below_walker() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let j_w = sot.walker_current_density();
let j1 = j_w * 0.1;
let j2 = j_w * 0.2;
let v1 = sot.velocity(j1);
let v2 = sot.velocity(j2);
let ratio = v2 / v1;
assert!(
(ratio - 2.0).abs() < 1.0e-10,
"SOT velocity must be linear in J below Walker, ratio = {:.6}",
ratio
);
}
#[test]
fn test_sot_velocity_zero_at_zero_current() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let v = sot.velocity(0.0);
assert!(v.abs() < 1.0e-30, "Zero current → zero velocity");
}
#[test]
fn test_sot_velocity_with_field_additive() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let j = 1.0e11;
let v_no_field = sot.velocity(j);
let v_with_zero = sot.velocity_with_field(j, 0.0);
assert!(
(v_no_field - v_with_zero).abs() < 1.0e-30,
"velocity_with_field(j, 0) must equal velocity(j)"
);
}
#[test]
fn test_sot_walker_current_density_positive() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let j_w = sot.walker_current_density();
assert!(j_w > 0.0, "SOT Walker current density must be positive");
assert!(
j_w > 1.0e10 && j_w < 1.0e16,
"SOT Walker current density out of plausible range: {:.2e} A/m²",
j_w
);
}
#[test]
fn test_sot_velocity_curve_length_and_ordering() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let curve = sot.velocity_curve(0.0, 3.0e11, 10);
assert_eq!(curve.len(), 10, "Curve must have exactly 10 points");
assert!(curve[0].0.abs() < 1.0, "First J should be ~0");
assert!((curve[9].0 - 3.0e11).abs() < 1.0, "Last J should be ~3e11");
}
#[test]
fn test_sot_velocity_curve_monotone_below_walker() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let j_w = sot.walker_current_density();
let curve = sot.velocity_curve(0.0, j_w * 0.9, 20);
for i in 1..curve.len() {
assert!(
curve[i].1 >= curve[i - 1].1,
"Velocity curve must be monotonically non-decreasing below Walker: v[{}]={:.2} < v[{}]={:.2}",
i, curve[i].1, i - 1, curve[i - 1].1
);
}
}
#[test]
fn test_sot_velocity_pt_co_alox_reasonable_scale() {
let sot = DwSotDynamics::new(DwMaterial::pt_co_alox());
let v = sot.velocity(3.0e11);
assert!(
v > 1.0 && v < 1000.0,
"Pt/Co/AlOx DW velocity at 3e11 A/m² should be 1–1000 m/s, got {:.2} m/s",
v
);
}
}