use petir::mathf::RealMath;
use crate::primitives::scalar::VSMALL;
#[derive(Debug, Clone, Copy)]
pub struct Polynomial<const N: usize> {
coeffs: [f64; N],
log_coeff: f64,
log_active: bool,
}
impl<const N: usize> Polynomial<N> {
#[inline]
pub fn new(coeffs: [f64; N]) -> Self {
Self {
coeffs,
log_coeff: 0.0,
log_active: false,
}
}
#[inline]
pub fn coeffs(&self) -> &[f64; N] {
&self.coeffs
}
#[inline]
pub fn log_coeff(&self) -> f64 {
self.log_coeff
}
#[inline]
pub fn log_active(&self) -> bool {
self.log_active
}
pub fn value(&self, x: f64) -> f64 {
let mut val = self.coeffs.first().copied().unwrap_or(0.0);
let mut pow_x = x;
for &c_i in self.coeffs.iter().skip(1) {
val += c_i * pow_x;
pow_x *= x;
}
if self.log_active {
val += self.log_coeff * x.r_ln();
}
val
}
pub fn derivative(&self, x: f64) -> f64 {
let mut deriv = 0.0;
if let Some((&c1, tail)) = self.coeffs.split_first().and_then(|(_, r)| r.split_first()) {
deriv = c1;
let mut pow_x = x;
for (i, &c_i) in (2..).zip(tail.iter()) {
deriv += (i as f64) * c_i * pow_x;
pow_x *= x;
}
}
if self.log_active {
deriv += self.log_coeff / x;
}
deriv
}
pub fn integral(&self, x1: f64, x2: f64) -> f64 {
let mut pow_x1 = x1;
let mut pow_x2 = x2;
let mut integ = self.coeffs.first().copied().unwrap_or(0.0) * (x2 - x1);
for (i, &c_i) in (1..).zip(self.coeffs.iter().skip(1)) {
pow_x1 *= x1;
pow_x2 *= x2;
integ += c_i / (i as f64 + 1.0) * (pow_x2 - pow_x1);
}
if self.log_active {
integ += self.log_coeff * ((x2 * x2.r_ln() - x2) - (x1 * x1.r_ln() - x1));
}
integ
}
pub fn integral_minus1(&self, int_constant: f64) -> Self {
let mut new_coeffs = [0.0f64; N];
if let Some(slot) = new_coeffs.first_mut() {
*slot = int_constant;
}
for (i, (slot, &c_i)) in (1..).zip(
new_coeffs
.iter_mut()
.skip(1)
.zip(self.coeffs.iter().skip(1)),
) {
*slot = c_i / (i as f64);
}
let log_coeff = self.coeffs.first().copied().unwrap_or(0.0);
let log_active = log_coeff.abs() > VSMALL;
Self {
coeffs: new_coeffs,
log_coeff,
log_active,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constant_poly() {
let p = Polynomial::new([5.0_f64; 1]);
assert_eq!(p.value(3.0), 5.0);
assert_eq!(p.derivative(3.0), 0.0);
assert!((p.integral(0.0, 2.0) - 10.0).abs() < 1e-14);
}
#[test]
fn linear_poly() {
let p = Polynomial::new([1.0, 2.0]);
assert!((p.value(3.0) - 7.0).abs() < 1e-14);
assert!((p.derivative(0.0) - 2.0).abs() < 1e-14);
assert!((p.integral(0.0, 1.0) - 2.0).abs() < 1e-14);
}
#[test]
fn quadratic_poly() {
let p = Polynomial::new([1.0, 2.0, 3.0]);
assert!((p.value(2.0) - 17.0).abs() < 1e-14); assert!((p.derivative(2.0) - 14.0).abs() < 1e-14); assert!((p.integral(0.0, 1.0) - 3.0).abs() < 1e-14);
}
#[test]
fn integral_minus1_log_term() {
let p = Polynomial::new([1.0, 2.0]);
let pi = p.integral_minus1(0.0);
assert!(pi.log_active());
assert!((pi.log_coeff() - 1.0).abs() < 1e-14);
let e = std::f64::consts::E;
assert!((pi.value(e) - (2.0 * e + 1.0)).abs() < 1e-12);
}
#[test]
fn integral_additivity() {
let p = Polynomial::new([1.0, 2.0, 3.0]);
let i01 = p.integral(0.0, 1.0);
let i12 = p.integral(1.0, 2.0);
let i02 = p.integral(0.0, 2.0);
assert!((i01 + i12 - i02).abs() < 1e-12);
}
}