Skip to main content

problemreductions/models/set/
two_dimensional_consecutive_sets.rs

1//! 2-Dimensional Consecutive Sets problem implementation.
2//!
3//! Given a finite alphabet Σ and a collection C of subsets of Σ, determine whether
4//! Σ can be partitioned into disjoint ordered groups X₁, ..., Xₖ such that each
5//! group has at most one element from each subset, and each subset's elements
6//! are spread across consecutive groups.
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::de::Error as _;
11use serde::{Deserialize, Serialize};
12use std::collections::HashSet;
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "TwoDimensionalConsecutiveSets",
17        display_name: "2-Dimensional Consecutive Sets",
18        aliases: &[],
19        dimensions: &[],
20        module_path: module_path!(),
21        description: "Determine if alphabet can be partitioned into ordered groups with intersection and consecutiveness constraints",
22        fields: &[
23            FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet (elements are 0..alphabet_size-1)" },
24            FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of the alphabet" },
25        ],
26    }
27}
28
29/// 2-Dimensional Consecutive Sets problem.
30///
31/// Given a finite alphabet Σ = {0, 1, ..., n-1} and a collection C = {Σ₁, ..., Σₘ}
32/// of subsets of Σ, determine whether Σ can be partitioned into disjoint sets
33/// X₁, X₂, ..., Xₖ such that:
34/// 1. Each Xᵢ has at most one element in common with each Σⱼ (intersection constraint)
35/// 2. For each Σⱼ, its elements are spread across |Σⱼ| consecutive groups (consecutiveness)
36///
37/// This is NP-complete (Lipski, 1977) via transformation from Graph 3-Colorability.
38///
39/// # Example
40///
41/// ```
42/// use problemreductions::models::set::TwoDimensionalConsecutiveSets;
43/// use problemreductions::{Problem, Solver, BruteForce};
44///
45/// // Alphabet: {0,1,2,3,4,5}
46/// // Subsets: {0,1,2}, {3,4,5}, {1,3}, {2,4}, {0,5}
47/// let problem = TwoDimensionalConsecutiveSets::new(
48///     6,
49///     vec![vec![0, 1, 2], vec![3, 4, 5], vec![1, 3], vec![2, 4], vec![0, 5]],
50/// );
51///
52/// // Partition: X0={0}, X1={1,5}, X2={2,3}, X3={4}
53/// // config[i] = group index of symbol i
54/// assert!(problem.evaluate(&[0, 1, 2, 2, 3, 1]));
55/// ```
56#[derive(Debug, Clone, Serialize)]
57pub struct TwoDimensionalConsecutiveSets {
58    /// Size of the alphabet (elements are 0..alphabet_size-1).
59    alphabet_size: usize,
60    /// Collection of subsets, each a sorted list of alphabet elements.
61    subsets: Vec<Vec<usize>>,
62}
63
64#[derive(Debug, Deserialize)]
65struct TwoDimensionalConsecutiveSetsUnchecked {
66    alphabet_size: usize,
67    subsets: Vec<Vec<usize>>,
68}
69
70fn validate(alphabet_size: usize, subsets: &[Vec<usize>]) -> Result<(), String> {
71    if alphabet_size == 0 {
72        return Err("Alphabet size must be positive".to_string());
73    }
74
75    for (i, subset) in subsets.iter().enumerate() {
76        let mut seen = HashSet::new();
77        for &elem in subset {
78            if elem >= alphabet_size {
79                return Err(format!(
80                    "Subset {} contains element {} which is outside alphabet of size {}",
81                    i, elem, alphabet_size
82                ));
83            }
84            if !seen.insert(elem) {
85                return Err(format!("Subset {} contains duplicate element {}", i, elem));
86            }
87        }
88    }
89
90    Ok(())
91}
92
93impl<'de> Deserialize<'de> for TwoDimensionalConsecutiveSets {
94    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95    where
96        D: serde::Deserializer<'de>,
97    {
98        let unchecked = TwoDimensionalConsecutiveSetsUnchecked::deserialize(deserializer)?;
99        Self::try_new(unchecked.alphabet_size, unchecked.subsets).map_err(D::Error::custom)
100    }
101}
102
103impl TwoDimensionalConsecutiveSets {
104    /// Create a new 2-Dimensional Consecutive Sets instance, returning validation errors.
105    pub fn try_new(alphabet_size: usize, subsets: Vec<Vec<usize>>) -> Result<Self, String> {
106        validate(alphabet_size, &subsets)?;
107        let subsets = subsets
108            .into_iter()
109            .map(|mut s| {
110                s.sort();
111                s
112            })
113            .collect();
114        Ok(Self {
115            alphabet_size,
116            subsets,
117        })
118    }
119
120    /// Create a new 2-Dimensional Consecutive Sets instance.
121    ///
122    /// # Panics
123    ///
124    /// Panics if `alphabet_size` is 0, if any subset contains elements
125    /// outside the alphabet, or if any subset has duplicate elements.
126    pub fn new(alphabet_size: usize, subsets: Vec<Vec<usize>>) -> Self {
127        Self::try_new(alphabet_size, subsets).unwrap_or_else(|message| panic!("{message}"))
128    }
129
130    /// Get the alphabet size.
131    pub fn alphabet_size(&self) -> usize {
132        self.alphabet_size
133    }
134
135    /// Get the number of subsets.
136    pub fn num_subsets(&self) -> usize {
137        self.subsets.len()
138    }
139
140    /// Get the subsets.
141    pub fn subsets(&self) -> &[Vec<usize>] {
142        &self.subsets
143    }
144}
145
146impl Problem for TwoDimensionalConsecutiveSets {
147    const NAME: &'static str = "TwoDimensionalConsecutiveSets";
148    type Value = crate::types::Or;
149
150    fn dims(&self) -> Vec<usize> {
151        vec![self.alphabet_size; self.alphabet_size]
152    }
153
154    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
155        crate::types::Or({
156            if config.len() != self.alphabet_size {
157                return crate::types::Or(false);
158            }
159            if config.iter().any(|&v| v >= self.alphabet_size) {
160                return crate::types::Or(false);
161            }
162
163            // Empty labels do not create gaps in the partition order, so compress used labels first.
164            let mut used = vec![false; self.alphabet_size];
165            for &group in config {
166                used[group] = true;
167            }
168            let mut dense_labels = vec![0; self.alphabet_size];
169            let mut next_label = 0;
170            for (label, is_used) in used.into_iter().enumerate() {
171                if is_used {
172                    dense_labels[label] = next_label;
173                    next_label += 1;
174                }
175            }
176
177            for subset in &self.subsets {
178                if subset.is_empty() {
179                    continue;
180                }
181                let groups: Vec<usize> = subset.iter().map(|&s| dense_labels[config[s]]).collect();
182
183                // Intersection constraint: all group indices must be distinct
184                let unique: HashSet<usize> = groups.iter().copied().collect();
185                if unique.len() != subset.len() {
186                    return crate::types::Or(false);
187                }
188
189                // Consecutiveness: group indices must form a contiguous range
190                let min_g = *unique.iter().min().unwrap();
191                let max_g = *unique.iter().max().unwrap();
192                if max_g - min_g + 1 != subset.len() {
193                    return crate::types::Or(false);
194                }
195            }
196
197            true
198        })
199    }
200
201    fn variant() -> Vec<(&'static str, &'static str)> {
202        crate::variant_params![]
203    }
204}
205
206crate::declare_variants! {
207    default TwoDimensionalConsecutiveSets => "alphabet_size^alphabet_size",
208}
209
210#[cfg(feature = "example-db")]
211pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
212    vec![crate::example_db::specs::ModelExampleSpec {
213        id: "two_dimensional_consecutive_sets",
214        instance: Box::new(TwoDimensionalConsecutiveSets::new(
215            6,
216            vec![
217                vec![0, 1, 2],
218                vec![3, 4, 5],
219                vec![1, 3],
220                vec![2, 4],
221                vec![0, 5],
222            ],
223        )),
224        optimal_config: vec![0, 1, 2, 2, 3, 1],
225        optimal_value: serde_json::json!(true),
226    }]
227}
228
229#[cfg(test)]
230#[path = "../../unit_tests/models/set/two_dimensional_consecutive_sets.rs"]
231mod tests;