problemreductions/models/graph/
bounded_diameter_spanning_tree.rs1use 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(bound(
64 deserialize = "G: serde::Deserialize<'de>, W: serde::Deserialize<'de>, W::Sum: serde::Deserialize<'de>"
65))]
66pub struct BoundedDiameterSpanningTree<G, W: WeightElement> {
67 graph: G,
69 edge_weights: Vec<W>,
71 weight_bound: W::Sum,
73 diameter_bound: usize,
75 edge_list: Vec<(usize, usize)>,
77}
78
79#[derive(Debug, Deserialize, crate::CreateSpec)]
80struct BoundedDiameterSpanningTreeCreateSpec {
81 #[create(codec = "edge-list")]
82 graph: Vec<(usize, usize)>,
83 num_vertices: Option<usize>,
84 #[create(codec = "comma-separated")]
85 edge_weights: Option<Vec<i64>>,
86 weight_bound: i64,
87 diameter_bound: usize,
88}
89
90impl TryFrom<BoundedDiameterSpanningTreeCreateSpec>
91 for BoundedDiameterSpanningTree<SimpleGraph, i64>
92{
93 type Error = crate::registry::ConstructionError;
94
95 fn try_from(spec: BoundedDiameterSpanningTreeCreateSpec) -> Result<Self, Self::Error> {
96 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
97 let edge_weights = spec
98 .edge_weights
99 .unwrap_or_else(|| vec![1; graph.num_edges()]);
100 if edge_weights.len() != graph.num_edges() {
101 return Err(format!(
102 "edge_weights has length {}, expected {}",
103 edge_weights.len(),
104 graph.num_edges()
105 )
106 .into());
107 }
108 if edge_weights.iter().any(|&weight| weight <= 0) {
109 return Err("edge_weights must be positive".to_string().into());
110 }
111 if spec.weight_bound <= 0 {
112 return Err("weight_bound must be positive".to_string().into());
113 }
114 if spec.diameter_bound == 0 {
115 return Err("diameter_bound must be at least 1".to_string().into());
116 }
117 Ok(Self::new(
118 graph,
119 edge_weights,
120 spec.weight_bound,
121 spec.diameter_bound,
122 ))
123 }
124}
125
126fn simple_graph_from_create(
127 edges: Vec<(usize, usize)>,
128 num_vertices: Option<usize>,
129) -> Result<SimpleGraph, crate::registry::ConstructionError> {
130 if edges.is_empty() && num_vertices.is_none() {
131 return Err("num_vertices is required for an empty graph"
132 .to_string()
133 .into());
134 }
135 for (index, &(u, v)) in edges.iter().enumerate() {
136 if u == v {
137 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
138 }
139 }
140 let inferred = edges
141 .iter()
142 .flat_map(|&(u, v)| [u, v])
143 .max()
144 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
145 .transpose()?
146 .unwrap_or(0);
147 let num_vertices = num_vertices.unwrap_or(inferred);
148 if num_vertices < inferred {
149 return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
150 }
151 Ok(SimpleGraph::new(num_vertices, edges))
152}
153
154impl<G: Graph, W: WeightElement> BoundedDiameterSpanningTree<G, W> {
155 pub fn new(
161 graph: G,
162 edge_weights: Vec<W>,
163 weight_bound: W::Sum,
164 diameter_bound: usize,
165 ) -> Self {
166 assert_eq!(
167 edge_weights.len(),
168 graph.num_edges(),
169 "edge_weights length must match num_edges"
170 );
171 let zero = W::Sum::zero();
172 assert!(
173 edge_weights.iter().all(|w| w.to_sum() > zero.clone()),
174 "All edge weights must be positive (> 0)"
175 );
176 assert!(weight_bound > zero, "weight_bound must be positive (> 0)");
177 assert!(diameter_bound >= 1, "diameter_bound must be at least 1");
178 let edge_list = graph.edges();
179 Self {
180 graph,
181 edge_weights,
182 weight_bound,
183 diameter_bound,
184 edge_list,
185 }
186 }
187
188 pub fn graph(&self) -> &G {
190 &self.graph
191 }
192
193 pub fn edge_weights(&self) -> &[W] {
195 &self.edge_weights
196 }
197
198 pub fn set_weights(&mut self, edge_weights: Vec<W>) {
200 assert_eq!(
201 edge_weights.len(),
202 self.graph.num_edges(),
203 "edge_weights length must match num_edges"
204 );
205 let zero = W::Sum::zero();
206 assert!(
207 edge_weights.iter().all(|w| w.to_sum() > zero.clone()),
208 "All edge weights must be positive (> 0)"
209 );
210 self.edge_weights = edge_weights;
211 }
212
213 pub fn weight_bound(&self) -> &W::Sum {
215 &self.weight_bound
216 }
217
218 pub fn diameter_bound(&self) -> usize {
220 self.diameter_bound
221 }
222
223 pub fn num_vertices(&self) -> usize {
225 self.graph.num_vertices()
226 }
227
228 pub fn num_edges(&self) -> usize {
230 self.graph.num_edges()
231 }
232
233 pub fn edge_list(&self) -> &[(usize, usize)] {
235 &self.edge_list
236 }
237
238 pub fn is_weighted(&self) -> bool {
240 !W::IS_UNIT
241 }
242
243 fn tree_diameter(adj: &[Vec<usize>], n: usize) -> usize {
247 let mut max_dist = 0;
248 for start in 0..n {
249 if adj[start].is_empty() {
250 continue;
251 }
252 let mut dist = vec![usize::MAX; n];
253 dist[start] = 0;
254 let mut queue = VecDeque::new();
255 queue.push_back(start);
256 while let Some(v) = queue.pop_front() {
257 for &u in &adj[v] {
258 if dist[u] == usize::MAX {
259 dist[u] = dist[v] + 1;
260 if dist[u] > max_dist {
261 max_dist = dist[u];
262 }
263 queue.push_back(u);
264 }
265 }
266 }
267 }
268 max_dist
269 }
270}
271
272impl<G, W> Problem for BoundedDiameterSpanningTree<G, W>
273where
274 G: Graph + VariantParam,
275 W: WeightElement + VariantParam,
276{
277 const NAME: &'static str = "BoundedDiameterSpanningTree";
278 type Solution = Vec<bool>;
279 type Value = crate::types::Or;
280
281 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
282
283 fn variant() -> Vec<(&'static str, &'static str)> {
284 crate::variant_params![G, W]
285 }
286
287 fn evaluate(
288 &self,
289 config: &Self::Solution,
290 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
291 Ok({
292 crate::types::Or({
293 let n = self.graph.num_vertices();
294 if config.len() != self.edge_list.len() {
295 return Err(crate::traits::EvaluationError::InvalidConfiguration(
296 "edge-selection length does not match the graph".into(),
297 ));
298 }
299
300 let selected_indices: Vec<usize> = config
302 .iter()
303 .enumerate()
304 .filter(|(_, &v)| v)
305 .map(|(i, _)| i)
306 .collect();
307
308 if n == 0 {
310 return Ok(crate::types::Or(selected_indices.is_empty()));
311 }
312 if selected_indices.len() != n - 1 {
313 return Ok(crate::types::Or(false));
314 }
315
316 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
318 let mut total_weight = W::Sum::zero();
319 for &idx in &selected_indices {
320 let (u, v) = self.edge_list[idx];
321 adj[u].push(v);
322 adj[v].push(u);
323 total_weight = W::checked_add_to_sum(
324 total_weight,
325 self.edge_weights[idx].to_sum(),
326 "summing bounded-diameter spanning tree weights",
327 )?;
328 }
329
330 if total_weight > self.weight_bound.clone() {
332 return Ok(crate::types::Or(false));
333 }
334
335 let mut visited = vec![false; n];
337 let mut queue = VecDeque::new();
338 visited[0] = true;
339 queue.push_back(0);
340 let mut count = 1;
341 while let Some(v) = queue.pop_front() {
342 for &u in &adj[v] {
343 if !visited[u] {
344 visited[u] = true;
345 count += 1;
346 queue.push_back(u);
347 }
348 }
349 }
350
351 if count != n {
352 return Ok(crate::types::Or(false));
353 }
354
355 let diameter = Self::tree_diameter(&adj, n);
357 diameter <= self.diameter_bound
358 })
359 })
360 }
361}
362
363impl<G, W> crate::solvers::BruteForceProblem for BoundedDiameterSpanningTree<G, W>
364where
365 G: Graph + VariantParam,
366 W: WeightElement + VariantParam,
367{
368 fn dimensions(&self) -> Vec<usize> {
369 vec![2; self.edge_list.len()]
370 }
371}
372
373crate::declare_variants! {
374 default BoundedDiameterSpanningTree<SimpleGraph, i64> => "num_vertices ^ num_vertices" create BoundedDiameterSpanningTreeCreateSpec,
375}
376
377crate::register_brute_force! {
378 BoundedDiameterSpanningTree<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
379}
380
381#[cfg(feature = "example-db")]
382pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
383 vec![crate::example_db::specs::ModelExampleSpec {
388 id: "bounded_diameter_spanning_tree_simplegraph",
389 instance: Box::new(BoundedDiameterSpanningTree::new(
390 SimpleGraph::new(
391 5,
392 vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 4), (2, 3), (3, 4)],
393 ),
394 vec![1, 2, 1, 1, 2, 1, 1],
395 5,
396 3,
397 )),
398 optimal_config: serde_json::json!(vec![true, false, true, false, false, true, true]),
399 optimal_value: serde_json::json!(true),
400 }]
401}
402
403#[cfg(test)]
404#[path = "../../unit_tests/models/graph/bounded_diameter_spanning_tree.rs"]
405mod tests;