Skip to main content

problemreductions/models/graph/
kth_best_spanning_tree.rs

1//! Kth Best Spanning Tree problem implementation.
2//!
3//! Given a weighted graph, determine whether it contains `k` distinct spanning
4//! trees whose total weights are all at most a prescribed bound.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::WeightElement;
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12use std::collections::VecDeque;
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "KthBestSpanningTree",
17        display_name: "Kth Best Spanning Tree",
18        aliases: &[],
19        dimensions: &[VariantDimension::new("weight", "i32", &["i32"])],
20        module_path: module_path!(),
21        description: "Do there exist k distinct spanning trees with total weight at most B?",
22        fields: &[
23            FieldInfo { name: "graph", type_name: "SimpleGraph", description: "The underlying graph G=(V,E)" },
24            FieldInfo { name: "weights", type_name: "Vec<W>", description: "Edge weights w(e) for each edge in E" },
25            FieldInfo { name: "k", type_name: "usize", description: "Number of distinct spanning trees required" },
26            FieldInfo { name: "bound", type_name: "W::Sum", description: "Upper bound B on each spanning tree weight" },
27        ],
28    }
29}
30
31/// Kth Best Spanning Tree.
32///
33/// Given an undirected graph `G = (V, E)`, non-negative edge weights `w(e)`,
34/// a positive integer `k`, and a bound `B`, determine whether there are `k`
35/// distinct spanning trees of `G` whose total weights are all at most `B`.
36///
37/// # Representation
38///
39/// A configuration is `k` consecutive binary blocks of length `|E|`.
40/// Each block selects the edges of one candidate spanning tree.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct KthBestSpanningTree<W: WeightElement> {
43    graph: SimpleGraph,
44    weights: Vec<W>,
45    k: usize,
46    bound: W::Sum,
47}
48
49impl<W: WeightElement> KthBestSpanningTree<W> {
50    /// Create a new KthBestSpanningTree instance.
51    ///
52    /// # Panics
53    ///
54    /// Panics if the number of weights does not match the number of edges, or
55    /// if `k` is zero.
56    pub fn new(graph: SimpleGraph, weights: Vec<W>, k: usize, bound: W::Sum) -> Self {
57        assert_eq!(
58            weights.len(),
59            graph.num_edges(),
60            "weights length must match graph num_edges"
61        );
62        assert!(k > 0, "k must be positive");
63
64        Self {
65            graph,
66            weights,
67            k,
68            bound,
69        }
70    }
71
72    /// Get the underlying graph.
73    pub fn graph(&self) -> &SimpleGraph {
74        &self.graph
75    }
76
77    /// Get the edge weights.
78    pub fn weights(&self) -> &[W] {
79        &self.weights
80    }
81
82    /// Get the requested number of trees.
83    pub fn k(&self) -> usize {
84        self.k
85    }
86
87    /// Get the weight bound.
88    pub fn bound(&self) -> &W::Sum {
89        &self.bound
90    }
91
92    /// Get the number of vertices.
93    pub fn num_vertices(&self) -> usize {
94        self.graph.num_vertices()
95    }
96
97    /// Get the number of edges.
98    pub fn num_edges(&self) -> usize {
99        self.graph.num_edges()
100    }
101
102    /// Check whether the problem uses a non-unit weight type.
103    pub fn is_weighted(&self) -> bool {
104        !W::IS_UNIT
105    }
106
107    /// Check whether a configuration satisfies the problem.
108    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
109        self.evaluate_config(config)
110    }
111
112    fn block_is_valid_tree(&self, block: &[usize], edges: &[(usize, usize)]) -> bool {
113        if block.len() != edges.len() || block.iter().any(|&value| value > 1) {
114            return false;
115        }
116
117        let num_vertices = self.graph.num_vertices();
118        let selected_count = block.iter().filter(|&&value| value == 1).count();
119        if selected_count != num_vertices.saturating_sub(1) {
120            return false;
121        }
122
123        let mut total_weight = W::Sum::zero();
124        let mut adjacency = vec![Vec::new(); num_vertices];
125        let mut start = None;
126
127        for (idx, &selected) in block.iter().enumerate() {
128            if selected == 0 {
129                continue;
130            }
131            total_weight += self.weights[idx].to_sum();
132            let (u, v) = edges[idx];
133            adjacency[u].push(v);
134            adjacency[v].push(u);
135            if start.is_none() {
136                start = Some(u);
137            }
138        }
139
140        if total_weight > self.bound {
141            return false;
142        }
143
144        if num_vertices <= 1 {
145            return true;
146        }
147
148        // SAFETY: num_vertices > 1 and selected_count == num_vertices - 1 > 0,
149        // so at least one edge was selected and `start` is Some.
150        let start = start.expect("at least one selected edge");
151
152        let mut visited = vec![false; num_vertices];
153        let mut queue = VecDeque::new();
154        visited[start] = true;
155        queue.push_back(start);
156
157        while let Some(vertex) = queue.pop_front() {
158            for &neighbor in &adjacency[vertex] {
159                if !visited[neighbor] {
160                    visited[neighbor] = true;
161                    queue.push_back(neighbor);
162                }
163            }
164        }
165
166        visited.into_iter().all(|seen| seen)
167    }
168
169    fn blocks_are_pairwise_distinct(&self, config: &[usize], block_size: usize) -> bool {
170        debug_assert!(block_size > 0, "block_size must be positive");
171        let blocks: Vec<&[usize]> = config.chunks_exact(block_size).collect();
172        for left in 0..blocks.len() {
173            for right in (left + 1)..blocks.len() {
174                if blocks[left] == blocks[right] {
175                    return false;
176                }
177            }
178        }
179        true
180    }
181
182    fn evaluate_config(&self, config: &[usize]) -> bool {
183        let block_size = self.graph.num_edges();
184        let expected_len = self.k * block_size;
185        if config.len() != expected_len {
186            return false;
187        }
188
189        if block_size == 0 {
190            return self.k == 1 && self.block_is_valid_tree(config, &[]);
191        }
192
193        let edges = self.graph.edges();
194
195        if !self.blocks_are_pairwise_distinct(config, block_size) {
196            return false;
197        }
198
199        config
200            .chunks_exact(block_size)
201            .all(|block| self.block_is_valid_tree(block, &edges))
202    }
203}
204
205impl<W> Problem for KthBestSpanningTree<W>
206where
207    W: WeightElement + crate::variant::VariantParam,
208{
209    const NAME: &'static str = "KthBestSpanningTree";
210    type Value = crate::types::Or;
211
212    fn variant() -> Vec<(&'static str, &'static str)> {
213        crate::variant_params![W]
214    }
215
216    fn dims(&self) -> Vec<usize> {
217        vec![2; self.k * self.graph.num_edges()]
218    }
219
220    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
221        crate::types::Or(self.evaluate_config(config))
222    }
223}
224
225#[cfg(feature = "example-db")]
226pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
227    // K4 with weights [1,1,2,2,2,3], k=2, B=4.
228    // 16 spanning trees; exactly 2 have weight ≤ 4 (both weight 4):
229    //   {01,02,03} (star at 0) and {01,02,13}.
230    // Satisfying configs = 2 (the two orderings of this pair).
231    // 12 variables → 2^12 = 4096 configs, fast to enumerate.
232    let graph = SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
233    let problem = KthBestSpanningTree::new(graph, vec![1, 1, 2, 2, 2, 3], 2, 4);
234    vec![crate::example_db::specs::ModelExampleSpec {
235        id: "kth_best_spanning_tree_i32",
236        instance: Box::new(problem),
237        optimal_config: vec![1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0],
238        optimal_value: serde_json::json!(true),
239    }]
240}
241
242crate::declare_variants! {
243    default KthBestSpanningTree<i32> => "2^(num_edges * k)",
244}
245
246#[cfg(test)]
247#[path = "../../unit_tests/models/graph/kth_best_spanning_tree.rs"]
248mod tests;