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