use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use sklears_core::{error::Result as SklResult, prelude::SklearsError};
#[derive(Debug, Clone)]
pub struct AutoregressiveExtractor {
order: usize,
method: String, normalize: bool,
}
impl AutoregressiveExtractor {
pub fn new() -> Self {
Self {
order: 10,
method: "yule_walker".to_string(),
normalize: true,
}
}
pub fn order(mut self, order: usize) -> Self {
self.order = order;
self
}
pub fn method(mut self, method: String) -> Self {
self.method = method;
self
}
pub fn normalize(mut self, normalize: bool) -> Self {
self.normalize = normalize;
self
}
pub fn extract_features(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if signal.is_empty() {
return Err(SklearsError::InvalidInput("Empty signal".to_string()));
}
if signal.len() <= self.order {
return Err(SklearsError::InvalidInput(
"Signal too short for AR order".to_string(),
));
}
let ar_coeffs = match self.method.as_str() {
"yule_walker" => self.yule_walker(signal)?,
"least_squares" => self.least_squares(signal)?,
_ => {
return Err(SklearsError::InvalidInput(format!(
"Unknown AR method: {}",
self.method
)))
}
};
let mut result = Array1::from_vec(ar_coeffs);
if self.normalize {
let norm = result.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm > 0.0 {
result.mapv_inplace(|x| x / norm);
}
}
Ok(result)
}
#[allow(non_snake_case)]
fn yule_walker(&self, signal: &ArrayView1<f64>) -> SklResult<Vec<f64>> {
let autocorr = self.autocorrelation(signal, self.order + 1);
let mut R = Array2::zeros((self.order, self.order));
let mut r = Array1::zeros(self.order);
for i in 0..self.order {
r[i] = autocorr[i + 1];
for j in 0..self.order {
R[[i, j]] = autocorr[(i as i32 - j as i32).unsigned_abs() as usize];
}
}
let coeffs = self.solve_linear_system(&R, &r)?;
Ok(coeffs.to_vec())
}
#[allow(non_snake_case)]
fn least_squares(&self, signal: &ArrayView1<f64>) -> SklResult<Vec<f64>> {
let n = signal.len();
let p = self.order;
if n <= p {
return Err(SklearsError::InvalidInput(
"Signal too short for least squares AR".to_string(),
));
}
let mut X = Array2::zeros((n - p, p));
let mut y = Array1::zeros(n - p);
for i in 0..(n - p) {
y[i] = signal[i + p];
for j in 0..p {
X[[i, j]] = signal[i + p - 1 - j];
}
}
let XtX = X.t().dot(&X);
let Xty = X.t().dot(&y);
let coeffs = self.solve_linear_system(&XtX, &Xty)?;
Ok(coeffs.iter().map(|&x| -x).collect())
}
fn autocorrelation(&self, signal: &ArrayView1<f64>, max_lag: usize) -> Vec<f64> {
let n = signal.len();
let mut autocorr = vec![0.0; max_lag + 1];
for lag in 0..=max_lag {
let mut sum = 0.0;
let count = n - lag;
for i in 0..count {
sum += signal[i] * signal[i + lag];
}
autocorr[lag] = sum / count as f64;
}
autocorr
}
#[allow(non_snake_case)]
fn solve_linear_system(&self, A: &Array2<f64>, b: &Array1<f64>) -> SklResult<Array1<f64>> {
let n = A.nrows();
if A.ncols() != n || b.len() != n {
return Err(SklearsError::InvalidInput(
"Matrix dimensions mismatch".to_string(),
));
}
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 i in 0..n {
let mut max_row = i;
for k in (i + 1)..n {
if aug[[k, i]].abs() > aug[[max_row, i]].abs() {
max_row = k;
}
}
if max_row != i {
for j in 0..=n {
let temp = aug[[i, j]];
aug[[i, j]] = aug[[max_row, j]];
aug[[max_row, j]] = temp;
}
}
if aug[[i, i]].abs() < 1e-10 {
return Err(SklearsError::InvalidInput("Singular matrix".to_string()));
}
for k in (i + 1)..n {
let factor = aug[[k, i]] / aug[[i, i]];
for j in i..=n {
aug[[k, j]] -= factor * aug[[i, j]];
}
}
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
x[i] = aug[[i, n]];
for j in (i + 1)..n {
x[i] -= aug[[i, j]] * x[j];
}
x[i] /= aug[[i, i]];
}
Ok(x)
}
}
impl Default for AutoregressiveExtractor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CrossCorrelationExtractor {
max_lag: usize,
normalize: bool,
mode: String, templates: Option<Vec<Array1<f64>>>,
}
impl CrossCorrelationExtractor {
pub fn new() -> Self {
Self {
max_lag: 50,
normalize: true,
mode: "valid".to_string(),
templates: None,
}
}
pub fn max_lag(mut self, max_lag: usize) -> Self {
self.max_lag = max_lag;
self
}
pub fn normalize(mut self, normalize: bool) -> Self {
self.normalize = normalize;
self
}
pub fn mode(mut self, mode: String) -> Self {
self.mode = mode;
self
}
pub fn templates(mut self, templates: Vec<Array1<f64>>) -> Self {
self.templates = Some(templates);
self
}
pub fn extract_features(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if signal.is_empty() {
return Err(SklearsError::InvalidInput("Empty signal".to_string()));
}
let mut features = Vec::new();
if let Some(ref templates) = self.templates {
for template in templates {
let corr = self.cross_correlate(signal, &template.view())?;
let max_corr = corr.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let max_idx = corr
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.map(|(idx, _)| idx)
.unwrap_or(0);
features.push(max_corr);
features.push(max_idx as f64);
let mean_corr = corr.mean().unwrap_or(0.0);
let std_corr = corr.std(0.0);
features.push(mean_corr);
features.push(std_corr);
}
} else {
let autocorr = self.autocorrelation(signal);
features.extend_from_slice(&autocorr.to_vec());
}
Ok(Array1::from_vec(features))
}
fn cross_correlate(
&self,
signal1: &ArrayView1<f64>,
signal2: &ArrayView1<f64>,
) -> SklResult<Array1<f64>> {
let n1 = signal1.len();
let n2 = signal2.len();
match self.mode.as_str() {
"full" => {
let n_out = n1 + n2 - 1;
let mut result = Array1::zeros(n_out);
for i in 0..n_out {
let mut sum = 0.0;
let start1 = i.saturating_sub(n2 - 1);
let end1 = (i + 1).min(n1);
for j in start1..end1 {
let k = i - j;
if k < n2 {
sum += signal1[j] * signal2[n2 - 1 - k];
}
}
result[i] = sum;
}
if self.normalize {
let norm1 = signal1.iter().map(|x| x * x).sum::<f64>().sqrt();
let norm2 = signal2.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm1 > 0.0 && norm2 > 0.0 {
result.mapv_inplace(|x| x / (norm1 * norm2));
}
}
Ok(result)
}
"valid" => {
if n2 > n1 {
return Err(SklearsError::InvalidInput(
"Template longer than signal in valid mode".to_string(),
));
}
let n_out = n1 - n2 + 1;
let mut result = Array1::zeros(n_out);
for i in 0..n_out {
let mut sum = 0.0;
for j in 0..n2 {
sum += signal1[i + j] * signal2[j];
}
result[i] = sum;
}
if self.normalize {
let norm1 = signal1.iter().map(|x| x * x).sum::<f64>().sqrt();
let norm2 = signal2.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm1 > 0.0 && norm2 > 0.0 {
result.mapv_inplace(|x| x / (norm1 * norm2));
}
}
Ok(result)
}
_ => Err(SklearsError::InvalidInput(format!(
"Unknown correlation mode: {}",
self.mode
))),
}
}
fn autocorrelation(&self, signal: &ArrayView1<f64>) -> Array1<f64> {
let n = signal.len();
let max_lag = self.max_lag.min(n - 1);
let mut autocorr = Array1::zeros(max_lag + 1);
for lag in 0..=max_lag {
let mut sum = 0.0;
let count = n - lag;
for i in 0..count {
sum += signal[i] * signal[i + lag];
}
autocorr[lag] = sum / count as f64;
}
if self.normalize && autocorr[0] > 0.0 {
let norm_factor = autocorr[0];
autocorr.mapv_inplace(|x| x / norm_factor);
}
autocorr
}
}
impl Default for CrossCorrelationExtractor {
fn default() -> Self {
Self::new()
}
}