problemreductions/models/set/
minimum_set_covering.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::traits::Problem;
8use crate::types::{Min, WeightElement};
9use num_traits::Zero;
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MinimumSetCovering",
16 display_name: "Minimum Set Covering",
17 aliases: &[],
18 dimensions: &[VariantDimension::new("weight", "i64", &["i64"])],
19 category: crate::registry::ProblemCategory::Set,
20 module_path: module_path!(),
21 description: "Find minimum weight collection covering the universe",
22 fields: MinimumSetCoveringCreateSpec::FIELDS,
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct MinimumSetCovering<W = i64> {
60 universe_size: usize,
62 sets: Vec<Vec<usize>>,
64 weights: Vec<W>,
66}
67
68#[derive(Debug, Deserialize, crate::CreateSpec)]
69struct MinimumSetCoveringCreateSpec {
70 universe_size: usize,
72 subsets: Vec<Vec<usize>>,
74 weights: Vec<i64>,
76}
77
78impl TryFrom<MinimumSetCoveringCreateSpec> for MinimumSetCovering<i64> {
79 type Error = crate::registry::ConstructionError;
80
81 fn try_from(spec: MinimumSetCoveringCreateSpec) -> Result<Self, Self::Error> {
82 if spec.subsets.len() != spec.weights.len() {
83 return Err(format!(
84 "weights has {} entries, expected one for each of {} subsets",
85 spec.weights.len(),
86 spec.subsets.len()
87 )
88 .into());
89 }
90 for (set_index, set) in spec.subsets.iter().enumerate() {
91 if let Some(&element) = set.iter().find(|&&element| element >= spec.universe_size) {
92 return Err(format!(
93 "subsets[{set_index}] contains element {element} outside universe of size {}",
94 spec.universe_size
95 )
96 .into());
97 }
98 }
99 Ok(Self::with_weights(
100 spec.universe_size,
101 spec.subsets,
102 spec.weights,
103 ))
104 }
105}
106
107impl<W: Clone + Default> MinimumSetCovering<W> {
108 pub fn new(universe_size: usize, sets: Vec<Vec<usize>>) -> Self
110 where
111 W: WeightElement,
112 {
113 let num_sets = sets.len();
114 let weights = vec![W::unit(); num_sets];
115 Self {
116 universe_size,
117 sets,
118 weights,
119 }
120 }
121
122 pub fn with_weights(universe_size: usize, sets: Vec<Vec<usize>>, weights: Vec<W>) -> Self {
124 assert_eq!(sets.len(), weights.len());
125 Self {
126 universe_size,
127 sets,
128 weights,
129 }
130 }
131
132 pub fn universe_size(&self) -> usize {
134 self.universe_size
135 }
136
137 pub fn num_sets(&self) -> usize {
139 self.sets.len()
140 }
141
142 pub fn sets(&self) -> &[Vec<usize>] {
144 &self.sets
145 }
146
147 pub fn get_set(&self, index: usize) -> Option<&Vec<usize>> {
149 self.sets.get(index)
150 }
151
152 pub fn weights_ref(&self) -> &[W] {
154 &self.weights
155 }
156
157 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
159 let covered = self.covered_elements(config);
160 covered.len() == self.universe_size && (0..self.universe_size).all(|e| covered.contains(&e))
161 }
162
163 pub fn covered_elements(&self, config: &[bool]) -> HashSet<usize> {
165 let mut covered = HashSet::new();
166 for (i, &selected) in config.iter().enumerate() {
167 if selected {
168 if let Some(set) = self.sets.get(i) {
169 covered.extend(set.iter().copied());
170 }
171 }
172 }
173 covered
174 }
175}
176
177impl<W> Problem for MinimumSetCovering<W>
178where
179 W: WeightElement + crate::variant::VariantParam,
180{
181 const NAME: &'static str = "MinimumSetCovering";
182 type Solution = Vec<bool>;
183 type Value = Min<W::Sum>;
184
185 crate::problem_parameters![("num_sets", num_sets), ("universe_size", universe_size),];
186
187 fn evaluate(
188 &self,
189 config: &Self::Solution,
190 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
191 if config.len() != self.sets.len() {
192 return Err(crate::traits::EvaluationError::InvalidConfiguration(
193 "set-selection length does not match the family".into(),
194 ));
195 }
196 Ok({
197 let covered = self.covered_elements(config);
198 let is_valid = covered.len() == self.universe_size
199 && (0..self.universe_size).all(|e| covered.contains(&e));
200 if !is_valid {
201 return Ok(Min(None));
202 }
203 let mut total = W::Sum::zero();
204 for (i, &selected) in config.iter().enumerate() {
205 if selected {
206 total = W::checked_add_to_sum(
207 total,
208 self.weights[i].to_sum(),
209 "summing selected set-cover weights",
210 )?;
211 }
212 }
213 Min(Some(total))
214 })
215 }
216
217 fn variant() -> Vec<(&'static str, &'static str)> {
218 crate::variant_params![W]
219 }
220}
221
222impl<W> crate::solvers::BruteForceProblem for MinimumSetCovering<W>
223where
224 W: WeightElement + crate::variant::VariantParam,
225{
226 fn dimensions(&self) -> Vec<usize> {
227 vec![2; self.sets.len()]
228 }
229}
230
231crate::declare_variants! {
232 default MinimumSetCovering<i64> => "2^num_sets" create MinimumSetCoveringCreateSpec,
233}
234
235crate::register_brute_force! {
236 MinimumSetCovering<i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
237}
238
239#[cfg(test)]
241pub(crate) fn is_set_cover(universe_size: usize, sets: &[Vec<usize>], selected: &[bool]) -> bool {
242 if selected.len() != sets.len() {
243 return false;
244 }
245
246 let mut covered = HashSet::new();
247 for (i, &sel) in selected.iter().enumerate() {
248 if sel {
249 covered.extend(sets[i].iter().copied());
250 }
251 }
252
253 (0..universe_size).all(|e| covered.contains(&e))
254}
255
256#[cfg(feature = "example-db")]
257pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
258 vec![crate::example_db::specs::ModelExampleSpec {
259 id: "minimum_set_covering",
260 instance: Box::new(MinimumSetCovering::<i64>::new(
261 5,
262 vec![vec![0, 1, 2], vec![1, 3], vec![2, 3, 4]],
263 )),
264 optimal_config: serde_json::json!(vec![true, false, true]),
265 optimal_value: serde_json::json!(2),
266 }]
267}
268
269#[cfg(test)]
270#[path = "../../unit_tests/models/set/minimum_set_covering.rs"]
271mod tests;