Skip to main content

problemreductions/models/graph/
partition_into_forests.rs

1//! Partition Into Forests problem implementation.
2//!
3//! Given a graph G = (V, E) and a positive integer K, determine whether the
4//! vertex set can be partitioned into K subsets such that the subgraph induced
5//! by each subset is a forest (acyclic graph).
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::variant::VariantParam;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "PartitionIntoForests",
16        display_name: "Partition into Forests",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20        ],
21        module_path: module_path!(),
22        description: "Partition vertices into K classes each inducing an acyclic subgraph",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
25            FieldInfo { name: "num_forests", type_name: "usize", description: "num_forests: number of forest classes K (>= 1)" },
26        ],
27    }
28}
29
30/// The Partition Into Forests problem.
31///
32/// Given a graph G = (V, E) and a positive integer K, determine whether the
33/// vertices can be partitioned into K classes V_1, ..., V_K such that the
34/// subgraph induced by each V_i is a forest (contains no cycle).
35///
36/// # Type Parameters
37///
38/// * `G` - Graph type (e.g., SimpleGraph)
39///
40/// # Example
41///
42/// ```
43/// use problemreductions::models::graph::PartitionIntoForests;
44/// use problemreductions::topology::SimpleGraph;
45/// use problemreductions::{Problem, Solver, BruteForce};
46///
47/// // Graph containing two triangles; K=2 forests suffice
48/// let graph = SimpleGraph::new(6, vec![(0,1),(1,2),(2,0),(2,3),(3,4),(4,5),(5,3)]);
49/// let problem = PartitionIntoForests::new(graph, 2);
50///
51/// let solver = BruteForce::new();
52/// let solution = solver.find_witness(&problem);
53/// assert!(solution.is_some());
54/// ```
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
57pub struct PartitionIntoForests<G> {
58    /// The underlying graph.
59    graph: G,
60    /// Number of forest classes.
61    num_forests: usize,
62}
63
64impl<G: Graph> PartitionIntoForests<G> {
65    /// Create a new Partition Into Forests instance.
66    ///
67    /// # Panics
68    /// Panics if `num_forests` is zero.
69    pub fn new(graph: G, num_forests: usize) -> Self {
70        assert!(num_forests >= 1, "num_forests must be at least 1");
71        Self { graph, num_forests }
72    }
73
74    /// Get a reference to the underlying graph.
75    pub fn graph(&self) -> &G {
76        &self.graph
77    }
78
79    /// Get the number of forest classes.
80    pub fn num_forests(&self) -> usize {
81        self.num_forests
82    }
83
84    /// Get the number of vertices in the underlying graph.
85    pub fn num_vertices(&self) -> usize {
86        self.graph.num_vertices()
87    }
88
89    /// Get the number of edges in the underlying graph.
90    pub fn num_edges(&self) -> usize {
91        self.graph.num_edges()
92    }
93}
94
95impl<G> Problem for PartitionIntoForests<G>
96where
97    G: Graph + VariantParam,
98{
99    const NAME: &'static str = "PartitionIntoForests";
100    type Value = crate::types::Or;
101
102    fn variant() -> Vec<(&'static str, &'static str)> {
103        crate::variant_params![G]
104    }
105
106    fn dims(&self) -> Vec<usize> {
107        vec![self.num_forests; self.graph.num_vertices()]
108    }
109
110    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
111        crate::types::Or(is_valid_forest_partition(
112            &self.graph,
113            self.num_forests,
114            config,
115        ))
116    }
117}
118
119/// Check whether `config` is a valid K-forest partition of `graph`.
120fn is_valid_forest_partition<G: Graph>(graph: &G, num_forests: usize, config: &[usize]) -> bool {
121    let n = graph.num_vertices();
122
123    // Basic validity checks
124    if config.len() != n {
125        return false;
126    }
127    if config.iter().any(|&c| c >= num_forests) {
128        return false;
129    }
130
131    // For each forest class, verify the induced subgraph is acyclic using union-find.
132    // An undirected graph is acyclic iff union-find never sees an edge (u, v) where
133    // u and v already share a component.
134    let mut parent: Vec<usize> = (0..n).collect();
135
136    fn find(parent: &mut Vec<usize>, x: usize) -> usize {
137        if parent[x] != x {
138            parent[x] = find(parent, parent[x]);
139        }
140        parent[x]
141    }
142
143    for (u, v) in graph.edges() {
144        if config[u] != config[v] {
145            // Edge crosses classes — not in any induced subgraph
146            continue;
147        }
148        // Both u and v are in the same class; check for cycle
149        let ru = find(&mut parent, u);
150        let rv = find(&mut parent, v);
151        if ru == rv {
152            return false; // Cycle detected
153        }
154        parent[ru] = rv; // Union
155    }
156
157    true
158}
159
160crate::declare_variants! {
161    default PartitionIntoForests<SimpleGraph> => "num_forests^num_vertices",
162}
163
164#[cfg(feature = "example-db")]
165pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
166    vec![crate::example_db::specs::ModelExampleSpec {
167        id: "partition_into_forests_simplegraph",
168        instance: Box::new(PartitionIntoForests::new(
169            SimpleGraph::new(
170                6,
171                vec![(0, 1), (1, 2), (2, 0), (2, 3), (3, 4), (4, 5), (5, 3)],
172            ),
173            2,
174        )),
175        // V0={0,3}: edges from graph in class 0: none among {0,3} → forest
176        // V1={1,2,4,5}: edges (1,2),(3,4) but 3∉V1; edges among V1: (1,2),(4,5) → path forest
177        optimal_config: vec![0, 1, 1, 0, 1, 1],
178        optimal_value: serde_json::json!(true),
179    }]
180}
181
182#[cfg(test)]
183#[path = "../../unit_tests/models/graph/partition_into_forests.rs"]
184mod tests;