1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::misc::StringToStringCorrection;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11
12#[derive(Debug, Clone)]
14pub struct ReductionSTSCToILP {
15 target: ILP<bool>,
16 n: usize,
17 bound: usize,
18}
19
20fn idx_z(n: usize, t: usize, p: usize, i: usize) -> usize {
22 t * n * n + p * n + i
23}
24
25fn idx_e(n: usize, k: usize, t: usize, p: usize) -> usize {
26 (k + 1) * n * n + t * n + p
27}
28
29fn idx_d(n: usize, k: usize, t: usize, j: usize) -> usize {
30 (k + 1) * (n * n + n) + (t - 1) * n + j
31}
32
33fn idx_s(n: usize, k: usize, t: usize, j: usize) -> usize {
34 let nm1 = n.saturating_sub(1);
35 (k + 1) * (n * n + n) + k * n + (t - 1) * nm1 + j
36}
37
38fn idx_nu(n: usize, k: usize, t: usize) -> usize {
39 let nm1 = n.saturating_sub(1);
40 (k + 1) * (n * n + n) + k * n + k * nm1 + (t - 1)
41}
42
43fn total_vars(n: usize, k: usize) -> usize {
44 let nm1 = n.saturating_sub(1);
45 (k + 1) * n * n + (k + 1) * n + k * n + k * nm1 + k
46}
47
48impl ReductionResult for ReductionSTSCToILP {
49 type Source = StringToStringCorrection;
50 type Target = ILP<bool>;
51
52 fn target_problem(&self) -> &ILP<bool> {
53 &self.target
54 }
55
56 fn extract_solution(
58 &self,
59 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
60 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
61 crate::rules::traits::validate_target_witness(
62 self.target_problem(),
63 target_solution,
64 |value| value.value.is_some(),
65 "target ILP assignment is infeasible",
66 )?;
67
68 Ok({
69 let n = self.n;
70 let k = self.bound;
71 let noop_code = 2 * n;
72
73 if n == 0 {
74 return Ok(vec![noop_code; k]);
75 }
76
77 let nm1 = n.saturating_sub(1);
78 let mut ops = Vec::with_capacity(k);
79
80 for t in 1..=k {
81 let current_len = (0..n)
83 .filter(|&p| target_solution[idx_e(n, k, t - 1, p)] == 0)
84 .count();
85
86 let mut selected = Vec::new();
87 if target_solution[idx_nu(n, k, t)] == 1 {
88 selected.push(noop_code);
89 }
90 selected.extend((0..n).filter(|&j| target_solution[idx_d(n, k, t, j)] == 1));
91 selected.extend(
92 (0..nm1)
93 .filter(|&j| target_solution[idx_s(n, k, t, j)] == 1)
94 .map(|j| current_len + j),
95 );
96 match selected.as_slice() {
97 [operation] => ops.push(*operation),
98 [] => {
99 return Err(crate::rules::ExtractionError::invalid(format!(
100 "edit step {t} has no selected operation"
101 )))
102 }
103 _ => {
104 return Err(crate::rules::ExtractionError::invalid(format!(
105 "edit step {t} has multiple selected operations"
106 )))
107 }
108 }
109 }
110 ops
111 })
112 }
113}
114
115#[crate::aggregate_reduction(ilp_feasibility)]
116impl crate::rules::AggregateReductionResult for ReductionSTSCToILP {}
117
118#[reduction(transform = upper_bound {
122 max_constraint_magnitude_bits = "1",
123 num_vars = "(bound + 1) * source_length^2 + (bound + 1) * source_length + 2 * bound * source_length + bound",
124 num_constraints = "4 * bound * source_length^3 + 2 * bound * source_length^2 + source_length^2 + 6 * bound * source_length + 5 * source_length + bound + 1",
125 num_nonzeros = "12 * bound * source_length^3 + (8 * bound + 4) * source_length^2 + (11 * bound + 5) * source_length + bound",
126})]
127impl ReduceTo<ILP<bool>> for StringToStringCorrection {
128 type Result = ReductionSTSCToILP;
129
130 #[allow(clippy::needless_range_loop)]
131 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
132 let n = self.source_length();
133 let m = self.target_length();
134 let k = self.bound();
135 let source = self.source();
136 let target = self.target();
137
138 if m > n || m < n.saturating_sub(k) {
140 return Ok(ReductionSTSCToILP {
141 target: ILP::new(
142 0,
143 vec![LinearConstraint::le(vec![], -1)],
144 vec![],
145 ObjectiveSense::Minimize,
146 )
147 .map_err(Self::target_construction)?,
148 n,
149 bound: k,
150 });
151 }
152
153 if n == 0 {
155 let nv = k;
156 let mut constraints = Vec::new();
157 for t in 1..=k {
158 constraints.push(LinearConstraint::eq(vec![(t - 1, 1)], 1));
159 }
160 return Ok(ReductionSTSCToILP {
161 target: ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize)
162 .map_err(Self::target_construction)?,
163 n,
164 bound: k,
165 });
166 }
167
168 let nm1 = n.saturating_sub(1);
169 let nv = total_vars(n, k);
170
171 let mut constraints = Vec::new();
172
173 for t in 0..=k {
177 for p in 0..n {
178 let mut terms = vec![(idx_e(n, k, t, p), 1)];
179 for i in 0..n {
180 terms.push((idx_z(n, t, p, i), 1));
181 }
182 constraints.push(LinearConstraint::eq(terms, 1));
183 }
184 }
185
186 for t in 0..=k {
188 for i in 0..n {
189 let terms: Vec<(usize, i64)> = (0..n).map(|p| (idx_z(n, t, p, i), 1)).collect();
190 constraints.push(LinearConstraint::le(terms, 1));
191 }
192 }
193
194 for t in 0..=k {
196 for p in 0..nm1 {
197 constraints.push(LinearConstraint::le(
198 vec![(idx_e(n, k, t, p), 1), (idx_e(n, k, t, p + 1), -1)],
199 0,
200 ));
201 }
202 }
203
204 for p in 0..n {
206 constraints.push(LinearConstraint::eq(vec![(idx_z(n, 0, p, p), 1)], 1));
207 for i in 0..n {
208 if i != p {
209 constraints.push(LinearConstraint::eq(vec![(idx_z(n, 0, p, i), 1)], 0));
210 }
211 }
212 constraints.push(LinearConstraint::eq(vec![(idx_e(n, k, 0, p), 1)], 0));
213 }
214
215 for t in 1..=k {
217 let mut terms = Vec::new();
218 for j in 0..n {
219 terms.push((idx_d(n, k, t, j), 1));
220 }
221 for j in 0..nm1 {
222 terms.push((idx_s(n, k, t, j), 1));
223 }
224 terms.push((idx_nu(n, k, t), 1));
225 constraints.push(LinearConstraint::eq(terms, 1));
226 }
227
228 for t in 1..=k {
230 for j in 0..n {
231 constraints.push(LinearConstraint::le(
232 vec![(idx_d(n, k, t, j), 1), (idx_e(n, k, t - 1, j), 1)],
233 1,
234 ));
235 }
236 for j in 0..nm1 {
237 constraints.push(LinearConstraint::le(
238 vec![(idx_s(n, k, t, j), 1), (idx_e(n, k, t - 1, j), 1)],
239 1,
240 ));
241 constraints.push(LinearConstraint::le(
242 vec![(idx_s(n, k, t, j), 1), (idx_e(n, k, t - 1, j + 1), 1)],
243 1,
244 ));
245 }
246 }
247
248 for t in 1..=k {
250 for p in 0..n {
251 for i in 0..n {
252 constraints.push(LinearConstraint::le(
254 vec![
255 (idx_z(n, t, p, i), 1),
256 (idx_z(n, t - 1, p, i), -1),
257 (idx_nu(n, k, t), 1),
258 ],
259 1,
260 ));
261 constraints.push(LinearConstraint::le(
262 vec![
263 (idx_z(n, t - 1, p, i), 1),
264 (idx_z(n, t, p, i), -1),
265 (idx_nu(n, k, t), 1),
266 ],
267 1,
268 ));
269
270 for j in 0..n {
272 if p < j {
273 constraints.push(LinearConstraint::le(
275 vec![
276 (idx_z(n, t, p, i), 1),
277 (idx_z(n, t - 1, p, i), -1),
278 (idx_d(n, k, t, j), 1),
279 ],
280 1,
281 ));
282 constraints.push(LinearConstraint::le(
283 vec![
284 (idx_z(n, t - 1, p, i), 1),
285 (idx_z(n, t, p, i), -1),
286 (idx_d(n, k, t, j), 1),
287 ],
288 1,
289 ));
290 } else if p + 1 < n {
291 constraints.push(LinearConstraint::le(
293 vec![
294 (idx_z(n, t, p, i), 1),
295 (idx_z(n, t - 1, p + 1, i), -1),
296 (idx_d(n, k, t, j), 1),
297 ],
298 1,
299 ));
300 constraints.push(LinearConstraint::le(
301 vec![
302 (idx_z(n, t - 1, p + 1, i), 1),
303 (idx_z(n, t, p, i), -1),
304 (idx_d(n, k, t, j), 1),
305 ],
306 1,
307 ));
308 } else {
309 constraints.push(LinearConstraint::le(
311 vec![(idx_z(n, t, n - 1, i), 1), (idx_d(n, k, t, j), 1)],
312 1,
313 ));
314 }
315 }
316
317 for j in 0..nm1 {
319 if p != j && p != j + 1 {
320 constraints.push(LinearConstraint::le(
321 vec![
322 (idx_z(n, t, p, i), 1),
323 (idx_z(n, t - 1, p, i), -1),
324 (idx_s(n, k, t, j), 1),
325 ],
326 1,
327 ));
328 constraints.push(LinearConstraint::le(
329 vec![
330 (idx_z(n, t - 1, p, i), 1),
331 (idx_z(n, t, p, i), -1),
332 (idx_s(n, k, t, j), 1),
333 ],
334 1,
335 ));
336 } else if p == j {
337 constraints.push(LinearConstraint::le(
338 vec![
339 (idx_z(n, t, j, i), 1),
340 (idx_z(n, t - 1, j + 1, i), -1),
341 (idx_s(n, k, t, j), 1),
342 ],
343 1,
344 ));
345 constraints.push(LinearConstraint::le(
346 vec![
347 (idx_z(n, t - 1, j + 1, i), 1),
348 (idx_z(n, t, j, i), -1),
349 (idx_s(n, k, t, j), 1),
350 ],
351 1,
352 ));
353 } else {
354 constraints.push(LinearConstraint::le(
356 vec![
357 (idx_z(n, t, j + 1, i), 1),
358 (idx_z(n, t - 1, j, i), -1),
359 (idx_s(n, k, t, j), 1),
360 ],
361 1,
362 ));
363 constraints.push(LinearConstraint::le(
364 vec![
365 (idx_z(n, t - 1, j, i), 1),
366 (idx_z(n, t, j + 1, i), -1),
367 (idx_s(n, k, t, j), 1),
368 ],
369 1,
370 ));
371 }
372 }
373 }
374 }
375 }
376
377 for p in 0..m {
379 let terms: Vec<(usize, i64)> = (0..n)
380 .filter(|&i| source[i] == target[p])
381 .map(|i| (idx_z(n, k, p, i), 1))
382 .collect();
383 constraints.push(LinearConstraint::eq(terms, 1));
384 }
385 for p in m..n {
386 constraints.push(LinearConstraint::eq(vec![(idx_e(n, k, k, p), 1)], 1));
387 }
388
389 let target_ilp = ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize)
390 .map_err(Self::target_construction)?;
391 Ok(ReductionSTSCToILP {
392 target: target_ilp,
393 n,
394 bound: k,
395 })
396 }
397}
398
399#[cfg(feature = "example-db")]
400pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
401 use crate::export::SolutionPair;
402 vec![crate::example_db::specs::RuleExampleSpec {
403 id: "stringtostringcorrection_to_ilp",
404 build: || {
405 let source = StringToStringCorrection::new(2, vec![0, 1, 0], vec![1, 0], 1);
407 let reduction: ReductionSTSCToILP =
408 ReduceTo::<ILP<bool>>::reduce_to(&source).expect("reduction should succeed");
409 let target_config = {
410 let ilp_solver = crate::solvers::ILPSolver::new();
411 ilp_solver
412 .solve(reduction.target_problem())
413 .expect("ILP should be solvable")
414 };
415 let source_config = reduction.extract_solution(&target_config).unwrap();
416 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
417 source,
418 SolutionPair {
419 source_config: serde_json::to_value(source_config)
420 .expect("solution serialization must succeed"),
421 target_config: serde_json::to_value(target_config)
422 .expect("solution serialization must succeed"),
423 },
424 )
425 },
426 }]
427}
428
429#[cfg(test)]
430#[path = "../unit_tests/rules/stringtostringcorrection_ilp.rs"]
431mod tests;