Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
preprocessor.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <cmath>
18#include <cstdint>
19#include <deque>
20#include <limits>
21#include <utility>
22
23#include "absl/log/check.h"
24#include "absl/log/log.h"
32#include "ortools/util/stats.h"
33
34using ::operations_research::glop::ColIndex;
40using ::operations_research::glop::RowIndex;
46
47namespace operations_research {
48
49// Helper function to check the bounds of the SetVariableBounds() and
50// SetConstraintBounds() functions.
51inline bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound) {
52 if (std::isnan(lower_bound)) return false;
53 if (std::isnan(upper_bound)) return false;
54 if (lower_bound == kInfinity && upper_bound == kInfinity) return false;
55 if (lower_bound == -kInfinity && upper_bound == -kInfinity) return false;
56 if (lower_bound > upper_bound) return false;
57 return true;
58}
59
60// --------------------------------------------------------
61// IntegerBoundsPreprocessor
62// --------------------------------------------------------
63
66 RETURN_VALUE_IF_NULL(linear_program, false);
67 const Fractional tolerance = integer_solution_tolerance_;
68
69 // Make integer the bounds of integer variables.
70 // NOTE(user): it may happen that the new bound will be less strict (but at
71 // most it will be off by integer_solution_tolerance).
72 int64_t num_changed_bounds = 0;
73 for (ColIndex col : linear_program->IntegerVariablesList()) {
74 const Fractional lb =
75 ceil(linear_program->variable_lower_bounds()[col] - tolerance);
76 const Fractional ub =
77 floor(linear_program->variable_upper_bounds()[col] + tolerance);
78 if (!AreBoundsValid(lb, ub)) {
80 return false;
81 }
82 if (lb != linear_program->variable_lower_bounds()[col] ||
83 ub != linear_program->variable_upper_bounds()[col]) {
84 num_changed_bounds++;
85 }
86 linear_program->SetVariableBounds(col, lb, ub);
87 }
88 VLOG(2) << "IntegerBoundsPreprocessor changed " << num_changed_bounds
89 << " variable bounds.";
90
91 // Make integer the bounds of integer constraints, if it makes them stricter.
92 const SparseMatrix& transpose = linear_program->GetTransposeSparseMatrix();
93 num_changed_bounds = 0;
94 for (RowIndex row = RowIndex(0); row < linear_program->num_constraints();
95 ++row) {
96 bool integer_constraint = true;
97 for (const SparseColumn::Entry var : transpose.column(RowToColIndex(row))) {
98 // Don't affect the constraint if it has a non-integer variable.
99 if (!linear_program->IsVariableInteger(RowToColIndex(var.row()))) {
100 integer_constraint = false;
101 break;
102 }
103 // Don't affect the constraint if it has a non-integer coefficient. Note
104 // that we require each coefficient to be precisely an integer in order to
105 // avoid floating point errors.
106 //
107 // TODO(user): checking integer constraints can go further, e.g.,
108 // x + 2 * y = 4 for binary x and y can never be satisfied. But this
109 // perhaps starts to resemble bound propagation, which might be too much
110 // for a lightweighted preprocessor like this one.
111 if (round(var.coefficient()) != var.coefficient()) {
112 integer_constraint = false;
113 break;
114 }
115 }
116 if (integer_constraint) {
117 const Fractional lb =
118 std::ceil(linear_program->constraint_lower_bounds()[row] - tolerance);
119 const Fractional ub = std::floor(
120 linear_program->constraint_upper_bounds()[row] + tolerance);
121 if (!AreBoundsValid(lb, ub)) {
123 return false;
124 }
125 if (lb != linear_program->constraint_lower_bounds()[row] ||
126 ub != linear_program->constraint_upper_bounds()[row]) {
127 num_changed_bounds++;
128 }
129 linear_program->SetConstraintBounds(row, lb, ub);
130 }
131 }
132 VLOG(2) << "IntegerBoundsPreprocessor changed " << num_changed_bounds
133 << " constraint bounds.";
134 DCHECK(linear_program->BoundsOfIntegerVariablesAreInteger(tolerance));
135 DCHECK(linear_program->BoundsOfIntegerConstraintsAreInteger(tolerance));
136 return false;
137}
138
139// --------------------------------------------------------
140// BoundPropagationPreprocessor
141// --------------------------------------------------------
142// TODO(user): This preprocessor is not as efficient as it could be because each
143// time we process a constraint, we rescan all the involved variables. Make it
144// more efficient if it becomes needed. Note that this kind of propagation is
145// probably something we want to do each time we take a branch in the mip
146// search, so probably an efficient class for this will be created at some
147// point.
148bool BoundPropagationPreprocessor::Run(LinearProgram* linear_program) {
150 RETURN_VALUE_IF_NULL(linear_program, false);
151 const Fractional tolerance = integer_solution_tolerance_;
153 // Starts by adding all the row in the 'to_process' queue.
154 StrictITIVector<RowIndex, bool> in_queue(linear_program->num_constraints(),
155 false);
156 std::deque<RowIndex> to_process;
157 for (RowIndex row(0); row < linear_program->num_constraints(); ++row) {
158 to_process.push_back(row);
159 in_queue[row] = true;
160 }
161
162 // This preprocessor will need to access the constraints row by row.
163 const SparseMatrix& transpose = linear_program->GetTransposeSparseMatrix();
164
165 // Now process all the rows until none are left, or a limit on the number of
166 // processed rows is reached. The limit is mainly here to prevent infinite
167 // loops on corner cases problems. It should not be reached often in practice.
168 const int kMaxNumberOfProcessedRows =
169 linear_program->num_constraints().value() * 10;
170 for (int i = 0; i < kMaxNumberOfProcessedRows && !to_process.empty(); ++i) {
171 const RowIndex row = to_process.front();
172 in_queue[row] = false;
173 to_process.pop_front();
174
175 // For each variable of a constraint on n variables, we want the bound
176 // implied by the (n - 1) other variables and the constraint bounds. We use
177 // two handy utility classes that allow us to do that efficiently while
178 // dealing properly with infinite bounds.
181
182 // Initialize the sums.
183 bool skip = false;
184 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
185 const ColIndex col = RowToColIndex(e.row());
186 Fractional entry_lb =
187 e.coefficient() * linear_program->variable_lower_bounds()[col];
188 Fractional entry_ub =
189 e.coefficient() * linear_program->variable_upper_bounds()[col];
190 if (e.coefficient() < 0.0) std::swap(entry_lb, entry_ub);
191 if (entry_lb == kInfinity || entry_ub == -kInfinity) {
192 // TODO(user): our SumWithOneMissing does not deal well with infinity of
193 // the wrong sign. For now when this happen we skip this constraint.
194 // Note however than the other implied bounds could still be used.
195 skip = true;
196 break;
197 }
198 lb_sum.Add(entry_lb);
199 ub_sum.Add(entry_ub);
200 }
201 if (skip) continue;
202
203 // The inequality
204 // constraint_lb <= sum(entries) <= constraint_ub
205 // can be rewritten as:
206 // sum(entries) + (-activity) = 0,
207 // where (-activity) has bounds [-constraint_ub, -constraint_lb].
208 // We use this latter convention to simplify our code.
209 lb_sum.Add(-linear_program->constraint_upper_bounds()[row]);
210 ub_sum.Add(-linear_program->constraint_lower_bounds()[row]);
211
212 // Process the variables one by one and check if the implied bounds are
213 // more restrictive.
214 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
215 const ColIndex col = RowToColIndex(e.row());
216 const Fractional coeff = e.coefficient();
217 const Fractional var_lb = linear_program->variable_lower_bounds()[col];
218 const Fractional var_ub = linear_program->variable_upper_bounds()[col];
219 Fractional entry_lb = coeff * var_lb;
220 Fractional entry_ub = coeff * var_ub;
221 if (coeff < 0.0) std::swap(entry_lb, entry_ub);
222
223 // If X is the variable with index col and Y the sum of all the other
224 // variables and of (-activity), then coeff * X + Y = 0. Since Y's bounds
225 // are [lb_sum without X, ub_sum without X], it is easy to derive the
226 // implied bounds on X.
227 Fractional implied_lb = -ub_sum.SumWithout(entry_ub) / coeff;
228 Fractional implied_ub = -lb_sum.SumWithout(entry_lb) / coeff;
229 if (coeff < 0.0) std::swap(implied_lb, implied_ub);
230
231 // If the variable is integer, make the implied bounds integer.
232 if (linear_program->IsVariableInteger(col)) {
233 implied_lb = std::ceil(implied_lb - tolerance);
234 implied_ub = std::floor(implied_ub + tolerance);
235 }
236
237 // more restrictive? If yes, sets the bounds, and add all the impacted
238 // row back into to_process if they are not already there.
239 if (implied_lb > var_lb || implied_ub < var_ub) {
240 Fractional new_lb = std::max(implied_lb, var_lb);
241 Fractional new_ub = std::min(implied_ub, var_ub);
242 if (new_lb > new_ub) {
243 // TODO(user): Investigate what tolerance we should use here.
244 if (new_lb - tolerance > new_ub) {
246 return false;
247 } else {
248 // We choose the nearest integer for an integer variable, or the
249 // middle value for a non-integer one.
250 if (linear_program->IsVariableInteger(col)) {
251 new_lb = new_ub = round(new_lb);
252 } else {
253 new_lb = new_ub = (new_lb + new_ub) / 2.0;
254 }
255 }
256 }
257
258 // This extra test avoids reprocessing many rows for no reason.
259 // It can be false if we run into the case new_lb > new_ub above.
260 if (new_ub != var_ub || new_lb != var_lb) {
261 linear_program->SetVariableBounds(col, new_lb, new_ub);
262 for (SparseColumn::Entry e : linear_program->GetSparseColumn(col)) {
263 if (!in_queue[e.row()]) {
264 to_process.push_back(e.row());
265 in_queue[e.row()] = true;
266 }
267 }
268 }
269 }
270 }
271 }
272 if (!to_process.empty()) {
273 LOG_FIRST_N(WARNING, 10)
274 << "Propagation limit reached in the BoundPropagationPreprocessor, "
275 << "maybe the limit should be increased.";
276 }
277 DCHECK(linear_program->BoundsOfIntegerVariablesAreInteger(
278 integer_solution_tolerance_));
279 DCHECK(linear_program->BoundsOfIntegerConstraintsAreInteger(
280 integer_solution_tolerance_));
281 return false;
282}
283
284// --------------------------------------------------------
285// ImpliedIntegerPreprocessor
286// --------------------------------------------------------
287bool ImpliedIntegerPreprocessor::Run(LinearProgram* linear_program) {
289 RETURN_VALUE_IF_NULL(linear_program, false);
290 int64_t num_implied_integer_variables = 0;
291 const Fractional tolerance = integer_solution_tolerance_;
292 for (ColIndex col(0); col < linear_program->num_variables(); ++col) {
293 DCHECK_EQ(linear_program->GetFirstSlackVariable(), glop::kInvalidCol);
294
295 // Skip the integer variables.
296 if (linear_program->GetVariableType(col) !=
298 continue;
299 }
300
301 const bool is_implied_integer =
302 VariableOccursInAtLeastOneEqualityConstraint(*linear_program, col)
303 ? AnyEqualityConstraintImpliesIntegrality(*linear_program, col)
304 : AllInequalityConstraintsImplyIntegrality(*linear_program, col);
305
306 if (is_implied_integer) {
307 linear_program->SetVariableType(
309 num_implied_integer_variables++;
310 VLOG(2) << "Marked col " << col << " implied integer.";
311
312 // We need to tighten its bounds if they are not integer, otherwise
313 // other preprocessor complains.
314 const Fractional lb =
315 std::ceil(linear_program->variable_lower_bounds()[col] - tolerance);
316 const Fractional ub =
317 std::floor(linear_program->variable_upper_bounds()[col] + tolerance);
318 if (!AreBoundsValid(lb, ub)) {
320 return false;
321 }
322 linear_program->SetVariableBounds(col, lb, ub);
323 }
324 }
325 VLOG(2) << "ImpliedIntegerPreprocessor detected "
326 << num_implied_integer_variables << " implied integer variables.";
327
328 DCHECK(linear_program->BoundsOfIntegerVariablesAreInteger(
329 integer_solution_tolerance_));
330
331 // TODO(user): Because this presolve step detects new integer variables and
332 // does not tighten the bounds of a constraint if all its variables become
333 // integer, this invariant is currently not enforced:
334 // DCHECK(linear_program->BoundsOfIntegerConstraintsAreInteger(
335 // integer_solution_tolerance_));
336
337 return false; // Does not require postsolve.
338}
339
340bool ImpliedIntegerPreprocessor::AnyEqualityConstraintImpliesIntegrality(
341 const LinearProgram& linear_program, ColIndex variable) {
342 for (const SparseColumn::Entry entry :
343 linear_program.GetSparseColumn(variable)) {
344 // Process only equality constraints.
345 if (linear_program.constraint_upper_bounds()[entry.row()] ==
346 linear_program.constraint_lower_bounds()[entry.row()]) {
347 if (ConstraintSupportsImpliedIntegrality(linear_program, variable,
348 entry.row())) {
349 return true;
350 }
351 }
352 }
353 return false;
354}
355
356bool ImpliedIntegerPreprocessor::AllInequalityConstraintsImplyIntegrality(
357 const LinearProgram& linear_program, ColIndex variable) {
358 // Check variable bounds.
359 Fractional lower_bound = linear_program.variable_lower_bounds()[variable];
360 Fractional upper_bound = linear_program.variable_upper_bounds()[variable];
361 if (!IsIntegerWithinTolerance(lower_bound, integer_solution_tolerance_) ||
362 !IsIntegerWithinTolerance(upper_bound, integer_solution_tolerance_)) {
363 // The bounds are not integer.
364 // We cannot deduce anything if the variable as an objective.
365 //
366 // TODO(user): Actually we can if the bound that minimize the cost is
367 // integer but not the other. Improve the code.
368 if (linear_program.objective_coefficients()[variable] != 0.0) return false;
369
370 // No objective. If the variable domain contains an integer point, then
371 // there is a chance for this variable to be integer. This is because if the
372 // condition on the constraints below is true, then the constraints will
373 // always imply the variable to be inside a [integer_lb, integer_ub] domain.
374 // And if the intersection of this domain with the variable domain is
375 // non-empty, then it contains one or more integer points and we can always
376 // set the variable to one of these integer values.
377 if (std::ceil(lower_bound) > std::floor(upper_bound)) return false;
378 }
379
380 // Primal detection for each constraint containing variable.
381 for (const SparseColumn::Entry entry :
382 linear_program.GetSparseColumn(variable)) {
383 if (!ConstraintSupportsImpliedIntegrality(linear_program, variable,
384 entry.row())) {
385 return false;
386 }
387 }
388 return true;
389}
390
391bool ImpliedIntegerPreprocessor::ConstraintSupportsImpliedIntegrality(
392 const LinearProgram& linear_program, ColIndex variable, RowIndex row) {
393 const SparseMatrix& coefficients_transpose =
394 linear_program.GetTransposeSparseMatrix();
395 const Fractional variable_coefficient = coefficients_transpose.LookUpValue(
396 ColToRowIndex(variable), RowToColIndex(row));
397
398 for (const SparseColumn::Entry entry :
399 coefficients_transpose.column(RowToColIndex(row))) {
400 const ColIndex col = RowToColIndex(entry.row());
401 if (col == variable) continue;
402
403 // Check if the variables in the row are all integers.
404 if (!linear_program.IsVariableInteger(col)) {
405 return false;
406 }
407
408 // Check if the coefficient ratios are all integers.
409 const Fractional coefficient_ratio =
410 entry.coefficient() / variable_coefficient;
411 if (!IsIntegerWithinTolerance(coefficient_ratio,
412 integer_solution_tolerance_)) {
413 return false;
414 }
415 }
416
417 // Check if the constraint bound ratios are integers.
418 // Note that we ignore infinities.
419 if (linear_program.constraint_lower_bounds()[row] != -kInfinity) {
420 const Fractional constraint_lower_bound_ratio =
421 linear_program.constraint_lower_bounds()[row] / variable_coefficient;
422 if (!IsIntegerWithinTolerance(constraint_lower_bound_ratio,
423 integer_solution_tolerance_)) {
424 return false;
425 }
426 }
427 if (linear_program.constraint_upper_bounds()[row] != kInfinity) {
428 const Fractional constraint_upper_bound_ratio =
429 linear_program.constraint_upper_bounds()[row] / variable_coefficient;
430 if (!IsIntegerWithinTolerance(constraint_upper_bound_ratio,
431 integer_solution_tolerance_)) {
432 return false;
433 }
434 }
435 return true;
436}
437
438bool ImpliedIntegerPreprocessor::VariableOccursInAtLeastOneEqualityConstraint(
439 const LinearProgram& linear_program, ColIndex variable) {
440 for (const SparseColumn::Entry entry :
441 linear_program.GetSparseColumn(variable)) {
442 // Check if the constraint is an equality.
443 if (linear_program.constraint_upper_bounds()[entry.row()] ==
444 linear_program.constraint_lower_bounds()[entry.row()]) {
445 return true;
446 }
447 }
448 return false;
449}
450
451// --------------------------------------------------------
452// ReduceCostOverExclusiveOrConstraintPreprocessor
453// --------------------------------------------------------
454
456 LinearProgram* linear_program) {
458 RETURN_VALUE_IF_NULL(linear_program, false);
459 const RowIndex num_constraints = linear_program->num_constraints();
460 const SparseMatrix& transpose = linear_program->GetTransposeSparseMatrix();
461 for (RowIndex row(0); row < num_constraints; ++row) {
462 if (linear_program->constraint_lower_bounds()[row] != 1.0) continue;
463 if (linear_program->constraint_upper_bounds()[row] != 1.0) continue;
464 Fractional min_cost = std::numeric_limits<Fractional>::infinity();
465 bool constraint_is_exclusive_or = true;
466 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
467 const ColIndex var = RowToColIndex(e.row());
468 if (!linear_program->IsVariableInteger(var) ||
469 linear_program->variable_lower_bounds()[var] != 0.0 ||
470 linear_program->variable_upper_bounds()[var] != 1.0 ||
471 e.coefficient() != 1.0) {
472 constraint_is_exclusive_or = false;
473 break;
474 }
475 min_cost =
476 std::min(min_cost, linear_program->objective_coefficients()[var]);
477 }
478 if (constraint_is_exclusive_or && min_cost > 0.0 &&
479 glop::IsFinite(min_cost)) {
480 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
481 const ColIndex var = RowToColIndex(e.row());
482 const Fractional cost = linear_program->objective_coefficients()[var];
483 linear_program->SetObjectiveCoefficient(var, cost - min_cost);
484 }
485 linear_program->SetObjectiveOffset(linear_program->objective_offset() +
486 min_cost);
487 }
488 }
489 return false;
490}
491
492} // namespace operations_research
bool Run(glop::LinearProgram *linear_program) override
bool Run(glop::LinearProgram *linear_program) override
bool Run(glop::LinearProgram *linear_program) override
bool Run(glop::LinearProgram *linear_program) override
bool BoundsOfIntegerConstraintsAreInteger(Fractional tolerance) const
Definition lp_data.cc:1523
const DenseRow & objective_coefficients() const
Definition lp_data.h:230
bool BoundsOfIntegerVariablesAreInteger(Fractional tolerance) const
Definition lp_data.cc:1507
void SetObjectiveOffset(Fractional objective_offset)
Definition lp_data.cc:340
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition lp_data.cc:335
const DenseColumn & constraint_lower_bounds() const
Definition lp_data.h:222
const SparseMatrix & GetTransposeSparseMatrix() const
Definition lp_data.cc:385
const DenseRow & variable_lower_bounds() const
Definition lp_data.h:236
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition lp_data.cc:258
const std::vector< ColIndex > & IntegerVariablesList() const
Definition lp_data.cc:289
void SetVariableType(ColIndex col, VariableType type)
Definition lp_data.cc:245
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition lp_data.cc:318
const DenseRow & variable_upper_bounds() const
Definition lp_data.h:239
VariableType GetVariableType(ColIndex col) const
Definition lp_data.cc:381
const DenseColumn & constraint_upper_bounds() const
Definition lp_data.h:225
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition lp_data.cc:418
bool IsVariableInteger(ColIndex col) const
Definition lp_data.cc:304
const SparseColumn & column(ColIndex col) const
Definition sparse.h:193
Fractional LookUpValue(RowIndex row, ColIndex col) const
Definition sparse.cc:333
Fractional SumWithout(Fractional x) const
Definition lp_utils.h:364
RowIndex ColToRowIndex(ColIndex col)
Definition lp_types.h:57
constexpr ColIndex kInvalidCol(-1)
SumWithOneMissing< true > SumWithPositiveInfiniteAndOneMissing
Definition lp_utils.h:395
ColIndex RowToColIndex(RowIndex row)
Definition lp_types.h:54
bool IsFinite(Fractional value)
Definition lp_types.h:94
RowIndex ColToRowIndex(ColIndex col)
Definition lp_types.h:57
constexpr Fractional kInfinity
Definition lp_types.h:87
SumWithOneMissing< false > SumWithNegativeInfiniteAndOneMissing
Definition lp_utils.h:396
OR-Tools root namespace.
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition fp_utils.h:173
static constexpr double kInfinity
bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound)
#define RETURN_VALUE_IF_NULL(x, v)
#define SCOPED_INSTRUCTION_COUNT(time_limit)
Definition stats.h:422