Skip to main content

problemreductions/models/set/
exact_cover_by_3_sets.rs

1//! Exact Cover by 3-Sets (X3C) problem implementation.
2//!
3//! Given a universe X with |X| = 3q elements and a collection C of 3-element
4//! subsets of X, determine if C contains an exact cover -- a subcollection of
5//! q disjoint triples covering every element exactly once.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10use std::collections::HashSet;
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "ExactCoverBy3Sets",
15        display_name: "Exact Cover by 3-Sets",
16        aliases: &["X3C"],
17        dimensions: &[],
18        module_path: module_path!(),
19        description: "Determine if a collection of 3-element subsets contains an exact cover",
20        fields: &[
21            FieldInfo { name: "universe_size", type_name: "usize", description: "Size of universe X (must be divisible by 3)" },
22            FieldInfo { name: "subsets", type_name: "Vec<[usize; 3]>", description: "Collection C of 3-element subsets of X" },
23        ],
24    }
25}
26
27/// Exact Cover by 3-Sets (X3C) problem.
28///
29/// Given a universe X = {0, 1, ..., 3q-1} and a collection C of 3-element
30/// subsets of X, determine if there exists a subcollection C' of exactly q
31/// subsets such that every element of X appears in exactly one member of C'.
32///
33/// This is a classical NP-complete problem (Karp, 1972), widely used as
34/// a source problem for NP-completeness reductions.
35///
36/// # Example
37///
38/// ```
39/// use problemreductions::models::set::ExactCoverBy3Sets;
40/// use problemreductions::{Problem, Solver, BruteForce};
41///
42/// // Universe: {0, 1, 2, 3, 4, 5} (q = 2)
43/// // Subsets: S0={0,1,2}, S1={3,4,5}, S2={0,3,4}
44/// let problem = ExactCoverBy3Sets::new(
45///     6,
46///     vec![[0, 1, 2], [3, 4, 5], [0, 3, 4]],
47/// );
48///
49/// let solver = BruteForce::new();
50/// let solutions = solver.find_all_witnesses(&problem);
51///
52/// // S0 and S1 form an exact cover
53/// assert_eq!(solutions.len(), 1);
54/// assert!(problem.evaluate(&solutions[0]));
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ExactCoverBy3Sets {
58    /// Size of the universe (elements are 0..universe_size, must be divisible by 3).
59    universe_size: usize,
60    /// Collection of 3-element subsets, each represented as a sorted triple of elements.
61    subsets: Vec<[usize; 3]>,
62}
63
64impl ExactCoverBy3Sets {
65    /// Create a new X3C problem.
66    ///
67    /// # Panics
68    ///
69    /// Panics if `universe_size` is not divisible by 3, or if any subset
70    /// contains duplicate elements or elements outside the universe.
71    pub fn new(universe_size: usize, subsets: Vec<[usize; 3]>) -> Self {
72        assert!(
73            universe_size.is_multiple_of(3),
74            "Universe size must be divisible by 3, got {}",
75            universe_size
76        );
77        let mut subsets = subsets;
78        for (i, subset) in subsets.iter_mut().enumerate() {
79            assert!(
80                subset[0] != subset[1] && subset[0] != subset[2] && subset[1] != subset[2],
81                "Subset {} contains duplicate elements: {:?}",
82                i,
83                subset
84            );
85            for &elem in subset.iter() {
86                assert!(
87                    elem < universe_size,
88                    "Subset {} contains element {} which is outside universe of size {}",
89                    i,
90                    elem,
91                    universe_size
92                );
93            }
94            subset.sort();
95        }
96        Self {
97            universe_size,
98            subsets,
99        }
100    }
101
102    /// Get the universe size.
103    pub fn universe_size(&self) -> usize {
104        self.universe_size
105    }
106
107    /// Get the number of subsets in the collection.
108    pub fn num_subsets(&self) -> usize {
109        self.subsets.len()
110    }
111
112    /// Get q = universe_size / 3, the number of subsets in any exact cover.
113    ///
114    /// `ExactCoverBy3Sets::new` enforces `universe_size % 3 == 0`, so this
115    /// division is always exact.
116    pub fn q(&self) -> usize {
117        self.universe_size / 3
118    }
119
120    /// Get the number of sets in the collection.
121    pub fn num_sets(&self) -> usize {
122        self.num_subsets()
123    }
124
125    /// Get the subsets.
126    pub fn subsets(&self) -> &[[usize; 3]] {
127        &self.subsets
128    }
129
130    /// Get the sets.
131    pub fn sets(&self) -> &[[usize; 3]] {
132        self.subsets()
133    }
134
135    /// Get a specific subset.
136    pub fn get_subset(&self, index: usize) -> Option<&[usize; 3]> {
137        self.subsets.get(index)
138    }
139
140    /// Check if a configuration is a valid exact cover.
141    ///
142    /// A valid exact cover selects exactly q = universe_size/3 subsets
143    /// that are pairwise disjoint and whose union equals the universe.
144    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
145        self.evaluate(config).0
146    }
147
148    /// Get the elements covered by the selected subsets.
149    pub fn covered_elements(&self, config: &[usize]) -> HashSet<usize> {
150        let mut covered = HashSet::new();
151        for (i, &selected) in config.iter().enumerate() {
152            if selected == 1 {
153                if let Some(subset) = self.subsets.get(i) {
154                    covered.extend(subset.iter().copied());
155                }
156            }
157        }
158        covered
159    }
160}
161
162impl Problem for ExactCoverBy3Sets {
163    const NAME: &'static str = "ExactCoverBy3Sets";
164    type Value = crate::types::Or;
165
166    fn dims(&self) -> Vec<usize> {
167        vec![2; self.subsets.len()]
168    }
169
170    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
171        crate::types::Or({
172            if config.len() != self.subsets.len() || config.iter().any(|&value| value > 1) {
173                return crate::types::Or(false);
174            }
175
176            let q = self.universe_size / 3;
177
178            // Count selected subsets
179            let selected_count: usize = config.iter().filter(|&&v| v == 1).sum();
180            if selected_count != q {
181                return crate::types::Or(false);
182            }
183
184            // Check that selected subsets are pairwise disjoint and cover everything
185            let mut covered = HashSet::with_capacity(self.universe_size);
186            for (i, &selected) in config.iter().enumerate() {
187                if selected == 1 {
188                    if let Some(subset) = self.subsets.get(i) {
189                        for &elem in subset {
190                            if !covered.insert(elem) {
191                                // Element already covered -- not disjoint
192                                return crate::types::Or(false);
193                            }
194                        }
195                    }
196                }
197            }
198
199            // Check all elements are covered
200            covered.len() == self.universe_size
201        })
202    }
203
204    fn variant() -> Vec<(&'static str, &'static str)> {
205        crate::variant_params![]
206    }
207}
208
209crate::declare_variants! {
210    default ExactCoverBy3Sets => "2^universe_size",
211}
212
213#[cfg(feature = "example-db")]
214pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
215    vec![crate::example_db::specs::ModelExampleSpec {
216        id: "exact_cover_by_3_sets",
217        instance: Box::new(ExactCoverBy3Sets::new(
218            9,
219            vec![
220                [0, 1, 2],
221                [0, 2, 4],
222                [3, 4, 5],
223                [3, 5, 7],
224                [6, 7, 8],
225                [1, 4, 6],
226                [2, 5, 8],
227            ],
228        )),
229        optimal_config: vec![1, 0, 1, 0, 1, 0, 0],
230        optimal_value: serde_json::json!(true),
231    }]
232}
233
234#[cfg(test)]
235#[path = "../../unit_tests/models/set/exact_cover_by_3_sets.rs"]
236mod tests;