1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
28use crate::models::graph::MinimumCapacitatedSpanningTree;
29use crate::reduction;
30use crate::rules::traits::{ReduceTo, ReductionResult};
31use crate::topology::{Graph, SimpleGraph};
32use crate::types::WeightElement;
33
34#[derive(Debug, Clone)]
36pub struct ReductionMinimumCapacitatedSpanningTreeToILP {
37 target: ILP<i64, i64, Bounded>,
38 num_edges: usize,
39}
40
41impl ReductionResult for ReductionMinimumCapacitatedSpanningTreeToILP {
42 type Source = MinimumCapacitatedSpanningTree<SimpleGraph, i64>;
43 type Target = ILP<i64, i64, Bounded>;
44
45 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
46 &self.target
47 }
48
49 fn extract_solution(
50 &self,
51 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
52 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
53 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
54
55 Ok({
56 target_solution[..self.num_edges]
58 .iter()
59 .map(|&value| value == 1)
60 .collect()
61 })
62 }
63}
64
65#[reduction(transform = {
68 exact {
69 num_vars = "5 * num_edges",
70 num_constraints = "5 * num_edges + 2 * num_vertices + 1",
71 },
72 upper_bound {
73 max_constraint_magnitude_bits = "max_requirement_bits + num_vertices",
74 num_nonzeros = "18 * num_edges",
75 },
76})]
77impl ReduceTo<ILP<i64, i64, Bounded>> for MinimumCapacitatedSpanningTree<SimpleGraph, i64> {
78 type Result = ReductionMinimumCapacitatedSpanningTreeToILP;
79
80 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
81 let n = self.num_vertices();
82 let m = self.num_edges();
83 let edges = self.graph().edges();
84 let root = self.root();
85 let requirements = self.requirements();
86 let cap = *self.capacity();
87
88 let num_vars = 5 * m;
89
90 let edge_var = |e: usize| e; let flow_var = |e: usize, dir: usize| m + 2 * e + dir; let connectivity_var = |e: usize, dir: usize| 3 * m + 2 * e + dir; let total_req = requirements
97 .iter()
98 .enumerate()
99 .filter(|(vertex, _)| *vertex != root)
100 .try_fold(0_i64, |total, (_, requirement)| {
101 total.checked_add(requirement.to_sum()).ok_or_else(|| {
102 crate::rules::ReductionError::integer_overflow::<
103 MinimumCapacitatedSpanningTree<SimpleGraph, i64>,
104 ILP<i64, i64, Bounded>,
105 >("summing vertex requirements")
106 })
107 })?;
108 let mut constraints = Vec::new();
109
110 let terms: Vec<(usize, i64)> = (0..m).map(|e| (edge_var(e), 1)).collect();
112 constraints.push(LinearConstraint::eq(
113 terms,
114 Self::exact_i64(n, "encoding the spanning-tree order")? - 1,
115 ));
116
117 for e in 0..m {
119 constraints.push(LinearConstraint::le(vec![(edge_var(e), 1)], 1));
120 }
121
122 for (vertex, req) in requirements.iter().enumerate() {
126 let mut terms = Vec::new();
127 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
128 if v == vertex {
131 terms.push((flow_var(edge_idx, 0), 1));
133 terms.push((flow_var(edge_idx, 1), -1));
135 }
136 if u == vertex {
137 terms.push((flow_var(edge_idx, 0), -1));
139 terms.push((flow_var(edge_idx, 1), 1));
141 }
142 }
143
144 let rhs = if vertex == root {
145 total_req
147 } else {
148 -req.to_sum()
151 };
152 constraints.push(LinearConstraint::eq(terms, rhs));
153 }
154
155 for edge_idx in 0..m {
157 constraints.push(LinearConstraint::le(
158 vec![
159 (flow_var(edge_idx, 0), 1),
160 (flow_var(edge_idx, 1), 1),
161 (edge_var(edge_idx), -total_req),
162 ],
163 0,
164 ));
165 }
166
167 for edge_idx in 0..m {
169 constraints.push(LinearConstraint::le(vec![(flow_var(edge_idx, 0), 1)], cap));
170 constraints.push(LinearConstraint::le(vec![(flow_var(edge_idx, 1), 1)], cap));
171 }
172
173 let connectivity_total = Self::exact_i64(n, "encoding connectivity flow")? - 1;
176 for vertex in 0..n {
177 let mut terms = Vec::new();
178 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
179 if v == vertex {
180 terms.push((connectivity_var(edge_idx, 0), 1));
181 terms.push((connectivity_var(edge_idx, 1), -1));
182 }
183 if u == vertex {
184 terms.push((connectivity_var(edge_idx, 0), -1));
185 terms.push((connectivity_var(edge_idx, 1), 1));
186 }
187 }
188 let rhs = if vertex == root {
189 connectivity_total
190 } else {
191 -1
192 };
193 constraints.push(LinearConstraint::eq(terms, rhs));
194 }
195 for edge_idx in 0..m {
196 constraints.push(LinearConstraint::le(
197 vec![
198 (connectivity_var(edge_idx, 0), 1),
199 (connectivity_var(edge_idx, 1), 1),
200 (edge_var(edge_idx), -connectivity_total),
201 ],
202 0,
203 ));
204 }
205
206 let objective: Vec<(usize, i64)> = self
208 .weights()
209 .iter()
210 .enumerate()
211 .map(|(edge_idx, weight)| (edge_var(edge_idx), weight.to_sum()))
212 .collect();
213
214 let mut variables = vec![IntegerVariable::binary(); num_vars];
215 variables[m..3 * m].fill(
216 IntegerVariable::new(Some(0), Some(cap.min(total_req).max(0)))
217 .map_err(Self::target_construction)?,
218 );
219 variables[3 * m..].fill(
220 IntegerVariable::new(Some(0), Some(connectivity_total))
221 .map_err(Self::target_construction)?,
222 );
223
224 let target =
225 ILP::with_variables(variables, constraints, objective, ObjectiveSense::Minimize)
226 .map_err(Self::target_construction)?;
227
228 Ok(ReductionMinimumCapacitatedSpanningTreeToILP {
229 target,
230 num_edges: m,
231 })
232 }
233}
234
235#[cfg(feature = "example-db")]
236pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
237 vec![crate::example_db::specs::RuleExampleSpec {
238 id: "minimumcapacitatedspanningtree_to_ilp",
239 build: || {
240 let source = MinimumCapacitatedSpanningTree::new(
241 SimpleGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)]),
242 vec![2, 3, 1, 1, 2], 0, vec![0, 1, 1, 1], 2, );
247 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
248 },
249 }]
250}
251
252#[cfg(test)]
253#[path = "../unit_tests/rules/minimumcapacitatedspanningtree_ilp.rs"]
254mod tests;