problemreductions/models/graph/
partition_into_forests.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::variant::VariantParam;
11use petgraph::unionfind::UnionFind;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "PartitionIntoForests",
17 display_name: "Partition into Forests",
18 aliases: &[],
19 dimensions: &[
20 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21 ],
22 category: crate::registry::ProblemCategory::Graph,
23 module_path: module_path!(),
24 description: "Partition vertices into K classes each inducing an acyclic subgraph",
25 fields: &[
26 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
27 FieldInfo { name: "num_forests", type_name: "usize", description: "num_forests: number of forest classes K (>= 1)" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(try_from = "PartitionIntoForestsData<G>")]
59#[serde(bound(deserialize = "G: Graph + Deserialize<'de>"))]
60pub struct PartitionIntoForests<G> {
61 graph: G,
63 num_forests: usize,
65}
66
67#[derive(Deserialize)]
68#[serde(bound(deserialize = "G: Graph + Deserialize<'de>"))]
69struct PartitionIntoForestsData<G> {
70 graph: G,
71 num_forests: usize,
72}
73
74impl<G> TryFrom<PartitionIntoForestsData<G>> for PartitionIntoForests<G>
75where
76 G: Graph,
77{
78 type Error = crate::registry::ConstructionError;
79
80 fn try_from(data: PartitionIntoForestsData<G>) -> Result<Self, Self::Error> {
81 Self::try_new(data.graph, data.num_forests)
82 }
83}
84
85impl<G: Graph> PartitionIntoForests<G> {
86 pub fn new(graph: G, num_forests: usize) -> Self {
91 Self::try_new(graph, num_forests).unwrap_or_else(|error| panic!("{error}"))
92 }
93
94 fn try_new(graph: G, num_forests: usize) -> Result<Self, crate::registry::ConstructionError> {
95 if num_forests == 0 {
96 return Err("num_forests must be at least 1".into());
97 }
98 Ok(Self { graph, num_forests })
99 }
100
101 pub fn graph(&self) -> &G {
103 &self.graph
104 }
105
106 pub fn num_forests(&self) -> usize {
108 self.num_forests
109 }
110
111 pub fn num_vertices(&self) -> usize {
113 self.graph.num_vertices()
114 }
115
116 pub fn num_edges(&self) -> usize {
118 self.graph.num_edges()
119 }
120}
121
122impl<G> Problem for PartitionIntoForests<G>
123where
124 G: Graph + VariantParam,
125{
126 const NAME: &'static str = "PartitionIntoForests";
127 type Solution = Vec<usize>;
128 type Value = crate::types::Or;
129
130 crate::problem_parameters![
131 ("num_vertices", num_vertices),
132 ("num_edges", num_edges),
133 ("num_forests", num_forests),
134 ];
135
136 fn variant() -> Vec<(&'static str, &'static str)> {
137 crate::variant_params![G]
138 }
139
140 fn evaluate(
141 &self,
142 config: &Self::Solution,
143 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
144 if config.len() != self.graph.num_vertices() {
145 return Err(crate::traits::EvaluationError::InvalidConfiguration(
146 "partition assignment length does not match the graph vertices".into(),
147 ));
148 }
149 if config.iter().any(|&part| part >= self.num_forests) {
150 return Err(crate::traits::EvaluationError::InvalidConfiguration(
151 "partition assignment contains an out-of-range forest".into(),
152 ));
153 }
154 Ok({
155 crate::types::Or(is_valid_forest_partition(
156 &self.graph,
157 self.num_forests,
158 config,
159 ))
160 })
161 }
162}
163
164impl<G> crate::solvers::BruteForceProblem for PartitionIntoForests<G>
165where
166 G: Graph + VariantParam,
167{
168 fn dimensions(&self) -> Vec<usize> {
169 vec![self.num_forests; self.graph.num_vertices()]
170 }
171}
172
173fn is_valid_forest_partition<G: Graph>(graph: &G, num_forests: usize, config: &[usize]) -> bool {
175 let n = graph.num_vertices();
176
177 if config.len() != n {
179 return false;
180 }
181 if config.iter().any(|&c| c >= num_forests) {
182 return false;
183 }
184
185 let mut components = UnionFind::<usize>::new(n);
189
190 for (u, v) in graph.edges() {
191 if config[u] != config[v] {
192 continue;
194 }
195 if !components.union(u, v) {
197 return false; }
199 }
200
201 true
202}
203
204crate::declare_variants! {
205 default PartitionIntoForests<SimpleGraph> => "num_forests^num_vertices",
206}
207
208crate::register_brute_force! {
209 PartitionIntoForests<SimpleGraph>,
210}
211
212#[cfg(feature = "example-db")]
213pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
214 vec![crate::example_db::specs::ModelExampleSpec {
215 id: "partition_into_forests_simplegraph",
216 instance: Box::new(PartitionIntoForests::new(
217 SimpleGraph::new(
218 6,
219 vec![(0, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 5), (5, 3)],
220 ),
221 2,
222 )),
223 optimal_config: serde_json::json!(vec![0, 1, 1, 0, 1, 1]),
226 optimal_value: serde_json::json!(true),
227 }]
228}
229
230#[cfg(test)]
231#[path = "../../unit_tests/models/graph/partition_into_forests.rs"]
232mod tests;