problemreductions/models/graph/
bounded_component_spanning_forest.rs1use 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#[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 graph: G,
45 weights: Vec<W>,
47 max_components: usize,
49 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 graph: SimpleGraph,
86 weights: Vec<i64>,
88 k: usize,
90 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 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 pub fn graph(&self) -> &G {
146 &self.graph
147 }
148
149 pub fn weights(&self) -> &[W] {
151 &self.weights
152 }
153
154 pub fn max_components(&self) -> usize {
156 self.max_components
157 }
158
159 pub fn max_weight(&self) -> &W::Sum {
161 &self.max_weight
162 }
163
164 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 pub fn num_vertices(&self) -> usize {
176 self.graph.num_vertices()
177 }
178
179 pub fn num_edges(&self) -> usize {
181 self.graph.num_edges()
182 }
183
184 pub fn is_weighted(&self) -> bool {
186 !W::IS_UNIT
187 }
188
189 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;