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_solution(self.target_problem(), target_solution)?;
62
63 Ok({
64 let n = self.n;
65 let k = self.bound;
66 let noop_code = 2 * n;
67
68 if n == 0 {
69 return Ok(vec![noop_code; k]);
70 }
71
72 let nm1 = n.saturating_sub(1);
73 let mut ops = Vec::with_capacity(k);
74
75 for t in 1..=k {
76 let current_len = (0..n)
78 .filter(|&p| target_solution[idx_e(n, k, t - 1, p)] == 0)
79 .count();
80
81 let mut selected = Vec::new();
82 if target_solution[idx_nu(n, k, t)] == 1 {
83 selected.push(noop_code);
84 }
85 selected.extend((0..n).filter(|&j| target_solution[idx_d(n, k, t, j)] == 1));
86 selected.extend(
87 (0..nm1)
88 .filter(|&j| target_solution[idx_s(n, k, t, j)] == 1)
89 .map(|j| current_len + j),
90 );
91 match selected.as_slice() {
92 [operation] => ops.push(*operation),
93 [] => {
94 return Err(crate::rules::ExtractionError::invalid(format!(
95 "edit step {t} has no selected operation"
96 )))
97 }
98 _ => {
99 return Err(crate::rules::ExtractionError::invalid(format!(
100 "edit step {t} has multiple selected operations"
101 )))
102 }
103 }
104 }
105 ops
106 })
107 }
108}
109
110#[reduction(
111 transform = upper_bound {
112 num_vars = "(bound + 1) * source_length^2 + (bound + 1) * source_length + 2 * bound * source_length + bound",
113 num_constraints = "4 * bound * source_length^3 + 2 * bound * source_length^2 + source_length^2 + 6 * bound * source_length + 5 * source_length + bound",
114 },
115 unavailable = {
116 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
117 }
118)]
119impl ReduceTo<ILP<bool>> for StringToStringCorrection {
120 type Result = ReductionSTSCToILP;
121
122 #[allow(clippy::needless_range_loop)]
123 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
124 let n = self.source_length();
125 let m = self.target_length();
126 let k = self.bound();
127 let source = self.source();
128 let target = self.target();
129
130 if m > n || m < n.saturating_sub(k) {
132 return Ok(ReductionSTSCToILP {
133 target: ILP::new(
134 0,
135 vec![LinearConstraint::le(vec![], -1)],
136 vec![],
137 ObjectiveSense::Minimize,
138 )
139 .map_err(Self::target_construction)?,
140 n,
141 bound: k,
142 });
143 }
144
145 if n == 0 {
147 let nv = k;
148 let mut constraints = Vec::new();
149 for t in 1..=k {
150 constraints.push(LinearConstraint::eq(vec![(t - 1, 1)], 1));
151 }
152 return Ok(ReductionSTSCToILP {
153 target: ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize)
154 .map_err(Self::target_construction)?,
155 n,
156 bound: k,
157 });
158 }
159
160 let nm1 = n.saturating_sub(1);
161 let nv = total_vars(n, k);
162
163 let mut constraints = Vec::new();
164
165 for t in 0..=k {
169 for p in 0..n {
170 let mut terms = vec![(idx_e(n, k, t, p), 1)];
171 for i in 0..n {
172 terms.push((idx_z(n, t, p, i), 1));
173 }
174 constraints.push(LinearConstraint::eq(terms, 1));
175 }
176 }
177
178 for t in 0..=k {
180 for i in 0..n {
181 let terms: Vec<(usize, i64)> = (0..n).map(|p| (idx_z(n, t, p, i), 1)).collect();
182 constraints.push(LinearConstraint::le(terms, 1));
183 }
184 }
185
186 for t in 0..=k {
188 for p in 0..nm1 {
189 constraints.push(LinearConstraint::le(
190 vec![(idx_e(n, k, t, p), 1), (idx_e(n, k, t, p + 1), -1)],
191 0,
192 ));
193 }
194 }
195
196 for p in 0..n {
198 constraints.push(LinearConstraint::eq(vec![(idx_z(n, 0, p, p), 1)], 1));
199 for i in 0..n {
200 if i != p {
201 constraints.push(LinearConstraint::eq(vec![(idx_z(n, 0, p, i), 1)], 0));
202 }
203 }
204 constraints.push(LinearConstraint::eq(vec![(idx_e(n, k, 0, p), 1)], 0));
205 }
206
207 for t in 1..=k {
209 let mut terms = Vec::new();
210 for j in 0..n {
211 terms.push((idx_d(n, k, t, j), 1));
212 }
213 for j in 0..nm1 {
214 terms.push((idx_s(n, k, t, j), 1));
215 }
216 terms.push((idx_nu(n, k, t), 1));
217 constraints.push(LinearConstraint::eq(terms, 1));
218 }
219
220 for t in 1..=k {
222 for j in 0..n {
223 constraints.push(LinearConstraint::le(
224 vec![(idx_d(n, k, t, j), 1), (idx_e(n, k, t - 1, j), 1)],
225 1,
226 ));
227 }
228 for j in 0..nm1 {
229 constraints.push(LinearConstraint::le(
230 vec![(idx_s(n, k, t, j), 1), (idx_e(n, k, t - 1, j), 1)],
231 1,
232 ));
233 constraints.push(LinearConstraint::le(
234 vec![(idx_s(n, k, t, j), 1), (idx_e(n, k, t - 1, j + 1), 1)],
235 1,
236 ));
237 }
238 }
239
240 for t in 1..=k {
242 for p in 0..n {
243 for i in 0..n {
244 constraints.push(LinearConstraint::le(
246 vec![
247 (idx_z(n, t, p, i), 1),
248 (idx_z(n, t - 1, p, i), -1),
249 (idx_nu(n, k, t), 1),
250 ],
251 1,
252 ));
253 constraints.push(LinearConstraint::le(
254 vec![
255 (idx_z(n, t - 1, p, i), 1),
256 (idx_z(n, t, p, i), -1),
257 (idx_nu(n, k, t), 1),
258 ],
259 1,
260 ));
261
262 for j in 0..n {
264 if p < j {
265 constraints.push(LinearConstraint::le(
267 vec![
268 (idx_z(n, t, p, i), 1),
269 (idx_z(n, t - 1, p, i), -1),
270 (idx_d(n, k, t, j), 1),
271 ],
272 1,
273 ));
274 constraints.push(LinearConstraint::le(
275 vec![
276 (idx_z(n, t - 1, p, i), 1),
277 (idx_z(n, t, p, i), -1),
278 (idx_d(n, k, t, j), 1),
279 ],
280 1,
281 ));
282 } else if p + 1 < n {
283 constraints.push(LinearConstraint::le(
285 vec![
286 (idx_z(n, t, p, i), 1),
287 (idx_z(n, t - 1, p + 1, i), -1),
288 (idx_d(n, k, t, j), 1),
289 ],
290 1,
291 ));
292 constraints.push(LinearConstraint::le(
293 vec![
294 (idx_z(n, t - 1, p + 1, i), 1),
295 (idx_z(n, t, p, i), -1),
296 (idx_d(n, k, t, j), 1),
297 ],
298 1,
299 ));
300 } else {
301 constraints.push(LinearConstraint::le(
303 vec![(idx_z(n, t, n - 1, i), 1), (idx_d(n, k, t, j), 1)],
304 1,
305 ));
306 }
307 }
308
309 for j in 0..nm1 {
311 if p != j && p != j + 1 {
312 constraints.push(LinearConstraint::le(
313 vec![
314 (idx_z(n, t, p, i), 1),
315 (idx_z(n, t - 1, p, i), -1),
316 (idx_s(n, k, t, j), 1),
317 ],
318 1,
319 ));
320 constraints.push(LinearConstraint::le(
321 vec![
322 (idx_z(n, t - 1, p, i), 1),
323 (idx_z(n, t, p, i), -1),
324 (idx_s(n, k, t, j), 1),
325 ],
326 1,
327 ));
328 } else if p == j {
329 constraints.push(LinearConstraint::le(
330 vec![
331 (idx_z(n, t, j, i), 1),
332 (idx_z(n, t - 1, j + 1, i), -1),
333 (idx_s(n, k, t, j), 1),
334 ],
335 1,
336 ));
337 constraints.push(LinearConstraint::le(
338 vec![
339 (idx_z(n, t - 1, j + 1, i), 1),
340 (idx_z(n, t, j, i), -1),
341 (idx_s(n, k, t, j), 1),
342 ],
343 1,
344 ));
345 } else {
346 constraints.push(LinearConstraint::le(
348 vec![
349 (idx_z(n, t, j + 1, i), 1),
350 (idx_z(n, t - 1, j, i), -1),
351 (idx_s(n, k, t, j), 1),
352 ],
353 1,
354 ));
355 constraints.push(LinearConstraint::le(
356 vec![
357 (idx_z(n, t - 1, j, i), 1),
358 (idx_z(n, t, j + 1, i), -1),
359 (idx_s(n, k, t, j), 1),
360 ],
361 1,
362 ));
363 }
364 }
365 }
366 }
367 }
368
369 for p in 0..m {
371 let terms: Vec<(usize, i64)> = (0..n)
372 .filter(|&i| source[i] == target[p])
373 .map(|i| (idx_z(n, k, p, i), 1))
374 .collect();
375 constraints.push(LinearConstraint::eq(terms, 1));
376 }
377 for p in m..n {
378 constraints.push(LinearConstraint::eq(vec![(idx_e(n, k, k, p), 1)], 1));
379 }
380
381 let target_ilp = ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize)
382 .map_err(Self::target_construction)?;
383 Ok(ReductionSTSCToILP {
384 target: target_ilp,
385 n,
386 bound: k,
387 })
388 }
389}
390
391#[cfg(feature = "example-db")]
392pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
393 use crate::export::SolutionPair;
394 vec![crate::example_db::specs::RuleExampleSpec {
395 id: "stringtostringcorrection_to_ilp",
396 build: || {
397 let source = StringToStringCorrection::new(2, vec![0, 1, 0], vec![1, 0], 1);
399 let reduction: ReductionSTSCToILP =
400 ReduceTo::<ILP<bool>>::reduce_to(&source).expect("reduction should succeed");
401 let target_config = {
402 let ilp_solver = crate::solvers::ILPSolver::new();
403 ilp_solver
404 .solve(reduction.target_problem())
405 .expect("ILP should be solvable")
406 };
407 let source_config = reduction.extract_solution(&target_config).unwrap();
408 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
409 source,
410 SolutionPair {
411 source_config: serde_json::to_value(source_config)
412 .expect("solution serialization must succeed"),
413 target_config: serde_json::to_value(target_config)
414 .expect("solution serialization must succeed"),
415 },
416 )
417 },
418 }]
419}
420
421#[cfg(test)]
422#[path = "../unit_tests/rules/stringtostringcorrection_ilp.rs"]
423mod tests;