problemreductions/models/graph/
kth_best_spanning_tree.rs1use 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#[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 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 pub fn graph(&self) -> &SimpleGraph {
74 &self.graph
75 }
76
77 pub fn weights(&self) -> &[W] {
79 &self.weights
80 }
81
82 pub fn k(&self) -> usize {
84 self.k
85 }
86
87 pub fn bound(&self) -> &W::Sum {
89 &self.bound
90 }
91
92 pub fn num_vertices(&self) -> usize {
94 self.graph.num_vertices()
95 }
96
97 pub fn num_edges(&self) -> usize {
99 self.graph.num_edges()
100 }
101
102 pub fn is_weighted(&self) -> bool {
104 !W::IS_UNIT
105 }
106
107 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 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 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;