problemreductions/models/misc/
ensemble_computation.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
4use crate::traits::Problem;
5use crate::types::Min;
6use serde::{Deserialize, Serialize};
7
8inventory::submit! {
9 ProblemSchemaEntry {
10 name: "EnsembleComputation",
11 display_name: "Ensemble Computation",
12 aliases: &[],
13 dimensions: &[],
14 module_path: module_path!(),
15 description: "Find the minimum-length sequence of disjoint unions that builds all required subsets",
16 fields: &[
17 FieldInfo { name: "universe_size", type_name: "usize", description: "Number of elements in the universe A" },
18 FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Required subsets that must appear among the computed z_i values" },
19 FieldInfo { name: "budget", type_name: "usize", description: "Maximum number of union operations J" },
20 ],
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(try_from = "EnsembleComputationDef")]
26pub struct EnsembleComputation {
27 universe_size: usize,
28 subsets: Vec<Vec<usize>>,
29 budget: usize,
30}
31
32impl EnsembleComputation {
33 pub fn new(universe_size: usize, subsets: Vec<Vec<usize>>, budget: usize) -> Self {
34 Self::try_new(universe_size, subsets, budget).unwrap_or_else(|err| panic!("{err}"))
35 }
36
37 pub fn with_default_budget(universe_size: usize, subsets: Vec<Vec<usize>>) -> Self {
43 let budget = Self::default_budget(&subsets);
44 Self::new(universe_size, subsets, budget)
45 }
46
47 pub fn default_budget(subsets: &[Vec<usize>]) -> usize {
51 subsets.iter().map(|s| s.len()).sum::<usize>().max(1)
52 }
53
54 pub fn try_new(
55 universe_size: usize,
56 subsets: Vec<Vec<usize>>,
57 budget: usize,
58 ) -> Result<Self, String> {
59 if budget == 0 {
60 return Err("budget must be positive".to_string());
61 }
62 let subsets = subsets
63 .into_iter()
64 .enumerate()
65 .map(|(subset_index, subset)| {
66 Self::normalize_subset(universe_size, subset).ok_or_else(|| {
67 format!(
68 "subset {subset_index} contains element outside universe of size {universe_size}"
69 )
70 })
71 })
72 .collect::<Result<Vec<_>, _>>()?;
73 Ok(Self {
74 universe_size,
75 subsets,
76 budget,
77 })
78 }
79
80 pub fn universe_size(&self) -> usize {
81 self.universe_size
82 }
83
84 pub fn subsets(&self) -> &[Vec<usize>] {
85 &self.subsets
86 }
87
88 pub fn num_subsets(&self) -> usize {
89 self.subsets.len()
90 }
91
92 pub fn budget(&self) -> usize {
93 self.budget
94 }
95
96 fn normalize_subset(universe_size: usize, mut subset: Vec<usize>) -> Option<Vec<usize>> {
97 if subset.iter().any(|&element| element >= universe_size) {
98 return None;
99 }
100 subset.sort_unstable();
101 subset.dedup();
102 Some(subset)
103 }
104
105 fn decode_operand(&self, operand: usize, computed: &[Vec<usize>]) -> Option<Vec<usize>> {
106 if operand < self.universe_size {
107 return Some(vec![operand]);
108 }
109 computed.get(operand - self.universe_size).cloned()
110 }
111
112 fn are_disjoint(left: &[usize], right: &[usize]) -> bool {
113 let mut i = 0;
114 let mut j = 0;
115
116 while i < left.len() && j < right.len() {
117 match left[i].cmp(&right[j]) {
118 std::cmp::Ordering::Less => i += 1,
119 std::cmp::Ordering::Greater => j += 1,
120 std::cmp::Ordering::Equal => return false,
121 }
122 }
123
124 true
125 }
126
127 fn union_disjoint(left: &[usize], right: &[usize]) -> Vec<usize> {
128 let mut union = Vec::with_capacity(left.len() + right.len());
129 let mut i = 0;
130 let mut j = 0;
131
132 while i < left.len() && j < right.len() {
133 if left[i] < right[j] {
134 union.push(left[i]);
135 i += 1;
136 } else {
137 union.push(right[j]);
138 j += 1;
139 }
140 }
141
142 union.extend_from_slice(&left[i..]);
143 union.extend_from_slice(&right[j..]);
144 union
145 }
146
147 fn required_subsets(&self) -> Option<Vec<Vec<usize>>> {
148 self.subsets
149 .iter()
150 .cloned()
151 .map(|subset| Self::normalize_subset(self.universe_size, subset))
152 .collect()
153 }
154
155 fn all_required_subsets_present(
156 required_subsets: &[Vec<usize>],
157 computed: &[Vec<usize>],
158 ) -> bool {
159 required_subsets
160 .iter()
161 .all(|subset| computed.iter().any(|candidate| candidate == subset))
162 }
163}
164
165impl Problem for EnsembleComputation {
166 const NAME: &'static str = "EnsembleComputation";
167 type Value = Min<usize>;
168
169 fn dims(&self) -> Vec<usize> {
170 vec![self.universe_size + self.budget; 2 * self.budget]
171 }
172
173 fn evaluate(&self, config: &[usize]) -> Min<usize> {
174 if config.len() != 2 * self.budget {
175 return Min(None);
176 }
177
178 let Some(required_subsets) = self.required_subsets() else {
179 return Min(None);
180 };
181 if required_subsets.is_empty() {
182 return Min(Some(0));
183 }
184
185 let mut computed = Vec::with_capacity(self.budget);
186 for step in 0..self.budget {
187 let left_operand = config[2 * step];
188 let right_operand = config[2 * step + 1];
189
190 let Some(left) = self.decode_operand(left_operand, &computed) else {
191 return Min(None);
192 };
193 let Some(right) = self.decode_operand(right_operand, &computed) else {
194 return Min(None);
195 };
196
197 if !Self::are_disjoint(&left, &right) {
198 return Min(None);
199 }
200
201 computed.push(Self::union_disjoint(&left, &right));
202 if Self::all_required_subsets_present(&required_subsets, &computed) {
203 return Min(Some(step + 1));
204 }
205 }
206
207 Min(None)
208 }
209
210 fn variant() -> Vec<(&'static str, &'static str)> {
211 crate::variant_params![]
212 }
213}
214
215crate::declare_variants! {
216 default EnsembleComputation => "(universe_size + budget)^(2 * budget)",
217}
218
219#[derive(Debug, Clone, Deserialize)]
220struct EnsembleComputationDef {
221 universe_size: usize,
222 subsets: Vec<Vec<usize>>,
223 budget: usize,
224}
225
226impl TryFrom<EnsembleComputationDef> for EnsembleComputation {
227 type Error = String;
228
229 fn try_from(value: EnsembleComputationDef) -> Result<Self, Self::Error> {
230 Self::try_new(value.universe_size, value.subsets, value.budget)
231 }
232}
233
234#[cfg(feature = "example-db")]
235pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
236 vec![crate::example_db::specs::ModelExampleSpec {
239 id: "ensemble_computation",
240 instance: Box::new(EnsembleComputation::new(
241 3,
242 vec![vec![0, 1], vec![0, 1, 2]],
243 2,
244 )),
245 optimal_config: vec![0, 1, 3, 2],
246 optimal_value: serde_json::json!(2),
247 }]
248}
249
250#[cfg(test)]
251#[path = "../../unit_tests/models/misc/ensemble_computation.rs"]
252mod tests;