Skip to main content

problemreductions/models/set/
three_dimensional_matching.rs

1//! Three-Dimensional Matching (3DM) problem implementation.
2//!
3//! Given disjoint sets W, X, Y each with q elements and a set M of triples
4//! (w, x, y) with w in W, x in X, y in Y, determine if there exists a
5//! matching M' of size q where no two triples agree in any coordinate.
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: "ThreeDimensionalMatching",
15        display_name: "Three-Dimensional Matching",
16        aliases: &["3DM"],
17        dimensions: &[],
18        category: crate::registry::ProblemCategory::Set,
19        module_path: module_path!(),
20        description: "Find a perfect matching in a tripartite hypergraph",
21        fields: &[
22            FieldInfo { name: "universe_size", type_name: "usize", description: "Size of each set W, X, Y (q)" },
23            FieldInfo { name: "triples", type_name: "Vec<(usize, usize, usize)>", description: "Set M of triples (w, x, y)" },
24        ],
25    }
26}
27
28/// Three-Dimensional Matching (3DM) problem.
29///
30/// Given disjoint sets W = {0, ..., q-1}, X = {0, ..., q-1}, Y = {0, ..., q-1}
31/// and a set M of triples (w, x, y) where w is in W, x is in X, y is in Y,
32/// determine if there exists a subset M' of M with |M'| = q such that no two
33/// triples in M' agree in any coordinate.
34///
35/// This is a classical NP-complete problem (Karp, 1972), closely related to
36/// Exact Cover by 3-Sets.
37///
38/// # Example
39///
40/// ```
41/// use problemreductions::models::set::ThreeDimensionalMatching;
42/// use problemreductions::{Problem, BruteForce};
43///
44/// // W = X = Y = {0, 1, 2} (q = 3)
45/// // Triples: (0,1,2), (1,0,1), (2,2,0), (0,0,0), (1,2,2)
46/// let problem = ThreeDimensionalMatching::new(
47///     3,
48///     vec![(0, 1, 2), (1, 0, 1), (2, 2, 0), (0, 0, 0), (1, 2, 2)],
49/// );
50///
51/// let solver = BruteForce::new();
52/// let solutions = solver.find_all_witnesses(&problem).unwrap();
53///
54/// // First three triples form a valid matching
55/// assert!(!solutions.is_empty());
56/// assert!(problem.evaluate(&solutions[0]).unwrap());
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ThreeDimensionalMatching {
60    /// Size of each set W, X, Y (elements are 0..universe_size).
61    universe_size: usize,
62    /// Set M of triples (w, x, y) where w, x, y are in 0..universe_size.
63    triples: Vec<(usize, usize, usize)>,
64}
65
66impl ThreeDimensionalMatching {
67    /// Create a new 3DM problem.
68    ///
69    /// # Panics
70    ///
71    /// Panics if any triple contains an element outside 0..universe_size.
72    pub fn new(universe_size: usize, triples: Vec<(usize, usize, usize)>) -> Self {
73        for (i, &(w, x, y)) in triples.iter().enumerate() {
74            assert!(
75                w < universe_size,
76                "Triple {} has w-coordinate {} which is outside 0..{}",
77                i,
78                w,
79                universe_size
80            );
81            assert!(
82                x < universe_size,
83                "Triple {} has x-coordinate {} which is outside 0..{}",
84                i,
85                x,
86                universe_size
87            );
88            assert!(
89                y < universe_size,
90                "Triple {} has y-coordinate {} which is outside 0..{}",
91                i,
92                y,
93                universe_size
94            );
95        }
96        Self {
97            universe_size,
98            triples,
99        }
100    }
101
102    /// Get the universe size (q).
103    pub fn universe_size(&self) -> usize {
104        self.universe_size
105    }
106
107    /// Get the number of triples in M.
108    pub fn num_triples(&self) -> usize {
109        self.triples.len()
110    }
111
112    /// Get the triples.
113    pub fn triples(&self) -> &[(usize, usize, usize)] {
114        &self.triples
115    }
116
117    /// Get a specific triple.
118    pub fn get_triple(&self, index: usize) -> Option<&(usize, usize, usize)> {
119        self.triples.get(index)
120    }
121}
122
123impl Problem for ThreeDimensionalMatching {
124    const NAME: &'static str = "ThreeDimensionalMatching";
125    type Solution = Vec<bool>;
126    type Value = crate::types::Or;
127
128    crate::problem_parameters![
129        ("num_triples", num_triples),
130        ("universe_size", universe_size),
131    ];
132
133    fn evaluate(
134        &self,
135        config: &Self::Solution,
136    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
137        Ok({
138            crate::types::Or({
139                if config.len() != self.triples.len() {
140                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
141                        "triple-selection length does not match the instance".into(),
142                    ));
143                }
144
145                // Count selected triples
146                let selected_count = config.iter().filter(|&&v| v).count();
147                if selected_count != self.universe_size {
148                    return Ok(crate::types::Or(false));
149                }
150
151                // Check that selected triples have all distinct coordinates
152                let mut used_w = HashSet::with_capacity(self.universe_size);
153                let mut used_x = HashSet::with_capacity(self.universe_size);
154                let mut used_y = HashSet::with_capacity(self.universe_size);
155
156                for (i, &selected) in config.iter().enumerate() {
157                    if selected {
158                        let (w, x, y) = self.triples[i];
159                        if !used_w.insert(w) {
160                            return Ok(crate::types::Or(false));
161                        }
162                        if !used_x.insert(x) {
163                            return Ok(crate::types::Or(false));
164                        }
165                        if !used_y.insert(y) {
166                            return Ok(crate::types::Or(false));
167                        }
168                    }
169                }
170
171                true
172            })
173        })
174    }
175
176    fn variant() -> Vec<(&'static str, &'static str)> {
177        crate::variant_params![]
178    }
179}
180
181impl crate::solvers::BruteForceProblem for ThreeDimensionalMatching {
182    fn dimensions(&self) -> Vec<usize> {
183        vec![2; self.triples.len()]
184    }
185}
186
187crate::declare_variants! {
188    default ThreeDimensionalMatching => "2^num_triples",
189}
190
191crate::register_brute_force! {
192    ThreeDimensionalMatching decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
193}
194
195#[cfg(feature = "example-db")]
196pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
197    vec![crate::example_db::specs::ModelExampleSpec {
198        id: "three_dimensional_matching",
199        instance: Box::new(ThreeDimensionalMatching::new(
200            3,
201            vec![(0, 1, 2), (1, 0, 1), (2, 2, 0), (0, 0, 0), (1, 2, 2)],
202        )),
203        optimal_config: serde_json::json!(vec![true, true, true, false, false]),
204        optimal_value: serde_json::json!(true),
205    }]
206}
207
208#[cfg(test)]
209#[path = "../../unit_tests/models/set/three_dimensional_matching.rs"]
210mod tests;