1use crate::models::algebraic::{IntegerVariable, 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<i64>,
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<i64>,
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<i64>;
52
53 fn target_problem(&self) -> &ILP<i64> {
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 if crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?
62 .value
63 .is_none()
64 {
65 return Err(crate::rules::ExtractionError::invalid(
66 "target ILP assignment is infeasible",
67 ));
68 }
69
70 Ok(target_solution[..self.num_candidates]
71 .iter()
72 .map(|&value| value == 1)
73 .collect())
74 }
75}
76
77#[reduction(
78 transform = upper_bound {
79 num_vars = "num_potential_edges + 2 * num_vertices * (num_vertices + 1) * (num_edges + num_potential_edges)",
80 num_constraints = "1 + num_vertices * (num_vertices + 1) * (2 * num_edges + 4 * num_potential_edges + num_vertices)",
81 },
82 unavailable = {
83 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
84 }
85)]
86impl ReduceTo<ILP<i64>> for BiconnectivityAugmentation<SimpleGraph, i64> {
87 type Result = ReductionBiconnAugToILP;
88
89 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
90 let n = self.num_vertices();
91 let p = self.num_potential_edges();
92
93 let base_edges = self.graph().edges();
94 let m = base_edges.len();
95
96 let (candidate_start, num_vars) = ReductionBiconnAugToILP::dimensions(n, m, p)?;
99 let f_idx = |q: usize, t: usize, i: usize, eta: usize| -> usize {
100 p + ((q * n + t) * m + i) * 2 + eta
101 };
102 let g_idx = |q: usize, t: usize, j: usize, eta: usize| -> usize {
103 candidate_start + ((q * n + t) * p + j) * 2 + eta
104 };
105 let mut constraints = Vec::new();
106
107 let budget_terms: Vec<(usize, i64)> = self
109 .potential_weights()
110 .iter()
111 .enumerate()
112 .map(|(candidate, &(_, _, weight))| (candidate, weight))
113 .collect();
114 constraints.push(LinearConstraint::le(budget_terms, *self.budget()));
115
116 for q in 0..=n {
118 let root = if q != 0 { 0 } else { 1 };
119
120 for t in 0..n {
121 if t == q || t == root {
123 for i in 0..m {
124 for eta in 0..2 {
125 constraints
126 .push(LinearConstraint::eq(vec![(f_idx(q, t, i, eta), 1)], 0));
127 }
128 }
129 for j in 0..p {
130 for eta in 0..2 {
131 constraints
132 .push(LinearConstraint::eq(vec![(g_idx(q, t, j, eta), 1)], 0));
133 }
134 }
135 continue;
136 }
137
138 for (i, &(u, v)) in base_edges.iter().enumerate() {
140 if u == q || v == q {
141 for eta in 0..2 {
142 constraints
143 .push(LinearConstraint::eq(vec![(f_idx(q, t, i, eta), 1)], 0));
144 }
145 }
146 }
147 for (j, &(sj, tj, _)) in self.potential_weights().iter().enumerate() {
148 if sj == q || tj == q {
149 for eta in 0..2 {
150 constraints
151 .push(LinearConstraint::eq(vec![(g_idx(q, t, j, eta), 1)], 0));
152 }
153 }
154 }
155
156 for j in 0..p {
158 let &(sj, tj, _) = &self.potential_weights()[j];
159 if sj == q || tj == q {
160 continue; }
162 for eta in 0..2 {
163 constraints.push(LinearConstraint::le(
164 vec![(g_idx(q, t, j, eta), 1), (j, -1)],
165 0,
166 ));
167 }
168 }
169
170 for v in 0..n {
172 if v == q {
173 continue;
174 }
175 let mut terms: Vec<(usize, i64)> = Vec::new();
176
177 for (i, &(u_e, v_e)) in base_edges.iter().enumerate() {
179 if u_e == q || v_e == q {
180 continue;
181 }
182 if u_e == v {
184 terms.push((f_idx(q, t, i, 0), 1)); terms.push((f_idx(q, t, i, 1), -1)); }
187 if v_e == v {
188 terms.push((f_idx(q, t, i, 0), -1)); terms.push((f_idx(q, t, i, 1), 1)); }
191 }
192
193 for (j, &(sj, tj, _)) in self.potential_weights().iter().enumerate() {
195 if sj == q || tj == q {
196 continue;
197 }
198 if sj == v {
200 terms.push((g_idx(q, t, j, 0), 1));
201 terms.push((g_idx(q, t, j, 1), -1));
202 }
203 if tj == v {
204 terms.push((g_idx(q, t, j, 0), -1));
205 terms.push((g_idx(q, t, j, 1), 1));
206 }
207 }
208
209 let rhs = if v == root {
210 1
211 } else if v == t {
212 -1
213 } else {
214 0
215 };
216 constraints.push(LinearConstraint::eq(terms, rhs));
217 }
218 }
219 }
220
221 let target = ILP::with_variables(
222 vec![IntegerVariable::binary(); num_vars],
223 constraints,
224 vec![],
225 ObjectiveSense::Minimize,
226 )
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<i64>>::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<i64>>(
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;