Skip to main content

SquareComponents

Struct SquareComponents 

Source
pub struct SquareComponents {
    pub components: Vec<SquareComponent>,
}
Expand description

Decomposition of the square sub-graph into connected components.

Fields§

§components: Vec<SquareComponent>

One entry per component, sorted by smallest contained equality-row index for determinism.

Implementations§

Source§

impl SquareComponents

Source

pub fn of_square_part( inc: &EqualityIncidence, _m: &BipartiteMatching, part: &DulmageMendelsohnPartition, ) -> Self

Decompose the square sub-graph of part into connected components. Edges considered are exactly those of inc whose row AND column are both in DMPart::Square.

§Example
use pounce_presolve::incidence::{EqualityIncidence, ProbeView};
use pounce_presolve::matching::hopcroft_karp;
use pounce_presolve::dulmage_mendelsohn::DulmageMendelsohnPartition;
use pounce_presolve::components::SquareComponents;

// 4×4 with a 2-block and two singletons.
let p = ProbeView {
    n_vars: 4,
    m_rows: 4,
    jac_irow: &[0, 0, 1, 1, 2, 3],
    jac_jcol: &[0, 1, 0, 1, 2, 3],
    jac_values: None,
    g_l: &[0.0; 4],
    g_u: &[0.0; 4],
    linearity: None,
    one_based: false,
    eq_tol: 1e-12,
    excluded_vars: None,
    excluded_rows: None,
};
let inc = EqualityIncidence::from_probe(&p);
let m = hopcroft_karp(&inc);
let dm = DulmageMendelsohnPartition::from_matching(&inc, &m);
let c = SquareComponents::of_square_part(&inc, &m, &dm);
assert_eq!(c.components.len(), 3);
Examples found in repository?
examples/dm_partition.rs (line 60)
28fn main() {
29    let irow: [i32; 9] = [0, 1, 1, 2, 2, 3, 3, 4, 4];
30    let jcol: [i32; 9] = [0, 1, 2, 1, 2, 3, 4, 3, 4];
31    let g = [0.0; 5];
32
33    let probe = ProbeView {
34        n_vars: 5,
35        m_rows: 5,
36        jac_irow: &irow,
37        jac_jcol: &jcol,
38        jac_values: None,
39        g_l: &g,
40        g_u: &g,
41        linearity: None,
42        one_based: false,
43        eq_tol: 1e-12,
44        excluded_vars: None,
45        excluded_rows: None,
46    };
47    let inc = EqualityIncidence::from_probe(&probe);
48    let m = hopcroft_karp(&inc);
49    let dm = DulmageMendelsohnPartition::from_matching(&inc, &m);
50
51    println!("dm_partition example — PR 3 of pounce#53");
52    println!("matching size:    {}", m.size);
53    println!("over rows: {:?}, cols: {:?}", dm.over_rows, dm.over_cols);
54    println!(
55        "square rows: {:?}, cols: {:?}",
56        dm.square_rows, dm.square_cols
57    );
58    println!("under rows: {:?}, cols: {:?}", dm.under_rows, dm.under_cols);
59
60    let comps = SquareComponents::of_square_part(&inc, &m, &dm);
61    println!("square components: {}", comps.components.len());
62    for (i, c) in comps.components.iter().enumerate() {
63        println!("  component {i}: rows={:?}, cols={:?}", c.eq_rows, c.cols);
64    }
65
66    assert_eq!(comps.components.len(), 3, "expected 3 independent blocks");
67}
More examples
Hide additional examples
examples/btf_chain.rs (line 51)
29fn main() {
30    let irow: [i32; 7] = [0, 1, 1, 2, 2, 3, 3];
31    let jcol: [i32; 7] = [0, 0, 1, 1, 2, 2, 3];
32    let g = [0.0; 4];
33
34    let probe = ProbeView {
35        n_vars: 4,
36        m_rows: 4,
37        jac_irow: &irow,
38        jac_jcol: &jcol,
39        jac_values: None,
40        g_l: &g,
41        g_u: &g,
42        linearity: None,
43        one_based: false,
44        eq_tol: 1e-12,
45        excluded_vars: None,
46        excluded_rows: None,
47    };
48    let inc = EqualityIncidence::from_probe(&probe);
49    let m = hopcroft_karp(&inc);
50    let dm = DulmageMendelsohnPartition::from_matching(&inc, &m);
51    let comps = SquareComponents::of_square_part(&inc, &m, &dm);
52
53    println!("btf_chain example — PR 4 of pounce#53");
54    println!("matching size: {}", m.size);
55    println!("square components: {}", comps.components.len());
56    assert_eq!(
57        comps.components.len(),
58        1,
59        "expected one connected component"
60    );
61
62    let btf = BlockTriangularForm::of_component(&inc, &m, &comps.components[0]);
63    println!("BTF blocks (elimination order):");
64    for (i, b) in btf.blocks.iter().enumerate() {
65        println!("  block {i}: rows={:?}, cols={:?}", b.eq_rows, b.cols);
66    }
67    assert_eq!(btf.blocks.len(), 4, "chain expands to 4 size-1 blocks");
68    for (i, b) in btf.blocks.iter().enumerate() {
69        assert_eq!(b.eq_rows, vec![i]);
70        assert_eq!(b.cols, vec![i]);
71    }
72}
examples/pipeline_demo.rs (line 129)
68fn main() {
69    let n_vars = 4; // a, b, c, d
70    let n_rows = 4;
71    // Jacobian sparsity (row-major).
72    let jac_pattern = [
73        (0, 0), // c0: a
74        (1, 1),
75        (1, 2), // c1: b + c
76        (2, 1),
77        (2, 2), // c2: b - c
78        (3, 0),
79        (3, 1),
80        (3, 3), // c3: a + b + d
81    ];
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]; // all equalities
86
87    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    // ----- Stage 1: incidence + matching -----
106    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    // ----- Stage 2: DM partition -----
116    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    // ----- Stage 3: components -----
129    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    // ----- Stage 4: BTF -----
139    println!("\n[Stage 4] BTF (per component):");
140    let ineq = InequalityIncidence::from_probe(&probe);
141
142    // Walk each component's BTF, solve each block, build the frame.
143    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    // Full A and b for this problem, used both for block extraction
148    // and the final KKT check.
149    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            // Build a small linear system for this block, splicing in
168            // the values of already-solved variables.
169            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            // Solve.
185            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            // Record the block's solution.
195            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    // ----- Stage 5: lift via the reduction frame -----
207    println!("\n[Stage 5] After block solves:");
208    println!("   x_running (a, b, c, d) = {:?}", x_running);
209    // For this demo we declare ALL fixed vars and rows as one
210    // composite frame — in the real PR 8 orchestrator, each
211    // elimination layer becomes its own frame on the stack.
212    // Sort by variable index for the frame's contract.
213    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    // For this problem BTF decomposed every row into a block — the
238    // reduced shape is 0×0 and no IPM step is needed. The lift is
239    // trivial: x_full = the values the block solves produced.
240    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    // Sanity: should match (1, 2, 3, 1).
246    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    // ----- Stage 7: multiplier recovery -----
256    // Objective: f = (a-1)² + (b-2)² + (c-3)² + d²
257    // ∇f at the optimum (1, 2, 3, 1): (0, 0, 0, 2).
258    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    // No kept rows → reduced λ is empty.
267    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    // Build full Jacobian for the recovery LU.
273    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    // Final KKT residual at every variable.
288    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}

Trait Implementations§

Source§

impl Clone for SquareComponents

Source§

fn clone(&self) -> SquareComponents

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SquareComponents

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SquareComponents

Source§

fn default() -> SquareComponents

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more