use alloc::vec::Vec;
use core::any::Any;
use crate::{
boundary::bc_value_from_hit,
error::SemiflowError,
float::SemiflowFloat,
grid_nd::{GridFnND, GridND},
interp_stencil::supports_nd,
scratch::ScratchPool,
shift_nd::{flat_to_x, SquareMatrix},
};
const SWEEP_MAX_ELEMS: usize = 1 << 24;
pub(crate) type DriftRef<'a, F, const D: usize> = &'a (dyn Fn(&[F; D], &mut [F; D]) + Send + Sync);
pub(crate) type ReactionRef<'a, F, const D: usize> = &'a (dyn Fn(&[F; D]) -> F + Send + Sync);
pub(crate) struct ShiftStep<'a, F: SemiflowFloat, const D: usize> {
pub(crate) tau: F,
pub(crate) grid: &'a GridND<F, D>,
pub(crate) b: DriftRef<'a, F, D>,
pub(crate) c: ReactionRef<'a, F, D>,
pub(crate) chol: &'a [SquareMatrix<F, D>],
pub(crate) quad: &'a [([F; D], F)],
}
pub(crate) fn apply_shift_step<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
src: &GridFnND<F, D>,
dst: &mut GridFnND<F, D>,
scratch: &mut ScratchPool<F>,
) {
if !try_separable(step, src, dst, scratch, SWEEP_MAX_ELEMS) {
per_node(step, src, dst);
}
}
fn two_sqrt_tau<F: SemiflowFloat>(tau: F) -> F {
crate::float::from_f64::<F>(2.0_f64) * tau.sqrt()
}
fn node_value<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
src: &GridFnND<F, D>,
ns: &[usize; D],
flat: usize,
) -> F {
let tau = step.tau;
let two_sqrt_tau = two_sqrt_tau(tau);
let inv_pi_dhalf = crate::float::inv_pi_pow_half::<F>(D);
let xk = flat_to_x::<F, D>(flat, ns, step.grid);
let mut b_val = [F::zero(); D];
(step.b)(&xk, &mut b_val);
let c_val = (step.c)(&xk);
let exp_factor = (tau * c_val).libm_exp();
let l_k = &step.chol[flat];
let mut acc = F::zero();
for &(eta_q, w_q) in step.quad {
let mut l_eta = [F::zero(); D];
l_k.mat_vec(&eta_q, &mut l_eta);
let y_q: [F; D] =
core::array::from_fn(|d| xk[d] + tau * b_val[d] + two_sqrt_tau * l_eta[d]);
acc += w_q * src.sample(&y_q).unwrap_or(F::zero());
}
exp_factor * inv_pi_dhalf * acc
}
fn per_node<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
src: &GridFnND<F, D>,
dst: &mut GridFnND<F, D>,
) {
let ns: [usize; D] = core::array::from_fn(|d| step.grid.axes[d].n);
let total = src.values.len();
if let Some(out) = (&mut dst.values as &mut dyn Any).downcast_mut::<Vec<f64>>() {
let eval = |flat: usize| {
Ok(node_value(step, src, &ns, flat)
.to_f64()
.unwrap_or(f64::NAN))
};
let res: Result<(), SemiflowError> =
crate::parallel1d::parallel_eval_into(&mut out[..total], eval);
debug_assert!(res.is_ok(), "node_value is infallible");
return;
}
for flat in 0..total {
dst.values[flat] = node_value(step, src, &ns, flat);
}
}
fn same_bits<F: SemiflowFloat>(a: &F, b: &F) -> bool {
let (a, b): (&dyn Any, &dyn Any) = (a, b);
match (a.downcast_ref::<f64>(), b.downcast_ref::<f64>()) {
(Some(x), Some(y)) => x.to_bits() == y.to_bits(),
_ => match (a.downcast_ref::<f32>(), b.downcast_ref::<f32>()) {
(Some(x), Some(y)) => x.to_bits() == y.to_bits(),
_ => false,
},
}
}
fn constant_coefficients<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
ns: &[usize; D],
total: usize,
) -> Option<[F; D]> {
let probe = |flat: usize| {
let mut b = [F::zero(); D];
(step.b)(&flat_to_x::<F, D>(flat, ns, step.grid), &mut b);
b
};
if !same_bits(&F::zero(), &F::zero()) {
return None; }
let (b0, l0) = (probe(0), step.chol.first()?);
let same_l = |l: &SquareMatrix<F, D>| {
(0..D).all(|i| (0..D).all(|j| same_bits(&l.get(i, j), &l0.get(i, j))))
};
let all = (1..total).all(|flat| {
let b = probe(flat);
(0..D).all(|d| same_bits(&b[d], &b0[d])) && same_l(&step.chol[flat])
});
all.then_some(b0)
}
fn separable_inputs<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
src: &GridFnND<F, D>,
dst: &GridFnND<F, D>,
) -> Option<[F; D]> {
let ns: [usize; D] = core::array::from_fn(|d| step.grid.axes[d].n);
let total = src.values.len();
let shapes = D > 0
&& supports_nd(src.grid.interp)
&& (0..D).all(|d| src.grid.axes[d].n == ns[d])
&& ns.iter().product::<usize>() == total
&& total > 0
&& dst.values.len() >= total
&& step.chol.len() >= total;
if !shapes {
return None;
}
constant_coefficients(step, &ns, total)
}
fn try_separable<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
src: &GridFnND<F, D>,
dst: &mut GridFnND<F, D>,
scratch: &mut ScratchPool<F>,
cap: usize,
) -> bool {
let Some(b0) = separable_inputs(step, src, dst) else {
return false;
};
let total = src.values.len();
let shared = (D + 1).saturating_mul(total) <= cap;
if !shared && 3 * total > cap {
return false;
}
let mut levels: [Vec<F>; D] = core::array::from_fn(|d| {
if shared || d < 2 {
scratch.take_vec(total)
} else {
Vec::new()
}
});
let mut acc = scratch.take_vec(total);
let (base, offsets) = axis_bases(step, b0, scratch);
let mut sweep = Sweep {
src,
levels: &mut levels,
shared,
base: &base,
offsets,
};
let done = sweep.accumulate(step, &mut acc);
if done {
finish(step, &acc, dst);
}
for buf in levels.into_iter().filter(|b| b.capacity() > 0) {
scratch.return_vec(buf);
}
scratch.return_vec(acc);
scratch.return_vec(base);
done
}
fn axis_bases<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
b0: [F; D],
scratch: &mut ScratchPool<F>,
) -> (Vec<F>, [usize; D]) {
let mut base = scratch.take_vec(step.grid.axes.iter().map(|ax| ax.n).sum());
let mut offsets = [0_usize; D];
let mut at = 0;
for (d, ax) in step.grid.axes.iter().enumerate() {
offsets[d] = at;
for k in 0..ax.n {
base[at + k] = step.grid.x_at(d, k) + step.tau * b0[d];
}
at += ax.n;
}
(base, offsets)
}
fn finish<F: SemiflowFloat, const D: usize>(
step: &ShiftStep<'_, F, D>,
acc: &[F],
dst: &mut GridFnND<F, D>,
) {
let ns: [usize; D] = core::array::from_fn(|d| step.grid.axes[d].n);
let inv_pi_dhalf = crate::float::inv_pi_pow_half::<F>(D);
for (flat, &a) in acc.iter().enumerate() {
let c_val = (step.c)(&flat_to_x::<F, D>(flat, &ns, step.grid));
let exp_factor = (step.tau * c_val).libm_exp();
dst.values[flat] = exp_factor * inv_pi_dhalf * a;
}
}
struct Sweep<'s, F: SemiflowFloat, const D: usize> {
src: &'s GridFnND<F, D>,
levels: &'s mut [Vec<F>; D],
shared: bool,
base: &'s [F],
offsets: [usize; D],
}
impl<F: SemiflowFloat, const D: usize> Sweep<'_, F, D> {
fn accumulate(&mut self, step: &ShiftStep<'_, F, D>, acc: &mut [F]) -> bool {
let two_sqrt_tau = two_sqrt_tau(step.tau);
let mut prev: Option<[F; D]> = None;
for &(eta_q, w_q) in step.quad {
let mut l_eta = [F::zero(); D];
step.chol[0].mat_vec(&eta_q, &mut l_eta);
let key: [F; D] = core::array::from_fn(|d| two_sqrt_tau * l_eta[d]);
let first = match (self.shared, prev) {
(true, Some(p)) => (0..D).find(|&d| !same_bits(&p[d], &key[d])).unwrap_or(D),
_ => 0,
};
for (d, &off) in key.iter().enumerate().skip(first) {
if !self.pass(d, off) {
return false;
}
}
prev = Some(key);
let last = &self.levels[self.slot(D - 1)];
for (a, &g) in acc.iter_mut().zip(last.iter()) {
*a += w_q * g;
}
}
true
}
fn slot(&self, d: usize) -> usize {
if self.shared {
d
} else {
d % 2
}
}
fn pass(&mut self, d: usize, off: F) -> bool {
let n = self.src.grid.axes[d].n;
let xs = &self.base[self.offsets[d]..self.offsets[d] + n];
let out_slot = self.slot(d);
let (input, out): (&[F], &mut [F]) = if d == 0 {
(&self.src.values, &mut self.levels[out_slot])
} else {
let in_slot = self.slot(d - 1);
let (lo, hi) = self.levels.split_at_mut(in_slot.max(out_slot));
if in_slot < out_slot {
(&lo[in_slot], &mut hi[0])
} else {
(&hi[0], &mut lo[out_slot])
}
};
axis_pass(self.src, d, xs, off, input, out)
}
}
fn axis_pass<F: SemiflowFloat, const D: usize>(
src: &GridFnND<F, D>,
d: usize,
xs: &[F],
off: F,
input: &[F],
out: &mut [F],
) -> bool {
let ax = &src.grid.axes[d];
let n = ax.n;
for (k, &x) in xs.iter().enumerate() {
let Ok(st) = src.axis_stencil(d, x + off) else {
return false;
};
let block = st.stride * n;
for hi in (0..out.len()).step_by(block) {
for lo in 0..st.stride {
let base = hi + lo;
let mut acc = F::zero();
for j in 0..st.k {
let get = |i: usize| input[base + i * st.stride];
let v = bc_value_from_hit(st.hits[j], ax.boundary, get, n, ax.dx());
acc += st.weights[j] * v;
}
out[base + k * st.stride] = acc;
}
}
}
true
}
#[cfg(test)]
#[path = "grid_nd_sweep_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "grid_nd_sweep_engine_tests.rs"]
mod engine_tests;