#[must_use]
pub fn chebyshev_compute(coeffs: &[f64], x: f64) -> (f64, f64) {
assert!(
!coeffs.is_empty(),
"chebyshev_compute: coefficients must not be empty"
);
let n = coeffs.len();
if n == 1 {
return (coeffs[0], 0.0);
}
let two_x = 2.0 * x;
let mut b_k1 = 0.0;
let mut b_k2 = 0.0;
let mut db_k1 = 0.0;
let mut db_k2 = 0.0;
for k in (1..n).rev() {
let b_k = two_x * b_k1 - b_k2 + coeffs[k];
let db_k = 2.0 * b_k1 + two_x * db_k1 - db_k2;
b_k2 = b_k1;
b_k1 = b_k;
db_k2 = db_k1;
db_k1 = db_k;
}
let value = x * b_k1 - b_k2 + coeffs[0];
let derivative = b_k1 + x * db_k1 - db_k2;
(value, derivative)
}
#[must_use]
pub fn normalize_time(t: f64, t_start: f64, t_end: f64) -> f64 {
assert!(
(t_end - t_start).abs() > f64::EPSILON,
"normalize_time: zero-length interval (t_start == t_end)"
);
2.0 * (t - t_start) / (t_end - t_start) - 1.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_constant() {
let coeffs = [1.0_f64];
let (val, deriv) = chebyshev_compute(&coeffs, 0.5);
assert!((val - 1.0).abs() < 1e-15, "value: {val}");
assert!((deriv - 0.0).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_linear() {
let coeffs = [0.0_f64, 1.0];
let x = 0.7;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - x).abs() < 1e-15, "value: {val}");
assert!((deriv - 1.0).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_quadratic() {
let coeffs = [0.0_f64, 0.0, 1.0];
let x = 0.3;
let expected_val = 2.0 * x * x - 1.0;
let expected_deriv = 4.0 * x;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - expected_val).abs() < 1e-15, "value: {val}");
assert!((deriv - expected_deriv).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_cubic() {
let coeffs = [0.0_f64, 0.0, 0.0, 1.0];
let x = 0.5;
let expected_val = 4.0 * x * x * x - 3.0 * x;
let expected_deriv = 12.0 * x * x - 3.0;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - expected_val).abs() < 1e-15, "value: {val}");
assert!((deriv - expected_deriv).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_mixed_coefficients() {
let coeffs = [3.0_f64, 2.0, 1.0];
let x = 0.4;
let expected_val = 2.0 * x * x + 2.0 * x + 2.0;
let expected_deriv = 4.0 * x + 2.0;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - expected_val).abs() < 1e-14, "value: {val}");
assert!((deriv - expected_deriv).abs() < 1e-14, "deriv: {deriv}");
}
#[test]
fn compute_at_boundary_neg1() {
let coeffs = [0.0_f64, 0.0, 1.0];
let x = -1.0;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - 1.0).abs() < 1e-15, "value: {val}");
assert!((deriv - (-4.0)).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_at_boundary_pos1() {
let coeffs = [0.0_f64, 0.0, 1.0];
let x = 1.0;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - 1.0).abs() < 1e-15, "value: {val}");
assert!((deriv - 4.0).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_at_center() {
let coeffs = [0.0_f64, 0.0, 1.0];
let x = 0.0;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - (-1.0)).abs() < 1e-15, "value: {val}");
assert!((deriv - 0.0).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn compute_degree5() {
let coeffs = [0.0_f64, 0.0, 0.0, 0.0, 0.0, 1.0];
let x: f64 = 0.6;
let expected_val = 16.0 * x.powi(5) - 20.0 * x.powi(3) + 5.0 * x;
let expected_deriv = 80.0 * x.powi(4) - 60.0 * x.powi(2) + 5.0;
let (val, deriv) = chebyshev_compute(&coeffs, x);
assert!((val - expected_val).abs() < 1e-15, "value: {val}");
assert!((deriv - expected_deriv).abs() < 1e-15, "deriv: {deriv}");
}
#[test]
fn normalize_time_center() {
let result = normalize_time(1.5, 1.0, 2.0);
assert!((result - 0.0).abs() < 1e-15, "result: {result}");
}
#[test]
fn normalize_time_start() {
let result = normalize_time(1.0, 1.0, 2.0);
assert!((result - (-1.0)).abs() < 1e-15, "result: {result}");
}
#[test]
fn normalize_time_end() {
let result = normalize_time(2.0, 1.0, 2.0);
assert!((result - 1.0).abs() < 1e-15, "result: {result}");
}
#[test]
fn normalize_time_quarter() {
let result = normalize_time(1.25, 1.0, 2.0);
assert!((result - (-0.5)).abs() < 1e-15, "result: {result}");
}
}