1use crate::error::{CsError, CsResult};
17use crate::linalg::cholesky::{cholesky_factor, cholesky_solve};
18use crate::linalg::{mat_t_vec, mat_vec, norm2};
19use crate::thresholding::iht::soft_threshold;
20
21#[derive(Debug, Clone)]
23pub struct DantzigConfig {
24 pub delta: f64,
26 pub max_iter: usize,
28 pub tol: f64,
30}
31
32pub fn dantzig_selector(
51 a: &[f64],
52 b: &[f64],
53 m: usize,
54 n: usize,
55 cfg: &DantzigConfig,
56) -> CsResult<Vec<f64>> {
57 if m == 0 || n == 0 {
59 return Err(CsError::EmptyInput);
60 }
61 if a.len() != m * n {
62 return Err(CsError::ShapeMismatch {
63 expected: vec![m, n],
64 got: vec![a.len()],
65 });
66 }
67 if b.len() != m {
68 return Err(CsError::DimensionMismatch { a: b.len(), b: m });
69 }
70 if cfg.delta <= 0.0 {
71 return Err(CsError::InvalidParameter(format!(
72 "delta must be > 0, got {}",
73 cfg.delta
74 )));
75 }
76 if cfg.tol <= 0.0 {
77 return Err(CsError::InvalidParameter(format!(
78 "tol must be > 0, got {}",
79 cfg.tol
80 )));
81 }
82
83 let rho = 1.0_f64;
86
87 let mut ata = vec![0.0_f64; n * n];
89 for k in 0..m {
90 for i in 0..n {
91 let aki = a[k * n + i];
92 for j in 0..n {
93 ata[i * n + j] += aki * a[k * n + j];
94 }
95 }
96 }
97
98 let at_b = mat_t_vec(a, m, n, b)?;
100
101 let mut system = ata.clone();
103 for i in 0..n {
104 system[i * n + i] += rho;
105 }
106 let l = cholesky_factor(&system, n)?;
107
108 let mut x = vec![0.0_f64; n];
110 let mut s = vec![0.0_f64; n];
112 let mut u = vec![0.0_f64; n];
114
115 for _iter in 0..cfg.max_iter {
116 let mut rhs = vec![0.0_f64; n];
119 for j in 0..n {
120 rhs[j] = rho * (at_b[j] - s[j] + u[j]);
121 }
122 for j in 0..n {
124 rhs[j] += rho * x[j];
125 }
126 let x_hat = cholesky_solve(&l, n, &rhs)?;
127 let x_new = soft_threshold(&x_hat, 1.0 / rho);
129
130 let ax_new = mat_vec(a, m, n, &x_new)?;
133 let mut primal_res = vec![0.0_f64; m];
134 for i in 0..m {
135 primal_res[i] = b[i] - ax_new[i];
136 }
137 let at_res = mat_t_vec(a, m, n, &primal_res)?;
138
139 let mut s_new = vec![0.0_f64; n];
141 for j in 0..n {
142 let v = at_res[j] + u[j];
143 s_new[j] = v.clamp(-cfg.delta, cfg.delta);
144 }
145
146 for j in 0..n {
148 u[j] += at_res[j] - s_new[j];
149 }
150
151 let x_change = {
154 let mut sq = 0.0_f64;
155 for j in 0..n {
156 let d = x_new[j] - x[j];
157 sq += d * d;
158 }
159 sq.sqrt()
160 };
161 let constraint_viol = at_res.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()));
163 let x_norm = norm2(&x_new).max(1.0e-300);
164
165 x = x_new;
166 s = s_new;
167
168 if (constraint_viol - cfg.delta).max(0.0) < cfg.tol && x_change / x_norm < cfg.tol {
169 break;
170 }
171 }
172
173 Ok(x)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 fn constraint_inf(a: &[f64], b: &[f64], x: &[f64], m: usize, n: usize) -> f64 {
184 let ax: Vec<f64> = (0..m)
185 .map(|i| (0..n).map(|j| a[i * n + j] * x[j]).sum())
186 .collect();
187 let resid: Vec<f64> = (0..m).map(|i| b[i] - ax[i]).collect();
188 let at_r: Vec<f64> = (0..n)
189 .map(|j| (0..m).map(|i| a[i * n + j] * resid[i]).sum())
190 .collect();
191 at_r.iter().fold(0.0_f64, |acc, &v| acc.max(v.abs()))
192 }
193
194 #[test]
196 fn output_len() {
197 let a = vec![1.0, 0.0, 0.0, 1.0]; let b = vec![1.0, 0.5];
199 let cfg = DantzigConfig {
200 delta: 0.1,
201 max_iter: 50,
202 tol: 1.0e-6,
203 };
204 let x = dantzig_selector(&a, &b, 2, 2, &cfg).expect("output_len");
205 assert_eq!(x.len(), 2);
206 }
207
208 #[test]
210 fn constraint_satisfied() {
211 let a = vec![
212 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,
213 ];
214 let b = vec![1.0, 0.0, 0.5, 0.0];
215 let delta = 0.05_f64;
216 let cfg = DantzigConfig {
217 delta,
218 max_iter: 200,
219 tol: 1.0e-7,
220 };
221 let x = dantzig_selector(&a, &b, 4, 4, &cfg).expect("constraint_satisfied");
222 let viol = constraint_inf(&a, &b, &x, 4, 4);
223 assert!(
225 viol < delta + 0.5,
226 "constraint violation {viol} exceeds delta + slack"
227 );
228 }
229
230 #[test]
232 fn sparse_solution() {
233 let a = vec![
235 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,
236 ];
237 let b = vec![1.0, 0.0, 0.5, 0.0];
238 let cfg = DantzigConfig {
239 delta: 0.01,
240 max_iter: 200,
241 tol: 1.0e-7,
242 };
243 let x = dantzig_selector(&a, &b, 4, 4, &cfg).expect("sparse_solution");
244 let nnz = x.iter().filter(|&&v| v.abs() > 1.0e-4).count();
245 assert!(
247 nnz <= 3,
248 "expected sparse solution, got {nnz} nonzeros: {:?}",
249 x
250 );
251 }
252
253 #[test]
255 fn zero_noise_exact_recovery() {
256 let a = vec![1.0, 0.0, 0.0, 1.0]; let b = vec![1.0, 0.5];
258 let cfg = DantzigConfig {
259 delta: 0.01,
260 max_iter: 200,
261 tol: 1.0e-8,
262 };
263 let x = dantzig_selector(&a, &b, 2, 2, &cfg).expect("zero_noise_exact_recovery");
264 assert!((x[0] - 1.0).abs() < 0.1, "x[0]={} not close to 1.0", x[0]);
265 assert!((x[1] - 0.5).abs() < 0.1, "x[1]={} not close to 0.5", x[1]);
266 }
267
268 #[test]
270 fn delta_too_small_ok() {
271 let a = vec![1.0, 0.0, 0.0, 1.0];
272 let b = vec![1.0, 0.5];
273 let cfg = DantzigConfig {
275 delta: 1.0e-10,
276 max_iter: 30,
277 tol: 1.0e-5,
278 };
279 let result = dantzig_selector(&a, &b, 2, 2, &cfg);
280 assert!(
281 result.is_ok(),
282 "should not error on tiny delta: {:?}",
283 result
284 );
285 let x = result.expect("delta_too_small_ok");
286 assert!(
287 x.iter().all(|v| v.is_finite()),
288 "non-finite output: {:?}",
289 x
290 );
291 }
292
293 #[test]
295 fn tol_stops_early() {
296 let a = vec![
299 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,
300 ];
301 let b = vec![1.0, 0.0, 0.5, 0.0];
302 let cfg = DantzigConfig {
304 delta: 0.1,
305 max_iter: 1000,
306 tol: 1.0e5,
307 };
308 let x = dantzig_selector(&a, &b, 4, 4, &cfg).expect("tol_stops_early");
309 assert_eq!(x.len(), 4);
310 assert!(
311 x.iter().all(|v| v.is_finite()),
312 "non-finite output: {:?}",
313 x
314 );
315 }
316
317 #[test]
319 fn output_finite() {
320 let a = vec![2.0, 1.0, 0.0, 1.0, 3.0, 1.0, 0.0, 1.0, 2.0];
321 let b = vec![1.0, 2.0, 1.5];
322 let cfg = DantzigConfig {
323 delta: 0.1,
324 max_iter: 100,
325 tol: 1.0e-6,
326 };
327 let x = dantzig_selector(&a, &b, 3, 3, &cfg).expect("output_finite");
328 assert!(
329 x.iter().all(|v| v.is_finite()),
330 "non-finite output: {:?}",
331 x
332 );
333 }
334
335 #[test]
337 fn n_gt_m_underdetermined() {
338 let a = vec![
340 1.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
341 0.5,
342 ];
343 let b = vec![1.0, 0.5, 0.3];
344 let cfg = DantzigConfig {
345 delta: 0.1,
346 max_iter: 100,
347 tol: 1.0e-6,
348 };
349 let x = dantzig_selector(&a, &b, 3, 6, &cfg).expect("n_gt_m_underdetermined");
350 assert_eq!(x.len(), 6, "output length should be n=6");
351 assert!(
352 x.iter().all(|v| v.is_finite()),
353 "non-finite output: {:?}",
354 x
355 );
356 }
357
358 #[test]
361 fn empty_input_error_m0() {
362 let cfg = DantzigConfig {
363 delta: 0.1,
364 max_iter: 10,
365 tol: 1.0e-6,
366 };
367 let result = dantzig_selector(&[], &[], 0, 3, &cfg);
368 assert!(
369 matches!(result, Err(CsError::EmptyInput)),
370 "expected EmptyInput, got {:?}",
371 result
372 );
373 }
374
375 #[test]
376 fn empty_input_error_n0() {
377 let cfg = DantzigConfig {
378 delta: 0.1,
379 max_iter: 10,
380 tol: 1.0e-6,
381 };
382 let result = dantzig_selector(&[], &[1.0, 2.0], 2, 0, &cfg);
383 assert!(
384 matches!(result, Err(CsError::EmptyInput)),
385 "expected EmptyInput, got {:?}",
386 result
387 );
388 }
389
390 #[test]
391 fn invalid_delta_error() {
392 let a = vec![1.0, 0.0, 0.0, 1.0];
393 let b = vec![1.0, 0.5];
394 let cfg = DantzigConfig {
395 delta: -0.1,
396 max_iter: 10,
397 tol: 1.0e-6,
398 };
399 let result = dantzig_selector(&a, &b, 2, 2, &cfg);
400 assert!(
401 matches!(result, Err(CsError::InvalidParameter(_))),
402 "expected InvalidParameter, got {:?}",
403 result
404 );
405 }
406
407 #[test]
408 fn invalid_tol_error() {
409 let a = vec![1.0, 0.0, 0.0, 1.0];
410 let b = vec![1.0, 0.5];
411 let cfg = DantzigConfig {
412 delta: 0.1,
413 max_iter: 10,
414 tol: -1.0e-6,
415 };
416 let result = dantzig_selector(&a, &b, 2, 2, &cfg);
417 assert!(
418 matches!(result, Err(CsError::InvalidParameter(_))),
419 "expected InvalidParameter, got {:?}",
420 result
421 );
422 }
423}