Skip to main content

problemreductions/models/graph/
minimum_covering_by_cliques.rs

1//! Minimum Covering by Cliques problem implementation.
2//!
3//! Given a graph G = (V, E), find a minimum number of cliques whose union
4//! covers every edge in E.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "MinimumCoveringByCliques",
16        display_name: "Minimum Covering by Cliques",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20        ],
21        module_path: module_path!(),
22        description: "Find minimum number of cliques covering all edges",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
25        ],
26    }
27}
28
29/// The Minimum Covering by Cliques problem.
30///
31/// Given a graph G = (V, E), find a collection of cliques C_1, ..., C_k
32/// in G such that every edge is contained in at least one clique,
33/// and k is minimized.
34///
35/// Variables: one per edge, each selecting which clique group covers it.
36/// Each edge can be assigned to one of at most |E| groups (upper bound).
37///
38/// # Type Parameters
39///
40/// * `G` - The graph type (e.g., `SimpleGraph`)
41///
42/// # Example
43///
44/// ```
45/// use problemreductions::models::graph::MinimumCoveringByCliques;
46/// use problemreductions::topology::SimpleGraph;
47/// use problemreductions::{Problem, Solver, BruteForce};
48///
49/// // Triangle: 3 edges can be covered by 1 clique
50/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]);
51/// let problem = MinimumCoveringByCliques::new(graph);
52///
53/// let solver = BruteForce::new();
54/// let solution = solver.find_witness(&problem).unwrap();
55/// let value = problem.evaluate(&solution);
56/// assert_eq!(value, problemreductions::types::Min(Some(1)));
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct MinimumCoveringByCliques<G> {
60    /// The underlying graph.
61    graph: G,
62}
63
64impl<G: Graph> MinimumCoveringByCliques<G> {
65    /// Create a MinimumCoveringByCliques problem from a graph.
66    pub fn new(graph: G) -> Self {
67        Self { graph }
68    }
69
70    /// Get a reference to the underlying graph.
71    pub fn graph(&self) -> &G {
72        &self.graph
73    }
74
75    /// Get the number of vertices in the underlying graph.
76    pub fn num_vertices(&self) -> usize {
77        self.graph.num_vertices()
78    }
79
80    /// Get the number of edges in the underlying graph.
81    pub fn num_edges(&self) -> usize {
82        self.graph.num_edges()
83    }
84
85    /// Check whether a configuration is a valid edge clique cover.
86    ///
87    /// For each group index used, the edges assigned to it must form a clique:
88    /// all vertices touched by those edges must be pairwise adjacent.
89    pub fn is_valid_cover(&self, config: &[usize]) -> bool {
90        let edges = self.graph.edges();
91        let num_edges = edges.len();
92
93        if config.len() != num_edges {
94            return false;
95        }
96
97        // Group edges by their assigned clique in a single pass.
98        let max_group = match config.iter().max() {
99            Some(&m) => m,
100            None => return true, // no edges → trivially valid
101        };
102
103        let mut groups: Vec<HashSet<usize>> = vec![HashSet::new(); max_group + 1];
104        for (idx, &group) in config.iter().enumerate() {
105            let (u, v) = edges[idx];
106            groups[group].insert(u);
107            groups[group].insert(v);
108        }
109
110        // Check that each group's vertices form a clique.
111        for vertices in &groups {
112            let verts: Vec<usize> = vertices.iter().copied().collect();
113            for i in 0..verts.len() {
114                for j in (i + 1)..verts.len() {
115                    if !self.graph.has_edge(verts[i], verts[j]) {
116                        return false;
117                    }
118                }
119            }
120        }
121
122        true
123    }
124}
125
126impl<G> Problem for MinimumCoveringByCliques<G>
127where
128    G: Graph + crate::variant::VariantParam,
129{
130    const NAME: &'static str = "MinimumCoveringByCliques";
131    type Value = Min<usize>;
132
133    fn variant() -> Vec<(&'static str, &'static str)> {
134        crate::variant_params![G]
135    }
136
137    fn dims(&self) -> Vec<usize> {
138        vec![self.graph.num_edges(); self.graph.num_edges()]
139    }
140
141    fn evaluate(&self, config: &[usize]) -> Min<usize> {
142        if config.len() != self.graph.num_edges() {
143            return Min(None);
144        }
145        if self.graph.num_edges() == 0 {
146            return Min(Some(0));
147        }
148        if !self.is_valid_cover(config) {
149            return Min(None);
150        }
151        let distinct_groups: HashSet<usize> = config.iter().copied().collect();
152        Min(Some(distinct_groups.len()))
153    }
154}
155
156crate::declare_variants! {
157    default MinimumCoveringByCliques<SimpleGraph> => "2^num_edges",
158}
159
160#[cfg(feature = "example-db")]
161pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
162    // 6 vertices, 9 edges:
163    // (0,1),(1,2),(2,3),(3,0),(0,2),(4,0),(4,1),(5,2),(5,3)
164    // Optimal: 4 cliques
165    // edges 0,1,4 -> group 0 (clique {0,1,2})
166    // edges 2,3 -> group 1 (clique {0,2,3}... wait, (2,3) and (3,0) -> vertices {0,2,3})
167    // edges 5,6 -> group 2 (clique {0,1,4})
168    // edges 7,8 -> group 3 (clique {2,3,5})
169    // Config: [0, 0, 1, 1, 0, 2, 2, 3, 3]
170    vec![crate::example_db::specs::ModelExampleSpec {
171        id: "minimum_covering_by_cliques_simplegraph",
172        instance: Box::new(MinimumCoveringByCliques::new(SimpleGraph::new(
173            6,
174            vec![
175                (0, 1),
176                (1, 2),
177                (2, 3),
178                (3, 0),
179                (0, 2),
180                (4, 0),
181                (4, 1),
182                (5, 2),
183                (5, 3),
184            ],
185        ))),
186        optimal_config: vec![0, 0, 1, 1, 0, 2, 2, 3, 3],
187        optimal_value: serde_json::json!(4),
188    }]
189}
190
191#[cfg(test)]
192#[path = "../../unit_tests/models/graph/minimum_covering_by_cliques.rs"]
193mod tests;