use alloc::vec;
use alloc::vec::Vec;
use crate::core::{ChebyError, ChebySeries, ChebySeriesDyn, ChebySeriesOn, ChebyTime, Domain};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RemezOptions {
pub max_iterations: usize,
pub tolerance: f64,
pub grid_size: usize,
}
impl Default for RemezOptions {
fn default() -> Self {
Self {
max_iterations: 32,
tolerance: 1e-12,
grid_size: 1024,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RemezResult<X> {
pub series_on: ChebySeriesOnAny<X>,
pub series: ChebySeriesDyn<f64>,
pub leveled_error: f64,
pub max_error: f64,
pub iterations: usize,
pub converged: bool,
pub alternations: Vec<X>,
pub domain: Domain<X>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChebySeriesOnAny<X> {
domain: Domain<X>,
coeffs: Vec<f64>,
}
impl<X: ChebyTime> ChebySeriesOnAny<X> {
#[inline]
pub fn domain(&self) -> Domain<X> {
self.domain
}
#[inline]
pub fn coeffs(&self) -> &[f64] {
&self.coeffs
}
#[inline]
pub fn evaluate(&self, x: X) -> Result<f64, ChebyError> {
if !self.domain.contains(x) {
return Err(ChebyError::EvaluationOutOfDomain);
}
Ok(crate::core::eval::evaluate(
&self.coeffs,
self.domain.normalize(x),
))
}
}
pub fn try_into_fixed<const N: usize>(coeffs: &[f64]) -> Result<ChebySeries<f64, N>, ChebyError> {
if coeffs.len() != N {
return Err(ChebyError::InvalidDegree);
}
let mut arr = [0.0; N];
arr.copy_from_slice(coeffs);
Ok(ChebySeries::new(arr))
}
pub fn remez<X>(
domain: Domain<X>,
degree: usize,
f: impl Fn(X) -> f64,
options: RemezOptions,
) -> Result<RemezResult<X>, ChebyError>
where
X: ChebyTime,
{
if degree == 0 {
return Err(ChebyError::InvalidDegree);
}
let max_iter = options.max_iterations.max(1);
let m = degree + 2; let grid = options.grid_size.max(8 * m);
if !(options.tolerance.is_finite() && options.tolerance >= 0.0) {
return Err(ChebyError::NonFiniteInput);
}
let mut taus: Vec<f64> = (0..m)
.map(|k| -(core::f64::consts::PI * k as f64 / (m - 1) as f64).cos())
.collect();
let n = degree + 1;
let mut matrix = vec![0.0; m * (m + 1)];
let mut coeffs = vec![0.0; n];
let mut residual = vec![0.0_f64; grid];
let mut blocks: Vec<(f64, f64)> = Vec::with_capacity(grid / 2 + 2);
let mut prev_max_err = f64::INFINITY;
let mut max_err = 0.0;
let mut leveled = 0.0;
let mut iterations = 0;
let mut converged = false;
for iter in 1..=max_iter {
iterations = iter;
for (i, &tau) in taus.iter().enumerate() {
let row = i * (m + 1);
if n >= 1 {
matrix[row] = 1.0;
}
if n >= 2 {
matrix[row + 1] = tau;
let two_tau = 2.0 * tau;
let mut tkm1 = 1.0;
let mut tk = tau;
for k in 2..n {
let tkp1 = two_tau * tk - tkm1;
matrix[row + k] = tkp1;
tkm1 = tk;
tk = tkp1;
}
}
matrix[row + n] = if i % 2 == 0 { 1.0 } else { -1.0 };
matrix[row + m] = f(domain.denormalize(tau));
}
if !solve_in_place(&mut matrix, m) {
return Err(ChebyError::RemezDidNotConverge);
}
for k in 0..n {
coeffs[k] = matrix[k * (m + 1) + m];
}
leveled = matrix[n * (m + 1) + m].abs();
for (i, r) in residual.iter_mut().enumerate() {
let tau = -1.0 + 2.0 * i as f64 / (grid - 1) as f64;
let x = domain.denormalize(tau);
*r = f(x) - crate::core::eval::evaluate(&coeffs, tau);
}
blocks.clear();
let mut cur_sign = 0i8;
let mut cur_best_idx = 0usize;
let mut cur_best_abs = -1.0_f64;
for (i, &r) in residual.iter().enumerate() {
let s = if r > 0.0 {
1
} else if r < 0.0 {
-1
} else {
0
};
if s == 0 {
continue;
}
if cur_sign == 0 {
cur_sign = s;
cur_best_idx = i;
cur_best_abs = r.abs();
} else if s == cur_sign {
if r.abs() > cur_best_abs {
cur_best_abs = r.abs();
cur_best_idx = i;
}
} else {
let tau = -1.0 + 2.0 * cur_best_idx as f64 / (grid - 1) as f64;
blocks.push((tau, residual[cur_best_idx]));
cur_sign = s;
cur_best_idx = i;
cur_best_abs = r.abs();
}
}
if cur_sign != 0 {
let tau = -1.0 + 2.0 * cur_best_idx as f64 / (grid - 1) as f64;
blocks.push((tau, residual[cur_best_idx]));
}
if blocks.len() < m {
return Err(ChebyError::RemezAlternationFailure);
}
let mut best_start = 0usize;
let mut best_min = -1.0_f64;
for s in 0..=blocks.len() - m {
let window_min = blocks
.iter()
.skip(s)
.take(m)
.map(|b| b.1.abs())
.fold(f64::INFINITY, f64::min);
if window_min > best_min {
best_min = window_min;
best_start = s;
}
}
let mut new_taus: Vec<f64> = blocks[best_start..best_start + m]
.iter()
.map(|&(t, _)| t)
.collect();
new_taus.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
max_err = blocks[best_start..best_start + m]
.iter()
.map(|&(_, r)| r.abs())
.fold(0.0_f64, f64::max);
let gap = (max_err - leveled).abs();
let denom = max_err.max(f64::EPSILON);
if gap / denom <= options.tolerance {
converged = true;
taus = new_taus;
break;
}
if (prev_max_err - max_err).abs() <= options.tolerance * denom && iter > 1 {
taus = new_taus;
converged = true;
break;
}
prev_max_err = max_err;
taus = new_taus;
}
if !converged {
return Err(ChebyError::RemezDidNotConverge);
}
let series = ChebySeriesDyn::new(coeffs.clone())?;
Ok(RemezResult {
series_on: ChebySeriesOnAny {
domain,
coeffs: coeffs.clone(),
},
series,
leveled_error: leveled,
max_error: max_err,
iterations,
converged,
alternations: taus.into_iter().map(|t| domain.denormalize(t)).collect(),
domain,
})
}
fn solve_in_place(a: &mut [f64], m: usize) -> bool {
let stride = m + 1;
for k in 0..m {
let mut piv_row = k;
let mut piv_abs = a[k * stride + k].abs();
for r in (k + 1)..m {
let v = a[r * stride + k].abs();
if v > piv_abs {
piv_abs = v;
piv_row = r;
}
}
if piv_abs < 1e-300 {
return false;
}
if piv_row != k {
for c in 0..stride {
a.swap(k * stride + c, piv_row * stride + c);
}
}
let pivot = a[k * stride + k];
for r in (k + 1)..m {
let factor = a[r * stride + k] / pivot;
if factor == 0.0 {
continue;
}
for c in k..stride {
a[r * stride + c] -= factor * a[k * stride + c];
}
}
}
for r in (0..m).rev() {
let mut sum = a[r * stride + (stride - 1)];
for c in (r + 1)..m {
sum -= a[r * stride + c] * a[c * stride + (stride - 1)];
}
a[r * stride + (stride - 1)] = sum / a[r * stride + r];
}
true
}
pub fn into_fixed_series_on<X: ChebyTime, const N: usize>(
result: &RemezResult<X>,
) -> Result<ChebySeriesOn<f64, X, N>, ChebyError> {
let series = try_into_fixed::<N>(result.series.coeffs())?;
Ok(ChebySeriesOn::new(result.domain, series))
}