Skip to main content

problemreductions/models/graph/
minimum_capacitated_spanning_tree.rs

1//! Minimum Capacitated Spanning Tree problem implementation.
2//!
3//! Given a weighted graph with a designated root vertex, vertex requirements,
4//! and a capacity bound, find a minimum-weight spanning tree rooted at the root
5//! such that for each edge, the sum of requirements in its subtree (on the
6//! non-root side) does not exceed the capacity.
7
8use num_traits::Zero;
9use serde::{Deserialize, Serialize};
10
11use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
12use crate::topology::{Graph, SimpleGraph};
13use crate::traits::Problem;
14use crate::types::{Min, WeightElement};
15
16inventory::submit! {
17    ProblemSchemaEntry {
18        name: "MinimumCapacitatedSpanningTree",
19        display_name: "Minimum Capacitated Spanning Tree",
20        aliases: &["MCST"],
21        dimensions: &[
22            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
23            VariantDimension::new("weight", "i64", &["i64"]),
24        ],
25        category: crate::registry::ProblemCategory::Graph,
26        module_path: module_path!(),
27        description: "Find minimum weight spanning tree with subtree capacity constraints",
28        fields: MinimumCapacitatedSpanningTreeCreateSpec::FIELDS,
29    }
30}
31
32/// The Minimum Capacitated Spanning Tree problem.
33///
34/// Given a weighted graph G = (V, E), edge weights w_e, a root vertex v0,
35/// vertex requirements r_v (with r_{v0} = 0), and a capacity C, find a
36/// spanning tree T rooted at v0 such that:
37/// - For each edge e in T, the sum of requirements of all vertices in the
38///   subtree on the non-root side of e is at most C.
39/// - The total weight of T is minimized.
40///
41/// # Representation
42///
43/// Each edge is assigned a binary variable:
44/// - 0: edge is not in the spanning tree
45/// - 1: edge is in the spanning tree
46///
47/// # Type Parameters
48///
49/// * `G` - The graph type (e.g., `SimpleGraph`)
50/// * `W` - The weight type for edges and requirements (e.g., `i64`)
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct MinimumCapacitatedSpanningTree<G, W: WeightElement> {
53    /// The underlying graph.
54    graph: G,
55    /// Weights for each edge (in edge index order).
56    weights: Vec<W>,
57    /// Root vertex index.
58    root: usize,
59    /// Vertex requirements (root has requirement 0).
60    requirements: Vec<W>,
61    /// Subtree capacity bound.
62    capacity: W::Sum,
63}
64
65#[derive(Debug, Deserialize, crate::CreateSpec)]
66struct MinimumCapacitatedSpanningTreeCreateSpec {
67    /// The underlying graph.
68    graph: SimpleGraph,
69    /// Edge weights; defaults to one per edge.
70    weights: Option<Vec<i64>>,
71    /// Root vertex.
72    root: usize,
73    /// Vertex requirements.
74    requirements: Vec<i64>,
75    /// Subtree capacity bound.
76    capacity: i64,
77}
78impl TryFrom<MinimumCapacitatedSpanningTreeCreateSpec>
79    for MinimumCapacitatedSpanningTree<SimpleGraph, i64>
80{
81    type Error = crate::registry::ConstructionError;
82    fn try_from(spec: MinimumCapacitatedSpanningTreeCreateSpec) -> Result<Self, Self::Error> {
83        let edges = spec.graph.num_edges();
84        let weights = spec.weights.unwrap_or_else(|| vec![1; edges]);
85        if weights.len() != edges {
86            return Err(format!("weights has {} entries, expected {edges}", weights.len()).into());
87        }
88        let vertices = spec.graph.num_vertices();
89        if vertices < 2 {
90            return Err("graph must have at least two vertices".to_string().into());
91        }
92        if spec.requirements.len() != vertices {
93            return Err(format!(
94                "requirements has {} entries, expected {vertices}",
95                spec.requirements.len()
96            )
97            .into());
98        }
99        if spec.root >= vertices {
100            return Err("root is outside the graph".to_string().into());
101        }
102        Ok(Self::new(
103            spec.graph,
104            weights,
105            spec.root,
106            spec.requirements,
107            spec.capacity,
108        ))
109    }
110}
111
112impl<G: Graph, W: WeightElement> MinimumCapacitatedSpanningTree<G, W> {
113    /// Create a MinimumCapacitatedSpanningTree problem.
114    ///
115    /// # Panics
116    /// - If `weights.len() != graph.num_edges()`
117    /// - If `requirements.len() != graph.num_vertices()`
118    /// - If `root >= graph.num_vertices()`
119    /// - If `graph.num_vertices() < 2`
120    pub fn new(
121        graph: G,
122        weights: Vec<W>,
123        root: usize,
124        requirements: Vec<W>,
125        capacity: W::Sum,
126    ) -> Self {
127        assert_eq!(
128            weights.len(),
129            graph.num_edges(),
130            "weights length must match num_edges"
131        );
132        assert_eq!(
133            requirements.len(),
134            graph.num_vertices(),
135            "requirements length must match num_vertices"
136        );
137        assert!(
138            root < graph.num_vertices(),
139            "root {root} out of range (num_vertices = {})",
140            graph.num_vertices()
141        );
142        assert!(
143            graph.num_vertices() >= 2,
144            "graph must have at least 2 vertices"
145        );
146        Self {
147            graph,
148            weights,
149            root,
150            requirements,
151            capacity,
152        }
153    }
154
155    /// Get a reference to the underlying graph.
156    pub fn graph(&self) -> &G {
157        &self.graph
158    }
159
160    /// Get the edge weights.
161    pub fn weights(&self) -> &[W] {
162        &self.weights
163    }
164
165    /// Set new edge weights.
166    pub fn set_weights(&mut self, weights: Vec<W>) {
167        assert_eq!(weights.len(), self.graph.num_edges());
168        self.weights = weights;
169    }
170
171    /// Check if the problem uses a non-unit weight type.
172    pub fn is_weighted(&self) -> bool {
173        !W::IS_UNIT
174    }
175
176    /// Get the root vertex.
177    pub fn root(&self) -> usize {
178        self.root
179    }
180
181    /// Get the vertex requirements.
182    pub fn requirements(&self) -> &[W] {
183        &self.requirements
184    }
185
186    /// Get the capacity bound.
187    pub fn capacity(&self) -> &W::Sum {
188        &self.capacity
189    }
190
191    /// Get the number of vertices in the underlying graph.
192    pub fn num_vertices(&self) -> usize {
193        self.graph.num_vertices()
194    }
195
196    /// Get the number of edges in the underlying graph.
197    pub fn num_edges(&self) -> usize {
198        self.graph.num_edges()
199    }
200
201    /// Check if a configuration is a valid capacitated spanning tree.
202    pub fn is_valid_solution(
203        &self,
204        config: &[bool],
205    ) -> Result<bool, crate::traits::EvaluationError> {
206        is_valid_capacitated_spanning_tree(
207            &self.graph,
208            &self.requirements,
209            self.root,
210            &self.capacity,
211            config,
212        )
213    }
214}
215
216/// Check if a configuration forms a valid spanning tree:
217/// 1. Exactly n-1 edges selected
218/// 2. Selected edges form a connected subgraph
219fn is_spanning_tree<G: Graph>(graph: &G, config: &[bool]) -> bool {
220    let n = graph.num_vertices();
221    let edges = graph.edges();
222    if config.len() != edges.len() {
223        return false;
224    }
225
226    let selected_count = config.iter().filter(|&&selected| selected).count();
227    if selected_count != n - 1 {
228        return false;
229    }
230
231    // Build adjacency and BFS from vertex 0
232    let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
233    for (idx, &sel) in config.iter().enumerate() {
234        if sel {
235            let (u, v) = edges[idx];
236            adj[u].push(v);
237            adj[v].push(u);
238        }
239    }
240
241    let mut visited = vec![false; n];
242    let mut queue = std::collections::VecDeque::new();
243    visited[0] = true;
244    queue.push_back(0);
245    while let Some(v) = queue.pop_front() {
246        for &u in &adj[v] {
247            if !visited[u] {
248                visited[u] = true;
249                queue.push_back(u);
250            }
251        }
252    }
253
254    visited.iter().all(|&v| v)
255}
256
257/// Compute the subtree requirement sum for each edge in the tree rooted at `root`.
258/// Returns None if the tree is invalid, otherwise returns the max subtree sum.
259fn check_capacity<G: Graph, W: WeightElement>(
260    graph: &G,
261    requirements: &[W],
262    root: usize,
263    capacity: &W::Sum,
264    config: &[bool],
265) -> Result<bool, crate::traits::EvaluationError> {
266    let n = graph.num_vertices();
267    let edges = graph.edges();
268
269    // Build adjacency list with edge indices
270    let mut adj: Vec<Vec<(usize, usize)>> = vec![vec![]; n]; // (neighbor, edge_idx)
271    for (idx, &sel) in config.iter().enumerate() {
272        if sel {
273            let (u, v) = edges[idx];
274            adj[u].push((v, idx));
275            adj[v].push((u, idx));
276        }
277    }
278
279    // Root the tree using BFS from root
280    let mut parent = vec![usize::MAX; n];
281    let mut order = Vec::with_capacity(n);
282    let mut visited = vec![false; n];
283    let mut queue = std::collections::VecDeque::new();
284    visited[root] = true;
285    parent[root] = root;
286    queue.push_back(root);
287    while let Some(v) = queue.pop_front() {
288        order.push(v);
289        for &(u, _) in &adj[v] {
290            if !visited[u] {
291                visited[u] = true;
292                parent[u] = v;
293                queue.push_back(u);
294            }
295        }
296    }
297
298    // Compute subtree sums bottom-up
299    let mut subtree_sum: Vec<W::Sum> = requirements.iter().map(|r| r.to_sum()).collect();
300    for &v in order.iter().rev() {
301        if v != root {
302            let p = parent[v];
303            let sv = subtree_sum[v].clone();
304            subtree_sum[p] = W::checked_add_to_sum(
305                subtree_sum[p].clone(),
306                sv,
307                "summing capacitated spanning tree requirements",
308            )?;
309        }
310    }
311
312    // Check capacity for each non-root vertex (its subtree sum is the flow on its parent edge)
313    for (v, sum) in subtree_sum.iter().enumerate() {
314        if v != root && *sum > *capacity {
315            return Ok(false);
316        }
317    }
318
319    Ok(true)
320}
321
322/// Check if a configuration forms a valid capacitated spanning tree.
323fn is_valid_capacitated_spanning_tree<G: Graph, W: WeightElement>(
324    graph: &G,
325    requirements: &[W],
326    root: usize,
327    capacity: &W::Sum,
328    config: &[bool],
329) -> Result<bool, crate::traits::EvaluationError> {
330    if !is_spanning_tree(graph, config) {
331        return Ok(false);
332    }
333    check_capacity(graph, requirements, root, capacity, config)
334}
335
336impl<G, W> Problem for MinimumCapacitatedSpanningTree<G, W>
337where
338    G: Graph + crate::variant::VariantParam,
339    W: WeightElement + crate::variant::VariantParam,
340{
341    const NAME: &'static str = "MinimumCapacitatedSpanningTree";
342    type Solution = Vec<bool>;
343    type Value = Min<W::Sum>;
344
345    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
346
347    fn variant() -> Vec<(&'static str, &'static str)> {
348        crate::variant_params![G, W]
349    }
350
351    fn evaluate(
352        &self,
353        config: &Self::Solution,
354    ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
355        if config.len() != self.graph.num_edges() {
356            return Err(crate::traits::EvaluationError::InvalidConfiguration(
357                "edge-selection length does not match the graph".into(),
358            ));
359        }
360        Ok({
361            if !is_valid_capacitated_spanning_tree(
362                &self.graph,
363                &self.requirements,
364                self.root,
365                &self.capacity,
366                config,
367            )? {
368                return Ok(Min(None));
369            }
370            let mut total = W::Sum::zero();
371            for (idx, &selected) in config.iter().enumerate() {
372                if selected {
373                    if let Some(w) = self.weights.get(idx) {
374                        total = W::checked_add_to_sum(
375                            total,
376                            w.to_sum(),
377                            "summing capacitated spanning tree edge weights",
378                        )?;
379                    }
380                }
381            }
382            Min(Some(total))
383        })
384    }
385}
386
387impl<G, W> crate::solvers::BruteForceProblem for MinimumCapacitatedSpanningTree<G, W>
388where
389    G: Graph + crate::variant::VariantParam,
390    W: WeightElement + crate::variant::VariantParam,
391{
392    fn dimensions(&self) -> Vec<usize> {
393        vec![2; self.graph.num_edges()]
394    }
395}
396
397crate::declare_variants! {
398    default MinimumCapacitatedSpanningTree<SimpleGraph, i64> => "2^num_edges" create MinimumCapacitatedSpanningTreeCreateSpec,
399}
400
401crate::register_brute_force! {
402    MinimumCapacitatedSpanningTree<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
403}
404
405#[cfg(feature = "example-db")]
406pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
407    vec![crate::example_db::specs::ModelExampleSpec {
408        id: "minimum_capacitated_spanning_tree_simplegraph",
409        instance: Box::new(MinimumCapacitatedSpanningTree::new(
410            SimpleGraph::new(
411                5,
412                vec![
413                    (0, 1),
414                    (0, 2),
415                    (0, 3),
416                    (1, 2),
417                    (1, 4),
418                    (2, 3),
419                    (2, 4),
420                    (3, 4),
421                ],
422            ),
423            vec![2, 1, 4, 3, 1, 2, 3, 1], // edge weights
424            0,                            // root
425            vec![0, 1, 1, 1, 1],          // requirements (root=0)
426            3,                            // capacity
427        )),
428        // Optimal: edges {(0,1),(0,2),(1,4),(3,4)} = indices {0,1,4,7}
429        // Weight = 2+1+1+1 = 5
430        // Subtree sums: subtree(1)={1,4}->req=2<=3, subtree(2)={2}->req=1<=3,
431        //   subtree(4)={4}->req=1<=3, subtree(3)={3}->req=1<=3
432        optimal_config: serde_json::json!(vec![true, true, false, false, true, false, false, true]),
433        optimal_value: serde_json::json!(5),
434    }]
435}
436
437#[cfg(test)]
438#[path = "../../unit_tests/models/graph/minimum_capacitated_spanning_tree.rs"]
439mod tests;