problemreductions/models/graph/
kth_best_spanning_tree.rs1use crate::registry::{CreateSpec, 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", "i64", &["i64"])],
20 category: crate::registry::ProblemCategory::Graph,
21 module_path: module_path!(),
22 description: "Do there exist k distinct spanning trees with total weight at most B?",
23 fields: KthBestSpanningTreeCreateSpec::FIELDS,
24 }
25}
26
27#[derive(Debug, Clone, Serialize)]
38pub struct KthBestSpanningTree<W: WeightElement> {
39 graph: SimpleGraph,
40 weights: Vec<W>,
41 k: usize,
42 bound: W::Sum,
43}
44
45#[derive(Deserialize)]
46#[serde(bound(deserialize = "W: WeightElement + Deserialize<'de>, W::Sum: Deserialize<'de>"))]
47struct KthBestSpanningTreeData<W: WeightElement> {
48 graph: SimpleGraph,
49 weights: Vec<W>,
50 k: usize,
51 bound: W::Sum,
52}
53
54impl<'de, W> Deserialize<'de> for KthBestSpanningTree<W>
55where
56 W: WeightElement + Deserialize<'de>,
57 W::Sum: Deserialize<'de>,
58{
59 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
60 let data = KthBestSpanningTreeData::<W>::deserialize(deserializer)?;
61 Self::try_new(data.graph, data.weights, data.k, data.bound)
62 .map_err(serde::de::Error::custom)
63 }
64}
65
66#[derive(Debug, Deserialize, crate::CreateSpec)]
67struct KthBestSpanningTreeCreateSpec {
68 #[create(codec = "edge-list")]
69 graph: Vec<(usize, usize)>,
70 num_vertices: Option<usize>,
71 #[create(codec = "comma-separated")]
72 edge_weights: Option<Vec<i64>>,
73 k: usize,
74 bound: i64,
75}
76
77impl TryFrom<KthBestSpanningTreeCreateSpec> for KthBestSpanningTree<i64> {
78 type Error = crate::registry::ConstructionError;
79
80 fn try_from(spec: KthBestSpanningTreeCreateSpec) -> Result<Self, Self::Error> {
81 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
82 let weights = spec
83 .edge_weights
84 .unwrap_or_else(|| vec![1; graph.num_edges()]);
85 Self::try_new(graph, weights, spec.k, spec.bound)
86 }
87}
88
89fn simple_graph_from_create(
90 edges: Vec<(usize, usize)>,
91 num_vertices: Option<usize>,
92) -> Result<SimpleGraph, crate::registry::ConstructionError> {
93 if edges.is_empty() && num_vertices.is_none() {
94 return Err("num_vertices is required for an empty graph"
95 .to_string()
96 .into());
97 }
98 for (index, &(u, v)) in edges.iter().enumerate() {
99 if u == v {
100 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
101 }
102 }
103 let inferred = edges
104 .iter()
105 .flat_map(|&(u, v)| [u, v])
106 .max()
107 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
108 .transpose()?
109 .unwrap_or(0);
110 let num_vertices = num_vertices.unwrap_or(inferred);
111 if num_vertices < inferred {
112 return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
113 }
114 Ok(SimpleGraph::new(num_vertices, edges))
115}
116
117impl<W: WeightElement> KthBestSpanningTree<W> {
118 pub fn new(graph: SimpleGraph, weights: Vec<W>, k: usize, bound: W::Sum) -> Self {
125 Self::try_new(graph, weights, k, bound).unwrap_or_else(|error| panic!("{error}"))
126 }
127
128 fn try_new(
129 graph: SimpleGraph,
130 weights: Vec<W>,
131 k: usize,
132 bound: W::Sum,
133 ) -> Result<Self, crate::registry::ConstructionError> {
134 if weights.len() != graph.num_edges() {
135 return Err(crate::registry::ConstructionError::length_mismatch(
136 "weights",
137 weights.len(),
138 graph.num_edges(),
139 ));
140 }
141 if k == 0 {
142 return Err("k must be positive".into());
143 }
144
145 Ok(Self {
146 graph,
147 weights,
148 k,
149 bound,
150 })
151 }
152
153 pub fn graph(&self) -> &SimpleGraph {
155 &self.graph
156 }
157
158 pub fn weights(&self) -> &[W] {
160 &self.weights
161 }
162
163 pub fn k(&self) -> usize {
165 self.k
166 }
167
168 pub fn bound(&self) -> &W::Sum {
170 &self.bound
171 }
172
173 pub fn num_vertices(&self) -> usize {
175 self.graph.num_vertices()
176 }
177
178 pub fn num_edges(&self) -> usize {
180 self.graph.num_edges()
181 }
182
183 pub fn is_weighted(&self) -> bool {
185 !W::IS_UNIT
186 }
187
188 pub fn is_valid_solution(
190 &self,
191 config: &[Vec<bool>],
192 ) -> Result<bool, crate::traits::EvaluationError> {
193 if config.len() != self.k
194 || config
195 .iter()
196 .any(|tree| tree.len() != self.graph.num_edges())
197 {
198 return Ok(false);
199 }
200
201 let edges = self.graph.edges();
202 if !self.blocks_are_pairwise_distinct(config) {
203 return Ok(false);
204 }
205 for tree in config {
206 if !self.block_is_valid_tree(tree, &edges)? {
207 return Ok(false);
208 }
209 }
210 Ok(true)
211 }
212
213 fn block_is_valid_tree(
214 &self,
215 block: &[bool],
216 edges: &[(usize, usize)],
217 ) -> Result<bool, crate::traits::EvaluationError> {
218 if block.len() != edges.len() {
219 return Ok(false);
220 }
221
222 let num_vertices = self.graph.num_vertices();
223 let selected_count = block.iter().filter(|&&selected| selected).count();
224 if selected_count != num_vertices.saturating_sub(1) {
225 return Ok(false);
226 }
227
228 let mut total_weight = W::Sum::zero();
229 let mut adjacency = vec![Vec::new(); num_vertices];
230 let mut start = None;
231
232 for (idx, &selected) in block.iter().enumerate() {
233 if !selected {
234 continue;
235 }
236 total_weight = W::checked_add_to_sum(
237 total_weight,
238 self.weights[idx].to_sum(),
239 "summing spanning tree edge weights",
240 )?;
241 let (u, v) = edges[idx];
242 adjacency[u].push(v);
243 adjacency[v].push(u);
244 if start.is_none() {
245 start = Some(u);
246 }
247 }
248
249 if total_weight > self.bound {
250 return Ok(false);
251 }
252
253 if num_vertices <= 1 {
254 return Ok(true);
255 }
256
257 let start = start.expect("at least one selected edge");
260
261 let mut visited = vec![false; num_vertices];
262 let mut queue = VecDeque::new();
263 visited[start] = true;
264 queue.push_back(start);
265
266 while let Some(vertex) = queue.pop_front() {
267 for &neighbor in &adjacency[vertex] {
268 if !visited[neighbor] {
269 visited[neighbor] = true;
270 queue.push_back(neighbor);
271 }
272 }
273 }
274
275 Ok(visited.into_iter().all(|seen| seen))
276 }
277
278 fn blocks_are_pairwise_distinct(&self, config: &[Vec<bool>]) -> bool {
279 for left in 0..config.len() {
280 for right in (left + 1)..config.len() {
281 if config[left] == config[right] {
282 return false;
283 }
284 }
285 }
286 true
287 }
288}
289
290impl<W> Problem for KthBestSpanningTree<W>
291where
292 W: WeightElement + crate::variant::VariantParam,
293{
294 const NAME: &'static str = "KthBestSpanningTree";
295 type Solution = Vec<Vec<bool>>;
296 type Value = crate::types::Or;
297
298 crate::problem_parameters![
299 ("num_vertices", num_vertices),
300 ("num_edges", num_edges),
301 ("k", k),
302 ];
303
304 fn variant() -> Vec<(&'static str, &'static str)> {
305 crate::variant_params![W]
306 }
307
308 fn evaluate(
309 &self,
310 solution: &Self::Solution,
311 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
312 if solution.len() != self.k
313 || solution
314 .iter()
315 .any(|tree| tree.len() != self.graph.num_edges())
316 {
317 return Err(crate::traits::EvaluationError::InvalidConfiguration(
318 "spanning-tree collection dimensions do not match the instance".into(),
319 ));
320 }
321 Ok(crate::types::Or(self.is_valid_solution(solution)?))
322 }
323}
324
325impl<W> crate::solvers::BruteForceProblem for KthBestSpanningTree<W>
326where
327 W: WeightElement + crate::variant::VariantParam,
328{
329 fn dimensions(&self) -> Vec<usize> {
330 vec![2; self.k * self.graph.num_edges()]
331 }
332}
333
334#[cfg(feature = "example-db")]
335pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
336 let graph = SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
342 let problem = KthBestSpanningTree::new(graph, vec![1, 1, 2, 2, 2, 3], 2, 4);
343 vec![crate::example_db::specs::ModelExampleSpec {
344 id: "kth_best_spanning_tree",
345 instance: Box::new(problem),
346 optimal_config: serde_json::json!([
347 [true, true, true, false, false, false],
348 [true, true, false, false, true, false]
349 ]),
350 optimal_value: serde_json::json!(true),
351 }]
352}
353
354crate::declare_variants! {
355 default KthBestSpanningTree<i64> => "2^(num_edges * k)" create KthBestSpanningTreeCreateSpec,
356}
357
358crate::register_brute_force! {
359 KthBestSpanningTree<i64> decode |problem: &KthBestSpanningTree<i64>, indices: Vec<usize>| indices.chunks(problem.num_edges()).map(crate::config::config_to_bits).collect(),
360}
361
362#[cfg(test)]
363#[path = "../../unit_tests/models/graph/kth_best_spanning_tree.rs"]
364mod tests;