Skip to main content

problemreductions/models/set/
set_splitting.rs

1//! Set Splitting problem implementation.
2//!
3//! Set Splitting asks whether a universe can be 2-colored so that every
4//! specified subset is non-monochromatic (contains both colors).
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use serde::{Deserialize, Serialize};
9
10inventory::submit! {
11    ProblemSchemaEntry {
12        name: "SetSplitting",
13        display_name: "Set Splitting",
14        aliases: &[],
15        dimensions: &[],
16        module_path: module_path!(),
17        description: "Partition a universe into two parts so that every subset is non-monochromatic",
18        fields: &[
19            FieldInfo { name: "universe_size", type_name: "usize", description: "universe_size" },
20            FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Subsets that must each contain elements from both parts" },
21        ],
22    }
23}
24
25/// The Set Splitting problem.
26///
27/// Given a finite universe $U = \{0, \ldots, n-1\}$ and a collection
28/// $\mathcal{C}$ of subsets of $U$, decide whether there exists a
29/// 2-coloring (partition into $S_1$ and $S_2$) of $U$ such that every
30/// subset in $\mathcal{C}$ is non-monochromatic, i.e., contains at
31/// least one element from each part.
32///
33/// # Example
34///
35/// ```
36/// use problemreductions::models::set::SetSplitting;
37/// use problemreductions::{Problem, Solver, BruteForce};
38///
39/// // Universe {0,1,2,3,4,5}, subsets that all must be split
40/// let problem = SetSplitting::new(6, vec![
41///     vec![0, 1, 2],
42///     vec![2, 3, 4],
43///     vec![0, 4, 5],
44///     vec![1, 3, 5],
45/// ]);
46///
47/// let solver = BruteForce::new();
48/// let witness = solver.find_witness(&problem);
49/// assert!(witness.is_some());
50/// ```
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(try_from = "SetSplittingDef")]
53pub struct SetSplitting {
54    /// Size of the universe.
55    universe_size: usize,
56    /// Subsets that must each contain elements from both parts.
57    subsets: Vec<Vec<usize>>,
58}
59
60fn normalize_subsets(universe_size: usize, subsets: &[Vec<usize>]) -> (usize, Vec<Vec<usize>>) {
61    let mut next_element = universe_size;
62    let total_excess: usize = subsets
63        .iter()
64        .map(|subset| subset.len().saturating_sub(3))
65        .sum();
66    let mut normalized = Vec::with_capacity(subsets.len() + 2 * total_excess);
67
68    for subset in subsets {
69        let mut remainder = subset.clone();
70        while remainder.len() > 3 {
71            let positive_aux = next_element;
72            let negative_aux = next_element + 1;
73            next_element += 2;
74
75            normalized.push(vec![remainder[0], remainder[1], positive_aux]);
76            normalized.push(vec![positive_aux, negative_aux]);
77
78            let mut next_remainder = Vec::with_capacity(remainder.len() - 1);
79            next_remainder.push(negative_aux);
80            next_remainder.extend_from_slice(&remainder[2..]);
81            remainder = next_remainder;
82        }
83        normalized.push(remainder);
84    }
85
86    (next_element, normalized)
87}
88
89impl SetSplitting {
90    /// Create a new Set Splitting problem.
91    ///
92    /// # Panics
93    ///
94    /// Panics if any subset is empty, has fewer than 2 elements, or contains an
95    /// element outside the universe.
96    pub fn new(universe_size: usize, subsets: Vec<Vec<usize>>) -> Self {
97        Self::try_new(universe_size, subsets).unwrap_or_else(|err| panic!("{err}"))
98    }
99
100    /// Create a new Set Splitting problem, returning an error instead of panicking.
101    pub fn try_new(universe_size: usize, subsets: Vec<Vec<usize>>) -> Result<Self, String> {
102        for (i, subset) in subsets.iter().enumerate() {
103            if subset.len() < 2 {
104                return Err(format!(
105                    "Subset {} has {} element(s), expected at least 2",
106                    i,
107                    subset.len()
108                ));
109            }
110            for &elem in subset {
111                if elem >= universe_size {
112                    return Err(format!(
113                        "Subset {} contains element {} which is outside universe of size {}",
114                        i, elem, universe_size
115                    ));
116                }
117            }
118        }
119        Ok(Self {
120            universe_size,
121            subsets,
122        })
123    }
124
125    /// Get the size of the universe.
126    pub fn universe_size(&self) -> usize {
127        self.universe_size
128    }
129
130    /// Get the number of subsets.
131    pub fn num_subsets(&self) -> usize {
132        self.subsets.len()
133    }
134
135    /// Get the subsets.
136    pub fn subsets(&self) -> &[Vec<usize>] {
137        &self.subsets
138    }
139
140    pub(crate) fn normalized_instance(&self) -> (usize, Vec<Vec<usize>>) {
141        normalize_subsets(self.universe_size, &self.subsets)
142    }
143
144    fn normalized_stats(&self) -> (usize, usize, usize) {
145        let (universe_size, subsets) = self.normalized_instance();
146        let size2 = subsets.iter().filter(|s| s.len() == 2).count();
147        let size3 = subsets.iter().filter(|s| s.len() == 3).count();
148        (universe_size, size2, size3)
149    }
150
151    /// Universe size after decomposing all subsets to size 2 or 3.
152    pub fn normalized_universe_size(&self) -> usize {
153        self.normalized_stats().0
154    }
155
156    /// Number of size-2 subsets after decomposition.
157    pub fn normalized_num_size2_subsets(&self) -> usize {
158        self.normalized_stats().1
159    }
160
161    /// Number of size-3 subsets after decomposition.
162    pub fn normalized_num_size3_subsets(&self) -> usize {
163        self.normalized_stats().2
164    }
165
166    /// Check if a coloring (config) splits all subsets.
167    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
168        self.evaluate(config).0
169    }
170}
171
172impl Problem for SetSplitting {
173    const NAME: &'static str = "SetSplitting";
174    type Value = crate::types::Or;
175
176    fn dims(&self) -> Vec<usize> {
177        vec![2; self.universe_size]
178    }
179
180    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
181        crate::types::Or(self.subsets.iter().all(|subset| {
182            let has_zero = subset.iter().any(|&e| config[e] == 0);
183            let has_one = subset.iter().any(|&e| config[e] == 1);
184            has_zero && has_one
185        }))
186    }
187
188    fn variant() -> Vec<(&'static str, &'static str)> {
189        crate::variant_params![]
190    }
191}
192
193crate::declare_variants! {
194    default SetSplitting => "2^universe_size",
195}
196
197#[derive(Debug, Clone, Deserialize)]
198struct SetSplittingDef {
199    universe_size: usize,
200    subsets: Vec<Vec<usize>>,
201}
202
203impl TryFrom<SetSplittingDef> for SetSplitting {
204    type Error = String;
205
206    fn try_from(value: SetSplittingDef) -> Result<Self, Self::Error> {
207        Self::try_new(value.universe_size, value.subsets)
208    }
209}
210
211#[cfg(feature = "example-db")]
212pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
213    vec![crate::example_db::specs::ModelExampleSpec {
214        id: "set_splitting",
215        instance: Box::new(SetSplitting::new(
216            6,
217            vec![vec![0, 1, 2], vec![2, 3, 4], vec![0, 4, 5], vec![1, 3, 5]],
218        )),
219        // config[i]=0 means element i in S1, config[i]=1 means element i in S2
220        // S1={1,3,4}, S2={0,2,5} → config [1,0,1,0,0,1]
221        optimal_config: vec![1, 0, 1, 0, 0, 1],
222        optimal_value: serde_json::json!(true),
223    }]
224}
225
226#[cfg(test)]
227#[path = "../../unit_tests/models/set/set_splitting.rs"]
228mod tests;