use lstsq::lstsq;
use nalgebra::{DMatrix, DVector, SVD};
fn factorial(num: usize) -> usize {
match num {
0 => 1,
1 => 1,
_ => factorial(num - 1) * num,
}
}
fn SG_coeffs(window: usize, poly_order: usize, deriv: usize) -> Result<DVector<f64>, String> {
if poly_order >= window {
return Err("poly_order must be less than window.".to_string());
}
let (halflen, rem) = (window / 2, window % 2);
let pos = match rem {
0 => halflen as f64 - 0.5,
_ => halflen as f64,
};
if deriv > poly_order {
return Ok(DVector::from_element(window, 0.0));
}
let x = DVector::from_fn(window, |i, _| pos - i as f64);
let order = DVector::from_fn(poly_order + 1, |i, _| i);
let mat_a = DMatrix::from_fn(poly_order + 1, window, |i, j| x[j].powf(order[i] as f64));
let mut y = DVector::from_element(poly_order + 1, 0.0);
y[deriv] = factorial(deriv) as f64;
let epsilon = 1e-14;
let results = lstsq(&mat_a, &y, epsilon)?;
let solution = results.solution;
return Ok(solution);
}
fn poly_derivative(coeffs: &[f64]) -> Vec<f64> {
coeffs[1..]
.iter()
.enumerate()
.map(|(i, c)| c * (i + 1) as f64)
.collect()
}
fn polyval(poly: &[f64], values: &[f64]) -> Vec<f64> {
values
.iter()
.map(|v| {
poly.iter()
.enumerate()
.fold(0.0, |y, (i, c)| y + c * v.powf(i as f64))
})
.collect()
}
fn fit_edge(
x: &DVector<f64>,
window_start: usize,
window_stop: usize,
interp_start: usize,
interp_stop: usize,
poly_order: usize,
deriv: usize,
y: &mut Vec<f64>,
) -> Result<(), String> {
let x_edge: Vec<f64> = x.as_slice()[window_start..window_stop].to_vec();
let y_edge: Vec<f64> = (0..window_stop - window_start).map(|i| i as f64).collect();
let mut poly_coeffs = polyfit(&y_edge, &x_edge, poly_order)?;
let mut deriv = deriv;
while deriv > 0 {
poly_coeffs = poly_derivative(&poly_coeffs);
deriv -= 1;
}
let i: Vec<f64> = (0..interp_stop - interp_start)
.map(|i| (interp_start - window_start + i) as f64)
.collect();
let values = polyval(&poly_coeffs, &i);
y.splice(interp_start..interp_stop, values);
Ok(())
}
fn fit_edges_polyfit(
x: &DVector<f64>,
window: usize,
poly_order: usize,
deriv: usize,
y: &mut Vec<f64>,
) -> Result<(), String> {
let halflen = window / 2;
fit_edge(x, 0, window, 0, halflen, poly_order, deriv, y)?;
let n = x.len();
fit_edge(x, n - window, n, n - halflen, n, poly_order, deriv, y)?;
Ok(())
}
#[derive(Clone, Debug)]
pub struct SGInput<'a> {
pub data: &'a [f64],
pub window: usize,
pub poly_order: usize,
pub derivative: usize,
}
pub fn SG_filter(input: &SGInput) -> Result<Vec<f64>, String> {
if input.window > input.data.len() {
return Err("window must be less than or equal to the size of the input data".to_string());
}
if input.window % 2 == 0 {
return Err("window must be odd".to_string());
}
let coeffs = SG_coeffs(input.window, input.poly_order, input.derivative)?;
let x = DVector::from_vec(input.data.to_vec());
let y = x.convolve_full(coeffs);
let padding = y.len() - x.len();
let padding = padding / 2;
let y = y.as_slice();
let mut y = y[padding..y.len().saturating_sub(padding)].to_vec();
fit_edges_polyfit(&x, input.window, input.poly_order, input.derivative, &mut y)?;
return Ok(y);
}
pub fn polyfit(
x_values: &[f64],
y_values: &[f64],
polynomial_degree: usize,
) -> Result<Vec<f64>, &'static str> {
let number_of_columns = polynomial_degree + 1;
let number_of_rows = x_values.len();
let mut a = DMatrix::zeros(number_of_rows, number_of_columns);
for (row, &x) in x_values.iter().enumerate() {
a[(row, 0)] = 1.0f64;
for col in 1..number_of_columns {
a[(row, col)] = x.powf(col as f64);
}
}
let b = DVector::from_row_slice(y_values);
let decomp = SVD::new(a, true, true);
match decomp.solve(&b, 1e-18f64) {
Ok(mat) => Ok(mat.data.into()),
Err(error) => Err(error),
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_coeffs_basic() {
let c = SG_coeffs(5, 2, 0).unwrap();
let expected = DVector::from_vec(vec![
-0.08571428571428572,
0.34285714285714286,
0.4857142857142857,
0.34285714285714286,
-0.08571428571428572,
]);
assert_eq!(c.len(), expected.len());
for (a, e) in c.iter().zip(expected.iter()) {
assert_relative_eq!(a, e, max_relative = 1e-9);
}
}
#[test]
fn test_coeffs_derivative() {
let c = SG_coeffs(5, 2, 1).unwrap();
let expected = DVector::from_vec(vec![0.2, 0.1, 0.0, -0.1, -0.2]);
assert_eq!(c.len(), expected.len());
for (a, e) in c.iter().zip(expected.iter()) {
assert_relative_eq!(a, e, max_relative = 1e-9);
}
}
#[test]
fn test_coeffs_symmetry() {
let c = SG_coeffs(7, 3, 0).unwrap();
let n = c.len();
for i in 0..n / 2 {
assert_relative_eq!(c[i], c[n - 1 - i], max_relative = 1e-12);
}
}
#[test]
fn test_coeffs_error_cases() {
assert!(SG_coeffs(3, 3, 0).is_err()); assert!(SG_coeffs(5, 2, 3).is_ok());
let c = SG_coeffs(5, 2, 3).unwrap();
for coeff in c.iter() {
assert_relative_eq!(*coeff, 0.0, max_relative = 1e-12);
}
}
#[test]
fn test_filter_on_known_series() {
let input = [2.0, 2.0, 5.0, 2.0, 1.0, 0.0, 1.0, 4.0, 9.0];
let y = SG_filter(&SGInput {
data: &input,
window: 5,
poly_order: 2,
derivative: 0,
})
.unwrap();
let expected = vec![
1.65714285714,
3.02857143,
3.54285714,
2.85714286,
0.65714286,
0.17142857,
1.0,
4.05,
7.97142857,
];
assert_eq!(y.len(), expected.len());
for (a, e) in y.iter().zip(expected.iter()) {
assert_relative_eq!(a, e, max_relative = 1e-1);
}
}
#[test]
fn test_filter_derivative() {
let input = [1.0, 4.0, 9.0, 16.0, 25.0]; let y = SG_filter(&SGInput {
data: &input,
window: 5,
poly_order: 2,
derivative: 1,
})
.unwrap();
let expected = vec![2.0, 4.0, 6.0, 8.0, 10.0];
assert_eq!(y.len(), expected.len());
for (a, e) in y.iter().zip(expected.iter()) {
assert_relative_eq!(a, e, max_relative = 0.1);
}
}
#[test]
fn test_filter_constant_data() {
let input = [5.0; 10];
let y = SG_filter(&SGInput {
data: &input,
window: 5,
poly_order: 2,
derivative: 0,
})
.unwrap();
for val in y.iter() {
assert_relative_eq!(*val, 5.0, max_relative = 1e-10);
}
}
#[test]
fn test_filter_error_cases() {
let input = [1.0, 2.0, 3.0];
assert!(
SG_filter(&SGInput {
data: &input,
window: 5,
poly_order: 2,
derivative: 0
})
.is_err()
);
assert!(
SG_filter(&SGInput {
data: &input,
window: 4,
poly_order: 2,
derivative: 0
})
.is_err()
);
}
#[test]
fn test_filter_linear_function() {
let data: Vec<f64> = (0..100).map(|i| (3 * i - 2) as f64).collect();
let y = SG_filter(&SGInput {
data: &data,
window: 51,
poly_order: 5,
derivative: 0,
})
.unwrap();
assert_eq!(y.len(), data.len());
for x in 30..70 {
let expected = 3.0 * x as f64 - 2.0;
assert_relative_eq!(y[x], expected, max_relative = 1e-9);
}
}
#[test]
fn test_polyfit_basic() {
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y = vec![0.0, 1.0, 4.0, 9.0, 16.0]; let coeffs = polyfit(&x, &y, 2).unwrap();
assert_relative_eq!(coeffs[0].abs(), 0.0, epsilon = 1e-10);
assert_relative_eq!(coeffs[1].abs(), 0.0, epsilon = 1e-10);
assert_relative_eq!(coeffs[2].abs(), 1.0, epsilon = 1e-10);
}
#[test]
fn test_factorial() {
assert_eq!(factorial(0), 1);
assert_eq!(factorial(1), 1);
assert_eq!(factorial(2), 2);
assert_eq!(factorial(3), 6);
assert_eq!(factorial(4), 24);
assert_eq!(factorial(5), 120);
}
#[test]
fn test_poly_derivative() {
let coeffs = vec![4.0, 3.0, 2.0, 1.0]; let deriv_coeffs = poly_derivative(&coeffs);
let expected = vec![3.0, 4.0, 3.0];
assert_eq!(deriv_coeffs.len(), expected.len());
for (a, e) in deriv_coeffs.iter().zip(expected.iter()) {
assert_relative_eq!(a, e, max_relative = 1e-12);
}
}
#[test]
fn test_polyval() {
let coeffs = vec![1.0, 2.0, 3.0]; let x_vals = vec![0.0, 1.0, 2.0];
let y_vals = polyval(&coeffs, &x_vals);
let expected = vec![1.0, 6.0, 17.0];
assert_eq!(y_vals.len(), expected.len());
for (a, e) in y_vals.iter().zip(expected.iter()) {
assert_relative_eq!(a, e, max_relative = 1e-12);
}
}
}