Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
zero_half_cuts.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <utility>
18#include <vector>
19
20#include "absl/log/check.h"
21#include "absl/types/span.h"
24#include "ortools/sat/integer.h"
25#include "ortools/sat/util.h"
27
28namespace operations_research {
29namespace sat {
30
32 rows_.clear();
33 shifted_lp_values_.clear();
34 bound_parity_.clear();
35 col_to_rows_.clear();
36 col_to_rows_.resize(size);
37 tmp_marked_.resize(size);
38}
39
41 const std::vector<double>& lp_values,
42 absl::Span<const IntegerValue> lower_bounds,
43 absl::Span<const IntegerValue> upper_bounds) {
44 Reset(lp_values.size());
45
46 // Shift all variables to their closest bound.
47 lp_values_ = lp_values;
48 for (int i = 0; i < lp_values.size(); ++i) {
49 const double lb_dist = lp_values[i] - ToDouble(lower_bounds[i]);
50 const double ub_dist = ToDouble(upper_bounds[i]) - lp_values_[i];
51 if (lb_dist < ub_dist) {
52 shifted_lp_values_.push_back(lb_dist);
53 bound_parity_.push_back(lower_bounds[i].value() & 1);
54 } else {
55 shifted_lp_values_.push_back(ub_dist);
56 bound_parity_.push_back(upper_bounds[i].value() & 1);
57 }
58 }
59}
60
62 // No point pushing an all zero row with a zero rhs.
63 if (binary_row.cols.empty() && !binary_row.rhs_parity) return;
64 for (const int col : binary_row.cols) {
65 col_to_rows_[col].push_back(rows_.size());
66 }
67 rows_.push_back(binary_row);
68}
69
70void ZeroHalfCutHelper::AddOneConstraint(const glop::RowIndex row,
71 absl::Span<const glop::ColIndex> cols,
72 absl::Span<const IntegerValue> coeffs,
73 IntegerValue lb, IntegerValue ub) {
74 const int num_terms = cols.size();
75 if (num_terms > kMaxInputConstraintSize) return;
76
77 double activity = 0.0;
78 IntegerValue magnitude(0);
79 CombinationOfRows binary_row;
80 int rhs_adjust = 0;
81 for (int i = 0; i < num_terms; ++i) {
82 const int col = cols[i].value();
83 activity += ToDouble(coeffs[i]) * lp_values_[col];
84 magnitude = std::max(magnitude, IntTypeAbs(coeffs[i]));
85
86 // Only consider odd coefficient.
87 if ((coeffs[i].value() & 1) == 0) continue;
88
89 // Ignore column in the binary matrix if its lp value is almost zero.
90 if (shifted_lp_values_[col] > 1e-2) {
91 binary_row.cols.push_back(col);
92 }
93
94 // Because we work on the shifted variable, the rhs needs to be updated.
95 rhs_adjust ^= bound_parity_[col];
96 }
97
98 // We ignore constraint with large coefficient, since there is little chance
99 // to cancel them and because of that the efficacity of a generated cut will
100 // be limited.
101 if (magnitude > kMaxInputConstraintMagnitude) return;
102 if (binary_row.cols.empty()) return;
103
104 // TODO(user): experiment with the best value. probably only tight rows are
105 // best? and we could use the basis status rather than recomputing the
106 // activity for that.
107 //
108 // TODO(user): Avoid adding duplicates and just randomly pick one. Note
109 // that we should also remove duplicate in a generic way.
110 const double tighteness_threshold = 1e-2;
111 if (ToDouble(ub) - activity < tighteness_threshold) {
112 binary_row.multipliers = {{row, IntegerValue(1)}};
113 binary_row.slack = ToDouble(ub) - activity;
114 binary_row.rhs_parity = (ub.value() & 1) ^ rhs_adjust;
115 AddBinaryRow(binary_row);
116 }
117 if (activity - ToDouble(lb) < tighteness_threshold) {
118 binary_row.multipliers = {{row, IntegerValue(-1)}};
119 binary_row.slack = activity - ToDouble(lb);
120 binary_row.rhs_parity = (lb.value() & 1) ^ rhs_adjust;
121 AddBinaryRow(binary_row);
122 }
123}
124
125void ZeroHalfCutHelper::SymmetricDifference(absl::Span<const int> a,
126 std::vector<int>* b) {
127 for (const int v : *b) tmp_marked_[v] = true;
128 for (const int v : a) {
129 if (tmp_marked_[v]) {
130 tmp_marked_[v] = false;
131 } else {
132 tmp_marked_[v] = true;
133
134 // TODO(user): optim by doing that at the end?
135 b->push_back(v);
136 }
137 }
138
139 // Remove position that are not marked, and clear tmp_marked_.
140 int new_size = 0;
141 for (const int v : *b) {
142 if (tmp_marked_[v]) {
143 (*b)[new_size++] = v;
144 tmp_marked_[v] = false;
145 }
146 }
147 b->resize(new_size);
148}
149
150// This is basically one step of a Gaussian elimination with the given pivot.
152 int eliminated_row) {
153 CHECK_LE(rows_[eliminated_row].slack, 1e-6);
154 CHECK(!rows_[eliminated_row].cols.empty());
155
156 // First update the row representation of the matrix.
157 tmp_marked_.resize(std::max(col_to_rows_.size(), rows_.size()));
158 DCHECK(std::all_of(tmp_marked_.begin(), tmp_marked_.end(),
159 [](bool b) { return !b; }));
160 int new_size = 0;
161 for (const int other_row : col_to_rows_[eliminated_col]) {
162 if (other_row == eliminated_row) continue;
163 col_to_rows_[eliminated_col][new_size++] = other_row;
164
165 SymmetricDifference(rows_[eliminated_row].cols, &rows_[other_row].cols);
166
167 // Update slack & parity.
168 rows_[other_row].rhs_parity ^= rows_[eliminated_row].rhs_parity;
169 rows_[other_row].slack += rows_[eliminated_row].slack;
170
171 // Update the multipliers the same way.
172 {
173 auto& mutable_multipliers = rows_[other_row].multipliers;
174 mutable_multipliers.insert(mutable_multipliers.end(),
175 rows_[eliminated_row].multipliers.begin(),
176 rows_[eliminated_row].multipliers.end());
177 std::sort(mutable_multipliers.begin(), mutable_multipliers.end());
178 int new_size = 0;
179 for (const auto& entry : mutable_multipliers) {
180 if (new_size > 0 && entry == mutable_multipliers[new_size - 1]) {
181 // Cancel both.
182 --new_size;
183 } else {
184 mutable_multipliers[new_size++] = entry;
185 }
186 }
187 mutable_multipliers.resize(new_size);
188 }
189 }
190 col_to_rows_[eliminated_col].resize(new_size);
191
192 // Then update the col representation of the matrix.
193 {
194 int new_size = 0;
195 for (const int other_col : rows_[eliminated_row].cols) {
196 if (other_col == eliminated_col) continue;
197 SymmetricDifference(col_to_rows_[eliminated_col],
198 &col_to_rows_[other_col]);
199 if (col_to_rows_[other_col].size() == 1) {
200 CHECK_EQ(col_to_rows_[other_col][0], eliminated_row);
201
202 // Eliminate new singleton column right away.
203 col_to_rows_[other_col].clear();
204 rows_[eliminated_row].slack += shifted_lp_values_[other_col];
205 continue;
206 }
207 rows_[eliminated_row].cols[new_size++] = other_col;
208 }
209 rows_[eliminated_row].cols.resize(new_size);
210 }
211
212 // Clear col since it is now singleton.
213 col_to_rows_[eliminated_col].clear();
214 rows_[eliminated_row].slack += shifted_lp_values_[eliminated_col];
215}
216
217std::vector<std::vector<std::pair<glop::RowIndex, IntegerValue>>>
219 std::vector<std::vector<std::pair<glop::RowIndex, IntegerValue>>> result;
220
221 // Remove singleton column from the picture.
222 const int num_cols = col_to_rows_.size();
223 for (int singleton_col = 0; singleton_col < num_cols; ++singleton_col) {
224 if (col_to_rows_[singleton_col].size() != 1) continue;
225
226 const int row = col_to_rows_[singleton_col][0];
227 int new_size = 0;
228 auto& mutable_cols = rows_[row].cols;
229 for (const int col : mutable_cols) {
230 if (col == singleton_col) continue;
231 mutable_cols[new_size++] = col;
232 }
233 CHECK_LT(new_size, mutable_cols.size());
234 mutable_cols.resize(new_size);
235 col_to_rows_[singleton_col].clear();
236 rows_[row].slack += shifted_lp_values_[singleton_col];
237 }
238
239 // Process rows by increasing size, but randomize if same size.
240 std::vector<int> to_process;
241 for (int row = 0; row < rows_.size(); ++row) to_process.push_back(row);
242 std::shuffle(to_process.begin(), to_process.end(), *random);
243 std::stable_sort(to_process.begin(), to_process.end(), [this](int a, int b) {
244 return rows_[a].cols.size() < rows_[b].cols.size();
245 });
246
247 for (const int row : to_process) {
248 if (rows_[row].cols.empty()) continue;
249 if (rows_[row].slack > 1e-6) continue;
250 if (rows_[row].multipliers.size() > kMaxAggregationSize) continue;
251
252 // Heuristic: eliminate the variable with highest shifted lp value.
253 int eliminated_col = -1;
254 double max_lp_value = 0.0;
255 for (const int col : rows_[row].cols) {
256 if (shifted_lp_values_[col] > max_lp_value) {
257 max_lp_value = shifted_lp_values_[col];
258 eliminated_col = col;
259 }
260 }
261 if (eliminated_col == -1) continue;
262
263 EliminateVarUsingRow(eliminated_col, row);
264 }
265
266 // As an heuristic, we just try to add zero rows with an odd rhs and a low
267 // enough slack.
268 for (const auto& row : rows_) {
269 if (row.cols.empty() && row.rhs_parity && row.slack < kSlackThreshold) {
270 result.push_back(row.multipliers);
271 }
272 }
273 VLOG(2) << "#candidates: " << result.size() << " / " << rows_.size();
274 return result;
275}
276
277} // namespace sat
278} // namespace operations_research
IntegerValue size
void SymmetricDifference(absl::Span< const int > a, std::vector< int > *b)
void Reset(int size)
Visible for testing.
void AddOneConstraint(glop::RowIndex, absl::Span< const glop::ColIndex > cols, absl::Span< const IntegerValue > coeffs, IntegerValue lb, IntegerValue ub)
std::vector< std::vector< std::pair< glop::RowIndex, IntegerValue > > > InterestingCandidates(ModelRandomGenerator *random)
void ProcessVariables(const std::vector< double > &lp_values, absl::Span< const IntegerValue > lower_bounds, absl::Span< const IntegerValue > upper_bounds)
void EliminateVarUsingRow(int col, int row)
This is basically one step of a Gaussian elimination with the given pivot.
void AddBinaryRow(const CombinationOfRows &binary_row)
int64_t a
Definition table.cc:44
int64_t value
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
IntType IntTypeAbs(IntType t)
Definition integer.h:81
double ToDouble(IntegerValue value)
Definition integer.h:73
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< double > lower_bounds
Definition lp_utils.cc:746
std::vector< double > upper_bounds
Definition lp_utils.cc:747
double slack
How tight this constraints is under the current LP solution.
std::vector< std::pair< glop::RowIndex, IntegerValue > > multipliers
How this row was formed from the initial problem constraints.
std::vector< int > cols
The index of the odd coefficient of this combination.