use super::Device;
use crate::constants::{GAMMA, MU_0};
use crate::dynamics::llg::calc_dm_dt;
use crate::error::{dimension_mismatch, Result};
use crate::vector3::Vector3;
#[derive(Debug, Clone, Copy, Default)]
pub struct CpuDevice {
pub n_threads: usize,
}
impl CpuDevice {
pub fn new() -> Self {
Self { n_threads: 0 }
}
pub fn with_threads(n_threads: usize) -> Self {
Self { n_threads }
}
}
#[inline]
fn to_vec3(a: [f64; 3]) -> Vector3<f64> {
Vector3::new(a[0], a[1], a[2])
}
#[inline]
fn from_vec3(v: Vector3<f64>) -> [f64; 3] {
[v.x, v.y, v.z]
}
#[inline]
fn renormalize(v: Vector3<f64>) -> Vector3<f64> {
let mag = v.magnitude();
if mag > 1.0e-30 {
Vector3::new(v.x / mag, v.y / mag, v.z / mag)
} else {
v
}
}
#[inline]
fn rk4_single(m: Vector3<f64>, h: Vector3<f64>, alpha: f64, dt: f64) -> Vector3<f64> {
let k1 = calc_dm_dt(m, h, GAMMA, alpha);
let k2 = calc_dm_dt(m + k1 * (dt * 0.5), h, GAMMA, alpha);
let k3 = calc_dm_dt(m + k2 * (dt * 0.5), h, GAMMA, alpha);
let k4 = calc_dm_dt(m + k3 * dt, h, GAMMA, alpha);
let m_new = m + (k1 + k2 * 2.0 + k3 * 2.0 + k4) * (dt / 6.0);
renormalize(m_new)
}
impl Device for CpuDevice {
fn name(&self) -> &'static str {
"cpu"
}
fn is_available(&self) -> bool {
true
}
fn step_llg_rk4(
&self,
spins: &mut [[f64; 3]],
h_eff: &[[f64; 3]],
alpha: f64,
dt: f64,
) -> Result<()> {
if spins.len() != h_eff.len() {
return Err(dimension_mismatch(
&format!("h_eff.len() = spins.len() = {}", spins.len()),
&format!("h_eff.len() = {}", h_eff.len()),
));
}
for (spin, h) in spins.iter_mut().zip(h_eff.iter()) {
let m = to_vec3(*spin);
let h_vec = to_vec3(*h);
*spin = from_vec3(rk4_single(m, h_vec, alpha, dt));
}
Ok(())
}
fn step_llg_rk4_multi(
&self,
spins: &mut [[f64; 3]],
h_eff: &[[f64; 3]],
alpha: f64,
dt: f64,
n_steps: usize,
) -> Result<()> {
if spins.len() != h_eff.len() {
return Err(dimension_mismatch(
&format!("h_eff.len() = spins.len() = {}", spins.len()),
&format!("h_eff.len() = {}", h_eff.len()),
));
}
for _ in 0..n_steps {
for (spin, h) in spins.iter_mut().zip(h_eff.iter()) {
let m = to_vec3(*spin);
let h_vec = to_vec3(*h);
*spin = from_vec3(rk4_single(m, h_vec, alpha, dt));
}
}
Ok(())
}
fn zeeman_energy(&self, spins: &[[f64; 3]], h: &[[f64; 3]], ms: f64) -> Result<f64> {
if spins.len() != h.len() {
return Err(dimension_mismatch(
&format!("h.len() = spins.len() = {}", spins.len()),
&format!("h.len() = {}", h.len()),
));
}
let mut acc = 0.0_f64;
for (m, hi) in spins.iter().zip(h.iter()) {
acc += m[0] * hi[0] + m[1] * hi[1] + m[2] * hi[2];
}
Ok(-MU_0 * ms * acc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_device_new_and_with_threads() {
let a = CpuDevice::new();
assert_eq!(a.n_threads, 0);
let b = CpuDevice::with_threads(8);
assert_eq!(b.n_threads, 8);
let c = CpuDevice::default();
assert_eq!(c.n_threads, 0);
}
#[test]
fn test_cpu_device_name_is_cpu() {
let dev = CpuDevice::new();
assert_eq!(dev.name(), "cpu");
}
#[test]
fn test_cpu_device_is_available_true() {
let dev = CpuDevice::new();
assert!(dev.is_available());
assert_eq!(dev.max_spins(), usize::MAX);
}
#[test]
fn test_step_llg_rk4_size_mismatch_returns_error() {
let dev = CpuDevice::new();
let mut spins = vec![[1.0, 0.0, 0.0]; 3];
let h_eff = vec![[0.0, 0.0, 1.0]; 2];
let result = dev.step_llg_rk4(&mut spins, &h_eff, 0.01, 1.0e-13);
assert!(result.is_err());
}
#[test]
fn test_step_llg_rk4_multi_size_mismatch_returns_error() {
let dev = CpuDevice::new();
let mut spins = vec![[1.0, 0.0, 0.0]; 3];
let h_eff = vec![[0.0, 0.0, 1.0]; 2];
let result = dev.step_llg_rk4_multi(&mut spins, &h_eff, 0.01, 1.0e-13, 5);
assert!(result.is_err());
}
#[test]
fn test_single_spin_larmor_preserves_norm() {
let dev = CpuDevice::new();
let mut spins = vec![[1.0_f64, 0.0, 0.0]];
let b = 1.0_f64; let h_eff = vec![[0.0, 0.0, b]];
let dt = 5.0e-14_f64;
dev.step_llg_rk4(&mut spins, &h_eff, 0.01, dt).unwrap();
let mag =
(spins[0][0] * spins[0][0] + spins[0][1] * spins[0][1] + spins[0][2] * spins[0][2])
.sqrt();
assert!((mag - 1.0).abs() < 1.0e-10);
}
#[test]
fn test_multi_step_runs_and_norm_preserved() {
let dev = CpuDevice::new();
let mut spins = vec![[0.7_f64, 0.0, 0.7]];
let n = (spins[0][0] * spins[0][0] + spins[0][1] * spins[0][1] + spins[0][2] * spins[0][2])
.sqrt();
spins[0][0] /= n;
spins[0][1] /= n;
spins[0][2] /= n;
let h_eff = vec![[0.0_f64, 0.0, 1.0]];
let alpha = 0.5_f64; let dt = 1.0e-13_f64;
dev.step_llg_rk4_multi(&mut spins, &h_eff, alpha, dt, 2000)
.unwrap();
let mag =
(spins[0][0] * spins[0][0] + spins[0][1] * spins[0][1] + spins[0][2] * spins[0][2])
.sqrt();
assert!((mag - 1.0).abs() < 1.0e-8);
assert!(spins[0][2] > 0.7);
}
#[test]
fn test_zeeman_energy_aligned_is_negative() {
let dev = CpuDevice::new();
let spins = vec![[0.0, 0.0, 1.0]];
let h = vec![[0.0, 0.0, 1.0]];
let ms = 8.0e5_f64; let e = dev.zeeman_energy(&spins, &h, ms).unwrap();
let expected = -MU_0 * ms * 1.0_f64;
assert!((e - expected).abs() < 1.0e-20);
assert!(e < 0.0);
}
#[test]
fn test_zeeman_energy_orthogonal_is_zero() {
let dev = CpuDevice::new();
let spins = vec![[1.0, 0.0, 0.0]];
let h = vec![[0.0, 0.0, 1.0]];
let ms = 8.0e5_f64;
let e = dev.zeeman_energy(&spins, &h, ms).unwrap();
assert!(e.abs() < 1.0e-25);
}
#[test]
fn test_zeeman_energy_size_mismatch_returns_error() {
let dev = CpuDevice::new();
let spins = vec![[1.0, 0.0, 0.0]; 5];
let h = vec![[0.0, 0.0, 1.0]; 4];
let ms = 8.0e5_f64;
let result = dev.zeeman_energy(&spins, &h, ms);
assert!(result.is_err());
}
#[test]
fn test_large_batch_runs() {
let dev = CpuDevice::new();
let n = 1000;
let mut spins = vec![[1.0_f64, 0.0, 0.0]; n];
let h_eff = vec![[0.0_f64, 0.0, 1.0]; n];
dev.step_llg_rk4_multi(&mut spins, &h_eff, 0.01, 1.0e-13, 10)
.unwrap();
for spin in &spins {
let mag = (spin[0] * spin[0] + spin[1] * spin[1] + spin[2] * spin[2]).sqrt();
assert!((mag - 1.0).abs() < 1.0e-9);
}
}
}