Skip to main content

problemreductions/models/graph/
min_max_multicenter.rs

1//! Min-Max Multicenter (vertex p-center) problem implementation.
2//!
3//! The vertex p-center problem asks for K centers on vertices of a graph that
4//! minimize the maximum weighted distance from any vertex to its nearest center.
5
6use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Min, One, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "MinMaxMulticenter",
16        display_name: "Min-Max Multicenter",
17        aliases: &["pCenter"],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20            VariantDimension::new("weight", "i64", &["i64", "One"]),
21        ],
22        category: crate::registry::ProblemCategory::Graph,
23        module_path: module_path!(),
24        description: "Find K centers minimizing the maximum weighted distance from any vertex to its nearest center (vertex p-center)",
25        fields: MinMaxMulticenterI64CreateSpec::FIELDS,
26    }
27}
28
29/// The Min-Max Multicenter (vertex p-center) problem.
30///
31/// Given a graph G = (V, E) with vertex weights w(v) and edge lengths l(e),
32/// and a number K of centers to place, find a subset P of K vertices (centers)
33/// that minimizes max_{v in V} w(v) * d(v, P),
34/// where d(v, P) is the shortest-path distance from v to the nearest center.
35///
36/// # Type Parameters
37///
38/// * `G` - The graph type (e.g., `SimpleGraph`)
39/// * `W` - The weight/length type (e.g., `i64`)
40///
41/// # Example
42///
43/// ```
44/// use problemreductions::models::graph::MinMaxMulticenter;
45/// use problemreductions::topology::SimpleGraph;
46/// use problemreductions::{Problem, BruteForce};
47///
48/// // Hexagonal-like graph: 6 vertices, 7 edges, unit weights/lengths, K=2
49/// let graph = SimpleGraph::new(6, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (0, 5), (1, 4)]);
50/// let problem = MinMaxMulticenter::new(graph, vec![1i64; 6], vec![1i64; 7], 2);
51///
52/// let solver = BruteForce::new();
53/// let solution = solver.solve(&problem).unwrap();
54/// assert!(solution.is_some());
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(try_from = "MinMaxMulticenterData<G, W>")]
58#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
59pub struct MinMaxMulticenter<G, W: WeightElement> {
60    /// The underlying graph.
61    graph: G,
62    /// Non-negative weight for each vertex.
63    vertex_weights: Vec<W>,
64    /// Non-negative length for each edge (in edge index order).
65    edge_lengths: Vec<W>,
66    /// Number of centers to place.
67    k: usize,
68}
69
70#[derive(Deserialize)]
71#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
72struct MinMaxMulticenterData<G, W: WeightElement> {
73    graph: G,
74    vertex_weights: Vec<W>,
75    edge_lengths: Vec<W>,
76    k: usize,
77}
78
79impl<G, W> TryFrom<MinMaxMulticenterData<G, W>> for MinMaxMulticenter<G, W>
80where
81    G: Graph,
82    W: WeightElement,
83{
84    type Error = crate::registry::ConstructionError;
85
86    fn try_from(data: MinMaxMulticenterData<G, W>) -> Result<Self, Self::Error> {
87        Self::try_new(data.graph, data.vertex_weights, data.edge_lengths, data.k)
88    }
89}
90
91macro_rules! min_max_multicenter_create_spec {
92    ($name:ident, $weight:ty, $one:expr $(, $weights:ident, $edge_weights:ident)?) => {
93        #[derive(Debug, Deserialize, crate::CreateSpec)]
94        struct $name {
95            #[create(codec = "edge-list")]
96            graph: Vec<(usize, usize)>,
97            num_vertices: Option<usize>,
98            $(
99            #[create(codec = "comma-separated")]
100            $weights: Option<Vec<$weight>>,
101            )?
102            $(
103            #[create(codec = "comma-separated")]
104            $edge_weights: Option<Vec<$weight>>,
105            )?
106            k: usize,
107        }
108
109        impl TryFrom<$name> for MinMaxMulticenter<SimpleGraph, $weight> {
110            type Error = crate::registry::ConstructionError;
111
112            fn try_from(spec: $name) -> Result<Self, Self::Error> {
113                let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
114                let vertex_weights = { $(if let Some(value) = spec.$weights { value } else)? { vec![$one; graph.num_vertices()] } };
115                let edge_lengths = { $(if let Some(value) = spec.$edge_weights { value } else)? { vec![$one; graph.num_edges()] } };
116                Self::try_new(graph, vertex_weights, edge_lengths, spec.k)
117            }
118        }
119    };
120}
121
122min_max_multicenter_create_spec!(
123    MinMaxMulticenterI64CreateSpec,
124    i64,
125    1,
126    weights,
127    edge_weights
128);
129min_max_multicenter_create_spec!(MinMaxMulticenterOneCreateSpec, One, One);
130
131fn simple_graph_from_create(
132    edges: Vec<(usize, usize)>,
133    num_vertices: Option<usize>,
134) -> Result<SimpleGraph, crate::registry::ConstructionError> {
135    if edges.is_empty() && num_vertices.is_none() {
136        return Err("num_vertices is required for an empty graph"
137            .to_string()
138            .into());
139    }
140    for (index, &(u, v)) in edges.iter().enumerate() {
141        if u == v {
142            return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
143        }
144    }
145    let inferred = edges
146        .iter()
147        .flat_map(|&(u, v)| [u, v])
148        .max()
149        .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
150        .transpose()?
151        .unwrap_or(0);
152    let num_vertices = num_vertices.unwrap_or(inferred);
153    if num_vertices < inferred {
154        return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
155    }
156    Ok(SimpleGraph::new(num_vertices, edges))
157}
158
159impl<G: Graph, W: WeightElement> MinMaxMulticenter<G, W> {
160    /// Create a MinMaxMulticenter problem.
161    ///
162    /// # Panics
163    /// - If `vertex_weights.len() != graph.num_vertices()`
164    /// - If `edge_lengths.len() != graph.num_edges()`
165    /// - If any vertex weight or edge length is negative
166    /// - If `k == 0` or `k > graph.num_vertices()`
167    pub fn new(graph: G, vertex_weights: Vec<W>, edge_lengths: Vec<W>, k: usize) -> Self {
168        Self::try_new(graph, vertex_weights, edge_lengths, k)
169            .unwrap_or_else(|error| panic!("{error}"))
170    }
171
172    fn try_new(
173        graph: G,
174        vertex_weights: Vec<W>,
175        edge_lengths: Vec<W>,
176        k: usize,
177    ) -> Result<Self, crate::registry::ConstructionError> {
178        if vertex_weights.len() != graph.num_vertices() {
179            return Err(crate::registry::ConstructionError::length_mismatch(
180                "vertex_weights",
181                vertex_weights.len(),
182                graph.num_vertices(),
183            ));
184        }
185        if edge_lengths.len() != graph.num_edges() {
186            return Err(crate::registry::ConstructionError::length_mismatch(
187                "edge_lengths",
188                edge_lengths.len(),
189                graph.num_edges(),
190            ));
191        }
192        let zero = W::Sum::zero();
193        if !vertex_weights
194            .iter()
195            .all(|weight| weight.to_sum() >= zero.clone())
196        {
197            return Err("vertex_weights must be non-negative".into());
198        }
199        if !edge_lengths
200            .iter()
201            .all(|length| length.to_sum() >= zero.clone())
202        {
203            return Err("edge_lengths must be non-negative".into());
204        }
205        if k == 0 {
206            return Err("k must be positive".into());
207        }
208        if k > graph.num_vertices() {
209            return Err("k must not exceed num_vertices".into());
210        }
211        Ok(Self {
212            graph,
213            vertex_weights,
214            edge_lengths,
215            k,
216        })
217    }
218
219    /// Get a reference to the underlying graph.
220    pub fn graph(&self) -> &G {
221        &self.graph
222    }
223
224    /// Get a reference to the vertex weights.
225    pub fn vertex_weights(&self) -> &[W] {
226        &self.vertex_weights
227    }
228
229    /// Get a reference to the edge lengths.
230    pub fn edge_lengths(&self) -> &[W] {
231        &self.edge_lengths
232    }
233
234    /// Get the number of centers K.
235    pub fn k(&self) -> usize {
236        self.k
237    }
238
239    /// Smallest h >= 1 with every vertex weight and edge length strictly below 2^h.
240    pub fn max_numeric_magnitude_bits(&self) -> u64 {
241        crate::types::max_numeric_magnitude_bits(
242            self.vertex_weights
243                .iter()
244                .chain(&self.edge_lengths)
245                .map(|value| value.to_sum()),
246        )
247    }
248
249    /// Get the number of vertices in the underlying graph.
250    pub fn num_vertices(&self) -> usize {
251        self.graph().num_vertices()
252    }
253
254    /// Get the number of edges in the underlying graph.
255    pub fn num_edges(&self) -> usize {
256        self.graph().num_edges()
257    }
258
259    /// Get the number of centers K.
260    pub fn num_centers(&self) -> usize {
261        self.k
262    }
263
264    /// Compute shortest distances from each vertex to the nearest center.
265    ///
266    /// Uses multi-source Dijkstra with linear scan: initializes all centers
267    /// at distance 0 and greedily relaxes edges by increasing distance.
268    /// Correct because all edge lengths are non-negative.
269    ///
270    /// Returns `None` if any vertex is unreachable from all centers.
271    fn shortest_distances(
272        &self,
273        config: &[bool],
274    ) -> Result<Option<Vec<W::Sum>>, crate::traits::EvaluationError> {
275        let n = self.graph.num_vertices();
276        let edges = self.graph.edges();
277
278        let mut adj: Vec<Vec<(usize, W::Sum)>> = vec![Vec::new(); n];
279        for (idx, &(u, v)) in edges.iter().enumerate() {
280            let len = self.edge_lengths[idx].to_sum();
281            adj[u].push((v, len.clone()));
282            adj[v].push((u, len));
283        }
284
285        // Multi-source Dijkstra with linear scan (works with PartialOrd)
286        let mut dist: Vec<Option<W::Sum>> = vec![None; n];
287        let mut visited = vec![false; n];
288
289        // Initialize centers
290        for (v, &selected) in config.iter().enumerate() {
291            if selected {
292                dist[v] = Some(W::Sum::zero());
293            }
294        }
295
296        for _ in 0..n {
297            // Find unvisited vertex with smallest distance
298            let mut u = None;
299            for v in 0..n {
300                if visited[v] {
301                    continue;
302                }
303                if let Some(ref dv) = dist[v] {
304                    match u {
305                        None => u = Some(v),
306                        Some(prev) => {
307                            if *dv < dist[prev].clone().unwrap() {
308                                u = Some(v);
309                            }
310                        }
311                    }
312                }
313            }
314            let u = match u {
315                Some(v) => v,
316                None => break, // remaining vertices are unreachable
317            };
318            visited[u] = true;
319
320            let du = dist[u].clone().unwrap();
321            for &(next, ref len) in &adj[u] {
322                if visited[next] {
323                    continue;
324                }
325                let new_dist = W::checked_add_to_sum(
326                    du.clone(),
327                    len.clone(),
328                    "adding min-max multicenter path lengths",
329                )?;
330                let update = match &dist[next] {
331                    None => true,
332                    Some(d) => new_dist < *d,
333                };
334                if update {
335                    dist[next] = Some(new_dist);
336                }
337            }
338        }
339
340        Ok(dist.into_iter().collect())
341    }
342}
343
344impl<G, W> Problem for MinMaxMulticenter<G, W>
345where
346    G: Graph + crate::variant::VariantParam,
347    W: WeightElement + crate::variant::VariantParam,
348{
349    const NAME: &'static str = "MinMaxMulticenter";
350    type Solution = Vec<bool>;
351    type Value = Min<W::Sum>;
352
353    crate::problem_parameters![
354        ("max_numeric_magnitude_bits", max_numeric_magnitude_bits),
355        ("num_edges", num_edges),
356        ("num_vertices", num_vertices),
357    ];
358
359    fn variant() -> Vec<(&'static str, &'static str)> {
360        crate::variant_params![G, W]
361    }
362
363    fn evaluate(
364        &self,
365        config: &Self::Solution,
366    ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
367        Ok({
368            if config.len() != self.graph.num_vertices() {
369                return Err(crate::traits::EvaluationError::InvalidConfiguration(
370                    "center-selection length does not match the graph vertices".into(),
371                ));
372            }
373
374            // Check exactly K centers are selected
375            let num_selected = config.iter().filter(|&&selected| selected).count();
376            if num_selected != self.k {
377                return Ok(Min(None));
378            }
379
380            // Compute shortest distances to nearest center
381            let distances = match self.shortest_distances(config)? {
382                Some(d) => d,
383                None => {
384                    return Ok(Min(None));
385                }
386            };
387
388            // Compute max weighted distance: max_{v} w(v) * d(v)
389            let mut max_wd = W::Sum::zero();
390            for (v, dist) in distances.iter().enumerate() {
391                let wd = W::checked_mul_sum(
392                    self.vertex_weights[v].to_sum(),
393                    dist.clone(),
394                    "multiplying min-max multicenter vertex weight by distance",
395                )?;
396                if wd > max_wd {
397                    max_wd = wd;
398                }
399            }
400
401            Min(Some(max_wd))
402        })
403    }
404}
405
406impl<G, W> crate::solvers::BruteForceProblem for MinMaxMulticenter<G, W>
407where
408    G: Graph + crate::variant::VariantParam,
409    W: WeightElement + crate::variant::VariantParam,
410{
411    fn dimensions(&self) -> Vec<usize> {
412        vec![2; self.graph.num_vertices()]
413    }
414}
415
416crate::declare_variants! {
417    default MinMaxMulticenter<SimpleGraph, i64> => "1.4969^num_vertices" create MinMaxMulticenterI64CreateSpec,
418    MinMaxMulticenter<SimpleGraph, One> => "1.4969^num_vertices" create MinMaxMulticenterOneCreateSpec,
419}
420
421crate::register_brute_force! {
422    MinMaxMulticenter<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
423    MinMaxMulticenter<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
424}
425
426#[cfg(feature = "example-db")]
427pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
428    vec![crate::example_db::specs::ModelExampleSpec {
429        id: "min_max_multicenter_simplegraph",
430        instance: Box::new(MinMaxMulticenter::new(
431            SimpleGraph::new(
432                6,
433                vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (0, 5), (1, 4)],
434            ),
435            vec![1i64; 6],
436            vec![1i64; 7],
437            2,
438        )),
439        optimal_config: serde_json::json!(vec![false, true, false, false, true, false]),
440        optimal_value: serde_json::json!(1),
441    }]
442}
443
444#[cfg(test)]
445#[path = "../../unit_tests/models/graph/min_max_multicenter.rs"]
446mod tests;
447
448crate::decision_problem_meta!(MinMaxMulticenter<SimpleGraph, One>, "DecisionMinMaxMulticenter");
449crate::register_decision_variant!(
450    MinMaxMulticenter<SimpleGraph, One>, "DecisionMinMaxMulticenter", "1.4969^num_vertices", &[],
451    "Does a feasible solution have objective value <= the bound?",
452    category: crate::registry::ProblemCategory::Graph,
453    dims: [
454            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
455            VariantDimension::new("weight", "One", &["One"]),
456        ],
457    fields: [
458        crate::registry::FieldInfo { name: "graph", type_name: "Vec<(usize,usize)>", description: "Graph edges as comma-separated vertex pairs." },
459        crate::registry::FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices, including isolated vertices." },
460        crate::registry::FieldInfo { name: "k", type_name: "usize", description: "Number of centers." },
461        crate::registry::FieldInfo { name: "bound", type_name: "i64", description: "Accept objective values <= this bound" },
462    ],
463    decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices)
464);
465
466#[cfg(feature = "example-db")]
467pub(crate) fn decision_canonical_rule_example_specs(
468) -> Vec<crate::example_db::specs::RuleExampleSpec> {
469    vec![crate::example_db::specs::RuleExampleSpec {
470        id: "decision_min_max_multicenter_to_min_max_multicenter",
471        build: || {
472            let source = crate::models::decision::Decision::new(
473                MinMaxMulticenter::new(
474                    SimpleGraph::new(
475                        6,
476                        vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (0, 5), (1, 4)],
477                    ),
478                    vec![crate::types::One; 6],
479                    vec![crate::types::One; 7],
480                    2,
481                ),
482                1,
483            );
484            let witness = serde_json::json!(vec![false, true, false, false, true, false]);
485            crate::example_db::specs::rule_example_with_witness::<
486                _,
487                MinMaxMulticenter<SimpleGraph, One>,
488            >(
489                source,
490                crate::export::SolutionPair {
491                    source_config: witness.clone(),
492                    target_config: witness,
493                },
494            )
495        },
496    }]
497}