problemreductions/rules/
shortestcommonsupersequence_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
9use crate::models::misc::ShortestCommonSupersequence;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13#[derive(Debug, Clone)]
14pub struct ReductionSCSToILP {
15 target: ILP<bool>,
16 max_length: usize,
17 alphabet_size: usize,
18}
19
20impl ReductionResult for ReductionSCSToILP {
21 type Source = ShortestCommonSupersequence;
22 type Target = ILP<bool>;
23
24 fn target_problem(&self) -> &ILP<bool> {
25 &self.target
26 }
27
28 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
31 let b = self.max_length;
32 let k = self.alphabet_size + 1; (0..b)
34 .map(|p| {
35 (0..k)
36 .find(|&a| target_solution[p * k + a] == 1)
37 .unwrap_or(0)
38 })
39 .collect()
40 }
41}
42
43#[reduction(
44 overhead = {
45 num_vars = "max_length * (alphabet_size + 1) + total_length * max_length",
46 num_constraints = "max_length + total_length + total_length * max_length + total_length + max_length",
47 }
48)]
49impl ReduceTo<ILP<bool>> for ShortestCommonSupersequence {
50 type Result = ReductionSCSToILP;
51
52 fn reduce_to(&self) -> Self::Result {
53 let b = self.max_length();
54 let alpha = self.alphabet_size();
55 let k = alpha + 1; let strings = self.strings();
57 let pad = alpha; let x_count = b * k;
64
65 let mut char_offsets = Vec::with_capacity(strings.len());
67 let mut total_chars = 0usize;
68 for s_str in strings {
69 char_offsets.push(total_chars);
70 total_chars += s_str.len();
71 }
72
73 let m_offset = x_count;
75 let num_vars = x_count + total_chars * b;
76
77 let mut constraints = Vec::new();
78
79 for p in 0..b {
81 let terms: Vec<(usize, f64)> = (0..k).map(|a| (p * k + a, 1.0)).collect();
82 constraints.push(LinearConstraint::eq(terms, 1.0));
83 }
84
85 for gc in 0..total_chars {
87 let terms: Vec<(usize, f64)> = (0..b).map(|p| (m_offset + gc * b + p, 1.0)).collect();
88 constraints.push(LinearConstraint::eq(terms, 1.0));
89 }
90
91 for (s_idx, s_str) in strings.iter().enumerate() {
93 for (j, &sym) in s_str.iter().enumerate() {
94 let gc = char_offsets[s_idx] + j;
95 for p in 0..b {
96 constraints.push(LinearConstraint::le(
98 vec![(m_offset + gc * b + p, 1.0), (p * k + sym, -1.0)],
99 0.0,
100 ));
101 }
102 }
103 }
104
105 for (s_idx, s_str) in strings.iter().enumerate() {
110 for j in 0..s_str.len().saturating_sub(1) {
111 let gc_j = char_offsets[s_idx] + j;
112 let gc_next = char_offsets[s_idx] + j + 1;
113 let mut terms = Vec::new();
114 for p in 0..b {
115 terms.push((m_offset + gc_next * b + p, p as f64));
116 terms.push((m_offset + gc_j * b + p, -(p as f64)));
117 }
118 constraints.push(LinearConstraint::ge(terms, 1.0));
119 }
120 }
121
122 for p in 0..b.saturating_sub(1) {
125 constraints.push(LinearConstraint::le(
126 vec![(p * k + pad, 1.0), ((p + 1) * k + pad, -1.0)],
127 0.0,
128 ));
129 }
130
131 let objective: Vec<(usize, f64)> = (0..b).map(|p| (p * k + pad, 1.0)).collect();
133 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Maximize);
134 ReductionSCSToILP {
135 target,
136 max_length: b,
137 alphabet_size: alpha,
138 }
139 }
140}
141
142#[cfg(feature = "example-db")]
143pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
144 use crate::export::SolutionPair;
145 vec![crate::example_db::specs::RuleExampleSpec {
146 id: "shortestcommonsupersequence_to_ilp",
147 build: || {
148 let source = ShortestCommonSupersequence::new(2, vec![vec![0, 1], vec![1, 0]]);
150 let reduction: ReductionSCSToILP = ReduceTo::<ILP<bool>>::reduce_to(&source);
151 let target_config = {
152 let ilp_solver = crate::solvers::ILPSolver::new();
153 ilp_solver
154 .solve(reduction.target_problem())
155 .expect("ILP should be solvable")
156 };
157 let source_config = reduction.extract_solution(&target_config);
158 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
159 source,
160 SolutionPair {
161 source_config,
162 target_config,
163 },
164 )
165 },
166 }]
167}
168
169#[cfg(test)]
170#[path = "../unit_tests/rules/shortestcommonsupersequence_ilp.rs"]
171mod tests;