Google OR-Tools v9.11
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-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 <cmath>
18#include <cstdint>
19#include <cstdlib>
20#include <deque>
21#include <iomanip>
22#include <ios>
23#include <limits>
24#include <memory>
25#include <set>
26#include <string>
27#include <utility>
28#include <vector>
29
30#include "absl/strings/str_format.h"
31#include "absl/strings/string_view.h"
35#include "ortools/glop/status.h"
40
41namespace operations_research {
42namespace glop {
43
44using ::util::Reverse;
45
46namespace {
47// Returns an interval as an human readable string for debugging.
48std::string IntervalString(Fractional lb, Fractional ub) {
49 return absl::StrFormat("[%g, %g]", lb, ub);
50}
51
52#if defined(_MSC_VER)
53double trunc(double d) { return d > 0 ? floor(d) : ceil(d); }
54#endif
55} // namespace
56
57// --------------------------------------------------------
58// Preprocessor
59// --------------------------------------------------------
60Preprocessor::Preprocessor(const GlopParameters* parameters)
61 : status_(ProblemStatus::INIT),
62 parameters_(*parameters),
63 in_mip_context_(false),
64 infinite_time_limit_(TimeLimit::Infinite()),
65 time_limit_(infinite_time_limit_.get()) {}
67
68// --------------------------------------------------------
69// MainLpPreprocessor
70// --------------------------------------------------------
71
72#define RUN_PREPROCESSOR(name) \
73 RunAndPushIfRelevant(std::unique_ptr<Preprocessor>(new name(&parameters_)), \
74 #name, time_limit_, lp)
75
76bool MainLpPreprocessor::Run(LinearProgram* lp) {
77 RETURN_VALUE_IF_NULL(lp, false);
78
79 default_logger_.EnableLogging(parameters_.log_search_progress());
80 default_logger_.SetLogToStdOut(parameters_.log_to_stdout());
81
82 SOLVER_LOG(logger_, "");
83 SOLVER_LOG(logger_, "Starting presolve...");
84
85 initial_num_rows_ = lp->num_constraints();
86 initial_num_cols_ = lp->num_variables();
87 initial_num_entries_ = lp->num_entries();
88 if (parameters_.use_preprocessing()) {
90
91 // We run it a few times because running one preprocessor may allow another
92 // one to remove more stuff.
93 const int kMaxNumPasses = 20;
94 for (int i = 0; i < kMaxNumPasses; ++i) {
95 const int old_stack_size = preprocessors_.size();
104
105 // Abort early if none of the preprocessors did something. Technically
106 // this is true if none of the preprocessors above needs postsolving,
107 // which has exactly the same meaning for these particular preprocessors.
108 if (preprocessors_.size() == old_stack_size) {
109 // We use i here because the last pass did nothing.
110 SOLVER_LOG(logger_, "Reached fixed point after presolve pass #", i);
111 break;
112 }
113 }
114 RUN_PREPROCESSOR(EmptyColumnPreprocessor);
115 RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
116
117 // TODO(user): Run them in the loop above if the effect on the running time
118 // is good. This needs more investigation.
119 RUN_PREPROCESSOR(ProportionalColumnPreprocessor);
120 RUN_PREPROCESSOR(ProportionalRowPreprocessor);
121
122 // If DualizerPreprocessor was run, we need to do some extra preprocessing.
123 // This is because it currently adds a lot of zero-cost singleton columns.
124 const int old_stack_size = preprocessors_.size();
125
126 // TODO(user): We probably want to scale the costs before and after this
127 // preprocessor so that the rhs/objective of the dual are with a good
128 // magnitude.
129 RUN_PREPROCESSOR(DualizerPreprocessor);
130 if (old_stack_size != preprocessors_.size()) {
131 RUN_PREPROCESSOR(SingletonPreprocessor);
132 RUN_PREPROCESSOR(FreeConstraintPreprocessor);
133 RUN_PREPROCESSOR(UnconstrainedVariablePreprocessor);
134 RUN_PREPROCESSOR(EmptyColumnPreprocessor);
135 RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
136 }
137
138 RUN_PREPROCESSOR(SingletonColumnSignPreprocessor);
139 }
140
141 // The scaling is controlled by use_scaling, not use_preprocessing.
142 RUN_PREPROCESSOR(ScalingPreprocessor);
143
144 return !preprocessors_.empty();
145}
146
147#undef RUN_PREPROCESSOR
148
149void MainLpPreprocessor::RunAndPushIfRelevant(
150 std::unique_ptr<Preprocessor> preprocessor, absl::string_view name,
151 TimeLimit* time_limit, LinearProgram* lp) {
154 if (status_ != ProblemStatus::INIT || time_limit->LimitReached()) return;
155
156 const double start_time = time_limit->GetElapsedTime();
157 preprocessor->SetTimeLimit(time_limit);
158
159 // No need to run the preprocessor if the lp is empty.
160 // TODO(user): without this test, the code is failing as of 2013-03-18.
161 if (lp->num_variables() == 0 && lp->num_constraints() == 0) {
163 return;
164 }
165
166 if (preprocessor->Run(lp)) {
167 const EntryIndex new_num_entries = lp->num_entries();
168 const double preprocess_time = time_limit->GetElapsedTime() - start_time;
169 SOLVER_LOG(logger_,
170 absl::StrFormat(
171 "%-45s: %d(%d) rows, %d(%d) columns, %d(%d) entries. (%fs)",
172 name, lp->num_constraints().value(),
173 (lp->num_constraints() - initial_num_rows_).value(),
174 lp->num_variables().value(),
175 (lp->num_variables() - initial_num_cols_).value(),
176 // static_cast<int64_t> is needed because the Android port
177 // uses int32_t.
178 static_cast<int64_t>(new_num_entries.value()),
179 static_cast<int64_t>(new_num_entries.value() -
180 initial_num_entries_.value()),
181 preprocess_time));
182 status_ = preprocessor->status();
183 preprocessors_.push_back(std::move(preprocessor));
184 return;
185 } else {
186 // Even if a preprocessor returns false (i.e. no need for postsolve), it
187 // can detect an issue with the problem.
188 status_ = preprocessor->status();
190 SOLVER_LOG(logger_, name, " detected that the problem is ",
192 }
193 }
194}
195
196void MainLpPreprocessor::RecoverSolution(ProblemSolution* solution) const {
198 for (const auto& p : gtl::reversed_view(preprocessors_)) {
199 p->RecoverSolution(solution);
201}
202
205 while (!preprocessors_.empty()) {
206 preprocessors_.back()->RecoverSolution(solution);
207 preprocessors_.pop_back();
208 }
209}
210
211// --------------------------------------------------------
212// ColumnDeletionHelper
213// --------------------------------------------------------
214
215void ColumnsSaver::SaveColumn(ColIndex col, const SparseColumn& column) {
216 const int index = saved_columns_.size();
217 CHECK(saved_columns_index_.insert({col, index}).second);
218 saved_columns_.push_back(column);
219}
220
222 const SparseColumn& column) {
223 const int index = saved_columns_.size();
224 const bool inserted = saved_columns_index_.insert({col, index}).second;
225 if (inserted) saved_columns_.push_back(column);
226}
228const SparseColumn& ColumnsSaver::SavedColumn(ColIndex col) const {
229 const auto it = saved_columns_index_.find(col);
230 CHECK(it != saved_columns_index_.end());
231 return saved_columns_[it->second];
232}
233
235 const auto it = saved_columns_index_.find(col);
236 return it == saved_columns_index_.end() ? empty_column_
237 : saved_columns_[it->second];
238}
239
241 is_column_deleted_.clear();
242 stored_value_.clear();
243}
244
250 ColIndex col, Fractional fixed_value, VariableStatus status) {
251 DCHECK_GE(col, 0);
252 if (col >= is_column_deleted_.size()) {
253 is_column_deleted_.resize(col + 1, false);
254 stored_value_.resize(col + 1, 0.0);
255 stored_status_.resize(col + 1, VariableStatus::FREE);
256 }
257 is_column_deleted_[col] = true;
258 stored_value_[col] = fixed_value;
259 stored_status_[col] = status;
260}
261
263 ProblemSolution* solution) const {
264 DenseRow new_primal_values;
265 VariableStatusRow new_variable_statuses;
266 ColIndex old_index(0);
267 for (ColIndex col(0); col < is_column_deleted_.size(); ++col) {
268 if (is_column_deleted_[col]) {
269 new_primal_values.push_back(stored_value_[col]);
270 new_variable_statuses.push_back(stored_status_[col]);
271 } else {
272 new_primal_values.push_back(solution->primal_values[old_index]);
273 new_variable_statuses.push_back(solution->variable_statuses[old_index]);
274 ++old_index;
275 }
276 }
277
278 // Copy the end of the vectors and swap them with the ones in solution.
279 const ColIndex num_cols = solution->primal_values.size();
280 DCHECK_EQ(num_cols, solution->variable_statuses.size());
281 for (; old_index < num_cols; ++old_index) {
282 new_primal_values.push_back(solution->primal_values[old_index]);
283 new_variable_statuses.push_back(solution->variable_statuses[old_index]);
284 }
285 new_primal_values.swap(solution->primal_values);
286 new_variable_statuses.swap(solution->variable_statuses);
287}
288
289// --------------------------------------------------------
290// RowDeletionHelper
291// --------------------------------------------------------
292
293void RowDeletionHelper::Clear() { is_row_deleted_.clear(); }
294
296 DCHECK_GE(row, 0);
297 if (row >= is_row_deleted_.size()) {
298 is_row_deleted_.resize(row + 1, false);
299 }
300 is_row_deleted_[row] = true;
302
304 if (row >= is_row_deleted_.size()) return;
305 is_row_deleted_[row] = false;
306}
307
309 return is_row_deleted_;
310}
313 DenseColumn new_dual_values;
314 ConstraintStatusColumn new_constraint_statuses;
315 RowIndex old_index(0);
316 const RowIndex end = is_row_deleted_.size();
317 for (RowIndex row(0); row < end; ++row) {
318 if (is_row_deleted_[row]) {
319 new_dual_values.push_back(0.0);
320 new_constraint_statuses.push_back(ConstraintStatus::BASIC);
321 } else {
322 new_dual_values.push_back(solution->dual_values[old_index]);
323 new_constraint_statuses.push_back(
324 solution->constraint_statuses[old_index]);
325 ++old_index;
326 }
327 }
328
329 // Copy the end of the vectors and swap them with the ones in solution.
330 const RowIndex num_rows = solution->dual_values.size();
331 DCHECK_EQ(num_rows, solution->constraint_statuses.size());
332 for (; old_index < num_rows; ++old_index) {
333 new_dual_values.push_back(solution->dual_values[old_index]);
334 new_constraint_statuses.push_back(solution->constraint_statuses[old_index]);
335 }
336 new_dual_values.swap(solution->dual_values);
337 new_constraint_statuses.swap(solution->constraint_statuses);
338}
339
340// --------------------------------------------------------
341// EmptyColumnPreprocessor
342// --------------------------------------------------------
343
344namespace {
345
346// Computes the status of a variable given its value and bounds. This only works
347// with a value exactly at one of the bounds, or a value of 0.0 for free
348// variables.
351 if (lower_bound == upper_bound) {
352 DCHECK_EQ(value, lower_bound);
353 DCHECK(IsFinite(lower_bound));
355 }
356 if (value == lower_bound) {
357 DCHECK_NE(lower_bound, -kInfinity);
359 }
360 if (value == upper_bound) {
361 DCHECK_NE(upper_bound, kInfinity);
363 }
364
365 // TODO(user): restrict this to unbounded variables with a value of zero.
366 // We can't do that when postsolving infeasible problem. Don't call postsolve
367 // on an infeasible problem?
369}
370
371// Returns the input with the smallest magnitude or zero if both are infinite.
372Fractional MinInMagnitudeOrZeroIfInfinite(Fractional a, Fractional b) {
373 const Fractional value = std::abs(a) < std::abs(b) ? a : b;
374 return IsFinite(value) ? value : 0.0;
375}
376
377Fractional MagnitudeOrZeroIfInfinite(Fractional value) {
378 return IsFinite(value) ? std::abs(value) : 0.0;
379}
380
381// Returns the maximum magnitude of the finite variable bounds of the given
382// linear program.
383Fractional ComputeMaxVariableBoundsMagnitude(const LinearProgram& lp) {
384 Fractional max_bounds_magnitude = 0.0;
385 const ColIndex num_cols = lp.num_variables();
386 for (ColIndex col(0); col < num_cols; ++col) {
387 max_bounds_magnitude = std::max(
388 max_bounds_magnitude,
389 std::max(MagnitudeOrZeroIfInfinite(lp.variable_lower_bounds()[col]),
390 MagnitudeOrZeroIfInfinite(lp.variable_upper_bounds()[col])));
391 }
392 return max_bounds_magnitude;
393}
394
395} // namespace
396
397bool EmptyColumnPreprocessor::Run(LinearProgram* lp) {
399 RETURN_VALUE_IF_NULL(lp, false);
400 column_deletion_helper_.Clear();
401 const ColIndex num_cols = lp->num_variables();
402 for (ColIndex col(0); col < num_cols; ++col) {
403 if (lp->GetSparseColumn(col).IsEmpty()) {
404 const Fractional lower_bound = lp->variable_lower_bounds()[col];
405 const Fractional upper_bound = lp->variable_upper_bounds()[col];
406 const Fractional objective_coefficient =
407 lp->GetObjectiveCoefficientForMinimizationVersion(col);
409 if (objective_coefficient == 0) {
410 // Any feasible value will do.
411 if (upper_bound != kInfinity) {
413 } else {
414 if (lower_bound != -kInfinity) {
416 } else {
417 value = Fractional(0.0);
418 }
419 }
420 } else {
421 value = objective_coefficient > 0 ? lower_bound : upper_bound;
422 if (!IsFinite(value)) {
423 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, empty column " << col
424 << " has a minimization cost of " << objective_coefficient
425 << " and bounds" << " [" << lower_bound << "," << upper_bound
426 << "]";
428 return false;
429 }
430 lp->SetObjectiveOffset(lp->objective_offset() +
431 value * lp->objective_coefficients()[col]);
432 }
433 column_deletion_helper_.MarkColumnForDeletionWithState(
434 col, value, ComputeVariableStatus(value, lower_bound, upper_bound));
435 }
436 }
437 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
438 return !column_deletion_helper_.IsEmpty();
439}
440
441void EmptyColumnPreprocessor::RecoverSolution(ProblemSolution* solution) const {
444 column_deletion_helper_.RestoreDeletedColumns(solution);
445}
446
447// --------------------------------------------------------
448// ProportionalColumnPreprocessor
449// --------------------------------------------------------
450
451namespace {
452
453// Subtracts 'multiple' times the column col of the given linear program from
454// the constraint bounds. That is, for a non-zero entry of coefficient c,
455// c * multiple is subtracted from both the constraint upper and lower bound.
456void SubtractColumnMultipleFromConstraintBound(ColIndex col,
457 Fractional multiple,
458 LinearProgram* lp) {
461 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
462 const RowIndex row = e.row();
463 const Fractional delta = multiple * e.coefficient();
464 (*lbs)[row] -= delta;
465 (*ubs)[row] -= delta;
466 }
467 // While not needed for correctness, this allows the presolved problem to
468 // have the same objective value as the original one.
470 lp->objective_coefficients()[col] * multiple);
471}
472
473// Struct used to detect proportional columns with the same cost. For that, a
474// vector of such struct will be sorted, and only the columns that end up
475// together need to be compared.
476struct ColumnWithRepresentativeAndScaledCost {
477 ColumnWithRepresentativeAndScaledCost(ColIndex _col, ColIndex _representative,
478 Fractional _scaled_cost)
479 : col(_col), representative(_representative), scaled_cost(_scaled_cost) {}
480 ColIndex col;
481 ColIndex representative;
483
484 bool operator<(const ColumnWithRepresentativeAndScaledCost& other) const {
485 if (representative == other.representative) {
486 if (scaled_cost == other.scaled_cost) {
487 return col < other.col;
488 }
489 return scaled_cost < other.scaled_cost;
490 }
491 return representative < other.representative;
495} // namespace
496
497bool ProportionalColumnPreprocessor::Run(LinearProgram* lp) {
499 RETURN_VALUE_IF_NULL(lp, false);
501 lp->GetSparseMatrix(), parameters_.preprocessor_zero_tolerance());
502
503 // Compute some statistics and make each class representative point to itself
504 // in the mapping. Also store the columns that are proportional to at least
505 // another column in proportional_columns to iterate on them more efficiently.
506 //
507 // TODO(user): Change FindProportionalColumns for this?
508 int num_proportionality_classes = 0;
509 std::vector<ColIndex> proportional_columns;
510 for (ColIndex col(0); col < mapping.size(); ++col) {
511 const ColIndex representative = mapping[col];
513 if (mapping[representative] == kInvalidCol) {
514 proportional_columns.push_back(representative);
515 ++num_proportionality_classes;
517 }
518 proportional_columns.push_back(col);
519 }
520 }
521 if (proportional_columns.empty()) return false;
522 VLOG(1) << "The problem contains " << proportional_columns.size()
523 << " columns which belong to " << num_proportionality_classes
524 << " proportionality classes.";
525
526 // Note(user): using the first coefficient may not give the best precision.
527 const ColIndex num_cols = lp->num_variables();
528 column_factors_.assign(num_cols, 0.0);
529 for (const ColIndex col : proportional_columns) {
530 const SparseColumn& column = lp->GetSparseColumn(col);
531 column_factors_[col] = column.GetFirstCoefficient();
532 }
533
534 // This is only meaningful for column representative.
535 //
536 // The reduced cost of a column is 'cost - dual_values.column' and we know
537 // that for all proportional columns, 'dual_values.column /
538 // column_factors_[col]' is the same. Here, we bound this quantity which is
539 // related to the cost 'slope' of a proportional column:
540 // cost / column_factors_[col].
541 DenseRow slope_lower_bound(num_cols, -kInfinity);
542 DenseRow slope_upper_bound(num_cols, +kInfinity);
543 for (const ColIndex col : proportional_columns) {
544 const ColIndex representative = mapping[col];
545
546 // We reason in terms of a minimization problem here.
547 const bool is_rc_positive_or_zero =
548 (lp->variable_upper_bounds()[col] == kInfinity);
549 const bool is_rc_negative_or_zero =
550 (lp->variable_lower_bounds()[col] == -kInfinity);
551 bool is_slope_upper_bounded = is_rc_positive_or_zero;
552 bool is_slope_lower_bounded = is_rc_negative_or_zero;
553 if (column_factors_[col] < 0.0) {
554 std::swap(is_slope_lower_bounded, is_slope_upper_bounded);
555 }
556 const Fractional slope =
557 lp->GetObjectiveCoefficientForMinimizationVersion(col) /
558 column_factors_[col];
559 if (is_slope_lower_bounded) {
560 slope_lower_bound[representative] =
561 std::max(slope_lower_bound[representative], slope);
562 }
563 if (is_slope_upper_bounded) {
564 slope_upper_bound[representative] =
565 std::min(slope_upper_bound[representative], slope);
566 }
567 }
568
569 // Deal with empty slope intervals.
570 for (const ColIndex col : proportional_columns) {
571 const ColIndex representative = mapping[col];
572
573 // This is only needed for class representative columns.
574 if (representative == col) {
576 slope_lower_bound[representative],
577 slope_upper_bound[representative])) {
578 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, no feasible dual values"
579 << " can satisfy the constraints of the proportional columns"
580 << " with representative " << representative << "."
581 << " the associated quantity must be in ["
582 << slope_lower_bound[representative] << ","
583 << slope_upper_bound[representative] << "].";
585 return false;
586 }
587 }
588 }
589
590 // Now, fix the columns that can be fixed to one of their bounds.
591 for (const ColIndex col : proportional_columns) {
592 const ColIndex representative = mapping[col];
593 const Fractional slope =
594 lp->GetObjectiveCoefficientForMinimizationVersion(col) /
595 column_factors_[col];
596
597 // The scaled reduced cost is slope - quantity.
598 bool variable_can_be_fixed = false;
600
601 const Fractional lower_bound = lp->variable_lower_bounds()[col];
602 const Fractional upper_bound = lp->variable_upper_bounds()[col];
604 slope)) {
605 // The scaled reduced cost is < 0.
606 variable_can_be_fixed = true;
607 target_bound = (column_factors_[col] >= 0.0) ? upper_bound : lower_bound;
609 slope, slope_upper_bound[representative])) {
610 // The scaled reduced cost is > 0.
611 variable_can_be_fixed = true;
612 target_bound = (column_factors_[col] >= 0.0) ? lower_bound : upper_bound;
613 }
614
615 if (variable_can_be_fixed) {
616 // Clear mapping[col] so this column will not be considered for the next
617 // stage.
618 mapping[col] = kInvalidCol;
619 if (!IsFinite(target_bound)) {
620 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED.";
622 return false;
623 } else {
624 SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
625 column_deletion_helper_.MarkColumnForDeletionWithState(
627 ComputeVariableStatus(target_bound, lower_bound, upper_bound));
628 }
629 }
630 }
631
632 // Merge the variables with the same scaled cost.
633 std::vector<ColumnWithRepresentativeAndScaledCost> sorted_columns;
634 for (const ColIndex col : proportional_columns) {
635 const ColIndex representative = mapping[col];
636
637 // This test is needed because we already removed some columns.
638 if (mapping[col] != kInvalidCol) {
639 sorted_columns.push_back(ColumnWithRepresentativeAndScaledCost(
641 lp->objective_coefficients()[col] / column_factors_[col]));
642 }
643 }
644 std::sort(sorted_columns.begin(), sorted_columns.end());
645
646 // All this will be needed during postsolve.
647 merged_columns_.assign(num_cols, kInvalidCol);
648 lower_bounds_.assign(num_cols, -kInfinity);
649 upper_bounds_.assign(num_cols, kInfinity);
650 new_lower_bounds_.assign(num_cols, -kInfinity);
651 new_upper_bounds_.assign(num_cols, kInfinity);
652
653 for (int i = 0; i < sorted_columns.size();) {
654 const ColIndex target_col = sorted_columns[i].col;
655 const ColIndex target_representative = sorted_columns[i].representative;
656 const Fractional target_scaled_cost = sorted_columns[i].scaled_cost;
657
658 // Save the initial bounds before modifying them.
659 lower_bounds_[target_col] = lp->variable_lower_bounds()[target_col];
660 upper_bounds_[target_col] = lp->variable_upper_bounds()[target_col];
661
662 int num_merged = 0;
663 for (++i; i < sorted_columns.size(); ++i) {
664 if (sorted_columns[i].representative != target_representative) break;
665 if (std::abs(sorted_columns[i].scaled_cost - target_scaled_cost) >=
666 parameters_.preprocessor_zero_tolerance()) {
667 break;
668 }
669 ++num_merged;
670 const ColIndex col = sorted_columns[i].col;
671 const Fractional lower_bound = lp->variable_lower_bounds()[col];
672 const Fractional upper_bound = lp->variable_upper_bounds()[col];
673 lower_bounds_[col] = lower_bound;
674 upper_bounds_[col] = upper_bound;
675 merged_columns_[col] = target_col;
676
677 // This is a bit counter intuitive, but when a column is divided by x,
678 // the corresponding bounds have to be multiplied by x.
679 const Fractional bound_factor =
680 column_factors_[col] / column_factors_[target_col];
681
682 // We need to shift the variable so that a basic solution of the new
683 // problem can easily be converted to a basic solution of the original
684 // problem.
685
686 // A feasible value for the variable must be chosen, and the variable must
687 // be shifted by this value. This is done to make sure that it will be
688 // possible to recreate a basic solution of the original problem from a
689 // basic solution of the pre-solved problem during post-solve.
690 const Fractional target_value =
691 MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
692 Fractional lower_diff = (lower_bound - target_value) * bound_factor;
693 Fractional upper_diff = (upper_bound - target_value) * bound_factor;
694 if (bound_factor < 0.0) {
695 std::swap(lower_diff, upper_diff);
696 }
697 lp->SetVariableBounds(
698 target_col, lp->variable_lower_bounds()[target_col] + lower_diff,
699 lp->variable_upper_bounds()[target_col] + upper_diff);
700 SubtractColumnMultipleFromConstraintBound(col, target_value, lp);
701 column_deletion_helper_.MarkColumnForDeletionWithState(
702 col, target_value,
703 ComputeVariableStatus(target_value, lower_bound, upper_bound));
704 }
705
706 // If at least one column was merged, the target column must be shifted like
707 // the other columns in the same equivalence class for the same reason (see
708 // above).
709 if (num_merged > 0) {
710 merged_columns_[target_col] = target_col;
711 const Fractional target_value = MinInMagnitudeOrZeroIfInfinite(
712 lower_bounds_[target_col], upper_bounds_[target_col]);
713 lp->SetVariableBounds(
714 target_col, lp->variable_lower_bounds()[target_col] - target_value,
715 lp->variable_upper_bounds()[target_col] - target_value);
716 SubtractColumnMultipleFromConstraintBound(target_col, target_value, lp);
717 new_lower_bounds_[target_col] = lp->variable_lower_bounds()[target_col];
718 new_upper_bounds_[target_col] = lp->variable_upper_bounds()[target_col];
719 }
720 }
721
722 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
723 return !column_deletion_helper_.IsEmpty();
724}
725
727 ProblemSolution* solution) const {
730 column_deletion_helper_.RestoreDeletedColumns(solution);
731
732 // The rest of this function is to unmerge the columns so that the solution be
733 // primal-feasible.
734 const ColIndex num_cols = merged_columns_.size();
735 DenseBooleanRow is_representative_basic(num_cols, false);
736 DenseBooleanRow is_distance_to_upper_bound(num_cols, false);
737 DenseRow distance_to_bound(num_cols, 0.0);
738 DenseRow wanted_value(num_cols, 0.0);
739
740 // The first pass is a loop over the representatives to compute the current
741 // distance to the new bounds.
742 for (ColIndex col(0); col < num_cols; ++col) {
743 if (merged_columns_[col] == col) {
744 const Fractional value = solution->primal_values[col];
745 const Fractional distance_to_upper_bound = new_upper_bounds_[col] - value;
746 const Fractional distance_to_lower_bound = value - new_lower_bounds_[col];
747 if (distance_to_upper_bound < distance_to_lower_bound) {
748 distance_to_bound[col] = distance_to_upper_bound;
749 is_distance_to_upper_bound[col] = true;
750 } else {
751 distance_to_bound[col] = distance_to_lower_bound;
752 is_distance_to_upper_bound[col] = false;
753 }
754 is_representative_basic[col] =
755 solution->variable_statuses[col] == VariableStatus::BASIC;
756
757 // Restore the representative value to a feasible value of the initial
758 // variable. Now all the merged variable are at a feasible value.
759 wanted_value[col] = value;
760 solution->primal_values[col] = MinInMagnitudeOrZeroIfInfinite(
761 lower_bounds_[col], upper_bounds_[col]);
762 solution->variable_statuses[col] = ComputeVariableStatus(
763 solution->primal_values[col], lower_bounds_[col], upper_bounds_[col]);
764 }
765 }
766
767 // Second pass to correct the values.
768 for (ColIndex col(0); col < num_cols; ++col) {
769 const ColIndex representative = merged_columns_[col];
771 if (IsFinite(distance_to_bound[representative])) {
772 // If the distance is finite, then each variable is set to its
773 // corresponding bound (the one from which the distance is computed) and
774 // is then changed by as much as possible until the distance is zero.
775 const Fractional bound_factor =
776 column_factors_[col] / column_factors_[representative];
777 const Fractional scaled_distance =
778 distance_to_bound[representative] / std::abs(bound_factor);
779 const Fractional width = upper_bounds_[col] - lower_bounds_[col];
780 const bool to_upper_bound =
781 (bound_factor > 0.0) == is_distance_to_upper_bound[representative];
782 if (width <= scaled_distance) {
783 solution->primal_values[col] =
784 to_upper_bound ? lower_bounds_[col] : upper_bounds_[col];
785 solution->variable_statuses[col] =
786 ComputeVariableStatus(solution->primal_values[col],
787 lower_bounds_[col], upper_bounds_[col]);
788 distance_to_bound[representative] -= width * std::abs(bound_factor);
789 } else {
790 solution->primal_values[col] =
791 to_upper_bound ? upper_bounds_[col] - scaled_distance
792 : lower_bounds_[col] + scaled_distance;
793 solution->variable_statuses[col] =
794 is_representative_basic[representative]
796 : ComputeVariableStatus(solution->primal_values[col],
797 lower_bounds_[col],
798 upper_bounds_[col]);
799 distance_to_bound[representative] = 0.0;
800 is_representative_basic[representative] = false;
801 }
802 } else {
803 // If the distance is not finite, then only one variable needs to be
804 // changed from its current feasible value in order to have a
805 // primal-feasible solution.
806 const Fractional error = wanted_value[representative];
807 if (error == 0.0) {
808 if (is_representative_basic[representative]) {
809 solution->variable_statuses[col] = VariableStatus::BASIC;
810 is_representative_basic[representative] = false;
811 }
812 } else {
813 const Fractional bound_factor =
814 column_factors_[col] / column_factors_[representative];
815 const bool use_this_variable =
816 (error * bound_factor > 0.0) ? (upper_bounds_[col] == kInfinity)
817 : (lower_bounds_[col] == -kInfinity);
818 if (use_this_variable) {
819 wanted_value[representative] = 0.0;
820 solution->primal_values[col] += error / bound_factor;
821 if (is_representative_basic[representative]) {
822 solution->variable_statuses[col] = VariableStatus::BASIC;
823 is_representative_basic[representative] = false;
824 } else {
825 // This should not happen on an OPTIMAL or FEASIBLE solution.
826 DCHECK(solution->status != ProblemStatus::OPTIMAL &&
828 solution->variable_statuses[col] = VariableStatus::FREE;
829 }
830 }
831 }
832 }
833 }
834 }
835}
836
837// --------------------------------------------------------
838// ProportionalRowPreprocessor
839// --------------------------------------------------------
840
841bool ProportionalRowPreprocessor::Run(LinearProgram* lp) {
843 RETURN_VALUE_IF_NULL(lp, false);
844 const RowIndex num_rows = lp->num_constraints();
845 const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
846
847 // Use the first coefficient of each row to compute the proportionality
848 // factor. Note that the sign is important.
849 //
850 // Note(user): using the first coefficient may not give the best precision.
851 row_factors_.assign(num_rows, 0.0);
852 for (RowIndex row(0); row < num_rows; ++row) {
853 const SparseColumn& row_transpose = transpose.column(RowToColIndex(row));
854 if (!row_transpose.IsEmpty()) {
855 row_factors_[row] = row_transpose.GetFirstCoefficient();
856 }
857 }
858
859 // The new row bounds (only meaningful for the proportional rows).
860 DenseColumn lower_bounds(num_rows, -kInfinity);
861 DenseColumn upper_bounds(num_rows, +kInfinity);
862
863 // Where the new bounds are coming from. Only for the constraints that stay
864 // in the lp and are modified, kInvalidRow otherwise.
865 upper_bound_sources_.assign(num_rows, kInvalidRow);
866 lower_bound_sources_.assign(num_rows, kInvalidRow);
867
868 // Initialization.
869 // We need the first representative of each proportional row class to point to
870 // itself for the loop below. TODO(user): Already return such a mapping from
871 // FindProportionalColumns()?
873 transpose, parameters_.preprocessor_zero_tolerance());
874 DenseBooleanColumn is_a_representative(num_rows, false);
875 int num_proportional_rows = 0;
876 for (RowIndex row(0); row < num_rows; ++row) {
877 const ColIndex representative_row_as_col = mapping[RowToColIndex(row)];
878 if (representative_row_as_col != kInvalidCol) {
879 mapping[representative_row_as_col] = representative_row_as_col;
880 is_a_representative[ColToRowIndex(representative_row_as_col)] = true;
881 ++num_proportional_rows;
882 }
883 }
884
885 // Compute the bound of each representative as implied by the rows
886 // which are proportional to it. Also keep the source row of each bound.
887 for (RowIndex row(0); row < num_rows; ++row) {
888 const ColIndex row_as_col = RowToColIndex(row);
889 if (mapping[row_as_col] != kInvalidCol) {
890 // For now, delete all the rows that are proportional to another one.
891 // Note that we will unmark the final representative of this class later.
892 row_deletion_helper_.MarkRowForDeletion(row);
893 const RowIndex representative_row = ColToRowIndex(mapping[row_as_col]);
894
895 const Fractional factor =
896 row_factors_[representative_row] / row_factors_[row];
897 Fractional implied_lb = factor * lp->constraint_lower_bounds()[row];
898 Fractional implied_ub = factor * lp->constraint_upper_bounds()[row];
899 if (factor < 0.0) {
900 std::swap(implied_lb, implied_ub);
901 }
902
903 // TODO(user): if the bounds are equal, use the largest row in magnitude?
904 if (implied_lb >= lower_bounds[representative_row]) {
905 lower_bounds[representative_row] = implied_lb;
906 lower_bound_sources_[representative_row] = row;
907 }
908 if (implied_ub <= upper_bounds[representative_row]) {
909 upper_bounds[representative_row] = implied_ub;
910 upper_bound_sources_[representative_row] = row;
911 }
912 }
913 }
914
915 // For maximum precision, and also to simplify the postsolve, we choose
916 // a representative for each class of proportional columns that has at least
917 // one of the two tightest bounds.
918 for (RowIndex row(0); row < num_rows; ++row) {
919 if (!is_a_representative[row]) continue;
920 const RowIndex lower_source = lower_bound_sources_[row];
921 const RowIndex upper_source = upper_bound_sources_[row];
922 lower_bound_sources_[row] = kInvalidRow;
923 upper_bound_sources_[row] = kInvalidRow;
924 DCHECK_NE(lower_source, kInvalidRow);
925 DCHECK_NE(upper_source, kInvalidRow);
926 if (lower_source == upper_source) {
927 // In this case, a simple change of representative is enough.
928 // The constraint bounds of the representative will not change.
929 DCHECK_NE(lower_source, kInvalidRow);
930 row_deletion_helper_.UnmarkRow(lower_source);
931 } else {
932 // Report ProblemStatus::PRIMAL_INFEASIBLE if the new lower bound is not
933 // lower than the new upper bound modulo the default tolerance.
935 upper_bounds[row])) {
937 return false;
938 }
939
940 // Special case for fixed rows.
941 if (lp->constraint_lower_bounds()[lower_source] ==
942 lp->constraint_upper_bounds()[lower_source]) {
943 row_deletion_helper_.UnmarkRow(lower_source);
944 continue;
945 }
946 if (lp->constraint_lower_bounds()[upper_source] ==
947 lp->constraint_upper_bounds()[upper_source]) {
948 row_deletion_helper_.UnmarkRow(upper_source);
949 continue;
950 }
951
952 // This is the only case where a more complex postsolve is needed.
953 // To maximize precision, the class representative is changed to either
954 // upper_source or lower_source depending of which row has the largest
955 // proportionality factor.
956 RowIndex new_representative = lower_source;
957 RowIndex other = upper_source;
958 if (std::abs(row_factors_[new_representative]) <
959 std::abs(row_factors_[other])) {
960 std::swap(new_representative, other);
961 }
962
963 // Initialize the new bounds with the implied ones.
964 const Fractional factor =
965 row_factors_[new_representative] / row_factors_[other];
966 Fractional new_lb = factor * lp->constraint_lower_bounds()[other];
967 Fractional new_ub = factor * lp->constraint_upper_bounds()[other];
968 if (factor < 0.0) {
969 std::swap(new_lb, new_ub);
970 }
971
972 lower_bound_sources_[new_representative] = new_representative;
973 upper_bound_sources_[new_representative] = new_representative;
974
975 if (new_lb > lp->constraint_lower_bounds()[new_representative]) {
976 lower_bound_sources_[new_representative] = other;
977 } else {
978 new_lb = lp->constraint_lower_bounds()[new_representative];
979 }
980 if (new_ub < lp->constraint_upper_bounds()[new_representative]) {
981 upper_bound_sources_[new_representative] = other;
982 } else {
983 new_ub = lp->constraint_upper_bounds()[new_representative];
984 }
985 const RowIndex new_lower_source =
986 lower_bound_sources_[new_representative];
987 if (new_lower_source == upper_bound_sources_[new_representative]) {
988 row_deletion_helper_.UnmarkRow(new_lower_source);
989 lower_bound_sources_[new_representative] = kInvalidRow;
990 upper_bound_sources_[new_representative] = kInvalidRow;
991 continue;
992 }
993
994 // Take care of small numerical imprecision by making sure that lb <= ub.
995 // Note that if the imprecision was greater than the tolerance, the code
996 // at the beginning of this block would have reported
997 // ProblemStatus::PRIMAL_INFEASIBLE.
998 DCHECK(IsSmallerWithinFeasibilityTolerance(new_lb, new_ub));
999 if (new_lb > new_ub) {
1000 if (lower_bound_sources_[new_representative] == new_representative) {
1001 new_ub = lp->constraint_lower_bounds()[new_representative];
1002 } else {
1003 new_lb = lp->constraint_upper_bounds()[new_representative];
1004 }
1005 }
1006 row_deletion_helper_.UnmarkRow(new_representative);
1007 lp->SetConstraintBounds(new_representative, new_lb, new_ub);
1008 }
1009 }
1010
1011 lp_is_maximization_problem_ = lp->IsMaximizationProblem();
1012 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1013 return !row_deletion_helper_.IsEmpty();
1014}
1015
1017 ProblemSolution* solution) const {
1020 row_deletion_helper_.RestoreDeletedRows(solution);
1021
1022 // Make sure that all non-zero dual values on the proportional rows are
1023 // assigned to the correct row with the correct sign and that the statuses
1024 // are correct.
1025 const RowIndex num_rows = solution->dual_values.size();
1026 for (RowIndex row(0); row < num_rows; ++row) {
1027 const RowIndex lower_source = lower_bound_sources_[row];
1028 const RowIndex upper_source = upper_bound_sources_[row];
1029 if (lower_source == kInvalidRow && upper_source == kInvalidRow) continue;
1030 DCHECK_NE(lower_source, upper_source);
1031 DCHECK(lower_source == row || upper_source == row);
1032
1033 // If the representative is ConstraintStatus::BASIC, then all rows in this
1034 // class will be ConstraintStatus::BASIC and there is nothing to do.
1035 ConstraintStatus status = solution->constraint_statuses[row];
1036 if (status == ConstraintStatus::BASIC) continue;
1037
1038 // If the row is FIXED it will behave as a row
1039 // ConstraintStatus::AT_UPPER_BOUND or
1040 // ConstraintStatus::AT_LOWER_BOUND depending on the corresponding dual
1041 // variable sign.
1043 const Fractional corrected_dual_value = lp_is_maximization_problem_
1044 ? -solution->dual_values[row]
1045 : solution->dual_values[row];
1046 if (corrected_dual_value != 0.0) {
1047 status = corrected_dual_value > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1049 }
1050 }
1051
1052 // If one of the two conditions below are true, set the row status to
1053 // ConstraintStatus::BASIC.
1054 // Note that the source which is not row can't be FIXED (see presolve).
1055 if (lower_source != row && status == ConstraintStatus::AT_LOWER_BOUND) {
1056 DCHECK_EQ(0.0, solution->dual_values[lower_source]);
1057 const Fractional factor = row_factors_[row] / row_factors_[lower_source];
1058 solution->dual_values[lower_source] = factor * solution->dual_values[row];
1059 solution->dual_values[row] = 0.0;
1060 solution->constraint_statuses[row] = ConstraintStatus::BASIC;
1061 solution->constraint_statuses[lower_source] =
1064 }
1065 if (upper_source != row && status == ConstraintStatus::AT_UPPER_BOUND) {
1066 DCHECK_EQ(0.0, solution->dual_values[upper_source]);
1067 const Fractional factor = row_factors_[row] / row_factors_[upper_source];
1068 solution->dual_values[upper_source] = factor * solution->dual_values[row];
1069 solution->dual_values[row] = 0.0;
1070 solution->constraint_statuses[row] = ConstraintStatus::BASIC;
1071 solution->constraint_statuses[upper_source] =
1074 }
1075
1076 // If the row status is still ConstraintStatus::FIXED_VALUE, we need to
1077 // relax its status.
1078 if (solution->constraint_statuses[row] == ConstraintStatus::FIXED_VALUE) {
1079 solution->constraint_statuses[row] =
1080 lower_source != row ? ConstraintStatus::AT_UPPER_BOUND
1082 }
1083 }
1084}
1085
1086// --------------------------------------------------------
1087// FixedVariablePreprocessor
1088// --------------------------------------------------------
1089
1090bool FixedVariablePreprocessor::Run(LinearProgram* lp) {
1092 RETURN_VALUE_IF_NULL(lp, false);
1093 const ColIndex num_cols = lp->num_variables();
1094 for (ColIndex col(0); col < num_cols; ++col) {
1095 const Fractional lower_bound = lp->variable_lower_bounds()[col];
1096 const Fractional upper_bound = lp->variable_upper_bounds()[col];
1097 if (lower_bound == upper_bound) {
1098 const Fractional fixed_value = lower_bound;
1099 DCHECK(IsFinite(fixed_value));
1100
1101 // We need to change the constraint bounds.
1102 SubtractColumnMultipleFromConstraintBound(col, fixed_value, lp);
1103 column_deletion_helper_.MarkColumnForDeletionWithState(
1104 col, fixed_value, VariableStatus::FIXED_VALUE);
1105 }
1107
1108 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1109 return !column_deletion_helper_.IsEmpty();
1110}
1111
1113 ProblemSolution* solution) const {
1116 column_deletion_helper_.RestoreDeletedColumns(solution);
1117}
1118
1119// --------------------------------------------------------
1120// ForcingAndImpliedFreeConstraintPreprocessor
1121// --------------------------------------------------------
1122
1125 RETURN_VALUE_IF_NULL(lp, false);
1126 const RowIndex num_rows = lp->num_constraints();
1127
1128 // Compute the implied constraint bounds from the variable bounds.
1129 DenseColumn implied_lower_bounds(num_rows, 0);
1130 DenseColumn implied_upper_bounds(num_rows, 0);
1131 const ColIndex num_cols = lp->num_variables();
1132 StrictITIVector<RowIndex, int> row_degree(num_rows, 0);
1133 for (ColIndex col(0); col < num_cols; ++col) {
1134 const Fractional lower = lp->variable_lower_bounds()[col];
1135 const Fractional upper = lp->variable_upper_bounds()[col];
1136 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1137 const RowIndex row = e.row();
1138 const Fractional coeff = e.coefficient();
1139 if (coeff > 0.0) {
1140 implied_lower_bounds[row] += lower * coeff;
1141 implied_upper_bounds[row] += upper * coeff;
1142 } else {
1143 implied_lower_bounds[row] += upper * coeff;
1144 implied_upper_bounds[row] += lower * coeff;
1145 }
1146 ++row_degree[row];
1147 }
1148 }
1149
1150 // Note that the ScalingPreprocessor is currently executed last, so here the
1151 // problem has not been scaled yet.
1152 int num_implied_free_constraints = 0;
1153 int num_forcing_constraints = 0;
1154 is_forcing_up_.assign(num_rows, false);
1155 DenseBooleanColumn is_forcing_down(num_rows, false);
1156 for (RowIndex row(0); row < num_rows; ++row) {
1157 if (row_degree[row] == 0) continue;
1158 Fractional lower = lp->constraint_lower_bounds()[row];
1159 Fractional upper = lp->constraint_upper_bounds()[row];
1160
1161 // Check for infeasibility.
1163 implied_upper_bounds[row]) ||
1164 !IsSmallerWithinFeasibilityTolerance(implied_lower_bounds[row],
1165 upper)) {
1166 VLOG(1) << "implied bound " << implied_lower_bounds[row] << " "
1167 << implied_upper_bounds[row];
1168 VLOG(1) << "constraint bound " << lower << " " << upper;
1170 return false;
1171 }
1172
1173 // Check if the constraint is forcing. That is, all the variables that
1174 // appear in it must be at one of their bounds.
1175 if (IsSmallerWithinPreprocessorZeroTolerance(implied_upper_bounds[row],
1176 lower)) {
1177 is_forcing_down[row] = true;
1178 ++num_forcing_constraints;
1179 continue;
1180 }
1182 implied_lower_bounds[row])) {
1183 is_forcing_up_[row] = true;
1184 ++num_forcing_constraints;
1185 continue;
1186 }
1187
1188 // We relax the constraint bounds only if the constraint is implied to be
1189 // free. Such constraints will later be deleted by the
1190 // FreeConstraintPreprocessor.
1191 //
1192 // Note that we could relax only one of the two bounds, but the impact this
1193 // would have on the revised simplex algorithm is unclear at this point.
1195 implied_lower_bounds[row]) &&
1196 IsSmallerWithinPreprocessorZeroTolerance(implied_upper_bounds[row],
1197 upper)) {
1198 lp->SetConstraintBounds(row, -kInfinity, kInfinity);
1199 ++num_implied_free_constraints;
1200 }
1201 }
1202
1203 if (num_implied_free_constraints > 0) {
1204 VLOG(1) << num_implied_free_constraints << " implied free constraints.";
1205 }
1206
1207 if (num_forcing_constraints > 0) {
1208 VLOG(1) << num_forcing_constraints << " forcing constraints.";
1209 lp_is_maximization_problem_ = lp->IsMaximizationProblem();
1210 costs_.resize(num_cols, 0.0);
1211 for (ColIndex col(0); col < num_cols; ++col) {
1212 const SparseColumn& column = lp->GetSparseColumn(col);
1213 const Fractional lower = lp->variable_lower_bounds()[col];
1214 const Fractional upper = lp->variable_upper_bounds()[col];
1215 bool is_forced = false;
1217 for (const SparseColumn::Entry e : column) {
1218 if (is_forcing_down[e.row()]) {
1219 const Fractional candidate = e.coefficient() < 0.0 ? lower : upper;
1220 if (is_forced && candidate != target_bound) {
1221 // The bounds are really close, so we fix to the bound with
1222 // the lowest magnitude. As of 2019/11/19, this is "better" than
1223 // fixing to the mid-point, because at postsolve, we always put
1224 // non-basic variables to their exact bounds (so, with mid-point
1225 // there would be a difference of epsilon/2 between the inner
1226 // solution and the postsolved one, which might cause issues).
1228 target_bound = std::abs(lower) < std::abs(upper) ? lower : upper;
1229 continue;
1230 }
1231 VLOG(1) << "A variable is forced in both directions! bounds: ["
1232 << std::fixed << std::setprecision(10) << lower << ", "
1233 << upper << "]. coeff:" << e.coefficient();
1235 return false;
1236 }
1237 target_bound = candidate;
1238 is_forced = true;
1239 }
1240 if (is_forcing_up_[e.row()]) {
1241 const Fractional candidate = e.coefficient() < 0.0 ? upper : lower;
1242 if (is_forced && candidate != target_bound) {
1243 // The bounds are really close, so we fix to the bound with
1244 // the lowest magnitude.
1246 target_bound = std::abs(lower) < std::abs(upper) ? lower : upper;
1247 continue;
1248 }
1249 VLOG(1) << "A variable is forced in both directions! bounds: ["
1250 << std::fixed << std::setprecision(10) << lower << ", "
1251 << upper << "]. coeff:" << e.coefficient();
1253 return false;
1254 }
1255 target_bound = candidate;
1256 is_forced = true;
1257 }
1258 }
1259 if (is_forced) {
1260 // Fix the variable, update the constraint bounds and save this column
1261 // and its cost for the postsolve.
1262 SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
1263 column_deletion_helper_.MarkColumnForDeletionWithState(
1265 ComputeVariableStatus(target_bound, lower, upper));
1266 columns_saver_.SaveColumn(col, column);
1267 costs_[col] = lp->objective_coefficients()[col];
1268 }
1269 }
1270 for (RowIndex row(0); row < num_rows; ++row) {
1271 // In theory, an M exists such that for any magnitude >= M, we will be at
1272 // an optimal solution. However, because of numerical errors, if the value
1273 // is too large, it causes problem when verifying the solution. So we
1274 // select the smallest such M (at least a resonably small one) during
1275 // postsolve. It is the reason why we need to store the columns that were
1276 // fixed.
1277 if (is_forcing_down[row] || is_forcing_up_[row]) {
1278 row_deletion_helper_.MarkRowForDeletion(row);
1279 }
1280 }
1281 }
1282
1283 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1284 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1285 return !column_deletion_helper_.IsEmpty();
1286}
1287
1289 ProblemSolution* solution) const {
1292 column_deletion_helper_.RestoreDeletedColumns(solution);
1293 row_deletion_helper_.RestoreDeletedRows(solution);
1294
1295 struct DeletionEntry {
1296 RowIndex row;
1297 ColIndex col;
1299 };
1300 std::vector<DeletionEntry> entries;
1301
1302 // Compute for each deleted columns the last deleted row in which it appears.
1303 const ColIndex size = column_deletion_helper_.GetMarkedColumns().size();
1304 for (ColIndex col(0); col < size; ++col) {
1305 if (!column_deletion_helper_.IsColumnMarked(col)) continue;
1307 RowIndex last_row = kInvalidRow;
1308 Fractional last_coefficient;
1309 for (const SparseColumn::Entry e : columns_saver_.SavedColumn(col)) {
1310 const RowIndex row = e.row();
1311 if (row_deletion_helper_.IsRowMarked(row)) {
1312 last_row = row;
1313 last_coefficient = e.coefficient();
1314 }
1315 }
1316 if (last_row != kInvalidRow) {
1317 entries.push_back({last_row, col, last_coefficient});
1318 }
1319 }
1320
1321 // Sort by row first and then col.
1322 std::sort(entries.begin(), entries.end(),
1323 [](const DeletionEntry& a, const DeletionEntry& b) {
1324 if (a.row == b.row) return a.col < b.col;
1325 return a.row < b.row;
1326 });
1327
1328 // For each deleted row (in order), compute a bound on the dual values so
1329 // that all the deleted columns for which this row is the last deleted row are
1330 // dual-feasible. Note that for the other columns, it will always be possible
1331 // to make them dual-feasible with a later row.
1332 // There are two possible outcomes:
1333 // - The dual value stays 0.0, and nothing changes.
1334 // - The bounds enforce a non-zero dual value, and one column will have a
1335 // reduced cost of 0.0. This column becomes VariableStatus::BASIC, and the
1336 // constraint status is changed to ConstraintStatus::AT_LOWER_BOUND,
1337 // ConstraintStatus::AT_UPPER_BOUND or ConstraintStatus::FIXED_VALUE.
1338 for (int i = 0; i < entries.size();) {
1339 const RowIndex row = entries[i].row;
1340 DCHECK(row_deletion_helper_.IsRowMarked(row));
1341
1342 // Process column with this last deleted row.
1343 Fractional new_dual_value = 0.0;
1344 ColIndex new_basic_column = kInvalidCol;
1345 for (; i < entries.size(); ++i) {
1346 if (entries[i].row != row) break;
1347 const ColIndex col = entries[i].col;
1348
1349 const Fractional scalar_product =
1350 ScalarProduct(solution->dual_values, columns_saver_.SavedColumn(col));
1351 const Fractional reduced_cost = costs_[col] - scalar_product;
1352 const Fractional bound = reduced_cost / entries[i].coefficient;
1353 if (is_forcing_up_[row] == !lp_is_maximization_problem_) {
1354 if (bound < new_dual_value) {
1355 new_dual_value = bound;
1356 new_basic_column = col;
1357 }
1358 } else {
1359 if (bound > new_dual_value) {
1360 new_dual_value = bound;
1361 new_basic_column = col;
1362 }
1363 }
1364 }
1365 if (new_basic_column != kInvalidCol) {
1366 solution->dual_values[row] = new_dual_value;
1367 solution->variable_statuses[new_basic_column] = VariableStatus::BASIC;
1368 solution->constraint_statuses[row] =
1369 is_forcing_up_[row] ? ConstraintStatus::AT_UPPER_BOUND
1371 }
1372 }
1373}
1374
1375// --------------------------------------------------------
1376// ImpliedFreePreprocessor
1377// --------------------------------------------------------
1378
1379bool ImpliedFreePreprocessor::Run(LinearProgram* lp) {
1381 RETURN_VALUE_IF_NULL(lp, false);
1382 if (!parameters_.use_implied_free_preprocessor()) return false;
1383 const RowIndex num_rows = lp->num_constraints();
1384 const ColIndex num_cols = lp->num_variables();
1385
1386 // For each constraint with n entries and each of its variable, we want the
1387 // bounds implied by the (n - 1) other variables and the constraint. We
1388 // use two handy utility classes that allow us to do that efficiently while
1389 // dealing properly with infinite bounds.
1390 const int size = num_rows.value();
1391 // TODO(user) : Replace SumWithNegativeInfiniteAndOneMissing and
1392 // SumWithPositiveInfiniteAndOneMissing with IntervalSumWithOneMissing.
1394 lb_sums(size);
1396 ub_sums(size);
1397
1398 // Initialize the sums by adding all the bounds of the variables.
1399 for (ColIndex col(0); col < num_cols; ++col) {
1400 const Fractional lower_bound = lp->variable_lower_bounds()[col];
1401 const Fractional upper_bound = lp->variable_upper_bounds()[col];
1402 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1403 Fractional entry_lb = e.coefficient() * lower_bound;
1404 Fractional entry_ub = e.coefficient() * upper_bound;
1405 if (e.coefficient() < 0.0) std::swap(entry_lb, entry_ub);
1406 lb_sums[e.row()].Add(entry_lb);
1407 ub_sums[e.row()].Add(entry_ub);
1408 }
1409 }
1410
1411 // The inequality
1412 // constraint_lb <= sum(entries) <= constraint_ub
1413 // can be rewritten as:
1414 // sum(entries) + (-activity) = 0,
1415 // where (-activity) has bounds [-constraint_ub, -constraint_lb].
1416 // We use this latter convention to simplify our code.
1417 for (RowIndex row(0); row < num_rows; ++row) {
1418 lb_sums[row].Add(-lp->constraint_upper_bounds()[row]);
1419 ub_sums[row].Add(-lp->constraint_lower_bounds()[row]);
1420 }
1421
1422 // Once a variable is freed, none of the rows in which it appears can be
1423 // used to make another variable free.
1424 DenseBooleanColumn used_rows(num_rows, false);
1425 postsolve_status_of_free_variables_.assign(num_cols, VariableStatus::FREE);
1426 variable_offsets_.assign(num_cols, 0.0);
1427
1428 // It is better to process columns with a small degree first:
1429 // - Degree-two columns make it possible to remove a row from the problem.
1430 // - This way there is more chance to make more free columns.
1431 // - It is better to have low degree free columns since a free column will
1432 // always end up in the simplex basis (except if there is more than the
1433 // number of rows in the problem).
1434 //
1435 // TODO(user): Only process degree-two so in subsequent passes more degree-two
1436 // columns could be made free. And only when no other reduction can be
1437 // applied, process the higher degree column?
1438 //
1439 // TODO(user): Be smarter about the order that maximizes the number of free
1440 // column. For instance if we have 3 doubleton columns that use the rows (1,2)
1441 // (2,3) and (3,4) then it is better not to make (2,3) free so the two other
1442 // two can be made free.
1443 std::vector<std::pair<EntryIndex, ColIndex>> col_by_degree;
1444 col_by_degree.reserve(num_cols.value());
1445 for (ColIndex col(0); col < num_cols; ++col) {
1446 col_by_degree.push_back({lp->GetSparseColumn(col).num_entries(), col});
1447 }
1448 std::sort(col_by_degree.begin(), col_by_degree.end());
1449
1450 // Now loop over the columns in order and make all implied-free columns free.
1451 int num_already_free_variables = 0;
1452 int num_implied_free_variables = 0;
1453 int num_fixed_variables = 0;
1454 for (const auto [_, col] : col_by_degree) {
1455 // If the variable is already free or fixed, we do nothing.
1456 const Fractional lower_bound = lp->variable_lower_bounds()[col];
1457 const Fractional upper_bound = lp->variable_upper_bounds()[col];
1459 ++num_already_free_variables;
1460 continue;
1461 }
1462 if (lower_bound == upper_bound) continue;
1463
1464 // Detect if the variable is implied free.
1465 Fractional overall_implied_lb = -kInfinity;
1466 Fractional overall_implied_ub = kInfinity;
1467 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1468 // If the row contains another implied free variable, then the bounds
1469 // implied by it will just be [-kInfinity, kInfinity] so we can skip it.
1470 if (used_rows[e.row()]) continue;
1471
1472 // This is the contribution of this column to the sum above.
1473 const Fractional coeff = e.coefficient();
1474 Fractional entry_lb = coeff * lower_bound;
1475 Fractional entry_ub = coeff * upper_bound;
1476 if (coeff < 0.0) std::swap(entry_lb, entry_ub);
1477
1478 // If X is the variable with index col and Y the sum of all the other
1479 // variables and of (-activity), then coeff * X + Y = 0. Since Y's bounds
1480 // are [lb_sum without X, ub_sum without X], it is easy to derive the
1481 // implied bounds on X.
1482 //
1483 // Important: If entry_lb (resp. entry_ub) are large, we cannot have a
1484 // good precision on the sum without. So we do add a defensive tolerance
1485 // that depends on these magnitude.
1486 const Fractional implied_lb =
1487 coeff > 0.0 ? -ub_sums[e.row()].SumWithoutUb(entry_ub) / coeff
1488 : -lb_sums[e.row()].SumWithoutLb(entry_lb) / coeff;
1489 const Fractional implied_ub =
1490 coeff > 0.0 ? -lb_sums[e.row()].SumWithoutLb(entry_lb) / coeff
1491 : -ub_sums[e.row()].SumWithoutUb(entry_ub) / coeff;
1492
1493 overall_implied_lb = std::max(overall_implied_lb, implied_lb);
1494 overall_implied_ub = std::min(overall_implied_ub, implied_ub);
1495 }
1496
1497 // Detect infeasible cases.
1498 if (!IsSmallerWithinFeasibilityTolerance(overall_implied_lb, upper_bound) ||
1499 !IsSmallerWithinFeasibilityTolerance(lower_bound, overall_implied_ub) ||
1500 !IsSmallerWithinFeasibilityTolerance(overall_implied_lb,
1501 overall_implied_ub)) {
1503 return false;
1504 }
1505
1506 // Detect fixed variable cases (there are two kinds).
1507 // Note that currently we don't do anything here except counting them.
1509 overall_implied_lb) ||
1511 lower_bound)) {
1512 // This case is already dealt with by the
1513 // ForcingAndImpliedFreeConstraintPreprocessor since it means that (at
1514 // least) one of the row is forcing.
1515 ++num_fixed_variables;
1516 continue;
1517 } else if (IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1518 overall_implied_lb)) {
1519 // TODO(user): As of July 2013, with our preprocessors this case is never
1520 // triggered on the Netlib. Note however that if it appears it can have a
1521 // big impact since by fixing the variable, the two involved constraints
1522 // are forcing and can be removed too (with all the variables they touch).
1523 // The postsolve step is quite involved though.
1524 ++num_fixed_variables;
1525 continue;
1526 }
1527
1528 // Is the variable implied free? Note that for an infinite lower_bound or
1529 // upper_bound the respective inequality is always true.
1531 overall_implied_lb) &&
1533 upper_bound)) {
1534 ++num_implied_free_variables;
1535 lp->SetVariableBounds(col, -kInfinity, kInfinity);
1536 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1537 used_rows[e.row()] = true;
1538 }
1539
1540 // This is a tricky part. We're freeing this variable, which means that
1541 // after solve, the modified variable will have status either
1542 // VariableStatus::FREE or VariableStatus::BASIC. In the former case
1543 // (VariableStatus::FREE, value = 0.0), we need to "fix" the
1544 // status (technically, our variable isn't free!) to either
1545 // VariableStatus::AT_LOWER_BOUND or VariableStatus::AT_UPPER_BOUND
1546 // (note that we skipped fixed variables), and "fix" the value to that
1547 // bound's value as well. We make the decision and the precomputation
1548 // here: we simply offset the variable by one of its bounds, and store
1549 // which bound that was. Note that if the modified variable turns out to
1550 // be VariableStatus::BASIC, we'll simply un-offset its value too;
1551 // and let the status be VariableStatus::BASIC.
1552 //
1553 // TODO(user): This trick is already used in the DualizerPreprocessor,
1554 // maybe we should just have a preprocessor that shifts all the variables
1555 // bounds to have at least one of them at 0.0, will that improve precision
1556 // and speed of the simplex? One advantage is that we can compute the
1557 // new constraint bounds with better precision using AccurateSum.
1558 DCHECK_NE(lower_bound, upper_bound);
1559 const Fractional offset =
1560 MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
1561 if (offset != 0.0) {
1562 variable_offsets_[col] = offset;
1563 SubtractColumnMultipleFromConstraintBound(col, offset, lp);
1564 }
1565 postsolve_status_of_free_variables_[col] =
1566 ComputeVariableStatus(offset, lower_bound, upper_bound);
1567 }
1568 }
1569 VLOG(1) << num_already_free_variables << " free variables in the problem.";
1570 VLOG(1) << num_implied_free_variables << " implied free columns.";
1571 VLOG(1) << num_fixed_variables << " variables can be fixed.";
1572
1573 return num_implied_free_variables > 0;
1574}
1575
1576void ImpliedFreePreprocessor::RecoverSolution(ProblemSolution* solution) const {
1579 const ColIndex num_cols = solution->variable_statuses.size();
1580 for (ColIndex col(0); col < num_cols; ++col) {
1581 // Skip variables that the preprocessor didn't change.
1582 if (postsolve_status_of_free_variables_[col] == VariableStatus::FREE) {
1583 DCHECK_EQ(0.0, variable_offsets_[col]);
1584 continue;
1585 }
1586 if (solution->variable_statuses[col] == VariableStatus::FREE) {
1587 solution->variable_statuses[col] =
1588 postsolve_status_of_free_variables_[col];
1589 } else {
1590 DCHECK_EQ(VariableStatus::BASIC, solution->variable_statuses[col]);
1591 }
1592 solution->primal_values[col] += variable_offsets_[col];
1593 }
1594}
1595
1596// --------------------------------------------------------
1597// DoubletonFreeColumnPreprocessor
1598// --------------------------------------------------------
1599
1602 RETURN_VALUE_IF_NULL(lp, false);
1603 // We will modify the matrix transpose and then push the change to the linear
1604 // program by calling lp->UseTransposeMatrixAsReference(). Note
1605 // that original_matrix will not change during this preprocessor run.
1606 const SparseMatrix& original_matrix = lp->GetSparseMatrix();
1608
1609 const ColIndex num_cols(lp->num_variables());
1610 for (ColIndex doubleton_col(0); doubleton_col < num_cols; ++doubleton_col) {
1611 // Only consider doubleton free columns.
1612 if (original_matrix.column(doubleton_col).num_entries() != 2) continue;
1613 if (lp->variable_lower_bounds()[doubleton_col] != -kInfinity) continue;
1614 if (lp->variable_upper_bounds()[doubleton_col] != kInfinity) continue;
1615
1616 // Collect the two column items. Note that we skip a column involving a
1617 // deleted row since it is no longer a doubleton then.
1618 RestoreInfo r;
1619 r.col = doubleton_col;
1620 r.objective_coefficient = lp->objective_coefficients()[r.col];
1621 int index = 0;
1622 for (const SparseColumn::Entry e : original_matrix.column(r.col)) {
1623 if (row_deletion_helper_.IsRowMarked(e.row())) break;
1624 r.row[index] = e.row();
1625 r.coeff[index] = e.coefficient();
1626 DCHECK_NE(0.0, e.coefficient());
1627 ++index;
1628 }
1629 if (index != NUM_ROWS) continue;
1630
1631 // Since the column didn't touch any previously deleted row, we are sure
1632 // that the coefficients were left untouched.
1633 DCHECK_EQ(r.coeff[DELETED], transpose->column(RowToColIndex(r.row[DELETED]))
1635 DCHECK_EQ(r.coeff[MODIFIED],
1636 transpose->column(RowToColIndex(r.row[MODIFIED]))
1638
1639 // We prefer deleting the row with the larger coefficient magnitude because
1640 // we will divide by this magnitude. TODO(user): Impact?
1641 if (std::abs(r.coeff[DELETED]) < std::abs(r.coeff[MODIFIED])) {
1642 std::swap(r.coeff[DELETED], r.coeff[MODIFIED]);
1643 std::swap(r.row[DELETED], r.row[MODIFIED]);
1644 }
1645
1646 // Save the deleted row for postsolve. Note that we remove it from the
1647 // transpose at the same time. This last operation is not strictly needed,
1648 // but it is faster to do it this way (both here and later when we will take
1649 // the transpose of the final transpose matrix).
1650 r.deleted_row_as_column.Swap(
1651 transpose->mutable_column(RowToColIndex(r.row[DELETED])));
1652
1653 // Move the bound of the deleted constraint to the initially free variable.
1654 {
1655 Fractional new_variable_lb =
1656 lp->constraint_lower_bounds()[r.row[DELETED]];
1657 Fractional new_variable_ub =
1658 lp->constraint_upper_bounds()[r.row[DELETED]];
1659 new_variable_lb /= r.coeff[DELETED];
1660 new_variable_ub /= r.coeff[DELETED];
1661 if (r.coeff[DELETED] < 0.0) std::swap(new_variable_lb, new_variable_ub);
1662 lp->SetVariableBounds(r.col, new_variable_lb, new_variable_ub);
1663 }
1664
1665 // Add a multiple of the deleted row to the modified row except on
1666 // column r.col where the coefficient will be left unchanged.
1667 r.deleted_row_as_column.AddMultipleToSparseVectorAndIgnoreCommonIndex(
1668 -r.coeff[MODIFIED] / r.coeff[DELETED], ColToRowIndex(r.col),
1669 parameters_.drop_tolerance(),
1670 transpose->mutable_column(RowToColIndex(r.row[MODIFIED])));
1671
1672 // We also need to correct the objective value of the variables involved in
1673 // the deleted row.
1674 if (r.objective_coefficient != 0.0) {
1675 for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1676 const ColIndex col = RowToColIndex(e.row());
1677 if (col == r.col) continue;
1678 const Fractional new_objective =
1680 e.coefficient() * r.objective_coefficient / r.coeff[DELETED];
1681
1682 // This detects if the objective should actually be zero, but because of
1683 // the numerical error in the formula above, we have a really low
1684 // objective instead. The logic is the same as in
1685 // AddMultipleToSparseVectorAndIgnoreCommonIndex().
1686 if (std::abs(new_objective) > parameters_.drop_tolerance()) {
1687 lp->SetObjectiveCoefficient(col, new_objective);
1688 } else {
1689 lp->SetObjectiveCoefficient(col, 0.0);
1690 }
1691 }
1692 }
1693 row_deletion_helper_.MarkRowForDeletion(r.row[DELETED]);
1694 restore_stack_.push_back(r);
1695 }
1696
1697 if (!row_deletion_helper_.IsEmpty()) {
1698 // The order is important.
1700 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1701 return true;
1702 }
1703 return false;
1704}
1705
1707 ProblemSolution* solution) const {
1709 row_deletion_helper_.RestoreDeletedRows(solution);
1710 for (const RestoreInfo& r : Reverse(restore_stack_)) {
1711 // Correct the constraint status.
1712 switch (solution->variable_statuses[r.col]) {
1714 solution->constraint_statuses[r.row[DELETED]] =
1716 break;
1718 solution->constraint_statuses[r.row[DELETED]] =
1719 r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_UPPER_BOUND
1721 break;
1723 solution->constraint_statuses[r.row[DELETED]] =
1724 r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1726 break;
1728 solution->constraint_statuses[r.row[DELETED]] = ConstraintStatus::FREE;
1729 break;
1731 // The default is good here:
1732 DCHECK_EQ(solution->constraint_statuses[r.row[DELETED]],
1734 break;
1735 }
1736
1737 // Correct the primal variable value.
1738 {
1739 Fractional new_variable_value = solution->primal_values[r.col];
1740 for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1741 const ColIndex col = RowToColIndex(e.row());
1742 if (col == r.col) continue;
1743 new_variable_value -= (e.coefficient() / r.coeff[DELETED]) *
1744 solution->primal_values[RowToColIndex(e.row())];
1745 }
1746 solution->primal_values[r.col] = new_variable_value;
1747 }
1748
1749 // In all cases, we will make the variable r.col VariableStatus::BASIC, so
1750 // we need to adjust the dual value of the deleted row so that the variable
1751 // reduced cost is zero. Note that there is nothing to do if the variable
1752 // was already basic.
1753 if (solution->variable_statuses[r.col] != VariableStatus::BASIC) {
1754 solution->variable_statuses[r.col] = VariableStatus::BASIC;
1755 Fractional current_reduced_cost =
1756 r.objective_coefficient -
1757 r.coeff[MODIFIED] * solution->dual_values[r.row[MODIFIED]];
1758 // We want current_reduced_cost - dual * coeff = 0, so:
1759 solution->dual_values[r.row[DELETED]] =
1760 current_reduced_cost / r.coeff[DELETED];
1761 } else {
1762 DCHECK_EQ(solution->dual_values[r.row[DELETED]], 0.0);
1763 }
1764 }
1765}
1766
1767// --------------------------------------------------------
1768// UnconstrainedVariablePreprocessor
1769// --------------------------------------------------------
1770
1771namespace {
1772
1773// Does the constraint block the variable to go to infinity in the given
1774// direction? direction is either positive or negative and row is the index of
1775// the constraint.
1776bool IsConstraintBlockingVariable(const LinearProgram& lp, Fractional direction,
1777 RowIndex row) {
1778 return direction > 0.0 ? lp.constraint_upper_bounds()[row] != kInfinity
1779 : lp.constraint_lower_bounds()[row] != -kInfinity;
1780}
1781
1782} // namespace
1783
1785 ColIndex col, Fractional target_bound, LinearProgram* lp) {
1786 DCHECK_EQ(0.0, lp->objective_coefficients()[col]);
1787 if (rhs_.empty()) {
1788 rhs_.resize(lp->num_constraints(), 0.0);
1789 activity_sign_correction_.resize(lp->num_constraints(), 1.0);
1790 is_unbounded_.resize(lp->num_variables(), false);
1791 }
1792 const bool is_unbounded_up = (target_bound == kInfinity);
1793 const SparseColumn& column = lp->GetSparseColumn(col);
1794 for (const SparseColumn::Entry e : column) {
1795 const RowIndex row = e.row();
1796 if (!row_deletion_helper_.IsRowMarked(row)) {
1797 row_deletion_helper_.MarkRowForDeletion(row);
1798 rows_saver_.SaveColumn(
1800 lp->GetTransposeSparseMatrix().column(RowToColIndex(row)));
1801 }
1802 const bool is_constraint_upper_bound_relevant =
1803 e.coefficient() > 0.0 ? !is_unbounded_up : is_unbounded_up;
1804 activity_sign_correction_[row] =
1805 is_constraint_upper_bound_relevant ? 1.0 : -1.0;
1806 rhs_[row] = is_constraint_upper_bound_relevant
1807 ? lp->constraint_upper_bounds()[row]
1808 : lp->constraint_lower_bounds()[row];
1809 DCHECK(IsFinite(rhs_[row]));
1810
1811 // TODO(user): Here, we may render the row free, so subsequent columns
1812 // processed by the columns loop in Run() have more chance to be removed.
1813 // However, we need to be more careful during the postsolve() if we do that.
1814 }
1815 is_unbounded_[col] = true;
1816 Fractional initial_feasible_value = MinInMagnitudeOrZeroIfInfinite(
1817 lp->variable_lower_bounds()[col], lp->variable_upper_bounds()[col]);
1818 column_deletion_helper_.MarkColumnForDeletionWithState(
1819 col, initial_feasible_value,
1820 ComputeVariableStatus(initial_feasible_value,
1821 lp->variable_lower_bounds()[col],
1822 lp->variable_upper_bounds()[col]));
1823}
1824
1825bool UnconstrainedVariablePreprocessor::Run(LinearProgram* lp) {
1827 RETURN_VALUE_IF_NULL(lp, false);
1828
1829 // To simplify the problem if something is almost zero, we use the low
1830 // tolerance (1e-9 by default) to be defensive. But to detect an infeasibility
1831 // we want to be sure (especially since the problem is not scaled in the
1832 // presolver) so we use an higher tolerance.
1833 //
1834 // TODO(user): Expose it as a parameter. We could rename both to
1835 // preprocessor_low_tolerance and preprocessor_high_tolerance.
1836 const Fractional low_tolerance = parameters_.preprocessor_zero_tolerance();
1837 const Fractional high_tolerance = 1e-4;
1838
1839 // We start by the dual variable bounds from the constraints.
1840 const RowIndex num_rows = lp->num_constraints();
1841 dual_lb_.assign(num_rows, -kInfinity);
1842 dual_ub_.assign(num_rows, kInfinity);
1843 for (RowIndex row(0); row < num_rows; ++row) {
1844 if (lp->constraint_lower_bounds()[row] == -kInfinity) {
1845 dual_ub_[row] = 0.0;
1846 }
1847 if (lp->constraint_upper_bounds()[row] == kInfinity) {
1848 dual_lb_[row] = 0.0;
1850 }
1851
1852 const ColIndex num_cols = lp->num_variables();
1853 may_have_participated_lb_.assign(num_cols, false);
1854 may_have_participated_ub_.assign(num_cols, false);
1855
1856 // We maintain a queue of columns to process.
1857 std::deque<ColIndex> columns_to_process;
1858 DenseBooleanRow in_columns_to_process(num_cols, true);
1859 std::vector<RowIndex> changed_rows;
1860 for (ColIndex col(0); col < num_cols; ++col) {
1861 columns_to_process.push_back(col);
1862 }
1863
1864 // Arbitrary limit to avoid corner cases with long loops.
1865 // TODO(user): expose this as a parameter? IMO it isn't really needed as we
1866 // shouldn't reach this limit except in corner cases.
1867 const int limit = 5 * num_cols.value();
1868 for (int count = 0; !columns_to_process.empty() && count < limit; ++count) {
1869 const ColIndex col = columns_to_process.front();
1870 columns_to_process.pop_front();
1871 in_columns_to_process[col] = false;
1872 if (column_deletion_helper_.IsColumnMarked(col)) continue;
1873
1874 const SparseColumn& column = lp->GetSparseColumn(col);
1875 const Fractional col_cost =
1876 lp->GetObjectiveCoefficientForMinimizationVersion(col);
1877 const Fractional col_lb = lp->variable_lower_bounds()[col];
1878 const Fractional col_ub = lp->variable_upper_bounds()[col];
1879
1880 // Compute the bounds on the reduced costs of this column.
1883 rc_lb.Add(col_cost);
1884 rc_ub.Add(col_cost);
1885 for (const SparseColumn::Entry e : column) {
1886 if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1887 const Fractional coeff = e.coefficient();
1888 if (coeff > 0.0) {
1889 rc_lb.Add(-coeff * dual_ub_[e.row()]);
1890 rc_ub.Add(-coeff * dual_lb_[e.row()]);
1891 } else {
1892 rc_lb.Add(-coeff * dual_lb_[e.row()]);
1893 rc_ub.Add(-coeff * dual_ub_[e.row()]);
1894 }
1895 }
1896
1897 // If the reduced cost domain do not contain zero (modulo the tolerance), we
1898 // can move the variable to its corresponding bound. Note that we need to be
1899 // careful that this variable didn't participate in creating the used
1900 // reduced cost bound in the first place.
1901 bool can_be_removed = false;
1903 bool rc_is_away_from_zero;
1904 if (rc_ub.Sum() <= low_tolerance) {
1905 can_be_removed = true;
1906 target_bound = col_ub;
1907 if (in_mip_context_ && lp->IsVariableInteger(col)) {
1908 target_bound = std::floor(target_bound + high_tolerance);
1909 }
1910
1911 rc_is_away_from_zero = rc_ub.Sum() <= -high_tolerance;
1912 can_be_removed = !may_have_participated_ub_[col];
1913 }
1914 if (rc_lb.Sum() >= -low_tolerance) {
1915 // The second condition is here for the case we can choose one of the two
1916 // directions.
1917 if (!can_be_removed || !IsFinite(target_bound)) {
1918 can_be_removed = true;
1919 target_bound = col_lb;
1920 if (in_mip_context_ && lp->IsVariableInteger(col)) {
1921 target_bound = std::ceil(target_bound - high_tolerance);
1922 }
1923
1924 rc_is_away_from_zero = rc_lb.Sum() >= high_tolerance;
1925 can_be_removed = !may_have_participated_lb_[col];
1926 }
1927 }
1928
1929 if (can_be_removed) {
1930 if (IsFinite(target_bound)) {
1931 // Note that in MIP context, this assumes that the bounds of an integer
1932 // variable are integer.
1933 column_deletion_helper_.MarkColumnForDeletionWithState(
1935 ComputeVariableStatus(target_bound, col_lb, col_ub));
1936 continue;
1937 }
1938
1939 // If the target bound is infinite and the reduced cost bound is non-zero,
1940 // then the problem is ProblemStatus::INFEASIBLE_OR_UNBOUNDED.
1941 if (rc_is_away_from_zero) {
1942 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, variable " << col
1943 << " can move to " << target_bound
1944 << " and its reduced cost is in [" << rc_lb.Sum() << ", "
1945 << rc_ub.Sum() << "]";
1947 return false;
1948 } else {
1949 // We can remove this column and all its constraints! We just need to
1950 // choose proper variable values during the call to RecoverSolution()
1951 // that make all the constraints satisfiable. Unfortunately, this is not
1952 // so easy to do in the general case, so we only deal with a simpler
1953 // case when the cost of the variable is zero, and none of the
1954 // constraints (even the deleted one) block the variable moving to its
1955 // infinite target_bound.
1956 //
1957 // TODO(user): deal with the more generic case.
1958 if (col_cost != 0.0) continue;
1959
1960 const double sign_correction = (target_bound == kInfinity) ? 1.0 : -1.0;
1961 bool skip = false;
1962 for (const SparseColumn::Entry e : column) {
1963 // Note that it is important to check the rows that are already
1964 // deleted here, otherwise the post-solve will not work.
1965 if (IsConstraintBlockingVariable(
1966 *lp, sign_correction * e.coefficient(), e.row())) {
1967 skip = true;
1968 break;
1969 }
1970 }
1971 if (skip) continue;
1972
1973 // TODO(user): this also works if the variable is integer, but we must
1974 // choose an integer value during the post-solve. Implement this.
1975 if (in_mip_context_) continue;
1977 continue;
1978 }
1979 }
1980
1981 // The rest of the code will update the dual bounds. There is no need to do
1982 // it if the column was removed or if it is not unconstrained in some
1983 // direction.
1984 DCHECK(!can_be_removed);
1985 if (col_lb != -kInfinity && col_ub != kInfinity) continue;
1986
1987 // For MIP, we only exploit the constraints. TODO(user): It should probably
1988 // work with only small modification, investigate.
1989 if (in_mip_context_) continue;
1990
1991 changed_rows.clear();
1992 for (const SparseColumn::Entry e : column) {
1993 if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1994 const Fractional c = e.coefficient();
1995 const RowIndex row = e.row();
1996 if (col_ub == kInfinity) {
1997 if (c > 0.0) {
1998 const Fractional candidate =
1999 rc_ub.SumWithoutUb(-c * dual_lb_[row]) / c;
2000 if (candidate < dual_ub_[row]) {
2001 dual_ub_[row] = candidate;
2002 may_have_participated_lb_[col] = true;
2003 changed_rows.push_back(row);
2004 }
2005 } else {
2006 const Fractional candidate =
2007 rc_ub.SumWithoutUb(-c * dual_ub_[row]) / c;
2008 if (candidate > dual_lb_[row]) {
2009 dual_lb_[row] = candidate;
2010 may_have_participated_lb_[col] = true;
2011 changed_rows.push_back(row);
2012 }
2013 }
2014 }
2015 if (col_lb == -kInfinity) {
2016 if (c > 0.0) {
2017 const Fractional candidate =
2018 rc_lb.SumWithoutLb(-c * dual_ub_[row]) / c;
2019 if (candidate > dual_lb_[row]) {
2020 dual_lb_[row] = candidate;
2021 may_have_participated_ub_[col] = true;
2022 changed_rows.push_back(row);
2023 }
2024 } else {
2025 const Fractional candidate =
2026 rc_lb.SumWithoutLb(-c * dual_lb_[row]) / c;
2027 if (candidate < dual_ub_[row]) {
2028 dual_ub_[row] = candidate;
2029 may_have_participated_ub_[col] = true;
2030 changed_rows.push_back(row);
2031 }
2032 }
2033 }
2034 }
2035
2036 if (!changed_rows.empty()) {
2037 const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2038 for (const RowIndex row : changed_rows) {
2039 for (const SparseColumn::Entry entry :
2040 transpose.column(RowToColIndex(row))) {
2041 const ColIndex col = RowToColIndex(entry.row());
2042 if (!in_columns_to_process[col]) {
2043 columns_to_process.push_back(col);
2044 in_columns_to_process[col] = true;
2045 }
2046 }
2047 }
2048 }
2049 }
2050
2051 // Change the rhs to reflect the fixed variables. Note that is important to do
2052 // that after all the calls to RemoveZeroCostUnconstrainedVariable() because
2053 // RemoveZeroCostUnconstrainedVariable() needs to store the rhs before this
2054 // modification!
2055 const ColIndex end = column_deletion_helper_.GetMarkedColumns().size();
2056 for (ColIndex col(0); col < end; ++col) {
2057 if (column_deletion_helper_.IsColumnMarked(col)) {
2058 const Fractional target_bound =
2059 column_deletion_helper_.GetStoredValue()[col];
2060 SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
2061 }
2062 }
2063
2064 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2065 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2066 return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2067}
2068
2070 ProblemSolution* solution) const {
2073 column_deletion_helper_.RestoreDeletedColumns(solution);
2074 row_deletion_helper_.RestoreDeletedRows(solution);
2075
2076 struct DeletionEntry {
2077 RowIndex row;
2078 ColIndex col;
2080 };
2081 std::vector<DeletionEntry> entries;
2082
2083 // Compute the last deleted column index for each deleted rows.
2084 const RowIndex num_rows = solution->dual_values.size();
2085 RowToColMapping last_deleted_column(num_rows, kInvalidCol);
2086 for (RowIndex row(0); row < num_rows; ++row) {
2087 if (!row_deletion_helper_.IsRowMarked(row)) continue;
2088
2089 ColIndex last_col = kInvalidCol;
2090 Fractional last_coefficient;
2091 for (const SparseColumn::Entry e :
2092 rows_saver_.SavedColumn(RowToColIndex(row))) {
2093 const ColIndex col = RowToColIndex(e.row());
2094 if (is_unbounded_[col]) {
2095 last_col = col;
2096 last_coefficient = e.coefficient();
2097 }
2098 }
2099 if (last_col != kInvalidCol) {
2100 entries.push_back({row, last_col, last_coefficient});
2101 }
2102 }
2103
2104 // Sort by col first and then row.
2105 std::sort(entries.begin(), entries.end(),
2106 [](const DeletionEntry& a, const DeletionEntry& b) {
2107 if (a.col == b.col) return a.row < b.row;
2108 return a.col < b.col;
2109 });
2110
2111 // Note that this will be empty if there were no deleted rows.
2112 for (int i = 0; i < entries.size();) {
2113 const ColIndex col = entries[i].col;
2114 CHECK(is_unbounded_[col]);
2115
2116 Fractional primal_value_shift = 0.0;
2117 RowIndex row_at_bound = kInvalidRow;
2118 for (; i < entries.size(); ++i) {
2119 if (entries[i].col != col) break;
2120 const RowIndex row = entries[i].row;
2121
2122 // This is for VariableStatus::FREE rows.
2123 //
2124 // TODO(user): In presence of free row, we must move them to 0.
2125 // Note that currently VariableStatus::FREE rows should be removed before
2126 // this is called.
2127 DCHECK(IsFinite(rhs_[row]));
2128 if (!IsFinite(rhs_[row])) continue;
2129
2130 const SparseColumn& row_as_column =
2131 rows_saver_.SavedColumn(RowToColIndex(row));
2132 const Fractional activity =
2133 rhs_[row] - ScalarProduct(solution->primal_values, row_as_column);
2134
2135 // activity and sign correction must have the same sign or be zero. If
2136 // not, we find the first unbounded variable and change it accordingly.
2137 // Note that by construction, the variable value will move towards its
2138 // unbounded direction.
2139 if (activity * activity_sign_correction_[row] < 0.0) {
2140 const Fractional bound = activity / entries[i].coefficient;
2141 if (std::abs(bound) > std::abs(primal_value_shift)) {
2142 primal_value_shift = bound;
2143 row_at_bound = row;
2144 }
2145 }
2146 }
2147 solution->primal_values[col] += primal_value_shift;
2148 if (row_at_bound != kInvalidRow) {
2149 solution->variable_statuses[col] = VariableStatus::BASIC;
2150 solution->constraint_statuses[row_at_bound] =
2151 activity_sign_correction_[row_at_bound] == 1.0
2154 }
2155 }
2156}
2157
2158// --------------------------------------------------------
2159// FreeConstraintPreprocessor
2160// --------------------------------------------------------
2161
2162bool FreeConstraintPreprocessor::Run(LinearProgram* lp) {
2164 RETURN_VALUE_IF_NULL(lp, false);
2165 const RowIndex num_rows = lp->num_constraints();
2166 for (RowIndex row(0); row < num_rows; ++row) {
2167 const Fractional lower_bound = lp->constraint_lower_bounds()[row];
2168 const Fractional upper_bound = lp->constraint_upper_bounds()[row];
2169 if (lower_bound == -kInfinity && upper_bound == kInfinity) {
2170 row_deletion_helper_.MarkRowForDeletion(row);
2171 }
2172 }
2173 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2174 return !row_deletion_helper_.IsEmpty();
2175}
2176
2178 ProblemSolution* solution) const {
2181 row_deletion_helper_.RestoreDeletedRows(solution);
2182}
2183
2184// --------------------------------------------------------
2185// EmptyConstraintPreprocessor
2186// --------------------------------------------------------
2187
2190 RETURN_VALUE_IF_NULL(lp, false);
2191 const RowIndex num_rows(lp->num_constraints());
2192 const ColIndex num_cols(lp->num_variables());
2193
2194 // Compute degree.
2195 StrictITIVector<RowIndex, int> degree(num_rows, 0);
2196 for (ColIndex col(0); col < num_cols; ++col) {
2197 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
2198 ++degree[e.row()];
2199 }
2200 }
2201
2202 // Delete degree 0 rows.
2203 for (RowIndex row(0); row < num_rows; ++row) {
2204 if (degree[row] == 0) {
2205 // We need to check that 0.0 is allowed by the constraint bounds,
2206 // otherwise, the problem is ProblemStatus::PRIMAL_INFEASIBLE.
2208 lp->constraint_lower_bounds()[row], 0) ||
2210 0, lp->constraint_upper_bounds()[row])) {
2211 VLOG(1) << "Problem PRIMAL_INFEASIBLE, constraint " << row
2212 << " is empty and its range ["
2213 << lp->constraint_lower_bounds()[row] << ","
2214 << lp->constraint_upper_bounds()[row] << "] doesn't contain 0.";
2216 return false;
2217 }
2218 row_deletion_helper_.MarkRowForDeletion(row);
2219 }
2220 }
2221 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2222 return !row_deletion_helper_.IsEmpty();
2223}
2224
2226 ProblemSolution* solution) const {
2229 row_deletion_helper_.RestoreDeletedRows(solution);
2230}
2231
2232// --------------------------------------------------------
2233// SingletonPreprocessor
2234// --------------------------------------------------------
2235
2236SingletonUndo::SingletonUndo(OperationType type, const LinearProgram& lp,
2237 MatrixEntry e, ConstraintStatus status)
2238 : type_(type),
2239 is_maximization_(lp.IsMaximizationProblem()),
2240 e_(e),
2241 cost_(lp.objective_coefficients()[e.col]),
2242 variable_lower_bound_(lp.variable_lower_bounds()[e.col]),
2243 variable_upper_bound_(lp.variable_upper_bounds()[e.col]),
2244 constraint_lower_bound_(lp.constraint_lower_bounds()[e.row]),
2245 constraint_upper_bound_(lp.constraint_upper_bounds()[e.row]),
2246 constraint_status_(status) {}
2247
2248void SingletonUndo::Undo(const GlopParameters& parameters,
2249 const SparseColumn& saved_column,
2250 const SparseColumn& saved_row,
2251 ProblemSolution* solution) const {
2252 switch (type_) {
2254 SingletonRowUndo(saved_column, solution);
2255 break;
2257 ZeroCostSingletonColumnUndo(parameters, saved_row, solution);
2258 break;
2260 SingletonColumnInEqualityUndo(parameters, saved_row, solution);
2261 break;
2263 MakeConstraintAnEqualityUndo(solution);
2264 break;
2265 }
2267
2268void SingletonPreprocessor::DeleteSingletonRow(MatrixEntry e,
2269 LinearProgram* lp) {
2270 Fractional implied_lower_bound =
2271 lp->constraint_lower_bounds()[e.row] / e.coeff;
2272 Fractional implied_upper_bound =
2273 lp->constraint_upper_bounds()[e.row] / e.coeff;
2274 if (e.coeff < 0.0) {
2275 std::swap(implied_lower_bound, implied_upper_bound);
2276 }
2277
2278 const Fractional old_lower_bound = lp->variable_lower_bounds()[e.col];
2279 const Fractional old_upper_bound = lp->variable_upper_bounds()[e.col];
2280
2281 const Fractional potential_error =
2282 std::abs(parameters_.preprocessor_zero_tolerance() / e.coeff);
2283 Fractional new_lower_bound =
2284 implied_lower_bound - potential_error > old_lower_bound
2285 ? implied_lower_bound
2286 : old_lower_bound;
2287 Fractional new_upper_bound =
2288 implied_upper_bound + potential_error < old_upper_bound
2289 ? implied_upper_bound
2290 : old_upper_bound;
2291
2292 // This can happen if we ask for 1e-300 * x to be >= 1e9.
2293 if (new_upper_bound == -kInfinity || new_lower_bound == kInfinity) {
2294 VLOG(1) << "Problem ProblemStatus::PRIMAL_INFEASIBLE, singleton "
2295 "row causes the bound of the variable "
2296 << e.col << " to go to infinity.";
2298 return;
2299 }
2300
2301 if (new_upper_bound < new_lower_bound) {
2302 if (!IsSmallerWithinFeasibilityTolerance(new_lower_bound,
2303 new_upper_bound)) {
2304 VLOG(1) << "Problem ProblemStatus::PRIMAL_INFEASIBLE, singleton "
2305 "row causes the bound of the variable "
2306 << e.col << " to be infeasible by "
2307 << new_lower_bound - new_upper_bound;
2309 return;
2310 }
2311
2312 // Otherwise, fix the variable to one of its bounds.
2313 if (new_lower_bound == lp->variable_lower_bounds()[e.col]) {
2314 new_upper_bound = new_lower_bound;
2315 }
2316 if (new_upper_bound == lp->variable_upper_bounds()[e.col]) {
2317 new_lower_bound = new_upper_bound;
2318 }
2319
2320 // When both new bounds are coming from the constraint and are crossing, it
2321 // means the constraint bounds where originally crossing too. We arbitrarily
2322 // choose one of the bound in this case.
2323 //
2324 // TODO(user): The code in this file shouldn't create crossing bounds at
2325 // any point, so we could decide which bound to use directly on the user
2326 // given problem before running any presolve.
2327 new_upper_bound = new_lower_bound;
2328 }
2329 row_deletion_helper_.MarkRowForDeletion(e.row);
2330 undo_stack_.push_back(SingletonUndo(SingletonUndo::SINGLETON_ROW, *lp, e,
2332 columns_saver_.SaveColumnIfNotAlreadyDone(e.col, lp->GetSparseColumn(e.col));
2333
2334 lp->SetVariableBounds(e.col, new_lower_bound, new_upper_bound);
2335}
2336
2337// The dual value of the row needs to be corrected to stay at the optimal.
2338void SingletonUndo::SingletonRowUndo(const SparseColumn& saved_column,
2339 ProblemSolution* solution) const {
2340 DCHECK_EQ(0, solution->dual_values[e_.row]);
2341
2342 // If the variable is basic or free, we can just keep the constraint
2343 // VariableStatus::BASIC and 0.0 as the dual value.
2344 const VariableStatus status = solution->variable_statuses[e_.col];
2346
2347 // Compute whether or not the variable bounds changed.
2348 Fractional implied_lower_bound = constraint_lower_bound_ / e_.coeff;
2349 Fractional implied_upper_bound = constraint_upper_bound_ / e_.coeff;
2350 if (e_.coeff < 0.0) {
2351 std::swap(implied_lower_bound, implied_upper_bound);
2352 }
2353 const bool lower_bound_changed = implied_lower_bound > variable_lower_bound_;
2354 const bool upper_bound_changed = implied_upper_bound < variable_upper_bound_;
2355
2356 if (!lower_bound_changed && !upper_bound_changed) return;
2357 if (status == VariableStatus::AT_LOWER_BOUND && !lower_bound_changed) return;
2358 if (status == VariableStatus::AT_UPPER_BOUND && !upper_bound_changed) return;
2359
2360 // This is the reduced cost of the variable before the singleton constraint is
2361 // added back.
2362 const Fractional reduced_cost =
2363 cost_ - ScalarProduct(solution->dual_values, saved_column);
2364 const Fractional reduced_cost_for_minimization =
2365 is_maximization_ ? -reduced_cost : reduced_cost;
2366
2368 DCHECK(lower_bound_changed || upper_bound_changed);
2369 if (reduced_cost_for_minimization >= 0.0 && !lower_bound_changed) {
2370 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2371 return;
2372 }
2373 if (reduced_cost_for_minimization <= 0.0 && !upper_bound_changed) {
2374 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2375 return;
2376 }
2377 }
2378
2379 // If one of the variable bounds changes, and the variable is no longer at one
2380 // of its bounds, then its reduced cost needs to be set to 0.0 and the
2381 // variable becomes a basic variable. This is what the line below do, since
2382 // the new reduced cost of the variable will be equal to:
2383 // old_reduced_cost - coeff * solution->dual_values[row]
2384 //
2385 // TODO(user): This code is broken for integer variable.
2386 // Say our singleton row is 2 * y <= 5, and y was at its implied bound y = 2
2387 // at postsolve. The problem is that we can end up with an AT_UPPER_BOUND
2388 // status for the constraint 2 * y <= 5 which is not correct since the
2389 // activity is 4, and that break later preconditions. Maybe there is a way to
2390 // fix everything, but it seems tough to be sure.
2391 solution->dual_values[e_.row] = reduced_cost / e_.coeff;
2392 ConstraintStatus new_constraint_status = VariableToConstraintStatus(status);
2394 (!lower_bound_changed || !upper_bound_changed)) {
2395 new_constraint_status = lower_bound_changed
2398 }
2399 if (e_.coeff < 0.0) {
2400 if (new_constraint_status == ConstraintStatus::AT_LOWER_BOUND) {
2401 new_constraint_status = ConstraintStatus::AT_UPPER_BOUND;
2402 } else if (new_constraint_status == ConstraintStatus::AT_UPPER_BOUND) {
2403 new_constraint_status = ConstraintStatus::AT_LOWER_BOUND;
2404 }
2405 }
2406 solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2407 solution->constraint_statuses[e_.row] = new_constraint_status;
2408}
2409
2410void SingletonPreprocessor::UpdateConstraintBoundsWithVariableBounds(
2411 MatrixEntry e, LinearProgram* lp) {
2412 Fractional lower_delta = -e.coeff * lp->variable_upper_bounds()[e.col];
2413 Fractional upper_delta = -e.coeff * lp->variable_lower_bounds()[e.col];
2414 if (e.coeff < 0.0) {
2415 std::swap(lower_delta, upper_delta);
2416 }
2417 lp->SetConstraintBounds(e.row,
2418 lp->constraint_lower_bounds()[e.row] + lower_delta,
2419 lp->constraint_upper_bounds()[e.row] + upper_delta);
2420}
2421
2422bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
2423 const MatrixEntry& matrix_entry, const LinearProgram& lp) const {
2424 DCHECK(in_mip_context_);
2425 DCHECK(lp.IsVariableInteger(matrix_entry.col));
2426 const SparseMatrix& transpose = lp.GetTransposeSparseMatrix();
2427 for (const SparseColumn::Entry entry :
2428 transpose.column(RowToColIndex(matrix_entry.row))) {
2429 // Check if the variable is integer.
2430 if (!lp.IsVariableInteger(RowToColIndex(entry.row()))) {
2431 return false;
2432 }
2433
2434 const Fractional coefficient = entry.coefficient();
2435 const Fractional coefficient_ratio = coefficient / matrix_entry.coeff;
2436 // Check if coefficient_ratio is integer.
2438 coefficient_ratio, parameters_.solution_feasibility_tolerance())) {
2439 return false;
2440 }
2441 }
2442 const Fractional constraint_lb =
2443 lp.constraint_lower_bounds()[matrix_entry.row];
2444 if (IsFinite(constraint_lb)) {
2445 const Fractional lower_bound_ratio = constraint_lb / matrix_entry.coeff;
2447 lower_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2448 return false;
2449 }
2450 }
2451 const Fractional constraint_ub =
2452 lp.constraint_upper_bounds()[matrix_entry.row];
2453 if (IsFinite(constraint_ub)) {
2454 const Fractional upper_bound_ratio = constraint_ub / matrix_entry.coeff;
2456 upper_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2457 return false;
2458 }
2459 }
2460 return true;
2461}
2462
2463void SingletonPreprocessor::DeleteZeroCostSingletonColumn(
2464 const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2465 const ColIndex transpose_col = RowToColIndex(e.row);
2466 undo_stack_.push_back(SingletonUndo(SingletonUndo::ZERO_COST_SINGLETON_COLUMN,
2467 *lp, e, ConstraintStatus::FREE));
2468 const SparseColumn& row_as_col = transpose.column(transpose_col);
2469 rows_saver_.SaveColumnIfNotAlreadyDone(RowToColIndex(e.row), row_as_col);
2470 UpdateConstraintBoundsWithVariableBounds(e, lp);
2471 column_deletion_helper_.MarkColumnForDeletion(e.col);
2472}
2473
2474// We need to restore the variable value in order to satisfy the constraint.
2475void SingletonUndo::ZeroCostSingletonColumnUndo(
2476 const GlopParameters& parameters, const SparseColumn& saved_row,
2477 ProblemSolution* solution) const {
2478 // If the variable was fixed, this is easy. Note that this is the only
2479 // possible case if the current constraint status is FIXED, except if the
2480 // variable bounds are small compared to the constraint bounds, like adding
2481 // 1e-100 to a fixed == 1 constraint.
2482 if (variable_upper_bound_ == variable_lower_bound_) {
2483 solution->primal_values[e_.col] = variable_lower_bound_;
2484 solution->variable_statuses[e_.col] = VariableStatus::FIXED_VALUE;
2485 return;
2486 }
2487
2488 const ConstraintStatus ct_status = solution->constraint_statuses[e_.row];
2489 if (ct_status == ConstraintStatus::FIXED_VALUE) {
2490 const Fractional corrected_dual = is_maximization_
2491 ? -solution->dual_values[e_.row]
2492 : solution->dual_values[e_.row];
2493 if (corrected_dual > 0) {
2494 DCHECK(IsFinite(variable_lower_bound_));
2495 solution->primal_values[e_.col] = variable_lower_bound_;
2496 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2497 } else {
2498 DCHECK(IsFinite(variable_upper_bound_));
2499 solution->primal_values[e_.col] = variable_upper_bound_;
2500 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2501 }
2502 return;
2503 } else if (ct_status == ConstraintStatus::AT_LOWER_BOUND ||
2504 ct_status == ConstraintStatus::AT_UPPER_BOUND) {
2505 if ((ct_status == ConstraintStatus::AT_UPPER_BOUND && e_.coeff > 0.0) ||
2506 (ct_status == ConstraintStatus::AT_LOWER_BOUND && e_.coeff < 0.0)) {
2507 DCHECK(IsFinite(variable_lower_bound_));
2508 solution->primal_values[e_.col] = variable_lower_bound_;
2509 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2510 } else {
2511 DCHECK(IsFinite(variable_upper_bound_));
2512 solution->primal_values[e_.col] = variable_upper_bound_;
2513 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2514 }
2515 if (constraint_upper_bound_ == constraint_lower_bound_) {
2516 solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2517 }
2518 return;
2519 }
2520
2521 // This is the activity of the constraint before the singleton variable is
2522 // added back to it.
2523 const Fractional activity = ScalarProduct(solution->primal_values, saved_row);
2524
2525 // First we try to fix the variable at its lower or upper bound and leave the
2526 // constraint VariableStatus::BASIC. Note that we use the same logic as in
2527 // Preprocessor::IsSmallerWithinPreprocessorZeroTolerance() which we can't use
2528 // here because we are not deriving from the Preprocessor class.
2529 const Fractional tolerance = parameters.preprocessor_zero_tolerance();
2530 const auto is_smaller_with_tolerance = [tolerance](Fractional a,
2531 Fractional b) {
2532 return ::operations_research::IsSmallerWithinTolerance(a, b, tolerance);
2533 };
2534 if (variable_lower_bound_ != -kInfinity) {
2535 const Fractional activity_at_lb =
2536 activity + e_.coeff * variable_lower_bound_;
2537 if (is_smaller_with_tolerance(constraint_lower_bound_, activity_at_lb) &&
2538 is_smaller_with_tolerance(activity_at_lb, constraint_upper_bound_)) {
2539 solution->primal_values[e_.col] = variable_lower_bound_;
2540 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2541 return;
2542 }
2543 }
2544 if (variable_upper_bound_ != kInfinity) {
2545 const Fractional activity_at_ub =
2546 activity + e_.coeff * variable_upper_bound_;
2547 if (is_smaller_with_tolerance(constraint_lower_bound_, activity_at_ub) &&
2548 is_smaller_with_tolerance(activity_at_ub, constraint_upper_bound_)) {
2549 solution->primal_values[e_.col] = variable_upper_bound_;
2550 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2551 return;
2552 }
2553 }
2554
2555 // If the current constraint is UNBOUNDED, then the variable is too
2556 // because of the two cases above. We just set its status to
2557 // VariableStatus::FREE.
2558 if (constraint_lower_bound_ == -kInfinity &&
2559 constraint_upper_bound_ == kInfinity) {
2560 solution->primal_values[e_.col] = 0.0;
2561 solution->variable_statuses[e_.col] = VariableStatus::FREE;
2562 return;
2563 }
2564
2565 // If the previous cases didn't apply, the constraint will be fixed to its
2566 // bounds and the variable will be made VariableStatus::BASIC.
2567 solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2568 if (constraint_lower_bound_ == constraint_upper_bound_) {
2569 solution->primal_values[e_.col] =
2570 (constraint_lower_bound_ - activity) / e_.coeff;
2571 solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2572 return;
2573 }
2574
2575 bool set_constraint_to_lower_bound;
2576 if (constraint_lower_bound_ == -kInfinity) {
2577 set_constraint_to_lower_bound = false;
2578 } else if (constraint_upper_bound_ == kInfinity) {
2579 set_constraint_to_lower_bound = true;
2580 } else {
2581 // In this case we select the value that is the most inside the variable
2582 // bound.
2583 const Fractional to_lb = (constraint_lower_bound_ - activity) / e_.coeff;
2584 const Fractional to_ub = (constraint_upper_bound_ - activity) / e_.coeff;
2585 set_constraint_to_lower_bound =
2586 std::max(variable_lower_bound_ - to_lb, to_lb - variable_upper_bound_) <
2587 std::max(variable_lower_bound_ - to_ub, to_ub - variable_upper_bound_);
2588 }
2589
2590 if (set_constraint_to_lower_bound) {
2591 solution->primal_values[e_.col] =
2592 (constraint_lower_bound_ - activity) / e_.coeff;
2593 solution->constraint_statuses[e_.row] = ConstraintStatus::AT_LOWER_BOUND;
2594 } else {
2595 solution->primal_values[e_.col] =
2596 (constraint_upper_bound_ - activity) / e_.coeff;
2597 solution->constraint_statuses[e_.row] = ConstraintStatus::AT_UPPER_BOUND;
2598 }
2599}
2600
2601void SingletonPreprocessor::DeleteSingletonColumnInEquality(
2602 const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2603 // Save information for the undo.
2604 const ColIndex transpose_col = RowToColIndex(e.row);
2605 const SparseColumn& row_as_column = transpose.column(transpose_col);
2606 undo_stack_.push_back(
2607 SingletonUndo(SingletonUndo::SINGLETON_COLUMN_IN_EQUALITY, *lp, e,
2609 rows_saver_.SaveColumnIfNotAlreadyDone(RowToColIndex(e.row), row_as_column);
2610
2611 // Update the objective function using the equality constraint. We have
2612 // v_col*coeff + expression = rhs,
2613 // so the contribution of this variable to the cost function (v_col * cost)
2614 // can be rewritten as:
2615 // (rhs * cost - expression * cost) / coeff.
2616 const Fractional rhs = lp->constraint_upper_bounds()[e.row];
2617 const Fractional cost = lp->objective_coefficients()[e.col];
2618 const Fractional multiplier = cost / e.coeff;
2619 lp->SetObjectiveOffset(lp->objective_offset() + rhs * multiplier);
2620 for (const SparseColumn::Entry e : row_as_column) {
2621 const ColIndex col = RowToColIndex(e.row());
2622 if (!column_deletion_helper_.IsColumnMarked(col)) {
2623 Fractional new_cost =
2624 lp->objective_coefficients()[col] - e.coefficient() * multiplier;
2625
2626 // TODO(user): It is important to avoid having non-zero costs which are
2627 // the result of numerical error. This is because we still miss some
2628 // tolerances in a few preprocessors. Like an empty column with a cost of
2629 // 1e-17 and unbounded towards infinity is currently implying that the
2630 // problem is unbounded. This will need fixing.
2631 if (std::abs(new_cost) < parameters_.preprocessor_zero_tolerance()) {
2632 new_cost = 0.0;
2633 }
2634 lp->SetObjectiveCoefficient(col, new_cost);
2635 }
2636 }
2637
2638 // Now delete the column like a singleton column without cost.
2639 UpdateConstraintBoundsWithVariableBounds(e, lp);
2640 column_deletion_helper_.MarkColumnForDeletion(e.col);
2641}
2642
2643void SingletonUndo::SingletonColumnInEqualityUndo(
2644 const GlopParameters& parameters, const SparseColumn& saved_row,
2645 ProblemSolution* solution) const {
2646 // First do the same as a zero-cost singleton column.
2647 ZeroCostSingletonColumnUndo(parameters, saved_row, solution);
2648
2649 // Then, restore the dual optimal value taking into account the cost
2650 // modification.
2651 solution->dual_values[e_.row] += cost_ / e_.coeff;
2652 if (solution->constraint_statuses[e_.row] == ConstraintStatus::BASIC) {
2653 solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2654 solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2655 }
2656}
2657
2658void SingletonUndo::MakeConstraintAnEqualityUndo(
2659 ProblemSolution* solution) const {
2660 if (solution->constraint_statuses[e_.row] == ConstraintStatus::FIXED_VALUE) {
2661 solution->constraint_statuses[e_.row] = constraint_status_;
2662 }
2663}
2664
2665bool SingletonPreprocessor::MakeConstraintAnEqualityIfPossible(
2666 const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2667 // TODO(user): We could skip early if the relevant constraint bound is
2668 // infinity.
2669 const Fractional cst_lower_bound = lp->constraint_lower_bounds()[e.row];
2670 const Fractional cst_upper_bound = lp->constraint_upper_bounds()[e.row];
2671 if (cst_lower_bound == cst_upper_bound) return true;
2672 if (cst_lower_bound == -kInfinity && cst_upper_bound == kInfinity) {
2673 return false;
2674 }
2675
2676 // The code below do not work as is for integer variable.
2677 if (in_mip_context_ && lp->IsVariableInteger(e.col)) return false;
2678
2679 // To be efficient, we only process a row once and cache the domain that an
2680 // "artificial" extra variable x with coefficient 1.0 could take while still
2681 // making the constraint feasible. The domain bounds for the constraint e.row
2682 // will be stored in row_lb_sum_[e.row] and row_ub_sum_[e.row].
2683 const DenseRow& variable_ubs = lp->variable_upper_bounds();
2684 const DenseRow& variable_lbs = lp->variable_lower_bounds();
2685 if (e.row >= row_sum_is_cached_.size() || !row_sum_is_cached_[e.row]) {
2686 if (e.row >= row_sum_is_cached_.size()) {
2687 const int new_size = e.row.value() + 1;
2688 row_sum_is_cached_.resize(new_size);
2689 row_lb_sum_.resize(new_size);
2690 row_ub_sum_.resize(new_size);
2691 }
2692 row_sum_is_cached_[e.row] = true;
2693 row_lb_sum_[e.row].Add(cst_lower_bound);
2694 row_ub_sum_[e.row].Add(cst_upper_bound);
2695 for (const SparseColumn::Entry entry :
2696 transpose.column(RowToColIndex(e.row))) {
2697 const ColIndex row_as_col = RowToColIndex(entry.row());
2698
2699 // Tricky: Even if later more columns are deleted, these "cached" sums
2700 // will actually still be valid because we only delete columns in a
2701 // compatible way.
2702 //
2703 // TODO(user): Find a more robust way? it seems easy to add new deletion
2704 // rules that may break this assumption.
2705 if (column_deletion_helper_.IsColumnMarked(row_as_col)) continue;
2706 if (entry.coefficient() > 0.0) {
2707 row_lb_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2708 row_ub_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2709 } else {
2710 row_lb_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2711 row_ub_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2712 }
2713
2714 // TODO(user): Abort early if both sums contain more than 1 infinity?
2715 }
2716 }
2717
2718 // Now that the lb/ub sum for the row is cached, we can use it to compute the
2719 // implied bounds on the variable from this constraint and the other
2720 // variables.
2721 const Fractional c = e.coeff;
2722 const Fractional lb =
2723 c > 0.0 ? row_lb_sum_[e.row].SumWithoutLb(-c * variable_ubs[e.col]) / c
2724 : row_ub_sum_[e.row].SumWithoutUb(-c * variable_ubs[e.col]) / c;
2725 const Fractional ub =
2726 c > 0.0 ? row_ub_sum_[e.row].SumWithoutUb(-c * variable_lbs[e.col]) / c
2727 : row_lb_sum_[e.row].SumWithoutLb(-c * variable_lbs[e.col]) / c;
2728
2729 // Note that we could do the same for singleton variables with a cost of
2730 // 0.0, but such variable are already dealt with by
2731 // DeleteZeroCostSingletonColumn() so there is no point.
2732 const Fractional cost =
2733 lp->GetObjectiveCoefficientForMinimizationVersion(e.col);
2734 DCHECK_NE(cost, 0.0);
2735
2736 // Note that some of the tests below will be always true if the bounds of
2737 // the column of index col are infinite. This is the desired behavior.
2740 ub, lp->variable_upper_bounds()[e.col])) {
2741 if (e.coeff > 0) {
2742 if (cst_upper_bound == kInfinity) {
2744 } else {
2745 relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2746 lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2747 }
2748 } else {
2749 if (cst_lower_bound == -kInfinity) {
2751 } else {
2752 relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2753 lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2754 }
2755 }
2756
2758 VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2759 "variable "
2760 << e.col << " has a cost (for minimization) of " << cost
2761 << " and is unbounded towards kInfinity.";
2762 DCHECK_EQ(ub, kInfinity);
2763 return false;
2764 }
2765
2766 // This is important but tricky: The upper bound of the variable needs to
2767 // be relaxed. This is valid because the implied bound is lower than the
2768 // original upper bound here. This is needed, so that the optimal
2769 // primal/dual values of the new problem will also be optimal of the
2770 // original one.
2771 //
2772 // Let's prove the case coeff > 0.0 for a minimization problem. In the new
2773 // problem, because the variable is unbounded towards +infinity, its
2774 // reduced cost must satisfy at optimality rc = cost - coeff * dual_v >=
2775 // 0. But this implies dual_v <= cost / coeff <= 0. This is exactly what
2776 // is needed for the optimality of the initial problem since the
2777 // constraint will be at its upper bound, and the corresponding slack
2778 // condition is that the dual value needs to be <= 0.
2779 lp->SetVariableBounds(e.col, lp->variable_lower_bounds()[e.col], kInfinity);
2780 }
2782 lp->variable_lower_bounds()[e.col], lb)) {
2783 if (e.coeff > 0) {
2784 if (cst_lower_bound == -kInfinity) {
2786 } else {
2787 relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2788 lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2789 }
2790 } else {
2791 if (cst_upper_bound == kInfinity) {
2793 } else {
2794 relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2795 lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2796 }
2797 }
2798
2800 DCHECK_EQ(lb, -kInfinity);
2801 VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2802 "variable "
2803 << e.col << " has a cost (for minimization) of " << cost
2804 << " and is unbounded towards -kInfinity.";
2805 return false;
2806 }
2807
2808 // Same remark as above for a lower bounded variable this time.
2809 lp->SetVariableBounds(e.col, -kInfinity,
2810 lp->variable_upper_bounds()[e.col]);
2811 }
2812
2813 if (lp->constraint_lower_bounds()[e.row] ==
2814 lp->constraint_upper_bounds()[e.row]) {
2815 undo_stack_.push_back(SingletonUndo(
2816 SingletonUndo::MAKE_CONSTRAINT_AN_EQUALITY, *lp, e, relaxed_status));
2817 return true;
2818 }
2819 return false;
2820}
2821
2822bool SingletonPreprocessor::Run(LinearProgram* lp) {
2824 RETURN_VALUE_IF_NULL(lp, false);
2825 const SparseMatrix& matrix = lp->GetSparseMatrix();
2826 const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2827
2828 // Initialize column_to_process with the current singleton columns.
2829 ColIndex num_cols(matrix.num_cols());
2830 RowIndex num_rows(matrix.num_rows());
2831 StrictITIVector<ColIndex, EntryIndex> column_degree(num_cols, EntryIndex(0));
2832 std::vector<ColIndex> column_to_process;
2833 for (ColIndex col(0); col < num_cols; ++col) {
2834 column_degree[col] = matrix.column(col).num_entries();
2835 if (column_degree[col] == 1) {
2836 column_to_process.push_back(col);
2837 }
2838 }
2839
2840 // Initialize row_to_process with the current singleton rows.
2841 StrictITIVector<RowIndex, EntryIndex> row_degree(num_rows, EntryIndex(0));
2842 std::vector<RowIndex> row_to_process;
2843 for (RowIndex row(0); row < num_rows; ++row) {
2844 row_degree[row] = transpose.column(RowToColIndex(row)).num_entries();
2845 if (row_degree[row] == 1) {
2846 row_to_process.push_back(row);
2847 }
2848 }
2849
2850 // Process current singleton rows/columns and enqueue new ones.
2851 while (status_ == ProblemStatus::INIT &&
2852 (!column_to_process.empty() || !row_to_process.empty())) {
2853 while (status_ == ProblemStatus::INIT && !column_to_process.empty()) {
2854 const ColIndex col = column_to_process.back();
2855 column_to_process.pop_back();
2856 if (column_degree[col] <= 0) continue;
2857 const MatrixEntry e = GetSingletonColumnMatrixEntry(col, matrix);
2858 if (in_mip_context_ && lp->IsVariableInteger(e.col) &&
2859 !IntegerSingletonColumnIsRemovable(e, *lp)) {
2860 continue;
2861 }
2862
2863 // TODO(user): It seems better to process all the singleton columns with
2864 // a cost of zero first.
2865 if (lp->objective_coefficients()[col] == 0.0) {
2866 DeleteZeroCostSingletonColumn(transpose, e, lp);
2867 } else {
2868 // We don't want to do a substitution if the entry is too small and
2869 // should be probably set to zero.
2870 if (std::abs(e.coeff) < parameters_.preprocessor_zero_tolerance()) {
2871 continue;
2872 }
2873 if (MakeConstraintAnEqualityIfPossible(transpose, e, lp)) {
2874 DeleteSingletonColumnInEquality(transpose, e, lp);
2875 } else {
2876 continue;
2877 }
2878 }
2879 --row_degree[e.row];
2880 if (row_degree[e.row] == 1) {
2881 row_to_process.push_back(e.row);
2882 }
2883 }
2884 while (status_ == ProblemStatus::INIT && !row_to_process.empty()) {
2885 const RowIndex row = row_to_process.back();
2886 row_to_process.pop_back();
2887 if (row_degree[row] <= 0) continue;
2888 const MatrixEntry e = GetSingletonRowMatrixEntry(row, transpose);
2889
2890 // TODO(user): We should be able to restrict the variable bounds with the
2891 // ones of the constraint all the time. However, some situation currently
2892 // break the presolve, and it seems hard to fix in a 100% safe way.
2893 if (in_mip_context_ && lp->IsVariableInteger(e.col) &&
2894 !IntegerSingletonColumnIsRemovable(e, *lp)) {
2895 continue;
2896 }
2897
2898 DeleteSingletonRow(e, lp);
2899 --column_degree[e.col];
2900 if (column_degree[e.col] == 1) {
2901 column_to_process.push_back(e.col);
2902 }
2903 }
2904 }
2905
2906 if (status_ != ProblemStatus::INIT) return false;
2907 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2908 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2909 return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2910}
2911
2912void SingletonPreprocessor::RecoverSolution(ProblemSolution* solution) const {
2915
2916 // Note that the two deletion helpers must restore 0.0 values in the positions
2917 // that will be used during Undo(). That is, all the calls done by this class
2918 // to MarkColumnForDeletion() should be done with 0.0 as the value to restore
2919 // (which is already the case when using MarkRowForDeletion()).
2920 // This is important because the various Undo() functions assume that a
2921 // primal/dual variable value which isn't restored yet has the value of 0.0.
2922 column_deletion_helper_.RestoreDeletedColumns(solution);
2923 row_deletion_helper_.RestoreDeletedRows(solution);
2924
2925 // It is important to undo the operations in the correct order, i.e. in the
2926 // reverse order in which they were done.
2927 for (int i = undo_stack_.size() - 1; i >= 0; --i) {
2928 const SparseColumn& saved_col =
2929 columns_saver_.SavedOrEmptyColumn(undo_stack_[i].Entry().col);
2930 const SparseColumn& saved_row = rows_saver_.SavedOrEmptyColumn(
2931 RowToColIndex(undo_stack_[i].Entry().row));
2932 undo_stack_[i].Undo(parameters_, saved_col, saved_row, solution);
2933 }
2934}
2935
2936MatrixEntry SingletonPreprocessor::GetSingletonColumnMatrixEntry(
2937 ColIndex col, const SparseMatrix& matrix) {
2938 for (const SparseColumn::Entry e : matrix.column(col)) {
2939 if (!row_deletion_helper_.IsRowMarked(e.row())) {
2940 DCHECK_NE(0.0, e.coefficient());
2941 return MatrixEntry(e.row(), col, e.coefficient());
2943 }
2944
2945 // This shouldn't happen.
2946 LOG(DFATAL) << "No unmarked entry in a column that is supposed to have one.";
2948 return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2949}
2950
2951MatrixEntry SingletonPreprocessor::GetSingletonRowMatrixEntry(
2952 RowIndex row, const SparseMatrix& transpose) {
2953 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
2954 const ColIndex col = RowToColIndex(e.row());
2955 if (!column_deletion_helper_.IsColumnMarked(col)) {
2956 DCHECK_NE(0.0, e.coefficient());
2957 return MatrixEntry(row, col, e.coefficient());
2958 }
2959 }
2960
2961 // This shouldn't happen.
2962 LOG(DFATAL) << "No unmarked entry in a row that is supposed to have one.";
2964 return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2965}
2966
2967// --------------------------------------------------------
2968// SingletonColumnSignPreprocessor
2969// --------------------------------------------------------
2970
2971bool SingletonColumnSignPreprocessor::Run(LinearProgram* lp) {
2973 RETURN_VALUE_IF_NULL(lp, false);
2974 const ColIndex num_cols = lp->num_variables();
2975 if (num_cols == 0) return false;
2976
2977 changed_columns_.clear();
2978 int num_singletons = 0;
2979 for (ColIndex col(0); col < num_cols; ++col) {
2980 SparseColumn* sparse_column = lp->GetMutableSparseColumn(col);
2981 const Fractional cost = lp->objective_coefficients()[col];
2982 if (sparse_column->num_entries() == 1) {
2983 ++num_singletons;
2984 }
2985 if (sparse_column->num_entries() == 1 &&
2986 sparse_column->GetFirstCoefficient() < 0) {
2987 sparse_column->MultiplyByConstant(-1.0);
2988 lp->SetVariableBounds(col, -lp->variable_upper_bounds()[col],
2989 -lp->variable_lower_bounds()[col]);
2990 lp->SetObjectiveCoefficient(col, -cost);
2991 changed_columns_.push_back(col);
2992 }
2993 }
2994 VLOG(1) << "Changed the sign of " << changed_columns_.size() << " columns.";
2995 VLOG(1) << num_singletons << " singleton columns left.";
2996 return !changed_columns_.empty();
2997}
2998
3000 ProblemSolution* solution) const {
3003 for (int i = 0; i < changed_columns_.size(); ++i) {
3004 const ColIndex col = changed_columns_[i];
3005 solution->primal_values[col] = -solution->primal_values[col];
3006 const VariableStatus status = solution->variable_statuses[col];
3008 solution->variable_statuses[col] = VariableStatus::AT_LOWER_BOUND;
3009 } else if (status == VariableStatus::AT_LOWER_BOUND) {
3010 solution->variable_statuses[col] = VariableStatus::AT_UPPER_BOUND;
3011 }
3012 }
3013}
3014
3015// --------------------------------------------------------
3016// DoubletonEqualityRowPreprocessor
3017// --------------------------------------------------------
3018
3019bool DoubletonEqualityRowPreprocessor::Run(LinearProgram* lp) {
3021 RETURN_VALUE_IF_NULL(lp, false);
3022
3023 // This is needed at postsolve.
3024 //
3025 // TODO(user): Get rid of the FIXED status instead to avoid spending
3026 // time/memory for no good reason here.
3027 saved_row_lower_bounds_ = lp->constraint_lower_bounds();
3028 saved_row_upper_bounds_ = lp->constraint_upper_bounds();
3029
3030 // This is needed for postsolving dual.
3031 saved_objective_ = lp->objective_coefficients();
3032
3033 // Note that we don't update the transpose during this preprocessor run.
3034 const SparseMatrix& original_transpose = lp->GetTransposeSparseMatrix();
3035
3036 // Heuristic: We try to subtitute sparse columns first to avoid a complexity
3037 // explosion. Note that if we do long chain of substitution, we can still end
3038 // up with a complexity of O(num_rows x num_cols) instead of O(num_entries).
3039 //
3040 // TODO(user): There is probably some more robust ways.
3041 std::vector<std::pair<int64_t, RowIndex>> sorted_rows;
3042 const RowIndex num_rows(lp->num_constraints());
3043 for (RowIndex row(0); row < num_rows; ++row) {
3044 const SparseColumn& original_row =
3045 original_transpose.column(RowToColIndex(row));
3046 if (original_row.num_entries() != 2 ||
3047 lp->constraint_lower_bounds()[row] !=
3048 lp->constraint_upper_bounds()[row]) {
3049 continue;
3050 }
3051 int64_t score = 0;
3052 for (const SparseColumn::Entry e : original_row) {
3053 const ColIndex col = RowToColIndex(e.row());
3054 score += lp->GetSparseColumn(col).num_entries().value();
3055 }
3056 sorted_rows.push_back({score, row});
3057 }
3058 std::sort(sorted_rows.begin(), sorted_rows.end());
3059
3060 // Iterate over the rows that were already doubletons before this preprocessor
3061 // run, and whose items don't belong to a column that we deleted during this
3062 // run. This implies that the rows are only ever touched once per run, because
3063 // we only modify rows that have an item on a deleted column.
3064 for (const auto p : sorted_rows) {
3065 const RowIndex row = p.second;
3066 const SparseColumn& original_row =
3067 original_transpose.column(RowToColIndex(row));
3068
3069 // Collect the two row items. Skip the ones involving a deleted column.
3070 // Note: we filled r.col[] and r.coeff[] by item order, and currently we
3071 // always pick the first column as the to-be-deleted one.
3072 // TODO(user): make a smarter choice of which column to delete, and
3073 // swap col[] and coeff[] accordingly.
3074 RestoreInfo r; // Use a short name since we're using it everywhere.
3075 int entry_index = 0;
3076 for (const SparseColumn::Entry e : original_row) {
3077 const ColIndex col = RowToColIndex(e.row());
3078 if (column_deletion_helper_.IsColumnMarked(col)) continue;
3079 r.col[entry_index] = col;
3080 r.coeff[entry_index] = e.coefficient();
3081 DCHECK_NE(0.0, r.coeff[entry_index]);
3082 ++entry_index;
3083 }
3084
3085 // Discard some cases that will be treated by other preprocessors, or by
3086 // another run of this one.
3087 // 1) One or two of the items were in a deleted column.
3088 if (entry_index < 2) continue;
3089
3090 // Fill the RestoreInfo, even if we end up not using it (because we
3091 // give up on preprocessing this row): it has a bunch of handy shortcuts.
3092 r.row = row;
3093 r.rhs = lp->constraint_lower_bounds()[row];
3094 for (int col_choice = 0; col_choice < NUM_DOUBLETON_COLS; ++col_choice) {
3095 const ColIndex col = r.col[col_choice];
3096 r.lb[col_choice] = lp->variable_lower_bounds()[col];
3097 r.ub[col_choice] = lp->variable_upper_bounds()[col];
3098 r.objective_coefficient[col_choice] = lp->objective_coefficients()[col];
3099 }
3100
3101 // 2) One of the columns is fixed: don't bother, it will be treated
3102 // by the FixedVariablePreprocessor.
3103 if (r.lb[DELETED] == r.ub[DELETED] || r.lb[MODIFIED] == r.ub[MODIFIED]) {
3104 continue;
3105 }
3106
3107 // Look at the bounds of both variables and exit early if we can delegate
3108 // to another pre-processor; otherwise adjust the bounds of the remaining
3109 // variable as necessary.
3110 // If the current row is: aX + bY = c, then the bounds of Y must be
3111 // adjusted to satisfy Y = c/b + (-a/b)X
3112 //
3113 // Note: when we compute the coefficients of these equations, we can cause
3114 // underflows/overflows that could be avoided if we did the computations
3115 // more carefully; but for now we just treat those cases as
3116 // ProblemStatus::ABNORMAL.
3117 // TODO(user): consider skipping the problematic rows in this preprocessor,
3118 // or trying harder to avoid the under/overflow.
3119 {
3120 const Fractional carry_over_offset = r.rhs / r.coeff[MODIFIED];
3121 const Fractional carry_over_factor =
3122 -r.coeff[DELETED] / r.coeff[MODIFIED];
3123 if (!IsFinite(carry_over_offset) || !IsFinite(carry_over_factor) ||
3124 carry_over_factor == 0.0) {
3126 break;
3127 }
3128
3129 Fractional lb = r.lb[MODIFIED];
3130 Fractional ub = r.ub[MODIFIED];
3131 Fractional carried_over_lb =
3132 r.lb[DELETED] * carry_over_factor + carry_over_offset;
3133 Fractional carried_over_ub =
3134 r.ub[DELETED] * carry_over_factor + carry_over_offset;
3135 if (carry_over_factor < 0) {
3136 std::swap(carried_over_lb, carried_over_ub);
3137 }
3138 if (carried_over_lb <= lb) {
3139 // Default (and simplest) case: the lower bound didn't change.
3140 r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3141 MODIFIED, VariableStatus::AT_LOWER_BOUND, lb);
3142 } else {
3143 lb = carried_over_lb;
3144 r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3145 DELETED,
3146 carry_over_factor > 0 ? VariableStatus::AT_LOWER_BOUND
3148 carry_over_factor > 0 ? r.lb[DELETED] : r.ub[DELETED]);
3149 }
3150 if (carried_over_ub >= ub) {
3151 // Default (and simplest) case: the upper bound didn't change.
3152 r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3153 MODIFIED, VariableStatus::AT_UPPER_BOUND, ub);
3154 } else {
3155 ub = carried_over_ub;
3156 r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3157 DELETED,
3158 carry_over_factor > 0 ? VariableStatus::AT_UPPER_BOUND
3160 carry_over_factor > 0 ? r.ub[DELETED] : r.lb[DELETED]);
3161 }
3162 // 3) If the new bounds are fixed (the domain is a singleton) or
3163 // infeasible, then we let the
3164 // ForcingAndImpliedFreeConstraintPreprocessor do the work.
3165 if (IsSmallerWithinPreprocessorZeroTolerance(ub, lb)) continue;
3166 lp->SetVariableBounds(r.col[MODIFIED], lb, ub);
3167 }
3168
3169 restore_stack_.push_back(r);
3170
3171 // Now, perform the substitution. If the current row is: aX + bY = c
3172 // then any other row containing 'X' with coefficient x can remove the
3173 // entry in X, and instead add an entry on 'Y' with coefficient x(-b/a)
3174 // and a constant offset x(c/a).
3175 // Looking at the matrix, this translates into colY += (-b/a) colX.
3176 DCHECK_NE(r.coeff[DELETED], 0.0);
3177 const Fractional substitution_factor =
3178 -r.coeff[MODIFIED] / r.coeff[DELETED]; // -b/a
3179 const Fractional constant_offset_factor = r.rhs / r.coeff[DELETED]; // c/a
3180 // Again we don't bother too much with over/underflows.
3181 if (!IsFinite(substitution_factor) || substitution_factor == 0.0 ||
3182 !IsFinite(constant_offset_factor)) {
3184 break;
3185 }
3186
3187 // Note that we do not save again a saved column, so that we only save
3188 // columns from the initial LP. This is important to limit the memory usage.
3189 // It complexify a bit the postsolve though.
3190 for (const int col_choice : {DELETED, MODIFIED}) {
3191 const ColIndex col = r.col[col_choice];
3192 columns_saver_.SaveColumnIfNotAlreadyDone(col, lp->GetSparseColumn(col));
3193 }
3194
3195 lp->GetSparseColumn(r.col[DELETED])
3196 .AddMultipleToSparseVectorAndDeleteCommonIndex(
3197 substitution_factor, r.row, parameters_.drop_tolerance(),
3198 lp->GetMutableSparseColumn(r.col[MODIFIED]));
3199
3200 // Apply similar operations on the objective coefficients.
3201 // Note that the offset is being updated by
3202 // SubtractColumnMultipleFromConstraintBound() below.
3203 {
3204 const Fractional new_objective =
3205 r.objective_coefficient[MODIFIED] +
3206 substitution_factor * r.objective_coefficient[DELETED];
3207 if (std::abs(new_objective) > parameters_.drop_tolerance()) {
3208 lp->SetObjectiveCoefficient(r.col[MODIFIED], new_objective);
3209 } else {
3210 lp->SetObjectiveCoefficient(r.col[MODIFIED], 0.0);
3211 }
3212 }
3213
3214 // Carry over the constant factor of the substitution as well.
3215 // TODO(user): rename that method to reflect the fact that it also updates
3216 // the objective offset, in the other direction.
3217 SubtractColumnMultipleFromConstraintBound(r.col[DELETED],
3218 constant_offset_factor, lp);
3219
3220 // If we keep substituing the same "dense" columns over and over, we can
3221 // have a memory in O(num_rows * num_cols) which can be order of magnitude
3222 // larger than the original problem. It is important to reclaim the memory
3223 // of the deleted column right away.
3224 lp->GetMutableSparseColumn(r.col[DELETED])->ClearAndRelease();
3225
3226 // Mark the column and the row for deletion.
3227 column_deletion_helper_.MarkColumnForDeletion(r.col[DELETED]);
3228 row_deletion_helper_.MarkRowForDeletion(r.row);
3229 }
3230 if (status_ != ProblemStatus::INIT) return false;
3231 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
3232 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
3233
3234 return !column_deletion_helper_.IsEmpty();
3235}
3236
3238 ProblemSolution* solution) const {
3241 column_deletion_helper_.RestoreDeletedColumns(solution);
3242 row_deletion_helper_.RestoreDeletedRows(solution);
3243
3244 const ColIndex num_cols = solution->variable_statuses.size();
3245 StrictITIVector<ColIndex, bool> new_basic_columns(num_cols, false);
3246
3247 for (const RestoreInfo& r : Reverse(restore_stack_)) {
3248 switch (solution->variable_statuses[r.col[MODIFIED]]) {
3250 LOG(DFATAL) << "FIXED variable produced by DoubletonPreprocessor!";
3251 // In non-fastbuild mode, we rely on the rest of the code producing an
3252 // ProblemStatus::ABNORMAL status here.
3253 break;
3254 // When the modified variable is either basic or free, we keep it as is,
3255 // and simply make the deleted one basic.
3257 ABSL_FALLTHROUGH_INTENDED;
3259 // Several code paths set the deleted column as basic. The code that
3260 // sets its value in that case is below, after the switch() block.
3261 solution->variable_statuses[r.col[DELETED]] = VariableStatus::BASIC;
3262 new_basic_columns[r.col[DELETED]] = true;
3263 break;
3265 ABSL_FALLTHROUGH_INTENDED;
3267 // The bound was induced by a bound of one of the two original
3268 // variables. Put that original variable at its bound, and make
3269 // the other one basic.
3270 const RestoreInfo::ColChoiceAndStatus& bound_backtracking =
3271 solution->variable_statuses[r.col[MODIFIED]] ==
3273 ? r.bound_backtracking_at_lower_bound
3274 : r.bound_backtracking_at_upper_bound;
3275 const ColIndex bounded_var = r.col[bound_backtracking.col_choice];
3276 const ColIndex basic_var =
3277 r.col[OtherColChoice(bound_backtracking.col_choice)];
3278 solution->variable_statuses[bounded_var] = bound_backtracking.status;
3279 solution->primal_values[bounded_var] = bound_backtracking.value;
3280 solution->variable_statuses[basic_var] = VariableStatus::BASIC;
3281 new_basic_columns[basic_var] = true;
3282 // If the modified column is VariableStatus::BASIC, then its value is
3283 // already set correctly. If it's the deleted column that is basic, its
3284 // value is set below the switch() block.
3285 }
3286 }
3287
3288 // Restore the value of the deleted column if it is VariableStatus::BASIC.
3289 if (solution->variable_statuses[r.col[DELETED]] == VariableStatus::BASIC) {
3290 solution->primal_values[r.col[DELETED]] =
3291 (r.rhs -
3292 solution->primal_values[r.col[MODIFIED]] * r.coeff[MODIFIED]) /
3293 r.coeff[DELETED];
3294 }
3295
3296 // Make the deleted constraint status FIXED.
3297 solution->constraint_statuses[r.row] = ConstraintStatus::FIXED_VALUE;
3298 }
3299
3300 // Now we need to reconstruct the dual. This is a bit tricky and is basically
3301 // the same as inverting a really structed and easy to invert matrix. For n
3302 // doubleton rows, looking only at the new_basic_columns, there is exactly n
3303 // by construction (one per row). We consider only this n x n matrix, and we
3304 // must choose dual row values so that we make the reduced costs zero on all
3305 // these columns.
3306 //
3307 // There is always an order that make this matrix triangular. We start with a
3308 // singleton column which fix its corresponding row and then work on the
3309 // square submatrix left. We can always start and continue, because if we take
3310 // the first substituted row of the current submatrix, if its deleted column
3311 // was in the submatrix we have a singleton column. If it is outside, we have
3312 // 2 n - 1 entries for a matrix with n columns, so one must be singleton.
3313 //
3314 // Note(user): Another advantage of working on the "original" matrix before
3315 // this presolve is an increased precision.
3316 //
3317 // TODO(user): We can probably use something better than a vector of set,
3318 // but the number of entry is really sparse though. And the size of a set<int>
3319 // is 24 bytes, same as a std::vector<int>.
3320 StrictITIVector<ColIndex, std::set<int>> col_to_index(num_cols);
3321 for (int i = 0; i < restore_stack_.size(); ++i) {
3322 const RestoreInfo& r = restore_stack_[i];
3323 col_to_index[r.col[MODIFIED]].insert(i);
3324 col_to_index[r.col[DELETED]].insert(i);
3325 }
3326 std::vector<ColIndex> singleton_col;
3327 for (ColIndex col(0); col < num_cols; ++col) {
3328 if (!new_basic_columns[col]) continue;
3329 if (col_to_index[col].size() == 1) singleton_col.push_back(col);
3330 }
3331 while (!singleton_col.empty()) {
3332 const ColIndex col = singleton_col.back();
3333 singleton_col.pop_back();
3334 if (!new_basic_columns[col]) continue;
3335 if (col_to_index[col].empty()) continue;
3336 CHECK_EQ(col_to_index[col].size(), 1);
3337 const int index = *col_to_index[col].begin();
3338 const RestoreInfo& r = restore_stack_[index];
3339
3340 const ColChoice col_choice = r.col[MODIFIED] == col ? MODIFIED : DELETED;
3341
3342 // Adjust the dual value of the deleted constraint so that col have a
3343 // reduced costs of zero.
3344 CHECK_EQ(solution->dual_values[r.row], 0.0);
3345 const SparseColumn& saved_col =
3346 columns_saver_.SavedColumn(r.col[col_choice]);
3347 const Fractional current_reduced_cost =
3348 saved_objective_[r.col[col_choice]] -
3349 PreciseScalarProduct(solution->dual_values, saved_col);
3350 solution->dual_values[r.row] = current_reduced_cost / r.coeff[col_choice];
3351
3352 // Update singleton
3353 col_to_index[r.col[DELETED]].erase(index);
3354 col_to_index[r.col[MODIFIED]].erase(index);
3355 if (col_to_index[r.col[DELETED]].size() == 1) {
3356 singleton_col.push_back(r.col[DELETED]);
3357 }
3358 if (col_to_index[r.col[MODIFIED]].size() == 1) {
3359 singleton_col.push_back(r.col[MODIFIED]);
3360 }
3361 }
3362
3363 // Fix potential bad ConstraintStatus::FIXED_VALUE statuses.
3364 FixConstraintWithFixedStatuses(saved_row_lower_bounds_,
3365 saved_row_upper_bounds_, solution);
3366}
3367
3368void FixConstraintWithFixedStatuses(const DenseColumn& row_lower_bounds,
3369 const DenseColumn& row_upper_bounds,
3370 ProblemSolution* solution) {
3371 const RowIndex num_rows = solution->constraint_statuses.size();
3372 DCHECK_EQ(row_lower_bounds.size(), num_rows);
3373 DCHECK_EQ(row_upper_bounds.size(), num_rows);
3374 for (RowIndex row(0); row < num_rows; ++row) {
3375 if (solution->constraint_statuses[row] != ConstraintStatus::FIXED_VALUE) {
3376 continue;
3377 }
3378 if (row_lower_bounds[row] == row_upper_bounds[row]) continue;
3379
3380 // We need to fix the status and we just need to make sure that the bound we
3381 // choose satisfies the LP optimality conditions.
3382 if (solution->dual_values[row] > 0) {
3383 solution->constraint_statuses[row] = ConstraintStatus::AT_LOWER_BOUND;
3384 } else {
3385 solution->constraint_statuses[row] = ConstraintStatus::AT_UPPER_BOUND;
3386 }
3387 }
3388}
3389
3390void DoubletonEqualityRowPreprocessor::
3391 SwapDeletedAndModifiedVariableRestoreInfo(RestoreInfo* r) {
3392 using std::swap;
3393 swap(r->col[DELETED], r->col[MODIFIED]);
3394 swap(r->coeff[DELETED], r->coeff[MODIFIED]);
3395 swap(r->lb[DELETED], r->lb[MODIFIED]);
3396 swap(r->ub[DELETED], r->ub[MODIFIED]);
3397 swap(r->objective_coefficient[DELETED], r->objective_coefficient[MODIFIED]);
3398}
3399
3400// --------------------------------------------------------
3401// DualizerPreprocessor
3402// --------------------------------------------------------
3403
3406 RETURN_VALUE_IF_NULL(lp, false);
3407 if (parameters_.solve_dual_problem() == GlopParameters::NEVER_DO) {
3408 return false;
3409 }
3410
3411 // Store the original problem size and direction.
3412 primal_num_cols_ = lp->num_variables();
3413 primal_num_rows_ = lp->num_constraints();
3414 primal_is_maximization_problem_ = lp->IsMaximizationProblem();
3415
3416 // If we need to decide whether or not to take the dual, we only take it when
3417 // the matrix has more rows than columns. The number of rows of a linear
3418 // program gives the size of the square matrices we need to invert and the
3419 // order of iterations of the simplex method. So solving a program with less
3420 // rows is likely a better alternative. Note that the number of row of the
3421 // dual is the number of column of the primal.
3422 //
3423 // Note however that the default is a conservative factor because if the
3424 // user gives us a primal program, we assume he knows what he is doing and
3425 // sometimes a problem is a lot faster to solve in a given formulation
3426 // even if its dimension would say otherwise.
3427 //
3428 // Another reason to be conservative, is that the number of columns of the
3429 // dual is the number of rows of the primal plus up to two times the number of
3430 // columns of the primal.
3431 //
3432 // TODO(user): This effect can be lowered if we use some of the extra
3433 // variables as slack variable which we are not doing at this point.
3434 if (parameters_.solve_dual_problem() == GlopParameters::LET_SOLVER_DECIDE) {
3435 if (1.0 * primal_num_rows_.value() <
3436 parameters_.dualizer_threshold() * primal_num_cols_.value()) {
3437 return false;
3438 }
3439 }
3441 // Save the linear program bounds.
3442 // Also make sure that all the bounded variable have at least one bound set to
3443 // zero. This will be needed to post-solve a dual-basic solution into a
3444 // primal-basic one.
3445 const ColIndex num_cols = lp->num_variables();
3446 variable_lower_bounds_.assign(num_cols, 0.0);
3447 variable_upper_bounds_.assign(num_cols, 0.0);
3448 for (ColIndex col(0); col < num_cols; ++col) {
3451
3452 // We need to shift one of the bound to zero.
3453 variable_lower_bounds_[col] = lower;
3454 variable_upper_bounds_[col] = upper;
3455 const Fractional value = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3456 if (value != 0.0) {
3458 SubtractColumnMultipleFromConstraintBound(col, value, lp);
3459 }
3460 }
3461
3462 // Fill the information that will be needed during postsolve.
3463 //
3464 // TODO(user): This will break if PopulateFromDual() is changed. so document
3465 // the convention or make the function fill these vectors?
3466 dual_status_correspondence_.clear();
3467 for (RowIndex row(0); row < primal_num_rows_; ++row) {
3470 if (lower_bound == upper_bound) {
3471 dual_status_correspondence_.push_back(VariableStatus::FIXED_VALUE);
3472 } else if (upper_bound != kInfinity) {
3473 dual_status_correspondence_.push_back(VariableStatus::AT_UPPER_BOUND);
3474 } else if (lower_bound != -kInfinity) {
3475 dual_status_correspondence_.push_back(VariableStatus::AT_LOWER_BOUND);
3476 } else {
3477 LOG(DFATAL) << "There should be no free constraint in this lp.";
3478 }
3479 }
3480 slack_or_surplus_mapping_.clear();
3481 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3484 if (lower_bound != -kInfinity) {
3485 dual_status_correspondence_.push_back(
3488 slack_or_surplus_mapping_.push_back(col);
3489 }
3490 }
3491 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3494 if (upper_bound != kInfinity) {
3495 dual_status_correspondence_.push_back(
3498 slack_or_surplus_mapping_.push_back(col);
3499 }
3500 }
3501
3502 // TODO(user): There are two different ways to deal with ranged rows when
3503 // taking the dual. The default way is to duplicate such rows, see
3504 // PopulateFromDual() for details. Another way is to call
3505 // lp->AddSlackVariablesForFreeAndBoxedRows() before calling
3506 // PopulateFromDual(). Adds an option to switch between the two as this may
3507 // change the running time?
3508 //
3509 // Note however that the default algorithm is likely to result in a faster
3510 // solving time because the dual program will have less rows.
3511 LinearProgram dual;
3512 dual.PopulateFromDual(*lp, &duplicated_rows_);
3513 dual.Swap(lp);
3514 return true;
3515}
3516
3517// Note(user): This assumes that LinearProgram.PopulateFromDual() uses
3518// the first ColIndex and RowIndex for the rows and columns of the given
3519// problem.
3520void DualizerPreprocessor::RecoverSolution(ProblemSolution* solution) const {
3523
3524 DenseRow new_primal_values(primal_num_cols_, 0.0);
3525 VariableStatusRow new_variable_statuses(primal_num_cols_,
3527 DCHECK_LE(primal_num_cols_, RowToColIndex(solution->dual_values.size()));
3528 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3529 RowIndex row = ColToRowIndex(col);
3530 const Fractional lower = variable_lower_bounds_[col];
3531 const Fractional upper = variable_upper_bounds_[col];
3532
3533 // The new variable value corresponds to the dual value of the dual.
3534 // The shift applied during presolve needs to be removed.
3535 const Fractional shift = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3536 new_primal_values[col] = solution->dual_values[row] + shift;
3537
3538 // A variable will be VariableStatus::BASIC if the dual constraint is not.
3539 if (solution->constraint_statuses[row] != ConstraintStatus::BASIC) {
3540 new_variable_statuses[col] = VariableStatus::BASIC;
3541 } else {
3542 // Otherwise, the dual value must be zero (if the solution is feasible),
3543 // and the variable is at an exact bound or zero if it is
3544 // VariableStatus::FREE. Note that this works because the bounds are
3545 // shifted to 0.0 in the presolve!
3546 new_variable_statuses[col] = ComputeVariableStatus(shift, lower, upper);
3547 }
3548 }
3549
3550 // A basic variable that corresponds to slack/surplus variable is the same as
3551 // a basic row. The new variable status (that was just set to
3552 // VariableStatus::BASIC above)
3553 // needs to be corrected and depends on the variable type (slack/surplus).
3554 const ColIndex begin = RowToColIndex(primal_num_rows_);
3555 const ColIndex end = dual_status_correspondence_.size();
3556 DCHECK_GE(solution->variable_statuses.size(), end);
3557 DCHECK_EQ(end - begin, slack_or_surplus_mapping_.size());
3558 for (ColIndex index(begin); index < end; ++index) {
3559 if (solution->variable_statuses[index] == VariableStatus::BASIC) {
3560 const ColIndex col = slack_or_surplus_mapping_[index - begin];
3561 const VariableStatus status = dual_status_correspondence_[index];
3562
3563 // The new variable value is set to its exact bound because the dual
3564 // variable value can be imprecise.
3565 new_variable_statuses[col] = status;
3568 new_primal_values[col] = variable_upper_bounds_[col];
3569 } else {
3571 new_primal_values[col] = variable_lower_bounds_[col];
3572 }
3573 }
3574 }
3575
3576 // Note the <= in the DCHECK, since we may need to add variables when taking
3577 // the dual.
3578 DCHECK_LE(primal_num_rows_, ColToRowIndex(solution->primal_values.size()));
3579 DenseColumn new_dual_values(primal_num_rows_, 0.0);
3580 ConstraintStatusColumn new_constraint_statuses(primal_num_rows_,
3582
3583 // Note that the sign need to be corrected because of the special behavior of
3584 // PopulateFromDual() on a maximization problem, see the comment in the
3585 // declaration of PopulateFromDual().
3586 Fractional sign = primal_is_maximization_problem_ ? -1 : 1;
3587 for (RowIndex row(0); row < primal_num_rows_; ++row) {
3588 const ColIndex col = RowToColIndex(row);
3589 new_dual_values[row] = sign * solution->primal_values[col];
3590
3591 // A constraint will be ConstraintStatus::BASIC if the dual variable is not.
3592 if (solution->variable_statuses[col] != VariableStatus::BASIC) {
3593 new_constraint_statuses[row] = ConstraintStatus::BASIC;
3594 if (duplicated_rows_[row] != kInvalidCol) {
3595 if (solution->variable_statuses[duplicated_rows_[row]] ==
3597 // The duplicated row is always about the lower bound.
3598 new_constraint_statuses[row] = ConstraintStatus::AT_LOWER_BOUND;
3599 }
3600 }
3601 } else {
3602 // ConstraintStatus::AT_LOWER_BOUND/ConstraintStatus::AT_UPPER_BOUND/
3603 // ConstraintStatus::FIXED depend on the type of the constraint at this
3604 // position.
3605 new_constraint_statuses[row] =
3606 VariableToConstraintStatus(dual_status_correspondence_[col]);
3607 }
3608
3609 // If the original row was duplicated, we need to take into account the
3610 // value of the corresponding dual column.
3611 if (duplicated_rows_[row] != kInvalidCol) {
3612 new_dual_values[row] +=
3613 sign * solution->primal_values[duplicated_rows_[row]];
3614 }
3615
3616 // Because non-basic variable values are exactly at one of their bounds, a
3617 // new basic constraint will have a dual value exactly equal to zero.
3618 DCHECK(new_dual_values[row] == 0 ||
3619 new_constraint_statuses[row] != ConstraintStatus::BASIC);
3620 }
3621
3622 solution->status = ChangeStatusToDualStatus(solution->status);
3623 new_primal_values.swap(solution->primal_values);
3624 new_dual_values.swap(solution->dual_values);
3625 new_variable_statuses.swap(solution->variable_statuses);
3626 new_constraint_statuses.swap(solution->constraint_statuses);
3627}
3628
3630 ProblemStatus status) const {
3631 switch (status) {
3644 default:
3645 return status;
3646 }
3647}
3648
3649// --------------------------------------------------------
3650// ShiftVariableBoundsPreprocessor
3651// --------------------------------------------------------
3652
3653bool ShiftVariableBoundsPreprocessor::Run(LinearProgram* lp) {
3655 RETURN_VALUE_IF_NULL(lp, false);
3656
3657 // Save the linear program bounds before shifting them.
3658 bool all_variable_domains_contain_zero = true;
3659 const ColIndex num_cols = lp->num_variables();
3660 variable_initial_lbs_.assign(num_cols, 0.0);
3661 variable_initial_ubs_.assign(num_cols, 0.0);
3662 for (ColIndex col(0); col < num_cols; ++col) {
3663 variable_initial_lbs_[col] = lp->variable_lower_bounds()[col];
3664 variable_initial_ubs_[col] = lp->variable_upper_bounds()[col];
3665 if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3666 all_variable_domains_contain_zero = false;
3667 }
3668 }
3669 VLOG(1) << "Maximum variable bounds magnitude (before shift): "
3670 << ComputeMaxVariableBoundsMagnitude(*lp);
3671
3672 // Abort early if there is nothing to do.
3673 if (all_variable_domains_contain_zero) return false;
3674
3675 // Shift the variable bounds and compute the changes to the constraint bounds
3676 // and objective offset in a precise way.
3677 int num_bound_shifts = 0;
3678 const RowIndex num_rows = lp->num_constraints();
3679 KahanSum objective_offset;
3680 util_intops::StrongVector<RowIndex, KahanSum> row_offsets(num_rows.value());
3681 offsets_.assign(num_cols, 0.0);
3682 for (ColIndex col(0); col < num_cols; ++col) {
3683 if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3684 Fractional offset = MinInMagnitudeOrZeroIfInfinite(
3685 variable_initial_lbs_[col], variable_initial_ubs_[col]);
3686 if (in_mip_context_ && lp->IsVariableInteger(col)) {
3687 // In the integer case, we truncate the number because if for instance
3688 // the lower bound is a positive integer + epsilon, we only want to
3689 // shift by the integer and leave the lower bound at epsilon.
3690 //
3691 // TODO(user): This would not be needed, if we always make the bound
3692 // of an integer variable integer before applying this preprocessor.
3693 offset = trunc(offset);
3694 } else {
3695 DCHECK_NE(offset, 0.0);
3696 }
3697 offsets_[col] = offset;
3698 lp->SetVariableBounds(col, variable_initial_lbs_[col] - offset,
3699 variable_initial_ubs_[col] - offset);
3700 const SparseColumn& sparse_column = lp->GetSparseColumn(col);
3701 for (const SparseColumn::Entry e : sparse_column) {
3702 row_offsets[e.row()].Add(e.coefficient() * offset);
3703 }
3704 objective_offset.Add(lp->objective_coefficients()[col] * offset);
3705 ++num_bound_shifts;
3706 }
3707 }
3708 VLOG(1) << "Maximum variable bounds magnitude (after " << num_bound_shifts
3709 << " shifts): " << ComputeMaxVariableBoundsMagnitude(*lp);
3710
3711 // Apply the changes to the constraint bound and objective offset.
3712 for (RowIndex row(0); row < num_rows; ++row) {
3713 if (!std::isfinite(row_offsets[row].Value())) {
3714 // This can happen for bad input where we get floating point overflow.
3715 // We can even get nan if we have two overflow in opposite direction.
3716 VLOG(1) << "Shifting variable bounds causes a floating point overflow "
3717 "for constraint "
3718 << row << ".";
3720 return false;
3721 }
3722 lp->SetConstraintBounds(
3723 row, lp->constraint_lower_bounds()[row] - row_offsets[row].Value(),
3724 lp->constraint_upper_bounds()[row] - row_offsets[row].Value());
3725 }
3726 if (!std::isfinite(objective_offset.Value())) {
3727 VLOG(1) << "Shifting variable bounds causes a floating point overflow "
3728 "for the objective.";
3730 return false;
3731 }
3732 lp->SetObjectiveOffset(lp->objective_offset() + objective_offset.Value());
3733 return true;
3734}
3735
3737 ProblemSolution* solution) const {
3740 const ColIndex num_cols = solution->variable_statuses.size();
3741 for (ColIndex col(0); col < num_cols; ++col) {
3742 if (in_mip_context_) {
3743 solution->primal_values[col] += offsets_[col];
3744 } else {
3745 switch (solution->variable_statuses[col]) {
3747 ABSL_FALLTHROUGH_INTENDED;
3749 solution->primal_values[col] = variable_initial_lbs_[col];
3750 break;
3752 solution->primal_values[col] = variable_initial_ubs_[col];
3753 break;
3755 solution->primal_values[col] += offsets_[col];
3756 break;
3758 break;
3759 }
3760 }
3761 }
3762}
3763
3764// --------------------------------------------------------
3765// ScalingPreprocessor
3766// --------------------------------------------------------
3767
3768bool ScalingPreprocessor::Run(LinearProgram* lp) {
3770 RETURN_VALUE_IF_NULL(lp, false);
3771 if (!parameters_.use_scaling()) return false;
3772
3773 // Save the linear program bounds before scaling them.
3774 const ColIndex num_cols = lp->num_variables();
3775 variable_lower_bounds_.assign(num_cols, 0.0);
3776 variable_upper_bounds_.assign(num_cols, 0.0);
3777 for (ColIndex col(0); col < num_cols; ++col) {
3778 variable_lower_bounds_[col] = lp->variable_lower_bounds()[col];
3779 variable_upper_bounds_[col] = lp->variable_upper_bounds()[col];
3780 }
3781
3782 // See the doc of these functions for more details.
3783 // It is important to call Scale() before the other two.
3784 Scale(lp, &scaler_, parameters_.scaling_method());
3785 cost_scaling_factor_ = lp->ScaleObjective(parameters_.cost_scaling());
3786 bound_scaling_factor_ = lp->ScaleBounds();
3787
3788 return true;
3789}
3790
3791void ScalingPreprocessor::RecoverSolution(ProblemSolution* solution) const {
3794
3795 scaler_.ScaleRowVector(false, &(solution->primal_values));
3796 for (ColIndex col(0); col < solution->primal_values.size(); ++col) {
3797 solution->primal_values[col] *= bound_scaling_factor_;
3798 }
3799
3800 scaler_.ScaleColumnVector(false, &(solution->dual_values));
3801 for (RowIndex row(0); row < solution->dual_values.size(); ++row) {
3802 solution->dual_values[row] *= cost_scaling_factor_;
3803 }
3804
3805 // Make sure the variable are at they exact bounds according to their status.
3806 // This just remove a really low error (about 1e-15) but allows to keep the
3807 // variables at their exact bounds.
3808 const ColIndex num_cols = solution->primal_values.size();
3809 for (ColIndex col(0); col < num_cols; ++col) {
3810 switch (solution->variable_statuses[col]) {
3812 ABSL_FALLTHROUGH_INTENDED;
3814 solution->primal_values[col] = variable_upper_bounds_[col];
3815 break;
3817 solution->primal_values[col] = variable_lower_bounds_[col];
3818 break;
3820 ABSL_FALLTHROUGH_INTENDED;
3822 break;
3823 }
3824 }
3825}
3826
3827// --------------------------------------------------------
3828// ToMinimizationPreprocessor
3829// --------------------------------------------------------
3830
3833 RETURN_VALUE_IF_NULL(lp, false);
3834 if (lp->IsMaximizationProblem()) {
3835 for (ColIndex col(0); col < lp->num_variables(); ++col) {
3836 const Fractional coeff = lp->objective_coefficients()[col];
3837 if (coeff != 0.0) {
3838 lp->SetObjectiveCoefficient(col, -coeff);
3839 }
3840 }
3841 lp->SetMaximizationProblem(false);
3844 }
3845 return false;
3846}
3847
3849 ProblemSolution* solution) const {}
3850
3851// --------------------------------------------------------
3852// AddSlackVariablesPreprocessor
3853// --------------------------------------------------------
3854
3855bool AddSlackVariablesPreprocessor::Run(LinearProgram* lp) {
3857 RETURN_VALUE_IF_NULL(lp, false);
3858 lp->AddSlackVariablesWhereNecessary(
3859 /*detect_integer_constraints=*/true);
3860 first_slack_col_ = lp->GetFirstSlackVariable();
3861 return true;
3862}
3863
3865 ProblemSolution* solution) const {
3868
3869 // Compute constraint statuses from statuses of slack variables.
3870 const RowIndex num_rows = solution->dual_values.size();
3871 for (RowIndex row(0); row < num_rows; ++row) {
3872 const ColIndex slack_col = first_slack_col_ + RowToColIndex(row);
3873 const VariableStatus variable_status =
3874 solution->variable_statuses[slack_col];
3875 ConstraintStatus constraint_status = ConstraintStatus::FREE;
3876 // The slack variables have reversed bounds - if the value of the variable
3877 // is at one bound, the value of the constraint is at the opposite bound.
3878 switch (variable_status) {
3880 constraint_status = ConstraintStatus::AT_UPPER_BOUND;
3881 break;
3883 constraint_status = ConstraintStatus::AT_LOWER_BOUND;
3884 break;
3885 default:
3886 constraint_status = VariableToConstraintStatus(variable_status);
3887 break;
3888 }
3889 solution->constraint_statuses[row] = constraint_status;
3891
3892 // Drop the primal values and variable statuses for slack variables.
3893 solution->primal_values.resize(first_slack_col_, 0.0);
3894 solution->variable_statuses.resize(first_slack_col_, VariableStatus::FREE);
3895}
3896
3897} // namespace glop
3898} // namespace operations_research
IntegerValue size
void EnableLogging(bool enable)
Definition logging.h:46
void SetLogToStdOut(bool enable)
Should all messages be displayed on stdout ?
Definition logging.h:52
void RecoverSolution(ProblemSolution *solution) const final
void Clear()
Restores the class to its initial state.
const DenseBooleanRow & GetMarkedColumns() const
Returns a Boolean vector of the column to be deleted.
void RestoreDeletedColumns(ProblemSolution *solution) const
bool IsEmpty() const
Returns true if no columns have been marked for deletion.
bool IsColumnMarked(ColIndex col) const
Returns whether or not the given column is marked for deletion.
void MarkColumnForDeletionWithState(ColIndex col, Fractional value, VariableStatus status)
const SparseColumn & SavedOrEmptyColumn(ColIndex col) const
void SaveColumn(ColIndex col, const SparseColumn &column)
Saves a column. The first version CHECKs that it is not already done.
const SparseColumn & SavedColumn(ColIndex col) const
Returns the saved column. The first version CHECKs that it was saved.
void SaveColumnIfNotAlreadyDone(ColIndex col, const SparseColumn &column)
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
ProblemStatus ChangeStatusToDualStatus(ProblemStatus status) const
Convert the given problem status to the one of its dual.
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
Removes the fixed variables from the problem.
void RecoverSolution(ProblemSolution *solution) const final
Removes the constraints with no bounds from the problem.
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
const DenseRow & objective_coefficients() const
Returns the objective coefficients (or cost) of variables as a row vector.
Definition lp_data.h:231
void SetObjectiveOffset(Fractional objective_offset)
Definition lp_data.cc:340
DenseColumn * mutable_constraint_upper_bounds()
Definition lp_data.h:564
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition lp_data.cc:335
void SetObjectiveScalingFactor(Fractional objective_scaling_factor)
Definition lp_data.cc:345
const DenseColumn & constraint_lower_bounds() const
Definition lp_data.h:223
const DenseRow & variable_lower_bounds() const
Definition lp_data.h:237
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition lp_data.cc:258
DenseColumn * mutable_constraint_lower_bounds()
Definition lp_data.h:561
SparseMatrix * GetMutableTransposeSparseMatrix()
Definition lp_data.cc:395
const SparseMatrix & GetSparseMatrix() const
Definition lp_data.h:183
void DeleteRows(const DenseBooleanColumn &rows_to_delete)
Definition lp_data.cc:1269
const DenseRow & variable_upper_bounds() const
Definition lp_data.h:240
const DenseColumn & constraint_upper_bounds() const
Definition lp_data.h:226
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition lp_data.cc:418
Fractional objective_scaling_factor() const
Definition lp_data.h:269
ColIndex num_variables() const
Returns the number of variables.
Definition lp_data.h:213
Fractional objective_offset() const
Returns the objective offset and scaling factor.
Definition lp_data.h:268
RowIndex num_constraints() const
Returns the number of constraints.
Definition lp_data.h:216
void SetMaximizationProblem(bool maximize)
Definition lp_data.cc:352
void DestructiveRecoverSolution(ProblemSolution *solution)
void RecoverSolution(ProblemSolution *solution) const override
bool IsSmallerWithinFeasibilityTolerance(Fractional a, Fractional b) const
Preprocessor(const GlopParameters *parameters)
bool IsSmallerWithinPreprocessorZeroTolerance(Fractional a, Fractional b) const
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
bool IsEmpty() const
Returns true if no rows have been marked for deletion.
void Clear()
Restores the class to its initial state.
void MarkRowForDeletion(RowIndex row)
Adds a deleted row to the helper.
bool IsRowMarked(RowIndex row) const
Returns whether or not the given row is marked for deletion.
const DenseBooleanColumn & GetMarkedRows() const
Returns a Boolean vector of the row to be deleted.
void RestoreDeletedRows(ProblemSolution *solution) const
void UnmarkRow(RowIndex row)
If the given row was marked for deletion, unmark it.
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
SingletonUndo(OperationType type, const LinearProgram &lp, MatrixEntry e, ConstraintStatus status)
void Undo(const GlopParameters &parameters, const SparseColumn &saved_column, const SparseColumn &saved_row, ProblemSolution *solution) const
void ScaleColumnVector(bool up, DenseColumn *column_vector) const
void ScaleRowVector(bool up, DenseRow *row_vector) const
const SparseColumn & column(ColIndex col) const
Access the underlying sparse columns.
Definition sparse.h:194
SparseColumn * mutable_column(ColIndex col)
Definition sparse.h:195
EntryIndex num_entries() const
Note this method can only be used when the vector has no duplicates.
Fractional LookUpCoefficient(Index index) const
void assign(IntType size, const T &v)
Definition lp_types.h:319
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RemoveZeroCostUnconstrainedVariable(ColIndex col, Fractional target_bound, LinearProgram *lp)
double coefficient(Variable variable) const
Returns 0.0 if the variable is not used in the constraint.
void swap(StrongVector &x) noexcept
void push_back(const value_type &val)
void resize(size_type new_size)
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
SatParameters parameters
const std::string name
A name for logging purposes.
int64_t value
absl::Status status
Definition g_gurobi.cc:44
double lower
double upper
double upper_bound
double lower_bound
time_limit
Definition solve.cc:22
int index
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
double solution
ReverseView< Container > reversed_view(const Container &c)
constexpr ColIndex kInvalidCol(-1)
constexpr double kInfinity
Infinity for type Fractional.
Definition lp_types.h:89
StrictITIVector< RowIndex, ColIndex > RowToColMapping
Definition lp_types.h:396
StrictITIVector< RowIndex, bool > DenseBooleanColumn
Column of booleans.
Definition lp_types.h:385
StrictITIVector< ColIndex, ColIndex > ColMapping
Row of column indices. Used to represent mappings between columns.
Definition lp_types.h:359
StrictITIVector< RowIndex, ConstraintStatus > ConstraintStatusColumn
Column of constraints (slack variables) statuses.
Definition lp_types.h:399
Fractional ScalarProduct(const DenseRowOrColumn1 &u, const DenseRowOrColumn2 &v)
Definition lp_utils.h:52
AccurateSum< Fractional > KahanSum
Definition lp_utils.h:37
Fractional PreciseScalarProduct(const DenseRowOrColumn &u, const DenseRowOrColumn2 &v)
Definition lp_utils.h:111
SumWithOneMissing< true > SumWithPositiveInfiniteAndOneMissing
Definition lp_utils.h:390
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
Definition lp_types.h:54
bool IsFinite(Fractional value)
Definition lp_types.h:96
constexpr RowIndex kInvalidRow(-1)
RowIndex ColToRowIndex(ColIndex col)
Get the RowIndex corresponding to the row # col.
Definition lp_types.h:57
StrictITIVector< ColIndex, VariableStatus > VariableStatusRow
Row of variable statuses.
Definition lp_types.h:374
void Scale(LinearProgram *lp, SparseMatrixScaler *scaler)
void FixConstraintWithFixedStatuses(const DenseColumn &row_lower_bounds, const DenseColumn &row_upper_bounds, ProblemSolution *solution)
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:382
StrictITIVector< ColIndex, Fractional > DenseRow
Row-vector types. Row-vector types are indexed by a column index.
Definition lp_types.h:353
StrictITIVector< ColIndex, bool > DenseBooleanRow
Row of booleans.
Definition lp_types.h:356
SumWithOneMissing< false > SumWithNegativeInfiniteAndOneMissing
Definition lp_utils.h:391
ProblemStatus
Different statuses for a given problem.
Definition lp_types.h:107
@ ABNORMAL
An error occurred during the solving process.
@ INVALID_PROBLEM
The input problem was invalid (see LinearProgram.IsValid()).
@ INIT
The solver didn't had a chance to prove anything.
ConstraintStatus VariableToConstraintStatus(VariableStatus status)
Returns the ConstraintStatus corresponding to a given VariableStatus.
Definition lp_types.cc:113
ColMapping FindProportionalColumns(const SparseMatrix &matrix, Fractional tolerance)
std::string GetProblemStatusString(ProblemStatus problem_status)
Returns the string representation of the ProblemStatus enum.
Definition lp_types.cc:23
std::function< int64_t(const Model &)> Value(IntegerVariable v)
This checks that the variable is fixed.
Definition integer.h:1975
In SWIG mode, we don't want anything besides these top-level includes.
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition fp_utils.h:167
util_intops::StrongVector< ColumnEntryIndex, ElementIndex, ElementAllocator > SparseColumn
BeginEndReverseIteratorWrapper< Container > Reverse(const Container &c)
Definition iterators.h:99
int column
Fractional scaled_cost
ColIndex representative
#define RUN_PREPROCESSOR(name)
glop::MainLpPreprocessor preprocessor
int64_t delta
Definition resource.cc:1709
#define RETURN_IF_NULL(x)
#define RETURN_VALUE_IF_NULL(x, v)
Fractional target_bound
int64_t bound
int64_t coefficient
std::vector< double > lower_bounds
Definition lp_utils.cc:746
std::vector< double > upper_bounds
Definition lp_utils.cc:747
std::optional< int64_t > end
const int width
Definition statistics.cc:38
#define SCOPED_INSTRUCTION_COUNT(time_limit)
Definition stats.h:419
Contains the solution of a LinearProgram as returned by a preprocessor.
Definition lp_data.h:671
#define SOLVER_LOG(logger,...)
Definition logging.h:109