problemreductions/rules/
partitionintopathsoflength2_ilp.rs1use 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#[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 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#[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 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 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 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 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 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;