use crate::error::{CsError, CsResult};
use crate::linalg::{mat_t_vec, mat_vec, norm2};
use crate::thresholding::ThresholdingResult;
pub fn hard_threshold_k(x: &[f64], k: usize) -> CsResult<(Vec<f64>, Vec<usize>)> {
if k > x.len() {
return Err(CsError::SupportTooLarge {
requested: k,
max: x.len(),
});
}
if k == 0 {
return Ok((vec![0.0; x.len()], Vec::new()));
}
let mut paired: Vec<(usize, f64, f64)> = x
.iter()
.enumerate()
.map(|(i, &v)| (i, v, v.abs()))
.collect();
paired.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
let mut out = vec![0.0_f64; x.len()];
let mut support = Vec::with_capacity(k);
for it in paired.iter().take(k) {
out[it.0] = it.1;
support.push(it.0);
}
support.sort();
Ok((out, support))
}
pub fn soft_threshold(x: &[f64], lambda: f64) -> Vec<f64> {
x.iter()
.map(|v| {
let s = v.signum();
let mag = (v.abs() - lambda).max(0.0);
s * mag
})
.collect()
}
pub fn iht(
phi: &[f64],
m: usize,
n: usize,
y: &[f64],
k: usize,
mu: f64,
max_iter: usize,
tol: f64,
) -> CsResult<ThresholdingResult> {
if phi.len() != m * n {
return Err(CsError::ShapeMismatch {
expected: vec![m, n],
got: vec![phi.len()],
});
}
if y.len() != m {
return Err(CsError::DimensionMismatch { a: y.len(), b: m });
}
if k == 0 || k > n {
return Err(CsError::InvalidSparsity(k));
}
if mu <= 0.0 {
return Err(CsError::InvalidParameter(format!(
"step mu must be > 0; got {mu}"
)));
}
let mut x = vec![0.0_f64; n];
let mut support: Vec<usize> = Vec::new();
let mut iter = 0usize;
for _ in 0..max_iter {
let ax = mat_vec(phi, m, n, &x)?;
let mut residual = vec![0.0_f64; m];
for i in 0..m {
residual[i] = y[i] - ax[i];
}
let g = mat_t_vec(phi, m, n, &residual)?;
let mut candidate = x.clone();
for j in 0..n {
candidate[j] += mu * g[j];
}
let (x_new, supp_new) = hard_threshold_k(&candidate, k)?;
let mut delta = 0.0_f64;
for j in 0..n {
let d = x_new[j] - x[j];
delta += d * d;
}
x = x_new;
support = supp_new;
iter += 1;
let xnorm = norm2(&x).max(1.0e-300);
if delta.sqrt() / xnorm < tol {
break;
}
}
let ax = mat_vec(phi, m, n, &x)?;
let mut residual = vec![0.0_f64; m];
for i in 0..m {
residual[i] = y[i] - ax[i];
}
Ok(ThresholdingResult {
x,
support,
residual_norm: norm2(&residual),
iterations: iter,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hard_threshold_top_k() {
let (out, supp) = hard_threshold_k(&[3.0, 1.0, 4.0, 1.0, 5.0], 2).expect("ok");
assert_eq!(supp, vec![2, 4]);
assert_eq!(out[2], 4.0);
assert_eq!(out[4], 5.0);
assert_eq!(out[0], 0.0);
}
#[test]
fn soft_threshold_doc_example() {
let x = [2.0, 0.5, -0.5, -2.0];
let p = soft_threshold(&x, 1.0);
assert!((p[0] - 1.0).abs() < 1.0e-12);
assert!(p[1].abs() < 1.0e-12);
assert!(p[2].abs() < 1.0e-12);
assert!((p[3] + 1.0).abs() < 1.0e-12);
}
#[test]
fn iht_recovers_canonical() {
let phi = vec![
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
];
let y = vec![1.0, 0.0, 0.5, 0.0];
let r = iht(&phi, 4, 4, &y, 2, 0.9, 200, 1.0e-9).expect("ok");
assert!(r.support.contains(&0));
assert!(r.support.contains(&2));
}
}