Skip to main content

problemreductions/models/graph/
bounded_component_spanning_forest.rs

1//! Bounded Component Spanning Forest problem implementation.
2//!
3//! The Bounded Component Spanning Forest problem asks whether the vertices of a
4//! weighted graph can be partitioned into at most `K` connected components, each
5//! of total weight at most `B`.
6
7use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::WeightElement;
11use num_traits::Zero;
12use serde::{Deserialize, Serialize};
13use std::collections::VecDeque;
14
15inventory::submit! {
16    ProblemSchemaEntry {
17        name: "BoundedComponentSpanningForest",
18        display_name: "Bounded Component Spanning Forest",
19        aliases: &[],
20        dimensions: &[
21            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
22            VariantDimension::new("weight", "i64", &["i64"]),
23        ],
24        category: crate::registry::ProblemCategory::Graph,
25        module_path: module_path!(),
26        description: "Partition vertices into at most K connected components, each of total weight at most B",
27        fields: BoundedComponentSpanningForestCreateSpec::FIELDS,
28    }
29}
30
31/// The Bounded Component Spanning Forest problem.
32///
33/// Given a graph `G = (V, E)`, a nonnegative weight `w(v)` for each vertex, an
34/// integer `K`, and a bound `B`, determine whether the vertices can be
35/// partitioned into at most `K` non-empty sets such that every set induces a
36/// connected subgraph and the total weight of each set is at most `B`.
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(try_from = "BoundedComponentSpanningForestData<G, W>")]
39#[serde(bound(
40    deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>, W::Sum: Deserialize<'de>"
41))]
42pub struct BoundedComponentSpanningForest<G, W: WeightElement> {
43    /// The underlying graph.
44    graph: G,
45    /// Weights for each vertex.
46    weights: Vec<W>,
47    /// Upper bound on the number of connected components.
48    max_components: usize,
49    /// Upper bound on the total weight of every component.
50    max_weight: W::Sum,
51}
52
53#[derive(Deserialize)]
54#[serde(bound(
55    deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>, W::Sum: Deserialize<'de>"
56))]
57struct BoundedComponentSpanningForestData<G, W: WeightElement> {
58    graph: G,
59    weights: Vec<W>,
60    max_components: usize,
61    max_weight: W::Sum,
62}
63
64impl<G, W> TryFrom<BoundedComponentSpanningForestData<G, W>>
65    for BoundedComponentSpanningForest<G, W>
66where
67    G: Graph,
68    W: WeightElement,
69{
70    type Error = crate::registry::ConstructionError;
71
72    fn try_from(data: BoundedComponentSpanningForestData<G, W>) -> Result<Self, Self::Error> {
73        Self::try_new(
74            data.graph,
75            data.weights,
76            data.max_components,
77            data.max_weight,
78        )
79    }
80}
81
82#[derive(Debug, Deserialize, crate::CreateSpec)]
83struct BoundedComponentSpanningForestCreateSpec {
84    /// The underlying graph G=(V,E).
85    graph: SimpleGraph,
86    /// Vertex weights w(v) for each vertex v in V.
87    weights: Vec<i64>,
88    /// Upper bound K on the number of connected components.
89    k: usize,
90    /// Upper bound B on the total weight of each component.
91    max_weight: i64,
92}
93
94impl TryFrom<BoundedComponentSpanningForestCreateSpec>
95    for BoundedComponentSpanningForest<SimpleGraph, i64>
96{
97    type Error = crate::registry::ConstructionError;
98
99    fn try_from(spec: BoundedComponentSpanningForestCreateSpec) -> Result<Self, Self::Error> {
100        Self::try_new(spec.graph, spec.weights, spec.k, spec.max_weight)
101    }
102}
103
104impl<G: Graph, W: WeightElement> BoundedComponentSpanningForest<G, W> {
105    /// Create a new bounded-component spanning forest instance.
106    pub fn new(graph: G, weights: Vec<W>, max_components: usize, max_weight: W::Sum) -> Self {
107        Self::try_new(graph, weights, max_components, max_weight)
108            .unwrap_or_else(|error| panic!("{error}"))
109    }
110
111    fn try_new(
112        graph: G,
113        weights: Vec<W>,
114        max_components: usize,
115        max_weight: W::Sum,
116    ) -> Result<Self, crate::registry::ConstructionError> {
117        if weights.len() != graph.num_vertices() {
118            return Err(crate::registry::ConstructionError::length_mismatch(
119                "weights",
120                weights.len(),
121                graph.num_vertices(),
122            ));
123        }
124        if !weights
125            .iter()
126            .all(|weight| weight.to_sum() >= W::Sum::zero())
127        {
128            return Err("weights must be nonnegative".into());
129        }
130        if max_components == 0 {
131            return Err("max_components must be at least 1".into());
132        }
133        if max_weight.partial_cmp(&W::Sum::zero()) != Some(std::cmp::Ordering::Greater) {
134            return Err("max_weight must be positive".into());
135        }
136        Ok(Self {
137            graph,
138            weights,
139            max_components,
140            max_weight,
141        })
142    }
143
144    /// Get a reference to the underlying graph.
145    pub fn graph(&self) -> &G {
146        &self.graph
147    }
148
149    /// Get the vertex weights.
150    pub fn weights(&self) -> &[W] {
151        &self.weights
152    }
153
154    /// Get the maximum number of components.
155    pub fn max_components(&self) -> usize {
156        self.max_components
157    }
158
159    /// Get the maximum allowed component weight.
160    pub fn max_weight(&self) -> &W::Sum {
161        &self.max_weight
162    }
163
164    /// Smallest h >= 1 with every vertex weight and the component weight limit below 2^h.
165    pub fn max_weight_bits(&self) -> u64 {
166        crate::types::max_numeric_magnitude_bits(
167            self.weights
168                .iter()
169                .map(|weight| weight.to_sum())
170                .chain(std::iter::once(self.max_weight.clone())),
171        )
172    }
173
174    /// Get the number of vertices in the underlying graph.
175    pub fn num_vertices(&self) -> usize {
176        self.graph.num_vertices()
177    }
178
179    /// Get the number of edges in the underlying graph.
180    pub fn num_edges(&self) -> usize {
181        self.graph.num_edges()
182    }
183
184    /// Check if the problem uses a non-unit weight type.
185    pub fn is_weighted(&self) -> bool {
186        !W::IS_UNIT
187    }
188
189    /// Check if a configuration is a valid bounded-component partition.
190    pub fn is_valid_solution(
191        &self,
192        config: &[usize],
193    ) -> Result<bool, crate::traits::EvaluationError> {
194        let num_vertices = self.graph.num_vertices();
195        if config.len() != num_vertices {
196            return Ok(false);
197        }
198
199        let mut component_weights = vec![W::Sum::zero(); self.max_components];
200        let mut component_sizes = vec![0usize; self.max_components];
201        let mut component_starts = vec![usize::MAX; self.max_components];
202        let mut used_components = Vec::with_capacity(self.max_components);
203
204        for (vertex, &component) in config.iter().enumerate() {
205            if component >= self.max_components {
206                return Ok(false);
207            }
208
209            if component_sizes[component] == 0 {
210                component_starts[component] = vertex;
211                used_components.push(component);
212            }
213
214            component_sizes[component] += 1;
215            component_weights[component] = W::checked_add_to_sum(
216                component_weights[component].clone(),
217                self.weights[vertex].to_sum(),
218                "summing bounded forest component weights",
219            )?;
220            if component_weights[component] > self.max_weight {
221                return Ok(false);
222            }
223        }
224
225        if used_components
226            .iter()
227            .all(|&component| component_sizes[component] <= 1)
228        {
229            return Ok(true);
230        }
231
232        let mut visited_marks = vec![0usize; num_vertices];
233        let mut queue = VecDeque::with_capacity(num_vertices);
234
235        for (mark, component) in used_components.into_iter().enumerate() {
236            let component_size = component_sizes[component];
237            if component_size <= 1 {
238                continue;
239            }
240
241            let start = component_starts[component];
242            queue.clear();
243            queue.push_back(start);
244            visited_marks[start] = mark + 1;
245            let mut visited_count = 0usize;
246
247            while let Some(vertex) = queue.pop_front() {
248                visited_count += 1;
249                for neighbor in self.graph.neighbors(vertex) {
250                    if config[neighbor] == component && visited_marks[neighbor] != mark + 1 {
251                        visited_marks[neighbor] = mark + 1;
252                        queue.push_back(neighbor);
253                    }
254                }
255            }
256
257            if visited_count != component_size {
258                return Ok(false);
259            }
260        }
261
262        Ok(true)
263    }
264}
265
266impl<G, W> Problem for BoundedComponentSpanningForest<G, W>
267where
268    G: Graph + crate::variant::VariantParam,
269    W: WeightElement + crate::variant::VariantParam,
270{
271    const NAME: &'static str = "BoundedComponentSpanningForest";
272    type Solution = Vec<usize>;
273    type Value = crate::types::Or;
274
275    crate::problem_parameters![
276        ("max_weight_bits", max_weight_bits),
277        ("max_components", max_components),
278        ("num_edges", num_edges),
279        ("num_vertices", num_vertices),
280    ];
281
282    fn variant() -> Vec<(&'static str, &'static str)> {
283        crate::variant_params![G, W]
284    }
285
286    fn evaluate(
287        &self,
288        config: &Self::Solution,
289    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
290        if config.len() != self.graph.num_vertices() {
291            return Err(crate::traits::EvaluationError::InvalidConfiguration(
292                "component assignment length does not match the graph vertices".into(),
293            ));
294        }
295        Ok(crate::types::Or(self.is_valid_solution(config)?))
296    }
297}
298
299impl<G, W> crate::solvers::BruteForceProblem for BoundedComponentSpanningForest<G, W>
300where
301    G: Graph + crate::variant::VariantParam,
302    W: WeightElement + crate::variant::VariantParam,
303{
304    fn dimensions(&self) -> Vec<usize> {
305        vec![self.max_components; self.graph.num_vertices()]
306    }
307}
308
309#[cfg(feature = "example-db")]
310pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
311    vec![crate::example_db::specs::ModelExampleSpec {
312        id: "bounded_component_spanning_forest_simplegraph",
313        instance: Box::new(BoundedComponentSpanningForest::new(
314            SimpleGraph::new(
315                8,
316                vec![
317                    (0, 1),
318                    (1, 2),
319                    (2, 3),
320                    (3, 4),
321                    (4, 5),
322                    (5, 6),
323                    (6, 7),
324                    (0, 7),
325                    (1, 5),
326                    (2, 6),
327                ],
328            ),
329            vec![2, 3, 1, 2, 3, 1, 2, 1],
330            3,
331            6,
332        )),
333        optimal_config: serde_json::json!(vec![0, 0, 1, 1, 1, 2, 2, 0]),
334        optimal_value: serde_json::json!(true),
335    }]
336}
337
338crate::declare_variants! {
339    default BoundedComponentSpanningForest<SimpleGraph, i64> => "3^num_vertices" create BoundedComponentSpanningForestCreateSpec,
340}
341
342crate::register_brute_force! {
343    BoundedComponentSpanningForest<SimpleGraph, i64>,
344}
345
346#[cfg(test)]
347#[path = "../../unit_tests/models/graph/bounded_component_spanning_forest.rs"]
348mod tests;