Skip to main content

problemreductions/models/graph/
degree_constrained_spanning_tree.rs

1//! Degree-Constrained Spanning Tree problem implementation.
2//!
3//! Given a graph G = (V, E) and a positive integer K, determine whether G has
4//! a spanning tree in which every vertex has degree at most K.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::variant::VariantParam;
10use serde::{Deserialize, Serialize};
11use std::collections::VecDeque;
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "DegreeConstrainedSpanningTree",
16        display_name: "Degree-Constrained Spanning Tree",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20        ],
21        module_path: module_path!(),
22        description: "Does G have a spanning tree with maximum vertex degree at most K?",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
25            FieldInfo { name: "max_degree", type_name: "usize", description: "max_degree: maximum allowed vertex degree K (>= 1)" },
26        ],
27    }
28}
29
30/// Degree-Constrained Spanning Tree problem.
31///
32/// Given an undirected graph G = (V, E) and a positive integer K, determine
33/// whether G contains a spanning tree T such that every vertex in T has degree
34/// at most K.
35///
36/// Each configuration entry corresponds to an edge (in the order returned by
37/// `graph.edges()`), with value 0 (not selected) or 1 (selected).
38///
39/// # Type Parameters
40///
41/// * `G` - Graph type (e.g., SimpleGraph)
42///
43/// # Example
44///
45/// ```
46/// use problemreductions::models::graph::DegreeConstrainedSpanningTree;
47/// use problemreductions::topology::SimpleGraph;
48/// use problemreductions::{Problem, Solver, BruteForce};
49///
50/// let graph = SimpleGraph::new(4, vec![(0,1),(1,2),(2,3),(0,3)]);
51/// let problem = DegreeConstrainedSpanningTree::new(graph, 2);
52///
53/// let solver = BruteForce::new();
54/// let solution = solver.find_witness(&problem);
55/// assert!(solution.is_some());
56/// ```
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
59pub struct DegreeConstrainedSpanningTree<G> {
60    /// The underlying graph.
61    graph: G,
62    /// Maximum allowed vertex degree in the spanning tree.
63    max_degree: usize,
64    /// Ordered edge list (mirrors `graph.edges()` order).
65    edge_list: Vec<(usize, usize)>,
66}
67
68impl<G: Graph> DegreeConstrainedSpanningTree<G> {
69    /// Create a new Degree-Constrained Spanning Tree instance.
70    ///
71    /// # Panics
72    /// Panics if `max_degree` is zero.
73    pub fn new(graph: G, max_degree: usize) -> Self {
74        assert!(max_degree >= 1, "max_degree must be at least 1");
75        let edge_list = graph.edges();
76        Self {
77            graph,
78            max_degree,
79            edge_list,
80        }
81    }
82
83    /// Get a reference to the underlying graph.
84    pub fn graph(&self) -> &G {
85        &self.graph
86    }
87
88    /// Get the max_degree parameter K.
89    pub fn max_degree(&self) -> usize {
90        self.max_degree
91    }
92
93    /// Get the number of vertices in the underlying graph.
94    pub fn num_vertices(&self) -> usize {
95        self.graph.num_vertices()
96    }
97
98    /// Get the number of edges in the underlying graph.
99    pub fn num_edges(&self) -> usize {
100        self.graph.num_edges()
101    }
102
103    /// Get the ordered edge list.
104    pub fn edge_list(&self) -> &[(usize, usize)] {
105        &self.edge_list
106    }
107}
108
109impl<G> Problem for DegreeConstrainedSpanningTree<G>
110where
111    G: Graph + VariantParam,
112{
113    const NAME: &'static str = "DegreeConstrainedSpanningTree";
114    type Value = crate::types::Or;
115
116    fn variant() -> Vec<(&'static str, &'static str)> {
117        crate::variant_params![G]
118    }
119
120    fn dims(&self) -> Vec<usize> {
121        vec![2; self.edge_list.len()]
122    }
123
124    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
125        crate::types::Or({
126            let n = self.graph.num_vertices();
127            if config.len() != self.edge_list.len() {
128                return crate::types::Or(false);
129            }
130
131            // Collect selected edges
132            let selected: Vec<(usize, usize)> = config
133                .iter()
134                .enumerate()
135                .filter(|(_, &v)| v == 1)
136                .map(|(i, _)| self.edge_list[i])
137                .collect();
138
139            // A spanning tree on n vertices must have exactly n-1 edges
140            if n == 0 {
141                return crate::types::Or(selected.is_empty());
142            }
143            if selected.len() != n - 1 {
144                return crate::types::Or(false);
145            }
146
147            // Check connectivity using BFS on selected edges
148            let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
149            let mut degree = vec![0usize; n];
150            for &(u, v) in &selected {
151                adj[u].push(v);
152                adj[v].push(u);
153                degree[u] += 1;
154                degree[v] += 1;
155            }
156
157            // Check max degree constraint
158            if degree.iter().any(|&d| d > self.max_degree) {
159                return crate::types::Or(false);
160            }
161
162            // BFS to check connectivity
163            let mut visited = vec![false; n];
164            let mut queue = VecDeque::new();
165            visited[0] = true;
166            queue.push_back(0);
167            let mut count = 1;
168            while let Some(v) = queue.pop_front() {
169                for &u in &adj[v] {
170                    if !visited[u] {
171                        visited[u] = true;
172                        count += 1;
173                        queue.push_back(u);
174                    }
175                }
176            }
177
178            count == n
179        })
180    }
181}
182
183crate::declare_variants! {
184    default DegreeConstrainedSpanningTree<SimpleGraph> => "2^num_vertices",
185}
186
187#[cfg(feature = "example-db")]
188pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
189    // 5 vertices, 7 edges: (0,1),(0,2),(0,3),(1,2),(1,4),(2,3),(3,4), K=2
190    // Spanning tree with max degree 2: edges (0,2),(0,3),(1,2),(1,4)
191    //   indices: 1,2,3,4 → config [0,1,1,1,1,0,0]
192    //   Degrees: 0→{2,3}=2, 1→{2,4}=2, 2→{0,1}=2, 3→{0}=1, 4→{1}=1
193    vec![crate::example_db::specs::ModelExampleSpec {
194        id: "degree_constrained_spanning_tree_simplegraph",
195        instance: Box::new(DegreeConstrainedSpanningTree::new(
196            SimpleGraph::new(
197                5,
198                vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 4), (2, 3), (3, 4)],
199            ),
200            2,
201        )),
202        optimal_config: vec![0, 1, 1, 1, 1, 0, 0],
203        optimal_value: serde_json::json!(true),
204    }]
205}
206
207#[cfg(test)]
208#[path = "../../unit_tests/models/graph/degree_constrained_spanning_tree.rs"]
209mod tests;