use alloc::{sync::Arc, vec::Vec};
use core::cell::Cell;
use std::sync::Mutex;
#[doc(hidden)]
pub use crate::parallel_pool::PARALLEL_2D_POOL_F64 as PARALLEL_2D_POOL;
use crate::{
chernoff::{ApplyChernoffExt, ChernoffFunction},
error::SemiflowError,
float::SemiflowFloat,
grid::Grid1D,
grid_fn::GridFn1D,
parallel_pool::{drain_thread_local_pools_2d_for, ParallelPool2D},
};
#[doc(hidden)]
pub fn drain_thread_local_pools_2d() {
drain_thread_local_pools_2d_for::<f64>();
drain_thread_local_pools_2d_for::<f32>();
}
pub(crate) const MIN_ROWS_PER_THREAD: usize = 16;
thread_local! {
#[doc(hidden)]
pub static FORCE_THREADS: Cell<Option<usize>> = const { Cell::new(None) };
}
pub(crate) fn resolve_threads(ny: usize) -> usize {
let raw = FORCE_THREADS.with(Cell::get).unwrap_or_else(|| {
std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get)
});
raw.min(ny / MIN_ROWS_PER_THREAD).max(1)
}
pub(crate) fn chunk_count(total: usize, n_threads: usize) -> usize {
total.div_ceil(n_threads)
}
type ErrorSlot = Arc<Mutex<Option<SemiflowError>>>;
pub(crate) fn parallel_x_pass<X, F>(
state: &mut [F],
gx: Grid1D<F>,
n_threads: usize,
op: &X,
tau: F,
) -> Result<(), SemiflowError>
where
F: SemiflowFloat + Send + Sync + ParallelPool2D,
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone + Send + Sync,
{
let nx = gx.n;
let ny = state.len() / nx;
let error_slot: ErrorSlot = Arc::new(Mutex::new(None));
let chunk_size = chunk_count(ny, n_threads);
std::thread::scope(|s| {
for row_chunk in state.chunks_mut(chunk_size * nx) {
let rows = row_chunk.len() / nx;
let err_arc = Arc::clone(&error_slot);
s.spawn(move || x_pass_chunk(row_chunk, gx, rows, op, tau, &err_arc));
}
});
extract_error(error_slot)
}
fn x_pass_chunk<X, F>(
row_chunk: &mut [F],
gx: Grid1D<F>,
rows: usize,
op: &X,
tau: F,
err: &ErrorSlot,
) where
F: SemiflowFloat + Send + Sync + ParallelPool2D,
X: ChernoffFunction<F, S = GridFn1D<F>>,
{
let nx = gx.n;
let mut row_buf = F::with_pool_2d(|pool| pool.take_vec(nx));
for r in 0..rows {
let row_start = r * nx;
row_buf.copy_from_slice(&row_chunk[row_start..row_start + nx]);
let row_fn = GridFn1D {
values: core::mem::take(&mut row_buf),
grid: gx,
};
match op.apply_chernoff(tau, &row_fn) {
Ok(evolved) => {
row_chunk[row_start..row_start + nx].copy_from_slice(&evolved.values);
row_buf = row_fn.values;
}
Err(e) => {
F::with_pool_2d(|pool| pool.return_vec(row_fn.values));
let mut slot = err.lock().unwrap();
if slot.is_none() {
*slot = Some(e);
}
return;
}
}
}
F::with_pool_2d(|pool| pool.return_vec(row_buf));
}
#[derive(Clone, Copy)]
struct YColCtx<F: SemiflowFloat> {
nx: usize,
gy: Grid1D<F>,
col_chunk: usize,
}
pub(crate) fn parallel_y_pass<Y, F>(
state: &mut [F],
gy: Grid1D<F>,
n_threads: usize,
op: &Y,
tau: F,
temp: &mut Vec<F>,
) -> Result<(), SemiflowError>
where
F: SemiflowFloat + Send + Sync + ParallelPool2D,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone + Send + Sync,
{
let ny = gy.n;
let nx = state.len() / ny;
let col_chunk = chunk_count(nx, n_threads);
let row_chunk = chunk_count(ny, n_threads);
let ctx = YColCtx { nx, gy, col_chunk };
temp.resize(nx * ny, F::zero());
let error_slot: ErrorSlot = Arc::new(Mutex::new(None));
y_phase1_apply(state, temp, ctx, op, tau, &error_slot);
{
let slot = error_slot.lock().unwrap();
if let Some(ref e) = *slot {
return Err(e.clone());
}
}
y_phase2_scatter(state, temp, nx, ny, row_chunk, col_chunk);
Ok(())
}
fn y_phase1_apply<Y, F>(
state: &[F],
temp: &mut [F],
ctx: YColCtx<F>,
op: &Y,
tau: F,
err: &ErrorSlot,
) where
F: SemiflowFloat + Send + Sync + ParallelPool2D,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone + Send + Sync,
{
let ny = ctx.gy.n;
std::thread::scope(|s| {
for (cidx, temp_chunk) in temp.chunks_mut(ctx.col_chunk * ny).enumerate() {
let col_start = cidx * ctx.col_chunk;
let cols = temp_chunk.len() / ny;
let err_arc = Arc::clone(err);
s.spawn(move || {
y_apply_cols(state, ctx, op, tau, col_start, cols, temp_chunk, &err_arc);
});
}
});
}
#[allow(clippy::too_many_arguments)]
fn y_apply_cols<Y, F>(
state: &[F],
ctx: YColCtx<F>,
op: &Y,
tau: F,
col_start: usize,
cols: usize,
temp_chunk: &mut [F],
err: &ErrorSlot,
) where
F: SemiflowFloat + Send + Sync + ParallelPool2D,
Y: ChernoffFunction<F, S = GridFn1D<F>>,
{
let ny = ctx.gy.n;
let nx = ctx.nx;
let mut col_buf = F::with_pool_2d(|pool| pool.take_vec(ny));
for c in 0..cols {
let col_idx = col_start + c;
if col_idx >= nx {
break;
}
for j in 0..ny {
col_buf[j] = state[j * nx + col_idx];
}
let col_fn = GridFn1D {
values: core::mem::take(&mut col_buf),
grid: ctx.gy,
};
match op.apply_chernoff(tau, &col_fn) {
Ok(evolved) => {
let ts = c * ny;
temp_chunk[ts..ts + ny].copy_from_slice(&evolved.values);
col_buf = col_fn.values;
}
Err(e) => {
F::with_pool_2d(|pool| pool.return_vec(col_fn.values));
let mut slot = err.lock().unwrap();
if slot.is_none() {
*slot = Some(e);
}
return;
}
}
}
F::with_pool_2d(|pool| pool.return_vec(col_buf));
}
#[derive(Clone, Copy)]
struct ScatterCtx {
nx: usize,
ny: usize,
col_chunk: usize,
}
fn y_phase2_scatter<F: SemiflowFloat + Send + Sync>(
state: &mut [F],
temp: &[F],
nx: usize,
ny: usize,
row_chunk: usize,
col_chunk: usize,
) {
let sctx = ScatterCtx { nx, ny, col_chunk };
std::thread::scope(|s| {
for (ridx, state_chunk) in state.chunks_mut(row_chunk * nx).enumerate() {
let row_start = ridx * row_chunk;
let rows = state_chunk.len() / nx;
s.spawn(move || scatter_rows(state_chunk, temp, sctx, row_start, rows));
}
});
}
fn scatter_rows<F: SemiflowFloat>(
state_chunk: &mut [F],
temp: &[F],
ctx: ScatterCtx,
row_start: usize,
rows: usize,
) {
let ScatterCtx { nx, ny, col_chunk } = ctx;
for r in 0..rows {
let j = row_start + r;
for col_idx in 0..nx {
let temp_idx = (col_idx / col_chunk) * col_chunk * ny + (col_idx % col_chunk) * ny + j;
state_chunk[r * nx + col_idx] = temp[temp_idx];
}
}
}
fn extract_error(slot: ErrorSlot) -> Result<(), SemiflowError> {
match Arc::try_unwrap(slot) {
Ok(mutex) => match mutex.into_inner().unwrap() {
Some(e) => Err(e),
None => Ok(()),
},
Err(_) => Ok(()), }
}