use crate::{
basis::{AugmentedFourierBasis, DifferentialBasis, FourierBasis, IntegralBasis},
error::Result,
value::Value,
Polynomial,
};
pub type LinearAugmentedFourierBasis<T = f64> = AugmentedFourierBasis<1, T>;
impl<T: Value> DifferentialBasis<T> for LinearAugmentedFourierBasis<T> {
type B2 = FourierBasis<T>;
fn derivative(&self, coefficients: &[T]) -> Result<(Self::B2, Vec<T>)> {
let coefs = self.derivative_coefs(coefficients)?;
let basis = FourierBasis::from_normalizer(self.normalizer);
Ok((basis, coefs))
}
}
pub type LinearAugmentedFourierPolynomial<'a, T> =
crate::Polynomial<'a, LinearAugmentedFourierBasis<T>, T>;
impl<T: Value> LinearAugmentedFourierPolynomial<'_, T> {
#[allow(
clippy::missing_panics_doc,
reason = "Always has valid coefficients for Fourier basis"
)]
pub fn new(x_range: (T, T), constant: T, terms: &[(T, T)]) -> Self {
let mut coefficients = Vec::with_capacity(1 + terms.len() * 2);
coefficients.push(constant);
for (a_n, b_n) in terms {
coefficients.push(*a_n); coefficients.push(*b_n); }
let basis = LinearAugmentedFourierBasis::new(x_range.0, x_range.1);
Polynomial::from_basis(basis, coefficients).expect("Failed to create Fourier polynomial")
}
}
macro_rules! support_fourier_level {
($own_degree:literal, $next_degree:literal) => {
impl<T: Value> IntegralBasis<T> for AugmentedFourierBasis<$own_degree, T> {
type B2 = AugmentedFourierBasis<$next_degree, T>;
fn integral(&self, coefficients: &[T], constant: T) -> Result<(Self::B2, Vec<T>)> {
let coefs = self.integral_coefs(coefficients, constant)?;
let basis = Self::B2::from_normalizer(self.normalizer);
Ok((basis, coefs))
}
}
impl<T: Value> DifferentialBasis<T> for AugmentedFourierBasis<$own_degree, T> {
type B2 = AugmentedFourierBasis<$own_degree, T>;
fn derivative(&self, coefficients: &[T]) -> Result<(Self::B2, Vec<T>)> {
let coefs = self.derivative_coefs(coefficients)?;
let basis = Self::B2::from_normalizer(self.normalizer);
Ok((basis, coefs))
}
}
};
}
support_fourier_level!(2, 3);
support_fourier_level!(3, 4);
#[cfg(test)]
mod tests {
use super::*;
use crate::{
assert_close, assert_fits, basis::Basis, score::Aic, statistics::DegreeBound,
test::basis_assertions, LinearAugmentedFourierFit, Polynomial,
};
fn get_poly() -> Polynomial<'static, LinearAugmentedFourierBasis<f64>> {
let basis = LinearAugmentedFourierBasis::new(0.0, 100.0);
Polynomial::from_basis(basis, &[1.0, 0.6, 3.0, -0.5]).unwrap()
}
#[test]
#[allow(clippy::unreadable_literal)]
fn test_basis() {
let poly = get_poly();
let data = poly.solve_range(0.0..=100.0, 1.0);
crate::plot!(data);
let fit = LinearAugmentedFourierFit::new_auto(&data, DegreeBound::Relaxed, &Aic).unwrap();
assert_fits!(&poly, &fit);
let basis = LinearAugmentedFourierBasis::new(0.0, 2.0 * std::f64::consts::PI);
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.479425538604203);
assert_close!(basis.solve_function(3, 0.5), 0.8775825618903728);
assert_close!(basis.solve_function(4, 0.5), 0.8414709848078965);
let poly =
LinearAugmentedFourierBasis::new_polynomial((0.0, 100.0), &[0.5, 2.0, 3.0, -1.5])
.unwrap();
let normalizer = poly.basis().normalizer;
basis_assertions::test_reversible_derivation(&poly, &normalizer);
}
}