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