problemreductions/models/set/
minimum_set_covering.rs

1//! Set Covering problem implementation.
2//!
3//! The Set Covering problem asks for a minimum weight collection of sets
4//! that covers all elements in the universe.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::{OptimizationProblem, Problem};
8use crate::types::{Direction, SolutionSize, WeightElement};
9use num_traits::Zero;
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "MinimumSetCovering",
16        module_path: module_path!(),
17        description: "Find minimum weight collection covering the universe",
18        fields: &[
19            FieldInfo { name: "universe_size", type_name: "usize", description: "Size of the universe U" },
20            FieldInfo { name: "sets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of U" },
21            FieldInfo { name: "weights", type_name: "Vec<W>", description: "Weight for each set" },
22        ],
23    }
24}
25
26/// The Set Covering problem.
27///
28/// Given a universe U of elements and a collection S of subsets of U,
29/// each with a weight, find a minimum weight subcollection of S
30/// that covers all elements in U.
31///
32/// # Example
33///
34/// ```
35/// use problemreductions::models::set::MinimumSetCovering;
36/// use problemreductions::{Problem, Solver, BruteForce};
37///
38/// // Universe: {0, 1, 2, 3}
39/// // Sets: S0={0,1}, S1={1,2}, S2={2,3}, S3={0,3}
40/// let problem = MinimumSetCovering::<i32>::new(
41///     4, // universe size
42///     vec![
43///         vec![0, 1],
44///         vec![1, 2],
45///         vec![2, 3],
46///         vec![0, 3],
47///     ],
48/// );
49///
50/// let solver = BruteForce::new();
51/// let solutions = solver.find_all_best(&problem);
52///
53/// // Verify solutions cover all elements
54/// for sol in solutions {
55///     assert!(problem.evaluate(&sol).is_valid());
56/// }
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct MinimumSetCovering<W = i32> {
60    /// Size of the universe (elements are 0..universe_size).
61    universe_size: usize,
62    /// Collection of sets, each represented as a vector of elements.
63    sets: Vec<Vec<usize>>,
64    /// Weights for each set.
65    weights: Vec<W>,
66}
67
68impl<W: Clone + Default> MinimumSetCovering<W> {
69    /// Create a new Set Covering problem with unit weights.
70    pub fn new(universe_size: usize, sets: Vec<Vec<usize>>) -> Self
71    where
72        W: From<i32>,
73    {
74        let num_sets = sets.len();
75        let weights = vec![W::from(1); num_sets];
76        Self {
77            universe_size,
78            sets,
79            weights,
80        }
81    }
82
83    /// Create a new Set Covering problem with custom weights.
84    pub fn with_weights(universe_size: usize, sets: Vec<Vec<usize>>, weights: Vec<W>) -> Self {
85        assert_eq!(sets.len(), weights.len());
86        Self {
87            universe_size,
88            sets,
89            weights,
90        }
91    }
92
93    /// Get the universe size.
94    pub fn universe_size(&self) -> usize {
95        self.universe_size
96    }
97
98    /// Get the number of sets.
99    pub fn num_sets(&self) -> usize {
100        self.sets.len()
101    }
102
103    /// Get the sets.
104    pub fn sets(&self) -> &[Vec<usize>] {
105        &self.sets
106    }
107
108    /// Get a specific set.
109    pub fn get_set(&self, index: usize) -> Option<&Vec<usize>> {
110        self.sets.get(index)
111    }
112
113    /// Get a reference to the weights.
114    pub fn weights_ref(&self) -> &[W] {
115        &self.weights
116    }
117
118    /// Check if a configuration is a valid set cover.
119    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
120        let covered = self.covered_elements(config);
121        covered.len() == self.universe_size && (0..self.universe_size).all(|e| covered.contains(&e))
122    }
123
124    /// Check which elements are covered by selected sets.
125    pub fn covered_elements(&self, config: &[usize]) -> HashSet<usize> {
126        let mut covered = HashSet::new();
127        for (i, &selected) in config.iter().enumerate() {
128            if selected == 1 {
129                if let Some(set) = self.sets.get(i) {
130                    covered.extend(set.iter().copied());
131                }
132            }
133        }
134        covered
135    }
136}
137
138impl<W> Problem for MinimumSetCovering<W>
139where
140    W: WeightElement + crate::variant::VariantParam,
141{
142    const NAME: &'static str = "MinimumSetCovering";
143    type Metric = SolutionSize<W::Sum>;
144
145    fn dims(&self) -> Vec<usize> {
146        vec![2; self.sets.len()]
147    }
148
149    fn evaluate(&self, config: &[usize]) -> SolutionSize<W::Sum> {
150        let covered = self.covered_elements(config);
151        let is_valid = covered.len() == self.universe_size
152            && (0..self.universe_size).all(|e| covered.contains(&e));
153        if !is_valid {
154            return SolutionSize::Invalid;
155        }
156        let mut total = W::Sum::zero();
157        for (i, &selected) in config.iter().enumerate() {
158            if selected == 1 {
159                total += self.weights[i].to_sum();
160            }
161        }
162        SolutionSize::Valid(total)
163    }
164
165    fn variant() -> Vec<(&'static str, &'static str)> {
166        crate::variant_params![W]
167    }
168}
169
170impl<W> OptimizationProblem for MinimumSetCovering<W>
171where
172    W: WeightElement + crate::variant::VariantParam,
173{
174    type Value = W::Sum;
175
176    fn direction(&self) -> Direction {
177        Direction::Minimize
178    }
179}
180
181crate::declare_variants! {
182    MinimumSetCovering<i32> => "2^num_sets",
183}
184
185/// Check if a selection of sets forms a valid set cover.
186#[cfg(test)]
187pub(crate) fn is_set_cover(universe_size: usize, sets: &[Vec<usize>], selected: &[bool]) -> bool {
188    if selected.len() != sets.len() {
189        return false;
190    }
191
192    let mut covered = HashSet::new();
193    for (i, &sel) in selected.iter().enumerate() {
194        if sel {
195            covered.extend(sets[i].iter().copied());
196        }
197    }
198
199    (0..universe_size).all(|e| covered.contains(&e))
200}
201
202#[cfg(test)]
203#[path = "../../unit_tests/models/set/minimum_set_covering.rs"]
204mod tests;