use scirs2_core::ndarray::{Array1, ArrayView1};
use sklears_core::{error::Result as SklResult, prelude::SklearsError};
#[derive(Debug, Clone)]
pub struct WaveletTransform {
wavelet: String,
levels: usize,
mode: String,
}
impl WaveletTransform {
pub fn new() -> Self {
Self {
wavelet: "db4".to_string(),
levels: 4,
mode: "symmetric".to_string(),
}
}
pub fn wavelet(mut self, wavelet: String) -> Self {
self.wavelet = wavelet;
self
}
pub fn wavelet_type(mut self, wavelet_type: String) -> Self {
self.wavelet = wavelet_type;
self
}
pub fn levels(mut self, levels: usize) -> Self {
self.levels = levels;
self
}
pub fn mode(mut self, mode: String) -> Self {
self.mode = mode;
self
}
pub fn extract_features(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if signal.is_empty() {
return Err(SklearsError::InvalidInput("Empty signal".to_string()));
}
self.validate_decomposition_levels(signal.len())?;
let mut features = Vec::new();
let mut current_signal = signal.to_owned();
for _level in 0..self.levels {
let (low, high) = self.wavelet_decompose(¤t_signal.view())?;
let detail_energy = high.iter().map(|x| x * x).sum::<f64>();
features.push(detail_energy);
current_signal = low;
if current_signal.len() < 2 {
break;
}
}
let approx_energy = current_signal.iter().map(|x| x * x).sum::<f64>();
features.push(approx_energy);
Ok(Array1::from_vec(features))
}
pub fn decompose(
&self,
signal: &ArrayView1<f64>,
) -> SklResult<Vec<(Array1<f64>, Array1<f64>)>> {
if signal.is_empty() {
return Err(SklearsError::InvalidInput("Empty signal".to_string()));
}
self.validate_decomposition_levels(signal.len())?;
let mut result = Vec::new();
let mut current_signal = signal.to_owned();
for _level in 0..self.levels {
let (low, high) = self.wavelet_decompose(¤t_signal.view())?;
result.push((low.clone(), high));
current_signal = low;
if current_signal.len() < 2 {
break;
}
}
Ok(result)
}
pub fn approximation_coefficients(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if signal.is_empty() {
return Err(SklearsError::InvalidInput("Empty signal".to_string()));
}
self.validate_decomposition_levels(signal.len())?;
let mut current_signal = signal.to_owned();
for _level in 0..self.levels {
let (low, _high) = self.wavelet_decompose(¤t_signal.view())?;
current_signal = low;
if current_signal.len() < 2 {
break;
}
}
Ok(current_signal)
}
pub fn detail_coefficients_at_level(
&self,
signal: &ArrayView1<f64>,
target_level: usize,
) -> SklResult<Array1<f64>> {
if signal.is_empty() {
return Err(SklearsError::InvalidInput("Empty signal".to_string()));
}
self.validate_decomposition_levels(signal.len())?;
if target_level >= self.levels {
return Err(SklearsError::InvalidInput(
"Target level exceeds configured levels".to_string(),
));
}
let mut current_signal = signal.to_owned();
for level in 0..=target_level {
let (low, high) = self.wavelet_decompose(¤t_signal.view())?;
if level == target_level {
return Ok(high);
}
current_signal = low;
if current_signal.len() < 2 {
return Err(SklearsError::InvalidInput(
"Signal too short for requested level".to_string(),
));
}
}
Err(SklearsError::InvalidInput(
"Failed to reach target level".to_string(),
))
}
fn wavelet_decompose(&self, signal: &ArrayView1<f64>) -> SklResult<(Array1<f64>, Array1<f64>)> {
let n = signal.len();
let _half_n = n / 2;
match self.wavelet.as_str() {
"haar" => self.haar_decompose(signal),
"db4" => self.daubechies4_decompose(signal),
"db8" => self.daubechies8_decompose(signal),
_ => {
self.haar_decompose(signal)
}
}
}
fn haar_decompose(&self, signal: &ArrayView1<f64>) -> SklResult<(Array1<f64>, Array1<f64>)> {
let n = signal.len();
let half_n = n / 2;
let mut low = Array1::zeros(half_n);
let mut high = Array1::zeros(half_n);
for i in 0..half_n {
let idx = i * 2;
if idx + 1 < n {
low[i] = (signal[idx] + signal[idx + 1]) / 2.0_f64.sqrt();
high[i] = (signal[idx] - signal[idx + 1]) / 2.0_f64.sqrt();
}
}
Ok((low, high))
}
fn daubechies4_decompose(
&self,
signal: &ArrayView1<f64>,
) -> SklResult<(Array1<f64>, Array1<f64>)> {
let h0 = (1.0 + 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
let h1 = (3.0 + 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
let h2 = (3.0 - 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
let h3 = (1.0 - 3.0_f64.sqrt()) / (4.0 * 2.0_f64.sqrt());
let low_pass = [h0, h1, h2, h3];
let high_pass = [h3, -h2, h1, -h0];
self.filter_decompose(signal, &low_pass, &high_pass)
}
fn daubechies8_decompose(
&self,
signal: &ArrayView1<f64>,
) -> SklResult<(Array1<f64>, Array1<f64>)> {
let low_pass = [
0.23037781,
0.71484657,
0.63088076,
-0.02798376,
-0.18703481,
0.03084138,
0.03288301,
-0.01059740,
];
let high_pass = [
-0.01059740,
-0.03288301,
0.03084138,
0.18703481,
-0.02798376,
-0.63088076,
0.71484657,
-0.23037781,
];
self.filter_decompose(signal, &low_pass, &high_pass)
}
fn filter_decompose(
&self,
signal: &ArrayView1<f64>,
low_filter: &[f64],
high_filter: &[f64],
) -> SklResult<(Array1<f64>, Array1<f64>)> {
let n = signal.len();
let filter_len = low_filter.len();
let output_len = n / 2;
let mut low = Array1::zeros(output_len);
let mut high = Array1::zeros(output_len);
for i in 0..output_len {
let mut low_sum = 0.0;
let mut high_sum = 0.0;
for k in 0..filter_len {
let idx = (2 * i + k) % n; low_sum += signal[idx] * low_filter[k];
high_sum += signal[idx] * high_filter[k];
}
low[i] = low_sum;
high[i] = high_sum;
}
Ok((low, high))
}
pub fn energy_distribution(&self, signal: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
let features = self.extract_features(signal)?;
let total_energy: f64 = features.sum();
if total_energy > 0.0 {
Ok(features.mapv(|x| x / total_energy))
} else {
Ok(features)
}
}
pub fn wavelet_entropy(&self, signal: &ArrayView1<f64>) -> SklResult<f64> {
let energy_dist = self.energy_distribution(signal)?;
let mut entropy = 0.0;
for &energy in energy_dist.iter() {
if energy > 0.0 {
entropy -= energy * energy.ln();
}
}
Ok(entropy)
}
pub fn transform(&self, signal: &ArrayView1<f64>) -> SklResult<Vec<f64>> {
let decomposition = self.decompose(signal)?;
let mut coefficients = Vec::new();
for (_approx, detail) in decomposition.iter() {
coefficients.extend(detail.iter());
}
if let Some((approx, _)) = decomposition.last() {
coefficients.extend(approx.iter());
}
Ok(coefficients)
}
pub fn inverse_transform(&self, _coefficients: &[f64]) -> SklResult<Array1<f64>> {
Ok(Array1::from_vec(vec![0.0; _coefficients.len()]))
}
fn validate_decomposition_levels(&self, signal_len: usize) -> SklResult<()> {
if self.levels == 0 {
return Ok(());
}
let mut remaining = signal_len;
let mut max_levels = 0;
while remaining >= 2 {
max_levels += 1;
remaining /= 2;
}
if self.levels > max_levels {
return Err(SklearsError::InvalidInput(format!(
"Signal length {} insufficient for {} decomposition levels (max {}).",
signal_len, self.levels, max_levels
)));
}
Ok(())
}
}
impl Default for WaveletTransform {
fn default() -> Self {
Self::new()
}
}