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<bool>,
15 num_candidates: usize,
16}
17
18impl ReductionResult for ReductionSCAToILP {
19 type Source = StrongConnectivityAugmentation<i64>;
20 type Target = ILP<bool>;
21
22 fn target_problem(&self) -> &ILP<bool> {
23 &self.target
24 }
25
26 fn extract_solution(
27 &self,
28 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
29 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
30 crate::rules::traits::validate_target_witness(
31 self.target_problem(),
32 target_solution,
33 |value| value.value.is_some(),
34 "target ILP assignment is infeasible",
35 )?;
36
37 Ok(target_solution[..self.num_candidates]
38 .iter()
39 .map(|&value| value == 1)
40 .collect())
41 }
42}
43
44#[crate::aggregate_reduction(ilp_feasibility)]
45impl crate::rules::AggregateReductionResult for ReductionSCAToILP {}
46
47#[reduction(transform = {
51 exact {
52 max_constraint_magnitude_bits = "max_numeric_magnitude_bits",
53 },
54 upper_bound {
55 num_vars = "num_potential_arcs + 2 * num_vertices * (num_arcs + num_potential_arcs)",
56 num_constraints = "1 + num_potential_arcs + 2 * num_arcs + 2 * num_vertices * num_potential_arcs + 2 * num_vertices * num_vertices",
57 num_nonzeros = "2 * num_potential_arcs + num_vertices * (4 * num_arcs + 8 * num_potential_arcs)",
58 },
59})]
60impl ReduceTo<ILP<bool>> for StrongConnectivityAugmentation<i64> {
61 type Result = ReductionSCAToILP;
62
63 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
64 let n = self.num_vertices();
65 let p = self.num_potential_arcs();
66
67 let base_arcs = self.graph().arcs();
68 let m = base_arcs.len();
69 let root = 0;
70
71 let num_vars = p + 2 * n * (m + p);
78 let f_base = |t: usize, i: usize| -> usize { p + t * m + i };
79 let f_cand = |t: usize, j: usize| -> usize { p + n * m + t * p + j };
80 let g_base = |t: usize, i: usize| -> usize { p + n * (m + p) + t * m + i };
81 let g_cand = |t: usize, j: usize| -> usize { p + n * (2 * m + p) + t * p + j };
82
83 let mut constraints = Vec::new();
84
85 for j in 0..p {
87 constraints.push(LinearConstraint::le(vec![(j, 1)], 1));
88 }
89
90 let budget_terms: Vec<(usize, i64)> = self
92 .candidate_arcs()
93 .iter()
94 .enumerate()
95 .map(|(candidate, &(_, _, weight))| (candidate, weight))
96 .collect();
97 constraints.push(LinearConstraint::le(budget_terms, *self.bound()));
98
99 for t in 0..n {
100 if t == root {
101 for i in 0..m {
103 constraints.push(LinearConstraint::eq(vec![(f_base(t, i), 1)], 0));
104 constraints.push(LinearConstraint::eq(vec![(g_base(t, i), 1)], 0));
105 }
106 for j in 0..p {
107 constraints.push(LinearConstraint::eq(vec![(f_cand(t, j), 1)], 0));
108 constraints.push(LinearConstraint::eq(vec![(g_cand(t, j), 1)], 0));
109 }
110 continue;
111 }
112
113 for j in 0..p {
115 constraints.push(LinearConstraint::le(vec![(f_cand(t, j), 1), (j, -1)], 0));
116 constraints.push(LinearConstraint::le(vec![(g_cand(t, j), 1), (j, -1)], 0));
117 }
118
119 for v in 0..n {
121 let mut terms: Vec<(usize, i64)> = Vec::new();
122
123 for (i, &(u_a, v_a)) in base_arcs.iter().enumerate() {
125 if u_a == v {
126 terms.push((f_base(t, i), 1)); }
128 if v_a == v {
129 terms.push((f_base(t, i), -1)); }
131 }
132
133 for (j, &(sj, tj, _)) in self.candidate_arcs().iter().enumerate() {
135 if sj == v {
136 terms.push((f_cand(t, j), 1)); }
138 if tj == v {
139 terms.push((f_cand(t, j), -1)); }
141 }
142
143 let rhs = if v == root {
144 1
145 } else if v == t {
146 -1
147 } else {
148 0
149 };
150 constraints.push(LinearConstraint::eq(terms, rhs));
151 }
152
153 for v in 0..n {
155 let mut terms: Vec<(usize, i64)> = Vec::new();
156
157 for (i, &(u_a, v_a)) in base_arcs.iter().enumerate() {
159 if u_a == v {
160 terms.push((g_base(t, i), 1));
161 }
162 if v_a == v {
163 terms.push((g_base(t, i), -1));
164 }
165 }
166
167 for (j, &(sj, tj, _)) in self.candidate_arcs().iter().enumerate() {
169 if sj == v {
170 terms.push((g_cand(t, j), 1));
171 }
172 if tj == v {
173 terms.push((g_cand(t, j), -1));
174 }
175 }
176
177 let rhs = if v == t {
178 1 } else if v == root {
180 -1 } else {
182 0
183 };
184 constraints.push(LinearConstraint::eq(terms, rhs));
185 }
186 }
187
188 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
190 .map_err(Self::target_construction)?;
191 Ok(ReductionSCAToILP {
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 use crate::topology::DirectedGraph;
202 vec![crate::example_db::specs::RuleExampleSpec {
203 id: "strongconnectivityaugmentation_to_ilp",
204 build: || {
205 let source = StrongConnectivityAugmentation::new(
207 DirectedGraph::new(3, vec![(0, 1), (1, 2)]),
208 vec![(2, 0, 1), (1, 0, 2)],
209 2,
210 );
211 let reduction: ReductionSCAToILP =
212 crate::rules::ReduceTo::<ILP<bool>>::reduce_to(&source)
213 .expect("reduction should succeed");
214 let ilp_sol = crate::solvers::ILPSolver::new()
215 .solve(reduction.target_problem())
216 .expect("ILP should be solvable");
217 let extracted = reduction.extract_solution(&ilp_sol).unwrap();
218 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
219 source,
220 SolutionPair {
221 source_config: serde_json::json!(extracted),
222 target_config: serde_json::json!(ilp_sol),
223 },
224 )
225 },
226 }]
227}
228
229#[cfg(test)]
230#[path = "../unit_tests/rules/strongconnectivityaugmentation_ilp.rs"]
231mod tests;