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(&self, target_solution: &[usize]) -> Vec<usize> {
58 let n = self.n;
59 let k = self.bound;
60 let noop_code = 2 * n;
61
62 if n == 0 {
63 return vec![noop_code; k];
64 }
65
66 let nm1 = n.saturating_sub(1);
67 let mut ops = Vec::with_capacity(k);
68
69 for t in 1..=k {
70 let current_len = (0..n)
72 .filter(|&p| target_solution[idx_e(n, k, t - 1, p)] == 0)
73 .count();
74
75 if target_solution[idx_nu(n, k, t)] == 1 {
76 ops.push(noop_code);
77 } else {
78 let mut found = false;
79 for j in 0..n {
80 if target_solution[idx_d(n, k, t, j)] == 1 {
81 ops.push(j);
82 found = true;
83 break;
84 }
85 }
86 if !found {
87 for j in 0..nm1 {
88 if target_solution[idx_s(n, k, t, j)] == 1 {
89 ops.push(current_len + j);
90 found = true;
91 break;
92 }
93 }
94 if !found {
95 ops.push(noop_code);
96 }
97 }
98 }
99 }
100 ops
101 }
102}
103
104#[reduction(
105 overhead = {
106 num_vars = "(bound + 1) * source_length * source_length + (bound + 1) * source_length + 2 * bound * source_length",
107 num_constraints = "(bound + 1) * source_length * source_length",
108 }
109)]
110impl ReduceTo<ILP<bool>> for StringToStringCorrection {
111 type Result = ReductionSTSCToILP;
112
113 #[allow(clippy::needless_range_loop)]
114 fn reduce_to(&self) -> Self::Result {
115 let n = self.source_length();
116 let m = self.target_length();
117 let k = self.bound();
118 let source = self.source();
119 let target = self.target();
120
121 if m > n || m < n.saturating_sub(k) {
123 return ReductionSTSCToILP {
124 target: ILP::new(
125 0,
126 vec![LinearConstraint::le(vec![], -1.0)],
127 vec![],
128 ObjectiveSense::Minimize,
129 ),
130 n,
131 bound: k,
132 };
133 }
134
135 if n == 0 {
137 let nv = k;
138 let mut constraints = Vec::new();
139 for t in 1..=k {
140 constraints.push(LinearConstraint::eq(vec![(t - 1, 1.0)], 1.0));
141 }
142 return ReductionSTSCToILP {
143 target: ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize),
144 n,
145 bound: k,
146 };
147 }
148
149 let nm1 = n.saturating_sub(1);
150 let nv = total_vars(n, k);
151
152 let mut constraints = Vec::new();
153
154 for t in 0..=k {
158 for p in 0..n {
159 let mut terms = vec![(idx_e(n, k, t, p), 1.0)];
160 for i in 0..n {
161 terms.push((idx_z(n, t, p, i), 1.0));
162 }
163 constraints.push(LinearConstraint::eq(terms, 1.0));
164 }
165 }
166
167 for t in 0..=k {
169 for i in 0..n {
170 let terms: Vec<(usize, f64)> = (0..n).map(|p| (idx_z(n, t, p, i), 1.0)).collect();
171 constraints.push(LinearConstraint::le(terms, 1.0));
172 }
173 }
174
175 for t in 0..=k {
177 for p in 0..nm1 {
178 constraints.push(LinearConstraint::le(
179 vec![(idx_e(n, k, t, p), 1.0), (idx_e(n, k, t, p + 1), -1.0)],
180 0.0,
181 ));
182 }
183 }
184
185 for p in 0..n {
187 constraints.push(LinearConstraint::eq(vec![(idx_z(n, 0, p, p), 1.0)], 1.0));
188 for i in 0..n {
189 if i != p {
190 constraints.push(LinearConstraint::eq(vec![(idx_z(n, 0, p, i), 1.0)], 0.0));
191 }
192 }
193 constraints.push(LinearConstraint::eq(vec![(idx_e(n, k, 0, p), 1.0)], 0.0));
194 }
195
196 for t in 1..=k {
198 let mut terms = Vec::new();
199 for j in 0..n {
200 terms.push((idx_d(n, k, t, j), 1.0));
201 }
202 for j in 0..nm1 {
203 terms.push((idx_s(n, k, t, j), 1.0));
204 }
205 terms.push((idx_nu(n, k, t), 1.0));
206 constraints.push(LinearConstraint::eq(terms, 1.0));
207 }
208
209 for t in 1..=k {
211 for j in 0..n {
212 constraints.push(LinearConstraint::le(
213 vec![(idx_d(n, k, t, j), 1.0), (idx_e(n, k, t - 1, j), 1.0)],
214 1.0,
215 ));
216 }
217 for j in 0..nm1 {
218 constraints.push(LinearConstraint::le(
219 vec![(idx_s(n, k, t, j), 1.0), (idx_e(n, k, t - 1, j), 1.0)],
220 1.0,
221 ));
222 constraints.push(LinearConstraint::le(
223 vec![(idx_s(n, k, t, j), 1.0), (idx_e(n, k, t - 1, j + 1), 1.0)],
224 1.0,
225 ));
226 }
227 }
228
229 for t in 1..=k {
231 for p in 0..n {
232 for i in 0..n {
233 constraints.push(LinearConstraint::le(
235 vec![
236 (idx_z(n, t, p, i), 1.0),
237 (idx_z(n, t - 1, p, i), -1.0),
238 (idx_nu(n, k, t), 1.0),
239 ],
240 1.0,
241 ));
242 constraints.push(LinearConstraint::le(
243 vec![
244 (idx_z(n, t - 1, p, i), 1.0),
245 (idx_z(n, t, p, i), -1.0),
246 (idx_nu(n, k, t), 1.0),
247 ],
248 1.0,
249 ));
250
251 for j in 0..n {
253 if p < j {
254 constraints.push(LinearConstraint::le(
256 vec![
257 (idx_z(n, t, p, i), 1.0),
258 (idx_z(n, t - 1, p, i), -1.0),
259 (idx_d(n, k, t, j), 1.0),
260 ],
261 1.0,
262 ));
263 constraints.push(LinearConstraint::le(
264 vec![
265 (idx_z(n, t - 1, p, i), 1.0),
266 (idx_z(n, t, p, i), -1.0),
267 (idx_d(n, k, t, j), 1.0),
268 ],
269 1.0,
270 ));
271 } else if p + 1 < n {
272 constraints.push(LinearConstraint::le(
274 vec![
275 (idx_z(n, t, p, i), 1.0),
276 (idx_z(n, t - 1, p + 1, i), -1.0),
277 (idx_d(n, k, t, j), 1.0),
278 ],
279 1.0,
280 ));
281 constraints.push(LinearConstraint::le(
282 vec![
283 (idx_z(n, t - 1, p + 1, i), 1.0),
284 (idx_z(n, t, p, i), -1.0),
285 (idx_d(n, k, t, j), 1.0),
286 ],
287 1.0,
288 ));
289 } else {
290 constraints.push(LinearConstraint::le(
292 vec![(idx_z(n, t, n - 1, i), 1.0), (idx_d(n, k, t, j), 1.0)],
293 1.0,
294 ));
295 }
296 }
297
298 for j in 0..nm1 {
300 if p != j && p != j + 1 {
301 constraints.push(LinearConstraint::le(
302 vec![
303 (idx_z(n, t, p, i), 1.0),
304 (idx_z(n, t - 1, p, i), -1.0),
305 (idx_s(n, k, t, j), 1.0),
306 ],
307 1.0,
308 ));
309 constraints.push(LinearConstraint::le(
310 vec![
311 (idx_z(n, t - 1, p, i), 1.0),
312 (idx_z(n, t, p, i), -1.0),
313 (idx_s(n, k, t, j), 1.0),
314 ],
315 1.0,
316 ));
317 } else if p == j {
318 constraints.push(LinearConstraint::le(
319 vec![
320 (idx_z(n, t, j, i), 1.0),
321 (idx_z(n, t - 1, j + 1, i), -1.0),
322 (idx_s(n, k, t, j), 1.0),
323 ],
324 1.0,
325 ));
326 constraints.push(LinearConstraint::le(
327 vec![
328 (idx_z(n, t - 1, j + 1, i), 1.0),
329 (idx_z(n, t, j, i), -1.0),
330 (idx_s(n, k, t, j), 1.0),
331 ],
332 1.0,
333 ));
334 } else {
335 constraints.push(LinearConstraint::le(
337 vec![
338 (idx_z(n, t, j + 1, i), 1.0),
339 (idx_z(n, t - 1, j, i), -1.0),
340 (idx_s(n, k, t, j), 1.0),
341 ],
342 1.0,
343 ));
344 constraints.push(LinearConstraint::le(
345 vec![
346 (idx_z(n, t - 1, j, i), 1.0),
347 (idx_z(n, t, j + 1, i), -1.0),
348 (idx_s(n, k, t, j), 1.0),
349 ],
350 1.0,
351 ));
352 }
353 }
354 }
355 }
356 }
357
358 for p in 0..m {
360 let terms: Vec<(usize, f64)> = (0..n)
361 .filter(|&i| source[i] == target[p])
362 .map(|i| (idx_z(n, k, p, i), 1.0))
363 .collect();
364 constraints.push(LinearConstraint::eq(terms, 1.0));
365 }
366 for p in m..n {
367 constraints.push(LinearConstraint::eq(vec![(idx_e(n, k, k, p), 1.0)], 1.0));
368 }
369
370 let target_ilp = ILP::new(nv, constraints, vec![], ObjectiveSense::Minimize);
371 ReductionSTSCToILP {
372 target: target_ilp,
373 n,
374 bound: k,
375 }
376 }
377}
378
379#[cfg(feature = "example-db")]
380pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
381 use crate::export::SolutionPair;
382 vec![crate::example_db::specs::RuleExampleSpec {
383 id: "stringtostringcorrection_to_ilp",
384 build: || {
385 let source = StringToStringCorrection::new(2, vec![0, 1, 0], vec![1, 0], 1);
387 let reduction: ReductionSTSCToILP = ReduceTo::<ILP<bool>>::reduce_to(&source);
388 let target_config = {
389 let ilp_solver = crate::solvers::ILPSolver::new();
390 ilp_solver
391 .solve(reduction.target_problem())
392 .expect("ILP should be solvable")
393 };
394 let source_config = reduction.extract_solution(&target_config);
395 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
396 source,
397 SolutionPair {
398 source_config,
399 target_config,
400 },
401 )
402 },
403 }]
404}
405
406#[cfg(test)]
407#[path = "../unit_tests/rules/stringtostringcorrection_ilp.rs"]
408mod tests;