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(
31 &self,
32 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
33 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
34 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
35
36 Ok(crate::rules::ilp_helpers::one_hot_decode_rows(
37 target_solution,
38 self.max_length,
39 self.alphabet_size + 1,
40 0,
41 )?
42 .into_iter()
43 .map(|symbol| (symbol < self.alphabet_size).then_some(symbol))
44 .collect())
45 }
46}
47
48#[reduction(transform = upper_bound {
53 max_constraint_magnitude_bits = "max_length / 2 + 1",
54 num_vars = "max_length * (alphabet_size + 1) + total_length * max_length",
55 num_constraints = "max_length + total_length + total_length * max_length + total_length + max_length",
56 num_nonzeros = "max_length * (alphabet_size + 3) + 5 * total_length * max_length",
57})]
58impl ReduceTo<ILP<bool>> for ShortestCommonSupersequence {
59 type Result = ReductionSCSToILP;
60
61 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
62 let b = self.max_length();
63 let alpha = self.alphabet_size();
64 let k = alpha + 1; let strings = self.strings();
66 let pad = alpha; let x_count = b * k;
73
74 let mut char_offsets = Vec::with_capacity(strings.len());
76 let mut total_chars = 0usize;
77 for s_str in strings {
78 char_offsets.push(total_chars);
79 total_chars += s_str.len();
80 }
81
82 let m_offset = x_count;
84 let num_vars = x_count + total_chars * b;
85
86 let mut constraints = Vec::new();
87
88 for p in 0..b {
90 let terms: Vec<(usize, i64)> = (0..k).map(|a| (p * k + a, 1)).collect();
91 constraints.push(LinearConstraint::eq(terms, 1));
92 }
93
94 for gc in 0..total_chars {
96 let terms: Vec<(usize, i64)> = (0..b).map(|p| (m_offset + gc * b + p, 1)).collect();
97 constraints.push(LinearConstraint::eq(terms, 1));
98 }
99
100 for (s_idx, s_str) in strings.iter().enumerate() {
102 for (j, &sym) in s_str.iter().enumerate() {
103 let gc = char_offsets[s_idx] + j;
104 for p in 0..b {
105 constraints.push(LinearConstraint::le(
107 vec![(m_offset + gc * b + p, 1), (p * k + sym, -1)],
108 0,
109 ));
110 }
111 }
112 }
113
114 for (s_idx, s_str) in strings.iter().enumerate() {
119 for j in 0..s_str.len().saturating_sub(1) {
120 let gc_j = char_offsets[s_idx] + j;
121 let gc_next = char_offsets[s_idx] + j + 1;
122 let mut terms = Vec::new();
123 for p in 0..b {
124 let p_i64 = Self::exact_i64(p, "encoding a sequence position")?;
125 terms.push((m_offset + gc_next * b + p, p_i64));
126 terms.push((m_offset + gc_j * b + p, -p_i64));
127 }
128 constraints.push(LinearConstraint::ge(terms, 1));
129 }
130 }
131
132 for p in 0..b.saturating_sub(1) {
135 constraints.push(LinearConstraint::le(
136 vec![(p * k + pad, 1), ((p + 1) * k + pad, -1)],
137 0,
138 ));
139 }
140
141 let objective: Vec<(usize, i64)> = (0..b).map(|p| (p * k + pad, 1)).collect();
143 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Maximize)
144 .map_err(Self::target_construction)?;
145 Ok(ReductionSCSToILP {
146 target,
147 max_length: b,
148 alphabet_size: alpha,
149 })
150 }
151}
152
153#[cfg(feature = "example-db")]
154pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
155 use crate::export::SolutionPair;
156 vec![crate::example_db::specs::RuleExampleSpec {
157 id: "shortestcommonsupersequence_to_ilp",
158 build: || {
159 let source = ShortestCommonSupersequence::new(2, vec![vec![0, 1], vec![1, 0]]);
161 let reduction: ReductionSCSToILP =
162 ReduceTo::<ILP<bool>>::reduce_to(&source).expect("reduction should succeed");
163 let target_config = {
164 let ilp_solver = crate::solvers::ILPSolver::new();
165 ilp_solver
166 .solve(reduction.target_problem())
167 .expect("ILP should be solvable")
168 };
169 let source_config = reduction.extract_solution(&target_config).unwrap();
170 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
171 source,
172 SolutionPair {
173 source_config: serde_json::to_value(source_config)
174 .expect("solution serialization must succeed"),
175 target_config: serde_json::to_value(target_config)
176 .expect("solution serialization must succeed"),
177 },
178 )
179 },
180 }]
181}
182
183#[cfg(test)]
184#[path = "../unit_tests/rules/shortestcommonsupersequence_ilp.rs"]
185mod tests;