1#![allow(clippy::expect_used, clippy::unwrap_used)]
34
35use pounce_common::types::Number;
36use pounce_presolve::matching::hopcroft_karp;
37use pounce_presolve::{
38 classify_block, AuxiliaryCouplingClass, BlockEquations, BlockSolveOptions, BlockSolver,
39 BlockTriangularForm, DampedNewtonSolver, DulmageMendelsohnPartition, EqualityIncidence,
40 InequalityIncidence, ProbeView, ReductionFrame, SquareComponents,
41};
42
43struct LinearBlockEqs {
44 a: Vec<Number>,
45 b: Vec<Number>,
46 n: usize,
47}
48impl BlockEquations for LinearBlockEqs {
49 fn dim(&self) -> usize {
50 self.n
51 }
52 fn eval(&mut self, x: &[Number], f: &mut [Number]) -> bool {
53 for i in 0..self.n {
54 let mut s = -self.b[i];
55 for j in 0..self.n {
56 s += self.a[i * self.n + j] * x[j];
57 }
58 f[i] = s;
59 }
60 true
61 }
62 fn jacobian(&mut self, _x: &[Number], j: &mut [Number]) -> bool {
63 j.copy_from_slice(&self.a);
64 true
65 }
66}
67
68fn main() {
69 let n_vars = 4; let n_rows = 4;
71 let jac_pattern = [
73 (0, 0), (1, 1),
75 (1, 2), (2, 1),
77 (2, 2), (3, 0),
79 (3, 1),
80 (3, 3), ];
82 let jac_irow: Vec<i32> = jac_pattern.iter().map(|(r, _)| *r as i32).collect();
83 let jac_jcol: Vec<i32> = jac_pattern.iter().map(|(_, c)| *c as i32).collect();
84 let g_l = [1.0, 5.0, -1.0, 4.0];
85 let g_u = [1.0, 5.0, -1.0, 4.0]; let probe = ProbeView {
88 n_vars,
89 m_rows: n_rows,
90 jac_irow: &jac_irow,
91 jac_jcol: &jac_jcol,
92 jac_values: None,
93 g_l: &g_l,
94 g_u: &g_u,
95 linearity: None,
96 one_based: false,
97 eq_tol: 1e-12,
98 excluded_vars: None,
99 excluded_rows: None,
100 };
101
102 println!("pipeline_demo — PRs 2-7 of pounce#53");
103 println!("======================================");
104
105 let inc = EqualityIncidence::from_probe(&probe);
107 println!(
108 "\n[Stage 1] EqualityIncidence: {} eq rows, {} vars",
109 inc.n_eq_rows(),
110 inc.n_vars
111 );
112 let m = hopcroft_karp(&inc);
113 println!("[Stage 1] Matching: size = {}", m.size);
114
115 let dm = DulmageMendelsohnPartition::from_matching(&inc, &m);
117 println!("\n[Stage 2] DM partition:");
118 println!(" over rows={:?}, cols={:?}", dm.over_rows, dm.over_cols);
119 println!(
120 " square rows={:?}, cols={:?}",
121 dm.square_rows, dm.square_cols
122 );
123 println!(
124 " under rows={:?}, cols={:?}",
125 dm.under_rows, dm.under_cols
126 );
127
128 let comps = SquareComponents::of_square_part(&inc, &m, &dm);
130 println!(
131 "\n[Stage 3] Components: {} square block(s)",
132 comps.components.len()
133 );
134 for (i, c) in comps.components.iter().enumerate() {
135 println!(" component {i}: rows={:?}, cols={:?}", c.eq_rows, c.cols);
136 }
137
138 println!("\n[Stage 4] BTF (per component):");
140 let ineq = InequalityIncidence::from_probe(&probe);
141
142 let mut all_fixed_vars: Vec<usize> = Vec::new();
144 let mut all_fixed_values: Vec<Number> = Vec::new();
145 let mut all_dropped_rows: Vec<usize> = Vec::new();
146 let mut x_running = vec![0.0; n_vars];
147 let a_full = [
150 [1.0, 0.0, 0.0, 0.0],
151 [0.0, 1.0, 1.0, 0.0],
152 [0.0, 1.0, -1.0, 0.0],
153 [1.0, 1.0, 0.0, 1.0],
154 ];
155 let b_full = [1.0, 5.0, -1.0, 4.0];
156
157 for comp in &comps.components {
158 let btf = BlockTriangularForm::of_component(&inc, &m, comp);
159 for (bi, block) in btf.blocks.iter().enumerate() {
160 let class = classify_block(block, &ineq, &Default::default());
161 println!(
162 " component cols={:?}, block {bi}: rows={:?}, cols={:?}, class={class:?}",
163 comp.cols, block.eq_rows, block.cols
164 );
165 assert_eq!(class, AuxiliaryCouplingClass::PureEquality);
166
167 let k = block.eq_rows.len();
170 let mut a_block = vec![0.0; k * k];
171 let mut b_block = vec![0.0; k];
172 for (ii, &r) in block.eq_rows.iter().enumerate() {
173 let mut rhs = b_full[r];
174 for j in 0..n_vars {
175 if let Some(jj) = block.cols.iter().position(|&c| c == j) {
176 a_block[ii * k + jj] = a_full[r][j];
177 } else {
178 rhs -= a_full[r][j] * x_running[j];
179 }
180 }
181 b_block[ii] = rhs;
182 }
183
184 let mut eqs = LinearBlockEqs {
186 a: a_block,
187 b: b_block,
188 n: k,
189 };
190 let mut solver = DampedNewtonSolver;
191 let opt = BlockSolveOptions::default();
192 let out = solver.solve(&vec![0.0; k], &mut eqs, &opt).unwrap();
193
194 for (ii, &c) in block.cols.iter().enumerate() {
196 x_running[c] = out.x[ii];
197 all_fixed_vars.push(c);
198 all_fixed_values.push(out.x[ii]);
199 }
200 for &r in &block.eq_rows {
201 all_dropped_rows.push(r);
202 }
203 }
204 }
205
206 println!("\n[Stage 5] After block solves:");
208 println!(" x_running (a, b, c, d) = {:?}", x_running);
209 let mut order: Vec<usize> = (0..all_fixed_vars.len()).collect();
214 order.sort_by_key(|&i| all_fixed_vars[i]);
215 let fixed_vars_sorted: Vec<usize> = order.iter().map(|&i| all_fixed_vars[i]).collect();
216 let fixed_values_sorted: Vec<Number> = order.iter().map(|&i| all_fixed_values[i]).collect();
217 let mut dropped_rows_sorted = all_dropped_rows.clone();
218 dropped_rows_sorted.sort_unstable();
219
220 let frame = ReductionFrame::new(
221 n_vars,
222 n_rows,
223 fixed_vars_sorted.clone(),
224 fixed_values_sorted,
225 dropped_rows_sorted.clone(),
226 );
227 println!(
228 " ReductionFrame: fixed_vars={:?}, dropped_rows={:?}",
229 frame.fixed_vars, frame.dropped_rows
230 );
231 println!(
232 " reduced shape: {} vars × {} rows",
233 frame.n_reduced_vars(),
234 frame.n_reduced_rows()
235 );
236
237 let x_reduced: Vec<Number> = Vec::new();
241 println!("\n[Stage 6] Reduced problem is empty (presolve solved it all).");
242 let x_full = frame.lift_x(&x_reduced);
243 println!(" lifted x_full = {:?}", x_full);
244
245 let expected = [1.0, 2.0, 3.0, 1.0];
247 let max_x_err = x_full
248 .iter()
249 .zip(expected.iter())
250 .map(|(a, b)| (a - b).abs())
251 .fold(0.0_f64, f64::max);
252 println!(" max |x - expected|: {:.3e}", max_x_err);
253 assert!(max_x_err < 1e-9);
254
255 let grad_f = [
259 2.0 * (x_full[0] - 1.0),
260 2.0 * (x_full[1] - 2.0),
261 2.0 * (x_full[2] - 3.0),
262 2.0 * x_full[3],
263 ];
264 println!("\n[Stage 7] ∇f at the optimum: {:?}", grad_f);
265
266 let lambda_reduced: Vec<Number> = Vec::new();
268 let mut lambda_full = frame.lift_lambda(&lambda_reduced);
269 println!(" reduced λ = {:?}", lambda_reduced);
270 println!(" lifted λ (pre-recovery) = {:?}", lambda_full);
271
272 let mut jac_full = vec![0.0; n_rows * n_vars];
274 for (r, row) in a_full.iter().enumerate() {
275 for (c, &v) in row.iter().enumerate() {
276 jac_full[r * n_vars + c] = v;
277 }
278 }
279 let lam_dropped = frame
280 .recover_dropped_multipliers(&grad_f, &jac_full, &lambda_full)
281 .unwrap();
282 for (idx, &r) in frame.dropped_rows.iter().enumerate() {
283 lambda_full[r] = lam_dropped[idx];
284 }
285 println!(" recovered λ (full) = {:?}", lambda_full);
286
287 println!("\n[Stage 8] Full-space KKT residual:");
289 let mut max_kkt: Number = 0.0;
290 for i in 0..n_vars {
291 let mut s = grad_f[i];
292 for r in 0..n_rows {
293 s -= jac_full[r * n_vars + i] * lambda_full[r];
294 }
295 println!(" ∂L/∂x[{i}] = {:.3e}", s);
296 max_kkt = max_kkt.max(s.abs());
297 }
298 println!(" max |KKT residual| = {:.3e}", max_kkt);
299 assert!(max_kkt < 1e-10);
300 println!("\n✓ Pipeline end-to-end correct.");
301}