problemreductions/rules/
minimummatrixcover_ilp.rs1use crate::models::algebraic::MinimumMatrixCover;
11use crate::models::algebraic::{ObjectiveSense, ILP};
12use crate::reduction;
13use crate::rules::ilp_helpers::mccormick_product;
14use crate::rules::traits::{ReduceTo, ReductionResult};
15
16#[derive(Debug, Clone)]
18pub struct ReductionMinimumMatrixCoverToILP {
19 target: ILP<bool>,
20 n: usize,
21}
22
23impl ReductionResult for ReductionMinimumMatrixCoverToILP {
24 type Source = MinimumMatrixCover;
25 type Target = ILP<bool>;
26
27 fn target_problem(&self) -> &ILP<bool> {
28 &self.target
29 }
30
31 fn extract_solution(
32 &self,
33 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
34 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
35 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
36
37 Ok({
38 target_solution[..self.n]
40 .iter()
41 .map(|&value| value == 1)
42 .collect()
43 })
44 }
45}
46
47fn y_index(n: usize, i: usize, j: usize) -> usize {
49 debug_assert!(i < j);
50 let offset: usize = (0..i).map(|k| n - 1 - k).sum();
52 n + offset + (j - i - 1)
53}
54
55#[reduction(
56 transform = exact {
57 max_constraint_magnitude_bits = "1",
58 num_vars = "num_rows + num_rows * (num_rows - 1) / 2",
59 num_constraints = "3 * num_rows * (num_rows - 1) / 2",
60 num_nonzeros = "7 * num_rows * (num_rows - 1) / 2",
61 },
62)]
63impl ReduceTo<ILP<bool>> for MinimumMatrixCover {
64 type Result = ReductionMinimumMatrixCoverToILP;
65
66 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
67 let n = self.num_rows();
68 let num_pairs = n * (n.saturating_sub(1)) / 2;
69 let num_vars = n + num_pairs;
70
71 let mut constraints = Vec::with_capacity(3 * num_pairs);
73 for i in 0..n {
74 for j in (i + 1)..n {
75 let y = y_index(n, i, j);
76
77 constraints.extend(mccormick_product(y, i, j));
78 }
79 }
80
81 let matrix = self.matrix();
98 let mut obj_coeffs = vec![0i64; num_vars];
99
100 for (i, row_i) in matrix.iter().enumerate() {
102 for j in (i + 1)..n {
103 let y = y_index(n, i, j);
104 let coefficient = row_i[j]
105 .checked_add(matrix[j][i])
106 .and_then(|value| value.checked_mul(4))
107 .ok_or_else(|| {
108 crate::rules::ReductionError::integer_overflow::<
109 MinimumMatrixCover,
110 ILP<bool>,
111 >(
112 "computing an off-diagonal matrix-cover coefficient"
113 )
114 })?;
115 obj_coeffs[y] = coefficient;
116 }
117 }
118
119 for (k, row_k) in matrix.iter().enumerate() {
121 let sum = (0..n).filter(|&j| j != k).try_fold(0_i64, |total, j| {
122 let pair = row_k[j].checked_add(matrix[j][k]).ok_or_else(|| {
123 crate::rules::ReductionError::integer_overflow::<MinimumMatrixCover, ILP<bool>>(
124 "adding symmetric matrix-cover entries",
125 )
126 })?;
127 total.checked_add(pair).ok_or_else(|| {
128 crate::rules::ReductionError::integer_overflow::<MinimumMatrixCover, ILP<bool>>(
129 "summing matrix-cover row coefficients",
130 )
131 })
132 })?;
133 let coefficient = sum.checked_mul(-2).ok_or_else(|| {
134 crate::rules::ReductionError::integer_overflow::<MinimumMatrixCover, ILP<bool>>(
135 "scaling a matrix-cover row coefficient",
136 )
137 })?;
138 obj_coeffs[k] = coefficient;
139 }
140
141 let objective: Vec<(usize, i64)> = obj_coeffs
142 .into_iter()
143 .enumerate()
144 .filter(|&(_, c)| c != 0)
145 .collect();
146
147 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
148 .map_err(Self::target_construction)?;
149
150 Ok(ReductionMinimumMatrixCoverToILP { target, n })
151 }
152}
153
154#[cfg(feature = "example-db")]
155pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
156 use crate::export::SolutionPair;
157
158 vec![crate::example_db::specs::RuleExampleSpec {
159 id: "minimum_matrix_cover_to_ilp",
160 build: || {
161 let source = MinimumMatrixCover::new(vec![vec![0, 3], vec![2, 0]]);
163 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
170 source,
171 SolutionPair {
172 source_config: serde_json::json!(vec![false, true]),
173 target_config: serde_json::json!(vec![0, 1, 0]),
174 },
175 )
176 },
177 }]
178}
179
180#[cfg(test)]
181#[path = "../unit_tests/rules/minimummatrixcover_ilp.rs"]
182mod tests;