use crate::error::{CsError, CsResult};
pub fn tv_1d_chambolle(y: &[f64], lambda: f64, max_iter: usize, tol: f64) -> CsResult<Vec<f64>> {
if y.is_empty() {
return Err(CsError::EmptyInput);
}
if lambda < 0.0 {
return Err(CsError::InvalidParameter("lambda must be ≥ 0".into()));
}
if lambda == 0.0 {
return Ok(y.to_vec());
}
let n = y.len();
let mut p = vec![0.0_f64; n.saturating_sub(1)];
let tau = 0.249_f64;
let mut x = y.to_vec();
for _ in 0..max_iter {
x.copy_from_slice(y);
for j in 0..p.len() {
x[j] += p[j];
x[j + 1] -= p[j];
}
let mut delta = 0.0_f64;
for j in 0..p.len() {
let dx = x[j + 1] - x[j];
let p_new = (p[j] + tau * dx).clamp(-lambda, lambda);
let dp = (p_new - p[j]).abs();
if dp > delta {
delta = dp;
}
p[j] = p_new;
}
if delta < tol {
break;
}
}
let mut x_out = y.to_vec();
for j in 0..p.len() {
x_out[j] += p[j];
x_out[j + 1] -= p[j];
}
Ok(x_out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tv_1d_no_noise_passes_constant() {
let y = vec![5.0, 5.0, 5.0, 5.0];
let x = tv_1d_chambolle(&y, 1.0, 100, 1.0e-9).expect("ok");
for &xi in &x {
assert!((xi - 5.0).abs() < 1.0e-6);
}
}
#[test]
fn tv_1d_step_edge() {
let y = vec![1.0, 1.05, 0.95, 1.02, 5.0, 4.97, 5.03, 5.0];
let x = tv_1d_chambolle(&y, 0.5, 500, 1.0e-9).expect("ok");
let mean_lo = x[..4].iter().sum::<f64>() / 4.0;
let mean_hi = x[4..].iter().sum::<f64>() / 4.0;
for &v in &x[..4] {
assert!((v - mean_lo).abs() < 0.2);
}
for &v in &x[4..] {
assert!((v - mean_hi).abs() < 0.2);
}
assert!(mean_hi > mean_lo + 1.0);
}
}