use crate::seqlock::{AtomicComplex, AtomicVec2};
use crate::Options;
use num_complex::Complex;
use rayon::prelude::*;
use std::sync::Mutex;
pub const PARALLEL_THRESHOLD: usize = 4;
#[inline]
pub fn should_parallelize(num_roots: usize) -> bool {
num_roots > PARALLEL_THRESHOLD
}
pub trait AtomicCell<T>: Sync {
fn new(value: T) -> Self;
fn load(&self) -> T;
fn store(&self, value: T);
}
impl AtomicCell<Complex<f64>> for AtomicComplex {
#[inline]
fn new(value: Complex<f64>) -> Self {
AtomicComplex::new(value)
}
#[inline]
fn load(&self) -> Complex<f64> {
self.load()
}
#[inline]
fn store(&self, value: Complex<f64>) {
self.store(value);
}
}
impl AtomicCell<Vector2F64> for AtomicVec2 {
#[inline]
fn new(value: Vector2F64) -> Self {
AtomicVec2::new(value)
}
#[inline]
fn load(&self) -> Vector2F64 {
self.load()
}
#[inline]
fn store(&self, value: Vector2F64) {
self.store(value);
}
}
type Vector2F64 = crate::vector2::Vector2<f64>;
pub trait Step<T>: Sync {
type Cell: AtomicCell<T>;
fn run<G, N>(&self, idx: usize, get: G, neighbors: N) -> (f64, Option<T>)
where
G: Fn(usize) -> T,
N: Iterator<Item = usize>;
}
pub fn sequential_run<T, S>(state: &mut [T], options: &Options, step: &S) -> (usize, bool)
where
T: Copy,
S: Step<T>,
{
let m = state.len();
for niter in 0..options.max_iters {
let mut tolerance: f64 = 0.0;
for idx in 0..m {
let (tol_i, new_value) = step.run(idx, |j| state[j], (0..m).filter(move |&j| j != idx));
tolerance = tolerance.max(tol_i);
if let Some(value) = new_value {
state[idx] = value;
}
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
pub fn jacobi_mt_run<T, S>(state: &mut [T], options: &Options, step: &S) -> (usize, bool)
where
T: Copy + Default + Send + Sync,
S: Step<T> + Sync,
{
if !should_parallelize(state.len()) {
return sequential_run(state, options, step);
}
let m = state.len();
let mut snapshot = vec![T::default(); m];
for niter in 0..options.max_iters {
snapshot.copy_from_slice(state);
let updates: Vec<(f64, Option<T>)> = (0..m)
.into_par_iter()
.map(|idx| step.run(idx, |j| snapshot[j], (0..m).filter(move |&j| j != idx)))
.collect();
let mut tolerance: f64 = 0.0;
for (idx, (tol_i, new_value)) in updates.into_iter().enumerate() {
tolerance = tolerance.max(tol_i);
if let Some(value) = new_value {
state[idx] = value;
}
}
if tolerance < options.tolerance {
return (niter, true);
}
}
(options.max_iters, false)
}
pub fn atomic_decoupled_run<T, S>(state: &mut [T], options: &Options, step: &S) -> (usize, bool)
where
T: Copy + Send + Sync,
S: Step<T>,
{
let num_roots = state.len();
let buffer: Vec<S::Cell> = state.iter().copied().map(S::Cell::new).collect();
let buffer_ref = &buffer;
let use_mt = should_parallelize(num_roots);
let num_threads = if use_mt {
rayon::current_num_threads().min(num_roots)
} else {
1
};
let chunk_size = (num_roots + num_threads - 1) / num_threads;
let results: Mutex<Vec<(usize, bool)>> = Mutex::new(Vec::new());
let results_ref = &results;
rayon::scope(|s| {
for t in 0..num_threads {
let start = t * chunk_size;
let end = (start + chunk_size).min(num_roots);
if start >= end {
break;
}
s.spawn(move |_| {
let mut niter = 0;
loop {
if niter == options.max_iters {
results_ref.lock().unwrap().push((options.max_iters, false));
return;
}
let mut max_tol: f64 = 0.0;
for idx in start..end {
let neighbors = (0..num_roots - 1).map(move |k| (idx + k + 1) % num_roots);
let (tol_i, new_value) = step.run(idx, |j| buffer_ref[j].load(), neighbors);
max_tol = max_tol.max(tol_i);
if let Some(value) = new_value {
buffer_ref[idx].store(value);
}
}
if max_tol < options.tolerance {
results_ref.lock().unwrap().push((niter, true));
return;
}
niter += 1;
}
});
}
});
let mut niter_max = 0;
let mut all_converged = true;
for (niter, converged) in results.into_inner().unwrap() {
niter_max = niter_max.max(niter);
all_converged &= converged;
}
for (i, z) in state.iter_mut().enumerate() {
*z = buffer[i].load();
}
(niter_max, all_converged)
}