use crate::nodes;
use crate::scalar::ChebyScalar;
#[inline]
pub fn fit_coeffs<T: ChebyScalar, const N: usize>(values: &[T; N]) -> [T; N] {
let mut coeffs = [T::zero(); N];
let n = N as f64;
for (j, coeff) in coeffs.iter_mut().enumerate() {
let mut sum = T::zero();
for (k, value) in values.iter().enumerate() {
let arg = std::f64::consts::PI * (j as f64) * (2.0 * k as f64 + 1.0) / (2.0 * n);
sum = sum + *value * arg.cos();
}
*coeff = if j == 0 { sum / n } else { sum * (2.0 / n) };
}
coeffs
}
#[inline]
pub fn fit_from_fn<T: ChebyScalar, const N: usize>(
f: impl Fn(f64) -> T,
start: f64,
end: f64,
) -> [T; N] {
let mapped: [f64; N] = nodes::nodes_mapped(start, end);
let values: [T; N] = std::array::from_fn(|k| f(mapped[k]));
fit_coeffs(&values)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::evaluate;
#[test]
fn test_fit_constant() {
let values = [5.0_f64; 9];
let coeffs = fit_coeffs(&values);
assert!((coeffs[0] - 5.0).abs() < 1e-14);
for &c in &coeffs[1..] {
assert!(c.abs() < 1e-14, "non-zero higher coeff: {c}");
}
}
#[test]
fn test_fit_linear() {
let xi: [f64; 9] = crate::nodes();
let values: [f64; 9] = std::array::from_fn(|k| xi[k]);
let coeffs = fit_coeffs(&values);
assert!(coeffs[0].abs() < 1e-14, "c_0 = {}", coeffs[0]);
assert!((coeffs[1] - 1.0).abs() < 1e-14, "c_1 = {}", coeffs[1]);
for &c in &coeffs[2..] {
assert!(c.abs() < 1e-14, "non-zero higher coeff: {c}");
}
}
#[test]
fn test_fit_sin_roundtrip() {
let coeffs: [f64; 15] = fit_from_fn(f64::sin, -1.0, 1.0);
for &x in &[-0.9, -0.5, 0.0, 0.3, 0.8] {
let approx = evaluate(&coeffs, x);
let exact = x.sin();
assert!(
(approx - exact).abs() < 1e-12,
"sin({x}): approx={approx}, exact={exact}"
);
}
}
#[test]
fn test_fit_from_fn_mapped() {
let coeffs: [f64; 15] = fit_from_fn(f64::sin, 0.0, std::f64::consts::PI);
let approx = evaluate(&coeffs, 0.0);
assert!((approx - 1.0).abs() < 1e-12, "sin(π/2) ≈ {approx}");
let approx2 = evaluate(&coeffs, -0.5);
let exact2 = (std::f64::consts::PI / 4.0).sin();
assert!(
(approx2 - exact2).abs() < 1e-10,
"sin(π/4) ≈ {approx2}, exact = {exact2}"
);
}
#[test]
fn test_fit_quantity_type() {
use qtty::Quantity;
type Km = qtty::Kilometer;
type Kilometers = Quantity<Km>;
let xi: [f64; 9] = crate::nodes();
let values: [Kilometers; 9] =
std::array::from_fn(|k| Kilometers::new(xi[k].sin() * 1000.0));
let coeffs = fit_coeffs(&values);
let val = evaluate(&coeffs, 0.0);
let f64_vals: [f64; 9] = std::array::from_fn(|k| xi[k].sin() * 1000.0);
let f64_coeffs = fit_coeffs(&f64_vals);
let f64_val = evaluate(&f64_coeffs, 0.0);
assert!(
(val.value() - f64_val).abs() < 1e-10,
"quantity val = {}, f64 val = {f64_val}",
val.value()
);
}
}