ATTEMPT, HAS BUGS: Make shifted_nonneg_soft_thresholding more readable draft dev

Mon, 23 Feb 2026 18:18:02 -0500

author
Tuomo Valkonen <tuomov@iki.fi>
date
Mon, 23 Feb 2026 18:18:02 -0500
branch
dev
changeset 64
d3be4f7ffd60
parent 63
7a8a55fd41c0

ATTEMPT, HAS BUGS: Make shifted_nonneg_soft_thresholding more readable

src/subproblem/l1squared_nonneg.rs file | annotate | diff | comparison | revisions
--- a/src/subproblem/l1squared_nonneg.rs	Thu Feb 26 11:36:22 2026 -0500
+++ b/src/subproblem/l1squared_nonneg.rs	Mon Feb 23 18:18:02 2026 -0500
@@ -100,10 +100,43 @@
 /// which is also used internally.
 /// The third output indicates whether the output is “locked” to either $0$ (resp. $-y$ in
 /// the reformulation), or $y$ ($0$ in the reformulation).
-/// The fourth output indicates the next highest $λ$ where such locking would happen.
+/// The fourth output indicates $λ$ where such locking to y would happen. It may be negative,
+/// in which case the prox is always locked to y.
 #[replace_float_literals(F::cast_from(literal))]
 #[inline]
-fn shifted_nonneg_soft_thresholding<F: Float>(x: F, y: F, λ: F) -> (F, F, bool, Option<F>) {
+fn shifted_nonneg_soft_thresholding<F: Float>(x: F, y: F, λ: F) -> (F, F, bool, F) {
+    // The OC is
+    // x ∈ w + λsign(w-y) + N_{≥ 0}(w)
+    // x-y ∈ w' + λsign(w') + N_{≥ -y}(w')
+    // if w' > -y, then soft-thresholding. So if soft-thresholding stays above -y.
+    // If w' = -y, and w'≠0, then x ≤ λsign(-y)
+    // If w' = -y and w'=0, then x ≤ λ.
+    // w_i=max{sign(x-y)*max(|x-y| - λ, 0), -y} + y = max{sign(x-y)*max(|x-y| - λ, 0)+y, 0}
+    let (w, locked, lock) = if x >= y {
+        // w_i=max{max(x-y - λ, 0)+y, 0}=max{max(x - λ, y), 0} = max{x-λ, max(y, 0)}
+        let l = 0.0.max(y);
+        // x - l = x + min(0.0, y) = min(x, x + y)
+        if x - λ >= l {
+            (x - λ, false, x - l)
+        } else {
+            (l, true, F::NEG_INFINITY) // λ is increasing, so NEG_INFINITY is ok. So is x-l < λ
+        }
+    } else {
+        // w_i=max{-max(-(x-y) - λ, 0)+y, 0}=max{min((x-y) + λ, 0)+y, 0}=max{min(x + λ, y), 0}
+        if x + λ <= y {
+            // = max(x+λ, 0)
+            if x >= -λ {
+                (x + λ, false, y - x)
+            } else {
+                (0.0, true, 0.0.min(y) - x)
+            }
+        } else {
+            (0.0.max(y), true, F::NEG_INFINITY)
+        }
+    };
+    return (w, w - y, locked, lock);
+
+    /*
     let z = x - y; // The shift to f_0
     if -y >= 0.0 {
         if x > λ {
@@ -123,7 +156,7 @@
         (x - λ, z - λ, false, Some(z))
     } else {
         (y, 0.0, true, None)
-    }
+    }*/
 }
 
 /// Calculate $\prox\_f(x)$ for $f(x)=\frac{β}{2}\norm{x-y}\_1\^2 + δ\_{≥0}(x)$.
@@ -180,14 +213,15 @@
         let mut x_prime = 0.0;
         let mut max_shift = F::INFINITY;
         for (&x_i, &y_i) in izip!(x.iter(), y.iter()) {
-            let (_, a_shifted, locked, next_lock) = shifted_nonneg_soft_thresholding(x_i, y_i, λ);
+            let (_, w_i, locked, lock) = shifted_nonneg_soft_thresholding(x_i, y_i, λ);
 
-            if let Some(t) = next_lock {
-                max_shift = min(max_shift, t);
-                assert!(max_shift > λ);
+            // If not already locked (lock <= y), consider this coordinate in next level for λ.
+            if lock > λ {
+                max_shift = min(max_shift, lock);
             }
+
             if locked {
-                w_locked += abs(a_shifted);
+                w_locked += abs(w_i);
             } else {
                 n_unlocked += 1;
                 x_prime += abs(x_i - y_i);
@@ -203,9 +237,7 @@
             assert!(λ_new >= λ);
             // success
             x.zip_apply(y, |x_i, y_i| {
-                let (a, _, _, _) = shifted_nonneg_soft_thresholding(*x_i, y_i, λ_new);
-                //*x_i = y_i + lbd_soft_thresholding(*x_i, λ_new, -y_i)
-                *x_i = a;
+                (*x_i, _, _, _) = shifted_nonneg_soft_thresholding(*x_i, y_i, λ_new);
             });
             return;
         }

mercurial