pub fn diff_central(xs: &[f64], ys: &[f64]) -> Result<Vec<f64>, String> {
if xs.len() != ys.len() {
return Err(format!(
"diff_central: xs and ys must have equal length, got {} and {}",
xs.len(),
ys.len()
));
}
if xs.len() < 3 {
return Err("diff_central: need at least 3 points".into());
}
let n = xs.len();
let mut result = Vec::with_capacity(n - 2);
for i in 1..n - 1 {
let dx = xs[i + 1] - xs[i - 1];
if dx == 0.0 {
return Err(format!(
"diff_central: zero spacing at index {}: xs[{}]={} == xs[{}]={}",
i,
i - 1,
xs[i - 1],
i + 1,
xs[i + 1]
));
}
result.push((ys[i + 1] - ys[i - 1]) / dx);
}
Ok(result)
}
pub fn diff_forward(xs: &[f64], ys: &[f64]) -> Result<Vec<f64>, String> {
if xs.len() != ys.len() {
return Err(format!(
"diff_forward: xs and ys must have equal length, got {} and {}",
xs.len(),
ys.len()
));
}
if xs.len() < 2 {
return Err("diff_forward: need at least 2 points".into());
}
let n = xs.len();
let mut result = Vec::with_capacity(n - 1);
for i in 0..n - 1 {
let dx = xs[i + 1] - xs[i];
if dx == 0.0 {
return Err(format!(
"diff_forward: zero spacing at index {}: xs[{}]={} == xs[{}]={}",
i, i, xs[i], i + 1, xs[i + 1]
));
}
result.push((ys[i + 1] - ys[i]) / dx);
}
Ok(result)
}
pub fn gradient_1d(ys: &[f64], dx: f64) -> Vec<f64> {
let n = ys.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![0.0];
}
let mut result = Vec::with_capacity(n);
result.push((ys[1] - ys[0]) / dx);
let inv_2dx = 1.0 / (2.0 * dx);
for i in 1..n - 1 {
result.push((ys[i + 1] - ys[i - 1]) * inv_2dx);
}
result.push((ys[n - 1] - ys[n - 2]) / dx);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diff_central_quadratic() {
let xs: Vec<f64> = (0..=40).map(|i| i as f64 * 0.1).collect();
let ys: Vec<f64> = xs.iter().map(|&x| x * x).collect();
let derivs = diff_central(&xs, &ys).unwrap();
let idx = 19; assert!(
(derivs[idx] - 4.0).abs() < 1e-10,
"diff_central at x=2: expected ~4.0, got {}",
derivs[idx]
);
}
#[test]
fn test_diff_forward_linear() {
let xs = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let ys = vec![1.0, 4.0, 7.0, 10.0, 13.0];
let derivs = diff_forward(&xs, &ys).unwrap();
for &d in &derivs {
assert!((d - 3.0).abs() < 1e-12);
}
}
#[test]
fn test_gradient_1d_quadratic() {
let ys = vec![0.0, 1.0, 4.0, 9.0, 16.0];
let grad = gradient_1d(&ys, 1.0);
assert_eq!(grad.len(), 5);
assert!((grad[0] - 1.0).abs() < 1e-12);
assert!((grad[1] - 2.0).abs() < 1e-12);
assert!((grad[2] - 4.0).abs() < 1e-12);
assert!((grad[3] - 6.0).abs() < 1e-12);
assert!((grad[4] - 7.0).abs() < 1e-12);
}
#[test]
fn test_diff_central_length_mismatch() {
assert!(diff_central(&[0.0, 1.0, 2.0], &[0.0, 1.0]).is_err());
}
#[test]
fn test_diff_forward_too_few_points() {
assert!(diff_forward(&[0.0], &[0.0]).is_err());
}
#[test]
fn test_gradient_1d_empty() {
assert!(gradient_1d(&[], 1.0).is_empty());
}
#[test]
fn test_gradient_1d_single() {
assert_eq!(gradient_1d(&[5.0], 1.0), vec![0.0]);
}
#[test]
fn test_determinism() {
let xs: Vec<f64> = (0..=100).map(|i| i as f64 * 0.01).collect();
let ys: Vec<f64> = xs.iter().map(|&x| x.sin()).collect();
let r1 = diff_central(&xs, &ys).unwrap();
let r2 = diff_central(&xs, &ys).unwrap();
for (a, b) in r1.iter().zip(r2.iter()) {
assert_eq!(a.to_bits(), b.to_bits());
}
}
}