1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::graph::StrongConnectivityAugmentation;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11
12#[derive(Debug, Clone)]
13pub struct ReductionSCAToILP {
14 target: ILP<i32>,
15 num_candidates: usize,
16}
17
18impl ReductionResult for ReductionSCAToILP {
19 type Source = StrongConnectivityAugmentation<i32>;
20 type Target = ILP<i32>;
21
22 fn target_problem(&self) -> &ILP<i32> {
23 &self.target
24 }
25
26 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
27 target_solution[..self.num_candidates].to_vec()
28 }
29}
30
31#[reduction(
32 overhead = {
33 num_vars = "num_potential_arcs + 2 * num_vertices * (num_arcs + num_potential_arcs)",
34 num_constraints = "1 + 2 * num_vertices * num_potential_arcs + 2 * num_vertices * num_vertices",
35 }
36)]
37impl ReduceTo<ILP<i32>> for StrongConnectivityAugmentation<i32> {
38 type Result = ReductionSCAToILP;
39
40 fn reduce_to(&self) -> Self::Result {
41 let n = self.num_vertices();
42 let p = self.num_potential_arcs();
43
44 let base_arcs = self.graph().arcs();
45 let m = base_arcs.len();
46 let root = 0;
47
48 let num_vars = p + 2 * n * (m + p);
55 let f_base = |t: usize, i: usize| -> usize { p + t * m + i };
56 let f_cand = |t: usize, j: usize| -> usize { p + n * m + t * p + j };
57 let g_base = |t: usize, i: usize| -> usize { p + n * (m + p) + t * m + i };
58 let g_cand = |t: usize, j: usize| -> usize { p + n * (2 * m + p) + t * p + j };
59
60 let mut constraints = Vec::new();
61
62 for j in 0..p {
64 constraints.push(LinearConstraint::le(vec![(j, 1.0)], 1.0));
65 }
66
67 let budget_terms: Vec<(usize, f64)> = self
69 .candidate_arcs()
70 .iter()
71 .enumerate()
72 .map(|(j, &(_, _, w))| (j, w as f64))
73 .collect();
74 constraints.push(LinearConstraint::le(budget_terms, *self.bound() as f64));
75
76 for t in 0..n {
77 if t == root {
78 for i in 0..m {
80 constraints.push(LinearConstraint::eq(vec![(f_base(t, i), 1.0)], 0.0));
81 constraints.push(LinearConstraint::eq(vec![(g_base(t, i), 1.0)], 0.0));
82 }
83 for j in 0..p {
84 constraints.push(LinearConstraint::eq(vec![(f_cand(t, j), 1.0)], 0.0));
85 constraints.push(LinearConstraint::eq(vec![(g_cand(t, j), 1.0)], 0.0));
86 }
87 continue;
88 }
89
90 for j in 0..p {
92 constraints.push(LinearConstraint::le(
93 vec![(f_cand(t, j), 1.0), (j, -1.0)],
94 0.0,
95 ));
96 constraints.push(LinearConstraint::le(
97 vec![(g_cand(t, j), 1.0), (j, -1.0)],
98 0.0,
99 ));
100 }
101
102 for v in 0..n {
104 let mut terms: Vec<(usize, f64)> = Vec::new();
105
106 for (i, &(u_a, v_a)) in base_arcs.iter().enumerate() {
108 if u_a == v {
109 terms.push((f_base(t, i), 1.0)); }
111 if v_a == v {
112 terms.push((f_base(t, i), -1.0)); }
114 }
115
116 for (j, &(sj, tj, _)) in self.candidate_arcs().iter().enumerate() {
118 if sj == v {
119 terms.push((f_cand(t, j), 1.0)); }
121 if tj == v {
122 terms.push((f_cand(t, j), -1.0)); }
124 }
125
126 let rhs = if v == root {
127 1.0
128 } else if v == t {
129 -1.0
130 } else {
131 0.0
132 };
133 constraints.push(LinearConstraint::eq(terms, rhs));
134 }
135
136 for v in 0..n {
138 let mut terms: Vec<(usize, f64)> = Vec::new();
139
140 for (i, &(u_a, v_a)) in base_arcs.iter().enumerate() {
142 if u_a == v {
143 terms.push((g_base(t, i), 1.0));
144 }
145 if v_a == v {
146 terms.push((g_base(t, i), -1.0));
147 }
148 }
149
150 for (j, &(sj, tj, _)) in self.candidate_arcs().iter().enumerate() {
152 if sj == v {
153 terms.push((g_cand(t, j), 1.0));
154 }
155 if tj == v {
156 terms.push((g_cand(t, j), -1.0));
157 }
158 }
159
160 let rhs = if v == t {
161 1.0 } else if v == root {
163 -1.0 } else {
165 0.0
166 };
167 constraints.push(LinearConstraint::eq(terms, rhs));
168 }
169 }
170
171 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize);
172 ReductionSCAToILP {
173 target,
174 num_candidates: p,
175 }
176 }
177}
178
179#[cfg(feature = "example-db")]
180pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
181 use crate::export::SolutionPair;
182 use crate::topology::DirectedGraph;
183 vec![crate::example_db::specs::RuleExampleSpec {
184 id: "strongconnectivityaugmentation_to_ilp",
185 build: || {
186 let source = StrongConnectivityAugmentation::new(
188 DirectedGraph::new(3, vec![(0, 1), (1, 2)]),
189 vec![(2, 0, 1), (1, 0, 2)],
190 2,
191 );
192 let reduction: ReductionSCAToILP =
193 crate::rules::ReduceTo::<ILP<i32>>::reduce_to(&source);
194 let ilp_sol = crate::solvers::ILPSolver::new()
195 .solve(reduction.target_problem())
196 .expect("ILP should be solvable");
197 let extracted = reduction.extract_solution(&ilp_sol);
198 crate::example_db::specs::rule_example_with_witness::<_, ILP<i32>>(
199 source,
200 SolutionPair {
201 source_config: extracted,
202 target_config: ilp_sol,
203 },
204 )
205 },
206 }]
207}
208
209#[cfg(test)]
210#[path = "../unit_tests/rules/strongconnectivityaugmentation_ilp.rs"]
211mod tests;