1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::WeightElement;
11use crate::variant::VariantParam;
12use num_traits::Zero;
13use serde::{Deserialize, Serialize};
14use std::collections::VecDeque;
15
16inventory::submit! {
17 ProblemSchemaEntry {
18 name: "BoundedDiameterSpanningTree",
19 display_name: "Bounded Diameter Spanning Tree",
20 aliases: &[],
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: "Does G have a spanning tree with total weight <= B and diameter <= D?",
28 fields: BoundedDiameterSpanningTreeCreateSpec::FIELDS,
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(try_from = "BoundedDiameterSpanningTreeData<G, W>")]
64#[serde(bound(
65 deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>, W::Sum: Deserialize<'de>"
66))]
67pub struct BoundedDiameterSpanningTree<G, W: WeightElement> {
68 graph: G,
70 edge_weights: Vec<W>,
72 weight_bound: W::Sum,
74 diameter_bound: usize,
76 edge_list: Vec<(usize, usize)>,
78}
79
80#[derive(Deserialize)]
81#[serde(bound(
82 deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>, W::Sum: Deserialize<'de>"
83))]
84struct BoundedDiameterSpanningTreeData<G, W: WeightElement> {
85 graph: G,
86 edge_weights: Vec<W>,
87 weight_bound: W::Sum,
88 diameter_bound: usize,
89}
90
91impl<G, W> TryFrom<BoundedDiameterSpanningTreeData<G, W>> for BoundedDiameterSpanningTree<G, W>
92where
93 G: Graph,
94 W: WeightElement,
95{
96 type Error = crate::registry::ConstructionError;
97
98 fn try_from(data: BoundedDiameterSpanningTreeData<G, W>) -> Result<Self, Self::Error> {
99 Self::try_new(
100 data.graph,
101 data.edge_weights,
102 data.weight_bound,
103 data.diameter_bound,
104 )
105 }
106}
107
108#[derive(Debug, Deserialize, crate::CreateSpec)]
109struct BoundedDiameterSpanningTreeCreateSpec {
110 #[create(codec = "edge-list")]
111 graph: Vec<(usize, usize)>,
112 num_vertices: Option<usize>,
113 #[create(codec = "comma-separated")]
114 edge_weights: Option<Vec<i64>>,
115 weight_bound: i64,
116 diameter_bound: usize,
117}
118
119impl TryFrom<BoundedDiameterSpanningTreeCreateSpec>
120 for BoundedDiameterSpanningTree<SimpleGraph, i64>
121{
122 type Error = crate::registry::ConstructionError;
123
124 fn try_from(spec: BoundedDiameterSpanningTreeCreateSpec) -> Result<Self, Self::Error> {
125 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
126 let edge_weights = spec
127 .edge_weights
128 .unwrap_or_else(|| vec![1; graph.num_edges()]);
129 Self::try_new(graph, edge_weights, spec.weight_bound, spec.diameter_bound)
130 }
131}
132
133fn simple_graph_from_create(
134 edges: Vec<(usize, usize)>,
135 num_vertices: Option<usize>,
136) -> Result<SimpleGraph, crate::registry::ConstructionError> {
137 if edges.is_empty() && num_vertices.is_none() {
138 return Err("num_vertices is required for an empty graph"
139 .to_string()
140 .into());
141 }
142 for (index, &(u, v)) in edges.iter().enumerate() {
143 if u == v {
144 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
145 }
146 }
147 let inferred = edges
148 .iter()
149 .flat_map(|&(u, v)| [u, v])
150 .max()
151 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
152 .transpose()?
153 .unwrap_or(0);
154 let num_vertices = num_vertices.unwrap_or(inferred);
155 if num_vertices < inferred {
156 return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
157 }
158 Ok(SimpleGraph::new(num_vertices, edges))
159}
160
161impl<G: Graph, W: WeightElement> BoundedDiameterSpanningTree<G, W> {
162 pub fn new(
168 graph: G,
169 edge_weights: Vec<W>,
170 weight_bound: W::Sum,
171 diameter_bound: usize,
172 ) -> Self {
173 Self::try_new(graph, edge_weights, weight_bound, diameter_bound)
174 .unwrap_or_else(|error| panic!("{error}"))
175 }
176
177 fn try_new(
178 graph: G,
179 edge_weights: Vec<W>,
180 weight_bound: W::Sum,
181 diameter_bound: usize,
182 ) -> Result<Self, crate::registry::ConstructionError> {
183 Self::check_weights(&graph, &edge_weights)?;
184 let zero = W::Sum::zero();
185 if weight_bound.partial_cmp(&zero) != Some(std::cmp::Ordering::Greater) {
186 return Err("weight_bound must be positive (> 0)".into());
187 }
188 if diameter_bound == 0 {
189 return Err("diameter_bound must be at least 1".into());
190 }
191 let edge_list = graph.edges();
192 Ok(Self {
193 graph,
194 edge_weights,
195 weight_bound,
196 diameter_bound,
197 edge_list,
198 })
199 }
200
201 pub fn graph(&self) -> &G {
203 &self.graph
204 }
205
206 pub fn edge_weights(&self) -> &[W] {
208 &self.edge_weights
209 }
210
211 pub fn set_weights(&mut self, edge_weights: Vec<W>) {
213 Self::check_weights(&self.graph, &edge_weights).unwrap_or_else(|error| panic!("{error}"));
214 self.edge_weights = edge_weights;
215 }
216
217 fn check_weights(graph: &G, weights: &[W]) -> Result<(), crate::registry::ConstructionError> {
218 if weights.len() != graph.num_edges() {
219 return Err(crate::registry::ConstructionError::length_mismatch(
220 "weights",
221 weights.len(),
222 graph.num_edges(),
223 ));
224 }
225 if !weights
226 .iter()
227 .all(|weight| weight.to_sum() > W::Sum::zero())
228 {
229 return Err("All edge weights must be positive (> 0)".into());
230 }
231 Ok(())
232 }
233
234 pub fn weight_bound(&self) -> &W::Sum {
236 &self.weight_bound
237 }
238
239 pub fn diameter_bound(&self) -> usize {
241 self.diameter_bound
242 }
243
244 pub fn num_vertices(&self) -> usize {
246 self.graph.num_vertices()
247 }
248
249 pub fn num_edges(&self) -> usize {
251 self.graph.num_edges()
252 }
253
254 pub fn edge_list(&self) -> &[(usize, usize)] {
256 &self.edge_list
257 }
258
259 pub fn is_weighted(&self) -> bool {
261 !W::IS_UNIT
262 }
263
264 fn tree_diameter(adj: &[Vec<usize>], n: usize) -> usize {
268 let mut max_dist = 0;
269 for start in 0..n {
270 if adj[start].is_empty() {
271 continue;
272 }
273 let mut dist = vec![usize::MAX; n];
274 dist[start] = 0;
275 let mut queue = VecDeque::new();
276 queue.push_back(start);
277 while let Some(v) = queue.pop_front() {
278 for &u in &adj[v] {
279 if dist[u] == usize::MAX {
280 dist[u] = dist[v] + 1;
281 if dist[u] > max_dist {
282 max_dist = dist[u];
283 }
284 queue.push_back(u);
285 }
286 }
287 }
288 }
289 max_dist
290 }
291}
292
293impl<G, W> Problem for BoundedDiameterSpanningTree<G, W>
294where
295 G: Graph + VariantParam,
296 W: WeightElement + VariantParam,
297{
298 const NAME: &'static str = "BoundedDiameterSpanningTree";
299 type Solution = Vec<bool>;
300 type Value = crate::types::Or;
301
302 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
303
304 fn variant() -> Vec<(&'static str, &'static str)> {
305 crate::variant_params![G, W]
306 }
307
308 fn evaluate(
309 &self,
310 config: &Self::Solution,
311 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
312 Ok({
313 crate::types::Or({
314 let n = self.graph.num_vertices();
315 if config.len() != self.edge_list.len() {
316 return Err(crate::traits::EvaluationError::InvalidConfiguration(
317 "edge-selection length does not match the graph".into(),
318 ));
319 }
320
321 let selected_indices: Vec<usize> = config
323 .iter()
324 .enumerate()
325 .filter(|(_, &v)| v)
326 .map(|(i, _)| i)
327 .collect();
328
329 if n == 0 {
331 return Ok(crate::types::Or(selected_indices.is_empty()));
332 }
333 if selected_indices.len() != n - 1 {
334 return Ok(crate::types::Or(false));
335 }
336
337 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
339 let mut total_weight = W::Sum::zero();
340 for &idx in &selected_indices {
341 let (u, v) = self.edge_list[idx];
342 adj[u].push(v);
343 adj[v].push(u);
344 total_weight = W::checked_add_to_sum(
345 total_weight,
346 self.edge_weights[idx].to_sum(),
347 "summing bounded-diameter spanning tree weights",
348 )?;
349 }
350
351 if total_weight > self.weight_bound.clone() {
353 return Ok(crate::types::Or(false));
354 }
355
356 let mut visited = vec![false; n];
358 let mut queue = VecDeque::new();
359 visited[0] = true;
360 queue.push_back(0);
361 let mut count = 1;
362 while let Some(v) = queue.pop_front() {
363 for &u in &adj[v] {
364 if !visited[u] {
365 visited[u] = true;
366 count += 1;
367 queue.push_back(u);
368 }
369 }
370 }
371
372 if count != n {
373 return Ok(crate::types::Or(false));
374 }
375
376 let diameter = Self::tree_diameter(&adj, n);
378 diameter <= self.diameter_bound
379 })
380 })
381 }
382}
383
384impl<G, W> crate::solvers::BruteForceProblem for BoundedDiameterSpanningTree<G, W>
385where
386 G: Graph + VariantParam,
387 W: WeightElement + VariantParam,
388{
389 fn dimensions(&self) -> Vec<usize> {
390 vec![2; self.edge_list.len()]
391 }
392}
393
394crate::declare_variants! {
395 default BoundedDiameterSpanningTree<SimpleGraph, i64> => "num_vertices ^ num_vertices" create BoundedDiameterSpanningTreeCreateSpec,
396}
397
398crate::register_brute_force! {
399 BoundedDiameterSpanningTree<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
400}
401
402#[cfg(feature = "example-db")]
403pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
404 vec![crate::example_db::specs::ModelExampleSpec {
409 id: "bounded_diameter_spanning_tree_simplegraph",
410 instance: Box::new(BoundedDiameterSpanningTree::new(
411 SimpleGraph::new(
412 5,
413 vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 4), (2, 3), (3, 4)],
414 ),
415 vec![1, 2, 1, 1, 2, 1, 1],
416 5,
417 3,
418 )),
419 optimal_config: serde_json::json!(vec![true, false, true, false, false, true, true]),
420 optimal_value: serde_json::json!(true),
421 }]
422}
423
424#[cfg(test)]
425#[path = "../../unit_tests/models/graph/bounded_diameter_spanning_tree.rs"]
426mod tests;