#![deny(clippy::indexing_slicing)]
use crate::error::{LinalgError, PolynomialError};
use crate::linear_algebra::{Matrix, PivotedQr, Vector};
use crate::polynomial::Polynomial;
use crate::scalar::{Jet, Numeric};
fn multiply_in_a_root<T: Numeric>(coefficients: &mut [T], raised_degree: usize, root: T) {
let mut carried = T::ZERO;
for power in 0..=raised_degree {
let current = coefficients.get(power).copied().unwrap_or(T::ZERO);
if let Some(slot) = coefficients.get_mut(power) {
*slot = carried - root * current;
}
carried = current;
}
}
fn sample_range<T: Numeric>(nodes: &[T]) -> Result<(T, T), PolynomialError> {
let mut smallest = nodes.first().copied().ok_or(PolynomialError::Empty)?;
let mut largest = smallest;
for node in nodes {
smallest = smallest.min(*node);
largest = largest.max(*node);
}
let half_width = (largest - smallest) * T::HALF;
if half_width == T::ZERO {
return Err(PolynomialError::DuplicateNode);
}
Ok(((smallest + largest) * T::HALF, half_width))
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Polynomial<COEFFICIENT_COUNT, T> {
pub fn from_roots(roots: &[T]) -> Result<Self, PolynomialError> {
if roots.iter().any(|root| !root.is_finite()) {
return Err(PolynomialError::NonFinite);
}
if roots.len() >= COEFFICIENT_COUNT {
return Err(PolynomialError::DegreeOverflow);
}
let mut coefficients = [T::ZERO; COEFFICIENT_COUNT];
match coefficients.get_mut(0) {
Some(slot) => *slot = T::ONE,
None => return Err(PolynomialError::DegreeOverflow),
}
for (index, root) in roots.iter().enumerate() {
multiply_in_a_root(&mut coefficients, index + 1, *root);
}
Ok(Self::new(coefficients))
}
pub fn from_jet<const JET_ORDER: usize>(
jet: &Jet<T, JET_ORDER>,
) -> Result<Self, PolynomialError> {
if JET_ORDER > COEFFICIENT_COUNT {
return Err(PolynomialError::DegreeOverflow);
}
let mut coefficients = [T::ZERO; COEFFICIENT_COUNT];
for (slot, coefficient) in coefficients.iter_mut().zip(jet.coeffs.iter()) {
*slot = *coefficient;
}
Ok(Self::new(coefficients))
}
#[must_use]
pub fn chebyshev_nodes(lower: T, upper: T) -> [T; COEFFICIENT_COUNT] {
let centre = (lower + upper) * T::HALF;
let half_width = (upper - lower) * T::HALF;
let count = T::from_usize(COEFFICIENT_COUNT);
let mut nodes = [T::ZERO; COEFFICIENT_COUNT];
for (index, slot) in nodes.iter_mut().enumerate() {
let step = COEFFICIENT_COUNT - 1 - index;
let angle = (T::TWO * T::from_usize(step) + T::ONE) * T::PI / (T::TWO * count);
*slot = centre + half_width * angle.cos();
}
nodes
}
pub fn from_points(
nodes: &[T; COEFFICIENT_COUNT],
values: &[T; COEFFICIENT_COUNT],
) -> Result<Self, PolynomialError> {
if nodes
.iter()
.chain(values.iter())
.any(|value| !value.is_finite())
{
return Err(PolynomialError::NonFinite);
}
if COEFFICIENT_COUNT <= 1 {
return Ok(Self::new(*values));
}
let (centre, half_width) = sample_range(nodes)?;
let mut normalized = [T::ZERO; COEFFICIENT_COUNT];
for (slot, node) in normalized.iter_mut().zip(nodes.iter()) {
*slot = (*node - centre) / half_width;
}
let mut table = *values;
let mut bends = [T::ZERO; COEFFICIENT_COUNT];
if let Some(slot) = bends.get_mut(0) {
*slot = table.first().copied().unwrap_or(T::ZERO);
}
for order in 1..COEFFICIENT_COUNT {
for index in 0..COEFFICIENT_COUNT - order {
let current = table.get(index).copied().unwrap_or(T::ZERO);
let next = table.get(index + 1).copied().unwrap_or(T::ZERO);
let near = normalized.get(index).copied().unwrap_or(T::ZERO);
let far = normalized.get(index + order).copied().unwrap_or(T::ZERO);
let gap = far - near;
if gap == T::ZERO {
return Err(PolynomialError::DuplicateNode);
}
if let Some(slot) = table.get_mut(index) {
*slot = (next - current) / gap;
}
}
if let Some(slot) = bends.get_mut(order) {
*slot = table.first().copied().unwrap_or(T::ZERO);
}
}
let mut coefficients = [T::ZERO; COEFFICIENT_COUNT];
if let Some(slot) = coefficients.get_mut(0) {
*slot = bends.last().copied().unwrap_or(T::ZERO);
}
for step in (0..COEFFICIENT_COUNT - 1).rev() {
let node = normalized.get(step).copied().unwrap_or(T::ZERO);
multiply_in_a_root(&mut coefficients, COEFFICIENT_COUNT - 1 - step, node);
if let Some(slot) = coefficients.get_mut(0) {
*slot += bends.get(step).copied().unwrap_or(T::ZERO);
}
}
Ok(Self::new(coefficients)
.scale_argument(T::ONE / half_width)
.shift_argument(-centre))
}
pub fn fit_least_squares<const SAMPLE_COUNT: usize>(
nodes: &[T; SAMPLE_COUNT],
values: &[T; SAMPLE_COUNT],
) -> Result<Self, PolynomialError> {
if SAMPLE_COUNT < COEFFICIENT_COUNT {
return Err(PolynomialError::TooFewSamples);
}
if nodes
.iter()
.chain(values.iter())
.any(|value| !value.is_finite())
{
return Err(PolynomialError::NonFinite);
}
let (centre, half_width) = sample_range(nodes)?;
let mut design = Matrix::<SAMPLE_COUNT, COEFFICIENT_COUNT, T>::zeros();
for (row, node) in nodes.iter().enumerate() {
let normalized = (*node - centre) / half_width;
let mut raised = T::ONE;
for column in 0..COEFFICIENT_COUNT {
if let Some(slot) = design.get_mut(row, column) {
*slot = raised;
}
raised *= normalized;
}
}
let solved = PivotedQr::decompose(design)?.solve_least_squares(Vector::new(*values))?;
Ok(Self::new(solved.into_array())
.scale_argument(T::ONE / half_width)
.shift_argument(-centre))
}
}
pub(crate) fn endpoint_mapping<T: Numeric>(span: T) -> Matrix<8, 8, T> {
let mut mapping = Matrix::<8, 8, T>::zeros();
if let Some(slot) = mapping.get_mut(0, 0) {
*slot = T::ONE;
}
let mut ways = T::ONE;
let mut span_raised = T::ONE;
for order in 1..4 {
ways *= T::from_usize(order);
span_raised *= span;
if let Some(slot) = mapping.get_mut(order, order) {
*slot = ways / span_raised;
}
}
for column in 0..8 {
if let Some(slot) = mapping.get_mut(4, column) {
*slot = T::ONE;
}
}
let mut span_raised = T::ONE;
for order in 1..4 {
span_raised *= span;
for column in order..8 {
let mut ways = T::ONE;
for step in 0..order {
ways *= T::from_usize(column - step);
}
if let Some(slot) = mapping.get_mut(4 + order, column) {
*slot = ways / span_raised;
}
}
}
mapping
}
pub(crate) fn endpoint_mapping_inverse<T: Numeric>(
span: T,
) -> Result<Matrix<8, 8, T>, LinalgError> {
endpoint_mapping(span).inverse()
}
impl<T: Numeric> Polynomial<4, T> {
pub fn from_endpoint_derivatives(
start_value: T,
start_slope: T,
end_value: T,
end_slope: T,
span: T,
) -> Result<Self, PolynomialError> {
for value in [start_value, start_slope, end_value, end_slope, span] {
if !value.is_finite() {
return Err(PolynomialError::NonFinite);
}
}
if span <= T::ZERO {
return Err(PolynomialError::SpanNotPositive);
}
let start_rise = start_slope * span;
let end_rise = end_slope * span;
Ok(Self::new([
start_value,
start_rise,
-T::THREE * start_value - T::TWO * start_rise + T::THREE * end_value - end_rise,
T::TWO * start_value + start_rise - T::TWO * end_value + end_rise,
]))
}
}
impl<T: Numeric> Polynomial<8, T> {
pub fn from_endpoint_derivatives(
start: &[T; 4],
end: &[T; 4],
span: T,
) -> Result<Self, PolynomialError> {
if !span.is_finite()
|| start
.iter()
.chain(end.iter())
.any(|value| !value.is_finite())
{
return Err(PolynomialError::NonFinite);
}
if span <= T::ZERO {
return Err(PolynomialError::SpanNotPositive);
}
let mut wanted = [T::ZERO; 8];
for (slot, value) in wanted.iter_mut().zip(start.iter().chain(end.iter())) {
*slot = *value;
}
let coefficients = endpoint_mapping_inverse(span)? * Vector::new(wanted);
Ok(Self::new(coefficients.into_array()))
}
}