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(transform = {
221 exact {
222 max_constraint_magnitude_bits = "1",
223 num_vars = "string_length * alphabet_size + 2 * string_length + string_length * (string_length + 1) * (2 * string_length + 1) / 6",
224 num_constraints = "3 * string_length + string_length * alphabet_size + string_length * (string_length + 1)^2 * (string_length + 2) / 12",
225 },
226 upper_bound {
227 num_nonzeros = "3 * string_length * alphabet_size + 4 * string_length + string_length * (string_length + 1) * (2 * string_length + 1) / 3 + string_length * (string_length + 1)^2 * (string_length + 2) / 6",
228 },
229})]
230impl ReduceTo<ILP<bool>> for MinimumExternalMacroDataCompression {
231 type Result = ReductionEMDCToILP;
232
233 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
234 let n = self.string_length();
235 let k = self.alphabet_size();
236 let h = self.pointer_cost();
237 let s = self.string();
238
239 if n == 0 {
241 let layout = VarLayout::new(0, k);
242 let target = ILP::new(0, vec![], vec![], ObjectiveSense::Minimize)
243 .map_err(Self::target_construction)?;
244 return Ok(ReductionEMDCToILP {
245 target,
246 layout,
247 source_string: vec![],
248 alphabet_size: k,
249 });
250 }
251
252 let layout = VarLayout::new(n, k);
253 let num_vars = layout.total_vars;
254 let mut constraints = Vec::new();
255
256 for j in 0..n {
258 let terms: Vec<(usize, i64)> = (0..k).map(|c| (layout.d_var(j, c), 1)).collect();
259 constraints.push(LinearConstraint::le(terms, 1));
260 }
261
262 for j in 0..n {
264 for c in 0..k {
265 constraints.push(LinearConstraint::le(
266 vec![(layout.d_var(j, c), 1), (layout.d_used_var(j), -1)],
267 0,
268 ));
269 }
270 }
271
272 for j in 0..n.saturating_sub(1) {
274 constraints.push(LinearConstraint::le(
275 vec![(layout.d_used_var(j + 1), 1), (layout.d_used_var(j), -1)],
276 0,
277 ));
278 }
279
280 let segment_terms = |i: usize, l: usize| -> Vec<(usize, i64)> {
294 let mut terms = Vec::new();
295 if l == 1 {
296 terms.push((layout.lit_var(i), 1));
297 }
298 for &var in &layout.ptr_vars_for_segment(i, l) {
299 terms.push((var, 1));
300 }
301 terms
302 };
303
304 for node in 0..=n {
306 let mut all_terms: Vec<(usize, i64)> = Vec::new();
307
308 if node == 0 {
309 for l in 1..=n {
311 all_terms.extend(segment_terms(0, l));
312 }
313 constraints.push(LinearConstraint::eq(all_terms, 1));
314 } else if node == n {
315 for j in 0..n {
318 let l = n - j;
319 all_terms.extend(segment_terms(j, l));
320 }
321 constraints.push(LinearConstraint::eq(all_terms, 1));
322 } else {
323 let mut incoming = Vec::new();
326 for j in 0..node {
327 let l = node - j;
328 incoming.extend(segment_terms(j, l));
329 }
330 let mut outgoing = Vec::new();
332 for l in 1..=(n - node) {
333 outgoing.extend(segment_terms(node, l));
334 }
335 for (var, coef) in incoming {
337 all_terms.push((var, coef));
338 }
339 for (var, coef) in outgoing {
340 all_terms.push((var, -coef));
341 }
342 constraints.push(LinearConstraint::eq(all_terms, 0));
343 }
344 }
345
346 for (idx, &(i, l, d_start)) in layout.ptr_triples.iter().enumerate() {
349 let ptr_idx = layout.ptr_offset + idx;
350 for offset in 0..l {
351 let symbol = s[i + offset];
352 constraints.push(LinearConstraint::le(
354 vec![(ptr_idx, 1), (layout.d_var(d_start + offset, symbol), -1)],
355 0,
356 ));
357 }
358 }
359
360 let mut objective: Vec<(usize, i64)> = Vec::new();
368 for j in 0..n {
369 objective.push((layout.d_used_var(j), 1));
370 }
371 for i in 0..n {
372 objective.push((layout.lit_var(i), 1));
373 }
374 for (idx, _) in layout.ptr_triples.iter().enumerate() {
375 objective.push((layout.ptr_offset + idx, h));
376 }
377
378 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
379 .map_err(Self::target_construction)?;
380
381 Ok(ReductionEMDCToILP {
382 target,
383 layout,
384 source_string: s.to_vec(),
385 alphabet_size: k,
386 })
387 }
388}
389
390#[cfg(feature = "example-db")]
391pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
392 use crate::export::SolutionPair;
393
394 vec![crate::example_db::specs::RuleExampleSpec {
399 id: "minimumexternalmacrodatacompression_to_ilp",
400 build: || {
401 let source = MinimumExternalMacroDataCompression::new(2, vec![0, 1], 2);
402 let reduction =
403 ReduceTo::<ILP<bool>>::reduce_to(&source).expect("reduction should succeed");
404 let layout = &reduction.layout;
405 let n = 2;
406 let k = 2;
407
408 let mut target_config = vec![0_i64; layout.total_vars];
410 target_config[layout.lit_var(0)] = 1;
411 target_config[layout.lit_var(1)] = 1;
412
413 let source_config = reduction.extract_solution(&target_config).unwrap();
415 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>>(
419 source,
420 SolutionPair {
421 source_config: serde_json::to_value(source_config)
422 .expect("solution serialization must succeed"),
423 target_config: serde_json::to_value(target_config)
424 .expect("solution serialization must succeed"),
425 },
426 )
427 },
428 }]
429}
430
431#[cfg(test)]
432#[path = "../unit_tests/rules/minimumexternalmacrodatacompression_ilp.rs"]
433mod tests;