problemreductions/rules/
minimumsummulticenter_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
24use crate::models::graph::MinimumSumMulticenter;
25use crate::reduction;
26use crate::rules::traits::{ReduceTo, ReductionResult};
27use crate::topology::{Graph, SimpleGraph};
28
29#[derive(Debug, Clone)]
31pub struct ReductionMSMCToILP {
32 target: ILP<bool>,
33 num_vertices: usize,
34}
35
36impl ReductionResult for ReductionMSMCToILP {
37 type Source = MinimumSumMulticenter<SimpleGraph, i64>;
38 type Target = ILP<bool>;
39
40 fn target_problem(&self) -> &ILP<bool> {
41 &self.target
42 }
43
44 fn extract_solution(
45 &self,
46 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
47 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
48 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
49
50 Ok(target_solution[..self.num_vertices]
51 .iter()
52 .map(|&value| value == 1)
53 .collect())
54 }
55}
56
57fn weighted_distances_msmc(
61 graph: &SimpleGraph,
62 edge_lengths: &[i64],
63 source: usize,
64 n: usize,
65) -> Vec<Option<i64>> {
66 let mut adj: Vec<Vec<(usize, i64)>> = vec![Vec::new(); n];
67 for (idx, &(u, v)) in graph.edges().iter().enumerate() {
68 let len = edge_lengths[idx];
69 adj[u].push((v, len));
70 adj[v].push((u, len));
71 }
72
73 let mut dist = vec![None; n];
74 let mut visited = vec![false; n];
75 dist[source] = Some(0);
76
77 for _ in 0..n {
78 let mut next = None;
79 for vertex in 0..n {
80 if visited[vertex] {
81 continue;
82 }
83 let Some(dv) = dist[vertex] else {
84 continue;
85 };
86 match next {
87 None => next = Some(vertex),
88 Some(prev) => {
89 if dv < dist[prev].expect("selected vertex must have a distance") {
90 next = Some(vertex);
91 }
92 }
93 }
94 }
95
96 let Some(u) = next else {
97 break;
98 };
99 visited[u] = true;
100 let du = dist[u].expect("selected vertex must have a distance");
101
102 for &(v, len) in &adj[u] {
103 if visited[v] {
104 continue;
105 }
106 let candidate = du + len;
107 let should_update = match dist[v] {
108 None => true,
109 Some(current) => candidate < current,
110 };
111 if should_update {
112 dist[v] = Some(candidate);
113 }
114 }
115 }
116
117 dist
118}
119
120#[reduction(
121 transform = upper_bound {
122 num_vars = "num_vertices + num_vertices^2",
123 num_constraints = "num_vertices^2 + 2 * num_vertices + 1",
124 },
125 unavailable = {
126 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
127 }
128)]
129impl ReduceTo<ILP<bool>> for MinimumSumMulticenter<SimpleGraph, i64> {
130 type Result = ReductionMSMCToILP;
131
132 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
133 let n = self.num_vertices();
134 let k = Self::exact_i64(self.k(), "encoding the number of centers")?;
135 let vertex_weights = self.vertex_weights();
136 let edge_lengths = self.edge_lengths();
137
138 let all_dist: Vec<Vec<Option<i64>>> = (0..n)
140 .map(|s| weighted_distances_msmc(self.graph(), edge_lengths, s, n))
141 .collect();
142
143 let x_var = |j: usize| j;
145 let y_var = |i: usize, j: usize| n + i * n + j;
146
147 let num_vars = n + n * n;
148 let mut constraints = Vec::with_capacity(n * n + 2 * n + 1);
150
151 let center_terms: Vec<(usize, i64)> = (0..n).map(|j| (x_var(j), 1)).collect();
153 constraints.push(LinearConstraint::eq(center_terms, k));
154
155 for i in 0..n {
157 let terms: Vec<(usize, i64)> = (0..n).map(|j| (y_var(i, j), 1)).collect();
158 constraints.push(LinearConstraint::eq(terms, 1));
159 }
160
161 for (i, distances) in all_dist.iter().enumerate() {
164 for (j, distance) in distances.iter().enumerate() {
165 if distance.is_some() {
166 constraints.push(LinearConstraint::le(
167 vec![(y_var(i, j), 1), (x_var(j), -1)],
168 0,
169 ));
170 } else {
171 constraints.push(LinearConstraint::eq(vec![(y_var(i, j), 1)], 0));
172 }
173 }
174 }
175
176 let mut objective: Vec<(usize, i64)> = Vec::new();
178 for (i, &w) in vertex_weights.iter().enumerate() {
179 for (j, distance) in all_dist[i].iter().enumerate() {
180 if let Some(distance) = distance {
181 let weighted_distance = w.checked_mul(*distance).ok_or_else(|| {
182 crate::rules::ReductionError::integer_overflow::<
183 MinimumSumMulticenter<SimpleGraph, i64>,
184 ILP<bool>,
185 >(
186 "multiplying a vertex weight by a shortest-path distance"
187 )
188 })?;
189 let coeff = weighted_distance;
190 if coeff != 0 {
191 objective.push((y_var(i, j), coeff));
192 }
193 }
194 }
195 }
196
197 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
198 .map_err(Self::target_construction)?;
199 Ok(ReductionMSMCToILP {
200 target,
201 num_vertices: n,
202 })
203 }
204}
205
206#[cfg(feature = "example-db")]
207pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
208 vec![crate::example_db::specs::RuleExampleSpec {
209 id: "minimumsummulticenter_to_ilp",
210 build: || {
211 let source = MinimumSumMulticenter::new(
214 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
215 vec![1i64; 3],
216 vec![1i64; 2],
217 1,
218 );
219 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
220 },
221 }]
222}
223
224#[cfg(test)]
225#[path = "../unit_tests/rules/minimumsummulticenter_ilp.rs"]
226mod tests;