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<i32>,
16 num_candidates: usize,
17}
18
19impl ReductionResult for ReductionBiconnAugToILP {
20 type Source = BiconnectivityAugmentation<SimpleGraph, i32>;
21 type Target = ILP<i32>;
22
23 fn target_problem(&self) -> &ILP<i32> {
24 &self.target
25 }
26
27 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
28 target_solution[..self.num_candidates].to_vec()
29 }
30}
31
32#[reduction(
33 overhead = {
34 num_vars = "num_potential_edges + 2 * num_vertices * num_vertices * (num_edges + num_potential_edges)",
35 num_constraints = "1 + 2 * num_vertices * num_vertices * num_potential_edges + num_vertices * num_vertices * num_vertices",
36 }
37)]
38impl ReduceTo<ILP<i32>> for BiconnectivityAugmentation<SimpleGraph, i32> {
39 type Result = ReductionBiconnAugToILP;
40
41 fn reduce_to(&self) -> Self::Result {
42 let n = self.num_vertices();
43 let p = self.num_potential_edges();
44
45 if n <= 1 {
47 let target = ILP::new(p, vec![], vec![], ObjectiveSense::Minimize);
48 return ReductionBiconnAugToILP {
49 target,
50 num_candidates: p,
51 };
52 }
53
54 let base_edges = self.graph().edges();
55 let m = base_edges.len();
56
57 let num_vars = p + 2 * n * n * (m + p);
62 let f_idx = |q: usize, t: usize, i: usize, eta: usize| -> usize {
63 p + ((q * n + t) * m + i) * 2 + eta
64 };
65 let g_idx = |q: usize, t: usize, j: usize, eta: usize| -> usize {
66 p + 2 * m * n * n + ((q * n + t) * p + j) * 2 + eta
67 };
68
69 let mut constraints = Vec::new();
70
71 for j in 0..p {
73 constraints.push(LinearConstraint::le(vec![(j, 1.0)], 1.0));
74 }
75
76 let budget_terms: Vec<(usize, f64)> = self
78 .potential_weights()
79 .iter()
80 .enumerate()
81 .map(|(j, &(_, _, w))| (j, w as f64))
82 .collect();
83 constraints.push(LinearConstraint::le(budget_terms, *self.budget() as f64));
84
85 for q in 0..n {
87 let root = if q != 0 { 0 } else { 1 };
88
89 for t in 0..n {
90 if t == q || t == root {
92 for i in 0..m {
93 for eta in 0..2 {
94 constraints
95 .push(LinearConstraint::eq(vec![(f_idx(q, t, i, eta), 1.0)], 0.0));
96 }
97 }
98 for j in 0..p {
99 for eta in 0..2 {
100 constraints
101 .push(LinearConstraint::eq(vec![(g_idx(q, t, j, eta), 1.0)], 0.0));
102 }
103 }
104 continue;
105 }
106
107 for (i, &(u, v)) in base_edges.iter().enumerate() {
109 if u == q || v == q {
110 for eta in 0..2 {
111 constraints
112 .push(LinearConstraint::eq(vec![(f_idx(q, t, i, eta), 1.0)], 0.0));
113 }
114 }
115 }
116 for (j, &(sj, tj, _)) in self.potential_weights().iter().enumerate() {
117 if sj == q || tj == q {
118 for eta in 0..2 {
119 constraints
120 .push(LinearConstraint::eq(vec![(g_idx(q, t, j, eta), 1.0)], 0.0));
121 }
122 }
123 }
124
125 for j in 0..p {
127 let &(sj, tj, _) = &self.potential_weights()[j];
128 if sj == q || tj == q {
129 continue; }
131 for eta in 0..2 {
132 constraints.push(LinearConstraint::le(
133 vec![(g_idx(q, t, j, eta), 1.0), (j, -1.0)],
134 0.0,
135 ));
136 }
137 }
138
139 for v in 0..n {
141 if v == q {
142 continue;
143 }
144 let mut terms: Vec<(usize, f64)> = Vec::new();
145
146 for (i, &(u_e, v_e)) in base_edges.iter().enumerate() {
148 if u_e == q || v_e == q {
149 continue;
150 }
151 if u_e == v {
153 terms.push((f_idx(q, t, i, 0), 1.0)); terms.push((f_idx(q, t, i, 1), -1.0)); }
156 if v_e == v {
157 terms.push((f_idx(q, t, i, 0), -1.0)); terms.push((f_idx(q, t, i, 1), 1.0)); }
160 }
161
162 for (j, &(sj, tj, _)) in self.potential_weights().iter().enumerate() {
164 if sj == q || tj == q {
165 continue;
166 }
167 if sj == v {
169 terms.push((g_idx(q, t, j, 0), 1.0));
170 terms.push((g_idx(q, t, j, 1), -1.0));
171 }
172 if tj == v {
173 terms.push((g_idx(q, t, j, 0), -1.0));
174 terms.push((g_idx(q, t, j, 1), 1.0));
175 }
176 }
177
178 let rhs = if v == root {
179 1.0
180 } else if v == t {
181 -1.0
182 } else {
183 0.0
184 };
185 constraints.push(LinearConstraint::eq(terms, rhs));
186 }
187 }
188 }
189
190 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize);
191 ReductionBiconnAugToILP {
192 target,
193 num_candidates: p,
194 }
195 }
196}
197
198#[cfg(feature = "example-db")]
199pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
200 use crate::export::SolutionPair;
201 vec![crate::example_db::specs::RuleExampleSpec {
202 id: "biconnectivityaugmentation_to_ilp",
203 build: || {
204 let source = BiconnectivityAugmentation::new(
206 SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]),
207 vec![(0, 2, 1), (0, 3, 2), (1, 3, 1)],
208 3,
209 );
210 let reduction: ReductionBiconnAugToILP =
211 crate::rules::ReduceTo::<ILP<i32>>::reduce_to(&source);
212 let ilp_sol = crate::solvers::ILPSolver::new()
213 .solve(reduction.target_problem())
214 .expect("ILP should be solvable");
215 let extracted = reduction.extract_solution(&ilp_sol);
216 crate::example_db::specs::rule_example_with_witness::<_, ILP<i32>>(
217 source,
218 SolutionPair {
219 source_config: extracted,
220 target_config: ilp_sol,
221 },
222 )
223 },
224 }]
225}
226
227#[cfg(test)]
228#[path = "../unit_tests/rules/biconnectivityaugmentation_ilp.rs"]
229mod tests;