Skip to main content

problemreductions/models/graph/
partition_into_paths_of_length_2.rs

1//! Partition into Paths of Length 2 problem implementation.
2//!
3//! Given a graph G = (V, E) with |V| = 3q, determine whether V can be partitioned
4//! into q disjoint sets of three vertices each, such that each set induces at least
5//! two edges (i.e., a path of length 2 or a triangle).
6//!
7//! This is a classical NP-complete problem from Garey & Johnson, Chapter 3, Section 3.3, p.76.
8
9use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
10use crate::topology::{Graph, SimpleGraph};
11use crate::traits::Problem;
12use crate::variant::VariantParam;
13use serde::{Deserialize, Serialize};
14
15inventory::submit! {
16    ProblemSchemaEntry {
17        name: "PartitionIntoPathsOfLength2",
18        display_name: "Partition into Paths of Length 2",
19        aliases: &[],
20        dimensions: &[
21            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
22        ],
23        module_path: module_path!(),
24        description: "Partition vertices into triples each inducing at least two edges (P3 or triangle)",
25        fields: &[
26            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E) with |V| divisible by 3" },
27        ],
28    }
29}
30
31/// Partition into Paths of Length 2 problem.
32///
33/// Given a graph G = (V, E) with |V| = 3q for a positive integer q,
34/// determine whether V can be partitioned into q disjoint sets
35/// V_1, V_2, ..., V_q of three vertices each, such that each V_t
36/// induces at least two edges in G.
37///
38/// Each triple must form either a path of length 2 (exactly 2 edges)
39/// or a triangle (all 3 edges).
40///
41/// # Type Parameters
42///
43/// * `G` - Graph type (e.g., SimpleGraph)
44///
45/// # Example
46///
47/// ```
48/// use problemreductions::models::graph::PartitionIntoPathsOfLength2;
49/// use problemreductions::topology::SimpleGraph;
50/// use problemreductions::{Problem, Solver, BruteForce};
51///
52/// // 6-vertex graph with two P3 paths: 0-1-2 and 3-4-5
53/// let graph = SimpleGraph::new(6, vec![(0, 1), (1, 2), (3, 4), (4, 5)]);
54/// let problem = PartitionIntoPathsOfLength2::new(graph);
55///
56/// let solver = BruteForce::new();
57/// let solution = solver.find_witness(&problem);
58/// assert!(solution.is_some());
59/// ```
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
62pub struct PartitionIntoPathsOfLength2<G> {
63    /// The underlying graph.
64    graph: G,
65}
66
67impl<G: Graph> PartitionIntoPathsOfLength2<G> {
68    /// Create a new PartitionIntoPathsOfLength2 problem from a graph.
69    ///
70    /// # Panics
71    /// Panics if `graph.num_vertices()` is not divisible by 3.
72    pub fn new(graph: G) -> Self {
73        assert_eq!(
74            graph.num_vertices() % 3,
75            0,
76            "Number of vertices ({}) must be divisible by 3",
77            graph.num_vertices()
78        );
79        Self { graph }
80    }
81
82    /// Get a reference to the underlying graph.
83    pub fn graph(&self) -> &G {
84        &self.graph
85    }
86
87    /// Get the number of vertices in the graph.
88    pub fn num_vertices(&self) -> usize {
89        self.graph.num_vertices()
90    }
91
92    /// Get the number of edges in the graph.
93    pub fn num_edges(&self) -> usize {
94        self.graph.num_edges()
95    }
96
97    /// Get q = |V| / 3, the number of groups in the partition.
98    pub fn num_groups(&self) -> usize {
99        self.graph.num_vertices() / 3
100    }
101
102    /// Check if a configuration represents a valid partition.
103    ///
104    /// A valid configuration assigns each vertex to a group (0..q-1) such that:
105    /// 1. Each group contains exactly 3 vertices.
106    /// 2. Each group induces at least 2 edges.
107    pub fn is_valid_partition(&self, config: &[usize]) -> bool {
108        let n = self.graph.num_vertices();
109        let q = self.num_groups();
110
111        if config.len() != n {
112            return false;
113        }
114
115        // Check all assignments are in range
116        if config.iter().any(|&g| g >= q) {
117            return false;
118        }
119
120        // Count vertices per group
121        let mut group_sizes = vec![0usize; q];
122        for &g in config {
123            group_sizes[g] += 1;
124        }
125
126        // Each group must have exactly 3 vertices
127        if group_sizes.iter().any(|&s| s != 3) {
128            return false;
129        }
130
131        // Check each group induces at least 2 edges (single pass over edges)
132        let mut group_edge_counts = vec![0usize; q];
133        for (u, v) in self.graph.edges() {
134            if config[u] == config[v] {
135                group_edge_counts[config[u]] += 1;
136            }
137        }
138        if group_edge_counts.iter().any(|&c| c < 2) {
139            return false;
140        }
141
142        true
143    }
144}
145
146impl<G> Problem for PartitionIntoPathsOfLength2<G>
147where
148    G: Graph + VariantParam,
149{
150    const NAME: &'static str = "PartitionIntoPathsOfLength2";
151    type Value = crate::types::Or;
152
153    fn variant() -> Vec<(&'static str, &'static str)> {
154        crate::variant_params![G]
155    }
156
157    fn dims(&self) -> Vec<usize> {
158        let q = self.num_groups();
159        vec![q; self.graph.num_vertices()]
160    }
161
162    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
163        crate::types::Or(self.is_valid_partition(config))
164    }
165}
166
167crate::declare_variants! {
168    default PartitionIntoPathsOfLength2<SimpleGraph> => "3^num_vertices",
169}
170
171#[cfg(feature = "example-db")]
172pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
173    vec![crate::example_db::specs::ModelExampleSpec {
174        id: "partition_into_paths_of_length_2_simplegraph",
175        instance: Box::new(PartitionIntoPathsOfLength2::new(SimpleGraph::new(
176            9,
177            vec![
178                (0, 1),
179                (1, 2),
180                (3, 4),
181                (4, 5),
182                (6, 7),
183                (7, 8),
184                (0, 3),
185                (2, 5),
186                (3, 6),
187                (5, 8),
188                (1, 4),
189                (4, 7),
190            ],
191        ))),
192        optimal_config: vec![0, 0, 0, 1, 1, 1, 2, 2, 2],
193        optimal_value: serde_json::json!(true),
194    }]
195}
196
197#[cfg(test)]
198#[path = "../../unit_tests/models/graph/partition_into_paths_of_length_2.rs"]
199mod tests;