#[cfg(not(feature = "std"))]
use num_traits::Float;
use crate::{
chernoff::ChernoffFunction, error::SemiflowError, float::SemiflowFloat, grid_fn::GridFn1D,
grid_fn2d::GridFn2D, scratch::ScratchPool,
};
pub trait PointEval<F: SemiflowFloat = f64>: ChernoffFunction<F> {
fn eval_at(&self, tau: F, src: &Self::S, x: &[F], n_steps: u32) -> Result<F, SemiflowError>;
fn eval_at_batch(
&self,
tau: F,
src: &Self::S,
x_queries: &[&[F]],
n_steps: u32,
) -> Result<alloc::vec::Vec<F>, SemiflowError> {
x_queries
.iter()
.map(|x| self.eval_at(tau, src, x, n_steps))
.collect()
}
}
#[allow(clippy::cast_precision_loss)]
#[inline]
fn guard_1d(n_steps: u32, x: &[impl Copy]) -> Result<(), SemiflowError> {
if n_steps == 0 {
return Err(SemiflowError::DomainViolation {
what: "eval_at: n_steps must be >= 1",
value: 0.0,
});
}
if x.len() != 1 {
return Err(SemiflowError::DomainViolation {
what: "eval_at: 1-D backend requires x.len() == 1",
value: x.len() as f64,
});
}
Ok(())
}
#[allow(clippy::cast_precision_loss)]
#[inline]
fn guard_2d(n_steps: u32, x: &[impl Copy]) -> Result<(), SemiflowError> {
if n_steps == 0 {
return Err(SemiflowError::DomainViolation {
what: "eval_at: n_steps must be >= 1",
value: 0.0,
});
}
if x.len() != 2 {
return Err(SemiflowError::DomainViolation {
what: "eval_at: 2-D backend requires x.len() == 2",
value: x.len() as f64,
});
}
Ok(())
}
fn iterate_1d<C, F>(
kernel: &C,
tau: F,
src: &GridFn1D<F>,
n_steps: u32,
) -> Result<GridFn1D<F>, SemiflowError>
where
C: ChernoffFunction<F, S = GridFn1D<F>>,
F: SemiflowFloat,
{
let mut pool = ScratchPool::new();
let mut cur = src.clone();
let mut nxt = src.clone();
for _ in 0..n_steps {
kernel.apply_into(tau, &cur, &mut nxt, &mut pool)?;
core::mem::swap(&mut cur, &mut nxt);
}
Ok(cur)
}
fn iterate_2d<C>(
kernel: &C,
tau: f64,
src: &GridFn2D<f64>,
n_steps: u32,
) -> Result<GridFn2D<f64>, SemiflowError>
where
C: ChernoffFunction<f64, S = GridFn2D<f64>>,
{
let mut pool = ScratchPool::<f64>::new();
let n = src.values.len();
let mut cur = GridFn2D {
values: src.values.clone(),
grid: src.grid,
};
let mut nxt = GridFn2D {
values: alloc::vec![0.0_f64; n],
grid: src.grid,
};
for _ in 0..n_steps {
kernel.apply_into(tau, &cur, &mut nxt, &mut pool)?;
core::mem::swap(&mut cur, &mut nxt);
}
Ok(cur)
}
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
#[must_use]
pub fn sample_gridfn2d(state: &GridFn2D<f64>, cx: f64, cy: f64) -> f64 {
let nx = state.grid.nx();
let ny = state.grid.ny();
let xi = (cx - state.grid.x.xmin) / (state.grid.x.xmax - state.grid.x.xmin) * (nx - 1) as f64;
let yi = (cy - state.grid.y.xmin) / (state.grid.y.xmax - state.grid.y.xmin) * (ny - 1) as f64;
let xi = xi.clamp(0.0, (nx - 2) as f64);
let yi = yi.clamp(0.0, (ny - 2) as f64);
let i0 = xi.floor() as usize;
let j0 = yi.floor() as usize;
let fx = (xi - i0 as f64).clamp(0.0, 1.0);
let fy = (yi - j0 as f64).clamp(0.0, 1.0);
let v00 = state.values[j0 * nx + i0];
let v10 = state.values[j0 * nx + i0 + 1];
let v01 = state.values[(j0 + 1) * nx + i0];
let v11 = state.values[(j0 + 1) * nx + i0 + 1];
v00 * (1.0 - fx) * (1.0 - fy) + v10 * fx * (1.0 - fy) + v01 * (1.0 - fx) * fy + v11 * fx * fy
}
use crate::diffusion::DiffusionChernoff;
impl PointEval<f64> for DiffusionChernoff<f64> {
fn eval_at(
&self,
tau: f64,
src: &GridFn1D<f64>,
x: &[f64],
n_steps: u32,
) -> Result<f64, SemiflowError> {
guard_1d(n_steps, x)?;
let final_state = iterate_1d(self, tau, src, n_steps)?;
final_state.sample(x[0])
}
}
use crate::shift1d::ShiftChernoff1D;
impl PointEval<f64> for ShiftChernoff1D<f64> {
fn eval_at(
&self,
tau: f64,
src: &GridFn1D<f64>,
x: &[f64],
n_steps: u32,
) -> Result<f64, SemiflowError> {
guard_1d(n_steps, x)?;
let final_state = iterate_1d(self, tau, src, n_steps)?;
final_state.sample(x[0])
}
}
use crate::{manifold::Sphere2, manifold_chernoff::ManifoldChernoff};
impl PointEval<f64> for ManifoldChernoff<Sphere2<f64>, f64> {
fn eval_at(
&self,
tau: f64,
src: &GridFn2D<f64>,
x: &[f64],
n_steps: u32,
) -> Result<f64, SemiflowError> {
guard_2d(n_steps, x)?;
let final_state = iterate_2d(self, tau, src, n_steps)?;
Ok(sample_gridfn2d(&final_state, x[0], x[1]))
}
}
use crate::hormander::HypoellipticChernoff;
impl PointEval<f64> for HypoellipticChernoff<f64, 2, 1> {
fn eval_at(
&self,
tau: f64,
src: &GridFn2D<f64>,
x: &[f64],
n_steps: u32,
) -> Result<f64, SemiflowError> {
guard_2d(n_steps, x)?;
let final_state = iterate_2d(self, tau, src, n_steps)?;
Ok(sample_gridfn2d(&final_state, x[0], x[1]))
}
}
use crate::{grid_nd::GridFnND, shift_nd::AnisotropicShiftChernoffND};
#[allow(clippy::cast_precision_loss)]
#[inline]
fn guard_nd<const D: usize>(n_steps: u32, x: &[f64]) -> Result<(), SemiflowError> {
if n_steps == 0 {
return Err(SemiflowError::DomainViolation {
what: "eval_at: n_steps must be >= 1",
value: 0.0,
});
}
if x.len() != D {
return Err(SemiflowError::DomainViolation {
what: "eval_at: x.len() must equal D",
value: x.len() as f64,
});
}
Ok(())
}
fn iterate_nd<const D: usize>(
kernel: &AnisotropicShiftChernoffND<f64, D>,
tau: f64,
src: &GridFnND<f64, D>,
n_steps: u32,
) -> Result<GridFnND<f64, D>, SemiflowError>
where
AnisotropicShiftChernoffND<f64, D>: ChernoffFunction<f64, S = GridFnND<f64, D>>,
{
let mut pool = ScratchPool::new();
let mut cur = src.clone();
let mut nxt = GridFnND::new(src.grid.clone(), alloc::vec![0.0_f64; src.values.len()])?;
for _ in 0..n_steps {
kernel.apply_into(tau, &cur, &mut nxt, &mut pool)?;
core::mem::swap(&mut cur, &mut nxt);
}
Ok(cur)
}
impl<const D: usize> PointEval<f64> for AnisotropicShiftChernoffND<f64, D>
where
AnisotropicShiftChernoffND<f64, D>: ChernoffFunction<f64, S = GridFnND<f64, D>>,
{
fn eval_at(
&self,
tau: f64,
src: &GridFnND<f64, D>,
x: &[f64],
n_steps: u32,
) -> Result<f64, SemiflowError> {
guard_nd::<D>(n_steps, x)?;
let final_state = iterate_nd(self, tau, src, n_steps)?;
final_state.sample(x)
}
}
#[cfg(test)]
mod tests {
include!("point_eval_tests.rs");
}