1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Min, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MinimumSumMulticenter",
16 display_name: "Minimum Sum Multicenter",
17 aliases: &["pmedian"],
18 dimensions: &[
19 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20 VariantDimension::new("weight", "i64", &["i64"]),
21 ],
22 category: crate::registry::ProblemCategory::Graph,
23 module_path: module_path!(),
24 description: "Find K centers minimizing total weighted distance (p-median problem)",
25 fields: MinimumSumMulticenterCreateSpec::FIELDS,
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct MinimumSumMulticenter<G, W> {
59 graph: G,
61 vertex_weights: Vec<W>,
63 edge_lengths: Vec<W>,
65 k: usize,
67}
68
69#[derive(Debug, Deserialize, crate::CreateSpec)]
70struct MinimumSumMulticenterCreateSpec {
71 #[create(codec = "edge-list")]
72 graph: Vec<(usize, usize)>,
73 num_vertices: Option<usize>,
74 #[create(codec = "comma-separated")]
75 weights: Option<Vec<i64>>,
76 #[create(codec = "comma-separated")]
77 edge_weights: Option<Vec<i64>>,
78 k: usize,
79}
80
81#[derive(Debug, Deserialize, crate::CreateSpec)]
82struct MinimumSumMulticenterRandomSpec {
83 num_vertices: usize,
85 edge_prob: Option<f64>,
87 seed: Option<i64>,
89 k: Option<usize>,
91}
92
93impl TryFrom<MinimumSumMulticenterCreateSpec> for MinimumSumMulticenter<SimpleGraph, i64> {
94 type Error = crate::registry::ConstructionError;
95
96 fn try_from(spec: MinimumSumMulticenterCreateSpec) -> Result<Self, Self::Error> {
97 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
98 let vertex_weights = spec
99 .weights
100 .unwrap_or_else(|| vec![1; graph.num_vertices()]);
101 if vertex_weights.len() != graph.num_vertices() {
102 return Err(format!(
103 "weights has length {}, expected {}",
104 vertex_weights.len(),
105 graph.num_vertices()
106 )
107 .into());
108 }
109 let edge_lengths = spec
110 .edge_weights
111 .unwrap_or_else(|| vec![1; graph.num_edges()]);
112 if edge_lengths.len() != graph.num_edges() {
113 return Err(format!(
114 "edge_weights has length {}, expected {}",
115 edge_lengths.len(),
116 graph.num_edges()
117 )
118 .into());
119 }
120 if spec.k == 0 || spec.k > graph.num_vertices() {
121 return Err(format!("k must be between 1 and {}", graph.num_vertices()).into());
122 }
123 Ok(Self::new(graph, vertex_weights, edge_lengths, spec.k))
124 }
125}
126
127fn simple_graph_from_create(
128 edges: Vec<(usize, usize)>,
129 num_vertices: Option<usize>,
130) -> Result<SimpleGraph, crate::registry::ConstructionError> {
131 if edges.is_empty() && num_vertices.is_none() {
132 return Err("num_vertices is required for an empty graph"
133 .to_string()
134 .into());
135 }
136 for (index, &(u, v)) in edges.iter().enumerate() {
137 if u == v {
138 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
139 }
140 }
141 let inferred = edges
142 .iter()
143 .flat_map(|&(u, v)| [u, v])
144 .max()
145 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
146 .transpose()?
147 .unwrap_or(0);
148 let num_vertices = num_vertices.unwrap_or(inferred);
149 if num_vertices < inferred {
150 return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
151 }
152 Ok(SimpleGraph::new(num_vertices, edges))
153}
154
155impl<G: Graph, W: Clone + Default> MinimumSumMulticenter<G, W> {
156 pub fn new(graph: G, vertex_weights: Vec<W>, edge_lengths: Vec<W>, k: usize) -> Self {
163 assert_eq!(
164 vertex_weights.len(),
165 graph.num_vertices(),
166 "vertex_weights length must match num_vertices"
167 );
168 assert_eq!(
169 edge_lengths.len(),
170 graph.num_edges(),
171 "edge_lengths length must match num_edges"
172 );
173 assert!(k > 0, "k must be positive");
174 assert!(k <= graph.num_vertices(), "k must not exceed num_vertices");
175 Self {
176 graph,
177 vertex_weights,
178 edge_lengths,
179 k,
180 }
181 }
182
183 pub fn graph(&self) -> &G {
185 &self.graph
186 }
187
188 pub fn vertex_weights(&self) -> &[W] {
190 &self.vertex_weights
191 }
192
193 pub fn edge_lengths(&self) -> &[W] {
195 &self.edge_lengths
196 }
197
198 pub fn k(&self) -> usize {
200 self.k
201 }
202}
203
204impl<G: Graph, W: WeightElement> MinimumSumMulticenter<G, W> {
205 pub fn num_vertices(&self) -> usize {
207 self.graph().num_vertices()
208 }
209
210 pub fn num_edges(&self) -> usize {
212 self.graph().num_edges()
213 }
214
215 pub fn num_centers(&self) -> usize {
217 self.k
218 }
219
220 fn shortest_distances(&self, config: &[bool]) -> Option<Vec<W::Sum>> {
228 let n = self.graph.num_vertices();
229 let edges = self.graph.edges();
230
231 let mut adj: Vec<Vec<(usize, W::Sum)>> = vec![Vec::new(); n];
232 for (idx, &(u, v)) in edges.iter().enumerate() {
233 let len = self.edge_lengths[idx].to_sum();
234 adj[u].push((v, len.clone()));
235 adj[v].push((u, len));
236 }
237
238 let mut dist: Vec<Option<W::Sum>> = vec![None; n];
240 let mut visited = vec![false; n];
241
242 for (v, &selected) in config.iter().enumerate() {
244 if selected {
245 dist[v] = Some(W::Sum::zero());
246 }
247 }
248
249 for _ in 0..n {
250 let mut u = None;
252 for v in 0..n {
253 if visited[v] {
254 continue;
255 }
256 if let Some(ref dv) = dist[v] {
257 match u {
258 None => u = Some(v),
259 Some(prev) => {
260 if *dv < dist[prev].clone().unwrap() {
261 u = Some(v);
262 }
263 }
264 }
265 }
266 }
267 let u = match u {
268 Some(v) => v,
269 None => break, };
271 visited[u] = true;
272
273 let du = dist[u].clone().unwrap();
274 for &(next, ref len) in &adj[u] {
275 if visited[next] {
276 continue;
277 }
278 let new_dist = du.clone() + len.clone();
279 let update = match &dist[next] {
280 None => true,
281 Some(d) => new_dist < *d,
282 };
283 if update {
284 dist[next] = Some(new_dist);
285 }
286 }
287 }
288
289 dist.into_iter().collect()
290 }
291}
292
293impl<G, W> Problem for MinimumSumMulticenter<G, W>
294where
295 G: Graph + crate::variant::VariantParam,
296 W: WeightElement + crate::variant::VariantParam,
297{
298 const NAME: &'static str = "MinimumSumMulticenter";
299 type Solution = Vec<bool>;
300 type Value = Min<W::Sum>;
301
302 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
303
304 fn variant() -> Vec<(&'static str, &'static str)> {
305 crate::variant_params![G, W]
306 }
307
308 fn evaluate(
309 &self,
310 config: &Self::Solution,
311 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
312 if config.len() != self.graph.num_vertices() {
313 return Err(crate::traits::EvaluationError::InvalidConfiguration(
314 "center-selection length does not match the graph vertices".into(),
315 ));
316 }
317 Ok({
318 let num_selected = config.iter().filter(|&&selected| selected).count();
320 if num_selected != self.k {
321 return Ok(Min(None));
322 }
323
324 let distances = match self.shortest_distances(config) {
326 Some(d) => d,
327 None => return Ok(Min(None)),
328 };
329
330 let mut total = W::Sum::zero();
332 for (v, dist) in distances.iter().enumerate() {
333 let weighted_distance = W::checked_mul_sum(
334 self.vertex_weights[v].to_sum(),
335 dist.clone(),
336 "multiplying multicenter vertex weight by distance",
337 )?;
338 total = W::checked_add_to_sum(
339 total,
340 weighted_distance,
341 "summing weighted multicenter distances",
342 )?;
343 }
344
345 Min(Some(total))
346 })
347 }
348}
349
350impl<G, W> crate::solvers::BruteForceProblem for MinimumSumMulticenter<G, W>
351where
352 G: Graph + crate::variant::VariantParam,
353 W: WeightElement + crate::variant::VariantParam,
354{
355 fn dimensions(&self) -> Vec<usize> {
356 vec![2; self.graph.num_vertices()]
357 }
358}
359
360crate::impl_random_generate!(MinimumSumMulticenter<SimpleGraph, i64>, MinimumSumMulticenterRandomSpec, |spec| {
361 let graph = crate::random::SimpleGraphRandomSpec {
362 num_vertices: spec.num_vertices,
363 edge_prob: spec.edge_prob,
364 seed: spec.seed,
365 }.graph()?;
366 let k = spec.k.unwrap_or(std::cmp::max(1, spec.num_vertices / 3));
367 if k == 0 || k > spec.num_vertices {
368 return Err(format!("k must be between 1 and {}", spec.num_vertices).into());
369 }
370 let lengths = vec![1; graph.num_edges()];
371 Ok(MinimumSumMulticenter::new(graph, vec![1; spec.num_vertices], lengths, k))
372});
373
374crate::declare_variants! {
375 default MinimumSumMulticenter<SimpleGraph, i64> => "2^num_vertices" create MinimumSumMulticenterCreateSpec random,
376}
377
378crate::register_brute_force! {
379 MinimumSumMulticenter<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
380}
381
382#[cfg(feature = "example-db")]
383pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
384 vec![crate::example_db::specs::ModelExampleSpec {
385 id: "minimum_sum_multicenter_simplegraph",
386 instance: Box::new(MinimumSumMulticenter::new(
387 SimpleGraph::new(
388 7,
389 vec![
390 (0, 1),
391 (1, 2),
392 (2, 3),
393 (3, 4),
394 (4, 5),
395 (5, 6),
396 (0, 6),
397 (2, 5),
398 ],
399 ),
400 vec![1i64; 7],
401 vec![1i64; 8],
402 2,
403 )),
404 optimal_config: serde_json::json!(vec![false, false, true, false, false, true, false]),
405 optimal_value: serde_json::json!(6),
406 }]
407}
408
409#[cfg(test)]
410#[path = "../../unit_tests/models/graph/minimum_sum_multicenter.rs"]
411mod tests;