use nalgebra::{ComplexField, MatrixViewMut};
use num_traits::{One, Zero};
use crate::{
basis::{Basis, DifferentialBasis, IntoMonomialBasis, OrthogonalBasis, RootFindingBasis},
display::{self, format_coefficient, PolynomialDisplay, Sign, Term, DEFAULT_PRECISION},
error::Result,
statistics::DomainNormalizer,
value::{FloatClampedCast, IntClampedCast, Value},
};
#[derive(Debug, Clone, Copy)]
pub struct LegendreBasis<T: Value = f64> {
pub normalizer: DomainNormalizer<T>,
}
impl<T: Value> LegendreBasis<T> {
pub fn new(x_min: T, x_max: T) -> Self {
let normalizer = DomainNormalizer::new((x_min, x_max), (-T::one(), T::one()));
Self { normalizer }
}
pub fn new_polynomial(
x_range: (T, T),
coefficients: &[T],
) -> Result<crate::Polynomial<'_, Self, T>> {
let basis = Self::new(x_range.0, x_range.1);
crate::Polynomial::<Self, T>::from_basis(basis, coefficients)
}
pub fn solve_and_derive(&self, n: usize, x: T) -> (T, T) {
let mut p0 = T::one();
let mut p1 = x;
for k in 2..=n {
let k = T::from_positive_int(k);
let p2 = ((T::two() * k - T::one()) * x * p1 - (k - T::one()) * p0) / k;
p0 = p1;
p1 = p2;
}
let p = if n == 0 { p0 } else { p1 };
let dp = T::from_positive_int(n) * (x * p - p0) / (x * x - T::one());
(p, dp)
}
}
impl<T: Value> Basis<T> for LegendreBasis<T> {
fn from_range(x_range: std::ops::RangeInclusive<T>) -> Self {
let normalizer = DomainNormalizer::from_range(x_range, (-T::one(), T::one()));
Self { normalizer }
}
#[inline(always)]
fn normalize_x(&self, x: T) -> T {
self.normalizer.normalize(x)
}
#[inline(always)]
fn denormalize_x(&self, x: T) -> T {
self.normalizer.denormalize(x)
}
#[inline(always)]
fn solve_function(&self, j: usize, x: T) -> T {
match j {
0 => T::one(),
1 => x,
_ => {
let mut p0 = T::one();
let mut p1 = x;
for n in 1..j {
let p_next = ((T::from_positive_int(2 * n + 1) * x * p1)
- (T::from_positive_int(n) * p0))
/ T::from_positive_int(n + 1);
p0 = p1;
p1 = p_next;
}
p1
}
}
}
#[inline(always)]
fn fill_matrix_row<R: nalgebra::Dim, C: nalgebra::Dim, RS: nalgebra::Dim, CS: nalgebra::Dim>(
&self,
start_index: usize,
x: T,
mut row: MatrixViewMut<T, R, C, RS, CS>,
) {
let x = x * self.gauss_weight(x);
for j in start_index..row.ncols() {
row[j] = self.solve_function(j, x);
}
}
}
impl<T: Value> PolynomialDisplay<T> for LegendreBasis<T> {
fn format_term(&self, degree: i32, coef: T) -> Option<Term> {
let sign = Sign::from_coef(coef);
let x = display::unicode::subscript("s");
let x = format!("x{x}");
let rank = display::unicode::subscript(°ree.to_string());
let func = if degree > 0 {
format!("P{rank}({x})")
} else {
String::new()
};
let coef = format_coefficient(coef, degree, DEFAULT_PRECISION)?;
let glue = if coef.is_empty() || func.is_empty() {
""
} else {
"·"
};
let body = format!("{coef}{glue}{func}");
Some(display::Term::new(sign, body))
}
fn format_scaling_formula(&self) -> Option<String> {
let x = display::unicode::subscript("s");
let x = format!("x{x}");
Some(format!("{x} = {}", self.normalizer))
}
}
impl<T: Value> IntoMonomialBasis<T> for LegendreBasis<T> {
fn as_monomial(&self, coefficients: &mut [T]) -> Result<()> {
let n = coefficients.len();
let mut result = vec![T::zero(); n];
for j in 0..n {
let c_j = coefficients[j];
for k in 0..=(j / 2) {
let sign = if k % 2 == 0 { T::one() } else { -T::one() };
let numerator = T::factorial(2 * j - 2 * k);
let denom = Value::powi(T::two(), j.clamped_cast())
* T::factorial(k)
* T::factorial(j - k)
* T::factorial(j - 2 * k);
let x_power = j - 2 * k;
result[x_power] += c_j * sign * numerator / denom;
}
}
result = self.normalizer.denormalize_coefs(&result);
coefficients.copy_from_slice(&result);
Ok(())
}
}
impl<T: Value> DifferentialBasis<T> for LegendreBasis<T> {
type B2 = LegendreBasis<T>;
fn derivative(&self, a: &[T]) -> Result<(Self::B2, Vec<T>)> {
if a.is_empty() {
return Ok((*self, vec![T::zero()]));
}
let mut b = vec![T::zero(); a.len() - 1];
for n in 1..a.len() {
for m in 0..n {
let tm1 = T::two() * T::from_positive_int(m) + T::one();
if (n - m) % 2 == 1 {
b[m] += tm1 * a[n];
}
}
}
let scale = self.normalizer.scale();
for coeff in &mut b {
*coeff *= scale;
}
Ok((*self, b))
}
}
impl<T: Value> OrthogonalBasis<T> for LegendreBasis<T> {
fn gauss_nodes(&self, n: usize) -> Vec<(T, T)> {
let tol = T::epsilon().sqrt();
let mut nodes = Vec::with_capacity(n);
let m = n.div_ceil(2); for i in 0..m {
let theta = T::pi() * (T::from_positive_int(i) + 0.75f64.clamped_cast::<T>())
/ (T::from_positive_int(n) + T::half());
let mut x = theta.cos();
loop {
let (p, dp) = self.solve_and_derive(n, x);
let dx = -p / dp;
x += dx;
if Value::abs(dx) < tol {
break;
}
}
let (_, dp) = self.solve_and_derive(n, x);
let w = T::two() / ((T::one() - x * x) * dp * dp);
nodes.push((-x, w));
if i != m - 1 || n % 2 == 0 {
nodes.push((x, w));
}
}
nodes.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
nodes
}
fn gauss_weight(&self, _: T) -> T {
T::one()
}
fn gauss_normalization(&self, n: usize) -> T {
T::two() / (T::two() * T::from_positive_int(n) + T::one())
}
}
impl<T: Value> RootFindingBasis<T> for LegendreBasis<T> {
fn complex_y(&self, z: nalgebra::Complex<T>, coefs: &[T]) -> nalgebra::Complex<T> {
use nalgebra::Complex;
if coefs.is_empty() {
return Complex::zero();
}
let mut p_nm1 = Complex::one();
if coefs.len() == 1 {
return p_nm1 * Complex::from_real(coefs[0]);
}
let mut p_n = z;
let mut result = Complex::from_real(coefs[0]) * p_nm1 + Complex::from_real(coefs[1]) * p_n;
for n in 1..coefs.len() - 1 {
let n_t = T::from_positive_int(n);
let n1_t = T::from_positive_int(n + 1);
let a = Complex::from_real(T::from_positive_int(2 * n + 1)) * z * p_n;
let b = Complex::from_real(n_t) * p_nm1;
let p_np1 = (a - b) / Complex::from_real(n1_t);
result += Complex::from_real(coefs[n + 1]) * p_np1;
p_nm1 = p_n;
p_n = p_np1;
}
result
}
}
#[cfg(test)]
mod tests {
use crate::{
assert_close, assert_fits,
score::Aic,
statistics::DegreeBound,
test::basis_assertions::{self, assert_basis_orthogonal},
LegendreFit, Polynomial,
};
use super::*;
fn get_poly() -> Polynomial<'static, LegendreBasis<f64>> {
let basis = LegendreBasis::new(0.0, 100.0);
Polynomial::from_basis(basis, &[1.0, 2.0, -0.5]).unwrap()
}
#[test]
fn test_legendre_solve_function() {
let poly = get_poly();
let data = poly.solve_range(0.0..=100.0, 1.0);
let fit = LegendreFit::new_auto(&data, DegreeBound::Relaxed, &Aic).unwrap();
assert_fits!(&poly, &fit);
assert_basis_orthogonal(fit.basis(), 3, 100, 1e-12);
let mono = fit.as_monomial().unwrap();
assert_fits!(mono, fit);
let basis = LegendreBasis::new(-1.0, 1.0);
assert_close!(basis.solve_function(0, 0.5), 1.0);
assert_close!(basis.solve_function(1, 0.5), 0.5);
assert_close!(basis.solve_function(2, 0.5), -0.125);
assert_close!(basis.solve_function(3, 0.5), -0.4375);
let poly = LegendreBasis::new_polynomial((0.0, 100.0), &[3.0, 2.0, 1.5, 3.0]).unwrap();
basis_assertions::test_derivation(&poly, &poly.basis().normalizer);
basis_assertions::test_complex_y(&poly, 0.0..=100.0);
}
}