problemreductions/rules/
closeststring_ilp.rs1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
25use crate::models::misc::ClosestString;
26use crate::reduction;
27use crate::rules::traits::{ReduceTo, ReductionResult};
28
29#[derive(Debug, Clone)]
36pub struct ReductionClosestStringToILP {
37 target: ILP<i64, i64, Bounded>,
38 alphabet_size: usize,
39 string_length: usize,
40}
41
42impl ReductionResult for ReductionClosestStringToILP {
43 type Source = ClosestString;
44 type Target = ILP<i64, i64, Bounded>;
45
46 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
47 &self.target
48 }
49
50 fn extract_solution(
55 &self,
56 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
57 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
58 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
59
60 let q = self.alphabet_size;
61 let mut center = Vec::with_capacity(self.string_length);
62 for position in 0..self.string_length {
63 let block = &target_solution[position * q..(position + 1) * q];
64 let mut selected = block.iter().enumerate().filter(|(_, value)| **value == 1);
65 let symbol = selected.next().map(|(symbol, _)| symbol).ok_or_else(|| {
66 crate::rules::ExtractionError::invalid(format!(
67 "center position {position} has no selected symbol"
68 ))
69 })?;
70 if selected.next().is_some() || block.iter().any(|&value| value > 1) {
71 return Err(crate::rules::ExtractionError::invalid(format!(
72 "center position {position} is not one-hot"
73 )));
74 }
75 center.push(symbol);
76 }
77 Ok(center)
78 }
79}
80
81#[reduction(transform = {
82 exact {
83 num_vars = "alphabet_size * string_length + 1",
84 num_constraints = "string_length + num_strings",
85 num_nonzeros = "alphabet_size * string_length + num_strings * (string_length + 1)",
86 },
87 upper_bound {
88 max_constraint_magnitude_bits = "string_length + 1",
89 },
90})]
91impl ReduceTo<ILP<i64, i64, Bounded>> for ClosestString {
92 type Result = ReductionClosestStringToILP;
93
94 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
95 let q = self.alphabet_size();
96 let m = self.string_length();
97 let strings = self.strings();
98 let n = strings.len();
99
100 let x_idx = |j: usize, a: usize| -> usize { j * q + a };
101 let r_idx = q * m;
102 let num_vars = q * m + 1;
103
104 let mut constraints: Vec<LinearConstraint> = Vec::with_capacity(m + n);
105
106 for j in 0..m {
110 let terms: Vec<(usize, i64)> = (0..q).map(|a| (x_idx(j, a), 1)).collect();
111 constraints.push(LinearConstraint::eq(terms, 1));
112 }
113
114 for s in strings.iter() {
117 let mut terms: Vec<(usize, i64)> = Vec::with_capacity(m + 1);
118 terms.push((r_idx, 1));
119 for (j, &symbol) in s.iter().enumerate() {
120 terms.push((x_idx(j, symbol), 1));
121 }
122 constraints.push(LinearConstraint::ge(
123 terms,
124 Self::exact_i64(m, "encoding the string length")?,
125 ));
126 }
127
128 let objective = vec![(r_idx, 1)];
130
131 let mut variables = vec![IntegerVariable::binary(); num_vars];
133 variables[r_idx] = IntegerVariable::new(
134 Some(0),
135 Some(Self::exact_i64(m, "bounding the Hamming radius")?),
136 )
137 .map_err(Self::target_construction)?;
138
139 let target =
140 ILP::with_variables(variables, constraints, objective, ObjectiveSense::Minimize)
141 .map_err(Self::target_construction)?;
142
143 Ok(ReductionClosestStringToILP {
144 target,
145 alphabet_size: q,
146 string_length: m,
147 })
148 }
149}
150
151#[cfg(feature = "example-db")]
152pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
153 vec![crate::example_db::specs::RuleExampleSpec {
154 id: "closeststring_to_ilp",
155 build: || {
156 let source = ClosestString::new(
160 2,
161 vec![vec![0, 0, 0], vec![0, 1, 1], vec![1, 0, 1], vec![1, 1, 0]],
162 );
163 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
164 },
165 }]
166}
167
168#[cfg(test)]
169#[path = "../unit_tests/rules/closeststring_ilp.rs"]
170mod tests;