OR-Tools  9.3
zero_half_cuts.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <functional>
18#include <utility>
19#include <vector>
20
23#include "ortools/sat/integer.h"
24#include "ortools/sat/util.h"
26
27namespace operations_research {
28namespace sat {
29
31 rows_.clear();
32 shifted_lp_values_.clear();
33 bound_parity_.clear();
34 col_to_rows_.clear();
35 col_to_rows_.resize(size);
36 tmp_marked_.resize(size);
37}
38
40 const std::vector<double>& lp_values,
41 const std::vector<IntegerValue>& lower_bounds,
42 const std::vector<IntegerValue>& upper_bounds) {
43 Reset(lp_values.size());
44
45 // Shift all variables to their closest bound.
46 lp_values_ = lp_values;
47 for (int i = 0; i < lp_values.size(); ++i) {
48 const double lb_dist = lp_values[i] - ToDouble(lower_bounds[i]);
49 const double ub_dist = ToDouble(upper_bounds[i]) - lp_values_[i];
50 if (lb_dist < ub_dist) {
51 shifted_lp_values_.push_back(lb_dist);
52 bound_parity_.push_back(lower_bounds[i].value() & 1);
53 } else {
54 shifted_lp_values_.push_back(ub_dist);
55 bound_parity_.push_back(upper_bounds[i].value() & 1);
56 }
57 }
58}
59
61 // No point pushing an all zero row with a zero rhs.
62 if (binary_row.cols.empty() && !binary_row.rhs_parity) return;
63 for (const int col : binary_row.cols) {
64 col_to_rows_[col].push_back(rows_.size());
65 }
66 rows_.push_back(binary_row);
67}
68
70 const glop::RowIndex row,
71 const std::vector<std::pair<glop::ColIndex, IntegerValue>>& terms,
72 IntegerValue lb, IntegerValue ub) {
73 if (terms.size() > kMaxInputConstraintSize) return;
74
75 double activity = 0.0;
76 IntegerValue magnitude(0);
77 CombinationOfRows binary_row;
78 int rhs_adjust = 0;
79 for (const auto& term : terms) {
80 const int col = term.first.value();
81 activity += ToDouble(term.second) * lp_values_[col];
82 magnitude = std::max(magnitude, IntTypeAbs(term.second));
83
84 // Only consider odd coefficient.
85 if ((term.second.value() & 1) == 0) continue;
86
87 // Ignore column in the binary matrix if its lp value is almost zero.
88 if (shifted_lp_values_[col] > 1e-2) {
89 binary_row.cols.push_back(col);
90 }
91
92 // Because we work on the shifted variable, the rhs needs to be updated.
93 rhs_adjust ^= bound_parity_[col];
94 }
95
96 // We ignore constraint with large coefficient, since there is little chance
97 // to cancel them and because of that the efficacity of a generated cut will
98 // be limited.
99 if (magnitude > kMaxInputConstraintMagnitude) return;
100
101 // TODO(user): experiment with the best value. probably only tight rows are
102 // best? and we could use the basis status rather than recomputing the
103 // activity for that.
104 //
105 // TODO(user): Avoid adding duplicates and just randomly pick one. Note
106 // that we should also remove duplicate in a generic way.
107 const double tighteness_threshold = 1e-2;
108 if (ToDouble(ub) - activity < tighteness_threshold) {
109 binary_row.multipliers = {{row, IntegerValue(1)}};
110 binary_row.slack = ToDouble(ub) - activity;
111 binary_row.rhs_parity = (ub.value() & 1) ^ rhs_adjust;
112 AddBinaryRow(binary_row);
113 }
114 if (activity - ToDouble(lb) < tighteness_threshold) {
115 binary_row.multipliers = {{row, IntegerValue(-1)}};
116 binary_row.slack = activity - ToDouble(lb);
117 binary_row.rhs_parity = (lb.value() & 1) ^ rhs_adjust;
118 AddBinaryRow(binary_row);
119 }
120}
121
123 std::function<bool(int)> extra_condition, const std::vector<int>& a,
124 std::vector<int>* b) {
125 for (const int v : *b) tmp_marked_[v] = true;
126 for (const int v : a) {
127 if (tmp_marked_[v]) {
128 tmp_marked_[v] = false;
129 } else {
130 tmp_marked_[v] = true;
131
132 // TODO(user): optim by doing that at the end?
133 b->push_back(v);
134 }
135 }
136
137 // Remove position that are not marked, and clear tmp_marked_.
138 int new_size = 0;
139 for (const int v : *b) {
140 if (tmp_marked_[v]) {
141 if (extra_condition(v)) {
142 (*b)[new_size++] = v;
143 }
144 tmp_marked_[v] = false;
145 }
146 }
147 b->resize(new_size);
148}
149
150void ZeroHalfCutHelper::ProcessSingletonColumns() {
151 for (const int singleton_col : singleton_cols_) {
152 if (col_to_rows_[singleton_col].empty()) continue;
153 CHECK_EQ(col_to_rows_[singleton_col].size(), 1);
154 const int row = col_to_rows_[singleton_col][0];
155 int new_size = 0;
156 auto& mutable_cols = rows_[row].cols;
157 for (const int col : mutable_cols) {
158 if (col == singleton_col) continue;
159 mutable_cols[new_size++] = col;
160 }
161 CHECK_LT(new_size, mutable_cols.size());
162 mutable_cols.resize(new_size);
163 col_to_rows_[singleton_col].clear();
164 rows_[row].slack += shifted_lp_values_[singleton_col];
165 }
166 singleton_cols_.clear();
167}
168
169// This is basically one step of a Gaussian elimination with the given pivot.
171 int eliminated_row) {
172 CHECK_LE(rows_[eliminated_row].slack, 1e-6);
173 CHECK(!rows_[eliminated_row].cols.empty());
174
175 // First update the row representation of the matrix.
176 tmp_marked_.resize(std::max(col_to_rows_.size(), rows_.size()));
177 DCHECK(std::all_of(tmp_marked_.begin(), tmp_marked_.end(),
178 [](bool b) { return !b; }));
179 int new_size = 0;
180 for (const int other_row : col_to_rows_[eliminated_col]) {
181 if (other_row == eliminated_row) continue;
182 col_to_rows_[eliminated_col][new_size++] = other_row;
183
184 SymmetricDifference([](int i) { return true; }, rows_[eliminated_row].cols,
185 &rows_[other_row].cols);
186
187 // Update slack & parity.
188 rows_[other_row].rhs_parity ^= rows_[eliminated_row].rhs_parity;
189 rows_[other_row].slack += rows_[eliminated_row].slack;
190
191 // Update the multipliers the same way.
192 {
193 auto& mutable_multipliers = rows_[other_row].multipliers;
194 mutable_multipliers.insert(mutable_multipliers.end(),
195 rows_[eliminated_row].multipliers.begin(),
196 rows_[eliminated_row].multipliers.end());
197 std::sort(mutable_multipliers.begin(), mutable_multipliers.end());
198 int new_size = 0;
199 for (const auto& entry : mutable_multipliers) {
200 if (new_size > 0 && entry == mutable_multipliers[new_size - 1]) {
201 // Cancel both.
202 --new_size;
203 } else {
204 mutable_multipliers[new_size++] = entry;
205 }
206 }
207 mutable_multipliers.resize(new_size);
208 }
209 }
210 col_to_rows_[eliminated_col].resize(new_size);
211
212 // Then update the col representation of the matrix.
213 //
214 // Note that we remove from the col-wise representation any rows with a large
215 // slack.
216 {
217 int new_size = 0;
218 for (const int other_col : rows_[eliminated_row].cols) {
219 if (other_col == eliminated_col) continue;
220 const int old_size = col_to_rows_[other_col].size();
221 rows_[eliminated_row].cols[new_size++] = other_col;
223 [this](int i) { return rows_[i].slack < kSlackThreshold; },
224 col_to_rows_[eliminated_col], &col_to_rows_[other_col]);
225 if (old_size != 1 && col_to_rows_[other_col].size() == 1) {
226 singleton_cols_.push_back(other_col);
227 }
228 }
229 rows_[eliminated_row].cols.resize(new_size);
230 }
231
232 // Clear col.
233 col_to_rows_[eliminated_col].clear();
234 rows_[eliminated_row].slack += shifted_lp_values_[eliminated_col];
235}
236
237std::vector<std::vector<std::pair<glop::RowIndex, IntegerValue>>>
239 std::vector<std::vector<std::pair<glop::RowIndex, IntegerValue>>> result;
240
241 // Initialize singleton_cols_.
242 singleton_cols_.clear();
243 for (int col = 0; col < col_to_rows_.size(); ++col) {
244 if (col_to_rows_[col].size() == 1) singleton_cols_.push_back(col);
245 }
246
247 // Process rows by increasing size, but randomize if same size.
248 std::vector<int> to_process;
249 for (int row = 0; row < rows_.size(); ++row) to_process.push_back(row);
250 std::shuffle(to_process.begin(), to_process.end(), *random);
251 std::stable_sort(to_process.begin(), to_process.end(), [this](int a, int b) {
252 return rows_[a].cols.size() < rows_[b].cols.size();
253 });
254
255 for (const int row : to_process) {
256 ProcessSingletonColumns();
257
258 if (rows_[row].cols.empty()) continue;
259 if (rows_[row].slack > 1e-6) continue;
260 if (rows_[row].multipliers.size() > kMaxAggregationSize) continue;
261
262 // Heuristic: eliminate the variable with highest shifted lp value.
263 int eliminated_col = -1;
264 double max_lp_value = 0.0;
265 for (const int col : rows_[row].cols) {
266 if (shifted_lp_values_[col] > max_lp_value) {
267 max_lp_value = shifted_lp_values_[col];
268 eliminated_col = col;
269 }
270 }
271 if (eliminated_col == -1) continue;
272
273 EliminateVarUsingRow(eliminated_col, row);
274 }
275
276 // As an heuristic, we just try to add zero rows with an odd rhs and a low
277 // enough slack.
278 for (const auto& row : rows_) {
279 if (row.cols.empty() && row.rhs_parity && row.slack < kSlackThreshold) {
280 result.push_back(row.multipliers);
281 }
282 }
283 VLOG(1) << "#candidates: " << result.size() << " / " << rows_.size();
284 return result;
285}
286
287} // namespace sat
288} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define DCHECK(condition)
Definition: base/logging.h:890
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
#define VLOG(verboselevel)
Definition: base/logging.h:984
void AddBinaryRow(const CombinationOfRows &binary_row)
void SymmetricDifference(std::function< bool(int)> extra_condition, const std::vector< int > &a, std::vector< int > *b)
void ProcessVariables(const std::vector< double > &lp_values, const std::vector< IntegerValue > &lower_bounds, const std::vector< IntegerValue > &upper_bounds)
void AddOneConstraint(glop::RowIndex, const std::vector< std::pair< glop::ColIndex, IntegerValue > > &terms, IntegerValue lb, IntegerValue ub)
std::vector< std::vector< std::pair< glop::RowIndex, IntegerValue > > > InterestingCandidates(ModelRandomGenerator *random)
int64_t b
int64_t a
int64_t value
ColIndex col
Definition: markowitz.cc:183
RowIndex row
Definition: markowitz.cc:182
IntType IntTypeAbs(IntType t)
Definition: integer.h:85
double ToDouble(IntegerValue value)
Definition: integer.h:77
Collection of objects used to extend the Constraint Solver library.
std::vector< double > lower_bounds
std::vector< double > upper_bounds
std::vector< std::pair< glop::RowIndex, IntegerValue > > multipliers