problemreductions/models/misc/
minimum_external_macro_data_compression.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
18use crate::traits::Problem;
19use crate::types::Min;
20use serde::{Deserialize, Serialize};
21
22inventory::submit! {
23 ProblemSchemaEntry {
24 name: "MinimumExternalMacroDataCompression",
25 display_name: "Minimum External Macro Data Compression",
26 aliases: &[],
27 dimensions: &[],
28 category: crate::registry::ProblemCategory::Misc,
29 module_path: module_path!(),
30 description: "Find minimum-cost compression using an external dictionary and compressed string with pointers",
31 fields: &[
32 FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet (symbols indexed 0..alphabet_size)" },
33 FieldInfo { name: "string", type_name: "Vec<usize>", description: "Source string as symbol indices" },
34 FieldInfo { name: "pointer_cost", type_name: "i64", description: "Pointer cost h (each pointer contributes h to the cost)" },
35 ],
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(try_from = "MinimumExternalMacroDataCompressionSerde")]
73pub struct MinimumExternalMacroDataCompression {
74 alphabet_size: usize,
75 string: Vec<usize>,
76 pointer_cost: i64,
77}
78
79#[derive(Deserialize)]
80struct MinimumExternalMacroDataCompressionSerde {
81 alphabet_size: usize,
82 string: Vec<usize>,
83 pointer_cost: i64,
84}
85
86impl TryFrom<MinimumExternalMacroDataCompressionSerde> for MinimumExternalMacroDataCompression {
87 type Error = crate::registry::ConstructionError;
88
89 fn try_from(value: MinimumExternalMacroDataCompressionSerde) -> Result<Self, Self::Error> {
90 if value.alphabet_size == 0 && !value.string.is_empty() {
91 return Err("alphabet_size must be > 0 when the string is non-empty"
92 .to_string()
93 .into());
94 }
95 if value
96 .string
97 .iter()
98 .any(|&symbol| symbol >= value.alphabet_size)
99 {
100 return Err("all symbols must be less than alphabet_size"
101 .to_string()
102 .into());
103 }
104 if value.pointer_cost <= 0 {
105 return Err("pointer_cost must be positive".to_string().into());
106 }
107 Ok(Self {
108 alphabet_size: value.alphabet_size,
109 string: value.string,
110 pointer_cost: value.pointer_cost,
111 })
112 }
113}
114
115impl MinimumExternalMacroDataCompression {
116 pub fn new(alphabet_size: usize, string: Vec<usize>, pointer_cost: i64) -> Self {
123 assert!(
124 alphabet_size > 0 || string.is_empty(),
125 "alphabet_size must be > 0 when the string is non-empty"
126 );
127 assert!(
128 string
129 .iter()
130 .all(|&s| s < alphabet_size || alphabet_size == 0),
131 "all symbols must be less than alphabet_size"
132 );
133 assert!(pointer_cost > 0, "pointer_cost must be positive");
134 Self {
135 alphabet_size,
136 string,
137 pointer_cost,
138 }
139 }
140
141 pub fn string_length(&self) -> usize {
143 self.string.len()
144 }
145
146 pub fn alphabet_size(&self) -> usize {
148 self.alphabet_size
149 }
150
151 pub fn pointer_cost(&self) -> i64 {
153 self.pointer_cost
154 }
155
156 pub fn string(&self) -> &[usize] {
158 &self.string
159 }
160
161 fn num_pointers(&self) -> usize {
163 let n = self.string.len();
164 n * (n + 1) / 2
165 }
166
167 fn c_domain_size(&self) -> usize {
169 self.alphabet_size + 1 + self.num_pointers()
170 }
171
172 fn decode_pointer(&self, ptr_idx: usize) -> Option<(usize, usize)> {
177 let n = self.string.len();
178 let mut idx = 0;
180 for start in 0..n {
181 let max_len = n - start;
182 if ptr_idx < idx + max_len {
183 let len = ptr_idx - idx + 1;
184 return Some((start, len));
185 }
186 idx += max_len;
187 }
188 None
189 }
190}
191
192impl Problem for MinimumExternalMacroDataCompression {
193 const NAME: &'static str = "MinimumExternalMacroDataCompression";
194 type Solution = Vec<usize>;
195 type Value = Min<i64>;
196
197 crate::problem_parameters![
198 ("alphabet_size", alphabet_size),
199 ("string_length", string_length),
200 ];
201
202 fn variant() -> Vec<(&'static str, &'static str)> {
203 crate::variant_params![]
204 }
205
206 fn evaluate(
207 &self,
208 config: &Self::Solution,
209 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
210 Ok({
211 let n = self.string.len();
212 if config.len() != 2 * n {
213 return Err(crate::traits::EvaluationError::InvalidConfiguration(
214 "macro encoding length does not match the string".into(),
215 ));
216 }
217
218 if n == 0 {
220 return Ok(Min(Some(0)));
221 }
222
223 let empty_d = self.alphabet_size; let empty_c = self.alphabet_size; let d_slots = &config[..n];
228 let d_len = d_slots.iter().position(|&v| v == empty_d).unwrap_or(n);
229
230 for &v in &d_slots[d_len..] {
232 if v != empty_d {
233 return Ok(Min(None));
234 }
235 }
236
237 let d_str: Vec<usize> = d_slots[..d_len].to_vec();
239 if d_str.iter().any(|&v| v >= self.alphabet_size) {
240 return Ok(Min(None));
241 }
242
243 let c_slots = &config[n..];
245 let c_len = c_slots.iter().position(|&v| v == empty_c).unwrap_or(n);
246
247 for &v in &c_slots[c_len..] {
249 if v != empty_c {
250 return Ok(Min(None));
251 }
252 }
253
254 let mut decoded = Vec::new();
256 let mut pointer_count: usize = 0;
257
258 for &v in &c_slots[..c_len] {
259 if v < self.alphabet_size {
260 decoded.push(v);
262 } else if v > self.alphabet_size {
263 let ptr_idx = v - (self.alphabet_size + 1);
265 if let Some((start, len)) = self.decode_pointer(ptr_idx) {
266 if start + len > d_len {
268 return Ok(Min(None));
269 }
270 decoded.extend_from_slice(&d_str[start..start + len]);
271 pointer_count += 1;
272 } else {
273 return Ok(Min(None));
274 }
275 } else {
276 return Ok(Min(None));
278 }
279 }
280
281 if decoded != self.string {
283 return Ok(Min(None));
284 }
285
286 let d_len = i64::try_from(d_len).map_err(|_| {
288 crate::traits::EvaluationError::IntegerOverflow(
289 "converting the dictionary length to i64".to_string(),
290 )
291 })?;
292 let c_len = i64::try_from(c_len).map_err(|_| {
293 crate::traits::EvaluationError::IntegerOverflow(
294 "converting the compressed-string length to i64".to_string(),
295 )
296 })?;
297 let pointer_count = i64::try_from(pointer_count).map_err(|_| {
298 crate::traits::EvaluationError::IntegerOverflow(
299 "converting the pointer count to i64".to_string(),
300 )
301 })?;
302 let pointer_cost = self
303 .pointer_cost
304 .checked_sub(1)
305 .and_then(|cost| cost.checked_mul(pointer_count))
306 .ok_or_else(|| {
307 crate::traits::EvaluationError::IntegerOverflow(
308 "computing the external macro pointer cost".to_string(),
309 )
310 })?;
311 let cost = d_len
312 .checked_add(c_len)
313 .and_then(|cost| cost.checked_add(pointer_cost))
314 .ok_or_else(|| {
315 crate::traits::EvaluationError::IntegerOverflow(
316 "computing the external macro compression cost".to_string(),
317 )
318 })?;
319 Min(Some(cost))
320 })
321 }
322}
323
324impl crate::solvers::BruteForceProblem for MinimumExternalMacroDataCompression {
325 fn dimensions(&self) -> Vec<usize> {
326 let n = self.string.len();
327 let d_domain = self.alphabet_size + 1; let c_domain = self.c_domain_size(); let mut dims = vec![d_domain; n]; dims.extend(vec![c_domain; n]); dims
332 }
333}
334
335crate::declare_variants! {
336 default MinimumExternalMacroDataCompression => "(alphabet_size + 1) ^ string_length * (alphabet_size + 1 + string_length * (string_length + 1) / 2) ^ string_length",
337}
338
339crate::register_brute_force! {
340 MinimumExternalMacroDataCompression,
341}
342
343#[cfg(feature = "example-db")]
344pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
345 let s: Vec<usize> = (0..6).cycle().take(18).collect();
354 let mut optimal_config = vec![0, 1, 2, 3, 4, 5];
355 optimal_config.extend(vec![6; 12]); optimal_config.extend(vec![12, 12, 12]); optimal_config.extend(vec![6; 15]); vec![crate::example_db::specs::ModelExampleSpec {
359 id: "minimum_external_macro_data_compression",
360 instance: Box::new(MinimumExternalMacroDataCompression::new(6, s, 2)),
361 optimal_config: serde_json::to_value(optimal_config)
362 .expect("solution serialization must succeed"),
363 optimal_value: serde_json::json!(12),
364 }]
365}
366
367#[cfg(test)]
368#[path = "../../unit_tests/models/misc/minimum_external_macro_data_compression.rs"]
369mod tests;