problemreductions/models/set/
minimum_hitting_set.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "MinimumHittingSet",
14 display_name: "Minimum Hitting Set",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Set,
18 module_path: module_path!(),
19 description: "Find a minimum-size subset of universe elements that hits every set",
20 fields: MinimumHittingSetCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct MinimumHittingSet {
30 universe_size: usize,
31 sets: Vec<Vec<usize>>,
32}
33
34#[derive(Debug, Deserialize, crate::CreateSpec)]
35struct MinimumHittingSetCreateSpec {
36 universe_size: usize,
38 subsets: Vec<Vec<usize>>,
40}
41
42impl TryFrom<MinimumHittingSetCreateSpec> for MinimumHittingSet {
43 type Error = crate::registry::ConstructionError;
44
45 fn try_from(spec: MinimumHittingSetCreateSpec) -> Result<Self, Self::Error> {
46 for (set_index, set) in spec.subsets.iter().enumerate() {
47 if let Some(&element) = set.iter().find(|&&element| element >= spec.universe_size) {
48 return Err(format!(
49 "subsets[{set_index}] contains element {element} outside universe of size {}",
50 spec.universe_size
51 )
52 .into());
53 }
54 }
55 Ok(Self::new(spec.universe_size, spec.subsets))
56 }
57}
58
59impl MinimumHittingSet {
60 pub fn new(universe_size: usize, sets: Vec<Vec<usize>>) -> Self {
66 let mut sets = sets;
67 for (set_index, set) in sets.iter_mut().enumerate() {
68 set.sort_unstable();
69 set.dedup();
70 for &element in set.iter() {
71 assert!(
72 element < universe_size,
73 "Set {set_index} contains element {element} which is outside universe of size {universe_size}"
74 );
75 }
76 }
77
78 Self {
79 universe_size,
80 sets,
81 }
82 }
83
84 pub fn universe_size(&self) -> usize {
86 self.universe_size
87 }
88
89 pub fn num_sets(&self) -> usize {
91 self.sets.len()
92 }
93
94 pub fn sets(&self) -> &[Vec<usize>] {
96 &self.sets
97 }
98
99 pub fn get_set(&self, index: usize) -> Option<&Vec<usize>> {
101 self.sets.get(index)
102 }
103
104 pub fn selected_elements(&self, config: &[bool]) -> Option<Vec<usize>> {
106 if config.len() != self.universe_size {
107 return None;
108 }
109
110 let mut selected = Vec::new();
111 for (element, &is_selected) in config.iter().enumerate() {
112 if is_selected {
113 selected.push(element);
114 }
115 }
116 Some(selected)
117 }
118
119 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
121 let Some(selected) = self.selected_elements(config) else {
122 return false;
123 };
124
125 self.sets.iter().all(|set| {
126 set.iter()
127 .any(|element| selected.binary_search(element).is_ok())
128 })
129 }
130}
131
132impl Problem for MinimumHittingSet {
133 const NAME: &'static str = "MinimumHittingSet";
134 type Solution = Vec<bool>;
135 type Value = Min<i64>;
136
137 crate::problem_parameters![("num_sets", num_sets), ("universe_size", universe_size),];
138
139 fn evaluate(
140 &self,
141 config: &Self::Solution,
142 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
143 Ok({
144 let Some(selected) = self.selected_elements(config) else {
145 return Err(crate::traits::EvaluationError::InvalidConfiguration(
146 "element-selection length does not match the universe".into(),
147 ));
148 };
149
150 if self.sets.iter().all(|set| {
151 set.iter()
152 .any(|element| selected.binary_search(element).is_ok())
153 }) {
154 Min(Some(i64::try_from(selected.len()).map_err(|_| {
155 crate::traits::EvaluationError::IntegerOverflow(
156 "converting hitting-set cardinality to i64".into(),
157 )
158 })?))
159 } else {
160 Min(None)
161 }
162 })
163 }
164
165 fn variant() -> Vec<(&'static str, &'static str)> {
166 crate::variant_params![]
167 }
168}
169
170impl crate::solvers::BruteForceProblem for MinimumHittingSet {
171 fn dimensions(&self) -> Vec<usize> {
172 vec![2; self.universe_size]
173 }
174}
175
176crate::declare_variants! {
177 default MinimumHittingSet => "2^universe_size" create MinimumHittingSetCreateSpec,
178}
179
180crate::register_brute_force! {
181 MinimumHittingSet decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
182}
183
184#[cfg(feature = "example-db")]
185pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
186 vec![crate::example_db::specs::ModelExampleSpec {
187 id: "minimum_hitting_set",
188 instance: Box::new(MinimumHittingSet::new(
189 6,
190 vec![
191 vec![0, 1, 2],
192 vec![0, 3, 4],
193 vec![1, 3, 5],
194 vec![2, 4, 5],
195 vec![0, 1, 5],
196 vec![2, 3],
197 vec![1, 4],
198 ],
199 )),
200 optimal_config: serde_json::json!(vec![false, true, false, true, true, false]),
201 optimal_value: serde_json::json!(3),
202 }]
203}
204
205#[cfg(test)]
206#[path = "../../unit_tests/models/set/minimum_hitting_set.rs"]
207mod tests;