use super::{validate_a_f64, validate_tau_f64, ShiftChernoff1D};
use crate::{
chernoff::ChernoffFunction,
diffusion_storage::Storage3,
error::SemiflowError,
grid::Grid1D,
grid_fn::GridFn1D,
parallel1d::parallel_nodes_into,
sample_table::{plan_sample, planned_stride, sample_probe, PreparedGridFn, Sample1D},
scratch::ScratchPool,
step_plan::{grid_words, header, header_matches, ptr_bits, words_match, HEADER},
};
const PLAN_MAX: usize = 1 << 22;
const TAG: f64 = 3.0;
const ENGINE: usize = 10;
const META: usize = HEADER + 1 + ENGINE;
fn engine_words(s: &ShiftChernoff1D<f64>) -> [f64; ENGINE] {
let [g0, g1, g2] = grid_words(&s.grid);
let [c0, c1, c2, c3] = s.storage.as_ref().map_or([0.0; 4], Storage3::fingerprint);
let fp = |f: fn(f64) -> f64| ptr_bits(f as *const ());
[g0, g1, g2, fp(s.a), fp(s.b), fp(s.c), c0, c1, c2, c3]
}
fn data_len(n: usize, grid: &Grid1D) -> Option<usize> {
let data = n
.checked_mul(1 + 3 * planned_stride(grid))?
.checked_add(META)?;
(data <= PLAN_MAX).then_some(data)
}
pub(super) fn len(proto: &GridFn1D<f64>) -> Option<(usize, usize)> {
let data = data_len(proto.values.len(), &proto.grid)?;
Some((data, PreparedGridFn::work_len(&proto.grid)))
}
#[allow(clippy::cast_precision_loss)] pub(super) fn fill(
s: &ShiftChernoff1D<f64>,
tau: f64,
proto: &GridFn1D<f64>,
data: &mut [f64],
) -> Result<(), SemiflowError> {
validate_tau_f64(tau)?;
if data_len(proto.values.len(), &proto.grid) != Some(data.len()) {
return Err(SemiflowError::DomainViolation {
what: "ShiftChernoff1D::fill_plan: data length differs from plan_len",
value: data.len() as f64,
});
}
let grid = proto.grid;
let stride = planned_stride(&grid);
data[..HEADER].copy_from_slice(&header(s, TAG, tau, proto.values.len(), &grid));
data[HEADER] = stride as f64;
data[HEADER + 1..META].copy_from_slice(&engine_words(s));
parallel_nodes_into(&mut data[META..], 1 + 3 * stride, |i, node| {
let x = s.grid.x_at(i);
let (a_x, b_x, c_x) = (s.eval_a(x), s.eval_b(x), s.eval_c(x));
validate_a_f64(a_x, x)?;
let s_diff = 2.0 * libm::sqrt(a_x * tau);
let s_drift = 2.0 * b_x * tau;
if i == 0 {
sample_probe(&proto.values, &grid, x + s_diff)?;
}
node[0] = tau * c_x;
for (pos, slot) in [x + s_diff, x - s_diff, x + s_drift]
.into_iter()
.zip(node[1..].chunks_exact_mut(stride))
{
plan_sample(&grid, pos, slot);
}
Ok(())
})
}
#[allow(clippy::cast_precision_loss, clippy::too_many_arguments)] pub(super) fn apply(
s: &ShiftChernoff1D<f64>,
tau: f64,
data: &[f64],
work: &mut [f64],
src: &GridFn1D<f64>,
dst: &mut GridFn1D<f64>,
scratch: &mut ScratchPool<f64>,
) -> Result<(), SemiflowError> {
let n = src.values.len();
let (table_len, stride) = (
PreparedGridFn::work_len(&src.grid),
planned_stride(&src.grid),
);
let usable = header_matches(data, &header(s, TAG, tau, n, &src.grid))
&& data_len(n, &src.grid) == Some(data.len())
&& data[HEADER].to_bits() == (stride as f64).to_bits()
&& words_match(data, HEADER + 1, &engine_words(s))
&& work.len() >= table_len;
if !usable {
return s.apply_into(tau, src, dst, scratch);
}
let per_node = 1 + 3 * stride;
dst.values.resize(n, 0.0);
let table = &mut work[..table_len];
PreparedGridFn::fill(&src.values, &src.grid, table);
let f = PreparedGridFn::view(&src.values, src.grid, table);
let nodes = &data[META..];
crate::parallel1d::parallel_eval_into(&mut dst.values, |i| {
let node = &nodes[i * per_node..(i + 1) * per_node];
let smp = |k: usize| {
let at = 1 + k * stride;
f.sample_planned(&node[at..at + stride])
};
let term1 = 0.25 * smp(0)?;
let term2 = 0.25 * smp(1)?;
let term3 = 0.50 * smp(2)?;
let term4 = node[0] * f.node(i);
Ok(term1 + term2 + term3 + term4)
})
}