Skip to main content

problemreductions/rules/
partitionintopathsoflength2_ilp.rs

1//! Reduction from PartitionIntoPathsOfLength2 to ILP (Integer Linear Programming).
2//!
3//! Each triple must contain at least 2 edges. We introduce product variables y_{e,g} = x_{u,g} * x_{v,g}
4//! for each edge (u,v) and group g, linearized with McCormick constraints:
5//!
6//! Variables:
7//! - x_{v,g}: binary, vertex v in group g (index: v * q + g)
8//! - y_{e,g}: binary product for edge e=(u,v) and group g (index: n*q + e * q + g)
9//!
10//! Constraints:
11//! - Σ_g x_{v,g} = 1 for each vertex v (assignment)
12//! - Σ_v x_{v,g} = 3 for each group g (size constraint)
13//! - For each edge e=(u,v) and group g (McCormick for y_{e,g} = x_{u,g} * x_{v,g}):
14//!   y_{e,g} ≤ x_{u,g}, y_{e,g} ≤ x_{v,g}, y_{e,g} ≥ x_{u,g} + x_{v,g} - 1
15//! - For each group g: Σ_e y_{e,g} ≥ 2 (at least 2 edges in group)
16//!
17//! Objective: Minimize 0 (feasibility)
18
19use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
20use crate::models::graph::PartitionIntoPathsOfLength2;
21use crate::reduction;
22use crate::rules::ilp_helpers::mccormick_product;
23use crate::rules::traits::{ReduceTo, ReductionResult};
24use crate::topology::{Graph, SimpleGraph};
25
26/// Result of reducing PartitionIntoPathsOfLength2 to ILP.
27///
28/// Variable layout:
29/// - x_{v,g} at index v * q + g  (v ∈ 0..n, g ∈ 0..q)
30/// - y_{e,g} at index n * q + e * q + g  (e ∈ 0..num_edges, g ∈ 0..q)
31#[derive(Debug, Clone)]
32pub struct ReductionPIPL2ToILP {
33    target: ILP<bool>,
34    num_vertices: usize,
35    num_groups: usize,
36}
37
38impl ReductionResult for ReductionPIPL2ToILP {
39    type Source = PartitionIntoPathsOfLength2<SimpleGraph>;
40    type Target = ILP<bool>;
41
42    fn target_problem(&self) -> &ILP<bool> {
43        &self.target
44    }
45
46    /// Extract solution: for each vertex v, find the unique group g where x_{v,g} = 1.
47    fn extract_solution(
48        &self,
49        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
50    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
51        crate::rules::traits::validate_target_witness(
52            self.target_problem(),
53            target_solution,
54            |value| value.value.is_some(),
55            "target ILP assignment is infeasible",
56        )?;
57
58        crate::rules::ilp_helpers::one_hot_decode_rows(
59            target_solution,
60            self.num_vertices,
61            self.num_groups,
62            0,
63        )
64    }
65}
66
67#[crate::aggregate_reduction(ilp_feasibility)]
68impl crate::rules::AggregateReductionResult for ReductionPIPL2ToILP {}
69
70// With q=floor(n/3) and e distinct non-loop edges: q(n+e) variables,
71// n+2q+3eq rows, and 2nq+8eq terms. Deduplication gives e<=num_edges.
72#[reduction(transform = upper_bound {
73    max_constraint_magnitude_bits = "2",
74    num_vars = "num_vertices * (num_vertices + num_edges) / 3",
75    num_constraints = "5 * num_vertices / 3 + num_edges * num_vertices",
76    num_nonzeros = "(2 * num_vertices^2 + 8 * num_edges * num_vertices) / 3",
77})]
78impl ReduceTo<ILP<bool>> for PartitionIntoPathsOfLength2<SimpleGraph> {
79    type Result = ReductionPIPL2ToILP;
80
81    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
82        let num_vertices = self.num_vertices();
83        let q = self.num_groups();
84        let edges: Vec<_> = self
85            .graph()
86            .edges()
87            .into_iter()
88            .filter(|&(u, v)| u != v)
89            .map(|(u, v)| (u.min(v), u.max(v)))
90            .collect::<std::collections::BTreeSet<_>>()
91            .into_iter()
92            .collect();
93        let num_edges = edges.len();
94        let num_vars = num_vertices * q + num_edges * q;
95
96        let mut constraints = Vec::new();
97
98        // Assignment constraints: for each vertex v, Σ_g x_{v,g} = 1
99        for v in 0..num_vertices {
100            let terms: Vec<(usize, i64)> = (0..q).map(|g| (v * q + g, 1)).collect();
101            constraints.push(LinearConstraint::eq(terms, 1));
102        }
103
104        // Group size constraints: for each group g, Σ_v x_{v,g} = 3
105        for g in 0..q {
106            let terms: Vec<(usize, i64)> = (0..num_vertices).map(|v| (v * q + g, 1)).collect();
107            constraints.push(LinearConstraint::eq(terms, 3));
108        }
109
110        // McCormick linearization: y_{e,g} = x_{u,g} * x_{v,g} for each edge e=(u,v) and group g
111        // y_{e,g} is at index num_vertices * q + e * q + g
112        for (e, &(u, v)) in edges.iter().enumerate() {
113            for g in 0..q {
114                let y = num_vertices * q + e * q + g;
115                let xu = u * q + g;
116                let xv = v * q + g;
117
118                constraints.extend(mccormick_product(y, xu, xv));
119            }
120        }
121
122        // At-least-2-edges constraint: for each group g, Σ_e y_{e,g} ≥ 2
123        for g in 0..q {
124            let terms: Vec<(usize, i64)> = (0..num_edges)
125                .map(|e| (num_vertices * q + e * q + g, 1))
126                .collect();
127            constraints.push(LinearConstraint::ge(terms, 2));
128        }
129
130        let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
131            .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
132
133        Ok(ReductionPIPL2ToILP {
134            target,
135            num_vertices,
136            num_groups: q,
137        })
138    }
139}
140
141#[cfg(feature = "example-db")]
142pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
143    vec![crate::example_db::specs::RuleExampleSpec {
144        id: "partitionintopathsoflength2_to_ilp",
145        build: || {
146            // Two P3 paths: 0-1-2 and 3-4-5
147            let source = PartitionIntoPathsOfLength2::new(SimpleGraph::new(
148                6,
149                vec![(0, 1), (1, 2), (3, 4), (4, 5)],
150            ));
151            crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
152        },
153    }]
154}
155
156#[cfg(test)]
157#[path = "../unit_tests/rules/partitionintopathsoflength2_ilp.rs"]
158mod tests;