use super::types::{EstimatorResult, WelfordAccumulator};
use crate::bytecode_tape::BytecodeTape;
use crate::taylor::Taylor;
use crate::taylor_dyn::{TaylorArenaLocal, TaylorDyn, TaylorDynGuard};
use crate::Float;
pub fn diagonal_kth_order<F: Float + TaylorArenaLocal>(
tape: &BytecodeTape<F>,
x: &[F],
k: usize,
) -> (F, Vec<F>) {
let mut buf = Vec::new();
diagonal_kth_order_with_buf(tape, x, k, &mut buf)
}
pub fn diagonal_kth_order_with_buf<F: Float + TaylorArenaLocal>(
tape: &BytecodeTape<F>,
x: &[F],
k: usize,
buf: &mut Vec<TaylorDyn<F>>,
) -> (F, Vec<F>) {
assert!(k >= 2, "k must be >= 2 (use gradient for k=1)");
assert!(
k <= 20,
"k must be <= 20 (k! loses f64 precision for k > 18)"
);
assert!(
k < 13 || std::mem::size_of::<F>() > 4,
"k must be < 13 for f32 (k! loses precision for k >= 13; use f64)"
);
let n = tape.num_inputs();
assert_eq!(x.len(), n, "x.len() must match tape.num_inputs()");
let order = k + 1; let _guard = TaylorDynGuard::<F>::new(order);
let mut k_factorial = F::one();
for i in 2..=k {
k_factorial = k_factorial * F::from(i).unwrap();
}
let mut diag = Vec::with_capacity(n);
let mut value = F::zero();
for j in 0..n {
let inputs: Vec<TaylorDyn<F>> = (0..n)
.map(|i| {
let mut coeffs = vec![F::zero(); order];
coeffs[0] = x[i];
if i == j {
coeffs[1] = F::one();
}
TaylorDyn::from_coeffs(&coeffs)
})
.collect();
tape.forward_tangent(&inputs, buf);
let out_coeffs = buf[tape.output_index()].coeffs();
value = out_coeffs[0];
diag.push(k_factorial * out_coeffs[k]);
}
(value, diag)
}
pub fn diagonal_kth_order_const<F: Float, const ORDER: usize>(
tape: &BytecodeTape<F>,
x: &[F],
) -> (F, Vec<F>) {
let mut buf: Vec<Taylor<F, ORDER>> = Vec::new();
diagonal_kth_order_const_with_buf(tape, x, &mut buf)
}
pub fn diagonal_kth_order_const_with_buf<F: Float, const ORDER: usize>(
tape: &BytecodeTape<F>,
x: &[F],
buf: &mut Vec<Taylor<F, ORDER>>,
) -> (F, Vec<F>) {
const { assert!(ORDER >= 3, "ORDER must be >= 3 (k=ORDER-1 >= 2)") }
let k = ORDER - 1;
assert!(
k < 13 || std::mem::size_of::<F>() > 4,
"k must be < 13 for f32 (k! loses precision for k >= 13; use f64)"
);
let n = tape.num_inputs();
assert_eq!(x.len(), n, "x.len() must match tape.num_inputs()");
let mut k_factorial = F::one();
for i in 2..=k {
k_factorial = k_factorial * F::from(i).unwrap();
}
let mut diag = Vec::with_capacity(n);
let mut value = F::zero();
for j in 0..n {
let inputs: Vec<Taylor<F, ORDER>> = (0..n)
.map(|i| {
let mut coeffs = [F::zero(); ORDER];
coeffs[0] = x[i];
if i == j {
coeffs[1] = F::one();
}
Taylor::new(coeffs)
})
.collect();
tape.forward_tangent(&inputs, buf);
let out = buf[tape.output_index()];
value = out.coeffs[0];
diag.push(k_factorial * out.coeffs[k]);
}
(value, diag)
}
pub fn diagonal_kth_order_stochastic<F: Float + TaylorArenaLocal>(
tape: &BytecodeTape<F>,
x: &[F],
k: usize,
sampled_indices: &[usize],
) -> EstimatorResult<F> {
assert!(
!sampled_indices.is_empty(),
"sampled_indices must not be empty"
);
assert!(k >= 2, "k must be >= 2 (use gradient for k=1)");
assert!(
k <= 20,
"k must be <= 20 (k! loses f64 precision for k > 18)"
);
assert!(
k < 13 || std::mem::size_of::<F>() > 4,
"k must be < 13 for f32 (k! loses precision for k >= 13; use f64)"
);
let n = tape.num_inputs();
assert_eq!(x.len(), n, "x.len() must match tape.num_inputs()");
let order = k + 1;
let _guard = TaylorDynGuard::<F>::new(order);
let mut k_factorial = F::one();
for i in 2..=k {
k_factorial = k_factorial * F::from(i).unwrap();
}
let nf = F::from(n).unwrap();
let mut value = F::zero();
let mut acc = WelfordAccumulator::new();
let mut buf: Vec<TaylorDyn<F>> = Vec::new();
for &j in sampled_indices {
assert!(j < n, "sampled index {} out of bounds (n={})", j, n);
let inputs: Vec<TaylorDyn<F>> = (0..n)
.map(|i| {
let mut coeffs = vec![F::zero(); order];
coeffs[0] = x[i];
if i == j {
coeffs[1] = F::one();
}
TaylorDyn::from_coeffs(&coeffs)
})
.collect();
tape.forward_tangent(&inputs, &mut buf);
let out_coeffs = buf[tape.output_index()].coeffs();
value = out_coeffs[0];
acc.update(k_factorial * out_coeffs[k]);
}
let (mean, sample_variance, standard_error) = acc.finalize();
EstimatorResult {
value,
estimate: mean * nf,
sample_variance: sample_variance * nf * nf,
standard_error: standard_error * nf,
num_samples: sampled_indices.len(),
}
}
pub fn taylor_jet_dyn<F: Float + TaylorArenaLocal>(
tape: &BytecodeTape<F>,
x: &[F],
v: &[F],
order: usize,
) -> Vec<F> {
let n = tape.num_inputs();
assert_eq!(x.len(), n, "x.len() must match tape.num_inputs()");
assert_eq!(v.len(), n, "v.len() must match tape.num_inputs()");
assert!(order >= 2, "order must be >= 2");
let _guard = TaylorDynGuard::<F>::new(order);
let inputs: Vec<TaylorDyn<F>> = x
.iter()
.zip(v.iter())
.map(|(&xi, &vi)| {
let mut coeffs = vec![F::zero(); order];
coeffs[0] = xi;
coeffs[1] = vi;
TaylorDyn::from_coeffs(&coeffs)
})
.collect();
let mut buf = Vec::new();
tape.forward_tangent(&inputs, &mut buf);
buf[tape.output_index()].coeffs()
}
pub fn laplacian_dyn<F: Float + TaylorArenaLocal>(
tape: &BytecodeTape<F>,
x: &[F],
directions: &[&[F]],
) -> (F, F) {
assert!(!directions.is_empty(), "directions must not be empty");
let n = tape.num_inputs();
assert_eq!(x.len(), n, "x.len() must match tape.num_inputs()");
let _guard = TaylorDynGuard::<F>::new(3);
let two = F::from(2.0).unwrap();
let s = F::from(directions.len()).unwrap();
let mut sum = F::zero();
let mut value = F::zero();
let mut buf: Vec<TaylorDyn<F>> = Vec::new();
for v in directions {
assert_eq!(v.len(), n, "direction length must match tape.num_inputs()");
let inputs: Vec<TaylorDyn<F>> = x
.iter()
.zip(v.iter())
.map(|(&xi, &vi)| TaylorDyn::from_coeffs(&[xi, vi, F::zero()]))
.collect();
tape.forward_tangent(&inputs, &mut buf);
let out = buf[tape.output_index()];
let coeffs = out.coeffs();
value = coeffs[0];
let c2 = coeffs[2];
sum = sum + two * c2;
}
(value, sum / s)
}