#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ChebyshevLimits {
pub max_coefficients: usize,
pub max_steps: u64,
}
pub const DEFAULT_CHEBYSHEV_LIMITS: ChebyshevLimits = ChebyshevLimits {
max_coefficients: 65_536,
max_steps: 1_000_000,
};
impl Default for ChebyshevLimits {
fn default() -> Self {
DEFAULT_CHEBYSHEV_LIMITS
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ChebyshevError {
InvalidTime,
OrderOverflow,
OutputLimit,
WorkLimit,
Allocation,
Convergence,
}
impl core::fmt::Display for ChebyshevError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::InvalidTime => "time must be finite and nonnegative",
Self::OrderOverflow => "order plus one overflows",
Self::OutputLimit => "coefficient count exceeds the output limit",
Self::WorkLimit => "coefficient work exceeds the step limit",
Self::Allocation => "coefficient allocation or layout was refused",
Self::Convergence => "coefficient estimates did not converge",
})
}
}
impl std::error::Error for ChebyshevError {}
struct Work {
used: u64,
limit: u64,
}
impl Work {
fn charge(&mut self, steps: u64) -> Result<(), ChebyshevError> {
let next = self
.used
.checked_add(steps)
.ok_or(ChebyshevError::WorkLimit)?;
if next > self.limit {
return Err(ChebyshevError::WorkLimit);
}
self.used = next;
Ok(())
}
}
fn zeroed<T: Clone + Default>(len: usize) -> Result<Vec<T>, ChebyshevError> {
let mut result = Vec::new();
result
.try_reserve_exact(len)
.map_err(|_| ChebyshevError::Allocation)?;
result.resize(len, T::default());
Ok(result)
}
fn log_tail(tau: f64, depth: usize) -> f64 {
let n = depth as f64 + 1.0;
let u = n / tau;
std::f64::consts::LN_2 + n * (u / (1.0 + u.hypot(1.0)) - u.asinh())
}
pub fn try_chebyshev_coeffs(
tau: f32,
order: usize,
limits: ChebyshevLimits,
) -> Result<Vec<f32>, ChebyshevError> {
if !tau.is_finite() || tau < 0.0 {
return Err(ChebyshevError::InvalidTime);
}
let count = order.checked_add(1).ok_or(ChebyshevError::OrderOverflow)?;
if count > limits.max_coefficients {
return Err(ChebyshevError::OutputLimit);
}
let mut work = Work {
used: 0,
limit: limits.max_steps,
};
if tau == 0.0 {
work.charge(count as u64)?;
let mut result = zeroed(count)?;
result[0] = 1.0;
return Ok(result);
}
let mut depth = count.max(16);
let tau = f64::from(tau);
let mut tail;
loop {
work.charge(1)?;
tail = log_tail(tau, depth);
if !tail.is_finite() {
return Err(ChebyshevError::Convergence);
}
let required = (depth as u64)
.checked_add(count as u64)
.ok_or(ChebyshevError::WorkLimit)?;
if required > limits.max_steps - work.used {
return Err(ChebyshevError::WorkLimit);
}
if tail <= -40.0 * std::f64::consts::LN_2 {
break;
}
depth = depth.checked_mul(2).ok_or(ChebyshevError::WorkLimit)?;
}
let mut ratios: Vec<[f64; 2]> = zeroed(order)?;
let mut result = zeroed(count)?;
let mut previous_gap = f64::INFINITY;
loop {
work.charge(
(depth as u64)
.checked_add(count as u64)
.ok_or(ChebyshevError::WorkLimit)?,
)?;
let mut ratio = [0.0, 1.0];
let mut sum = [1.0, 1.0];
for j in (1..=depth).rev() {
let term = 2.0 * j as f64 / tau;
for side in 0..2 {
ratio[side] = 1.0 / (term + ratio[side]);
sum[side] = 1.0 + ratio[side] * sum[side];
}
if j <= order {
ratios[j - 1] = ratio;
}
}
let mut q = [1.0 / (2.0 * sum[0] - 1.0), 1.0 / (2.0 * sum[1] - 1.0)];
let tail_estimate = tail.exp();
let mut largest_gap = 0.0_f64;
for k in 0..count {
if k != 0 {
q[0] *= ratios[k - 1][0];
q[1] *= ratios[k - 1][1];
}
let factor = if k == 0 { 1.0 } else { 2.0 };
let low = q[0].min(q[1]) * (1.0 - tail_estimate) * factor;
let high = q[0].max(q[1]) * factor;
if !low.is_finite() || !high.is_finite() || low < 0.0 || high < low {
return Err(ChebyshevError::Convergence);
}
let tolerance = high * 2.0_f64.powi(-25) + f64::from(f32::from_bits(1)) / 8.0;
largest_gap = largest_gap.max((high - low) / tolerance);
let middle = (low + high) * 0.5;
result[k] = (if k % 2 == 0 { middle } else { -middle }) as f32;
}
if largest_gap <= 1.0 {
return Ok(result);
}
if largest_gap >= previous_gap {
return Err(ChebyshevError::Convergence);
}
previous_gap = largest_gap;
depth = depth.checked_mul(2).ok_or(ChebyshevError::WorkLimit)?;
work.charge(1)?;
tail = log_tail(tau, depth);
}
}