ginger/
execution_policy.rs1use crate::seqlock::{AtomicComplex, AtomicVec2};
19use crate::Options;
20use num_complex::Complex;
21use rayon::prelude::*;
22use std::sync::Mutex;
23
24pub const PARALLEL_THRESHOLD: usize = 4;
26
27#[inline]
29pub fn should_parallelize(num_roots: usize) -> bool {
30 num_roots > PARALLEL_THRESHOLD
31}
32
33pub trait AtomicCell<T>: Sync {
38 fn new(value: T) -> Self;
40 fn load(&self) -> T;
42 fn store(&self, value: T);
44}
45
46impl AtomicCell<Complex<f64>> for AtomicComplex {
47 #[inline]
48 fn new(value: Complex<f64>) -> Self {
49 AtomicComplex::new(value)
50 }
51 #[inline]
52 fn load(&self) -> Complex<f64> {
53 self.load()
54 }
55 #[inline]
56 fn store(&self, value: Complex<f64>) {
57 self.store(value);
58 }
59}
60
61impl AtomicCell<Vector2F64> for AtomicVec2 {
62 #[inline]
63 fn new(value: Vector2F64) -> Self {
64 AtomicVec2::new(value)
65 }
66 #[inline]
67 fn load(&self) -> Vector2F64 {
68 self.load()
69 }
70 #[inline]
71 fn store(&self, value: Vector2F64) {
72 self.store(value);
73 }
74}
75
76type Vector2F64 = crate::vector2::Vector2<f64>;
77
78pub trait Step<T>: Sync {
94 type Cell: AtomicCell<T>;
96
97 fn run<G, N>(&self, idx: usize, get: G, neighbors: N) -> (f64, Option<T>)
99 where
100 G: Fn(usize) -> T,
101 N: Iterator<Item = usize>;
102}
103
104pub fn sequential_run<T, S>(state: &mut [T], options: &Options, step: &S) -> (usize, bool)
110where
111 T: Copy,
112 S: Step<T>,
113{
114 let m = state.len();
115 for niter in 0..options.max_iters {
116 let mut tolerance: f64 = 0.0;
117 for idx in 0..m {
118 let (tol_i, new_value) = step.run(idx, |j| state[j], (0..m).filter(move |&j| j != idx));
119 tolerance = tolerance.max(tol_i);
120 if let Some(value) = new_value {
121 state[idx] = value;
122 }
123 }
124 if tolerance < options.tolerance {
125 return (niter, true);
126 }
127 }
128 (options.max_iters, false)
129}
130
131pub fn jacobi_mt_run<T, S>(state: &mut [T], options: &Options, step: &S) -> (usize, bool)
136where
137 T: Copy + Default + Send + Sync,
138 S: Step<T> + Sync,
139{
140 if !should_parallelize(state.len()) {
141 return sequential_run(state, options, step);
142 }
143 let m = state.len();
144 let mut snapshot = vec![T::default(); m];
145 for niter in 0..options.max_iters {
146 snapshot.copy_from_slice(state);
147
148 let updates: Vec<(f64, Option<T>)> = (0..m)
149 .into_par_iter()
150 .map(|idx| step.run(idx, |j| snapshot[j], (0..m).filter(move |&j| j != idx)))
151 .collect();
152
153 let mut tolerance: f64 = 0.0;
154 for (idx, (tol_i, new_value)) in updates.into_iter().enumerate() {
155 tolerance = tolerance.max(tol_i);
156 if let Some(value) = new_value {
157 state[idx] = value;
158 }
159 }
160 if tolerance < options.tolerance {
161 return (niter, true);
162 }
163 }
164 (options.max_iters, false)
165}
166
167pub fn atomic_decoupled_run<T, S>(state: &mut [T], options: &Options, step: &S) -> (usize, bool)
173where
174 T: Copy + Send + Sync,
175 S: Step<T>,
176{
177 let num_roots = state.len();
178 let buffer: Vec<S::Cell> = state.iter().copied().map(S::Cell::new).collect();
179 let buffer_ref = &buffer;
180
181 let use_mt = should_parallelize(num_roots);
182 let num_threads = if use_mt {
183 rayon::current_num_threads().min(num_roots)
184 } else {
185 1
186 };
187 let chunk_size = (num_roots + num_threads - 1) / num_threads;
188
189 let results: Mutex<Vec<(usize, bool)>> = Mutex::new(Vec::new());
190 let results_ref = &results;
191 rayon::scope(|s| {
192 for t in 0..num_threads {
193 let start = t * chunk_size;
194 let end = (start + chunk_size).min(num_roots);
195 if start >= end {
196 break;
197 }
198 s.spawn(move |_| {
199 let mut niter = 0;
200 loop {
201 if niter == options.max_iters {
202 results_ref.lock().unwrap().push((options.max_iters, false));
203 return;
204 }
205 let mut max_tol: f64 = 0.0;
206 for idx in start..end {
207 let neighbors = (0..num_roots - 1).map(move |k| (idx + k + 1) % num_roots);
211 let (tol_i, new_value) = step.run(idx, |j| buffer_ref[j].load(), neighbors);
212 max_tol = max_tol.max(tol_i);
213 if let Some(value) = new_value {
214 buffer_ref[idx].store(value);
215 }
216 }
217 if max_tol < options.tolerance {
218 results_ref.lock().unwrap().push((niter, true));
219 return;
220 }
221 niter += 1;
222 }
223 });
224 }
225 });
226
227 let mut niter_max = 0;
228 let mut all_converged = true;
229 for (niter, converged) in results.into_inner().unwrap() {
230 niter_max = niter_max.max(niter);
231 all_converged &= converged;
232 }
233
234 for (i, z) in state.iter_mut().enumerate() {
235 *z = buffer[i].load();
236 }
237 (niter_max, all_converged)
238}