1use crate::models::algebraic::{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>,
37 num_vertices: usize,
38}
39
40impl ReductionResult for ReductionMMCToILP {
41 type Source = MinMaxMulticenter<SimpleGraph, i64>;
42 type Target = ILP<i64>;
43
44 fn target_problem(&self) -> &ILP<i64> {
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) -> Vec<Option<i64>> {
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; 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 + len;
111 let should_update = match dist[v] {
112 None => true,
113 Some(current) => candidate < current,
114 };
115 if should_update {
116 dist[v] = Some(candidate);
117 }
118 }
119 }
120
121 dist
122}
123
124#[reduction(
125 transform = exact {
126 num_vars = "num_vertices + num_vertices^2 + 1",
127 num_constraints = "2 * num_vertices^2 + 3 * num_vertices + 2",
128 },
129 unavailable = {
130 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
131 }
132)]
133impl ReduceTo<ILP<i64>> for MinMaxMulticenter<SimpleGraph, i64> {
134 type Result = ReductionMMCToILP;
135
136 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
137 let n = self.num_vertices();
138 let k = Self::exact_i64(self.k(), "encoding the number of centers")?;
139 let vertex_weights = self.vertex_weights();
140 let edge_lengths = self.edge_lengths();
141
142 let all_dist: Vec<Vec<Option<i64>>> = (0..n)
144 .map(|s| weighted_distances_mmc(self.graph(), edge_lengths, s, n))
145 .collect();
146
147 let x_var = |j: usize| j;
149 let y_var = |i: usize, j: usize| n + i * n + j;
150 let z_var = n + n * n;
151
152 let num_vars = n + n * n + 1;
153 let mut constraints = Vec::with_capacity(2 * n * n + 3 * n + 2);
154
155 let center_terms: Vec<(usize, i64)> = (0..n).map(|j| (x_var(j), 1)).collect();
157 constraints.push(LinearConstraint::eq(center_terms, k));
158
159 for i in 0..n {
161 let terms: Vec<(usize, i64)> = (0..n).map(|j| (y_var(i, j), 1)).collect();
162 constraints.push(LinearConstraint::eq(terms, 1));
163 }
164
165 for (i, distances) in all_dist.iter().enumerate() {
168 for (j, distance) in distances.iter().enumerate() {
169 if distance.is_some() {
170 constraints.push(LinearConstraint::le(
171 vec![(y_var(i, j), 1), (x_var(j), -1)],
172 0,
173 ));
174 } else {
175 constraints.push(LinearConstraint::eq(vec![(y_var(i, j), 1)], 0));
176 }
177 }
178 }
179
180 for j in 0..n {
182 constraints.push(LinearConstraint::le(vec![(x_var(j), 1)], 1));
183 }
184 for i in 0..n {
185 for j in 0..n {
186 constraints.push(LinearConstraint::le(vec![(y_var(i, j), 1)], 1));
187 }
188 }
189
190 let weighted_distances: Vec<Vec<Option<i64>>> = all_dist
193 .iter()
194 .enumerate()
195 .map(|(i, row)| {
196 row.iter()
197 .map(|distance| {
198 distance
199 .map(|distance| {
200 vertex_weights[i].checked_mul(distance).ok_or_else(|| {
201 crate::rules::ReductionError::integer_overflow::<
202 MinMaxMulticenter<SimpleGraph, i64>,
203 ILP<i64>,
204 >(
205 "multiplying a vertex weight by a shortest-path distance"
206 )
207 })
208 })
209 .transpose()
210 })
211 .collect::<Result<Vec<_>, _>>()
212 })
213 .collect::<Result<_, crate::rules::ReductionError>>()?;
214 let z_upper = weighted_distances
215 .iter()
216 .flatten()
217 .filter_map(|distance| *distance)
218 .fold(0_i64, i64::max);
219 constraints.push(LinearConstraint::le(vec![(z_var, 1)], z_upper));
220
221 for (i, distances) in weighted_distances.iter().enumerate() {
223 let mut terms: Vec<(usize, i64)> = distances
224 .iter()
225 .enumerate()
226 .filter_map(|(j, distance)| distance.map(|distance| (y_var(i, j), distance)))
227 .collect();
228 terms.push((z_var, -1));
229 constraints.push(LinearConstraint::le(terms, 0));
230 }
231
232 let objective = vec![(z_var, 1)];
234
235 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
236 .map_err(Self::target_construction)?;
237 Ok(ReductionMMCToILP {
238 target,
239 num_vertices: n,
240 })
241 }
242}
243
244#[cfg(feature = "example-db")]
245pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
246 vec![crate::example_db::specs::RuleExampleSpec {
247 id: "minmaxmulticenter_to_ilp",
248 build: || {
249 let source = MinMaxMulticenter::new(
252 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
253 vec![1i64; 3],
254 vec![1i64; 2],
255 1,
256 );
257 crate::example_db::specs::rule_example_via_ilp::<_, i64>(source)
258 },
259 }]
260}
261
262#[cfg(test)]
263#[path = "../unit_tests/rules/minmaxmulticenter_ilp.rs"]
264mod tests;