1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
28use crate::models::graph::MinMaxMulticenter;
29use crate::reduction;
30use crate::rules::traits::{ReduceTo, ReductionResult};
31use crate::topology::{Graph, SimpleGraph};
32
33#[derive(Debug, Clone)]
35pub struct ReductionMMCToILP {
36 target: ILP<i64, i64, Bounded>,
37 num_vertices: usize,
38}
39
40impl ReductionResult for ReductionMMCToILP {
41 type Source = MinMaxMulticenter<SimpleGraph, i64>;
42 type Target = ILP<i64, i64, Bounded>;
43
44 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
45 &self.target
46 }
47
48 fn extract_solution(
49 &self,
50 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
51 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
52 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
53
54 Ok(target_solution[..self.num_vertices]
55 .iter()
56 .map(|&value| value == 1)
57 .collect())
58 }
59}
60
61fn weighted_distances_mmc(
65 graph: &SimpleGraph,
66 edge_lengths: &[i64],
67 source: usize,
68 n: usize,
69) -> Result<Vec<Option<i64>>, crate::rules::ReductionError> {
70 let mut adj: Vec<Vec<(usize, i64)>> = vec![Vec::new(); n];
71 for (idx, &(u, v)) in graph.edges().iter().enumerate() {
72 let len = edge_lengths[idx];
73 adj[u].push((v, len));
74 adj[v].push((u, len));
75 }
76
77 let mut dist = vec![None::<i64>; n];
78 let mut visited = vec![false; n];
79 dist[source] = Some(0);
80
81 for _ in 0..n {
82 let mut next = None;
83 for vertex in 0..n {
84 if visited[vertex] {
85 continue;
86 }
87 let Some(dv) = dist[vertex] else {
88 continue;
89 };
90 match next {
91 None => next = Some(vertex),
92 Some(prev) => {
93 if dv < dist[prev].expect("selected vertex must have a distance") {
94 next = Some(vertex);
95 }
96 }
97 }
98 }
99
100 let Some(u) = next else {
101 break;
102 };
103 visited[u] = true;
104 let du = dist[u].expect("selected vertex must have a distance");
105
106 for &(v, len) in &adj[u] {
107 if visited[v] {
108 continue;
109 }
110 let candidate = du.checked_add(len).ok_or_else(|| {
111 crate::rules::ReductionError::integer_overflow::<
112 MinMaxMulticenter<SimpleGraph, i64>,
113 ILP<i64, i64, Bounded>,
114 >("adding shortest-path lengths")
115 })?;
116 let should_update = match dist[v] {
117 None => true,
118 Some(current) => candidate < current,
119 };
120 if should_update {
121 dist[v] = Some(candidate);
122 }
123 }
124 }
125
126 Ok(dist)
127}
128
129#[reduction(transform = {
130 exact {
131 num_vars = "num_vertices + num_vertices^2 + 1",
132 num_constraints = "2 * num_vertices^2 + 3 * num_vertices + 2",
133 },
134 upper_bound {
135 max_constraint_magnitude_bits = "2 * max_numeric_magnitude_bits + num_vertices",
136 num_nonzeros = "(num_vertices + num_vertices^2 + 1) * (2 * num_vertices^2 + 3 * num_vertices + 2)",
137 },
138})]
139impl ReduceTo<ILP<i64, i64, Bounded>> for MinMaxMulticenter<SimpleGraph, i64> {
140 type Result = ReductionMMCToILP;
141
142 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
143 let n = self.num_vertices();
144 let k = Self::exact_i64(self.k(), "encoding the number of centers")?;
145 let vertex_weights = self.vertex_weights();
146 let edge_lengths = self.edge_lengths();
147
148 let all_dist: Vec<Vec<Option<i64>>> = (0..n)
150 .map(|s| weighted_distances_mmc(self.graph(), edge_lengths, s, n))
151 .collect::<Result<_, _>>()?;
152
153 let x_var = |j: usize| j;
155 let y_var = |i: usize, j: usize| n + i * n + j;
156 let z_var = n + n * n;
157
158 let num_vars = n + n * n + 1;
159 let mut constraints = Vec::with_capacity(2 * n * n + 3 * n + 2);
160
161 let center_terms: Vec<(usize, i64)> = (0..n).map(|j| (x_var(j), 1)).collect();
163 constraints.push(LinearConstraint::eq(center_terms, k));
164
165 for i in 0..n {
167 let terms: Vec<(usize, i64)> = (0..n).map(|j| (y_var(i, j), 1)).collect();
168 constraints.push(LinearConstraint::eq(terms, 1));
169 }
170
171 for (i, distances) in all_dist.iter().enumerate() {
174 for (j, distance) in distances.iter().enumerate() {
175 if distance.is_some() {
176 constraints.push(LinearConstraint::le(
177 vec![(y_var(i, j), 1), (x_var(j), -1)],
178 0,
179 ));
180 } else {
181 constraints.push(LinearConstraint::eq(vec![(y_var(i, j), 1)], 0));
182 }
183 }
184 }
185
186 for j in 0..n {
188 constraints.push(LinearConstraint::le(vec![(x_var(j), 1)], 1));
189 }
190 for i in 0..n {
191 for j in 0..n {
192 constraints.push(LinearConstraint::le(vec![(y_var(i, j), 1)], 1));
193 }
194 }
195
196 let weighted_distances: Vec<Vec<Option<i64>>> = all_dist
199 .iter()
200 .enumerate()
201 .map(|(i, row)| {
202 row.iter()
203 .map(|distance| {
204 distance
205 .map(|distance| {
206 vertex_weights[i].checked_mul(distance).ok_or_else(|| {
207 crate::rules::ReductionError::integer_overflow::<
208 MinMaxMulticenter<SimpleGraph, i64>,
209 ILP<i64, i64, Bounded>,
210 >(
211 "multiplying a vertex weight by a shortest-path distance"
212 )
213 })
214 })
215 .transpose()
216 })
217 .collect::<Result<Vec<_>, _>>()
218 })
219 .collect::<Result<_, crate::rules::ReductionError>>()?;
220 let z_upper = weighted_distances
221 .iter()
222 .flatten()
223 .filter_map(|distance| *distance)
224 .fold(0_i64, i64::max);
225 constraints.push(LinearConstraint::le(vec![(z_var, 1)], z_upper));
226
227 for (i, distances) in weighted_distances.iter().enumerate() {
229 let mut terms: Vec<(usize, i64)> = distances
230 .iter()
231 .enumerate()
232 .filter_map(|(j, distance)| distance.map(|distance| (y_var(i, j), distance)))
233 .collect();
234 terms.push((z_var, -1));
235 constraints.push(LinearConstraint::le(terms, 0));
236 }
237
238 let objective = vec![(z_var, 1)];
240
241 let mut variables = vec![IntegerVariable::binary(); num_vars];
242 variables[z_var] =
243 IntegerVariable::new(Some(0), Some(z_upper)).map_err(Self::target_construction)?;
244
245 let target =
246 ILP::with_variables(variables, constraints, objective, ObjectiveSense::Minimize)
247 .map_err(Self::target_construction)?;
248 Ok(ReductionMMCToILP {
249 target,
250 num_vertices: n,
251 })
252 }
253}
254
255#[cfg(feature = "example-db")]
256pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
257 vec![crate::example_db::specs::RuleExampleSpec {
258 id: "minmaxmulticenter_to_ilp",
259 build: || {
260 let source = MinMaxMulticenter::new(
263 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
264 vec![1i64; 3],
265 vec![1i64; 2],
266 1,
267 );
268 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
269 },
270 }]
271}
272
273#[cfg(test)]
274#[path = "../unit_tests/rules/minmaxmulticenter_ilp.rs"]
275mod tests;