1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
7use crate::models::graph::DisjointConnectingPaths;
8use crate::reduction;
9use crate::rules::traits::{ReduceTo, ReductionResult};
10use crate::topology::SimpleGraph;
11use std::collections::VecDeque;
12
13#[derive(Debug, Clone)]
22pub struct ReductionDCPToILP {
23 target: ILP<bool>,
24 edges: Vec<(usize, usize)>,
26 num_vertices: usize,
27 terminal_pairs: Vec<(usize, usize)>,
28 num_edge_vars_per_commodity: usize,
29}
30
31impl ReductionResult for ReductionDCPToILP {
32 type Source = DisjointConnectingPaths<SimpleGraph>;
33 type Target = ILP<bool>;
34
35 fn target_problem(&self) -> &ILP<bool> {
36 &self.target
37 }
38
39 fn extract_solution(
40 &self,
41 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
42 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
43 crate::rules::traits::validate_target_witness(
44 self.target_problem(),
45 target_solution,
46 |value| value.value.is_some(),
47 "target ILP assignment is infeasible",
48 )?;
49
50 let mut result = vec![false; self.edges.len()];
51 for (k, &(source, sink)) in self.terminal_pairs.iter().enumerate() {
52 let offset = k * self.num_edge_vars_per_commodity;
53 let mut adjacency = vec![Vec::new(); self.num_vertices];
54 for (edge, &(u, v)) in self.edges.iter().enumerate() {
55 if target_solution[offset + 2 * edge] == 1 {
56 adjacency[u].push((v, edge));
57 }
58 if target_solution[offset + 2 * edge + 1] == 1 {
59 adjacency[v].push((u, edge));
60 }
61 }
62 let mut visited = vec![false; self.num_vertices];
63 let mut predecessor = vec![None; self.num_vertices];
64 let mut queue = VecDeque::from([source]);
65 visited[source] = true;
66 while let Some(u) = queue.pop_front() {
67 if u == sink {
68 break;
69 }
70 for &(v, edge) in &adjacency[u] {
71 if !visited[v] {
72 visited[v] = true;
73 predecessor[v] = Some((u, edge));
74 queue.push_back(v);
75 }
76 }
77 }
78 let mut vertex = sink;
79 while vertex != source {
80 let (previous, edge) = predecessor[vertex].ok_or_else(|| {
81 crate::rules::ExtractionError::invalid(
82 "commodity flow does not connect its terminal pair",
83 )
84 })?;
85 result[edge] = true;
86 vertex = previous;
87 }
88 }
89 Ok(result)
90 }
91}
92
93#[crate::aggregate_reduction(ilp_feasibility)]
94impl crate::rules::AggregateReductionResult for ReductionDCPToILP {}
95
96#[reduction(transform = {
97 exact {
98 num_vars = "num_pairs * 2 * num_edges",
99 num_constraints = "num_pairs * num_vertices + num_vertices",
100 },
101 upper_bound {
102 max_constraint_magnitude_bits = "1",
103 num_nonzeros = "(num_pairs * 2 * num_edges) * (num_pairs * num_vertices + num_vertices)",
104 },
105})]
106impl ReduceTo<ILP<bool>> for DisjointConnectingPaths<SimpleGraph> {
107 type Result = ReductionDCPToILP;
108
109 #[allow(clippy::needless_range_loop)]
110 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
111 let edges = self.ordered_edges();
112 let m = edges.len();
113 let n = self.num_vertices();
114 let k_count = self.num_pairs();
115
116 let overflow = |operation| {
117 crate::rules::ReductionError::integer_overflow::<Self, ILP<bool>>(operation)
118 };
119 let num_flow_vars_per_k = m
120 .checked_mul(2)
121 .ok_or_else(|| overflow("computing the flow-variable stride"))?;
122 let num_vars = k_count
123 .checked_mul(num_flow_vars_per_k)
124 .ok_or_else(|| overflow("computing the flow-variable count"))?;
125
126 let flow_var =
127 |k: usize, e: usize, dir: usize| -> usize { k * num_flow_vars_per_k + 2 * e + dir };
128
129 let mut constraints = Vec::new();
130
131 let mut vertex_edges: Vec<Vec<usize>> = vec![Vec::new(); n];
133 for (e, &(u, v)) in edges.iter().enumerate() {
134 if u == v {
135 continue;
136 }
137 vertex_edges[u].push(e);
138 vertex_edges[v].push(e);
139 }
140
141 let mut is_source = vec![false; n];
142 for &(source, _) in self.terminal_pairs() {
143 is_source[source] = true;
144 }
145
146 for (k, &(s_k, t_k)) in self.terminal_pairs().iter().enumerate() {
147 for vertex in 0..n {
149 let mut terms = Vec::new();
150 for &e in &vertex_edges[vertex] {
151 let (eu, _ev) = edges[e];
152 if vertex == eu {
153 terms.push((flow_var(k, e, 0), 1));
155 terms.push((flow_var(k, e, 1), -1));
156 } else {
157 terms.push((flow_var(k, e, 1), 1));
159 terms.push((flow_var(k, e, 0), -1));
160 }
161 }
162
163 let demand = if vertex == s_k {
164 1
165 } else if vertex == t_k {
166 -1
167 } else {
168 0
169 };
170 constraints.push(LinearConstraint::eq(terms, demand));
171 }
172 }
173
174 for v in 0..n {
177 let mut terms = Vec::new();
178 for k in 0..k_count {
179 for &e in &vertex_edges[v] {
180 let (eu, _ev) = edges[e];
181 if v == eu {
182 terms.push((flow_var(k, e, 1), 1));
183 } else {
184 terms.push((flow_var(k, e, 0), 1));
185 }
186 }
187 }
188 constraints.push(LinearConstraint::le(terms, 1 - i64::from(is_source[v])));
189 }
190
191 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
192 .map_err(Self::target_construction)?;
193
194 Ok(ReductionDCPToILP {
195 target,
196 edges,
197 num_vertices: n,
198 terminal_pairs: self.terminal_pairs().to_vec(),
199 num_edge_vars_per_commodity: num_flow_vars_per_k,
200 })
201 }
202}
203
204#[cfg(feature = "example-db")]
205pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
206 vec![crate::example_db::specs::RuleExampleSpec {
207 id: "disjointconnectingpaths_to_ilp",
208 build: || {
209 let source = DisjointConnectingPaths::new(
211 SimpleGraph::new(6, vec![(0, 1), (1, 2), (3, 4), (4, 5)]),
212 vec![(0, 2), (3, 5)],
213 );
214 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
215 },
216 }]
217}
218
219#[cfg(test)]
220#[path = "../unit_tests/rules/disjointconnectingpaths_ilp.rs"]
221mod tests;