use crate::autodiff::tape::{Tape, Var};
use crate::constants::{GAMMA, MU_0};
pub fn kittel_frequency_diff<'t>(_tape: &'t Tape, ms: Var<'t>, h_ext: Var<'t>) -> Var<'t> {
let gamma = GAMMA.abs();
let mu0 = MU_0;
let h_plus_ms = h_ext + ms;
let product = h_ext * h_plus_ms;
let sq = product.sqrt();
sq * (gamma * mu0)
}
pub fn zeeman_energy_diff<'t>(
_tape: &'t Tape,
mx: Var<'t>,
my: Var<'t>,
mz: Var<'t>,
hx: f64,
hy: f64,
hz: f64,
ms: Var<'t>,
) -> Var<'t> {
let mu0 = MU_0;
let mdoth = mx * hx + my * hy + mz * hz;
let neg_mu0 = -mu0;
ms * mdoth * neg_mu0
}
pub fn exchange_energy_diff<'t>(
_tape: &'t Tape,
m1x: Var<'t>,
m1y: Var<'t>,
m1z: Var<'t>,
m2x: Var<'t>,
m2y: Var<'t>,
m2z: Var<'t>,
a_ex: Var<'t>,
lattice_const: f64,
) -> Var<'t> {
let dot = m1x * m2x + m1y * m2y + m1z * m2z;
let coeff = -2.0 / (lattice_const * lattice_const);
a_ex * dot * coeff
}
pub fn dmi_energy_diff<'t>(
_tape: &'t Tape,
m1x: Var<'t>,
m1y: Var<'t>,
_m1z: Var<'t>,
m2x: Var<'t>,
m2y: Var<'t>,
_m2z: Var<'t>,
d: Var<'t>,
) -> Var<'t> {
let cross_z = m1x * m2y - m1y * m2x;
d * cross_z
}
pub fn anisotropy_energy_diff<'t>(_tape: &'t Tape, mz: Var<'t>, ku: Var<'t>) -> Var<'t> {
let mz_sq = mz * mz;
ku * mz_sq * (-1.0)
}
pub fn llg_torque_norm_diff<'t>(
_tape: &'t Tape,
mx: Var<'t>,
my: Var<'t>,
mz: Var<'t>,
heff_x: f64,
heff_y: f64,
heff_z: f64,
_alpha: Var<'t>,
gamma: f64,
) -> Var<'t> {
let cross_x = my * heff_z - mz * heff_y;
let cross_y = mz * heff_x - mx * heff_z;
let cross_z = mx * heff_y - my * heff_x;
let norm_sq = cross_x * cross_x + cross_y * cross_y + cross_z * cross_z;
norm_sq.sqrt() * gamma
}
#[cfg(test)]
mod tests {
use super::*;
use crate::autodiff::tape::finite_diff_grad;
use crate::constants::{GAMMA, MU_0};
const TOL: f64 = 1e-9;
#[test]
fn test_kittel_value_matches_formula() {
let ms_val = 1.4e5_f64; let h_val = 1.592e5_f64;
let tape = Tape::new();
let ms = Var::leaf(&tape, ms_val);
let h = Var::leaf(&tape, h_val);
let omega = kittel_frequency_diff(&tape, ms, h);
let expected = GAMMA.abs() * MU_0 * (h_val * (h_val + ms_val)).sqrt();
assert!(
(omega.value() - expected).abs() < TOL * expected.abs().max(1.0),
"Kittel value mismatch: got {}, expected {}",
omega.value(),
expected
);
}
#[test]
fn test_kittel_grad_wrt_ms() {
let ms_val = 1.4e5_f64;
let h_val = 1.592e5_f64;
let tape = Tape::new();
let ms = Var::leaf(&tape, ms_val);
let h = Var::leaf(&tape, h_val);
let omega = kittel_frequency_diff(&tape, ms, h);
tape.backward(omega);
let ad_grad = ms.grad();
let fd_grad = finite_diff_grad(
|ms_i| {
let t2 = Tape::new();
let ms2 = Var::leaf(&t2, ms_i);
let h2 = Var::leaf(&t2, h_val);
kittel_frequency_diff(&t2, ms2, h2).value()
},
ms_val,
ms_val * 1e-5,
);
let rel_err = (ad_grad - fd_grad).abs() / fd_grad.abs().max(1.0);
assert!(
rel_err < 1e-5,
"dω/dms AD ({}) vs FD ({}), rel_err={}",
ad_grad,
fd_grad,
rel_err
);
}
#[test]
fn test_kittel_grad_wrt_hext() {
let ms_val = 1.4e5_f64;
let h_val = 1.592e5_f64;
let tape = Tape::new();
let ms = Var::leaf(&tape, ms_val);
let h = Var::leaf(&tape, h_val);
let omega = kittel_frequency_diff(&tape, ms, h);
tape.backward(omega);
let ad_grad = h.grad();
let fd_grad = finite_diff_grad(
|hi| {
let t2 = Tape::new();
let ms2 = Var::leaf(&t2, ms_val);
let h2 = Var::leaf(&t2, hi);
kittel_frequency_diff(&t2, ms2, h2).value()
},
h_val,
h_val * 1e-5,
);
let rel_err = (ad_grad - fd_grad).abs() / fd_grad.abs().max(1.0);
assert!(
rel_err < 1e-5,
"dω/dh AD ({}) vs FD ({}), rel_err={}",
ad_grad,
fd_grad,
rel_err
);
}
#[test]
fn test_zeeman_value() {
let ms_val = 1.4e5_f64;
let (mx_val, my_val, mz_val) = (0.6, 0.8, 0.0);
let (hx, hy, hz) = (1e5_f64, 0.0, 0.0);
let tape = Tape::new();
let mx = Var::leaf(&tape, mx_val);
let my = Var::leaf(&tape, my_val);
let mz = Var::leaf(&tape, mz_val);
let ms = Var::leaf(&tape, ms_val);
let e = zeeman_energy_diff(&tape, mx, my, mz, hx, hy, hz, ms);
let mdoth = mx_val * hx + my_val * hy + mz_val * hz;
let expected = -MU_0 * ms_val * mdoth;
assert!(
(e.value() - expected).abs() < TOL * expected.abs().max(1.0),
"Zeeman energy: got {}, expected {}",
e.value(),
expected
);
}
#[test]
fn test_exchange_orthogonal_spins() {
let tape = Tape::new();
let m1x = Var::leaf(&tape, 1.0);
let m1y = Var::leaf(&tape, 0.0);
let m1z = Var::leaf(&tape, 0.0);
let m2x = Var::leaf(&tape, 0.0);
let m2y = Var::leaf(&tape, 1.0);
let m2z = Var::leaf(&tape, 0.0);
let a_ex = Var::leaf(&tape, 3.5e-12_f64); let lattice_const = 3.5e-10_f64;
let e = exchange_energy_diff(&tape, m1x, m1y, m1z, m2x, m2y, m2z, a_ex, lattice_const);
assert!(
e.value().abs() < 1e-30,
"exchange energy of orthogonal spins should be 0"
);
}
#[test]
fn test_exchange_parallel_spins() {
let a_ex_val = 3.5e-12_f64;
let a = 3.5e-10_f64;
let tape = Tape::new();
let m1x = Var::leaf(&tape, 1.0);
let m1y = Var::leaf(&tape, 0.0);
let m1z = Var::leaf(&tape, 0.0);
let m2x = Var::leaf(&tape, 1.0);
let m2y = Var::leaf(&tape, 0.0);
let m2z = Var::leaf(&tape, 0.0);
let a_ex = Var::leaf(&tape, a_ex_val);
let e = exchange_energy_diff(&tape, m1x, m1y, m1z, m2x, m2y, m2z, a_ex, a);
let expected = -2.0 * a_ex_val / (a * a); assert!(
(e.value() - expected).abs() < 1e-3 * expected.abs().max(1.0),
"parallel exchange: got {}, expected {}",
e.value(),
expected
);
}
#[test]
fn test_dmi_value_and_grad() {
let d_val = 1.5e-3_f64;
let tape = Tape::new();
let m1x = Var::leaf(&tape, 1.0);
let m1y = Var::leaf(&tape, 0.0);
let m1z = Var::leaf(&tape, 0.0);
let m2x = Var::leaf(&tape, 0.0);
let m2y = Var::leaf(&tape, 1.0);
let m2z = Var::leaf(&tape, 0.0);
let d = Var::leaf(&tape, d_val);
let e = dmi_energy_diff(&tape, m1x, m1y, m1z, m2x, m2y, m2z, d);
tape.backward(e);
let expected_val = d_val * 1.0; assert!(
(e.value() - expected_val).abs() < TOL * expected_val.abs().max(1.0),
"DMI value: got {}, expected {}",
e.value(),
expected_val
);
assert!(
(d.grad() - 1.0).abs() < 1e-12,
"DMI ∂E/∂D should be 1.0, got {}",
d.grad()
);
}
#[test]
fn test_anisotropy_value() {
let ku_val = 5e4_f64;
let tape = Tape::new();
let mz = Var::leaf(&tape, 1.0);
let ku = Var::leaf(&tape, ku_val);
let e = anisotropy_energy_diff(&tape, mz, ku);
let expected = -ku_val * 1.0; assert!(
(e.value() - expected).abs() < TOL * expected.abs().max(1.0),
"anisotropy: got {}, expected {}",
e.value(),
expected
);
}
#[test]
fn test_anisotropy_grad_mz() {
let ku_val = 5e4_f64;
let mz_val = 0.7_f64;
let tape = Tape::new();
let mz = Var::leaf(&tape, mz_val);
let ku = Var::leaf(&tape, ku_val);
let e = anisotropy_energy_diff(&tape, mz, ku);
tape.backward(e);
let expected_grad = -2.0 * ku_val * mz_val; assert!(
(mz.grad() - expected_grad).abs() < 1.0,
"anisotropy ∂E/∂mz: got {}, expected {}",
mz.grad(),
expected_grad
);
}
#[test]
fn test_llg_torque_norm_value() {
let h_mag = 1e5_f64;
let tape = Tape::new();
let mx = Var::leaf(&tape, 1.0);
let my = Var::leaf(&tape, 0.0);
let mz = Var::leaf(&tape, 0.0);
let alpha = Var::leaf(&tape, 0.01);
let torque = llg_torque_norm_diff(&tape, mx, my, mz, 0.0, h_mag, 0.0, alpha, GAMMA.abs());
tape.backward(torque);
let expected = GAMMA.abs() * h_mag;
let rel_err = (torque.value() - expected).abs() / expected;
assert!(
rel_err < 1e-10,
"LLG torque: got {}, expected {}",
torque.value(),
expected
);
}
#[test]
fn test_total_energy_gradient() {
let ku_val = 4.0e4_f64;
let ms_val = 8.0e5_f64;
let hz_val = 1.0e5_f64;
let mz_val = 0.5_f64;
let tape = Tape::new();
let mx = Var::leaf(&tape, 0.0);
let my = Var::leaf(&tape, 0.0);
let mz = Var::leaf(&tape, mz_val);
let ms = Var::leaf(&tape, ms_val);
let ku = Var::leaf(&tape, ku_val);
let e_z = zeeman_energy_diff(&tape, mx, my, mz, 0.0, 0.0, hz_val, ms);
let e_a = anisotropy_energy_diff(&tape, mz, ku);
let total = e_z + e_a;
tape.backward(total);
let expected = -MU_0 * ms_val * hz_val + (-2.0 * ku_val * mz_val);
let rel_err = (mz.grad() - expected).abs() / expected.abs().max(1.0);
assert!(
rel_err < 1e-8,
"total ∂E/∂mz: got {}, expected {}",
mz.grad(),
expected
);
}
}