use scirs2_core::parallel_ops::*;
use crate::magnon::chain::{ChainParameters, SpinChain};
use crate::vector3::Vector3;
#[derive(Clone)]
pub struct MultiDomainSystem {
pub domains: Vec<SpinChain>,
pub interdomain_coupling: f64,
pub domain_spacing: f64,
}
impl MultiDomainSystem {
pub fn new(n_domains: usize, cells_per_domain: usize, params: ChainParameters) -> Self {
let domains = (0..n_domains)
.map(|_| SpinChain::new(cells_per_domain, params.clone()))
.collect();
Self {
domains,
interdomain_coupling: params.a_ex * 0.1, domain_spacing: params.cell_size * 2.0,
}
}
pub fn new_with_noise(
n_domains: usize,
cells_per_domain: usize,
params: ChainParameters,
noise_amplitude: f64,
) -> Self {
let domains = (0..n_domains)
.map(|_| SpinChain::new_with_noise(cells_per_domain, params.clone(), noise_amplitude))
.collect();
Self {
domains,
interdomain_coupling: params.a_ex * 0.1,
domain_spacing: params.cell_size * 2.0,
}
}
pub fn n_domains(&self) -> usize {
self.domains.len()
}
pub fn total_spins(&self) -> usize {
self.domains.iter().map(|d| d.n_cells).sum()
}
pub fn evolve_parallel(&mut self, h_ext: Vector3<f64>, dt: f64) {
let boundary_fields = self.compute_boundary_fields();
self.domains
.par_iter_mut()
.enumerate()
.for_each(|(i, domain)| {
let h_total = h_ext + boundary_fields[i];
domain.evolve_heun(h_total, dt);
});
}
#[allow(clippy::needless_range_loop)]
fn compute_boundary_fields(&self) -> Vec<Vector3<f64>> {
let n = self.domains.len();
let mut fields = vec![Vector3::new(0.0, 0.0, 0.0); n];
for i in 0..n {
let mut boundary_field = Vector3::new(0.0, 0.0, 0.0);
if i > 0 {
let left_spin = self.domains[i - 1].spins[self.domains[i - 1].n_cells - 1];
let coupling_strength =
self.interdomain_coupling / (self.domain_spacing * self.domain_spacing);
let exchange_field = left_spin * coupling_strength;
boundary_field = boundary_field + exchange_field;
}
if i < n - 1 {
let right_spin = self.domains[i + 1].spins[0];
let coupling_strength =
self.interdomain_coupling / (self.domain_spacing * self.domain_spacing);
let exchange_field = right_spin * coupling_strength;
boundary_field = boundary_field + exchange_field;
}
fields[i] = boundary_field;
}
fields
}
pub fn total_magnetization(&self) -> Vector3<f64> {
self.domains
.par_iter()
.map(|domain| domain.average_magnetization() * (domain.n_cells as f64))
.reduce(|| Vector3::new(0.0, 0.0, 0.0), |a, b| a + b)
* (1.0 / self.total_spins() as f64)
}
pub fn domain_magnetizations(&self) -> Vec<Vector3<f64>> {
self.domains
.par_iter()
.map(|domain| domain.average_magnetization())
.collect()
}
pub fn reset_all(&mut self) {
self.domains.par_iter_mut().for_each(|domain| {
domain.reset_to_z();
});
}
pub fn total_energy(&self) -> f64 {
let intra_energy: f64 = self
.domains
.par_iter()
.map(|domain| {
let mut energy = 0.0;
for i in 0..domain.n_cells - 1 {
let dot = domain.spins[i].dot(&domain.spins[i + 1]);
energy -= domain.params.a_ex * dot / domain.params.cell_size;
}
energy
})
.sum();
let mut inter_energy = 0.0;
for i in 0..self.domains.len() - 1 {
let left_last = self.domains[i].spins[self.domains[i].n_cells - 1];
let right_first = self.domains[i + 1].spins[0];
let dot = left_last.dot(&right_first);
inter_energy -= self.interdomain_coupling * dot / self.domain_spacing;
}
intra_energy + inter_energy
}
}
impl SpinChain {
#[allow(clippy::needless_range_loop)]
pub fn evolve_simd_optimized(&mut self, h_ext: Vector3<f64>, dt: f64) {
const BLOCK_SIZE: usize = 8;
let mut k1 = vec![Vector3::new(0.0, 0.0, 0.0); self.n_cells];
let mut k2 = vec![Vector3::new(0.0, 0.0, 0.0); self.n_cells];
for block_start in (0..self.n_cells).step_by(BLOCK_SIZE) {
let block_end = (block_start + BLOCK_SIZE).min(self.n_cells);
for i in block_start..block_end {
let h_eff = self.calc_effective_field(i, h_ext);
k1[i] = self.calc_llg_torque(i, h_eff);
}
}
let original_spins = self.spins.clone();
for block_start in (0..self.n_cells).step_by(BLOCK_SIZE) {
let block_end = (block_start + BLOCK_SIZE).min(self.n_cells);
for i in block_start..block_end {
self.spins[i] = (self.spins[i] + k1[i] * dt).normalize();
}
}
for block_start in (0..self.n_cells).step_by(BLOCK_SIZE) {
let block_end = (block_start + BLOCK_SIZE).min(self.n_cells);
for i in block_start..block_end {
let h_eff = self.calc_effective_field(i, h_ext);
k2[i] = self.calc_llg_torque(i, h_eff);
}
}
for block_start in (0..self.n_cells).step_by(BLOCK_SIZE) {
let block_end = (block_start + BLOCK_SIZE).min(self.n_cells);
for i in block_start..block_end {
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_multi_domain_creation() {
let system = MultiDomainSystem::new(10, 50, ChainParameters::default());
assert_eq!(system.n_domains(), 10);
assert_eq!(system.total_spins(), 500);
}
#[test]
fn test_parallel_evolution() {
let mut system = MultiDomainSystem::new(4, 20, ChainParameters::yig());
let h_ext = Vector3::new(0.0, 0.0, 1000.0);
system.evolve_parallel(h_ext, 1e-12);
let m_total = system.total_magnetization();
assert!(m_total.magnitude() <= 1.1); }
#[test]
fn test_boundary_coupling() {
let system = MultiDomainSystem::new(3, 10, ChainParameters::default());
let fields = system.compute_boundary_fields();
assert_eq!(fields.len(), 3);
assert!(fields[1].magnitude() > 0.0);
}
#[test]
fn test_simd_vs_standard() {
let params = ChainParameters::permalloy();
let mut chain1 = SpinChain::new_with_noise(100, params.clone(), 0.01);
let mut chain2 = chain1.clone();
let h_ext = Vector3::new(0.0, 0.0, 1000.0);
let dt = 1e-12;
chain1.evolve_heun(h_ext, dt);
chain2.evolve_simd_optimized(h_ext, dt);
let m1 = chain1.average_magnetization();
let m2 = chain2.average_magnetization();
let diff = (m1 - m2).magnitude();
assert!(diff < 0.01, "SIMD and standard methods diverged: {}", diff);
}
#[test]
fn test_parallel_scaling() {
let params = ChainParameters::yig();
let small = MultiDomainSystem::new(2, 10, params.clone());
assert_eq!(small.total_spins(), 20);
let large = MultiDomainSystem::new(100, 100, params);
assert_eq!(large.total_spins(), 10000);
let m_small = small.total_magnetization();
let m_large = large.total_magnetization();
assert!(m_small.magnitude() > 0.9);
assert!(m_large.magnitude() > 0.9);
}
#[test]
fn test_energy_conservation() {
let mut system = MultiDomainSystem::new(5, 20, ChainParameters::permalloy());
let e0 = system.total_energy();
let h_ext = Vector3::new(0.0, 0.0, 0.0);
for _ in 0..10 {
system.evolve_parallel(h_ext, 1e-13);
}
let e1 = system.total_energy();
let relative_change = ((e1 - e0) / e0).abs();
assert!(
relative_change < 0.5,
"Energy conservation violated: {}",
relative_change
);
}
}