scirs2_optimize/differentiable_optimization/
implicit_diff.rs1use crate::error::{OptimizeError, OptimizeResult};
13
14pub fn compute_kkt_jacobian(
51 q: &[Vec<f64>],
52 g: &[Vec<f64>],
53 a: &[Vec<f64>],
54 x: &[f64],
55 lam: &[f64],
56 nu: &[f64],
57) -> Vec<Vec<f64>> {
58 let n = x.len();
59 let m = lam.len();
60 let p = nu.len();
61 let dim = n + m + p;
62
63 let mut jac = vec![vec![0.0; dim]; dim];
64
65 for i in 0..n {
67 for j in 0..n {
68 jac[i][j] = if i < q.len() && j < q[i].len() {
69 q[i][j]
70 } else {
71 0.0
72 };
73 }
74 }
75
76 for j in 0..m {
81 for i in 0..n {
82 let g_val = if j < g.len() && i < g[j].len() {
83 g[j][i]
84 } else {
85 0.0
86 };
87 jac[i][n + j] = g_val;
88 }
89 }
90
91 for j in 0..p {
93 for i in 0..n {
94 let a_val = if j < a.len() && i < a[j].len() {
95 a[j][i]
96 } else {
97 0.0
98 };
99 jac[i][n + m + j] = a_val;
100 }
101 }
102
103 for i in 0..m {
105 let li = lam[i];
106 for j in 0..n {
107 let g_val = if i < g.len() && j < g[i].len() {
108 g[i][j]
109 } else {
110 0.0
111 };
112 jac[n + i][j] = li * g_val;
113 }
114 }
115
116 for i in 0..m {
119 let mut gx_i = 0.0;
120 if i < g.len() {
121 for j in 0..n.min(g[i].len()) {
122 gx_i += g[i][j] * x[j];
123 }
124 }
125 jac[n + i][n + i] = gx_i; }
130
131 for i in 0..p {
133 for j in 0..n {
134 let a_val = if i < a.len() && j < a[i].len() {
135 a[i][j]
136 } else {
137 0.0
138 };
139 jac[n + m + i][j] = a_val;
140 }
141 }
142
143 jac
145}
146
147pub fn adjust_complementarity_diagonal(jac: &mut [Vec<f64>], h: &[f64], n: usize) {
151 for (i, &h_i) in h.iter().enumerate() {
152 jac[n + i][n + i] -= h_i;
153 }
154}
155
156pub fn solve_implicit_system(mat: &[Vec<f64>], rhs: &[f64]) -> OptimizeResult<Vec<f64>> {
168 let n = rhs.len();
169 if mat.len() != n {
170 return Err(OptimizeError::InvalidInput(format!(
171 "KKT matrix rows ({}) != rhs length ({})",
172 mat.len(),
173 n
174 )));
175 }
176
177 let mut aug: Vec<Vec<f64>> = mat
179 .iter()
180 .enumerate()
181 .map(|(i, row)| {
182 let mut r = row.clone();
183 r.push(rhs[i]);
184 r
185 })
186 .collect();
187
188 for col in 0..n {
190 let mut max_val = aug[col][col].abs();
192 let mut max_row = col;
193 for row in (col + 1)..n {
194 let v = aug[row][col].abs();
195 if v > max_val {
196 max_val = v;
197 max_row = row;
198 }
199 }
200
201 if max_val < 1e-30 {
202 return Err(OptimizeError::ComputationError(
203 "Singular KKT matrix in implicit differentiation".to_string(),
204 ));
205 }
206
207 if max_row != col {
208 aug.swap(col, max_row);
209 }
210
211 let pivot = aug[col][col];
212 for row in (col + 1)..n {
213 let factor = aug[row][col] / pivot;
214 for j in col..=n {
215 let val = aug[col][j];
216 aug[row][j] -= factor * val;
217 }
218 }
219 }
220
221 let mut solution = vec![0.0; n];
223 for i in (0..n).rev() {
224 let mut sum = aug[i][n];
225 for j in (i + 1)..n {
226 sum -= aug[i][j] * solution[j];
227 }
228 let diag = aug[i][i];
229 if diag.abs() < 1e-30 {
230 return Err(OptimizeError::ComputationError(
231 "Zero diagonal in back substitution".to_string(),
232 ));
233 }
234 solution[i] = sum / diag;
235 }
236
237 Ok(solution)
238}
239
240pub fn solve_implicit_system_multi(
243 mat: &[Vec<f64>],
244 rhs_cols: &[Vec<f64>],
245) -> OptimizeResult<Vec<Vec<f64>>> {
246 rhs_cols
247 .iter()
248 .map(|rhs| solve_implicit_system(mat, rhs))
249 .collect()
250}
251
252pub fn identify_active_constraints(g: &[Vec<f64>], h: &[f64], x: &[f64], tol: f64) -> Vec<usize> {
261 let m = h.len();
262 let n = x.len();
263 let mut active = Vec::new();
264
265 for i in 0..m {
266 let mut gx_i = 0.0;
267 if i < g.len() {
268 for j in 0..n.min(g[i].len()) {
269 gx_i += g[i][j] * x[j];
270 }
271 }
272 let slack = h[i] - gx_i; if slack.abs() <= tol {
274 active.push(i);
275 }
276 }
277
278 active
279}
280
281pub fn extract_active_constraints(
283 g: &[Vec<f64>],
284 h: &[f64],
285 active: &[usize],
286) -> (Vec<Vec<f64>>, Vec<f64>) {
287 let g_active: Vec<Vec<f64>> = active.iter().filter_map(|&i| g.get(i).cloned()).collect();
288 let h_active: Vec<f64> = active.iter().filter_map(|&i| h.get(i).copied()).collect();
289 (g_active, h_active)
290}
291
292pub fn compute_full_implicit_gradient(
318 q: &[Vec<f64>],
319 g: &[Vec<f64>],
320 h: &[f64],
321 a: &[Vec<f64>],
322 x: &[f64],
323 lam: &[f64],
324 nu: &[f64],
325 dl_dx: &[f64],
326) -> OptimizeResult<super::types::ImplicitGradient> {
327 let n = x.len();
328 let m = lam.len();
329 let p = nu.len();
330 let dim = n + m + p;
331
332 let mut kkt = compute_kkt_jacobian(q, g, a, x, lam, nu);
334 adjust_complementarity_diagonal(&mut kkt, h, n);
335
336 let mut kkt_t = vec![vec![0.0; dim]; dim];
338 for i in 0..dim {
339 for j in 0..dim {
340 kkt_t[i][j] = kkt[j][i];
341 }
342 }
343
344 let mut rhs = vec![0.0; dim];
346 for i in 0..n {
347 rhs[i] = -dl_dx[i];
348 }
349
350 let dz = solve_implicit_system(&kkt_t, &rhs)?;
352
353 let dx = &dz[..n];
355 let dlam = &dz[n..n + m];
356 let dnu = &dz[n + m..];
357
358 let dl_dc = dx.to_vec();
361
362 let dl_dh: Vec<f64> = dlam.iter().map(|&v| -v).collect();
366
367 let dl_db: Vec<f64> = dnu.iter().map(|&v| -v).collect();
369
370 let mut dl_dq = vec![vec![0.0; n]; n];
372 for i in 0..n {
373 for j in 0..n {
374 dl_dq[i][j] = 0.5 * (dx[i] * x[j] + dx[j] * x[i]);
377 }
378 }
379
380 let mut dl_dg = vec![vec![0.0; n]; m];
382 for i in 0..m {
383 for j in 0..n {
384 dl_dg[i][j] = dx[j] * lam[i] + dlam[i] * lam[i] * x[j];
387 }
388 }
389
390 let mut dl_da = vec![vec![0.0; n]; p];
392 for i in 0..p {
393 for j in 0..n {
394 dl_da[i][j] = dx[j] * nu[i] + dnu[i] * x[j];
397 }
398 }
399
400 Ok(super::types::ImplicitGradient {
401 dl_dq: Some(dl_dq),
402 dl_dc,
403 dl_dg: Some(dl_dg),
404 dl_dh,
405 dl_da: if p > 0 { Some(dl_da) } else { None },
406 dl_db,
407 })
408}
409
410pub fn compute_active_set_implicit_gradient(
416 q: &[Vec<f64>],
417 g: &[Vec<f64>],
418 h: &[f64],
419 a: &[Vec<f64>],
420 x: &[f64],
421 lam: &[f64],
422 nu: &[f64],
423 dl_dx: &[f64],
424 active_tol: f64,
425) -> OptimizeResult<super::types::ImplicitGradient> {
426 let m = lam.len();
427
428 let active = identify_active_constraints(g, h, x, active_tol);
430 let (g_active, h_active) = extract_active_constraints(g, h, &active);
431 let lam_active: Vec<f64> = active
432 .iter()
433 .filter_map(|&i| if i < m { Some(lam[i]) } else { None })
434 .collect();
435
436 let grad =
438 compute_full_implicit_gradient(q, &g_active, &h_active, a, x, &lam_active, nu, dl_dx)?;
439
440 let m_full = lam.len();
442 let n = x.len();
443
444 let mut dl_dh_full = vec![0.0; m_full];
445 for (idx, &ai) in active.iter().enumerate() {
446 if ai < m_full && idx < grad.dl_dh.len() {
447 dl_dh_full[ai] = grad.dl_dh[idx];
448 }
449 }
450
451 let dl_dg_full = if let Some(ref dg) = grad.dl_dg {
452 let mut full = vec![vec![0.0; n]; m_full];
453 for (idx, &ai) in active.iter().enumerate() {
454 if ai < m_full && idx < dg.len() {
455 full[ai] = dg[idx].clone();
456 }
457 }
458 Some(full)
459 } else {
460 None
461 };
462
463 Ok(super::types::ImplicitGradient {
464 dl_dq: grad.dl_dq,
465 dl_dc: grad.dl_dc,
466 dl_dg: dl_dg_full,
467 dl_dh: dl_dh_full,
468 dl_da: grad.dl_da,
469 dl_db: grad.dl_db,
470 })
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_kkt_jacobian_structure_2x2() {
479 let q = vec![vec![2.0, 0.0], vec![0.0, 2.0]];
482 let g = vec![vec![1.0, 1.0]];
483 let a: Vec<Vec<f64>> = vec![];
484 let x = vec![0.25, 0.25];
485 let lam = vec![0.5];
486 let nu: Vec<f64> = vec![];
487
488 let jac = compute_kkt_jacobian(&q, &g, &a, &x, &lam, &nu);
489
490 assert_eq!(jac.len(), 3);
492 assert_eq!(jac[0].len(), 3);
493
494 assert!((jac[0][0] - 2.0).abs() < 1e-12);
496 assert!((jac[1][1] - 2.0).abs() < 1e-12);
497
498 assert!((jac[0][2] - 1.0).abs() < 1e-12);
500 assert!((jac[1][2] - 1.0).abs() < 1e-12);
501
502 assert!((jac[2][0] - 0.5).abs() < 1e-12);
504 assert!((jac[2][1] - 0.5).abs() < 1e-12);
505 }
506
507 #[test]
508 fn test_active_constraint_identification() {
509 let g = vec![vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 1.0]];
510 let h = vec![1.0, 2.0, 1.5];
511 let x = vec![1.0, 0.5]; let active = identify_active_constraints(&g, &h, &x, 1e-6);
514 assert_eq!(active, vec![0, 2]);
515 }
516
517 #[test]
518 fn test_solve_implicit_system_simple() {
519 let mat = vec![vec![2.0, 1.0], vec![1.0, 3.0]];
521 let rhs = vec![5.0, 7.0];
522 let sol = solve_implicit_system(&mat, &rhs).expect("solve failed");
523 assert!((sol[0] - 1.6).abs() < 1e-10);
524 assert!((sol[1] - 1.8).abs() < 1e-10);
525 }
526
527 #[test]
528 fn test_solve_singular_matrix() {
529 let mat = vec![vec![1.0, 2.0], vec![2.0, 4.0]];
530 let rhs = vec![3.0, 6.0];
531 let result = solve_implicit_system(&mat, &rhs);
532 assert!(result.is_err());
533 }
534
535 #[test]
536 fn test_extract_active_constraints() {
537 let g = vec![vec![1.0], vec![2.0], vec![3.0]];
538 let h = vec![10.0, 20.0, 30.0];
539 let active = vec![0, 2];
540
541 let (ga, ha) = extract_active_constraints(&g, &h, &active);
542 assert_eq!(ga.len(), 2);
543 assert!((ga[0][0] - 1.0).abs() < 1e-12);
544 assert!((ga[1][0] - 3.0).abs() < 1e-12);
545 assert!((ha[0] - 10.0).abs() < 1e-12);
546 assert!((ha[1] - 30.0).abs() < 1e-12);
547 }
548
549 #[test]
550 fn test_empty_constraints() {
551 let q = vec![vec![2.0, 0.0], vec![0.0, 2.0]];
552 let g: Vec<Vec<f64>> = vec![];
553 let a: Vec<Vec<f64>> = vec![];
554 let x = vec![1.0, 2.0];
555 let lam: Vec<f64> = vec![];
556 let nu: Vec<f64> = vec![];
557
558 let jac = compute_kkt_jacobian(&q, &g, &a, &x, &lam, &nu);
559 assert_eq!(jac.len(), 2);
560 assert!((jac[0][0] - 2.0).abs() < 1e-12);
561 }
562}