use crate::constants::GAMMA;
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct ChainParameters {
pub a_ex: f64,
pub ms: f64,
pub alpha: f64,
pub cell_size: f64,
pub mu0: f64,
}
impl Default for ChainParameters {
fn default() -> Self {
Self {
a_ex: 3.7e-12, ms: 1.4e5, alpha: 0.0001, cell_size: 5.0e-9, mu0: 1.256637e-6,
}
}
}
impl ChainParameters {
pub fn yig() -> Self {
Self::default()
}
pub fn permalloy() -> Self {
Self {
a_ex: 1.3e-11,
ms: 8.0e5,
alpha: 0.01,
..Self::default()
}
}
pub fn max_stable_dt(&self) -> f64 {
let base_dt = 1.0e-13;
let reference_size = 5.0e-9; let scaling = (self.cell_size / reference_size).powi(2);
base_dt * scaling
}
}
#[derive(Debug, Clone)]
pub struct SpinChain {
pub spins: Vec<Vector3<f64>>,
pub n_cells: usize,
pub params: ChainParameters,
}
impl SpinChain {
pub fn new(n_cells: usize, params: ChainParameters) -> Self {
let spins = vec![Vector3::new(1.0, 0.0, 0.0); n_cells];
Self {
spins,
n_cells,
params,
}
}
pub fn new_with_noise(n_cells: usize, params: ChainParameters, noise_amplitude: f64) -> Self {
let mut spins = Vec::with_capacity(n_cells);
for i in 0..n_cells {
let noise = (i as f64 * 0.1).sin() * noise_amplitude;
spins.push(Vector3::new(1.0, noise, 0.0).normalize());
}
Self {
spins,
n_cells,
params,
}
}
pub fn calc_effective_field(&self, idx: usize, h_ext: Vector3<f64>) -> Vector3<f64> {
let mut h_eff = h_ext;
h_eff = h_eff + self.calc_exchange_field(idx);
h_eff
}
fn calc_exchange_field(&self, idx: usize) -> Vector3<f64> {
let m_i = self.spins[idx];
let m_prev = if idx == 0 { m_i } else { self.spins[idx - 1] };
let m_next = if idx == self.n_cells - 1 {
m_i
} else {
self.spins[idx + 1]
};
let laplacian = (m_next + m_prev - m_i * 2.0) * (1.0 / self.params.cell_size.powi(2));
let coeff_ex = 2.0 * self.params.a_ex / (self.params.mu0 * self.params.ms);
laplacian * coeff_ex
}
pub fn calc_llg_torque(&self, idx: usize, h_eff: Vector3<f64>) -> Vector3<f64> {
let m = self.spins[idx];
let alpha = self.params.alpha;
let m_cross_h = m.cross(&h_eff);
let term1 = m_cross_h;
let term2 = m.cross(&m_cross_h);
let prefactor = -GAMMA / (1.0 + alpha * alpha);
(term1 + term2 * alpha) * prefactor
}
pub fn length(&self) -> f64 {
self.n_cells as f64 * self.params.cell_size
}
pub fn average_magnetization(&self) -> Vector3<f64> {
let sum = self
.spins
.iter()
.fold(Vector3::new(0.0, 0.0, 0.0), |acc, &m| acc + m);
sum * (1.0 / self.n_cells as f64)
}
pub fn reset_to_z(&mut self) {
for i in 0..self.n_cells {
self.spins[i] = Vector3::new(0.0, 0.0, 1.0);
}
}
#[allow(clippy::needless_range_loop)]
pub fn evolve_heun(&mut self, h_ext: Vector3<f64>, dt: f64) {
let mut k1 = Vec::with_capacity(self.n_cells);
for i in 0..self.n_cells {
let h_eff = self.calc_effective_field(i, h_ext);
let torque = self.calc_llg_torque(i, h_eff);
k1.push(torque);
}
let original_spins = self.spins.clone();
for i in 0..self.n_cells {
self.spins[i] = (self.spins[i] + k1[i] * dt).normalize();
}
let mut k2 = Vec::with_capacity(self.n_cells);
for i in 0..self.n_cells {
let h_eff = self.calc_effective_field(i, h_ext);
let torque = self.calc_llg_torque(i, h_eff);
k2.push(torque);
}
for i in 0..self.n_cells {
let dm_dt = (k1[i] + k2[i]) * 0.5;
self.spins[i] = (original_spins[i] + dm_dt * dt).normalize();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_creation() {
let chain = SpinChain::new(100, ChainParameters::default());
assert_eq!(chain.n_cells, 100);
assert_eq!(chain.spins.len(), 100);
}
#[test]
fn test_spin_normalization() {
let chain = SpinChain::new_with_noise(50, ChainParameters::default(), 0.1);
for spin in &chain.spins {
let mag = spin.magnitude();
assert!((mag - 1.0).abs() < 1e-10);
}
}
#[test]
fn test_exchange_field_uniform() {
let chain = SpinChain::new(10, ChainParameters::default());
let h_ex = chain.calc_exchange_field(5);
assert!(h_ex.magnitude() < 1e-10);
}
#[test]
fn test_max_stable_dt() {
let params = ChainParameters::yig();
let dt_max = params.max_stable_dt();
assert!(dt_max > 0.0);
assert!((dt_max - 1.0e-13).abs() < 1.0e-14);
let params_large = ChainParameters {
cell_size: 10.0e-9,
..params
};
assert!(params_large.max_stable_dt() > dt_max);
}
#[test]
fn test_llg_perpendicular_to_m() {
let chain = SpinChain::new(10, ChainParameters::default());
let h_ext = Vector3::new(0.0, 0.0, 1000.0);
let h_eff = chain.calc_effective_field(5, h_ext);
let torque = chain.calc_llg_torque(5, h_eff);
assert!(torque.dot(&chain.spins[5]).abs() < 1e-6);
}
}