use alloc::vec;
use alloc::vec::Vec;
use slatec_core::to_fortran_integer;
use slatec_sys::FortranInteger;
use crate::runtime::lock_native;
#[derive(Clone, Debug, PartialEq)]
pub enum TabulatedDataError {
TooFewSamples,
LengthMismatch {
abscissas: usize,
values: usize,
},
NonFiniteAbscissa {
index: usize,
},
NonFiniteValue {
index: usize,
},
AbscissasNotStrictlyIncreasing {
index: usize,
},
NonFiniteQuery,
IntegerOverflow,
AllocationFailed,
NativeContractViolation {
routine: &'static str,
status: FortranInteger,
},
}
impl core::fmt::Display for TabulatedDataError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::TooFewSamples => write!(formatter, "at least two tabulated samples are required"),
Self::LengthMismatch { abscissas, values } => write!(
formatter,
"tabulated abscissa count {abscissas} does not match value count {values}"
),
Self::NonFiniteAbscissa { index } => {
write!(
formatter,
"tabulated abscissa at index {index} is not finite"
)
}
Self::NonFiniteValue { index } => {
write!(formatter, "tabulated value at index {index} is not finite")
}
Self::AbscissasNotStrictlyIncreasing { index } => write!(
formatter,
"tabulated abscissa at index {index} is not strictly increasing"
),
Self::NonFiniteQuery => write!(formatter, "polynomial query is not finite"),
Self::IntegerOverflow => {
write!(formatter, "tabulated-data count exceeds Fortran INTEGER")
}
Self::AllocationFailed => write!(
formatter,
"tabulated-data private workspace allocation failed"
),
Self::NativeContractViolation { routine, status } => write!(
formatter,
"{routine} returned unexpected native status {status}"
),
}
}
}
impl std::error::Error for TabulatedDataError {}
#[derive(Clone, Debug, PartialEq)]
pub struct TabulatedData<T> {
abscissas: Vec<T>,
values: Vec<T>,
}
impl<T> TabulatedData<T> {
#[must_use]
pub fn abscissas(&self) -> &[T] {
&self.abscissas
}
#[must_use]
pub fn values(&self) -> &[T] {
&self.values
}
#[must_use]
pub fn len(&self) -> usize {
self.abscissas.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
false
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PolynomialEvaluation<T> {
pub value: T,
pub derivatives: Vec<T>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct InterpolatingPolynomial<T> {
abscissas: Vec<T>,
coefficients: Vec<T>,
}
impl<T> InterpolatingPolynomial<T> {
#[must_use]
pub fn degree(&self) -> usize {
self.coefficients.len() - 1
}
#[must_use]
pub fn sample_count(&self) -> usize {
self.coefficients.len()
}
}
fn native_integer(value: usize) -> Result<FortranInteger, TabulatedDataError> {
to_fortran_integer(value).map_err(|_| TabulatedDataError::IntegerOverflow)
}
fn zeroed<T: Default>(length: usize) -> Result<Vec<T>, TabulatedDataError> {
let mut values = Vec::new();
values
.try_reserve_exact(length)
.map_err(|_| TabulatedDataError::AllocationFailed)?;
values.resize_with(length, T::default);
Ok(values)
}
fn workspace_len(length: usize) -> Result<usize, TabulatedDataError> {
length
.checked_mul(2)
.ok_or(TabulatedDataError::IntegerOverflow)
}
macro_rules! impl_tabulated_precision {
($scalar:ty, $polint:path, $polyvl:path, $polcof:path) => {
impl TabulatedData<$scalar> {
pub fn from_samples(
abscissas: Vec<$scalar>,
values: Vec<$scalar>,
) -> Result<Self, TabulatedDataError> {
if abscissas.len() != values.len() {
return Err(TabulatedDataError::LengthMismatch {
abscissas: abscissas.len(),
values: values.len(),
});
}
if abscissas.len() < 2 {
return Err(TabulatedDataError::TooFewSamples);
}
for (index, value) in abscissas.iter().enumerate() {
if !value.is_finite() {
return Err(TabulatedDataError::NonFiniteAbscissa { index });
}
if index > 0 && *value <= abscissas[index - 1] {
return Err(TabulatedDataError::AbscissasNotStrictlyIncreasing { index });
}
}
if let Some((index, _)) = values
.iter()
.enumerate()
.find(|(_, value)| !value.is_finite())
{
return Err(TabulatedDataError::NonFiniteValue { index });
}
native_integer(abscissas.len())?;
Ok(Self { abscissas, values })
}
pub fn interpolating_polynomial(
&self,
) -> Result<InterpolatingPolynomial<$scalar>, TabulatedDataError> {
let mut coefficients = self.values.clone();
let mut abscissas = self.abscissas.clone();
let mut count = native_integer(self.len())?;
let _native = lock_native();
unsafe {
$polint(
&mut count,
abscissas.as_mut_ptr(),
self.values.as_ptr().cast_mut(),
coefficients.as_mut_ptr(),
);
}
Ok(InterpolatingPolynomial {
abscissas,
coefficients,
})
}
}
impl InterpolatingPolynomial<$scalar> {
pub fn evaluate(&self, point: $scalar) -> Result<$scalar, TabulatedDataError> {
Ok(self.evaluate_with_derivatives(point, 0)?.value)
}
pub fn evaluate_with_derivatives(
&self,
point: $scalar,
derivative_count: usize,
) -> Result<PolynomialEvaluation<$scalar>, TabulatedDataError> {
if !point.is_finite() {
return Err(TabulatedDataError::NonFiniteQuery);
}
let mut derivative_count_native = native_integer(derivative_count)?;
let mut point = point;
let mut value = <$scalar>::default();
let mut derivatives = zeroed(derivative_count)?;
let mut derivative_placeholder = <$scalar>::default();
let mut workspace = if derivative_count == 0 {
vec![<$scalar>::default()]
} else {
zeroed(workspace_len(self.sample_count())?)?
};
let mut count = native_integer(self.sample_count())?;
let mut status = 0;
let derivative_pointer = if derivative_count == 0 {
&mut derivative_placeholder
} else {
derivatives.as_mut_ptr()
};
let _native = lock_native();
unsafe {
$polyvl(
&mut derivative_count_native,
&mut point,
&mut value,
derivative_pointer,
&mut count,
self.abscissas.as_ptr().cast_mut(),
self.coefficients.as_ptr().cast_mut(),
workspace.as_mut_ptr(),
&mut status,
);
}
if status != 1 {
return Err(TabulatedDataError::NativeContractViolation {
routine: stringify!($polyvl),
status,
});
}
Ok(PolynomialEvaluation { value, derivatives })
}
pub fn taylor_coefficients_at(
&self,
centre: $scalar,
) -> Result<Vec<$scalar>, TabulatedDataError> {
if !centre.is_finite() {
return Err(TabulatedDataError::NonFiniteQuery);
}
let mut centre = centre;
let mut count = native_integer(self.sample_count())?;
let mut coefficients = zeroed(self.sample_count())?;
let mut workspace = zeroed(workspace_len(self.sample_count())?)?;
let _native = lock_native();
unsafe {
$polcof(
&mut centre,
&mut count,
self.abscissas.as_ptr().cast_mut(),
self.coefficients.as_ptr().cast_mut(),
coefficients.as_mut_ptr(),
workspace.as_mut_ptr(),
);
}
Ok(coefficients)
}
}
};
}
impl_tabulated_precision!(
f32,
slatec_sys::interpolation::polint,
slatec_sys::interpolation::polyvl,
slatec_sys::interpolation::polcof
);
impl_tabulated_precision!(
f64,
slatec_sys::interpolation::dplint,
slatec_sys::interpolation::dpolvl,
slatec_sys::interpolation::dpolcf
);
#[cfg(test)]
mod tests {
use alloc::vec;
use super::{TabulatedData, TabulatedDataError};
#[test]
fn rejects_mismatched_non_finite_and_unordered_samples() {
assert!(matches!(
TabulatedData::<f64>::from_samples(vec![0.0], vec![1.0]),
Err(TabulatedDataError::TooFewSamples)
));
assert!(matches!(
TabulatedData::<f64>::from_samples(vec![0.0, 1.0], vec![1.0]),
Err(TabulatedDataError::LengthMismatch { .. })
));
assert!(matches!(
TabulatedData::<f64>::from_samples(vec![0.0, f64::NAN], vec![0.0, 1.0]),
Err(TabulatedDataError::NonFiniteAbscissa { index: 1 })
));
assert!(matches!(
TabulatedData::<f64>::from_samples(vec![0.0, 0.0], vec![0.0, 1.0]),
Err(TabulatedDataError::AbscissasNotStrictlyIncreasing { index: 1 })
));
}
}