use nalgebra::MatrixViewMut;
use crate::{
basis::{Basis, DifferentialBasis, IntegralBasis, MonomialBasis},
display::{self, format_coefficient, PolynomialDisplay, Sign, Term, DEFAULT_PRECISION},
error::Result,
statistics::DomainNormalizer,
value::{IntClampedCast, Value},
};
mod fourier;
pub use fourier::FourierBasis;
mod linear_fourier;
pub use linear_fourier::LinearAugmentedFourierBasis;
#[derive(Debug, Clone)]
pub struct AugmentedFourierBasis<const MONOMIAL_DEGREE: usize, T: Value = f64> {
normalizer: DomainNormalizer<T>,
}
impl<const MONOMIAL_DEGREE: usize, T: Value> AugmentedFourierBasis<MONOMIAL_DEGREE, T> {
pub fn new(x_min: T, x_max: T) -> Self {
let normalizer = DomainNormalizer::new((x_min, x_max), (T::zero(), T::two_pi()));
Self { normalizer }
}
pub fn from_normalizer(normalizer: DomainNormalizer<T>) -> Self {
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)
}
fn integral_coefs(&self, coefficients: &[T], constant: T) -> Result<Vec<T>> {
if coefficients.is_empty() {
return Ok(vec![constant]);
}
let monomial_terms = &coefficients[..(MONOMIAL_DEGREE + 1).min(coefficients.len())];
let fourier_terms = &coefficients[monomial_terms.len()..];
let (_, mut integral_coeffs) =
MonomialBasis::default().integral(monomial_terms, constant)?;
let new_monomial_terms = integral_coeffs.len();
let mut n = T::one(); let mut coef_iter = fourier_terms.iter();
while let Some(a) = coef_iter.next().copied() {
let b = coef_iter.next().copied().unwrap_or(T::zero());
integral_coeffs.push(b / n);
integral_coeffs.push(-a / n);
n += T::one(); }
let scale = self.normalizer.scale();
for coeff in &mut integral_coeffs[new_monomial_terms..] {
*coeff /= scale;
}
Ok(integral_coeffs)
}
fn derivative_coefs(&self, coefficients: &[T]) -> Result<Vec<T>> {
if coefficients.len() <= 1 {
return Ok(vec![T::zero()]);
}
let monomial_terms = &coefficients[..(MONOMIAL_DEGREE + 1).min(coefficients.len())];
let fourier_terms = &coefficients[monomial_terms.len()..];
let (_, mut derivative_coeffs) = MonomialBasis::default().derivative(monomial_terms)?;
let new_monomial_terms = derivative_coeffs.len();
let mut n = T::one(); let mut coef_iter = fourier_terms.iter();
while let Some(a) = coef_iter.next().copied() {
let b = coef_iter.next().copied().unwrap_or(T::zero());
derivative_coeffs.push(n * -b);
derivative_coeffs.push(n * a);
n += T::one(); }
let scale = self.normalizer.scale();
for coeff in &mut derivative_coeffs[new_monomial_terms..] {
*coeff *= scale;
}
Ok(derivative_coeffs)
}
}
impl<const MONOMIAL_DEGREE: usize, T: Value> Basis<T>
for AugmentedFourierBasis<MONOMIAL_DEGREE, T>
{
fn from_range(x_range: std::ops::RangeInclusive<T>) -> Self {
Self::new(*x_range.start(), *x_range.end())
}
#[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 k(&self, degree: usize) -> usize {
(MONOMIAL_DEGREE + 1) + 2 * degree
}
#[inline(always)]
fn degree(&self, k: usize) -> Option<usize> {
let base = MONOMIAL_DEGREE + 1;
let fourier_terms = k.checked_sub(base)?;
if fourier_terms % 2 != 0 {
return None; }
let max_harmonic = fourier_terms / 2;
Some(max_harmonic)
}
#[inline(always)]
fn solve_function(&self, j: usize, x: T) -> T {
if j <= MONOMIAL_DEGREE {
let x = self.denormalize_x(x);
return Value::powi(x, j.clamped_cast());
}
let j = j - (MONOMIAL_DEGREE + 1);
if j % 2 == 0 {
let n = (j + 1).div_ceil(2);
let angle = x * T::from_positive_int(n);
angle.sin()
} else {
let n = j.div_ceil(2);
let angle = x * T::from_positive_int(n);
angle.cos()
}
}
#[inline(always)]
fn solve(&self, x: T, coefficients: &[T]) -> T {
let mut y = T::zero();
let monomial_terms = (MONOMIAL_DEGREE + 1).min(coefficients.len());
let monomial_coefs = &coefficients[..monomial_terms];
y += MonomialBasis::default().solve(self.denormalize_x(x), monomial_coefs);
let s1 = x.sin();
let c1 = x.cos();
let mut s_n = s1;
let mut c_n = c1;
let fourier_coefs = &coefficients[monomial_terms..];
for j in 0..fourier_coefs.len() {
let coef = fourier_coefs[j];
if j % 2 == 0 {
y += coef * s_n;
} else {
y += coef * c_n;
let s_next = s_n * c1 + c_n * s1;
let c_next = c_n * c1 - s_n * s1;
s_n = s_next;
c_n = c_next;
}
}
y
}
#[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 sx = x.sin();
let cx = x.cos();
let mut j = start_index;
while j <= MONOMIAL_DEGREE.min(row.ncols() - 1) {
let x = self.denormalize_x(x);
row[j] = Value::powi(x, j.clamped_cast());
j += 1;
}
while j < row.ncols() {
let fourier_index = j - (MONOMIAL_DEGREE + 1);
row[j] = match fourier_index {
0 => sx, 1 => cx, 2 => T::two() * cx * row[1],
3 => T::two() * cx * row[2] - T::one(),
_ => T::two() * cx * row[j - 2] - row[j - 4],
};
j += 1;
}
}
}
impl<const MONOMIAL_DEGREE: usize, T: Value> PolynomialDisplay<T>
for AugmentedFourierBasis<MONOMIAL_DEGREE, T>
{
fn format_term(&self, degree: i32, coef: T) -> Option<Term> {
let polynomial_terms = MONOMIAL_DEGREE + 1;
if degree < polynomial_terms.clamped_cast() {
return MonomialBasis::format_term(&MonomialBasis::default(), degree, coef);
}
let degree = degree - polynomial_terms.clamped_cast::<i32>() + 1;
let sign = Sign::from_coef(coef);
let coef = format_coefficient(coef, degree, DEFAULT_PRECISION)?;
let n = (degree + 1) / 2;
let n = if n == 1 { String::new() } else { n.to_string() };
let function = if degree % 2 == 0 { "cos" } else { "sin" };
let x = display::unicode::subscript("s");
let x = format!("x{x}");
let glue = if coef.is_empty() || function.is_empty() {
""
} else {
"·"
};
let body = format!("{coef}{glue}{function}({n}{x})");
Some(Term { 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))
}
}
#[cfg(test)]
mod tests {}