use super::chain::SpinChain;
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct RfExcitation {
pub frequency: f64,
pub amplitude: f64,
pub direction: Vector3<f64>,
pub excitation_range: (usize, usize),
}
impl RfExcitation {
pub fn new(
frequency: f64,
amplitude: f64,
direction: Vector3<f64>,
excitation_range: (usize, usize),
) -> Self {
Self {
frequency,
amplitude,
direction: direction.normalize(),
excitation_range,
}
}
pub fn field_at(&self, t: f64) -> Vector3<f64> {
let omega = 2.0 * std::f64::consts::PI * self.frequency;
self.direction * (self.amplitude * (omega * t).sin())
}
pub fn applies_to(&self, idx: usize) -> bool {
idx >= self.excitation_range.0 && idx < self.excitation_range.1
}
}
pub struct MagnonSolver {
pub bias_field: Vector3<f64>,
pub rf_excitation: Option<RfExcitation>,
pub dt: f64,
pub time: f64,
workspace_k1: Vec<Vector3<f64>>,
workspace_k2: Vec<Vector3<f64>>,
workspace_k3: Vec<Vector3<f64>>,
workspace_k4: Vec<Vector3<f64>>,
workspace_spins: Vec<Vector3<f64>>,
}
impl MagnonSolver {
pub fn new(dt: f64, bias_field: Vector3<f64>) -> Self {
Self {
bias_field,
rf_excitation: None,
dt,
time: 0.0,
workspace_k1: Vec::new(),
workspace_k2: Vec::new(),
workspace_k3: Vec::new(),
workspace_k4: Vec::new(),
workspace_spins: Vec::new(),
}
}
fn ensure_workspace_size(&mut self, n: usize) {
let zero = Vector3::new(0.0, 0.0, 0.0);
if self.workspace_k1.len() != n {
self.workspace_k1.resize(n, zero);
self.workspace_k2.resize(n, zero);
self.workspace_k3.resize(n, zero);
self.workspace_k4.resize(n, zero);
self.workspace_spins.resize(n, zero);
}
}
pub fn with_rf_excitation(mut self, rf: RfExcitation) -> Self {
self.rf_excitation = Some(rf);
self
}
fn get_external_field(&self, idx: usize) -> Vector3<f64> {
let mut h_total = self.bias_field;
if let Some(ref rf) = self.rf_excitation {
if rf.applies_to(idx) {
h_total = h_total + rf.field_at(self.time);
}
}
h_total
}
#[allow(clippy::needless_range_loop)]
pub fn step_euler(&mut self, chain: &mut SpinChain) {
let n = chain.n_cells;
self.ensure_workspace_size(n);
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k1[i] = chain.calc_llg_torque(i, h_eff);
}
for i in 0..n {
chain.spins[i] = (chain.spins[i] + self.workspace_k1[i] * self.dt).normalize();
}
self.time += self.dt;
}
#[allow(clippy::needless_range_loop)]
pub fn step_heun(&mut self, chain: &mut SpinChain) {
let n = chain.n_cells;
self.ensure_workspace_size(n);
self.workspace_spins.copy_from_slice(&chain.spins);
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k1[i] = chain.calc_llg_torque(i, h_eff);
}
for i in 0..n {
chain.spins[i] = (self.workspace_spins[i] + self.workspace_k1[i] * self.dt).normalize();
}
self.time += self.dt;
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k2[i] = chain.calc_llg_torque(i, h_eff);
}
for i in 0..n {
let avg_torque = (self.workspace_k1[i] + self.workspace_k2[i]) * 0.5;
chain.spins[i] = (self.workspace_spins[i] + avg_torque * self.dt).normalize();
}
}
#[allow(dead_code)]
#[allow(clippy::needless_range_loop)]
pub fn step_rk4(&mut self, chain: &mut SpinChain) {
let n = chain.n_cells;
self.ensure_workspace_size(n);
self.workspace_spins.copy_from_slice(&chain.spins);
let original_time = self.time;
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k1[i] = chain.calc_llg_torque(i, h_eff);
}
self.time = original_time + 0.5 * self.dt;
for i in 0..n {
chain.spins[i] =
(self.workspace_spins[i] + self.workspace_k1[i] * (0.5 * self.dt)).normalize();
}
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k2[i] = chain.calc_llg_torque(i, h_eff);
}
for i in 0..n {
chain.spins[i] =
(self.workspace_spins[i] + self.workspace_k2[i] * (0.5 * self.dt)).normalize();
}
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k3[i] = chain.calc_llg_torque(i, h_eff);
}
self.time = original_time + self.dt;
for i in 0..n {
chain.spins[i] = (self.workspace_spins[i] + self.workspace_k3[i] * self.dt).normalize();
}
for i in 0..n {
let h_ext = self.get_external_field(i);
let h_eff = chain.calc_effective_field(i, h_ext);
self.workspace_k4[i] = chain.calc_llg_torque(i, h_eff);
}
for i in 0..n {
let increment = (self.workspace_k1[i]
+ self.workspace_k2[i] * 2.0
+ self.workspace_k3[i] * 2.0
+ self.workspace_k4[i])
* (self.dt / 6.0);
chain.spins[i] = (self.workspace_spins[i] + increment).normalize();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::magnon::chain::ChainParameters;
#[test]
fn test_rf_excitation() {
let rf = RfExcitation::new(10.0e9, 1000.0, Vector3::new(0.0, 1.0, 0.0), (0, 5));
assert!(rf.applies_to(0));
assert!(rf.applies_to(4));
assert!(!rf.applies_to(5));
assert!(!rf.applies_to(10));
let field = rf.field_at(0.0);
assert!(field.magnitude() < 1e-10); }
#[test]
fn test_solver_time_advance() {
let mut chain = SpinChain::new(10, ChainParameters::default());
let mut solver = MagnonSolver::new(1.0e-13, Vector3::new(1000.0, 0.0, 0.0));
assert_eq!(solver.time, 0.0);
solver.step_euler(&mut chain);
assert!((solver.time - 1.0e-13).abs() < 1e-20);
}
#[test]
fn test_magnetization_conservation() {
let mut chain = SpinChain::new(10, ChainParameters::default());
let mut solver = MagnonSolver::new(1.0e-14, Vector3::new(10000.0, 0.0, 0.0));
for _ in 0..100 {
solver.step_euler(&mut chain);
}
for spin in &chain.spins {
assert!((spin.magnitude() - 1.0).abs() < 1e-8);
}
}
}