Skip to main content

problemreductions/models/graph/
partition_into_cliques.rs

1//! Partition Into Cliques problem implementation.
2//!
3//! Given a graph G = (V, E) and a positive integer K <= |V|, determine whether
4//! the vertex set can be partitioned into k <= K groups such that the subgraph
5//! induced by each group is a complete subgraph (clique).
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: "PartitionIntoCliques",
16        display_name: "Partition into Cliques",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20        ],
21        category: crate::registry::ProblemCategory::Graph,
22        module_path: module_path!(),
23        description: "Partition vertices into K groups each inducing a clique",
24        fields: &[
25            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
26            FieldInfo { name: "num_cliques", type_name: "usize", description: "num_cliques: maximum number of clique groups K (>= 1)" },
27        ],
28    }
29}
30
31/// The Partition Into Cliques problem.
32///
33/// Given a graph G = (V, E) and a positive integer K <= |V|, determine whether
34/// the vertices can be partitioned into k <= K groups V_1, ..., V_k such that
35/// the subgraph induced by each V_i is a complete subgraph (clique).
36///
37/// # Type Parameters
38///
39/// * `G` - Graph type (e.g., SimpleGraph)
40///
41/// # Example
42///
43/// ```
44/// use problemreductions::models::graph::PartitionIntoCliques;
45/// use problemreductions::topology::SimpleGraph;
46/// use problemreductions::{Problem, BruteForce};
47///
48/// // Two triangles: 0-1-2-0 and 3-4-5-3
49/// let graph = SimpleGraph::new(6, vec![(0,1),(0,2),(1,2),(3,4),(3,5),(4,5)]);
50/// let problem = PartitionIntoCliques::new(graph, 3);
51///
52/// let solver = BruteForce::new();
53/// let solution = solver.solve(&problem).unwrap();
54/// assert!(solution.is_some());
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
58pub struct PartitionIntoCliques<G> {
59    /// The underlying graph.
60    graph: G,
61    /// Maximum number of clique groups.
62    num_cliques: usize,
63}
64
65impl<G: Graph> PartitionIntoCliques<G> {
66    /// Create a new Partition Into Cliques instance.
67    ///
68    /// # Panics
69    /// Panics if `num_cliques` is zero or greater than `graph.num_vertices()`.
70    pub fn new(graph: G, num_cliques: usize) -> Self {
71        assert!(num_cliques >= 1, "num_cliques must be at least 1");
72        assert!(
73            num_cliques <= graph.num_vertices(),
74            "num_cliques must be at most num_vertices"
75        );
76        Self { graph, num_cliques }
77    }
78
79    /// Get a reference to the underlying graph.
80    pub fn graph(&self) -> &G {
81        &self.graph
82    }
83
84    /// Get the maximum number of clique groups.
85    pub fn num_cliques(&self) -> usize {
86        self.num_cliques
87    }
88
89    /// Get the number of vertices in the underlying graph.
90    pub fn num_vertices(&self) -> usize {
91        self.graph.num_vertices()
92    }
93
94    /// Get the number of edges in the underlying graph.
95    pub fn num_edges(&self) -> usize {
96        self.graph.num_edges()
97    }
98}
99
100impl<G> Problem for PartitionIntoCliques<G>
101where
102    G: Graph + VariantParam,
103{
104    const NAME: &'static str = "PartitionIntoCliques";
105    type Solution = Vec<usize>;
106    type Value = crate::types::Or;
107
108    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
109
110    fn variant() -> Vec<(&'static str, &'static str)> {
111        crate::variant_params![G]
112    }
113
114    fn evaluate(
115        &self,
116        config: &Self::Solution,
117    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
118        if config.len() != self.graph.num_vertices() {
119            return Err(crate::traits::EvaluationError::InvalidConfiguration(
120                "partition assignment length does not match the graph vertices".into(),
121            ));
122        }
123        if config.iter().any(|&part| part >= self.num_cliques) {
124            return Err(crate::traits::EvaluationError::InvalidConfiguration(
125                "partition assignment contains an out-of-range clique".into(),
126            ));
127        }
128        Ok({
129            crate::types::Or(is_valid_clique_partition(
130                &self.graph,
131                self.num_cliques,
132                config,
133            ))
134        })
135    }
136}
137
138impl<G> crate::solvers::BruteForceProblem for PartitionIntoCliques<G>
139where
140    G: Graph + VariantParam,
141{
142    fn dimensions(&self) -> Vec<usize> {
143        vec![self.num_cliques; self.graph.num_vertices()]
144    }
145}
146
147/// Check whether `config` is a valid K-clique partition of `graph`.
148fn is_valid_clique_partition<G: Graph>(graph: &G, num_cliques: usize, config: &[usize]) -> bool {
149    let n = graph.num_vertices();
150
151    // Basic validity checks
152    if config.len() != n {
153        return false;
154    }
155    if config.iter().any(|&c| c >= num_cliques) {
156        return false;
157    }
158
159    // For each group, collect the vertices and check all pairs are adjacent.
160    for group in 0..num_cliques {
161        let members: Vec<usize> = (0..n).filter(|&v| config[v] == group).collect();
162        for i in 0..members.len() {
163            for j in (i + 1)..members.len() {
164                if !graph.has_edge(members[i], members[j]) {
165                    return false;
166                }
167            }
168        }
169    }
170
171    true
172}
173
174crate::declare_variants! {
175    default PartitionIntoCliques<SimpleGraph> => "2^num_vertices",
176}
177
178crate::register_brute_force! {
179    PartitionIntoCliques<SimpleGraph>,
180}
181
182#[cfg(feature = "example-db")]
183pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
184    vec![crate::example_db::specs::ModelExampleSpec {
185        id: "partition_into_cliques_simplegraph",
186        instance: Box::new(PartitionIntoCliques::new(
187            SimpleGraph::new(
188                6,
189                vec![
190                    (0, 1),
191                    (0, 2),
192                    (1, 2),
193                    (3, 4),
194                    (3, 5),
195                    (4, 5),
196                    (0, 3),
197                    (1, 4),
198                    (2, 5),
199                ],
200            ),
201            3,
202        )),
203        optimal_config: serde_json::json!(vec![0, 0, 0, 1, 1, 1]),
204        optimal_value: serde_json::json!(true),
205    }]
206}
207
208#[cfg(test)]
209#[path = "../../unit_tests/models/graph/partition_into_cliques.rs"]
210mod tests;