use crate::constants::GAMMA;
use crate::vector3::Vector3;
#[inline]
pub fn calc_dm_dt(m: Vector3<f64>, h_eff: Vector3<f64>, gamma: f64, alpha: f64) -> Vector3<f64> {
let precession = m.cross(&h_eff) * (-gamma);
let damping_term = m.cross(&precession) * alpha;
(precession + damping_term) * (1.0 / (1.0 + alpha * alpha))
}
#[inline]
pub fn zeeman_energy(m: Vector3<f64>, h: Vector3<f64>, ms: f64) -> f64 {
use crate::constants::MU_0;
-MU_0 * ms * m.dot(&h)
}
#[inline]
pub fn anisotropy_energy(m: Vector3<f64>, easy_axis: Vector3<f64>, k: f64) -> f64 {
let projection = m.dot(&easy_axis);
-k * projection * projection
}
#[inline]
pub fn exchange_energy(a_ex: f64, angle: f64, volume: f64, length_scale: f64) -> f64 {
a_ex * volume * angle * angle / (length_scale * length_scale)
}
pub struct LlgSolver {
pub alpha: f64,
pub gamma: f64,
pub dt: f64,
}
impl Default for LlgSolver {
fn default() -> Self {
Self {
alpha: 0.01,
gamma: GAMMA,
dt: 1.0e-13, }
}
}
impl LlgSolver {
pub fn new(alpha: f64, dt: f64) -> Self {
Self {
alpha,
gamma: GAMMA,
dt,
}
}
pub fn step_euler(&self, m: Vector3<f64>, h_eff: Vector3<f64>) -> Vector3<f64> {
let dm_dt = calc_dm_dt(m, h_eff, self.gamma, self.alpha);
(m + dm_dt * self.dt).normalize()
}
pub fn step_rk4(
&self,
m: Vector3<f64>,
h_eff_fn: impl Fn(Vector3<f64>) -> Vector3<f64>,
) -> Vector3<f64> {
let k1 = calc_dm_dt(m, h_eff_fn(m), self.gamma, self.alpha);
let m2 = (m + k1 * (self.dt * 0.5)).normalize();
let k2 = calc_dm_dt(m2, h_eff_fn(m2), self.gamma, self.alpha);
let m3 = (m + k2 * (self.dt * 0.5)).normalize();
let k3 = calc_dm_dt(m3, h_eff_fn(m3), self.gamma, self.alpha);
let m4 = (m + k3 * self.dt).normalize();
let k4 = calc_dm_dt(m4, h_eff_fn(m4), self.gamma, self.alpha);
let dm = (k1 + k2 * 2.0 + k3 * 2.0 + k4) * (self.dt / 6.0);
(m + dm).normalize()
}
pub fn step_heun(
&self,
m: Vector3<f64>,
h_eff_fn: impl Fn(Vector3<f64>) -> Vector3<f64>,
) -> Vector3<f64> {
let k1 = calc_dm_dt(m, h_eff_fn(m), self.gamma, self.alpha);
let m_pred = (m + k1 * self.dt).normalize();
let k2 = calc_dm_dt(m_pred, h_eff_fn(m_pred), self.gamma, self.alpha);
let dm = (k1 + k2) * (self.dt * 0.5);
(m + dm).normalize()
}
#[allow(dead_code)]
pub fn step_rk4_adaptive(
&self,
m: Vector3<f64>,
h_eff_fn: impl Fn(Vector3<f64>) -> Vector3<f64>,
tolerance: f64,
) -> (Vector3<f64>, f64, f64) {
let k1 = calc_dm_dt(m, h_eff_fn(m), self.gamma, self.alpha);
let m2 = (m + k1 * (self.dt * 0.5)).normalize();
let k2 = calc_dm_dt(m2, h_eff_fn(m2), self.gamma, self.alpha);
let m3 = (m + k2 * (self.dt * 0.5)).normalize();
let k3 = calc_dm_dt(m3, h_eff_fn(m3), self.gamma, self.alpha);
let m4 = (m + k3 * self.dt).normalize();
let k4 = calc_dm_dt(m4, h_eff_fn(m4), self.gamma, self.alpha);
let dm_rk4 = (k1 + k2 * 2.0 + k3 * 2.0 + k4) * (self.dt / 6.0);
let dm_rk5 = (k1 * (16.0 / 135.0) + k2 * (6656.0 / 12825.0) + k3 * (28561.0 / 56430.0)
- k4 * (9.0 / 50.0))
* self.dt;
let error = (dm_rk5 - dm_rk4).magnitude();
let safety = 0.9;
let dt_new = if error > tolerance {
self.dt * safety * (tolerance / error).powf(0.2)
} else {
self.dt * safety * (tolerance / error).powf(0.25).min(2.0)
};
let m_new = (m + dm_rk4).normalize();
(m_new, dt_new, error)
}
#[allow(dead_code)]
pub fn step_implicit(
&self,
m: Vector3<f64>,
h_eff: Vector3<f64>,
max_iter: usize,
) -> Vector3<f64> {
let mut m_new = m;
for _ in 0..max_iter {
let m_mid = (m + m_new) * 0.5;
let dm_dt = calc_dm_dt(m_mid.normalize(), h_eff, self.gamma, self.alpha);
let m_next = (m + dm_dt * self.dt).normalize();
if (m_next - m_new).magnitude() < 1e-10 {
return m_next;
}
m_new = m_next;
}
m_new
}
pub fn with_dt(mut self, dt: f64) -> Self {
self.dt = dt;
self
}
pub fn with_alpha(mut self, alpha: f64) -> Self {
self.alpha = alpha;
self
}
pub fn with_gamma(mut self, gamma: f64) -> Self {
self.gamma = gamma;
self
}
#[inline]
pub fn dt(&self) -> f64 {
self.dt
}
#[inline]
pub fn llg_derivative(&self, m: Vector3<f64>, h_eff: Vector3<f64>) -> Vector3<f64> {
calc_dm_dt(m, h_eff, self.gamma, self.alpha)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_llg_precession() {
let m = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 1.0);
let alpha = 0.01;
let dm_dt = calc_dm_dt(m, h, GAMMA, alpha);
assert!(dm_dt.dot(&m).abs() < 1e-6);
let precession_only = m.cross(&h) * (-GAMMA);
assert!(precession_only.dot(&h).abs() < 1e-10);
}
#[test]
fn test_magnetization_conservation() {
let solver = LlgSolver::default();
let m = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 1.0);
let m_new = solver.step_euler(m, h);
assert!((m_new.magnitude() - 1.0).abs() < 1e-10);
}
#[test]
fn test_zero_field_relaxation() {
let m = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 0.0);
let dm_dt = calc_dm_dt(m, h, GAMMA, 0.01);
assert!(dm_dt.magnitude() < 1e-10);
}
#[test]
fn test_rk4_vs_euler() {
let solver = LlgSolver::new(0.01, 1.0e-12);
let m0 = Vector3::new(1.0, 0.1, 0.0).normalize();
let h_const = Vector3::new(0.0, 0.0, 1.0);
let h_fn = |_m: Vector3<f64>| h_const;
let m_euler = solver.step_euler(m0, h_const);
let m_rk4 = solver.step_rk4(m0, h_fn);
assert!((m_rk4 - m_euler).magnitude() > 1e-15);
assert!((m_euler.magnitude() - 1.0).abs() < 1e-10);
assert!((m_rk4.magnitude() - 1.0).abs() < 1e-10);
}
#[test]
fn test_heun_method() {
let solver = LlgSolver::new(0.01, 1.0e-12);
let m0 = Vector3::new(0.707, 0.707, 0.0);
let h_const = Vector3::new(0.0, 0.0, 1.0);
let h_fn = |_m: Vector3<f64>| h_const;
let m_heun = solver.step_heun(m0, h_fn);
assert!((m_heun.magnitude() - 1.0).abs() < 1e-10);
assert!((m_heun - m0).magnitude() > 1e-15);
}
#[test]
fn test_heun_vs_euler_accuracy() {
let solver = LlgSolver::new(0.01, 1.0e-11);
let m0 = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 1.0);
let h_fn = |_m: Vector3<f64>| h;
let m_euler = solver.step_euler(m0, h);
let m_heun = solver.step_heun(m0, h_fn);
assert!((m_heun - m_euler).magnitude() > 1e-15);
}
#[test]
fn test_adaptive_rk4_error_estimate() {
let solver = LlgSolver::new(0.01, 1.0e-12);
let m0 = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 1.0);
let h_fn = |_m: Vector3<f64>| h;
let (m_new, dt_suggest, error) = solver.step_rk4_adaptive(m0, h_fn, 1.0e-8);
assert!((m_new.magnitude() - 1.0).abs() < 1e-10);
assert!(error >= 0.0);
assert!(dt_suggest > 0.0);
}
#[test]
fn test_implicit_convergence() {
let solver = LlgSolver::new(0.01, 1.0e-12);
let m0 = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 1.0);
let m_impl = solver.step_implicit(m0, h, 20);
assert!((m_impl.magnitude() - 1.0).abs() < 1e-10);
assert!((m_impl - m0).magnitude() > 1e-15);
}
#[test]
fn test_implicit_stability() {
let solver = LlgSolver::new(0.01, 1.0e-10); let m0 = Vector3::new(1.0, 0.0, 0.0);
let h = Vector3::new(0.0, 0.0, 10.0);
let m_impl = solver.step_implicit(m0, h, 50);
assert!((m_impl.magnitude() - 1.0).abs() < 1e-9);
}
#[test]
fn test_builder_pattern() {
let solver = LlgSolver::default()
.with_dt(1.0e-11)
.with_alpha(0.05)
.with_gamma(1.76e11);
assert_eq!(solver.dt, 1.0e-11);
assert_eq!(solver.alpha, 0.05);
assert_eq!(solver.gamma, 1.76e11);
}
#[test]
fn test_rk4_energy_conservation_tendency() {
let solver = LlgSolver::new(0.0, 1.0e-12); let m0 = Vector3::new(0.707, 0.707, 0.0);
let h = Vector3::new(0.0, 0.0, 1.0);
let h_fn = |_m: Vector3<f64>| h;
let mut m_euler = m0;
let mut m_rk4 = m0;
for _ in 0..10 {
m_euler = solver.step_euler(m_euler, h);
m_rk4 = solver.step_rk4(m_rk4, h_fn);
}
assert!((m_euler.magnitude() - 1.0).abs() < 1e-9);
assert!((m_rk4.magnitude() - 1.0).abs() < 1e-9);
let e0 = -m0.dot(&h);
let e_euler = -m_euler.dot(&h);
let e_rk4 = -m_rk4.dot(&h);
assert!((e_rk4 - e0).abs() <= (e_euler - e0).abs());
}
}