Skip to main content

problemreductions/models/graph/
partition_into_triangles.rs

1//! Partition Into Triangles problem implementation.
2//!
3//! Given a graph G = (V, E) where |V| = 3q, determine whether V can be
4//! partitioned into q triples, each forming a triangle (K3) in G.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::variant::VariantParam;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "PartitionIntoTriangles",
15        display_name: "Partition Into Triangles",
16        aliases: &[],
17        dimensions: &[
18            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
19        ],
20        category: crate::registry::ProblemCategory::Graph,
21        module_path: module_path!(),
22        description: "Partition vertices into triangles (K3 subgraphs)",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E) with |V| divisible by 3" },
25        ],
26    }
27}
28
29/// The Partition Into Triangles problem.
30///
31/// Given a graph G = (V, E) where |V| = 3q, determine whether V can be
32/// partitioned into q triples, each forming a triangle (K3) in G.
33///
34/// # Type Parameters
35///
36/// * `G` - Graph type (e.g., SimpleGraph)
37///
38/// # Example
39///
40/// ```
41/// use problemreductions::models::graph::PartitionIntoTriangles;
42/// use problemreductions::topology::SimpleGraph;
43/// use problemreductions::{Problem, BruteForce};
44///
45/// // Triangle graph: 3 vertices forming a single triangle
46/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]);
47/// let problem = PartitionIntoTriangles::new(graph);
48///
49/// let solver = BruteForce::new();
50/// let solution = solver.solve(&problem).unwrap();
51/// assert!(solution.is_some());
52/// ```
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
55pub struct PartitionIntoTriangles<G> {
56    /// The underlying graph.
57    graph: G,
58}
59
60impl<G: Graph> PartitionIntoTriangles<G> {
61    /// Create a new Partition Into Triangles problem from a graph.
62    ///
63    /// # Panics
64    /// Panics if the number of vertices is not divisible by 3.
65    pub fn new(graph: G) -> Self {
66        assert!(
67            graph.num_vertices().is_multiple_of(3),
68            "Number of vertices ({}) must be divisible by 3",
69            graph.num_vertices()
70        );
71        Self { graph }
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 vertices in the underlying graph.
80    pub fn num_vertices(&self) -> usize {
81        self.graph.num_vertices()
82    }
83
84    /// Get the number of edges in the underlying graph.
85    pub fn num_edges(&self) -> usize {
86        self.graph.num_edges()
87    }
88}
89
90impl<G> Problem for PartitionIntoTriangles<G>
91where
92    G: Graph + VariantParam,
93{
94    const NAME: &'static str = "PartitionIntoTriangles";
95    type Solution = Vec<usize>;
96    type Value = crate::types::Or;
97
98    crate::problem_parameters![("num_vertices", num_vertices),];
99
100    fn variant() -> Vec<(&'static str, &'static str)> {
101        crate::variant_params![G]
102    }
103
104    fn evaluate(
105        &self,
106        config: &Self::Solution,
107    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
108        Ok({
109            crate::types::Or({
110                let n = self.graph.num_vertices();
111                let q = n / 3;
112
113                // Check config length
114                if config.len() != n {
115                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
116                        "partition assignment length does not match the graph vertices".into(),
117                    ));
118                }
119
120                // Check all values are in range [0, q)
121                if config.iter().any(|&c| c >= q) {
122                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
123                        "partition assignment contains an out-of-range group".into(),
124                    ));
125                }
126
127                // Count vertices per group
128                let mut counts = vec![0usize; q];
129                for &c in config {
130                    counts[c] += 1;
131                }
132
133                // Each group must have exactly 3 vertices
134                if counts.iter().any(|&c| c != 3) {
135                    return Ok(crate::types::Or(false));
136                }
137
138                // Build per-group vertex lists in a single pass over config.
139                let mut group_verts = vec![[0usize; 3]; q];
140                let mut group_pos = vec![0usize; q];
141
142                for (v, &g) in config.iter().enumerate() {
143                    let pos = group_pos[g];
144                    group_verts[g][pos] = v;
145                    group_pos[g] = pos + 1;
146                }
147
148                // Check each group forms a triangle
149                for verts in &group_verts {
150                    if !self.graph.has_edge(verts[0], verts[1]) {
151                        return Ok(crate::types::Or(false));
152                    }
153                    if !self.graph.has_edge(verts[0], verts[2]) {
154                        return Ok(crate::types::Or(false));
155                    }
156                    if !self.graph.has_edge(verts[1], verts[2]) {
157                        return Ok(crate::types::Or(false));
158                    }
159                }
160
161                true
162            })
163        })
164    }
165}
166
167impl<G> crate::solvers::BruteForceProblem for PartitionIntoTriangles<G>
168where
169    G: Graph + VariantParam,
170{
171    fn dimensions(&self) -> Vec<usize> {
172        let q = self.graph.num_vertices() / 3;
173        vec![q; self.graph.num_vertices()]
174    }
175}
176
177crate::declare_variants! {
178    default PartitionIntoTriangles<SimpleGraph> => "2^num_vertices",
179}
180
181crate::register_brute_force! {
182    PartitionIntoTriangles<SimpleGraph>,
183}
184
185#[cfg(feature = "example-db")]
186pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
187    vec![crate::example_db::specs::ModelExampleSpec {
188        id: "partition_into_triangles_simplegraph",
189        instance: Box::new(PartitionIntoTriangles::new(SimpleGraph::new(
190            6,
191            vec![(0, 1), (0, 2), (1, 2), (3, 4), (3, 5), (4, 5), (0, 3)],
192        ))),
193        optimal_config: serde_json::json!(vec![0, 0, 0, 1, 1, 1]),
194        optimal_value: serde_json::json!(true),
195    }]
196}
197
198#[cfg(test)]
199#[path = "../../unit_tests/models/graph/partition_into_triangles.rs"]
200mod tests;