use crate::error::{InterpolateError, InterpolateResult};
use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayView1, ArrayView2, ScalarOperand};
use scirs2_core::numeric::Float;
use std::fmt::Debug;
type BasisFunctionVec<T> = Vec<Box<dyn Fn(T) -> T>>;
#[derive(Debug, Clone)]
pub struct FDAConfig {
pub n_basis: usize,
pub basis_type: BasisType,
pub lambda: f64,
pub penalty_order: usize,
}
impl Default for FDAConfig {
fn default() -> Self {
Self {
n_basis: 20,
basis_type: BasisType::BSpline,
lambda: 1e-6,
penalty_order: 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BasisType {
BSpline,
Fourier,
Wavelet,
Polynomial,
PCA,
}
pub struct FunctionalDataInterpolator<T: Float + ScalarOperand> {
config: FDAConfig,
basis_coefficients: Option<Array2<T>>,
domain: (T, T),
basis_functions: Vec<Box<dyn Fn(T) -> T>>,
fitted: bool,
}
impl<T: crate::traits::InterpolationFloat + ScalarOperand> FunctionalDataInterpolator<T> {
pub fn new(config: FDAConfig) -> Self {
Self {
config,
basis_coefficients: None,
domain: (T::zero(), T::one()),
basis_functions: Vec::new(),
fitted: false,
}
}
pub fn fit(
&mut self,
x: &ArrayView1<T>,
y: &ArrayView2<T>, ) -> InterpolateResult<()> {
let n_points = x.len();
let n_curves = y.ncols();
if y.nrows() != n_points {
return Err(InterpolateError::invalid_input(format!(
"y must have {} rows to match x length, got {}",
n_points,
y.nrows()
)));
}
self.domain = (
x.iter()
.fold(T::infinity(), |a, &b| if a < b { a } else { b }),
x.iter()
.fold(T::neg_infinity(), |a, &b| if a > b { a } else { b }),
);
self.generate_basis_functions()?;
let mut basis_matrix = Array2::zeros((n_points, self.config.n_basis));
for (i, &x_val) in x.iter().enumerate() {
for (j, basis_fn) in self.basis_functions.iter().enumerate() {
basis_matrix[[i, j]] = basis_fn(x_val);
}
}
let penalty_matrix = self.compute_penalty_matrix()?;
let mut coefficients = Array2::zeros((self.config.n_basis, n_curves));
for curve_idx in 0..n_curves {
let y_curve = y.column(curve_idx);
let coeff = self.solve_penalized_ls(&basis_matrix, &penalty_matrix, &y_curve)?;
coefficients.column_mut(curve_idx).assign(&coeff);
}
self.basis_coefficients = Some(coefficients);
self.fitted = true;
Ok(())
}
pub fn predict(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array2<T>> {
if !self.fitted {
return Err(InterpolateError::ComputationError(
"Model not fitted".to_string(),
));
}
let coeffs = self.basis_coefficients.as_ref().expect("Operation failed");
let n_new = xnew.len();
let n_curves = coeffs.ncols();
let mut predictions = Array2::zeros((n_new, n_curves));
for (i, &x_val) in xnew.iter().enumerate() {
for curve_idx in 0..n_curves {
let mut y_val = T::zero();
for (j, basis_fn) in self.basis_functions.iter().enumerate() {
y_val += coeffs[[j, curve_idx]] * basis_fn(x_val);
}
predictions[[i, curve_idx]] = y_val;
}
}
Ok(predictions)
}
fn generate_basis_functions(&mut self) -> InterpolateResult<()> {
self.basis_functions.clear();
match self.config.basis_type {
BasisType::BSpline => self.generate_bspline_basis()?,
BasisType::Fourier => self.generate_fourier_basis()?,
BasisType::Polynomial => self.generate_polynomial_basis()?,
_ => {
return Err(InterpolateError::ComputationError(
"Basis type not implemented".to_string(),
))
}
}
Ok(())
}
fn generate_bspline_basis(&mut self) -> InterpolateResult<()> {
let domain_width = self.domain.1 - self.domain.0;
let knot_spacing =
domain_width / T::from_usize(self.config.n_basis + 1).expect("Operation failed");
for i in 0..self.config.n_basis {
let knot_center =
self.domain.0 + T::from_usize(i + 1).expect("Operation failed") * knot_spacing;
let knot_width = knot_spacing * T::from_f64(2.0).expect("Operation failed");
self.basis_functions.push(Box::new(move |x: T| -> T {
let distance = (x - knot_center).abs();
if distance <= knot_width {
let u = distance / knot_width;
if u <= T::from_f64(0.5).expect("Operation failed") {
T::from_f64(2.0 / 3.0).expect("Operation failed") - u * u
+ u * u * u / T::from_f64(2.0).expect("Operation failed")
} else {
let one_minus_u = T::one() - u;
one_minus_u * one_minus_u * one_minus_u
/ T::from_f64(6.0).expect("Operation failed")
}
} else {
T::zero()
}
}));
}
Ok(())
}
fn generate_fourier_basis(&mut self) -> InterpolateResult<()> {
let pi = T::from_f64(std::f64::consts::PI).expect("Operation failed");
let domain_width = self.domain.1 - self.domain.0;
self.basis_functions
.push(Box::new(|_: T| -> T { T::one() }));
for k in 1..=(self.config.n_basis / 2) {
let freq = T::from_usize(k).expect("Operation failed")
* T::from_f64(2.0).expect("Operation failed")
* pi
/ domain_width;
let domain_start = self.domain.0;
self.basis_functions.push(Box::new(move |x: T| -> T {
(freq * (x - domain_start)).cos()
}));
if self.basis_functions.len() < self.config.n_basis {
self.basis_functions.push(Box::new(move |x: T| -> T {
(freq * (x - domain_start)).sin()
}));
}
}
Ok(())
}
fn generate_polynomial_basis(&mut self) -> InterpolateResult<()> {
for i in 0..self.config.n_basis {
let power = i;
self.basis_functions
.push(Box::new(move |x: T| -> T { x.powi(power as i32) }));
}
Ok(())
}
fn compute_penalty_matrix(&self) -> InterpolateResult<Array2<T>> {
let n_basis = self.config.n_basis;
let mut penalty = Array2::zeros((n_basis, n_basis));
for i in 0..(n_basis - 1) {
penalty[[i, i]] = T::one();
penalty[[i, i + 1]] = -T::one();
if i > 0 {
penalty[[i, i - 1]] = -T::one();
penalty[[i, i]] = T::from_f64(2.0).expect("Operation failed");
}
}
Ok(penalty)
}
fn solve_penalized_ls(
&self,
basis_matrix: &Array2<T>,
penalty_matrix: &Array2<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
let lambda = T::from_f64(self.config.lambda).expect("Operation failed");
let btb = basis_matrix.t().dot(basis_matrix);
let bty = basis_matrix.t().dot(y);
let mut lhs = btb + &(penalty_matrix * lambda);
let reg = T::from_f64(1e-12).expect("Operation failed");
for i in 0..lhs.nrows() {
lhs[[i, i]] += reg;
}
self.solve_linear_system(&lhs, &bty)
}
fn solve_linear_system(&self, a: &Array2<T>, b: &Array1<T>) -> InterpolateResult<Array1<T>> {
let n = a.nrows();
let mut aug = Array2::zeros((n, n + 1));
for i in 0..n {
for j in 0..n {
aug[[i, j]] = a[[i, j]];
}
aug[[i, n]] = b[i];
}
for k in 0..n {
let mut max_row = k;
for i in (k + 1)..n {
if aug[[i, k]].abs() > aug[[max_row, k]].abs() {
max_row = i;
}
}
if max_row != k {
for j in 0..=n {
let temp = aug[[k, j]];
aug[[k, j]] = aug[[max_row, j]];
aug[[max_row, j]] = temp;
}
}
if aug[[k, k]].abs() < T::from_f64(1e-12).expect("Operation failed") {
return Err(InterpolateError::ComputationError(
"Singular matrix".to_string(),
));
}
for i in (k + 1)..n {
let factor = aug[[i, k]] / aug[[k, k]];
for j in k..=n {
let temp = aug[[k, j]];
aug[[i, j]] -= factor * temp;
}
}
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
x[i] = aug[[i, n]];
for j in (i + 1)..n {
let temp = x[j];
x[i] -= aug[[i, j]] * temp;
}
x[i] /= aug[[i, i]];
}
Ok(x)
}
}
pub struct MultiOutputInterpolator<T: Float + ScalarOperand> {
input_dim: usize,
output_dim: usize,
parameters: Option<Array3<T>>, basis_functions: Vec<BasisFunctionVec<T>>,
correlation_matrix: Option<Array2<T>>,
fitted: bool,
}
impl<T: crate::traits::InterpolationFloat + ScalarOperand + 'static> MultiOutputInterpolator<T> {
pub fn new(input_dim: usize, output_dim: usize, _n_basis_perdim: usize) -> Self {
Self {
input_dim,
output_dim,
parameters: None,
basis_functions: (0..input_dim).map(|_| Vec::new()).collect(),
correlation_matrix: None,
fitted: false,
}
}
pub fn fit(
&mut self,
x: &ArrayView2<T>, y: &ArrayView2<T>, ) -> InterpolateResult<()> {
let n_samples = x.nrows();
let input_dim = x.ncols();
let output_dim = y.ncols();
if input_dim != self.input_dim || output_dim != self.output_dim {
return Err(InterpolateError::invalid_input(
"Dimension mismatch".to_string(),
));
}
for dim in 0..input_dim {
self.generate_basis_for_dimension(dim, &x.column(dim))?;
}
let n_features = self.basis_functions.iter().map(|bf| bf.len()).product();
let mut feature_matrix = Array2::zeros((n_samples, n_features));
for (sample_idx, x_row) in x.outer_iter().enumerate() {
let features = self.compute_tensor_product_features(&x_row)?;
feature_matrix.row_mut(sample_idx).assign(&features);
}
let mut parameters = Array3::zeros((output_dim, n_features, 1));
for output_idx in 0..output_dim {
let y_output = y.column(output_idx);
let params = self.fit_ridge_regression(&feature_matrix, &y_output.to_owned())?;
for (param_idx, ¶m) in params.iter().enumerate() {
parameters[[output_idx, param_idx, 0]] = param;
}
}
let residuals = self.compute_residuals(&feature_matrix, y, ¶meters)?;
self.correlation_matrix = Some(self.estimate_output_correlations(&residuals)?);
self.parameters = Some(parameters);
self.fitted = true;
Ok(())
}
pub fn predict(&self, xnew: &ArrayView2<T>) -> InterpolateResult<Array2<T>> {
if !self.fitted {
return Err(InterpolateError::ComputationError(
"Model not fitted".to_string(),
));
}
let parameters = self.parameters.as_ref().expect("Operation failed");
let n_new = xnew.nrows();
let mut predictions = Array2::zeros((n_new, self.output_dim));
for (sample_idx, x_row) in xnew.outer_iter().enumerate() {
let features = self.compute_tensor_product_features(&x_row)?;
for output_idx in 0..self.output_dim {
let mut pred = T::zero();
for (feat_idx, &feat_val) in features.iter().enumerate() {
pred += feat_val * parameters[[output_idx, feat_idx, 0]];
}
predictions[[sample_idx, output_idx]] = pred;
}
}
Ok(predictions)
}
fn generate_basis_for_dimension(
&mut self,
dim: usize,
x_dim: &ArrayView1<T>,
) -> InterpolateResult<()> {
let min_val = x_dim
.iter()
.fold(T::infinity(), |a, &b| if a < b { a } else { b });
let max_val = x_dim
.iter()
.fold(T::neg_infinity(), |a, &b| if a > b { a } else { b });
let range = max_val - min_val;
for degree in 0..5 {
self.basis_functions[dim].push(Box::new(move |x: T| -> T {
let normalized = (x - min_val) / range;
normalized.powi(degree)
}));
}
for i in 0..3 {
let center = min_val + range * T::from_f64(i as f64 / 2.0).expect("Operation failed");
let width = range / T::from_f64(3.0).expect("Operation failed");
self.basis_functions[dim].push(Box::new(move |x: T| -> T {
let diff = (x - center) / width;
(-diff * diff).exp()
}));
}
Ok(())
}
fn compute_tensor_product_features(&self, x: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
let mut all_basis_values = Vec::new();
for (dim, x_val) in x.iter().enumerate() {
let mut dim_values = Vec::new();
for basis_fn in &self.basis_functions[dim] {
dim_values.push(basis_fn(*x_val));
}
all_basis_values.push(dim_values);
}
let total_features: usize = all_basis_values.iter().map(|bv| bv.len()).product();
let mut features = Array1::zeros(total_features);
let mut feature_idx = 0;
self.compute_tensor_product_recursive(
&all_basis_values,
&mut features,
&mut feature_idx,
0,
T::one(),
);
Ok(features)
}
#[allow(clippy::only_used_in_recursion)]
fn compute_tensor_product_recursive(
&self,
all_basis_values: &[Vec<T>],
features: &mut Array1<T>,
feature_idx: &mut usize,
dim: usize,
current_product: T,
) {
if dim == all_basis_values.len() {
features[*feature_idx] = current_product;
*feature_idx += 1;
return;
}
for &basis_val in &all_basis_values[dim] {
self.compute_tensor_product_recursive(
all_basis_values,
features,
feature_idx,
dim + 1,
current_product * basis_val,
);
}
}
fn fit_ridge_regression(&self, x: &Array2<T>, y: &Array1<T>) -> InterpolateResult<Array1<T>> {
let lambda = T::from_f64(1e-6).expect("Operation failed");
let xtx = x.t().dot(x);
let xty = x.t().dot(y);
let mut lhs = xtx;
for i in 0..lhs.nrows() {
lhs[[i, i]] += lambda;
}
self.solve_linear_system_simple(&lhs, &xty)
}
fn solve_linear_system_simple(
&self,
a: &Array2<T>,
b: &Array1<T>,
) -> InterpolateResult<Array1<T>> {
let n = a.nrows();
let mut l = Array2::zeros((n, n));
for i in 0..n {
for j in 0..=i {
if i == j {
let mut sum = T::zero();
for k in 0..j {
sum += l[[j, k]] * l[[j, k]];
}
l[[i, j]] = (a[[i, i]] - sum).sqrt();
} else {
let mut sum = T::zero();
for k in 0..j {
sum += l[[i, k]] * l[[j, k]];
}
l[[i, j]] = (a[[i, j]] - sum) / l[[j, j]];
}
}
}
let mut y = Array1::zeros(n);
for i in 0..n {
y[i] = b[i];
for j in 0..i {
let temp = y[j];
y[i] -= l[[i, j]] * temp;
}
y[i] /= l[[i, i]];
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
x[i] = y[i];
for j in (i + 1)..n {
let temp = x[j];
x[i] -= l[[j, i]] * temp;
}
x[i] /= l[[i, i]];
}
Ok(x)
}
fn compute_residuals(
&self,
x: &Array2<T>,
y: &ArrayView2<T>,
parameters: &Array3<T>,
) -> InterpolateResult<Array2<T>> {
let n_samples = x.nrows();
let mut residuals = Array2::zeros((n_samples, self.output_dim));
for sample_idx in 0..n_samples {
let features = x.row(sample_idx);
for output_idx in 0..self.output_dim {
let mut pred = T::zero();
for (feat_idx, &feat_val) in features.iter().enumerate() {
pred += feat_val * parameters[[output_idx, feat_idx, 0]];
}
residuals[[sample_idx, output_idx]] = y[[sample_idx, output_idx]] - pred;
}
}
Ok(residuals)
}
fn estimate_output_correlations(&self, residuals: &Array2<T>) -> InterpolateResult<Array2<T>> {
let n_samples = residuals.nrows();
let output_dim = residuals.ncols();
let mut corr_matrix = Array2::eye(output_dim);
for i in 0..output_dim {
for j in (i + 1)..output_dim {
let mut numerator = T::zero();
let mut sum_i_sq = T::zero();
let mut sum_j_sq = T::zero();
for sample_idx in 0..n_samples {
let res_i = residuals[[sample_idx, i]];
let res_j = residuals[[sample_idx, j]];
numerator += res_i * res_j;
sum_i_sq += res_i * res_i;
sum_j_sq += res_j * res_j;
}
let denominator = (sum_i_sq * sum_j_sq).sqrt();
if denominator > T::zero() {
let correlation = numerator / denominator;
corr_matrix[[i, j]] = correlation;
corr_matrix[[j, i]] = correlation;
}
}
}
Ok(corr_matrix)
}
}
pub struct PiecewisePolynomialInterpolator<T: Float + ScalarOperand> {
max_degree: usize,
min_points_per_segment: usize,
breakpoint_penalty: T,
breakpoints: Option<Array1<T>>,
segment_coefficients: Option<Vec<Array1<T>>>,
fitted: bool,
}
impl<T: crate::traits::InterpolationFloat + ScalarOperand + 'static>
PiecewisePolynomialInterpolator<T>
{
pub fn new(max_degree: usize, min_points_per_segment: usize, breakpointpenalty: T) -> Self {
Self {
max_degree,
min_points_per_segment,
breakpoint_penalty: breakpointpenalty,
breakpoints: None,
segment_coefficients: None,
fitted: false,
}
}
pub fn fit(&mut self, x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<()> {
let n = x.len();
if n < 2 * self.min_points_per_segment {
return Err(InterpolateError::invalid_input(
"Not enough data points".to_string(),
));
}
let breakpoints = self.detect_breakpoints(x, y)?;
let mut coefficients = Vec::new();
let num_segments = if breakpoints.is_empty() {
1
} else {
breakpoints.len() + 1
};
for i in 0..num_segments {
let start_idx = if i == 0 || breakpoints.is_empty() {
0
} else {
self.find_closest_index(x, breakpoints[i - 1])?
};
let end_idx = if i == num_segments - 1 {
n
} else if breakpoints.is_empty() {
n
} else {
self.find_closest_index(x, breakpoints[i])?
};
let x_segment = x.slice(s![start_idx..end_idx]);
let y_segment = y.slice(s![start_idx..end_idx]);
let segment_coeffs = self.fit_polynomial_segment(&x_segment, &y_segment)?;
coefficients.push(segment_coeffs);
}
self.breakpoints = Some(breakpoints);
self.segment_coefficients = Some(coefficients);
self.fitted = true;
Ok(())
}
pub fn predict(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
if !self.fitted {
return Err(InterpolateError::ComputationError(
"Model not fitted".to_string(),
));
}
let breakpoints = self.breakpoints.as_ref().expect("Operation failed");
let coefficients = self
.segment_coefficients
.as_ref()
.expect("Operation failed");
let mut predictions = Array1::zeros(xnew.len());
for (i, &x_val) in xnew.iter().enumerate() {
let segment_idx = self.find_segment(x_val, breakpoints);
let segment_coeffs = &coefficients[segment_idx];
let mut pred = T::zero();
for (degree, &coeff) in segment_coeffs.iter().enumerate() {
pred += coeff * x_val.powi(degree as i32);
}
predictions[i] = pred;
}
Ok(predictions)
}
fn detect_breakpoints(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
let n = x.len();
let max_breakpoints = n / self.min_points_per_segment - 1;
let mut best_breakpoints = Vec::new();
let mut best_score = T::infinity();
for n_breakpoints in 0..=max_breakpoints.min(5) {
if n_breakpoints == 0 {
let score = self.evaluate_single_polynomial_score(x, y)?;
if score < best_score {
best_score = score;
best_breakpoints.clear();
}
} else {
let candidates = self.generate_breakpoint_candidates(x, n_breakpoints)?;
for candidate_set in candidates {
let score = self.evaluate_breakpoint_set_score(x, y, &candidate_set)?;
if score < best_score {
best_score = score;
best_breakpoints = candidate_set;
}
}
}
}
Ok(Array1::from_vec(best_breakpoints))
}
fn evaluate_single_polynomial_score(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<T> {
let coeffs = self.fit_polynomial_segment(x, y)?;
let mse = self.compute_mse(x, y, &coeffs)?;
let penalty = T::from_usize(coeffs.len()).expect("Operation failed")
* T::from_f64(2.0).expect("Operation failed");
Ok(mse + penalty)
}
fn evaluate_breakpoint_set_score(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
breakpoints: &[T],
) -> InterpolateResult<T> {
let n = x.len();
let mut total_mse = T::zero();
let mut total_params = 0;
for i in 0..=breakpoints.len() {
let start_idx = if i == 0 {
0
} else {
self.find_closest_index(x, breakpoints[i - 1])?
};
let end_idx = if i == breakpoints.len() {
n
} else {
self.find_closest_index(x, breakpoints[i])?
};
if end_idx - start_idx < self.min_points_per_segment {
return Ok(T::infinity()); }
let x_segment = x.slice(s![start_idx..end_idx]);
let y_segment = y.slice(s![start_idx..end_idx]);
let coeffs = self.fit_polynomial_segment(&x_segment, &y_segment)?;
let mse = self.compute_mse(&x_segment, &y_segment, &coeffs)?;
total_mse += mse * T::from_usize(end_idx - start_idx).expect("Operation failed");
total_params += coeffs.len();
}
total_mse /= T::from_usize(n).expect("Operation failed");
let param_penalty = T::from_usize(total_params).expect("Operation failed")
* T::from_f64(2.0).expect("Operation failed");
let breakpoint_penalty_total =
T::from_usize(breakpoints.len()).expect("Operation failed") * self.breakpoint_penalty;
Ok(total_mse + param_penalty + breakpoint_penalty_total)
}
fn generate_breakpoint_candidates(
&self,
x: &ArrayView1<T>,
n_breakpoints: usize,
) -> InterpolateResult<Vec<Vec<T>>> {
let n = x.len();
let mut candidates = Vec::new();
if n_breakpoints == 1 {
for i in self.min_points_per_segment..(n - self.min_points_per_segment) {
candidates.push(vec![x[i]]);
}
} else {
for i in self.min_points_per_segment..(n - self.min_points_per_segment) {
candidates.push(vec![x[i]]);
}
}
Ok(candidates)
}
fn fit_polynomial_segment(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
let n = x.len();
let degree = self.max_degree.min(n - 1);
let mut vandermonde = Array2::zeros((n, degree + 1));
for (i, &x_val) in x.iter().enumerate() {
for j in 0..=degree {
vandermonde[[i, j]] = x_val.powi(j as i32);
}
}
let vt_v = vandermonde.t().dot(&vandermonde);
let vt_y = vandermonde.t().dot(y);
let mut lhs = vt_v;
let reg = T::from_f64(1e-12).expect("Operation failed");
for i in 0..lhs.nrows() {
lhs[[i, i]] += reg;
}
self.solve_linear_system_poly(&lhs, &vt_y)
}
fn compute_mse(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
coeffs: &Array1<T>,
) -> InterpolateResult<T> {
let mut mse = T::zero();
for (&x_val, &y_val) in x.iter().zip(y.iter()) {
let mut pred = T::zero();
for (degree, &coeff) in coeffs.iter().enumerate() {
pred += coeff * x_val.powi(degree as i32);
}
let residual = y_val - pred;
mse += residual * residual;
}
Ok(mse / T::from_usize(x.len()).expect("Operation failed"))
}
fn find_closest_index(&self, x: &ArrayView1<T>, value: T) -> InterpolateResult<usize> {
let mut best_idx = 0;
let mut best_dist = (x[0] - value).abs();
for (i, &x_val) in x.iter().enumerate() {
let dist = (x_val - value).abs();
if dist < best_dist {
best_dist = dist;
best_idx = i;
}
}
Ok(best_idx)
}
fn find_segment(&self, xval: T, breakpoints: &Array1<T>) -> usize {
if breakpoints.is_empty() {
return 0; }
for (i, &bp) in breakpoints.iter().enumerate() {
if xval <= bp {
return i;
}
}
breakpoints.len()
}
fn solve_linear_system_poly(
&self,
a: &Array2<T>,
b: &Array1<T>,
) -> InterpolateResult<Array1<T>> {
let n = a.nrows();
let mut aug = Array2::zeros((n, n + 1));
for i in 0..n {
for j in 0..n {
aug[[i, j]] = a[[i, j]];
}
aug[[i, n]] = b[i];
}
for k in 0..n {
let mut max_row = k;
for i in (k + 1)..n {
if aug[[i, k]].abs() > aug[[max_row, k]].abs() {
max_row = i;
}
}
if max_row != k {
for j in 0..=n {
let temp = aug[[k, j]];
aug[[k, j]] = aug[[max_row, j]];
aug[[max_row, j]] = temp;
}
}
if aug[[k, k]].abs() < T::from_f64(1e-12).expect("Operation failed") {
return Err(InterpolateError::ComputationError(
"Singular matrix".to_string(),
));
}
for i in (k + 1)..n {
let factor = aug[[i, k]] / aug[[k, k]];
for j in k..=n {
let temp = aug[[k, j]];
aug[[i, j]] -= factor * temp;
}
}
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
x[i] = aug[[i, n]];
for j in (i + 1)..n {
let temp = x[j];
x[i] -= aug[[i, j]] * temp;
}
x[i] /= aug[[i, i]];
}
Ok(x)
}
}
#[allow(dead_code)]
pub fn make_fda_interpolator<T: crate::traits::InterpolationFloat + ScalarOperand>(
config: Option<FDAConfig>,
) -> FunctionalDataInterpolator<T> {
FunctionalDataInterpolator::new(config.unwrap_or_default())
}
#[allow(dead_code)]
pub fn make_multi_output_interpolator<T: crate::traits::InterpolationFloat + ScalarOperand>(
input_dim: usize,
output_dim: usize,
n_basis_per_dim: Option<usize>,
) -> MultiOutputInterpolator<T> {
MultiOutputInterpolator::new(input_dim, output_dim, n_basis_per_dim.unwrap_or(8))
}
#[allow(dead_code)]
pub fn make_piecewise_polynomial_interpolator<
T: crate::traits::InterpolationFloat + ScalarOperand,
>(
max_degree: Option<usize>,
min_points_per_segment: Option<usize>,
breakpoint_penalty: Option<T>,
) -> PiecewisePolynomialInterpolator<T> {
PiecewisePolynomialInterpolator::new(
max_degree.unwrap_or(3),
min_points_per_segment.unwrap_or(5),
breakpoint_penalty.unwrap_or_else(|| T::from_f64(1.0).expect("Operation failed")),
)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array2;
#[test]
fn test_fda_interpolator() {
let mut interpolator = make_fda_interpolator::<f64>(None);
let x = Array1::from_vec(vec![0.0, 0.25, 0.5, 0.75, 1.0]);
let y = Array2::from_shape_vec(
(5, 2),
vec![0.0, 0.0, 0.5, 0.25, 1.0, 1.0, 1.5, 2.25, 2.0, 4.0],
)
.expect("Operation failed");
let result = interpolator.fit(&x.view(), &y.view());
assert!(result.is_ok());
let x_new = Array1::from_vec(vec![0.1, 0.3, 0.7]);
let predictions = interpolator
.predict(&x_new.view())
.expect("Operation failed");
assert_eq!(predictions.nrows(), 3);
assert_eq!(predictions.ncols(), 2);
}
#[test]
fn test_multi_output_interpolator() {
let mut interpolator = make_multi_output_interpolator::<f64>(2, 2, Some(4));
let x = Array2::from_shape_vec((4, 2), vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0])
.expect("Operation failed");
let y = Array2::from_shape_vec((4, 2), vec![0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0])
.expect("Operation failed");
let result = interpolator.fit(&x.view(), &y.view());
assert!(result.is_ok());
let x_new =
Array2::from_shape_vec((2, 2), vec![0.5, 0.5, 0.25, 0.75]).expect("Operation failed");
let predictions = interpolator
.predict(&x_new.view())
.expect("Operation failed");
assert_eq!(predictions.nrows(), 2);
assert_eq!(predictions.ncols(), 2);
}
#[test]
fn test_piecewise_polynomial() {
let mut interpolator =
make_piecewise_polynomial_interpolator::<f64>(Some(2), Some(3), None);
let x = Array1::from_vec(vec![0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]);
let y = Array1::from_vec(vec![
0.0, 0.04, 0.16, 0.36, 0.64, 1.0, 0.8, 0.6, 0.4, 0.2, 0.0,
]);
let result = interpolator.fit(&x.view(), &y.view());
assert!(result.is_ok());
let x_new = Array1::from_vec(vec![0.3, 0.7, 1.3]);
let predictions = interpolator
.predict(&x_new.view())
.expect("Operation failed");
assert_eq!(predictions.len(), 3);
}
}