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 num_vars = "num_rows + num_rows * (num_rows - 1) / 2",
58 num_constraints = "3 * num_rows * (num_rows - 1) / 2",
59 },
60 unavailable = {
61 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
62 }
63)]
64impl ReduceTo<ILP<bool>> for MinimumMatrixCover {
65 type Result = ReductionMinimumMatrixCoverToILP;
66
67 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
68 let n = self.num_rows();
69 let num_pairs = n * (n.saturating_sub(1)) / 2;
70 let num_vars = n + num_pairs;
71
72 let mut constraints = Vec::with_capacity(3 * num_pairs);
74 for i in 0..n {
75 for j in (i + 1)..n {
76 let y = y_index(n, i, j);
77
78 constraints.extend(mccormick_product(y, i, j));
79 }
80 }
81
82 let matrix = self.matrix();
99 let mut obj_coeffs = vec![0i64; num_vars];
100
101 for (i, row_i) in matrix.iter().enumerate() {
103 for j in (i + 1)..n {
104 let y = y_index(n, i, j);
105 let coefficient = row_i[j]
106 .checked_add(matrix[j][i])
107 .and_then(|value| value.checked_mul(4))
108 .ok_or_else(|| {
109 crate::rules::ReductionError::integer_overflow::<
110 MinimumMatrixCover,
111 ILP<bool>,
112 >(
113 "computing an off-diagonal matrix-cover coefficient"
114 )
115 })?;
116 obj_coeffs[y] = coefficient;
117 }
118 }
119
120 for (k, row_k) in matrix.iter().enumerate() {
122 let sum = (0..n).filter(|&j| j != k).try_fold(0_i64, |total, j| {
123 let pair = row_k[j].checked_add(matrix[j][k]).ok_or_else(|| {
124 crate::rules::ReductionError::integer_overflow::<MinimumMatrixCover, ILP<bool>>(
125 "adding symmetric matrix-cover entries",
126 )
127 })?;
128 total.checked_add(pair).ok_or_else(|| {
129 crate::rules::ReductionError::integer_overflow::<MinimumMatrixCover, ILP<bool>>(
130 "summing matrix-cover row coefficients",
131 )
132 })
133 })?;
134 let coefficient = sum.checked_mul(-2).ok_or_else(|| {
135 crate::rules::ReductionError::integer_overflow::<MinimumMatrixCover, ILP<bool>>(
136 "scaling a matrix-cover row coefficient",
137 )
138 })?;
139 obj_coeffs[k] = coefficient;
140 }
141
142 let objective: Vec<(usize, i64)> = obj_coeffs
143 .into_iter()
144 .enumerate()
145 .filter(|&(_, c)| c != 0)
146 .collect();
147
148 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
149 .map_err(Self::target_construction)?;
150
151 Ok(ReductionMinimumMatrixCoverToILP { target, n })
152 }
153}
154
155#[cfg(feature = "example-db")]
156pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
157 use crate::export::SolutionPair;
158
159 vec![crate::example_db::specs::RuleExampleSpec {
160 id: "minimum_matrix_cover_to_ilp",
161 build: || {
162 let source = MinimumMatrixCover::new(vec![vec![0, 3], vec![2, 0]]);
164 crate::example_db::specs::rule_example_with_witness::<_, ILP<bool>>(
171 source,
172 SolutionPair {
173 source_config: serde_json::json!(vec![false, true]),
174 target_config: serde_json::json!(vec![0, 1, 0]),
175 },
176 )
177 },
178 }]
179}
180
181#[cfg(test)]
182#[path = "../unit_tests/rules/minimummatrixcover_ilp.rs"]
183mod tests;