1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::graph::BiconnectivityAugmentation;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11use crate::topology::{Graph, SimpleGraph};
12
13#[derive(Debug, Clone)]
14pub struct ReductionBiconnAugToILP {
15 target: ILP<bool>,
16 num_candidates: usize,
17}
18
19impl ReductionBiconnAugToILP {
20 fn dimensions(
21 n: usize,
22 m: usize,
23 p: usize,
24 ) -> Result<(usize, usize), crate::rules::ReductionError> {
25 let overflow = || {
26 crate::rules::ReductionError::integer_overflow::<
27 BiconnectivityAugmentation<SimpleGraph, i64>,
28 ILP<bool>,
29 >("computing connectivity flow variable counts")
30 };
31 let commodities = n
32 .checked_add(1)
33 .and_then(|x| x.checked_mul(n))
34 .ok_or_else(overflow)?;
35 let base_variables = commodities
36 .checked_mul(m)
37 .and_then(|x| x.checked_mul(2))
38 .ok_or_else(overflow)?;
39 let candidate_start = p.checked_add(base_variables).ok_or_else(overflow)?;
40 let num_variables = commodities
41 .checked_mul(p)
42 .and_then(|x| x.checked_mul(2))
43 .and_then(|x| x.checked_add(candidate_start))
44 .ok_or_else(overflow)?;
45 Ok((candidate_start, num_variables))
46 }
47}
48
49impl ReductionResult for ReductionBiconnAugToILP {
50 type Source = BiconnectivityAugmentation<SimpleGraph, i64>;
51 type Target = ILP<bool>;
52
53 fn target_problem(&self) -> &ILP<bool> {
54 &self.target
55 }
56
57 fn extract_solution(
58 &self,
59 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
60 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
61 crate::rules::traits::validate_target_witness(
62 self.target_problem(),
63 target_solution,
64 |value| value.value.is_some(),
65 "target ILP assignment is infeasible",
66 )?;
67
68 Ok(target_solution[..self.num_candidates]
69 .iter()
70 .map(|&value| value == 1)
71 .collect())
72 }
73}
74
75#[crate::aggregate_reduction(ilp_feasibility)]
76impl crate::rules::AggregateReductionResult for ReductionBiconnAugToILP {}
77
78#[reduction(transform = {
82 exact {
83 max_constraint_magnitude_bits = "max_numeric_magnitude_bits",
84 },
85 upper_bound {
86 num_vars = "num_potential_edges + 2 * num_vertices * (num_vertices + 1) * (num_edges + num_potential_edges)",
87 num_constraints = "1 + num_vertices * (num_vertices + 1) * (2 * num_edges + 4 * num_potential_edges + num_vertices)",
88 num_nonzeros = "num_potential_edges + num_vertices * (num_vertices + 1) * (4 * num_edges + 8 * num_potential_edges)",
89 },
90})]
91impl ReduceTo<ILP<bool>> for BiconnectivityAugmentation<SimpleGraph, i64> {
92 type Result = ReductionBiconnAugToILP;
93
94 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
95 let n = self.num_vertices();
96 let p = self.num_potential_edges();
97
98 let base_edges = self.graph().edges();
99 let m = base_edges.len();
100
101 let (candidate_start, num_vars) = ReductionBiconnAugToILP::dimensions(n, m, p)?;
104 let f_idx = |q: usize, t: usize, i: usize, eta: usize| -> usize {
105 p + ((q * n + t) * m + i) * 2 + eta
106 };
107 let g_idx = |q: usize, t: usize, j: usize, eta: usize| -> usize {
108 candidate_start + ((q * n + t) * p + j) * 2 + eta
109 };
110 let mut constraints = Vec::new();
111
112 let budget_terms: Vec<(usize, i64)> = self
114 .potential_weights()
115 .iter()
116 .enumerate()
117 .map(|(candidate, &(_, _, weight))| (candidate, weight))
118 .collect();
119 constraints.push(LinearConstraint::le(budget_terms, *self.budget()));
120
121 for q in 0..=n {
123 let root = if q != 0 { 0 } else { 1 };
124
125 for t in 0..n {
126 if t == q || t == root {
128 for i in 0..m {
129 for eta in 0..2 {
130 constraints
131 .push(LinearConstraint::eq(vec![(f_idx(q, t, i, eta), 1)], 0));
132 }
133 }
134 for j in 0..p {
135 for eta in 0..2 {
136 constraints
137 .push(LinearConstraint::eq(vec![(g_idx(q, t, j, eta), 1)], 0));
138 }
139 }
140 continue;
141 }
142
143 for (i, &(u, v)) in base_edges.iter().enumerate() {
145 if u == q || v == q {
146 for eta in 0..2 {
147 constraints
148 .push(LinearConstraint::eq(vec![(f_idx(q, t, i, eta), 1)], 0));
149 }
150 }
151 }
152 for (j, &(sj, tj, _)) in self.potential_weights().iter().enumerate() {
153 if sj == q || tj == q {
154 for eta in 0..2 {
155 constraints
156 .push(LinearConstraint::eq(vec![(g_idx(q, t, j, eta), 1)], 0));
157 }
158 }
159 }
160
161 for j in 0..p {
163 let &(sj, tj, _) = &self.potential_weights()[j];
164 if sj == q || tj == q {
165 continue; }
167 for eta in 0..2 {
168 constraints.push(LinearConstraint::le(
169 vec![(g_idx(q, t, j, eta), 1), (j, -1)],
170 0,
171 ));
172 }
173 }
174
175 for v in 0..n {
177 if v == q {
178 continue;
179 }
180 let mut terms: Vec<(usize, i64)> = Vec::new();
181
182 for (i, &(u_e, v_e)) in base_edges.iter().enumerate() {
184 if u_e == q || v_e == q {
185 continue;
186 }
187 if u_e == v {
189 terms.push((f_idx(q, t, i, 0), 1)); terms.push((f_idx(q, t, i, 1), -1)); }
192 if v_e == v {
193 terms.push((f_idx(q, t, i, 0), -1)); terms.push((f_idx(q, t, i, 1), 1)); }
196 }
197
198 for (j, &(sj, tj, _)) in self.potential_weights().iter().enumerate() {
200 if sj == q || tj == q {
201 continue;
202 }
203 if sj == v {
205 terms.push((g_idx(q, t, j, 0), 1));
206 terms.push((g_idx(q, t, j, 1), -1));
207 }
208 if tj == v {
209 terms.push((g_idx(q, t, j, 0), -1));
210 terms.push((g_idx(q, t, j, 1), 1));
211 }
212 }
213
214 let rhs = if v == root {
215 1
216 } else if v == t {
217 -1
218 } else {
219 0
220 };
221 constraints.push(LinearConstraint::eq(terms, rhs));
222 }
223 }
224 }
225
226 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
227 .map_err(Self::target_construction)?;
228 Ok(ReductionBiconnAugToILP {
229 target,
230 num_candidates: p,
231 })
232 }
233}
234
235#[cfg(feature = "example-db")]
236pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
237 use crate::export::SolutionPair;
238 vec![crate::example_db::specs::RuleExampleSpec {
239 id: "biconnectivityaugmentation_to_ilp",
240 build: || {
241 let source = BiconnectivityAugmentation::new(
243 SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]),
244 vec![(0, 2, 1), (0, 3, 2), (1, 3, 1)],
245 3,
246 );
247 let reduction: ReductionBiconnAugToILP =
248 crate::rules::ReduceTo::<ILP<bool>>::reduce_to(&source)
249 .expect("reduction should succeed");
250 let ilp_sol = crate::solvers::ILPSolver::new()
251 .solve(reduction.target_problem())
252 .expect("ILP should be solvable");
253 let extracted = reduction.extract_solution(&ilp_sol).unwrap();
254 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
255 source,
256 SolutionPair {
257 source_config: serde_json::json!(extracted),
258 target_config: serde_json::json!(ilp_sol),
259 },
260 )
261 },
262 }]
263}
264
265#[cfg(test)]
266#[path = "../unit_tests/rules/biconnectivityaugmentation_ilp.rs"]
267mod tests;