use crate::bspline_modules;
pub use crate::bspline_modules::{
condition_number, generate_knots, lu_decomposition, make_auto_bspline, make_interp_bspline,
make_lsq_bspline, make_periodic_bspline, make_smoothing_bspline, matrix_multiply,
matrix_vector_multiply, solve_least_squares, solve_linear_system, solve_multiple_rhs,
solve_with_lu, transpose_matrix, BSpline, BSplineWorkspace, BSplineWorkspaceBuilder,
EvaluationStats, ExtrapolateMode, WorkspaceConfig, WorkspaceMemoryStats, WorkspaceProvider,
};
pub use crate::bspline_modules::prelude::*;
use crate::error::{InterpolateError, InterpolateResult};
use scirs2_core::ndarray::{Array1, ArrayView1, ArrayView2};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::{Debug, Display};
use std::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign};
pub type BSplineF32 = BSpline<f32>;
pub type BSplineF64 = BSpline<f64>;
pub fn linear_bspline<T>(x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<BSpline<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Display
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ scirs2_core::numeric::Zero
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign,
{
make_interp_bspline(x, y, 1, ExtrapolateMode::Extrapolate)
}
pub fn cubic_bspline<T>(x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<BSpline<T>>
where
T: Float
+ FromPrimitive
+ Debug
+ Display
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ scirs2_core::numeric::Zero
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign,
{
make_interp_bspline(x, y, 3, ExtrapolateMode::Extrapolate)
}
pub mod info {
pub use crate::bspline_modules::info::*;
}
impl<T> crate::traits::SplineInterpolator<T> for BSpline<T>
where
T: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
fn derivative(
&self,
query_points: &ArrayView2<T>,
order: usize,
) -> crate::InterpolateResult<Vec<T>> {
if query_points.ncols() != 1 {
return Err(crate::InterpolateError::invalid_input(
"BSpline only supports 1D interpolation",
));
}
let mut results = Vec::with_capacity(query_points.nrows());
for row in query_points.outer_iter() {
let x = row[0];
let deriv = self.derivative(x, order)?;
results.push(deriv);
}
Ok(results)
}
fn integrate(&self, bounds: &[(T, T)]) -> crate::InterpolateResult<Vec<T>> {
let mut results = Vec::with_capacity(bounds.len());
for &(a, b) in bounds {
let integral = self.integrate(a, b)?;
results.push(integral);
}
Ok(results)
}
fn antiderivative(
&self,
) -> crate::InterpolateResult<Box<dyn crate::traits::SplineInterpolator<T>>> {
let antideriv = self.antiderivative(1)?;
Ok(Box::new(antideriv))
}
fn find_roots(&self, bounds: &[(T, T)], tolerance: T) -> crate::InterpolateResult<Vec<T>> {
use crate::utils::find_multiple_roots;
let mut all_roots = Vec::new();
for &(a, b) in bounds {
if a >= b {
continue;
}
let eval_fn = |x: T| -> crate::InterpolateResult<T> { self.evaluate(x) };
match find_multiple_roots(a, b, tolerance, 10, eval_fn) {
Ok(mut roots) => all_roots.append(&mut roots),
Err(_) => continue,
}
}
all_roots.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
all_roots.dedup_by(|a, b| (*a - *b).abs() < tolerance);
Ok(all_roots)
}
fn find_extrema(
&self,
bounds: &[(T, T)],
tolerance: T,
) -> crate::InterpolateResult<Vec<(T, T, crate::traits::ExtremaType)>> {
use crate::utils::find_multiple_roots;
let mut extrema = Vec::new();
for &(a, b) in bounds {
if a >= b {
continue;
}
let deriv_fn = |x: T| -> crate::InterpolateResult<T> { self.derivative(x, 1) };
let critical_points = match find_multiple_roots(a, b, tolerance, 20, deriv_fn) {
Ok(points) => points,
Err(_) => continue,
};
for cp in critical_points {
if cp < a || cp > b {
continue;
}
let second_deriv = match self.derivative(cp, 2) {
Ok(d2) => d2,
Err(_) => continue,
};
let f_value = match self.evaluate(cp) {
Ok(val) => val,
Err(_) => continue,
};
let extrema_type = if second_deriv > T::zero() {
crate::traits::ExtremaType::Minimum
} else if second_deriv < T::zero() {
crate::traits::ExtremaType::Maximum
} else {
crate::traits::ExtremaType::InflectionPoint
};
extrema.push((cp, f_value, extrema_type));
}
}
extrema.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
Ok(extrema)
}
}
impl<T> crate::traits::Interpolator<T> for BSpline<T>
where
T: Float
+ FromPrimitive
+ Debug
+ Display
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ Send
+ Sync
+ 'static
+ crate::traits::InterpolationFloat,
{
fn evaluate(&self, query_points: &ArrayView2<T>) -> crate::InterpolateResult<Vec<T>> {
if query_points.ncols() != 1 {
return Err(crate::InterpolateError::invalid_input(
"BSpline only supports 1D interpolation",
));
}
let mut results = Vec::with_capacity(query_points.nrows());
for row in query_points.outer_iter() {
let x = row[0];
let value = self.evaluate(x)?;
results.push(value);
}
Ok(results)
}
fn dimension(&self) -> usize {
1
}
fn len(&self) -> usize {
self.coefficients().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::Interpolator;
use scirs2_core::ndarray::array;
#[test]
fn test_linear_bspline() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let spline = linear_bspline(&x.view(), &y.view()).expect("Operation failed");
let value = spline.evaluate(2.5);
assert!(value.is_ok());
assert_eq!(spline.degree(), 1);
}
#[test]
fn test_cubic_bspline() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 8.0, 27.0, 64.0];
let spline = cubic_bspline(&x.view(), &y.view()).expect("Operation failed");
let value = spline.evaluate(2.5);
assert!(value.is_ok());
assert_eq!(spline.degree(), 3);
}
#[test]
fn test_modular_api_integration() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let spline = make_interp_bspline(
&x.view(),
&y.view(),
3, ExtrapolateMode::Extrapolate,
);
assert!(spline.is_ok());
let spline = spline.expect("Operation failed");
let test_point = 2.5;
let value = spline.evaluate(test_point);
assert!(value.is_ok());
let deriv = spline.derivative(test_point, 1);
assert!(deriv.is_ok());
let integral = spline.integrate(0.0, 4.0);
assert!(integral.is_ok());
}
#[test]
fn test_workspace_optimization() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![1.0, 2.0, 3.0, 2.0, 1.0];
let spline = make_interp_bspline(&x.view(), &y.view(), 2, ExtrapolateMode::Extrapolate)
.expect("Operation failed");
let workspace = BSplineWorkspace::new();
let value1 = spline.evaluate_with_workspace(1.5, &workspace);
assert!(value1.is_ok());
let value2 = spline.evaluate_with_workspace(2.5, &workspace);
assert!(value2.is_ok());
let stats = workspace.get_statistics();
assert!(stats.evaluation_count >= 2);
}
#[test]
fn test_advanced_evaluation() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let y = array![0.0, 1.0, 8.0, 27.0, 64.0, 125.0];
let spline = make_interp_bspline(&x.view(), &y.view(), 3, ExtrapolateMode::Extrapolate)
.expect("Operation failed");
let fast_value = spline.evaluate_fast_recursive(2.5);
assert!(fast_value.is_ok());
let test_points = array![1.5, 2.5, 3.5];
let batch_values = spline.evaluate_array(&test_points.view());
assert!(batch_values.is_ok());
assert_eq!(batch_values.expect("Operation failed").len(), 3);
}
#[test]
fn test_factory_functions() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let knots = generate_knots(&x.view(), 3, "clamped");
assert!(knots.is_ok());
let knots = knots.expect("Operation failed");
let lsq_spline = make_lsq_bspline(
&x.view(),
&y.view(),
&knots.view(),
3,
None,
ExtrapolateMode::Extrapolate,
);
assert!(lsq_spline.is_ok());
let auto_spline =
make_auto_bspline(&x.view(), &y.view(), 3, 0.1, ExtrapolateMode::Extrapolate);
assert!(auto_spline.is_ok());
}
#[test]
fn test_extrapolation_modes() {
let x = array![1.0, 2.0, 3.0, 4.0];
let y = array![1.0, 4.0, 9.0, 16.0];
let spline_extrapolate =
make_interp_bspline(&x.view(), &y.view(), 2, ExtrapolateMode::Extrapolate)
.expect("Operation failed");
let val_out = spline_extrapolate.evaluate(5.0); assert!(val_out.is_ok());
let spline_nan = make_interp_bspline(&x.view(), &y.view(), 2, ExtrapolateMode::Nan)
.expect("Operation failed");
let val_nan = spline_nan.evaluate(5.0);
assert!(val_nan.is_ok());
assert!(val_nan.expect("Operation failed").is_nan());
let spline_error = make_interp_bspline(&x.view(), &y.view(), 2, ExtrapolateMode::Error)
.expect("Operation failed");
let val_error = spline_error.evaluate(5.0);
assert!(val_error.is_err());
}
#[test]
fn test_trait_implementations() {
let x = array![0.0, 1.0, 2.0, 3.0];
let y = array![0.0, 1.0, 4.0, 9.0];
let spline = make_interp_bspline(&x.view(), &y.view(), 2, ExtrapolateMode::Extrapolate)
.expect("Operation failed");
let query_points = array![1.5, 2.5];
let values = spline
.evaluate_array(&query_points.view())
.expect("Operation failed");
assert_eq!(values.len(), 2);
assert_eq!(spline.dimension(), 1);
assert_eq!(spline.len(), 4);
}
}