problemreductions/models/set/
minimum_hitting_set.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
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 module_path: module_path!(),
18 description: "Find a minimum-size subset of universe elements that hits every set",
19 fields: &[
20 FieldInfo { name: "universe_size", type_name: "usize", description: "Size of the universe U" },
21 FieldInfo { name: "sets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of U that must each be hit" },
22 ],
23 }
24}
25
26inventory::submit! {
27 ProblemSizeFieldEntry {
28 name: "MinimumHittingSet",
29 fields: &["num_sets", "universe_size"],
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct MinimumHittingSet {
39 universe_size: usize,
40 sets: Vec<Vec<usize>>,
41}
42
43impl MinimumHittingSet {
44 pub fn new(universe_size: usize, sets: Vec<Vec<usize>>) -> Self {
50 let mut sets = sets;
51 for (set_index, set) in sets.iter_mut().enumerate() {
52 set.sort_unstable();
53 set.dedup();
54 for &element in set.iter() {
55 assert!(
56 element < universe_size,
57 "Set {set_index} contains element {element} which is outside universe of size {universe_size}"
58 );
59 }
60 }
61
62 Self {
63 universe_size,
64 sets,
65 }
66 }
67
68 pub fn universe_size(&self) -> usize {
70 self.universe_size
71 }
72
73 pub fn num_sets(&self) -> usize {
75 self.sets.len()
76 }
77
78 pub fn sets(&self) -> &[Vec<usize>] {
80 &self.sets
81 }
82
83 pub fn get_set(&self, index: usize) -> Option<&Vec<usize>> {
85 self.sets.get(index)
86 }
87
88 pub fn selected_elements(&self, config: &[usize]) -> Option<Vec<usize>> {
90 if config.len() != self.universe_size {
91 return None;
92 }
93
94 let mut selected = Vec::new();
95 for (element, &value) in config.iter().enumerate() {
96 match value {
97 0 => {}
98 1 => selected.push(element),
99 _ => return None,
100 }
101 }
102 Some(selected)
103 }
104
105 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
107 let Some(selected) = self.selected_elements(config) else {
108 return false;
109 };
110
111 self.sets.iter().all(|set| {
112 set.iter()
113 .any(|element| selected.binary_search(element).is_ok())
114 })
115 }
116}
117
118impl Problem for MinimumHittingSet {
119 const NAME: &'static str = "MinimumHittingSet";
120 type Value = Min<usize>;
121
122 fn dims(&self) -> Vec<usize> {
123 vec![2; self.universe_size]
124 }
125
126 fn evaluate(&self, config: &[usize]) -> Min<usize> {
127 let Some(selected) = self.selected_elements(config) else {
128 return Min(None);
129 };
130
131 if self.sets.iter().all(|set| {
132 set.iter()
133 .any(|element| selected.binary_search(element).is_ok())
134 }) {
135 Min(Some(selected.len()))
136 } else {
137 Min(None)
138 }
139 }
140
141 fn variant() -> Vec<(&'static str, &'static str)> {
142 crate::variant_params![]
143 }
144}
145
146crate::declare_variants! {
147 default MinimumHittingSet => "2^universe_size",
148}
149
150#[cfg(feature = "example-db")]
151pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
152 vec![crate::example_db::specs::ModelExampleSpec {
153 id: "minimum_hitting_set",
154 instance: Box::new(MinimumHittingSet::new(
155 6,
156 vec![
157 vec![0, 1, 2],
158 vec![0, 3, 4],
159 vec![1, 3, 5],
160 vec![2, 4, 5],
161 vec![0, 1, 5],
162 vec![2, 3],
163 vec![1, 4],
164 ],
165 )),
166 optimal_config: vec![0, 1, 0, 1, 1, 0],
167 optimal_value: serde_json::json!(3),
168 }]
169}
170
171#[cfg(test)]
172#[path = "../../unit_tests/models/set/minimum_hitting_set.rs"]
173mod tests;