1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
17use crate::models::misc::MinimumExternalMacroDataCompression;
18use crate::reduction;
19use crate::rules::traits::{ReduceTo, ReductionResult};
20
21#[derive(Debug, Clone)]
23struct VarLayout {
24 n: usize,
25 k: usize,
26 d_offset: usize,
28 d_used_offset: usize,
30 lit_offset: usize,
32 ptr_offset: usize,
35 ptr_triples: Vec<(usize, usize, usize)>,
37 total_vars: usize,
39}
40
41impl VarLayout {
42 fn new(n: usize, k: usize) -> Self {
43 let d_offset = 0;
44 let d_used_offset = d_offset + n * k;
45 let lit_offset = d_used_offset + n;
46 let ptr_offset = lit_offset + n;
47
48 let mut ptr_triples = Vec::new();
50 for i in 0..n {
51 for l in 1..=(n - i) {
52 for d_start in 0..=(n - l) {
53 ptr_triples.push((i, l, d_start));
54 }
55 }
56 }
57
58 let total_vars = ptr_offset + ptr_triples.len();
59 Self {
60 n,
61 k,
62 d_offset,
63 d_used_offset,
64 lit_offset,
65 ptr_offset,
66 ptr_triples,
67 total_vars,
68 }
69 }
70
71 fn d_var(&self, j: usize, c: usize) -> usize {
72 self.d_offset + j * self.k + c
73 }
74
75 fn d_used_var(&self, j: usize) -> usize {
76 self.d_used_offset + j
77 }
78
79 fn lit_var(&self, i: usize) -> usize {
80 self.lit_offset + i
81 }
82
83 fn ptr_var(&self, i: usize, l: usize, d_start: usize) -> usize {
84 let idx = self
86 .ptr_triples
87 .iter()
88 .position(|&(pi, pl, pd)| pi == i && pl == l && pd == d_start)
89 .expect("invalid ptr triple");
90 self.ptr_offset + idx
91 }
92
93 fn ptr_vars_for_segment(&self, i: usize, l: usize) -> Vec<usize> {
95 self.ptr_triples
96 .iter()
97 .enumerate()
98 .filter(|(_, &(pi, pl, _))| pi == i && pl == l)
99 .map(|(idx, _)| self.ptr_offset + idx)
100 .collect()
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct ReductionEMDCToILP {
107 target: ILP<bool>,
108 layout: VarLayout,
110 source_string: Vec<usize>,
112 alphabet_size: usize,
114}
115
116impl ReductionResult for ReductionEMDCToILP {
117 type Source = MinimumExternalMacroDataCompression;
118 type Target = ILP<bool>;
119
120 fn target_problem(&self) -> &ILP<bool> {
121 &self.target
122 }
123
124 fn extract_solution(
125 &self,
126 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
127 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
128 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
129
130 Ok({
131 let n = self.layout.n;
132 let k = self.alphabet_size;
133 let empty = k; let mut d_slots = vec![empty; n];
137 for j in 0..n {
138 let symbols: Vec<_> = (0..k)
139 .filter(|&c| target_solution[self.layout.d_var(j, c)] == 1)
140 .collect();
141 if target_solution[self.layout.d_used_var(j)] == 1 {
142 match symbols.as_slice() {
143 [symbol] => d_slots[j] = *symbol,
144 [] => {
145 return Err(crate::rules::ExtractionError::invalid(format!(
146 "dictionary slot {j} is active without a symbol"
147 )))
148 }
149 _ => {
150 return Err(crate::rules::ExtractionError::invalid(format!(
151 "dictionary slot {j} selects multiple symbols"
152 )))
153 }
154 }
155 } else if !symbols.is_empty() {
156 return Err(crate::rules::ExtractionError::invalid(format!(
157 "inactive dictionary slot {j} selects a symbol"
158 )));
159 }
160 }
161
162 let mut c_slots = vec![empty; n];
164 let mut c_pos = 0;
165 let mut pos = 0;
166 while pos < n {
167 let pointers: Vec<_> = (1..=(n - pos))
168 .flat_map(|length| {
169 (0..=(n - length)).filter_map(move |start| {
170 (target_solution[self.layout.ptr_var(pos, length, start)] == 1)
171 .then_some((start, length))
172 })
173 })
174 .collect();
175 if target_solution[self.layout.lit_var(pos)] == 1 {
176 if !pointers.is_empty() {
177 return Err(crate::rules::ExtractionError::invalid(format!(
178 "position {pos} selects both a literal and a pointer"
179 )));
180 }
181 c_slots[c_pos] = self.source_string[pos];
183 c_pos += 1;
184 pos += 1;
185 continue;
186 }
187 let [(d_start, length)] = pointers.as_slice() else {
188 return Err(crate::rules::ExtractionError::invalid(format!(
189 "position {pos} must select exactly one pointer"
190 )));
191 };
192 let ptr_idx = encode_pointer(n, *d_start, *length);
193 c_slots[c_pos] = k + 1 + ptr_idx;
194 c_pos += 1;
195 pos += length;
196 }
197
198 let mut config = d_slots;
200 config.extend(c_slots);
201 config
202 })
203 }
204}
205
206fn encode_pointer(n: usize, start: usize, len: usize) -> usize {
209 let mut idx = 0;
210 for s in 0..start {
211 idx += n - s;
212 }
213 idx + len - 1
214}
215
216#[reduction(
217 transform = upper_bound {
218 num_vars = "string_length * alphabet_size + 2 * string_length + string_length ^ 3",
219 num_constraints = "string_length + string_length * alphabet_size + string_length + string_length + 1 + string_length ^ 3 * string_length",
220 },
221 unavailable = {
222 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
223 }
224)]
225impl ReduceTo<ILP<bool>> for MinimumExternalMacroDataCompression {
226 type Result = ReductionEMDCToILP;
227
228 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
229 let n = self.string_length();
230 let k = self.alphabet_size();
231 let h = self.pointer_cost();
232 let s = self.string();
233
234 if n == 0 {
236 let layout = VarLayout::new(0, k);
237 let target = ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
238 .map_err(Self::target_construction)?;
239 return Ok(ReductionEMDCToILP {
240 target,
241 layout,
242 source_string: vec![],
243 alphabet_size: k,
244 });
245 }
246
247 let layout = VarLayout::new(n, k);
248 let num_vars = layout.total_vars;
249 let mut constraints = Vec::new();
250
251 for j in 0..n {
253 let terms: Vec<(usize, i64)> = (0..k).map(|c| (layout.d_var(j, c), 1)).collect();
254 constraints.push(LinearConstraint::le(terms, 1));
255 }
256
257 for j in 0..n {
259 for c in 0..k {
260 constraints.push(LinearConstraint::le(
261 vec![(layout.d_var(j, c), 1), (layout.d_used_var(j), -1)],
262 0,
263 ));
264 }
265 }
266
267 for j in 0..n.saturating_sub(1) {
269 constraints.push(LinearConstraint::le(
270 vec![(layout.d_used_var(j + 1), 1), (layout.d_used_var(j), -1)],
271 0,
272 ));
273 }
274
275 let segment_terms = |i: usize, l: usize| -> Vec<(usize, i64)> {
289 let mut terms = Vec::new();
290 if l == 1 {
291 terms.push((layout.lit_var(i), 1));
292 }
293 for &var in &layout.ptr_vars_for_segment(i, l) {
294 terms.push((var, 1));
295 }
296 terms
297 };
298
299 for node in 0..=n {
301 let mut all_terms: Vec<(usize, i64)> = Vec::new();
302
303 if node == 0 {
304 for l in 1..=n {
306 all_terms.extend(segment_terms(0, l));
307 }
308 constraints.push(LinearConstraint::eq(all_terms, 1));
309 } else if node == n {
310 for j in 0..n {
313 let l = n - j;
314 all_terms.extend(segment_terms(j, l));
315 }
316 constraints.push(LinearConstraint::eq(all_terms, 1));
317 } else {
318 let mut incoming = Vec::new();
321 for j in 0..node {
322 let l = node - j;
323 incoming.extend(segment_terms(j, l));
324 }
325 let mut outgoing = Vec::new();
327 for l in 1..=(n - node) {
328 outgoing.extend(segment_terms(node, l));
329 }
330 for (var, coef) in incoming {
332 all_terms.push((var, coef));
333 }
334 for (var, coef) in outgoing {
335 all_terms.push((var, -coef));
336 }
337 constraints.push(LinearConstraint::eq(all_terms, 0));
338 }
339 }
340
341 for (idx, &(i, l, d_start)) in layout.ptr_triples.iter().enumerate() {
344 let ptr_idx = layout.ptr_offset + idx;
345 for offset in 0..l {
346 let symbol = s[i + offset];
347 constraints.push(LinearConstraint::le(
349 vec![(ptr_idx, 1), (layout.d_var(d_start + offset, symbol), -1)],
350 0,
351 ));
352 }
353 }
354
355 let mut objective: Vec<(usize, i64)> = Vec::new();
363 for j in 0..n {
364 objective.push((layout.d_used_var(j), 1));
365 }
366 for i in 0..n {
367 objective.push((layout.lit_var(i), 1));
368 }
369 for (idx, _) in layout.ptr_triples.iter().enumerate() {
370 objective.push((layout.ptr_offset + idx, h));
371 }
372
373 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
374 .map_err(Self::target_construction)?;
375
376 Ok(ReductionEMDCToILP {
377 target,
378 layout,
379 source_string: s.to_vec(),
380 alphabet_size: k,
381 })
382 }
383}
384
385#[cfg(feature = "example-db")]
386pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
387 use crate::export::SolutionPair;
388
389 vec![crate::example_db::specs::RuleExampleSpec {
394 id: "minimumexternalmacrodatacompression_to_ilp",
395 build: || {
396 let source = MinimumExternalMacroDataCompression::new(2, vec![0, 1], 2);
397 let reduction =
398 ReduceTo::<ILP<bool>>::reduce_to(&source).expect("reduction should succeed");
399 let layout = &reduction.layout;
400 let n = 2;
401 let k = 2;
402
403 let mut target_config = vec![0_i64; layout.total_vars];
405 target_config[layout.lit_var(0)] = 1;
406 target_config[layout.lit_var(1)] = 1;
407
408 let source_config = reduction.extract_solution(&target_config).unwrap();
410 debug_assert_eq!(source_config[..n], [k, k]); debug_assert_eq!(source_config[n..], [0, 1]); crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
414 source,
415 SolutionPair {
416 source_config: serde_json::to_value(source_config)
417 .expect("solution serialization must succeed"),
418 target_config: serde_json::to_value(target_config)
419 .expect("solution serialization must succeed"),
420 },
421 )
422 },
423 }]
424}
425
426#[cfg(test)]
427#[path = "../unit_tests/rules/minimumexternalmacrodatacompression_ilp.rs"]
428mod tests;