extern crate alloc;
use alloc::{vec, vec::Vec};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use num_traits::Float;
use crate::{error::SemiflowError, float::SemiflowFloat, grid::Grid1D};
const PROBE_HALF_WIDTH: i64 = 6;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub(crate) const MAX_ROW: usize = 2 * PROBE_HALF_WIDTH as usize + 2 + 6;
fn candidate_nodes(grid: &Grid1D<f64>, y: f64, idx_out: &mut [usize; MAX_ROW]) -> usize {
let n = grid.n;
#[allow(clippy::cast_possible_truncation)]
let centre = ((y - grid.xmin) / grid.dx()).floor() as i64;
let mut count = 0_usize;
let push = |j: usize, count: &mut usize, idx_out: &mut [usize; MAX_ROW]| {
if !idx_out[..*count].contains(&j) && *count < MAX_ROW {
idx_out[*count] = j;
*count += 1;
}
};
for d in -PROBE_HALF_WIDTH..=PROBE_HALF_WIDTH + 1 {
match crate::boundary::bc_index::<f64>(grid.boundary, n, centre + d) {
crate::boundary::BoundaryHit::Inside(j) => push(j, &mut count, idx_out),
crate::boundary::BoundaryHit::RobinSkew { reflected, .. }
| crate::boundary::BoundaryHit::OddReflected { reflected } => {
push(reflected, &mut count, idx_out);
}
_ => {}
}
}
for j in [0_usize, 1, 2] {
if j < n {
push(j, &mut count, idx_out);
}
}
for k in 1..=3_usize {
if n >= k {
push(n - k, &mut count, idx_out);
}
}
count
}
pub(crate) fn weight_row(
grid: &Grid1D<f64>,
y: f64,
probe: &mut [f64],
idx_out: &mut [usize; MAX_ROW],
w_out: &mut [f64; MAX_ROW],
) -> Result<usize, SemiflowError> {
let count = candidate_nodes(grid, y, idx_out);
for slot in 0..count {
let j = idx_out[slot];
probe[j] = 1.0;
w_out[slot] = grid.interp(probe, y)?;
probe[j] = 0.0;
}
Ok(count)
}
pub(crate) fn sample_deriv(
values: &[f64],
grid: &Grid1D<f64>,
y: f64,
) -> Result<f64, SemiflowError> {
let delta = grid.dx() * 1.0e-4_f64;
let plus = grid.interp(values, y + delta)?;
let minus = grid.interp(values, y - delta)?;
Ok((plus - minus) / (delta + delta))
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ShiftCoeffField {
A,
B,
C,
}
pub struct ShiftCoeffs<'a, F: SemiflowFloat = f64> {
pub a: &'a [F],
pub b: &'a [F],
pub c: &'a [F],
}
pub struct Shift1DProblem<'a> {
pub grid: &'a Grid1D<f64>,
pub coeffs: ShiftCoeffs<'a, f64>,
pub tau: f64,
pub n_steps: usize,
}
fn feet(grid: &Grid1D<f64>, co: &ShiftCoeffs<'_, f64>, tau: f64, i: usize) -> (f64, f64, f64) {
let x = grid.x_at(i);
let s_diff = 2.0 * libm::sqrt(co.a[i] * tau);
let s_drift = 2.0 * co.b[i] * tau;
(x + s_diff, x - s_diff, x + s_drift)
}
fn forward_step(
grid: &Grid1D<f64>,
co: &ShiftCoeffs<'_, f64>,
tau: f64,
src: &[f64],
dst: &mut [f64],
) -> Result<(), SemiflowError> {
for i in 0..grid.n {
let (fp, fm, fd) = feet(grid, co, tau, i);
dst[i] = 0.25 * grid.interp(src, fp)?
+ 0.25 * grid.interp(src, fm)?
+ 0.50 * grid.interp(src, fd)?
+ tau * co.c[i] * src[i];
}
Ok(())
}
fn adjoint_step(
grid: &Grid1D<f64>,
co: &ShiftCoeffs<'_, f64>,
tau: f64,
lam: &[f64],
out: &mut [f64],
probe: &mut [f64],
) -> Result<(), SemiflowError> {
for v in out.iter_mut() {
*v = 0.0;
}
let mut idx = [0_usize; MAX_ROW];
let mut w = [0.0_f64; MAX_ROW];
for i in 0..grid.n {
let (fp, fm, fd) = feet(grid, co, tau, i);
for (foot, weight) in [(fp, 0.25), (fm, 0.25), (fd, 0.50)] {
let k = weight_row(grid, foot, probe, &mut idx, &mut w)?;
let scale = weight * lam[i];
for slot in 0..k {
out[idx[slot]] += scale * w[slot];
}
}
out[i] += tau * co.c[i] * lam[i];
}
Ok(())
}
fn param_deriv_diag(
grid: &Grid1D<f64>,
co: &ShiftCoeffs<'_, f64>,
field: ShiftCoeffField,
tau: f64,
u: &[f64],
out: &mut [f64],
) -> Result<(), SemiflowError> {
for i in 0..grid.n {
let (fp, fm, fd) = feet(grid, co, tau, i);
out[i] = match field {
ShiftCoeffField::A => {
if co.a[i] <= 0.0 {
return Err(SemiflowError::DomainViolation {
what: "shift1d_coeff_gradient: d/da undefined at a_i <= 0",
value: co.a[i],
});
}
let chain = libm::sqrt(tau / co.a[i]);
0.25 * chain * (sample_deriv(u, grid, fp)? - sample_deriv(u, grid, fm)?)
}
ShiftCoeffField::B => tau * sample_deriv(u, grid, fd)?,
ShiftCoeffField::C => tau * u[i],
};
}
Ok(())
}
fn validate_gradient_inputs(
problem: &Shift1DProblem<'_>,
u0: &[f64],
dj_du_n: &[f64],
out: &[f64],
) -> Result<(), SemiflowError> {
let n = problem.grid.n;
let co = &problem.coeffs;
for len in [
u0.len(),
dj_du_n.len(),
out.len(),
co.a.len(),
co.b.len(),
co.c.len(),
] {
if len != n {
#[allow(clippy::cast_precision_loss)]
return Err(SemiflowError::DomainViolation {
what: "shift1d_coeff_gradient: array length != grid.n",
value: len as f64,
});
}
}
if !problem.tau.is_finite() || problem.tau <= 0.0 {
return Err(SemiflowError::DomainViolation {
what: "shift1d_coeff_gradient: tau must be finite and > 0",
value: problem.tau,
});
}
Ok(())
}
pub fn shift1d_coeff_gradient(
problem: &Shift1DProblem<'_>,
field: ShiftCoeffField,
u0: &[f64],
dj_du_n: &[f64],
out: &mut [f64],
) -> Result<(), SemiflowError> {
validate_gradient_inputs(problem, u0, dj_du_n, out)?;
let (grid, co, tau) = (problem.grid, &problem.coeffs, problem.tau);
let n = grid.n;
for g in out.iter_mut() {
*g = 0.0;
}
let mut traj: Vec<Vec<f64>> = Vec::with_capacity(problem.n_steps + 1);
traj.push(u0.to_vec());
for k in 0..problem.n_steps {
let mut next = vec![0.0_f64; n];
forward_step(grid, co, tau, &traj[k], &mut next)?;
traj.push(next);
}
let mut lam = dj_du_n.to_vec();
let mut lam_next = vec![0.0_f64; n];
let mut diag = vec![0.0_f64; n];
let mut probe = vec![0.0_f64; n];
for k in (0..problem.n_steps).rev() {
param_deriv_diag(grid, co, field, tau, &traj[k], &mut diag)?;
for i in 0..n {
out[i] += lam[i] * diag[i];
}
adjoint_step(grid, co, tau, &lam, &mut lam_next, &mut probe)?;
core::mem::swap(&mut lam, &mut lam_next);
}
Ok(())
}
pub fn shift1d_forward(
problem: &Shift1DProblem<'_>,
u0: &[f64],
) -> Result<Vec<f64>, SemiflowError> {
let mut cur = u0.to_vec();
let mut next = vec![0.0_f64; problem.grid.n];
for _ in 0..problem.n_steps {
forward_step(problem.grid, &problem.coeffs, problem.tau, &cur, &mut next)?;
core::mem::swap(&mut cur, &mut next);
}
Ok(cur)
}
pub fn weight_row_dot(grid: &Grid1D<f64>, y: f64, values: &[f64]) -> Result<f64, SemiflowError> {
let mut probe = vec![0.0_f64; grid.n];
let mut idx = [0_usize; MAX_ROW];
let mut w = [0.0_f64; MAX_ROW];
let k = weight_row(grid, y, &mut probe, &mut idx, &mut w)?;
let mut acc = 0.0_f64;
for slot in 0..k {
acc += w[slot] * values[idx[slot]];
}
Ok(acc)
}
pub fn forward_once(
grid: &Grid1D<f64>,
co: &ShiftCoeffs<'_, f64>,
tau: f64,
u: &[f64],
) -> Result<Vec<f64>, SemiflowError> {
let mut out = vec![0.0_f64; grid.n];
forward_step(grid, co, tau, u, &mut out)?;
Ok(out)
}
pub fn adjoint_once(
grid: &Grid1D<f64>,
co: &ShiftCoeffs<'_, f64>,
tau: f64,
lam: &[f64],
) -> Result<Vec<f64>, SemiflowError> {
let mut out = vec![0.0_f64; grid.n];
let mut probe = vec![0.0_f64; grid.n];
adjoint_step(grid, co, tau, lam, &mut out, &mut probe)?;
Ok(out)
}