Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
preprocessor.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <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#if defined(_MSC_VER)
48double trunc(double d) { return d > 0 ? floor(d) : ceil(d); }
49#endif
50} // namespace
51
52// --------------------------------------------------------
53// Preprocessor
54// --------------------------------------------------------
56 : status_(ProblemStatus::INIT),
63// --------------------------------------------------------
64// MainLpPreprocessor
65// --------------------------------------------------------
66
67#define RUN_PREPROCESSOR(name) \
68 RunAndPushIfRelevant(std::unique_ptr<Preprocessor>(new name(&parameters_)), \
69 #name, time_limit_, lp)
70
75 default_logger_.SetLogToStdOut(parameters_.log_to_stdout());
76
77 SOLVER_LOG(logger_, "");
78 SOLVER_LOG(logger_, "Starting presolve...");
79
80 initial_num_rows_ = lp->num_constraints();
81 initial_num_cols_ = lp->num_variables();
82 initial_num_entries_ = lp->num_entries();
83 if (parameters_.use_preprocessing()) {
85
86 // We run it a few times because running one preprocessor may allow another
87 // one to remove more stuff.
88 const int kMaxNumPasses = 20;
89 for (int i = 0; i < kMaxNumPasses; ++i) {
90 const int old_stack_size = preprocessors_.size();
99
100 // Abort early if none of the preprocessors did something. Technically
101 // this is true if none of the preprocessors above needs postsolving,
102 // which has exactly the same meaning for these particular preprocessors.
103 if (preprocessors_.size() == old_stack_size) {
104 // We use i here because the last pass did nothing.
105 SOLVER_LOG(logger_, "Reached fixed point after presolve pass #", i);
106 break;
107 }
108 }
109 RUN_PREPROCESSOR(EmptyColumnPreprocessor);
110 RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
111
112 // TODO(user): Run them in the loop above if the effect on the running time
113 // is good. This needs more investigation.
114 RUN_PREPROCESSOR(ProportionalColumnPreprocessor);
115 RUN_PREPROCESSOR(ProportionalRowPreprocessor);
116
117 // If DualizerPreprocessor was run, we need to do some extra preprocessing.
118 // This is because it currently adds a lot of zero-cost singleton columns.
119 const int old_stack_size = preprocessors_.size();
120
121 // TODO(user): We probably want to scale the costs before and after this
122 // preprocessor so that the rhs/objective of the dual are with a good
123 // magnitude.
124 RUN_PREPROCESSOR(DualizerPreprocessor);
125 if (old_stack_size != preprocessors_.size()) {
126 RUN_PREPROCESSOR(SingletonPreprocessor);
127 RUN_PREPROCESSOR(FreeConstraintPreprocessor);
128 RUN_PREPROCESSOR(UnconstrainedVariablePreprocessor);
129 RUN_PREPROCESSOR(EmptyColumnPreprocessor);
130 RUN_PREPROCESSOR(EmptyConstraintPreprocessor);
131 }
132
133 RUN_PREPROCESSOR(SingletonColumnSignPreprocessor);
134 }
135
136 // The scaling is controlled by use_scaling, not use_preprocessing.
137 RUN_PREPROCESSOR(ScalingPreprocessor);
138
139 return !preprocessors_.empty();
140}
141
142#undef RUN_PREPROCESSOR
143
144void MainLpPreprocessor::RunAndPushIfRelevant(
145 std::unique_ptr<Preprocessor> preprocessor, absl::string_view name,
146 TimeLimit* time_limit, LinearProgram* lp) {
147 RETURN_IF_NULL(preprocessor);
148 RETURN_IF_NULL(time_limit);
149 if (status_ != ProblemStatus::INIT || time_limit->LimitReached()) return;
150
151 const double start_time = time_limit->GetElapsedTime();
152 preprocessor->SetTimeLimit(time_limit);
153
154 // No need to run the preprocessor if the lp is empty.
155 // TODO(user): without this test, the code is failing as of 2013-03-18.
156 if (lp->num_variables() == 0 && lp->num_constraints() == 0) {
158 return;
159 }
160
161 if (preprocessor->Run(lp)) {
162 const EntryIndex new_num_entries = lp->num_entries();
163 const double preprocess_time = time_limit->GetElapsedTime() - start_time;
164 SOLVER_LOG(logger_,
165 absl::StrFormat(
166 "%-45s: %d(%d) rows, %d(%d) columns, %d(%d) entries. (%fs)",
167 name, lp->num_constraints().value(),
168 (lp->num_constraints() - initial_num_rows_).value(),
169 lp->num_variables().value(),
170 (lp->num_variables() - initial_num_cols_).value(),
171 // static_cast<int64_t> is needed because the Android port
172 // uses int32_t.
173 static_cast<int64_t>(new_num_entries.value()),
174 static_cast<int64_t>(new_num_entries.value() -
175 initial_num_entries_.value()),
176 preprocess_time));
177 status_ = preprocessor->status();
178 preprocessors_.push_back(std::move(preprocessor));
179 return;
180 } else {
181 // Even if a preprocessor returns false (i.e. no need for postsolve), it
182 // can detect an issue with the problem.
183 status_ = preprocessor->status();
185 SOLVER_LOG(logger_, name, " detected that the problem is ",
187 }
188 }
189}
190
193 for (const auto& p : gtl::reversed_view(preprocessors_)) {
194 p->RecoverSolution(solution);
196}
197
200 while (!preprocessors_.empty()) {
201 preprocessors_.back()->RecoverSolution(solution);
202 preprocessors_.pop_back();
203 }
204}
205
206// --------------------------------------------------------
207// ColumnDeletionHelper
208// --------------------------------------------------------
209
210void ColumnsSaver::SaveColumn(ColIndex col, const SparseColumn& column) {
211 const int index = saved_columns_.size();
212 CHECK(saved_columns_index_.insert({col, index}).second);
213 saved_columns_.push_back(column);
214}
215
217 const SparseColumn& column) {
218 const int index = saved_columns_.size();
219 const bool inserted = saved_columns_index_.insert({col, index}).second;
220 if (inserted) saved_columns_.push_back(column);
221}
223const SparseColumn& ColumnsSaver::SavedColumn(ColIndex col) const {
224 const auto it = saved_columns_index_.find(col);
225 CHECK(it != saved_columns_index_.end());
226 return saved_columns_[it->second];
227}
228
230 const auto it = saved_columns_index_.find(col);
231 return it == saved_columns_index_.end() ? empty_column_
232 : saved_columns_[it->second];
233}
234
236 is_column_deleted_.clear();
237 stored_value_.clear();
238}
239
245 ColIndex col, Fractional fixed_value, VariableStatus status) {
246 DCHECK_GE(col, 0);
247 if (col >= is_column_deleted_.size()) {
248 is_column_deleted_.resize(col + 1, false);
249 stored_value_.resize(col + 1, 0.0);
250 stored_status_.resize(col + 1, VariableStatus::FREE);
251 }
252 is_column_deleted_[col] = true;
253 stored_value_[col] = fixed_value;
254 stored_status_[col] = status;
255}
256
258 ProblemSolution* solution) const {
259 DenseRow new_primal_values;
260 VariableStatusRow new_variable_statuses;
261 ColIndex old_index(0);
262 for (ColIndex col(0); col < is_column_deleted_.size(); ++col) {
263 if (is_column_deleted_[col]) {
264 new_primal_values.push_back(stored_value_[col]);
265 new_variable_statuses.push_back(stored_status_[col]);
266 } else {
267 new_primal_values.push_back(solution->primal_values[old_index]);
268 new_variable_statuses.push_back(solution->variable_statuses[old_index]);
269 ++old_index;
270 }
271 }
272
273 // Copy the end of the vectors and swap them with the ones in solution.
274 const ColIndex num_cols = solution->primal_values.size();
275 DCHECK_EQ(num_cols, solution->variable_statuses.size());
276 for (; old_index < num_cols; ++old_index) {
277 new_primal_values.push_back(solution->primal_values[old_index]);
278 new_variable_statuses.push_back(solution->variable_statuses[old_index]);
279 }
280 new_primal_values.swap(solution->primal_values);
281 new_variable_statuses.swap(solution->variable_statuses);
282}
283
284// --------------------------------------------------------
285// RowDeletionHelper
286// --------------------------------------------------------
287
288void RowDeletionHelper::Clear() { is_row_deleted_.clear(); }
289
290void RowDeletionHelper::MarkRowForDeletion(RowIndex row) {
291 DCHECK_GE(row, 0);
292 if (row >= is_row_deleted_.size()) {
293 is_row_deleted_.resize(row + 1, false);
294 }
295 is_row_deleted_[row] = true;
297
299 if (row >= is_row_deleted_.size()) return;
300 is_row_deleted_[row] = false;
301}
302
304 return is_row_deleted_;
305}
308 DenseColumn new_dual_values;
309 ConstraintStatusColumn new_constraint_statuses;
310 RowIndex old_index(0);
311 const RowIndex end = is_row_deleted_.size();
312 for (RowIndex row(0); row < end; ++row) {
313 if (is_row_deleted_[row]) {
314 new_dual_values.push_back(0.0);
315 new_constraint_statuses.push_back(ConstraintStatus::BASIC);
316 } else {
317 new_dual_values.push_back(solution->dual_values[old_index]);
318 new_constraint_statuses.push_back(
319 solution->constraint_statuses[old_index]);
320 ++old_index;
321 }
322 }
323
324 // Copy the end of the vectors and swap them with the ones in solution.
325 const RowIndex num_rows = solution->dual_values.size();
326 DCHECK_EQ(num_rows, solution->constraint_statuses.size());
327 for (; old_index < num_rows; ++old_index) {
328 new_dual_values.push_back(solution->dual_values[old_index]);
329 new_constraint_statuses.push_back(solution->constraint_statuses[old_index]);
330 }
331 new_dual_values.swap(solution->dual_values);
332 new_constraint_statuses.swap(solution->constraint_statuses);
333}
334
335// --------------------------------------------------------
336// EmptyColumnPreprocessor
337// --------------------------------------------------------
338
339namespace {
340
341// Computes the status of a variable given its value and bounds. This only works
342// with a value exactly at one of the bounds, or a value of 0.0 for free
343// variables.
344VariableStatus ComputeVariableStatus(Fractional value, Fractional lower_bound,
345 Fractional upper_bound) {
346 if (lower_bound == upper_bound) {
347 DCHECK_EQ(value, lower_bound);
348 DCHECK(IsFinite(lower_bound));
350 }
351 if (value == lower_bound) {
352 DCHECK_NE(lower_bound, -kInfinity);
354 }
355 if (value == upper_bound) {
356 DCHECK_NE(upper_bound, kInfinity);
358 }
359
360 // TODO(user): restrict this to unbounded variables with a value of zero.
361 // We can't do that when postsolving infeasible problem. Don't call postsolve
362 // on an infeasible problem?
364}
365
366// Returns the input with the smallest magnitude or zero if both are infinite.
367Fractional MinInMagnitudeOrZeroIfInfinite(Fractional a, Fractional b) {
368 const Fractional value = std::abs(a) < std::abs(b) ? a : b;
369 return IsFinite(value) ? value : 0.0;
370}
371
372Fractional MagnitudeOrZeroIfInfinite(Fractional value) {
373 return IsFinite(value) ? std::abs(value) : 0.0;
374}
375
376// Returns the maximum magnitude of the finite variable bounds of the given
377// linear program.
378Fractional ComputeMaxVariableBoundsMagnitude(const LinearProgram& lp) {
379 Fractional max_bounds_magnitude = 0.0;
380 const ColIndex num_cols = lp.num_variables();
381 for (ColIndex col(0); col < num_cols; ++col) {
382 max_bounds_magnitude = std::max(
383 max_bounds_magnitude,
384 std::max(MagnitudeOrZeroIfInfinite(lp.variable_lower_bounds()[col]),
385 MagnitudeOrZeroIfInfinite(lp.variable_upper_bounds()[col])));
386 }
387 return max_bounds_magnitude;
388}
389
390} // namespace
391
394 RETURN_VALUE_IF_NULL(lp, false);
395 column_deletion_helper_.Clear();
396 const ColIndex num_cols = lp->num_variables();
397 for (ColIndex col(0); col < num_cols; ++col) {
398 if (lp->GetSparseColumn(col).IsEmpty()) {
399 const Fractional lower_bound = lp->variable_lower_bounds()[col];
400 const Fractional upper_bound = lp->variable_upper_bounds()[col];
401 const Fractional objective_coefficient =
403 Fractional value;
404 if (objective_coefficient == 0) {
405 // Any feasible value will do.
406 if (upper_bound != kInfinity) {
407 value = upper_bound;
408 } else {
409 if (lower_bound != -kInfinity) {
410 value = lower_bound;
411 } else {
412 value = Fractional(0.0);
413 }
414 }
415 } else {
416 value = objective_coefficient > 0 ? lower_bound : upper_bound;
417 if (!IsFinite(value)) {
418 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, empty column " << col
419 << " has a minimization cost of " << objective_coefficient
420 << " and bounds"
421 << " [" << lower_bound << "," << upper_bound << "]";
423 return false;
424 }
426 value * lp->objective_coefficients()[col]);
427 }
428 column_deletion_helper_.MarkColumnForDeletionWithState(
429 col, value, ComputeVariableStatus(value, lower_bound, upper_bound));
430 }
431 }
432 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
433 return !column_deletion_helper_.IsEmpty();
434}
435
439 column_deletion_helper_.RestoreDeletedColumns(solution);
440}
441
442// --------------------------------------------------------
443// ProportionalColumnPreprocessor
444// --------------------------------------------------------
445
446namespace {
447
448// Subtracts 'multiple' times the column col of the given linear program from
449// the constraint bounds. That is, for a non-zero entry of coefficient c,
450// c * multiple is subtracted from both the constraint upper and lower bound.
451void SubtractColumnMultipleFromConstraintBound(ColIndex col,
452 Fractional multiple,
453 LinearProgram* lp) {
456 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
457 const RowIndex row = e.row();
458 const Fractional delta = multiple * e.coefficient();
459 (*lbs)[row] -= delta;
460 (*ubs)[row] -= delta;
461 }
462 // While not needed for correctness, this allows the presolved problem to
463 // have the same objective value as the original one.
465 lp->objective_coefficients()[col] * multiple);
466}
467
468// Struct used to detect proportional columns with the same cost. For that, a
469// vector of such struct will be sorted, and only the columns that end up
470// together need to be compared.
471struct ColumnWithRepresentativeAndScaledCost {
472 ColumnWithRepresentativeAndScaledCost(ColIndex _col, ColIndex _representative,
473 Fractional _scaled_cost)
474 : col(_col), representative(_representative), scaled_cost(_scaled_cost) {}
475 ColIndex col;
476 ColIndex representative;
477 Fractional scaled_cost;
478
479 bool operator<(const ColumnWithRepresentativeAndScaledCost& other) const {
480 if (representative == other.representative) {
481 if (scaled_cost == other.scaled_cost) {
482 return col < other.col;
483 }
484 return scaled_cost < other.scaled_cost;
485 }
486 return representative < other.representative;
487 }
488};
489
490} // namespace
491
494 RETURN_VALUE_IF_NULL(lp, false);
497
498 // Compute some statistics and make each class representative point to itself
499 // in the mapping. Also store the columns that are proportional to at least
500 // another column in proportional_columns to iterate on them more efficiently.
501 //
502 // TODO(user): Change FindProportionalColumns for this?
503 int num_proportionality_classes = 0;
504 std::vector<ColIndex> proportional_columns;
505 for (ColIndex col(0); col < mapping.size(); ++col) {
506 const ColIndex representative = mapping[col];
507 if (representative != kInvalidCol) {
508 if (mapping[representative] == kInvalidCol) {
509 proportional_columns.push_back(representative);
510 ++num_proportionality_classes;
511 mapping[representative] = representative;
512 }
513 proportional_columns.push_back(col);
514 }
515 }
516 if (proportional_columns.empty()) return false;
517 VLOG(1) << "The problem contains " << proportional_columns.size()
518 << " columns which belong to " << num_proportionality_classes
519 << " proportionality classes.";
520
521 // Note(user): using the first coefficient may not give the best precision.
522 const ColIndex num_cols = lp->num_variables();
523 column_factors_.assign(num_cols, 0.0);
524 for (const ColIndex col : proportional_columns) {
525 const SparseColumn& column = lp->GetSparseColumn(col);
526 column_factors_[col] = column.GetFirstCoefficient();
527 }
528
529 // This is only meaningful for column representative.
530 //
531 // The reduced cost of a column is 'cost - dual_values.column' and we know
532 // that for all proportional columns, 'dual_values.column /
533 // column_factors_[col]' is the same. Here, we bound this quantity which is
534 // related to the cost 'slope' of a proportional column:
535 // cost / column_factors_[col].
536 DenseRow slope_lower_bound(num_cols, -kInfinity);
537 DenseRow slope_upper_bound(num_cols, +kInfinity);
538 for (const ColIndex col : proportional_columns) {
539 const ColIndex representative = mapping[col];
540
541 // We reason in terms of a minimization problem here.
542 const bool is_rc_positive_or_zero =
543 (lp->variable_upper_bounds()[col] == kInfinity);
544 const bool is_rc_negative_or_zero =
545 (lp->variable_lower_bounds()[col] == -kInfinity);
546 bool is_slope_upper_bounded = is_rc_positive_or_zero;
547 bool is_slope_lower_bounded = is_rc_negative_or_zero;
548 if (column_factors_[col] < 0.0) {
549 std::swap(is_slope_lower_bounded, is_slope_upper_bounded);
550 }
551 const Fractional slope =
553 column_factors_[col];
554 if (is_slope_lower_bounded) {
555 slope_lower_bound[representative] =
556 std::max(slope_lower_bound[representative], slope);
557 }
558 if (is_slope_upper_bounded) {
559 slope_upper_bound[representative] =
560 std::min(slope_upper_bound[representative], slope);
561 }
562 }
563
564 // Deal with empty slope intervals.
565 for (const ColIndex col : proportional_columns) {
566 const ColIndex representative = mapping[col];
567
568 // This is only needed for class representative columns.
569 if (representative == col) {
571 slope_lower_bound[representative],
572 slope_upper_bound[representative])) {
573 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, no feasible dual values"
574 << " can satisfy the constraints of the proportional columns"
575 << " with representative " << representative << "."
576 << " the associated quantity must be in ["
577 << slope_lower_bound[representative] << ","
578 << slope_upper_bound[representative] << "].";
580 return false;
581 }
582 }
583 }
584
585 // Now, fix the columns that can be fixed to one of their bounds.
586 for (const ColIndex col : proportional_columns) {
587 const ColIndex representative = mapping[col];
588 const Fractional slope =
590 column_factors_[col];
591
592 // The scaled reduced cost is slope - quantity.
593 bool variable_can_be_fixed = false;
594 Fractional target_bound = 0.0;
595
596 const Fractional lower_bound = lp->variable_lower_bounds()[col];
597 const Fractional upper_bound = lp->variable_upper_bounds()[col];
598 if (!IsSmallerWithinFeasibilityTolerance(slope_lower_bound[representative],
599 slope)) {
600 // The scaled reduced cost is < 0.
601 variable_can_be_fixed = true;
602 target_bound = (column_factors_[col] >= 0.0) ? upper_bound : lower_bound;
604 slope, slope_upper_bound[representative])) {
605 // The scaled reduced cost is > 0.
606 variable_can_be_fixed = true;
607 target_bound = (column_factors_[col] >= 0.0) ? lower_bound : upper_bound;
608 }
609
610 if (variable_can_be_fixed) {
611 // Clear mapping[col] so this column will not be considered for the next
612 // stage.
613 mapping[col] = kInvalidCol;
614 if (!IsFinite(target_bound)) {
615 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED.";
617 return false;
618 } else {
619 SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
620 column_deletion_helper_.MarkColumnForDeletionWithState(
621 col, target_bound,
622 ComputeVariableStatus(target_bound, lower_bound, upper_bound));
623 }
624 }
625 }
626
627 // Merge the variables with the same scaled cost.
628 std::vector<ColumnWithRepresentativeAndScaledCost> sorted_columns;
629 for (const ColIndex col : proportional_columns) {
630 const ColIndex representative = mapping[col];
631
632 // This test is needed because we already removed some columns.
633 if (mapping[col] != kInvalidCol) {
634 sorted_columns.push_back(ColumnWithRepresentativeAndScaledCost(
635 col, representative,
636 lp->objective_coefficients()[col] / column_factors_[col]));
637 }
638 }
639 std::sort(sorted_columns.begin(), sorted_columns.end());
640
641 // All this will be needed during postsolve.
642 merged_columns_.assign(num_cols, kInvalidCol);
643 lower_bounds_.assign(num_cols, -kInfinity);
644 upper_bounds_.assign(num_cols, kInfinity);
645 new_lower_bounds_.assign(num_cols, -kInfinity);
646 new_upper_bounds_.assign(num_cols, kInfinity);
647
648 for (int i = 0; i < sorted_columns.size();) {
649 const ColIndex target_col = sorted_columns[i].col;
650 const ColIndex target_representative = sorted_columns[i].representative;
651 const Fractional target_scaled_cost = sorted_columns[i].scaled_cost;
652
653 // Save the initial bounds before modifying them.
654 lower_bounds_[target_col] = lp->variable_lower_bounds()[target_col];
655 upper_bounds_[target_col] = lp->variable_upper_bounds()[target_col];
656
657 int num_merged = 0;
658 for (++i; i < sorted_columns.size(); ++i) {
659 if (sorted_columns[i].representative != target_representative) break;
660 if (std::abs(sorted_columns[i].scaled_cost - target_scaled_cost) >=
661 parameters_.preprocessor_zero_tolerance()) {
662 break;
663 }
664 ++num_merged;
665 const ColIndex col = sorted_columns[i].col;
666 const Fractional lower_bound = lp->variable_lower_bounds()[col];
667 const Fractional upper_bound = lp->variable_upper_bounds()[col];
668 lower_bounds_[col] = lower_bound;
669 upper_bounds_[col] = upper_bound;
670 merged_columns_[col] = target_col;
671
672 // This is a bit counter intuitive, but when a column is divided by x,
673 // the corresponding bounds have to be multiplied by x.
674 const Fractional bound_factor =
675 column_factors_[col] / column_factors_[target_col];
676
677 // We need to shift the variable so that a basic solution of the new
678 // problem can easily be converted to a basic solution of the original
679 // problem.
680
681 // A feasible value for the variable must be chosen, and the variable must
682 // be shifted by this value. This is done to make sure that it will be
683 // possible to recreate a basic solution of the original problem from a
684 // basic solution of the pre-solved problem during post-solve.
685 const Fractional target_value =
686 MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
687 Fractional lower_diff = (lower_bound - target_value) * bound_factor;
688 Fractional upper_diff = (upper_bound - target_value) * bound_factor;
689 if (bound_factor < 0.0) {
690 std::swap(lower_diff, upper_diff);
691 }
693 target_col, lp->variable_lower_bounds()[target_col] + lower_diff,
694 lp->variable_upper_bounds()[target_col] + upper_diff);
695 SubtractColumnMultipleFromConstraintBound(col, target_value, lp);
696 column_deletion_helper_.MarkColumnForDeletionWithState(
697 col, target_value,
698 ComputeVariableStatus(target_value, lower_bound, upper_bound));
699 }
700
701 // If at least one column was merged, the target column must be shifted like
702 // the other columns in the same equivalence class for the same reason (see
703 // above).
704 if (num_merged > 0) {
705 merged_columns_[target_col] = target_col;
706 const Fractional target_value = MinInMagnitudeOrZeroIfInfinite(
707 lower_bounds_[target_col], upper_bounds_[target_col]);
709 target_col, lp->variable_lower_bounds()[target_col] - target_value,
710 lp->variable_upper_bounds()[target_col] - target_value);
711 SubtractColumnMultipleFromConstraintBound(target_col, target_value, lp);
712 new_lower_bounds_[target_col] = lp->variable_lower_bounds()[target_col];
713 new_upper_bounds_[target_col] = lp->variable_upper_bounds()[target_col];
714 }
715 }
716
717 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
718 return !column_deletion_helper_.IsEmpty();
719}
720
722 ProblemSolution* solution) const {
725 column_deletion_helper_.RestoreDeletedColumns(solution);
726
727 // The rest of this function is to unmerge the columns so that the solution be
728 // primal-feasible.
729 const ColIndex num_cols = merged_columns_.size();
730 DenseBooleanRow is_representative_basic(num_cols, false);
731 DenseBooleanRow is_distance_to_upper_bound(num_cols, false);
732 DenseRow distance_to_bound(num_cols, 0.0);
733 DenseRow wanted_value(num_cols, 0.0);
734
735 // The first pass is a loop over the representatives to compute the current
736 // distance to the new bounds.
737 for (ColIndex col(0); col < num_cols; ++col) {
738 if (merged_columns_[col] == col) {
739 const Fractional value = solution->primal_values[col];
740 const Fractional distance_to_upper_bound = new_upper_bounds_[col] - value;
741 const Fractional distance_to_lower_bound = value - new_lower_bounds_[col];
742 if (distance_to_upper_bound < distance_to_lower_bound) {
743 distance_to_bound[col] = distance_to_upper_bound;
744 is_distance_to_upper_bound[col] = true;
745 } else {
746 distance_to_bound[col] = distance_to_lower_bound;
747 is_distance_to_upper_bound[col] = false;
748 }
749 is_representative_basic[col] =
750 solution->variable_statuses[col] == VariableStatus::BASIC;
751
752 // Restore the representative value to a feasible value of the initial
753 // variable. Now all the merged variable are at a feasible value.
754 wanted_value[col] = value;
755 solution->primal_values[col] = MinInMagnitudeOrZeroIfInfinite(
756 lower_bounds_[col], upper_bounds_[col]);
757 solution->variable_statuses[col] = ComputeVariableStatus(
758 solution->primal_values[col], lower_bounds_[col], upper_bounds_[col]);
759 }
760 }
761
762 // Second pass to correct the values.
763 for (ColIndex col(0); col < num_cols; ++col) {
764 const ColIndex representative = merged_columns_[col];
765 if (representative != kInvalidCol) {
766 if (IsFinite(distance_to_bound[representative])) {
767 // If the distance is finite, then each variable is set to its
768 // corresponding bound (the one from which the distance is computed) and
769 // is then changed by as much as possible until the distance is zero.
770 const Fractional bound_factor =
771 column_factors_[col] / column_factors_[representative];
772 const Fractional scaled_distance =
773 distance_to_bound[representative] / std::abs(bound_factor);
774 const Fractional width = upper_bounds_[col] - lower_bounds_[col];
775 const bool to_upper_bound =
776 (bound_factor > 0.0) == is_distance_to_upper_bound[representative];
777 if (width <= scaled_distance) {
778 solution->primal_values[col] =
779 to_upper_bound ? lower_bounds_[col] : upper_bounds_[col];
780 solution->variable_statuses[col] =
781 ComputeVariableStatus(solution->primal_values[col],
782 lower_bounds_[col], upper_bounds_[col]);
783 distance_to_bound[representative] -= width * std::abs(bound_factor);
784 } else {
785 solution->primal_values[col] =
786 to_upper_bound ? upper_bounds_[col] - scaled_distance
787 : lower_bounds_[col] + scaled_distance;
788 solution->variable_statuses[col] =
789 is_representative_basic[representative]
791 : ComputeVariableStatus(solution->primal_values[col],
792 lower_bounds_[col],
793 upper_bounds_[col]);
794 distance_to_bound[representative] = 0.0;
795 is_representative_basic[representative] = false;
796 }
797 } else {
798 // If the distance is not finite, then only one variable needs to be
799 // changed from its current feasible value in order to have a
800 // primal-feasible solution.
801 const Fractional error = wanted_value[representative];
802 if (error == 0.0) {
803 if (is_representative_basic[representative]) {
804 solution->variable_statuses[col] = VariableStatus::BASIC;
805 is_representative_basic[representative] = false;
806 }
807 } else {
808 const Fractional bound_factor =
809 column_factors_[col] / column_factors_[representative];
810 const bool use_this_variable =
811 (error * bound_factor > 0.0) ? (upper_bounds_[col] == kInfinity)
812 : (lower_bounds_[col] == -kInfinity);
813 if (use_this_variable) {
814 wanted_value[representative] = 0.0;
815 solution->primal_values[col] += error / bound_factor;
816 if (is_representative_basic[representative]) {
817 solution->variable_statuses[col] = VariableStatus::BASIC;
818 is_representative_basic[representative] = false;
819 } else {
820 // This should not happen on an OPTIMAL or FEASIBLE solution.
821 DCHECK(solution->status != ProblemStatus::OPTIMAL &&
823 solution->variable_statuses[col] = VariableStatus::FREE;
824 }
825 }
826 }
827 }
828 }
829 }
830}
831
832// --------------------------------------------------------
833// ProportionalRowPreprocessor
834// --------------------------------------------------------
835
838 RETURN_VALUE_IF_NULL(lp, false);
839 const RowIndex num_rows = lp->num_constraints();
840 const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
841
842 // Use the first coefficient of each row to compute the proportionality
843 // factor. Note that the sign is important.
844 //
845 // Note(user): using the first coefficient may not give the best precision.
846 row_factors_.assign(num_rows, 0.0);
847 for (RowIndex row(0); row < num_rows; ++row) {
848 const SparseColumn& row_transpose = transpose.column(RowToColIndex(row));
849 if (!row_transpose.IsEmpty()) {
850 row_factors_[row] = row_transpose.GetFirstCoefficient();
851 }
852 }
853
854 // The new row bounds (only meaningful for the proportional rows).
855 DenseColumn lower_bounds(num_rows, -kInfinity);
856 DenseColumn upper_bounds(num_rows, +kInfinity);
857
858 // Where the new bounds are coming from. Only for the constraints that stay
859 // in the lp and are modified, kInvalidRow otherwise.
860 upper_bound_sources_.assign(num_rows, kInvalidRow);
861 lower_bound_sources_.assign(num_rows, kInvalidRow);
862
863 // Initialization.
864 // We need the first representative of each proportional row class to point to
865 // itself for the loop below. TODO(user): Already return such a mapping from
866 // FindProportionalColumns()?
869 DenseBooleanColumn is_a_representative(num_rows, false);
870 int num_proportional_rows = 0;
871 for (RowIndex row(0); row < num_rows; ++row) {
872 const ColIndex representative_row_as_col = mapping[RowToColIndex(row)];
873 if (representative_row_as_col != kInvalidCol) {
874 mapping[representative_row_as_col] = representative_row_as_col;
875 is_a_representative[ColToRowIndex(representative_row_as_col)] = true;
876 ++num_proportional_rows;
877 }
878 }
879
880 // Compute the bound of each representative as implied by the rows
881 // which are proportional to it. Also keep the source row of each bound.
882 for (RowIndex row(0); row < num_rows; ++row) {
883 const ColIndex row_as_col = RowToColIndex(row);
884 if (mapping[row_as_col] != kInvalidCol) {
885 // For now, delete all the rows that are proportional to another one.
886 // Note that we will unmark the final representative of this class later.
887 row_deletion_helper_.MarkRowForDeletion(row);
888 const RowIndex representative_row = ColToRowIndex(mapping[row_as_col]);
889
890 const Fractional factor =
891 row_factors_[representative_row] / row_factors_[row];
892 Fractional implied_lb = factor * lp->constraint_lower_bounds()[row];
893 Fractional implied_ub = factor * lp->constraint_upper_bounds()[row];
894 if (factor < 0.0) {
895 std::swap(implied_lb, implied_ub);
896 }
897
898 // TODO(user): if the bounds are equal, use the largest row in magnitude?
899 if (implied_lb >= lower_bounds[representative_row]) {
900 lower_bounds[representative_row] = implied_lb;
901 lower_bound_sources_[representative_row] = row;
902 }
903 if (implied_ub <= upper_bounds[representative_row]) {
904 upper_bounds[representative_row] = implied_ub;
905 upper_bound_sources_[representative_row] = row;
906 }
907 }
908 }
909
910 // For maximum precision, and also to simplify the postsolve, we choose
911 // a representative for each class of proportional columns that has at least
912 // one of the two tightest bounds.
913 for (RowIndex row(0); row < num_rows; ++row) {
914 if (!is_a_representative[row]) continue;
915 const RowIndex lower_source = lower_bound_sources_[row];
916 const RowIndex upper_source = upper_bound_sources_[row];
917 lower_bound_sources_[row] = kInvalidRow;
918 upper_bound_sources_[row] = kInvalidRow;
919 DCHECK_NE(lower_source, kInvalidRow);
920 DCHECK_NE(upper_source, kInvalidRow);
921 if (lower_source == upper_source) {
922 // In this case, a simple change of representative is enough.
923 // The constraint bounds of the representative will not change.
924 DCHECK_NE(lower_source, kInvalidRow);
925 row_deletion_helper_.UnmarkRow(lower_source);
926 } else {
927 // Report ProblemStatus::PRIMAL_INFEASIBLE if the new lower bound is not
928 // lower than the new upper bound modulo the default tolerance.
929 if (!IsSmallerWithinFeasibilityTolerance(lower_bounds[row],
930 upper_bounds[row])) {
932 return false;
933 }
934
935 // Special case for fixed rows.
936 if (lp->constraint_lower_bounds()[lower_source] ==
937 lp->constraint_upper_bounds()[lower_source]) {
938 row_deletion_helper_.UnmarkRow(lower_source);
939 continue;
940 }
941 if (lp->constraint_lower_bounds()[upper_source] ==
942 lp->constraint_upper_bounds()[upper_source]) {
943 row_deletion_helper_.UnmarkRow(upper_source);
944 continue;
945 }
946
947 // This is the only case where a more complex postsolve is needed.
948 // To maximize precision, the class representative is changed to either
949 // upper_source or lower_source depending of which row has the largest
950 // proportionality factor.
951 RowIndex new_representative = lower_source;
952 RowIndex other = upper_source;
953 if (std::abs(row_factors_[new_representative]) <
954 std::abs(row_factors_[other])) {
955 std::swap(new_representative, other);
956 }
957
958 // Initialize the new bounds with the implied ones.
959 const Fractional factor =
960 row_factors_[new_representative] / row_factors_[other];
961 Fractional new_lb = factor * lp->constraint_lower_bounds()[other];
962 Fractional new_ub = factor * lp->constraint_upper_bounds()[other];
963 if (factor < 0.0) {
964 std::swap(new_lb, new_ub);
965 }
966
967 lower_bound_sources_[new_representative] = new_representative;
968 upper_bound_sources_[new_representative] = new_representative;
969
970 if (new_lb > lp->constraint_lower_bounds()[new_representative]) {
971 lower_bound_sources_[new_representative] = other;
972 } else {
973 new_lb = lp->constraint_lower_bounds()[new_representative];
974 }
975 if (new_ub < lp->constraint_upper_bounds()[new_representative]) {
976 upper_bound_sources_[new_representative] = other;
977 } else {
978 new_ub = lp->constraint_upper_bounds()[new_representative];
979 }
980 const RowIndex new_lower_source =
981 lower_bound_sources_[new_representative];
982 if (new_lower_source == upper_bound_sources_[new_representative]) {
983 row_deletion_helper_.UnmarkRow(new_lower_source);
984 lower_bound_sources_[new_representative] = kInvalidRow;
985 upper_bound_sources_[new_representative] = kInvalidRow;
986 continue;
987 }
988
989 // Take care of small numerical imprecision by making sure that lb <= ub.
990 // Note that if the imprecision was greater than the tolerance, the code
991 // at the beginning of this block would have reported
992 // ProblemStatus::PRIMAL_INFEASIBLE.
993 DCHECK(IsSmallerWithinFeasibilityTolerance(new_lb, new_ub));
994 if (new_lb > new_ub) {
995 if (lower_bound_sources_[new_representative] == new_representative) {
996 new_ub = lp->constraint_lower_bounds()[new_representative];
997 } else {
998 new_lb = lp->constraint_upper_bounds()[new_representative];
999 }
1000 }
1001 row_deletion_helper_.UnmarkRow(new_representative);
1002 lp->SetConstraintBounds(new_representative, new_lb, new_ub);
1003 }
1004 }
1005
1006 lp_is_maximization_problem_ = lp->IsMaximizationProblem();
1007 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1008 return !row_deletion_helper_.IsEmpty();
1009}
1010
1012 ProblemSolution* solution) const {
1015 row_deletion_helper_.RestoreDeletedRows(solution);
1016
1017 // Make sure that all non-zero dual values on the proportional rows are
1018 // assigned to the correct row with the correct sign and that the statuses
1019 // are correct.
1020 const RowIndex num_rows = solution->dual_values.size();
1021 for (RowIndex row(0); row < num_rows; ++row) {
1022 const RowIndex lower_source = lower_bound_sources_[row];
1023 const RowIndex upper_source = upper_bound_sources_[row];
1024 if (lower_source == kInvalidRow && upper_source == kInvalidRow) continue;
1025 DCHECK_NE(lower_source, upper_source);
1026 DCHECK(lower_source == row || upper_source == row);
1027
1028 // If the representative is ConstraintStatus::BASIC, then all rows in this
1029 // class will be ConstraintStatus::BASIC and there is nothing to do.
1030 ConstraintStatus status = solution->constraint_statuses[row];
1031 if (status == ConstraintStatus::BASIC) continue;
1032
1033 // If the row is FIXED it will behave as a row
1034 // ConstraintStatus::AT_UPPER_BOUND or
1035 // ConstraintStatus::AT_LOWER_BOUND depending on the corresponding dual
1036 // variable sign.
1038 const Fractional corrected_dual_value = lp_is_maximization_problem_
1039 ? -solution->dual_values[row]
1040 : solution->dual_values[row];
1041 if (corrected_dual_value != 0.0) {
1042 status = corrected_dual_value > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1044 }
1045 }
1046
1047 // If one of the two conditions below are true, set the row status to
1048 // ConstraintStatus::BASIC.
1049 // Note that the source which is not row can't be FIXED (see presolve).
1050 if (lower_source != row && status == ConstraintStatus::AT_LOWER_BOUND) {
1051 DCHECK_EQ(0.0, solution->dual_values[lower_source]);
1052 const Fractional factor = row_factors_[row] / row_factors_[lower_source];
1053 solution->dual_values[lower_source] = factor * solution->dual_values[row];
1054 solution->dual_values[row] = 0.0;
1055 solution->constraint_statuses[row] = ConstraintStatus::BASIC;
1056 solution->constraint_statuses[lower_source] =
1059 }
1060 if (upper_source != row && status == ConstraintStatus::AT_UPPER_BOUND) {
1061 DCHECK_EQ(0.0, solution->dual_values[upper_source]);
1062 const Fractional factor = row_factors_[row] / row_factors_[upper_source];
1063 solution->dual_values[upper_source] = factor * solution->dual_values[row];
1064 solution->dual_values[row] = 0.0;
1065 solution->constraint_statuses[row] = ConstraintStatus::BASIC;
1066 solution->constraint_statuses[upper_source] =
1069 }
1070
1071 // If the row status is still ConstraintStatus::FIXED_VALUE, we need to
1072 // relax its status.
1073 if (solution->constraint_statuses[row] == ConstraintStatus::FIXED_VALUE) {
1074 solution->constraint_statuses[row] =
1075 lower_source != row ? ConstraintStatus::AT_UPPER_BOUND
1077 }
1078 }
1079}
1080
1081// --------------------------------------------------------
1082// FixedVariablePreprocessor
1083// --------------------------------------------------------
1084
1087 RETURN_VALUE_IF_NULL(lp, false);
1088 const ColIndex num_cols = lp->num_variables();
1089 for (ColIndex col(0); col < num_cols; ++col) {
1090 const Fractional lower_bound = lp->variable_lower_bounds()[col];
1091 const Fractional upper_bound = lp->variable_upper_bounds()[col];
1092 if (lower_bound == upper_bound) {
1093 const Fractional fixed_value = lower_bound;
1094 DCHECK(IsFinite(fixed_value));
1095
1096 // We need to change the constraint bounds.
1097 SubtractColumnMultipleFromConstraintBound(col, fixed_value, lp);
1098 column_deletion_helper_.MarkColumnForDeletionWithState(
1099 col, fixed_value, VariableStatus::FIXED_VALUE);
1100 }
1102
1103 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1104 return !column_deletion_helper_.IsEmpty();
1105}
1106
1108 ProblemSolution* solution) const {
1111 column_deletion_helper_.RestoreDeletedColumns(solution);
1112}
1113
1114// --------------------------------------------------------
1115// ForcingAndImpliedFreeConstraintPreprocessor
1116// --------------------------------------------------------
1117
1120 RETURN_VALUE_IF_NULL(lp, false);
1121 const RowIndex num_rows = lp->num_constraints();
1122
1123 // Compute the implied constraint bounds from the variable bounds.
1124 DenseColumn implied_lower_bounds(num_rows, 0);
1125 DenseColumn implied_upper_bounds(num_rows, 0);
1126 const ColIndex num_cols = lp->num_variables();
1127 StrictITIVector<RowIndex, int> row_degree(num_rows, 0);
1128 for (ColIndex col(0); col < num_cols; ++col) {
1129 const Fractional lower = lp->variable_lower_bounds()[col];
1130 const Fractional upper = lp->variable_upper_bounds()[col];
1131 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1132 const RowIndex row = e.row();
1133 const Fractional coeff = e.coefficient();
1134 if (coeff > 0.0) {
1135 implied_lower_bounds[row] += lower * coeff;
1136 implied_upper_bounds[row] += upper * coeff;
1137 } else {
1138 implied_lower_bounds[row] += upper * coeff;
1139 implied_upper_bounds[row] += lower * coeff;
1140 }
1141 ++row_degree[row];
1142 }
1143 }
1144
1145 // Note that the ScalingPreprocessor is currently executed last, so here the
1146 // problem has not been scaled yet.
1147 int num_implied_free_constraints = 0;
1148 int num_forcing_constraints = 0;
1149 is_forcing_up_.assign(num_rows, false);
1150 DenseBooleanColumn is_forcing_down(num_rows, false);
1151 for (RowIndex row(0); row < num_rows; ++row) {
1152 if (row_degree[row] == 0) continue;
1153 Fractional lower = lp->constraint_lower_bounds()[row];
1154 Fractional upper = lp->constraint_upper_bounds()[row];
1155
1156 // Check for infeasibility.
1158 implied_upper_bounds[row]) ||
1159 !IsSmallerWithinFeasibilityTolerance(implied_lower_bounds[row],
1160 upper)) {
1161 VLOG(1) << "implied bound " << implied_lower_bounds[row] << " "
1162 << implied_upper_bounds[row];
1163 VLOG(1) << "constraint bound " << lower << " " << upper;
1165 return false;
1166 }
1167
1168 // Check if the constraint is forcing. That is, all the variables that
1169 // appear in it must be at one of their bounds.
1170 if (IsSmallerWithinPreprocessorZeroTolerance(implied_upper_bounds[row],
1171 lower)) {
1172 is_forcing_down[row] = true;
1173 ++num_forcing_constraints;
1174 continue;
1175 }
1177 implied_lower_bounds[row])) {
1178 is_forcing_up_[row] = true;
1179 ++num_forcing_constraints;
1180 continue;
1181 }
1182
1183 // We relax the constraint bounds only if the constraint is implied to be
1184 // free. Such constraints will later be deleted by the
1185 // FreeConstraintPreprocessor.
1186 //
1187 // Note that we could relax only one of the two bounds, but the impact this
1188 // would have on the revised simplex algorithm is unclear at this point.
1190 implied_lower_bounds[row]) &&
1191 IsSmallerWithinPreprocessorZeroTolerance(implied_upper_bounds[row],
1192 upper)) {
1194 ++num_implied_free_constraints;
1195 }
1196 }
1197
1198 if (num_implied_free_constraints > 0) {
1199 VLOG(1) << num_implied_free_constraints << " implied free constraints.";
1200 }
1201
1202 if (num_forcing_constraints > 0) {
1203 VLOG(1) << num_forcing_constraints << " forcing constraints.";
1204 lp_is_maximization_problem_ = lp->IsMaximizationProblem();
1205 costs_.resize(num_cols, 0.0);
1206 for (ColIndex col(0); col < num_cols; ++col) {
1207 const SparseColumn& column = lp->GetSparseColumn(col);
1208 const Fractional lower = lp->variable_lower_bounds()[col];
1209 const Fractional upper = lp->variable_upper_bounds()[col];
1210 bool is_forced = false;
1211 Fractional target_bound = 0.0;
1212 for (const SparseColumn::Entry e : column) {
1213 if (is_forcing_down[e.row()]) {
1214 const Fractional candidate = e.coefficient() < 0.0 ? lower : upper;
1215 if (is_forced && candidate != target_bound) {
1216 // The bounds are really close, so we fix to the bound with
1217 // the lowest magnitude. As of 2019/11/19, this is "better" than
1218 // fixing to the mid-point, because at postsolve, we always put
1219 // non-basic variables to their exact bounds (so, with mid-point
1220 // there would be a difference of epsilon/2 between the inner
1221 // solution and the postsolved one, which might cause issues).
1222 if (IsSmallerWithinPreprocessorZeroTolerance(upper, lower)) {
1223 target_bound = std::abs(lower) < std::abs(upper) ? lower : upper;
1224 continue;
1225 }
1226 VLOG(1) << "A variable is forced in both directions! bounds: ["
1227 << std::fixed << std::setprecision(10) << lower << ", "
1228 << upper << "]. coeff:" << e.coefficient();
1230 return false;
1231 }
1232 target_bound = candidate;
1233 is_forced = true;
1234 }
1235 if (is_forcing_up_[e.row()]) {
1236 const Fractional candidate = e.coefficient() < 0.0 ? upper : lower;
1237 if (is_forced && candidate != target_bound) {
1238 // The bounds are really close, so we fix to the bound with
1239 // the lowest magnitude.
1240 if (IsSmallerWithinPreprocessorZeroTolerance(upper, lower)) {
1241 target_bound = std::abs(lower) < std::abs(upper) ? lower : upper;
1242 continue;
1243 }
1244 VLOG(1) << "A variable is forced in both directions! bounds: ["
1245 << std::fixed << std::setprecision(10) << lower << ", "
1246 << upper << "]. coeff:" << e.coefficient();
1248 return false;
1249 }
1250 target_bound = candidate;
1251 is_forced = true;
1252 }
1253 }
1254 if (is_forced) {
1255 // Fix the variable, update the constraint bounds and save this column
1256 // and its cost for the postsolve.
1257 SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
1258 column_deletion_helper_.MarkColumnForDeletionWithState(
1259 col, target_bound,
1260 ComputeVariableStatus(target_bound, lower, upper));
1261 columns_saver_.SaveColumn(col, column);
1262 costs_[col] = lp->objective_coefficients()[col];
1263 }
1264 }
1265 for (RowIndex row(0); row < num_rows; ++row) {
1266 // In theory, an M exists such that for any magnitude >= M, we will be at
1267 // an optimal solution. However, because of numerical errors, if the value
1268 // is too large, it causes problem when verifying the solution. So we
1269 // select the smallest such M (at least a resonably small one) during
1270 // postsolve. It is the reason why we need to store the columns that were
1271 // fixed.
1272 if (is_forcing_down[row] || is_forcing_up_[row]) {
1273 row_deletion_helper_.MarkRowForDeletion(row);
1274 }
1275 }
1276 }
1277
1278 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1279 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1280 return !column_deletion_helper_.IsEmpty();
1281}
1282
1284 ProblemSolution* solution) const {
1287 column_deletion_helper_.RestoreDeletedColumns(solution);
1288 row_deletion_helper_.RestoreDeletedRows(solution);
1289
1290 struct DeletionEntry {
1291 RowIndex row;
1292 ColIndex col;
1293 Fractional coefficient;
1294 };
1295 std::vector<DeletionEntry> entries;
1296
1297 // Compute for each deleted columns the last deleted row in which it appears.
1298 const ColIndex size = column_deletion_helper_.GetMarkedColumns().size();
1299 for (ColIndex col(0); col < size; ++col) {
1300 if (!column_deletion_helper_.IsColumnMarked(col)) continue;
1302 RowIndex last_row = kInvalidRow;
1303 Fractional last_coefficient;
1304 for (const SparseColumn::Entry e : columns_saver_.SavedColumn(col)) {
1305 const RowIndex row = e.row();
1306 if (row_deletion_helper_.IsRowMarked(row)) {
1307 last_row = row;
1308 last_coefficient = e.coefficient();
1309 }
1310 }
1311 if (last_row != kInvalidRow) {
1312 entries.push_back({last_row, col, last_coefficient});
1313 }
1314 }
1315
1316 // Sort by row first and then col.
1317 std::sort(entries.begin(), entries.end(),
1318 [](const DeletionEntry& a, const DeletionEntry& b) {
1319 if (a.row == b.row) return a.col < b.col;
1320 return a.row < b.row;
1321 });
1322
1323 // For each deleted row (in order), compute a bound on the dual values so
1324 // that all the deleted columns for which this row is the last deleted row are
1325 // dual-feasible. Note that for the other columns, it will always be possible
1326 // to make them dual-feasible with a later row.
1327 // There are two possible outcomes:
1328 // - The dual value stays 0.0, and nothing changes.
1329 // - The bounds enforce a non-zero dual value, and one column will have a
1330 // reduced cost of 0.0. This column becomes VariableStatus::BASIC, and the
1331 // constraint status is changed to ConstraintStatus::AT_LOWER_BOUND,
1332 // ConstraintStatus::AT_UPPER_BOUND or ConstraintStatus::FIXED_VALUE.
1333 for (int i = 0; i < entries.size();) {
1334 const RowIndex row = entries[i].row;
1335 DCHECK(row_deletion_helper_.IsRowMarked(row));
1336
1337 // Process column with this last deleted row.
1338 Fractional new_dual_value = 0.0;
1339 ColIndex new_basic_column = kInvalidCol;
1340 for (; i < entries.size(); ++i) {
1341 if (entries[i].row != row) break;
1342 const ColIndex col = entries[i].col;
1343
1344 const Fractional scalar_product =
1345 ScalarProduct(solution->dual_values, columns_saver_.SavedColumn(col));
1346 const Fractional reduced_cost = costs_[col] - scalar_product;
1347 const Fractional bound = reduced_cost / entries[i].coefficient;
1348 if (is_forcing_up_[row] == !lp_is_maximization_problem_) {
1349 if (bound < new_dual_value) {
1350 new_dual_value = bound;
1351 new_basic_column = col;
1352 }
1353 } else {
1354 if (bound > new_dual_value) {
1355 new_dual_value = bound;
1356 new_basic_column = col;
1357 }
1358 }
1359 }
1360 if (new_basic_column != kInvalidCol) {
1361 solution->dual_values[row] = new_dual_value;
1362 solution->variable_statuses[new_basic_column] = VariableStatus::BASIC;
1363 solution->constraint_statuses[row] =
1364 is_forcing_up_[row] ? ConstraintStatus::AT_UPPER_BOUND
1366 }
1367 }
1368}
1369
1370// --------------------------------------------------------
1371// ImpliedFreePreprocessor
1372// --------------------------------------------------------
1373
1376 RETURN_VALUE_IF_NULL(lp, false);
1377 if (!parameters_.use_implied_free_preprocessor()) return false;
1378 const RowIndex num_rows = lp->num_constraints();
1379 const ColIndex num_cols = lp->num_variables();
1380
1381 // For each constraint with n entries and each of its variable, we want the
1382 // bounds implied by the (n - 1) other variables and the constraint. We
1383 // use two handy utility classes that allow us to do that efficiently while
1384 // dealing properly with infinite bounds.
1385 const int size = num_rows.value();
1386 // TODO(user) : Replace SumWithNegativeInfiniteAndOneMissing and
1387 // SumWithPositiveInfiniteAndOneMissing with IntervalSumWithOneMissing.
1388 util_intops::StrongVector<RowIndex, SumWithNegativeInfiniteAndOneMissing>
1389 lb_sums(size);
1390 util_intops::StrongVector<RowIndex, SumWithPositiveInfiniteAndOneMissing>
1391 ub_sums(size);
1392
1393 // Initialize the sums by adding all the bounds of the variables.
1394 for (ColIndex col(0); col < num_cols; ++col) {
1395 const Fractional lower_bound = lp->variable_lower_bounds()[col];
1396 const Fractional upper_bound = lp->variable_upper_bounds()[col];
1397 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1398 Fractional entry_lb = e.coefficient() * lower_bound;
1399 Fractional entry_ub = e.coefficient() * upper_bound;
1400 if (e.coefficient() < 0.0) std::swap(entry_lb, entry_ub);
1401 lb_sums[e.row()].Add(entry_lb);
1402 ub_sums[e.row()].Add(entry_ub);
1403 }
1404 }
1405
1406 // The inequality
1407 // constraint_lb <= sum(entries) <= constraint_ub
1408 // can be rewritten as:
1409 // sum(entries) + (-activity) = 0,
1410 // where (-activity) has bounds [-constraint_ub, -constraint_lb].
1411 // We use this latter convention to simplify our code.
1412 for (RowIndex row(0); row < num_rows; ++row) {
1413 lb_sums[row].Add(-lp->constraint_upper_bounds()[row]);
1414 ub_sums[row].Add(-lp->constraint_lower_bounds()[row]);
1415 }
1416
1417 // Once a variable is freed, none of the rows in which it appears can be
1418 // used to make another variable free.
1419 DenseBooleanColumn used_rows(num_rows, false);
1420 postsolve_status_of_free_variables_.assign(num_cols, VariableStatus::FREE);
1421 variable_offsets_.assign(num_cols, 0.0);
1422
1423 // It is better to process columns with a small degree first:
1424 // - Degree-two columns make it possible to remove a row from the problem.
1425 // - This way there is more chance to make more free columns.
1426 // - It is better to have low degree free columns since a free column will
1427 // always end up in the simplex basis (except if there is more than the
1428 // number of rows in the problem).
1429 //
1430 // TODO(user): Only process degree-two so in subsequent passes more degree-two
1431 // columns could be made free. And only when no other reduction can be
1432 // applied, process the higher degree column?
1433 //
1434 // TODO(user): Be smarter about the order that maximizes the number of free
1435 // column. For instance if we have 3 doubleton columns that use the rows (1,2)
1436 // (2,3) and (3,4) then it is better not to make (2,3) free so the two other
1437 // two can be made free.
1438 std::vector<std::pair<EntryIndex, ColIndex>> col_by_degree;
1439 col_by_degree.reserve(num_cols.value());
1440 for (ColIndex col(0); col < num_cols; ++col) {
1441 col_by_degree.push_back({lp->GetSparseColumn(col).num_entries(), col});
1442 }
1443 std::sort(col_by_degree.begin(), col_by_degree.end());
1444
1445 // Now loop over the columns in order and make all implied-free columns free.
1446 int num_already_free_variables = 0;
1447 int num_implied_free_variables = 0;
1448 int num_fixed_variables = 0;
1449 for (const auto [_, col] : col_by_degree) {
1450 // If the variable is already free or fixed, we do nothing.
1451 const Fractional lower_bound = lp->variable_lower_bounds()[col];
1452 const Fractional upper_bound = lp->variable_upper_bounds()[col];
1453 if (!IsFinite(lower_bound) && !IsFinite(upper_bound)) {
1454 ++num_already_free_variables;
1455 continue;
1456 }
1457 if (lower_bound == upper_bound) continue;
1458
1459 // Detect if the variable is implied free.
1460 Fractional overall_implied_lb = -kInfinity;
1461 Fractional overall_implied_ub = kInfinity;
1462 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1463 // If the row contains another implied free variable, then the bounds
1464 // implied by it will just be [-kInfinity, kInfinity] so we can skip it.
1465 if (used_rows[e.row()]) continue;
1466
1467 // This is the contribution of this column to the sum above.
1468 const Fractional coeff = e.coefficient();
1469 Fractional entry_lb = coeff * lower_bound;
1470 Fractional entry_ub = coeff * upper_bound;
1471 if (coeff < 0.0) std::swap(entry_lb, entry_ub);
1472
1473 // If X is the variable with index col and Y the sum of all the other
1474 // variables and of (-activity), then coeff * X + Y = 0. Since Y's bounds
1475 // are [lb_sum without X, ub_sum without X], it is easy to derive the
1476 // implied bounds on X.
1477 //
1478 // Important: If entry_lb (resp. entry_ub) are large, we cannot have a
1479 // good precision on the sum without. So we do add a defensive tolerance
1480 // that depends on these magnitude.
1481 const Fractional implied_lb =
1482 coeff > 0.0 ? -ub_sums[e.row()].SumWithoutUb(entry_ub) / coeff
1483 : -lb_sums[e.row()].SumWithoutLb(entry_lb) / coeff;
1484 const Fractional implied_ub =
1485 coeff > 0.0 ? -lb_sums[e.row()].SumWithoutLb(entry_lb) / coeff
1486 : -ub_sums[e.row()].SumWithoutUb(entry_ub) / coeff;
1487
1488 overall_implied_lb = std::max(overall_implied_lb, implied_lb);
1489 overall_implied_ub = std::min(overall_implied_ub, implied_ub);
1490 }
1491
1492 // Detect infeasible cases.
1493 if (!IsSmallerWithinFeasibilityTolerance(overall_implied_lb, upper_bound) ||
1494 !IsSmallerWithinFeasibilityTolerance(lower_bound, overall_implied_ub) ||
1495 !IsSmallerWithinFeasibilityTolerance(overall_implied_lb,
1496 overall_implied_ub)) {
1498 return false;
1499 }
1500
1501 // Detect fixed variable cases (there are two kinds).
1502 // Note that currently we don't do anything here except counting them.
1504 overall_implied_lb) ||
1506 lower_bound)) {
1507 // This case is already dealt with by the
1508 // ForcingAndImpliedFreeConstraintPreprocessor since it means that (at
1509 // least) one of the row is forcing.
1510 ++num_fixed_variables;
1511 continue;
1512 } else if (IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1513 overall_implied_lb)) {
1514 // TODO(user): As of July 2013, with our preprocessors this case is never
1515 // triggered on the Netlib. Note however that if it appears it can have a
1516 // big impact since by fixing the variable, the two involved constraints
1517 // are forcing and can be removed too (with all the variables they touch).
1518 // The postsolve step is quite involved though.
1519 ++num_fixed_variables;
1520 continue;
1521 }
1522
1523 // Is the variable implied free? Note that for an infinite lower_bound or
1524 // upper_bound the respective inequality is always true.
1526 overall_implied_lb) &&
1528 upper_bound)) {
1529 ++num_implied_free_variables;
1531 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1532 used_rows[e.row()] = true;
1533 }
1534
1535 // This is a tricky part. We're freeing this variable, which means that
1536 // after solve, the modified variable will have status either
1537 // VariableStatus::FREE or VariableStatus::BASIC. In the former case
1538 // (VariableStatus::FREE, value = 0.0), we need to "fix" the
1539 // status (technically, our variable isn't free!) to either
1540 // VariableStatus::AT_LOWER_BOUND or VariableStatus::AT_UPPER_BOUND
1541 // (note that we skipped fixed variables), and "fix" the value to that
1542 // bound's value as well. We make the decision and the precomputation
1543 // here: we simply offset the variable by one of its bounds, and store
1544 // which bound that was. Note that if the modified variable turns out to
1545 // be VariableStatus::BASIC, we'll simply un-offset its value too;
1546 // and let the status be VariableStatus::BASIC.
1547 //
1548 // TODO(user): This trick is already used in the DualizerPreprocessor,
1549 // maybe we should just have a preprocessor that shifts all the variables
1550 // bounds to have at least one of them at 0.0, will that improve precision
1551 // and speed of the simplex? One advantage is that we can compute the
1552 // new constraint bounds with better precision using AccurateSum.
1553 DCHECK_NE(lower_bound, upper_bound);
1554 const Fractional offset =
1555 MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
1556 if (offset != 0.0) {
1557 variable_offsets_[col] = offset;
1558 SubtractColumnMultipleFromConstraintBound(col, offset, lp);
1559 }
1560 postsolve_status_of_free_variables_[col] =
1561 ComputeVariableStatus(offset, lower_bound, upper_bound);
1562 }
1563 }
1564 VLOG(1) << num_already_free_variables << " free variables in the problem.";
1565 VLOG(1) << num_implied_free_variables << " implied free columns.";
1566 VLOG(1) << num_fixed_variables << " variables can be fixed.";
1567
1568 return num_implied_free_variables > 0;
1569}
1570
1574 const ColIndex num_cols = solution->variable_statuses.size();
1575 for (ColIndex col(0); col < num_cols; ++col) {
1576 // Skip variables that the preprocessor didn't change.
1577 if (postsolve_status_of_free_variables_[col] == VariableStatus::FREE) {
1578 DCHECK_EQ(0.0, variable_offsets_[col]);
1579 continue;
1580 }
1581 if (solution->variable_statuses[col] == VariableStatus::FREE) {
1582 solution->variable_statuses[col] =
1583 postsolve_status_of_free_variables_[col];
1584 } else {
1585 DCHECK_EQ(VariableStatus::BASIC, solution->variable_statuses[col]);
1586 }
1587 solution->primal_values[col] += variable_offsets_[col];
1588 }
1589}
1590
1591// --------------------------------------------------------
1592// DoubletonFreeColumnPreprocessor
1593// --------------------------------------------------------
1594
1597 RETURN_VALUE_IF_NULL(lp, false);
1598 // We will modify the matrix transpose and then push the change to the linear
1599 // program by calling lp->UseTransposeMatrixAsReference(). Note
1600 // that original_matrix will not change during this preprocessor run.
1601 const SparseMatrix& original_matrix = lp->GetSparseMatrix();
1603
1604 const ColIndex num_cols(lp->num_variables());
1605 for (ColIndex doubleton_col(0); doubleton_col < num_cols; ++doubleton_col) {
1606 // Only consider doubleton free columns.
1607 if (original_matrix.column(doubleton_col).num_entries() != 2) continue;
1608 if (lp->variable_lower_bounds()[doubleton_col] != -kInfinity) continue;
1609 if (lp->variable_upper_bounds()[doubleton_col] != kInfinity) continue;
1610
1611 // Collect the two column items. Note that we skip a column involving a
1612 // deleted row since it is no longer a doubleton then.
1613 RestoreInfo r;
1614 r.col = doubleton_col;
1615 r.objective_coefficient = lp->objective_coefficients()[r.col];
1616 int index = 0;
1617 for (const SparseColumn::Entry e : original_matrix.column(r.col)) {
1618 if (row_deletion_helper_.IsRowMarked(e.row())) break;
1619 r.row[index] = e.row();
1620 r.coeff[index] = e.coefficient();
1621 DCHECK_NE(0.0, e.coefficient());
1622 ++index;
1623 }
1624 if (index != NUM_ROWS) continue;
1625
1626 // Since the column didn't touch any previously deleted row, we are sure
1627 // that the coefficients were left untouched.
1628 DCHECK_EQ(r.coeff[DELETED], transpose->column(RowToColIndex(r.row[DELETED]))
1630 DCHECK_EQ(r.coeff[MODIFIED],
1631 transpose->column(RowToColIndex(r.row[MODIFIED]))
1633
1634 // We prefer deleting the row with the larger coefficient magnitude because
1635 // we will divide by this magnitude. TODO(user): Impact?
1636 if (std::abs(r.coeff[DELETED]) < std::abs(r.coeff[MODIFIED])) {
1637 std::swap(r.coeff[DELETED], r.coeff[MODIFIED]);
1638 std::swap(r.row[DELETED], r.row[MODIFIED]);
1639 }
1640
1641 // Save the deleted row for postsolve. Note that we remove it from the
1642 // transpose at the same time. This last operation is not strictly needed,
1643 // but it is faster to do it this way (both here and later when we will take
1644 // the transpose of the final transpose matrix).
1645 r.deleted_row_as_column.Swap(
1646 transpose->mutable_column(RowToColIndex(r.row[DELETED])));
1647
1648 // Move the bound of the deleted constraint to the initially free variable.
1649 {
1650 Fractional new_variable_lb =
1651 lp->constraint_lower_bounds()[r.row[DELETED]];
1652 Fractional new_variable_ub =
1653 lp->constraint_upper_bounds()[r.row[DELETED]];
1654 new_variable_lb /= r.coeff[DELETED];
1655 new_variable_ub /= r.coeff[DELETED];
1656 if (r.coeff[DELETED] < 0.0) std::swap(new_variable_lb, new_variable_ub);
1657 lp->SetVariableBounds(r.col, new_variable_lb, new_variable_ub);
1658 }
1659
1660 // Add a multiple of the deleted row to the modified row except on
1661 // column r.col where the coefficient will be left unchanged.
1662 r.deleted_row_as_column.AddMultipleToSparseVectorAndIgnoreCommonIndex(
1663 -r.coeff[MODIFIED] / r.coeff[DELETED], ColToRowIndex(r.col),
1665 transpose->mutable_column(RowToColIndex(r.row[MODIFIED])));
1666
1667 // We also need to correct the objective value of the variables involved in
1668 // the deleted row.
1669 if (r.objective_coefficient != 0.0) {
1670 for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1671 const ColIndex col = RowToColIndex(e.row());
1672 if (col == r.col) continue;
1673 const Fractional new_objective =
1674 lp->objective_coefficients()[col] -
1675 e.coefficient() * r.objective_coefficient / r.coeff[DELETED];
1676
1677 // This detects if the objective should actually be zero, but because of
1678 // the numerical error in the formula above, we have a really low
1679 // objective instead. The logic is the same as in
1680 // AddMultipleToSparseVectorAndIgnoreCommonIndex().
1681 if (std::abs(new_objective) > parameters_.drop_tolerance()) {
1682 lp->SetObjectiveCoefficient(col, new_objective);
1683 } else {
1684 lp->SetObjectiveCoefficient(col, 0.0);
1685 }
1686 }
1687 }
1688 row_deletion_helper_.MarkRowForDeletion(r.row[DELETED]);
1689 restore_stack_.push_back(r);
1690 }
1691
1692 if (!row_deletion_helper_.IsEmpty()) {
1693 // The order is important.
1695 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1696 return true;
1697 }
1698 return false;
1699}
1700
1702 ProblemSolution* solution) const {
1704 row_deletion_helper_.RestoreDeletedRows(solution);
1705 for (const RestoreInfo& r : Reverse(restore_stack_)) {
1706 // Correct the constraint status.
1707 switch (solution->variable_statuses[r.col]) {
1709 solution->constraint_statuses[r.row[DELETED]] =
1711 break;
1713 solution->constraint_statuses[r.row[DELETED]] =
1714 r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_UPPER_BOUND
1716 break;
1718 solution->constraint_statuses[r.row[DELETED]] =
1719 r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1721 break;
1723 solution->constraint_statuses[r.row[DELETED]] = ConstraintStatus::FREE;
1724 break;
1726 // The default is good here:
1727 DCHECK_EQ(solution->constraint_statuses[r.row[DELETED]],
1729 break;
1730 }
1731
1732 // Correct the primal variable value.
1733 {
1734 Fractional new_variable_value = solution->primal_values[r.col];
1735 for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1736 const ColIndex col = RowToColIndex(e.row());
1737 if (col == r.col) continue;
1738 new_variable_value -= (e.coefficient() / r.coeff[DELETED]) *
1739 solution->primal_values[RowToColIndex(e.row())];
1740 }
1741 solution->primal_values[r.col] = new_variable_value;
1742 }
1743
1744 // In all cases, we will make the variable r.col VariableStatus::BASIC, so
1745 // we need to adjust the dual value of the deleted row so that the variable
1746 // reduced cost is zero. Note that there is nothing to do if the variable
1747 // was already basic.
1748 if (solution->variable_statuses[r.col] != VariableStatus::BASIC) {
1749 solution->variable_statuses[r.col] = VariableStatus::BASIC;
1750 Fractional current_reduced_cost =
1751 r.objective_coefficient -
1752 r.coeff[MODIFIED] * solution->dual_values[r.row[MODIFIED]];
1753 // We want current_reduced_cost - dual * coeff = 0, so:
1754 solution->dual_values[r.row[DELETED]] =
1755 current_reduced_cost / r.coeff[DELETED];
1756 } else {
1757 DCHECK_EQ(solution->dual_values[r.row[DELETED]], 0.0);
1758 }
1759 }
1760}
1761
1762// --------------------------------------------------------
1763// UnconstrainedVariablePreprocessor
1764// --------------------------------------------------------
1765
1766namespace {
1767
1768// Does the constraint block the variable to go to infinity in the given
1769// direction? direction is either positive or negative and row is the index of
1770// the constraint.
1771bool IsConstraintBlockingVariable(const LinearProgram& lp, Fractional direction,
1772 RowIndex row) {
1773 return direction > 0.0 ? lp.constraint_upper_bounds()[row] != kInfinity
1774 : lp.constraint_lower_bounds()[row] != -kInfinity;
1775}
1776
1777} // namespace
1778
1780 ColIndex col, Fractional target_bound, LinearProgram* lp) {
1781 DCHECK_EQ(0.0, lp->objective_coefficients()[col]);
1782 if (rhs_.empty()) {
1783 rhs_.resize(lp->num_constraints(), 0.0);
1784 activity_sign_correction_.resize(lp->num_constraints(), 1.0);
1785 is_unbounded_.resize(lp->num_variables(), false);
1786 }
1787 const bool is_unbounded_up = (target_bound == kInfinity);
1788 const SparseColumn& column = lp->GetSparseColumn(col);
1789 for (const SparseColumn::Entry e : column) {
1790 const RowIndex row = e.row();
1791 if (!row_deletion_helper_.IsRowMarked(row)) {
1792 row_deletion_helper_.MarkRowForDeletion(row);
1793 rows_saver_.SaveColumn(
1794 RowToColIndex(row),
1796 }
1797 const bool is_constraint_upper_bound_relevant =
1798 e.coefficient() > 0.0 ? !is_unbounded_up : is_unbounded_up;
1799 activity_sign_correction_[row] =
1800 is_constraint_upper_bound_relevant ? 1.0 : -1.0;
1801 rhs_[row] = is_constraint_upper_bound_relevant
1802 ? lp->constraint_upper_bounds()[row]
1804 DCHECK(IsFinite(rhs_[row]));
1805
1806 // TODO(user): Here, we may render the row free, so subsequent columns
1807 // processed by the columns loop in Run() have more chance to be removed.
1808 // However, we need to be more careful during the postsolve() if we do that.
1809 }
1810 is_unbounded_[col] = true;
1811 Fractional initial_feasible_value = MinInMagnitudeOrZeroIfInfinite(
1812 lp->variable_lower_bounds()[col], lp->variable_upper_bounds()[col]);
1813 column_deletion_helper_.MarkColumnForDeletionWithState(
1814 col, initial_feasible_value,
1815 ComputeVariableStatus(initial_feasible_value,
1816 lp->variable_lower_bounds()[col],
1817 lp->variable_upper_bounds()[col]));
1818}
1819
1822 RETURN_VALUE_IF_NULL(lp, false);
1823
1824 // To simplify the problem if something is almost zero, we use the low
1825 // tolerance (1e-9 by default) to be defensive. But to detect an infeasibility
1826 // we want to be sure (especially since the problem is not scaled in the
1827 // presolver) so we use an higher tolerance.
1828 //
1829 // TODO(user): Expose it as a parameter. We could rename both to
1830 // preprocessor_low_tolerance and preprocessor_high_tolerance.
1831 const Fractional low_tolerance = parameters_.preprocessor_zero_tolerance();
1832 const Fractional high_tolerance = 1e-4;
1833
1834 // We start by the dual variable bounds from the constraints.
1835 const RowIndex num_rows = lp->num_constraints();
1836 dual_lb_.assign(num_rows, -kInfinity);
1837 dual_ub_.assign(num_rows, kInfinity);
1838 for (RowIndex row(0); row < num_rows; ++row) {
1839 if (lp->constraint_lower_bounds()[row] == -kInfinity) {
1840 dual_ub_[row] = 0.0;
1841 }
1842 if (lp->constraint_upper_bounds()[row] == kInfinity) {
1843 dual_lb_[row] = 0.0;
1845 }
1846
1847 const ColIndex num_cols = lp->num_variables();
1848 may_have_participated_lb_.assign(num_cols, false);
1849 may_have_participated_ub_.assign(num_cols, false);
1850
1851 // We maintain a queue of columns to process.
1852 std::deque<ColIndex> columns_to_process;
1853 DenseBooleanRow in_columns_to_process(num_cols, true);
1854 std::vector<RowIndex> changed_rows;
1855 for (ColIndex col(0); col < num_cols; ++col) {
1856 columns_to_process.push_back(col);
1857 }
1858
1859 // Arbitrary limit to avoid corner cases with long loops.
1860 // TODO(user): expose this as a parameter? IMO it isn't really needed as we
1861 // shouldn't reach this limit except in corner cases.
1862 const int limit = 5 * num_cols.value();
1863 for (int count = 0; !columns_to_process.empty() && count < limit; ++count) {
1864 const ColIndex col = columns_to_process.front();
1865 columns_to_process.pop_front();
1866 in_columns_to_process[col] = false;
1867 if (column_deletion_helper_.IsColumnMarked(col)) continue;
1868
1869 const SparseColumn& column = lp->GetSparseColumn(col);
1870 const Fractional col_cost =
1872 const Fractional col_lb = lp->variable_lower_bounds()[col];
1873 const Fractional col_ub = lp->variable_upper_bounds()[col];
1874
1875 // Compute the bounds on the reduced costs of this column.
1878 rc_lb.Add(col_cost);
1879 rc_ub.Add(col_cost);
1880 for (const SparseColumn::Entry e : column) {
1881 if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1882 const Fractional coeff = e.coefficient();
1883 if (coeff > 0.0) {
1884 rc_lb.Add(-coeff * dual_ub_[e.row()]);
1885 rc_ub.Add(-coeff * dual_lb_[e.row()]);
1886 } else {
1887 rc_lb.Add(-coeff * dual_lb_[e.row()]);
1888 rc_ub.Add(-coeff * dual_ub_[e.row()]);
1889 }
1890 }
1891
1892 // If the reduced cost domain do not contain zero (modulo the tolerance), we
1893 // can move the variable to its corresponding bound. Note that we need to be
1894 // careful that this variable didn't participate in creating the used
1895 // reduced cost bound in the first place.
1896 bool can_be_removed = false;
1897 Fractional target_bound;
1898 bool rc_is_away_from_zero;
1899 if (rc_ub.Sum() <= low_tolerance) {
1900 can_be_removed = true;
1901 target_bound = col_ub;
1902 if (in_mip_context_ && lp->IsVariableInteger(col)) {
1903 target_bound = std::floor(target_bound + high_tolerance);
1904 }
1905
1906 rc_is_away_from_zero = rc_ub.Sum() <= -high_tolerance;
1907 can_be_removed = !may_have_participated_ub_[col];
1908 }
1909 if (rc_lb.Sum() >= -low_tolerance) {
1910 // The second condition is here for the case we can choose one of the two
1911 // directions.
1912 if (!can_be_removed || !IsFinite(target_bound)) {
1913 can_be_removed = true;
1914 target_bound = col_lb;
1915 if (in_mip_context_ && lp->IsVariableInteger(col)) {
1916 target_bound = std::ceil(target_bound - high_tolerance);
1917 }
1918
1919 rc_is_away_from_zero = rc_lb.Sum() >= high_tolerance;
1920 can_be_removed = !may_have_participated_lb_[col];
1921 }
1922 }
1923
1924 if (can_be_removed) {
1925 if (IsFinite(target_bound)) {
1926 // Note that in MIP context, this assumes that the bounds of an integer
1927 // variable are integer.
1928 column_deletion_helper_.MarkColumnForDeletionWithState(
1929 col, target_bound,
1930 ComputeVariableStatus(target_bound, col_lb, col_ub));
1931 continue;
1932 }
1933
1934 // If the target bound is infinite and the reduced cost bound is non-zero,
1935 // then the problem is ProblemStatus::INFEASIBLE_OR_UNBOUNDED.
1936 if (rc_is_away_from_zero) {
1937 VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, variable " << col
1938 << " can move to " << target_bound
1939 << " and its reduced cost is in [" << rc_lb.Sum() << ", "
1940 << rc_ub.Sum() << "]";
1942 return false;
1943 } else {
1944 // We can remove this column and all its constraints! We just need to
1945 // choose proper variable values during the call to RecoverSolution()
1946 // that make all the constraints satisfiable. Unfortunately, this is not
1947 // so easy to do in the general case, so we only deal with a simpler
1948 // case when the cost of the variable is zero, and none of the
1949 // constraints (even the deleted one) block the variable moving to its
1950 // infinite target_bound.
1951 //
1952 // TODO(user): deal with the more generic case.
1953 if (col_cost != 0.0) continue;
1954
1955 const double sign_correction = (target_bound == kInfinity) ? 1.0 : -1.0;
1956 bool skip = false;
1957 for (const SparseColumn::Entry e : column) {
1958 // Note that it is important to check the rows that are already
1959 // deleted here, otherwise the post-solve will not work.
1960 if (IsConstraintBlockingVariable(
1961 *lp, sign_correction * e.coefficient(), e.row())) {
1962 skip = true;
1963 break;
1964 }
1965 }
1966 if (skip) continue;
1967
1968 // TODO(user): this also works if the variable is integer, but we must
1969 // choose an integer value during the post-solve. Implement this.
1970 if (in_mip_context_) continue;
1971 RemoveZeroCostUnconstrainedVariable(col, target_bound, lp);
1972 continue;
1973 }
1974 }
1975
1976 // The rest of the code will update the dual bounds. There is no need to do
1977 // it if the column was removed or if it is not unconstrained in some
1978 // direction.
1979 DCHECK(!can_be_removed);
1980 if (col_lb != -kInfinity && col_ub != kInfinity) continue;
1981
1982 // For MIP, we only exploit the constraints. TODO(user): It should probably
1983 // work with only small modification, investigate.
1984 if (in_mip_context_) continue;
1985
1986 changed_rows.clear();
1987 for (const SparseColumn::Entry e : column) {
1988 if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1989 const Fractional c = e.coefficient();
1990 const RowIndex row = e.row();
1991 if (col_ub == kInfinity) {
1992 if (c > 0.0) {
1993 const Fractional candidate =
1994 rc_ub.SumWithoutUb(-c * dual_lb_[row]) / c;
1995 if (candidate < dual_ub_[row]) {
1996 dual_ub_[row] = candidate;
1997 may_have_participated_lb_[col] = true;
1998 changed_rows.push_back(row);
1999 }
2000 } else {
2001 const Fractional candidate =
2002 rc_ub.SumWithoutUb(-c * dual_ub_[row]) / c;
2003 if (candidate > dual_lb_[row]) {
2004 dual_lb_[row] = candidate;
2005 may_have_participated_lb_[col] = true;
2006 changed_rows.push_back(row);
2007 }
2008 }
2009 }
2010 if (col_lb == -kInfinity) {
2011 if (c > 0.0) {
2012 const Fractional candidate =
2013 rc_lb.SumWithoutLb(-c * dual_ub_[row]) / c;
2014 if (candidate > dual_lb_[row]) {
2015 dual_lb_[row] = candidate;
2016 may_have_participated_ub_[col] = true;
2017 changed_rows.push_back(row);
2018 }
2019 } else {
2020 const Fractional candidate =
2021 rc_lb.SumWithoutLb(-c * dual_lb_[row]) / c;
2022 if (candidate < dual_ub_[row]) {
2023 dual_ub_[row] = candidate;
2024 may_have_participated_ub_[col] = true;
2025 changed_rows.push_back(row);
2026 }
2027 }
2028 }
2029 }
2030
2031 if (!changed_rows.empty()) {
2032 const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2033 for (const RowIndex row : changed_rows) {
2034 for (const SparseColumn::Entry entry :
2035 transpose.column(RowToColIndex(row))) {
2036 const ColIndex col = RowToColIndex(entry.row());
2037 if (!in_columns_to_process[col]) {
2038 columns_to_process.push_back(col);
2039 in_columns_to_process[col] = true;
2040 }
2041 }
2042 }
2043 }
2044 }
2045
2046 // Change the rhs to reflect the fixed variables. Note that is important to do
2047 // that after all the calls to RemoveZeroCostUnconstrainedVariable() because
2048 // RemoveZeroCostUnconstrainedVariable() needs to store the rhs before this
2049 // modification!
2050 const ColIndex end = column_deletion_helper_.GetMarkedColumns().size();
2051 for (ColIndex col(0); col < end; ++col) {
2052 if (column_deletion_helper_.IsColumnMarked(col)) {
2053 const Fractional target_bound =
2054 column_deletion_helper_.GetStoredValue()[col];
2055 SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
2056 }
2057 }
2058
2059 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2060 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2061 return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2062}
2063
2065 ProblemSolution* solution) const {
2068 column_deletion_helper_.RestoreDeletedColumns(solution);
2069 row_deletion_helper_.RestoreDeletedRows(solution);
2070
2071 struct DeletionEntry {
2072 RowIndex row;
2073 ColIndex col;
2074 Fractional coefficient;
2075 };
2076 std::vector<DeletionEntry> entries;
2077
2078 // Compute the last deleted column index for each deleted rows.
2079 const RowIndex num_rows = solution->dual_values.size();
2080 RowToColMapping last_deleted_column(num_rows, kInvalidCol);
2081 for (RowIndex row(0); row < num_rows; ++row) {
2082 if (!row_deletion_helper_.IsRowMarked(row)) continue;
2083
2084 ColIndex last_col = kInvalidCol;
2085 Fractional last_coefficient;
2086 for (const SparseColumn::Entry e :
2087 rows_saver_.SavedColumn(RowToColIndex(row))) {
2088 const ColIndex col = RowToColIndex(e.row());
2089 if (is_unbounded_[col]) {
2090 last_col = col;
2091 last_coefficient = e.coefficient();
2092 }
2093 }
2094 if (last_col != kInvalidCol) {
2095 entries.push_back({row, last_col, last_coefficient});
2096 }
2097 }
2098
2099 // Sort by col first and then row.
2100 std::sort(entries.begin(), entries.end(),
2101 [](const DeletionEntry& a, const DeletionEntry& b) {
2102 if (a.col == b.col) return a.row < b.row;
2103 return a.col < b.col;
2104 });
2105
2106 // Note that this will be empty if there were no deleted rows.
2107 for (int i = 0; i < entries.size();) {
2108 const ColIndex col = entries[i].col;
2109 CHECK(is_unbounded_[col]);
2110
2111 Fractional primal_value_shift = 0.0;
2112 RowIndex row_at_bound = kInvalidRow;
2113 for (; i < entries.size(); ++i) {
2114 if (entries[i].col != col) break;
2115 const RowIndex row = entries[i].row;
2116
2117 // This is for VariableStatus::FREE rows.
2118 //
2119 // TODO(user): In presence of free row, we must move them to 0.
2120 // Note that currently VariableStatus::FREE rows should be removed before
2121 // this is called.
2122 DCHECK(IsFinite(rhs_[row]));
2123 if (!IsFinite(rhs_[row])) continue;
2124
2125 const SparseColumn& row_as_column =
2126 rows_saver_.SavedColumn(RowToColIndex(row));
2127 const Fractional activity =
2128 rhs_[row] - ScalarProduct(solution->primal_values, row_as_column);
2129
2130 // activity and sign correction must have the same sign or be zero. If
2131 // not, we find the first unbounded variable and change it accordingly.
2132 // Note that by construction, the variable value will move towards its
2133 // unbounded direction.
2134 if (activity * activity_sign_correction_[row] < 0.0) {
2135 const Fractional bound = activity / entries[i].coefficient;
2136 if (std::abs(bound) > std::abs(primal_value_shift)) {
2137 primal_value_shift = bound;
2138 row_at_bound = row;
2139 }
2140 }
2141 }
2142 solution->primal_values[col] += primal_value_shift;
2143 if (row_at_bound != kInvalidRow) {
2144 solution->variable_statuses[col] = VariableStatus::BASIC;
2145 solution->constraint_statuses[row_at_bound] =
2146 activity_sign_correction_[row_at_bound] == 1.0
2149 }
2150 }
2151}
2152
2153// --------------------------------------------------------
2154// FreeConstraintPreprocessor
2155// --------------------------------------------------------
2156
2159 RETURN_VALUE_IF_NULL(lp, false);
2160 const RowIndex num_rows = lp->num_constraints();
2161 for (RowIndex row(0); row < num_rows; ++row) {
2162 const Fractional lower_bound = lp->constraint_lower_bounds()[row];
2163 const Fractional upper_bound = lp->constraint_upper_bounds()[row];
2164 if (lower_bound == -kInfinity && upper_bound == kInfinity) {
2165 row_deletion_helper_.MarkRowForDeletion(row);
2166 }
2167 }
2168 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2169 return !row_deletion_helper_.IsEmpty();
2170}
2171
2173 ProblemSolution* solution) const {
2176 row_deletion_helper_.RestoreDeletedRows(solution);
2177}
2178
2179// --------------------------------------------------------
2180// EmptyConstraintPreprocessor
2181// --------------------------------------------------------
2182
2185 RETURN_VALUE_IF_NULL(lp, false);
2186 const RowIndex num_rows(lp->num_constraints());
2187 const ColIndex num_cols(lp->num_variables());
2188
2189 // Compute degree.
2190 StrictITIVector<RowIndex, int> degree(num_rows, 0);
2191 for (ColIndex col(0); col < num_cols; ++col) {
2192 for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
2193 ++degree[e.row()];
2194 }
2195 }
2196
2197 // Delete degree 0 rows.
2198 for (RowIndex row(0); row < num_rows; ++row) {
2199 if (degree[row] == 0) {
2200 // We need to check that 0.0 is allowed by the constraint bounds,
2201 // otherwise, the problem is ProblemStatus::PRIMAL_INFEASIBLE.
2203 lp->constraint_lower_bounds()[row], 0) ||
2205 0, lp->constraint_upper_bounds()[row])) {
2206 VLOG(1) << "Problem PRIMAL_INFEASIBLE, constraint " << row
2207 << " is empty and its range ["
2208 << lp->constraint_lower_bounds()[row] << ","
2209 << lp->constraint_upper_bounds()[row] << "] doesn't contain 0.";
2211 return false;
2212 }
2213 row_deletion_helper_.MarkRowForDeletion(row);
2214 }
2215 }
2216 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2217 return !row_deletion_helper_.IsEmpty();
2218}
2219
2221 ProblemSolution* solution) const {
2224 row_deletion_helper_.RestoreDeletedRows(solution);
2225}
2226
2227// --------------------------------------------------------
2228// SingletonPreprocessor
2229// --------------------------------------------------------
2230
2231SingletonUndo::SingletonUndo(OperationType type, const LinearProgram& lp,
2232 MatrixEntry e, ConstraintStatus status)
2233 : type_(type),
2234 is_maximization_(lp.IsMaximizationProblem()),
2235 e_(e),
2236 cost_(lp.objective_coefficients()[e.col]),
2237 variable_lower_bound_(lp.variable_lower_bounds()[e.col]),
2238 variable_upper_bound_(lp.variable_upper_bounds()[e.col]),
2239 constraint_lower_bound_(lp.constraint_lower_bounds()[e.row]),
2240 constraint_upper_bound_(lp.constraint_upper_bounds()[e.row]),
2241 constraint_status_(status) {}
2242
2243void SingletonUndo::Undo(const GlopParameters& parameters,
2244 const SparseColumn& saved_column,
2245 const SparseColumn& saved_row,
2246 ProblemSolution* solution) const {
2247 switch (type_) {
2249 SingletonRowUndo(saved_column, solution);
2250 break;
2252 ZeroCostSingletonColumnUndo(parameters, saved_row, solution);
2253 break;
2255 SingletonColumnInEqualityUndo(parameters, saved_row, solution);
2256 break;
2258 MakeConstraintAnEqualityUndo(solution);
2259 break;
2260 }
2262
2263void SingletonPreprocessor::DeleteSingletonRow(MatrixEntry e,
2264 LinearProgram* lp) {
2265 Fractional implied_lower_bound =
2266 lp->constraint_lower_bounds()[e.row] / e.coeff;
2267 Fractional implied_upper_bound =
2268 lp->constraint_upper_bounds()[e.row] / e.coeff;
2269 if (e.coeff < 0.0) {
2270 std::swap(implied_lower_bound, implied_upper_bound);
2271 }
2272
2273 const Fractional old_lower_bound = lp->variable_lower_bounds()[e.col];
2274 const Fractional old_upper_bound = lp->variable_upper_bounds()[e.col];
2275
2276 const Fractional potential_error =
2278 Fractional new_lower_bound =
2279 implied_lower_bound - potential_error > old_lower_bound
2280 ? implied_lower_bound
2281 : old_lower_bound;
2282 Fractional new_upper_bound =
2283 implied_upper_bound + potential_error < old_upper_bound
2284 ? implied_upper_bound
2285 : old_upper_bound;
2286
2287 // This can happen if we ask for 1e-300 * x to be >= 1e9.
2288 if (new_upper_bound == -kInfinity || new_lower_bound == kInfinity) {
2289 VLOG(1) << "Problem ProblemStatus::PRIMAL_INFEASIBLE, singleton "
2290 "row causes the bound of the variable "
2291 << e.col << " to go to infinity.";
2293 return;
2294 }
2295
2296 if (new_upper_bound < new_lower_bound) {
2297 if (!IsSmallerWithinFeasibilityTolerance(new_lower_bound,
2298 new_upper_bound)) {
2299 VLOG(1) << "Problem ProblemStatus::PRIMAL_INFEASIBLE, singleton "
2300 "row causes the bound of the variable "
2301 << e.col << " to be infeasible by "
2302 << new_lower_bound - new_upper_bound;
2304 return;
2305 }
2306
2307 // Otherwise, fix the variable to one of its bounds.
2308 if (new_lower_bound == lp->variable_lower_bounds()[e.col]) {
2309 new_upper_bound = new_lower_bound;
2310 }
2311 if (new_upper_bound == lp->variable_upper_bounds()[e.col]) {
2312 new_lower_bound = new_upper_bound;
2313 }
2314
2315 // When both new bounds are coming from the constraint and are crossing, it
2316 // means the constraint bounds where originally crossing too. We arbitrarily
2317 // choose one of the bound in this case.
2318 //
2319 // TODO(user): The code in this file shouldn't create crossing bounds at
2320 // any point, so we could decide which bound to use directly on the user
2321 // given problem before running any presolve.
2322 new_upper_bound = new_lower_bound;
2323 }
2324 row_deletion_helper_.MarkRowForDeletion(e.row);
2325 undo_stack_.push_back(SingletonUndo(SingletonUndo::SINGLETON_ROW, *lp, e,
2327 columns_saver_.SaveColumnIfNotAlreadyDone(e.col, lp->GetSparseColumn(e.col));
2328
2329 lp->SetVariableBounds(e.col, new_lower_bound, new_upper_bound);
2330}
2331
2332// The dual value of the row needs to be corrected to stay at the optimal.
2333void SingletonUndo::SingletonRowUndo(const SparseColumn& saved_column,
2334 ProblemSolution* solution) const {
2335 DCHECK_EQ(0, solution->dual_values[e_.row]);
2336
2337 // If the variable is basic or free, we can just keep the constraint
2338 // VariableStatus::BASIC and 0.0 as the dual value.
2339 const VariableStatus status = solution->variable_statuses[e_.col];
2340 if (status == VariableStatus::BASIC || status == VariableStatus::FREE) return;
2341
2342 // Compute whether or not the variable bounds changed.
2343 Fractional implied_lower_bound = constraint_lower_bound_ / e_.coeff;
2344 Fractional implied_upper_bound = constraint_upper_bound_ / e_.coeff;
2345 if (e_.coeff < 0.0) {
2346 std::swap(implied_lower_bound, implied_upper_bound);
2347 }
2348 const bool lower_bound_changed = implied_lower_bound > variable_lower_bound_;
2349 const bool upper_bound_changed = implied_upper_bound < variable_upper_bound_;
2350
2351 if (!lower_bound_changed && !upper_bound_changed) return;
2352 if (status == VariableStatus::AT_LOWER_BOUND && !lower_bound_changed) return;
2353 if (status == VariableStatus::AT_UPPER_BOUND && !upper_bound_changed) return;
2354
2355 // This is the reduced cost of the variable before the singleton constraint is
2356 // added back.
2357 const Fractional reduced_cost =
2358 cost_ - ScalarProduct(solution->dual_values, saved_column);
2359 const Fractional reduced_cost_for_minimization =
2360 is_maximization_ ? -reduced_cost : reduced_cost;
2361
2362 if (status == VariableStatus::FIXED_VALUE) {
2363 DCHECK(lower_bound_changed || upper_bound_changed);
2364 if (reduced_cost_for_minimization >= 0.0 && !lower_bound_changed) {
2365 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2366 return;
2367 }
2368 if (reduced_cost_for_minimization <= 0.0 && !upper_bound_changed) {
2369 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2370 return;
2371 }
2372 }
2373
2374 // If one of the variable bounds changes, and the variable is no longer at one
2375 // of its bounds, then its reduced cost needs to be set to 0.0 and the
2376 // variable becomes a basic variable. This is what the line below do, since
2377 // the new reduced cost of the variable will be equal to:
2378 // old_reduced_cost - coeff * solution->dual_values[row]
2379 //
2380 // TODO(user): This code is broken for integer variable.
2381 // Say our singleton row is 2 * y <= 5, and y was at its implied bound y = 2
2382 // at postsolve. The problem is that we can end up with an AT_UPPER_BOUND
2383 // status for the constraint 2 * y <= 5 which is not correct since the
2384 // activity is 4, and that break later preconditions. Maybe there is a way to
2385 // fix everything, but it seems tough to be sure.
2386 solution->dual_values[e_.row] = reduced_cost / e_.coeff;
2387 ConstraintStatus new_constraint_status = VariableToConstraintStatus(status);
2388 if (status == VariableStatus::FIXED_VALUE &&
2389 (!lower_bound_changed || !upper_bound_changed)) {
2390 new_constraint_status = lower_bound_changed
2393 }
2394 if (e_.coeff < 0.0) {
2395 if (new_constraint_status == ConstraintStatus::AT_LOWER_BOUND) {
2396 new_constraint_status = ConstraintStatus::AT_UPPER_BOUND;
2397 } else if (new_constraint_status == ConstraintStatus::AT_UPPER_BOUND) {
2398 new_constraint_status = ConstraintStatus::AT_LOWER_BOUND;
2399 }
2400 }
2401 solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2402 solution->constraint_statuses[e_.row] = new_constraint_status;
2403}
2404
2405void SingletonPreprocessor::UpdateConstraintBoundsWithVariableBounds(
2406 MatrixEntry e, LinearProgram* lp) {
2407 Fractional lower_delta = -e.coeff * lp->variable_upper_bounds()[e.col];
2408 Fractional upper_delta = -e.coeff * lp->variable_lower_bounds()[e.col];
2409 if (e.coeff < 0.0) {
2410 std::swap(lower_delta, upper_delta);
2411 }
2412 lp->SetConstraintBounds(e.row,
2413 lp->constraint_lower_bounds()[e.row] + lower_delta,
2414 lp->constraint_upper_bounds()[e.row] + upper_delta);
2415}
2416
2417bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
2418 const MatrixEntry& matrix_entry, const LinearProgram& lp) const {
2419 DCHECK(in_mip_context_);
2420 DCHECK(lp.IsVariableInteger(matrix_entry.col));
2421 const SparseMatrix& transpose = lp.GetTransposeSparseMatrix();
2422 for (const SparseColumn::Entry entry :
2423 transpose.column(RowToColIndex(matrix_entry.row))) {
2424 // Check if the variable is integer.
2425 if (!lp.IsVariableInteger(RowToColIndex(entry.row()))) {
2426 return false;
2427 }
2428
2429 const Fractional coefficient = entry.coefficient();
2430 const Fractional coefficient_ratio = coefficient / matrix_entry.coeff;
2431 // Check if coefficient_ratio is integer.
2433 coefficient_ratio,
2434 Fractional(parameters_.solution_feasibility_tolerance()))) {
2435 return false;
2436 }
2437 }
2438 const Fractional constraint_lb =
2439 lp.constraint_lower_bounds()[matrix_entry.row];
2440 if (IsFinite(constraint_lb)) {
2441 const Fractional lower_bound_ratio = constraint_lb / matrix_entry.coeff;
2443 lower_bound_ratio,
2444 Fractional(parameters_.solution_feasibility_tolerance()))) {
2445 return false;
2446 }
2447 }
2448 const Fractional constraint_ub =
2449 lp.constraint_upper_bounds()[matrix_entry.row];
2450 if (IsFinite(constraint_ub)) {
2451 const Fractional upper_bound_ratio = constraint_ub / matrix_entry.coeff;
2453 upper_bound_ratio,
2454 Fractional(parameters_.solution_feasibility_tolerance()))) {
2455 return false;
2456 }
2457 }
2458 return true;
2459}
2460
2461void SingletonPreprocessor::DeleteZeroCostSingletonColumn(
2462 const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2463 const ColIndex transpose_col = RowToColIndex(e.row);
2464 undo_stack_.push_back(SingletonUndo(SingletonUndo::ZERO_COST_SINGLETON_COLUMN,
2465 *lp, e, ConstraintStatus::FREE));
2466 const SparseColumn& row_as_col = transpose.column(transpose_col);
2467 rows_saver_.SaveColumnIfNotAlreadyDone(RowToColIndex(e.row), row_as_col);
2468 UpdateConstraintBoundsWithVariableBounds(e, lp);
2469 column_deletion_helper_.MarkColumnForDeletion(e.col);
2470}
2471
2472// We need to restore the variable value in order to satisfy the constraint.
2473void SingletonUndo::ZeroCostSingletonColumnUndo(
2474 const GlopParameters& parameters, const SparseColumn& saved_row,
2475 ProblemSolution* solution) const {
2476 // If the variable was fixed, this is easy. Note that this is the only
2477 // possible case if the current constraint status is FIXED, except if the
2478 // variable bounds are small compared to the constraint bounds, like adding
2479 // 1e-100 to a fixed == 1 constraint.
2480 if (variable_upper_bound_ == variable_lower_bound_) {
2481 solution->primal_values[e_.col] = variable_lower_bound_;
2482 solution->variable_statuses[e_.col] = VariableStatus::FIXED_VALUE;
2483 return;
2484 }
2485
2486 const ConstraintStatus ct_status = solution->constraint_statuses[e_.row];
2487 if (ct_status == ConstraintStatus::FIXED_VALUE) {
2488 const Fractional corrected_dual = is_maximization_
2489 ? -solution->dual_values[e_.row]
2490 : solution->dual_values[e_.row];
2491 if (corrected_dual > 0) {
2492 DCHECK(IsFinite(variable_lower_bound_));
2493 solution->primal_values[e_.col] = variable_lower_bound_;
2494 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2495 } else {
2496 DCHECK(IsFinite(variable_upper_bound_));
2497 solution->primal_values[e_.col] = variable_upper_bound_;
2498 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2499 }
2500 return;
2501 } else if (ct_status == ConstraintStatus::AT_LOWER_BOUND ||
2502 ct_status == ConstraintStatus::AT_UPPER_BOUND) {
2503 if ((ct_status == ConstraintStatus::AT_UPPER_BOUND && e_.coeff > 0.0) ||
2504 (ct_status == ConstraintStatus::AT_LOWER_BOUND && e_.coeff < 0.0)) {
2505 DCHECK(IsFinite(variable_lower_bound_));
2506 solution->primal_values[e_.col] = variable_lower_bound_;
2507 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2508 } else {
2509 DCHECK(IsFinite(variable_upper_bound_));
2510 solution->primal_values[e_.col] = variable_upper_bound_;
2511 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2512 }
2513 if (constraint_upper_bound_ == constraint_lower_bound_) {
2514 solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2515 }
2516 return;
2517 }
2518
2519 // This is the activity of the constraint before the singleton variable is
2520 // added back to it.
2521 const Fractional activity = ScalarProduct(solution->primal_values, saved_row);
2522
2523 // First we try to fix the variable at its lower or upper bound and leave the
2524 // constraint VariableStatus::BASIC. Note that we use the same logic as in
2525 // Preprocessor::IsSmallerWithinPreprocessorZeroTolerance() which we can't use
2526 // here because we are not deriving from the Preprocessor class.
2527 const Fractional tolerance = parameters.preprocessor_zero_tolerance();
2528 const auto is_smaller_with_tolerance = [tolerance](Fractional a,
2529 Fractional b) {
2530 return ::operations_research::IsSmallerWithinTolerance(a, b, tolerance);
2531 };
2532 if (variable_lower_bound_ != -kInfinity) {
2533 const Fractional activity_at_lb =
2534 activity + e_.coeff * variable_lower_bound_;
2535 if (is_smaller_with_tolerance(constraint_lower_bound_, activity_at_lb) &&
2536 is_smaller_with_tolerance(activity_at_lb, constraint_upper_bound_)) {
2537 solution->primal_values[e_.col] = variable_lower_bound_;
2538 solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2539 return;
2540 }
2541 }
2542 if (variable_upper_bound_ != kInfinity) {
2543 const Fractional activity_at_ub =
2544 activity + e_.coeff * variable_upper_bound_;
2545 if (is_smaller_with_tolerance(constraint_lower_bound_, activity_at_ub) &&
2546 is_smaller_with_tolerance(activity_at_ub, constraint_upper_bound_)) {
2547 solution->primal_values[e_.col] = variable_upper_bound_;
2548 solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2549 return;
2550 }
2551 }
2552
2553 // If the current constraint is UNBOUNDED, then the variable is too
2554 // because of the two cases above. We just set its status to
2555 // VariableStatus::FREE.
2556 if (constraint_lower_bound_ == -kInfinity &&
2557 constraint_upper_bound_ == kInfinity) {
2558 solution->primal_values[e_.col] = 0.0;
2559 solution->variable_statuses[e_.col] = VariableStatus::FREE;
2560 return;
2561 }
2562
2563 // If the previous cases didn't apply, the constraint will be fixed to its
2564 // bounds and the variable will be made VariableStatus::BASIC.
2565 solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2566 if (constraint_lower_bound_ == constraint_upper_bound_) {
2567 solution->primal_values[e_.col] =
2568 (constraint_lower_bound_ - activity) / e_.coeff;
2569 solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2570 return;
2571 }
2572
2573 bool set_constraint_to_lower_bound;
2574 if (constraint_lower_bound_ == -kInfinity) {
2575 set_constraint_to_lower_bound = false;
2576 } else if (constraint_upper_bound_ == kInfinity) {
2577 set_constraint_to_lower_bound = true;
2578 } else {
2579 // In this case we select the value that is the most inside the variable
2580 // bound.
2581 const Fractional to_lb = (constraint_lower_bound_ - activity) / e_.coeff;
2582 const Fractional to_ub = (constraint_upper_bound_ - activity) / e_.coeff;
2583 set_constraint_to_lower_bound =
2584 std::max(variable_lower_bound_ - to_lb, to_lb - variable_upper_bound_) <
2585 std::max(variable_lower_bound_ - to_ub, to_ub - variable_upper_bound_);
2586 }
2587
2588 if (set_constraint_to_lower_bound) {
2589 solution->primal_values[e_.col] =
2590 (constraint_lower_bound_ - activity) / e_.coeff;
2591 solution->constraint_statuses[e_.row] = ConstraintStatus::AT_LOWER_BOUND;
2592 } else {
2593 solution->primal_values[e_.col] =
2594 (constraint_upper_bound_ - activity) / e_.coeff;
2595 solution->constraint_statuses[e_.row] = ConstraintStatus::AT_UPPER_BOUND;
2596 }
2597}
2598
2599void SingletonPreprocessor::DeleteSingletonColumnInEquality(
2600 const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2601 // Save information for the undo.
2602 const ColIndex transpose_col = RowToColIndex(e.row);
2603 const SparseColumn& row_as_column = transpose.column(transpose_col);
2604 undo_stack_.push_back(
2605 SingletonUndo(SingletonUndo::SINGLETON_COLUMN_IN_EQUALITY, *lp, e,
2607 rows_saver_.SaveColumnIfNotAlreadyDone(RowToColIndex(e.row), row_as_column);
2608
2609 // Update the objective function using the equality constraint. We have
2610 // v_col*coeff + expression = rhs,
2611 // so the contribution of this variable to the cost function (v_col * cost)
2612 // can be rewritten as:
2613 // (rhs * cost - expression * cost) / coeff.
2614 const Fractional rhs = lp->constraint_upper_bounds()[e.row];
2615 const Fractional cost = lp->objective_coefficients()[e.col];
2616 const Fractional multiplier = cost / e.coeff;
2617 lp->SetObjectiveOffset(lp->objective_offset() + rhs * multiplier);
2618 for (const SparseColumn::Entry e : row_as_column) {
2619 const ColIndex col = RowToColIndex(e.row());
2620 if (!column_deletion_helper_.IsColumnMarked(col)) {
2621 Fractional new_cost =
2622 lp->objective_coefficients()[col] - e.coefficient() * multiplier;
2623
2624 // TODO(user): It is important to avoid having non-zero costs which are
2625 // the result of numerical error. This is because we still miss some
2626 // tolerances in a few preprocessors. Like an empty column with a cost of
2627 // 1e-17 and unbounded towards infinity is currently implying that the
2628 // problem is unbounded. This will need fixing.
2629 if (std::abs(new_cost) < parameters_.preprocessor_zero_tolerance()) {
2630 new_cost = 0.0;
2631 }
2632 lp->SetObjectiveCoefficient(col, new_cost);
2633 }
2634 }
2635
2636 // Now delete the column like a singleton column without cost.
2637 UpdateConstraintBoundsWithVariableBounds(e, lp);
2638 column_deletion_helper_.MarkColumnForDeletion(e.col);
2639}
2640
2641void SingletonUndo::SingletonColumnInEqualityUndo(
2642 const GlopParameters& parameters, const SparseColumn& saved_row,
2643 ProblemSolution* solution) const {
2644 // First do the same as a zero-cost singleton column.
2645 ZeroCostSingletonColumnUndo(parameters, saved_row, solution);
2646
2647 // Then, restore the dual optimal value taking into account the cost
2648 // modification.
2649 solution->dual_values[e_.row] += cost_ / e_.coeff;
2650 if (solution->constraint_statuses[e_.row] == ConstraintStatus::BASIC) {
2651 solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2652 solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2653 }
2654}
2655
2656void SingletonUndo::MakeConstraintAnEqualityUndo(
2657 ProblemSolution* solution) const {
2658 if (solution->constraint_statuses[e_.row] == ConstraintStatus::FIXED_VALUE) {
2659 solution->constraint_statuses[e_.row] = constraint_status_;
2660 }
2661}
2662
2663bool SingletonPreprocessor::MakeConstraintAnEqualityIfPossible(
2664 const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2665 // TODO(user): We could skip early if the relevant constraint bound is
2666 // infinity.
2667 const Fractional cst_lower_bound = lp->constraint_lower_bounds()[e.row];
2668 const Fractional cst_upper_bound = lp->constraint_upper_bounds()[e.row];
2669 if (cst_lower_bound == cst_upper_bound) return true;
2670 if (cst_lower_bound == -kInfinity && cst_upper_bound == kInfinity) {
2671 return false;
2672 }
2673
2674 // The code below do not work as is for integer variable.
2675 if (in_mip_context_ && lp->IsVariableInteger(e.col)) return false;
2676
2677 // To be efficient, we only process a row once and cache the domain that an
2678 // "artificial" extra variable x with coefficient 1.0 could take while still
2679 // making the constraint feasible. The domain bounds for the constraint e.row
2680 // will be stored in row_lb_sum_[e.row] and row_ub_sum_[e.row].
2681 const DenseRow& variable_ubs = lp->variable_upper_bounds();
2682 const DenseRow& variable_lbs = lp->variable_lower_bounds();
2683 if (e.row >= row_sum_is_cached_.size() || !row_sum_is_cached_[e.row]) {
2684 if (e.row >= row_sum_is_cached_.size()) {
2685 const int new_size = e.row.value() + 1;
2686 row_sum_is_cached_.resize(new_size);
2687 row_lb_sum_.resize(new_size);
2688 row_ub_sum_.resize(new_size);
2689 }
2690 row_sum_is_cached_[e.row] = true;
2691 row_lb_sum_[e.row].Add(cst_lower_bound);
2692 row_ub_sum_[e.row].Add(cst_upper_bound);
2693 for (const SparseColumn::Entry entry :
2694 transpose.column(RowToColIndex(e.row))) {
2695 const ColIndex row_as_col = RowToColIndex(entry.row());
2696
2697 // Tricky: Even if later more columns are deleted, these "cached" sums
2698 // will actually still be valid because we only delete columns in a
2699 // compatible way.
2700 //
2701 // TODO(user): Find a more robust way? it seems easy to add new deletion
2702 // rules that may break this assumption.
2703 if (column_deletion_helper_.IsColumnMarked(row_as_col)) continue;
2704 if (entry.coefficient() > 0.0) {
2705 row_lb_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2706 row_ub_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2707 } else {
2708 row_lb_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2709 row_ub_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2710 }
2711
2712 // TODO(user): Abort early if both sums contain more than 1 infinity?
2713 }
2714 }
2715
2716 // Now that the lb/ub sum for the row is cached, we can use it to compute the
2717 // implied bounds on the variable from this constraint and the other
2718 // variables.
2719 const Fractional c = e.coeff;
2720 const Fractional lb =
2721 c > 0.0 ? row_lb_sum_[e.row].SumWithoutLb(-c * variable_ubs[e.col]) / c
2722 : row_ub_sum_[e.row].SumWithoutUb(-c * variable_ubs[e.col]) / c;
2723 const Fractional ub =
2724 c > 0.0 ? row_ub_sum_[e.row].SumWithoutUb(-c * variable_lbs[e.col]) / c
2725 : row_lb_sum_[e.row].SumWithoutLb(-c * variable_lbs[e.col]) / c;
2726
2727 // Note that we could do the same for singleton variables with a cost of
2728 // 0.0, but such variable are already dealt with by
2729 // DeleteZeroCostSingletonColumn() so there is no point.
2730 const Fractional cost =
2732 DCHECK_NE(cost, 0.0);
2733
2734 // Note that some of the tests below will be always true if the bounds of
2735 // the column of index col are infinite. This is the desired behavior.
2738 ub, lp->variable_upper_bounds()[e.col])) {
2739 if (e.coeff > 0) {
2740 if (cst_upper_bound == kInfinity) {
2742 } else {
2743 relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2744 lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2745 }
2746 } else {
2747 if (cst_lower_bound == -kInfinity) {
2749 } else {
2750 relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2751 lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2752 }
2753 }
2754
2756 VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2757 "variable "
2758 << e.col << " has a cost (for minimization) of " << cost
2759 << " and is unbounded towards kInfinity.";
2760 DCHECK_EQ(ub, kInfinity);
2761 return false;
2762 }
2763
2764 // This is important but tricky: The upper bound of the variable needs to
2765 // be relaxed. This is valid because the implied bound is lower than the
2766 // original upper bound here. This is needed, so that the optimal
2767 // primal/dual values of the new problem will also be optimal of the
2768 // original one.
2769 //
2770 // Let's prove the case coeff > 0.0 for a minimization problem. In the new
2771 // problem, because the variable is unbounded towards +infinity, its
2772 // reduced cost must satisfy at optimality rc = cost - coeff * dual_v >=
2773 // 0. But this implies dual_v <= cost / coeff <= 0. This is exactly what
2774 // is needed for the optimality of the initial problem since the
2775 // constraint will be at its upper bound, and the corresponding slack
2776 // condition is that the dual value needs to be <= 0.
2777 lp->SetVariableBounds(e.col, lp->variable_lower_bounds()[e.col], kInfinity);
2778 }
2780 lp->variable_lower_bounds()[e.col], lb)) {
2781 if (e.coeff > 0) {
2782 if (cst_lower_bound == -kInfinity) {
2784 } else {
2785 relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2786 lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2787 }
2788 } else {
2789 if (cst_upper_bound == kInfinity) {
2791 } else {
2792 relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2793 lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2794 }
2795 }
2796
2798 DCHECK_EQ(lb, -kInfinity);
2799 VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2800 "variable "
2801 << e.col << " has a cost (for minimization) of " << cost
2802 << " and is unbounded towards -kInfinity.";
2803 return false;
2804 }
2805
2806 // Same remark as above for a lower bounded variable this time.
2807 lp->SetVariableBounds(e.col, -kInfinity,
2808 lp->variable_upper_bounds()[e.col]);
2809 }
2810
2811 if (lp->constraint_lower_bounds()[e.row] ==
2812 lp->constraint_upper_bounds()[e.row]) {
2813 undo_stack_.push_back(SingletonUndo(
2814 SingletonUndo::MAKE_CONSTRAINT_AN_EQUALITY, *lp, e, relaxed_status));
2815 return true;
2816 }
2817 return false;
2818}
2819
2822 RETURN_VALUE_IF_NULL(lp, false);
2823 const SparseMatrix& matrix = lp->GetSparseMatrix();
2824 const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2825
2826 // Initialize column_to_process with the current singleton columns.
2827 ColIndex num_cols(matrix.num_cols());
2828 RowIndex num_rows(matrix.num_rows());
2829 StrictITIVector<ColIndex, EntryIndex> column_degree(num_cols, EntryIndex(0));
2830 std::vector<ColIndex> column_to_process;
2831 for (ColIndex col(0); col < num_cols; ++col) {
2832 column_degree[col] = matrix.column(col).num_entries();
2833 if (column_degree[col] == 1) {
2834 column_to_process.push_back(col);
2835 }
2836 }
2837
2838 // Initialize row_to_process with the current singleton rows.
2839 StrictITIVector<RowIndex, EntryIndex> row_degree(num_rows, EntryIndex(0));
2840 std::vector<RowIndex> row_to_process;
2841 for (RowIndex row(0); row < num_rows; ++row) {
2842 row_degree[row] = transpose.column(RowToColIndex(row)).num_entries();
2843 if (row_degree[row] == 1) {
2844 row_to_process.push_back(row);
2845 }
2846 }
2847
2848 // Process current singleton rows/columns and enqueue new ones.
2849 while (status_ == ProblemStatus::INIT &&
2850 (!column_to_process.empty() || !row_to_process.empty())) {
2851 while (status_ == ProblemStatus::INIT && !column_to_process.empty()) {
2852 const ColIndex col = column_to_process.back();
2853 column_to_process.pop_back();
2854 if (column_degree[col] <= 0) continue;
2855 const MatrixEntry e = GetSingletonColumnMatrixEntry(col, matrix);
2856 if (in_mip_context_ && lp->IsVariableInteger(e.col) &&
2857 !IntegerSingletonColumnIsRemovable(e, *lp)) {
2858 continue;
2859 }
2860
2861 // TODO(user): It seems better to process all the singleton columns with
2862 // a cost of zero first.
2863 if (lp->objective_coefficients()[col] == 0.0) {
2864 DeleteZeroCostSingletonColumn(transpose, e, lp);
2865 } else {
2866 // We don't want to do a substitution if the entry is too small and
2867 // should be probably set to zero.
2868 if (std::abs(e.coeff) < parameters_.preprocessor_zero_tolerance()) {
2869 continue;
2870 }
2871 if (MakeConstraintAnEqualityIfPossible(transpose, e, lp)) {
2872 DeleteSingletonColumnInEquality(transpose, e, lp);
2873 } else {
2874 continue;
2875 }
2876 }
2877 --row_degree[e.row];
2878 if (row_degree[e.row] == 1) {
2879 row_to_process.push_back(e.row);
2880 }
2881 }
2882 while (status_ == ProblemStatus::INIT && !row_to_process.empty()) {
2883 const RowIndex row = row_to_process.back();
2884 row_to_process.pop_back();
2885 if (row_degree[row] <= 0) continue;
2886 const MatrixEntry e = GetSingletonRowMatrixEntry(row, transpose);
2887
2888 // TODO(user): We should be able to restrict the variable bounds with the
2889 // ones of the constraint all the time. However, some situation currently
2890 // break the presolve, and it seems hard to fix in a 100% safe way.
2891 if (in_mip_context_ && lp->IsVariableInteger(e.col) &&
2892 !IntegerSingletonColumnIsRemovable(e, *lp)) {
2893 continue;
2894 }
2895
2896 DeleteSingletonRow(e, lp);
2897 --column_degree[e.col];
2898 if (column_degree[e.col] == 1) {
2899 column_to_process.push_back(e.col);
2900 }
2901 }
2902 }
2903
2904 if (status_ != ProblemStatus::INIT) return false;
2905 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2906 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2907 return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2908}
2909
2910void SingletonPreprocessor::RecoverSolution(ProblemSolution* solution) const {
2913
2914 // Note that the two deletion helpers must restore 0.0 values in the positions
2915 // that will be used during Undo(). That is, all the calls done by this class
2916 // to MarkColumnForDeletion() should be done with 0.0 as the value to restore
2917 // (which is already the case when using MarkRowForDeletion()).
2918 // This is important because the various Undo() functions assume that a
2919 // primal/dual variable value which isn't restored yet has the value of 0.0.
2920 column_deletion_helper_.RestoreDeletedColumns(solution);
2921 row_deletion_helper_.RestoreDeletedRows(solution);
2922
2923 // It is important to undo the operations in the correct order, i.e. in the
2924 // reverse order in which they were done.
2925 for (int i = undo_stack_.size() - 1; i >= 0; --i) {
2926 const SparseColumn& saved_col =
2927 columns_saver_.SavedOrEmptyColumn(undo_stack_[i].Entry().col);
2928 const SparseColumn& saved_row = rows_saver_.SavedOrEmptyColumn(
2929 RowToColIndex(undo_stack_[i].Entry().row));
2930 undo_stack_[i].Undo(parameters_, saved_col, saved_row, solution);
2931 }
2932}
2933
2934MatrixEntry SingletonPreprocessor::GetSingletonColumnMatrixEntry(
2935 ColIndex col, const SparseMatrix& matrix) {
2936 for (const SparseColumn::Entry e : matrix.column(col)) {
2937 if (!row_deletion_helper_.IsRowMarked(e.row())) {
2938 DCHECK_NE(0.0, e.coefficient());
2939 return MatrixEntry(e.row(), col, e.coefficient());
2941 }
2942 // COV_NF_START
2943 // This shouldn't happen.
2944 LOG(DFATAL) << "No unmarked entry in a column that is supposed to have one.";
2946 return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2947 // COV_NF_END
2948}
2949
2950MatrixEntry SingletonPreprocessor::GetSingletonRowMatrixEntry(
2951 RowIndex row, const SparseMatrix& transpose) {
2952 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
2953 const ColIndex col = RowToColIndex(e.row());
2954 if (!column_deletion_helper_.IsColumnMarked(col)) {
2955 DCHECK_NE(0.0, e.coefficient());
2956 return MatrixEntry(row, col, e.coefficient());
2957 }
2958 }
2959 // COV_NF_START
2960 // This shouldn't happen.
2961 LOG(DFATAL) << "No unmarked entry in a row that is supposed to have one.";
2963 return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2964 // COV_NF_END
2965}
2966
2967// --------------------------------------------------------
2968// SingletonColumnSignPreprocessor
2969// --------------------------------------------------------
2970
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
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])
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,
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);
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.
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) {
3449 const Fractional lower = lp->variable_lower_bounds()[col];
3450 const Fractional upper = lp->variable_upper_bounds()[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) {
3457 lp->SetVariableBounds(col, lower - value, upper - value);
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) {
3468 const Fractional lower_bound = lp->constraint_lower_bounds()[row];
3469 const Fractional upper_bound = lp->constraint_upper_bounds()[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) {
3482 const Fractional lower_bound = lp->variable_lower_bounds()[col];
3483 const Fractional upper_bound = lp->variable_upper_bounds()[col];
3484 if (lower_bound != -kInfinity) {
3485 dual_status_correspondence_.push_back(
3486 upper_bound == lower_bound ? VariableStatus::FIXED_VALUE
3488 slack_or_surplus_mapping_.push_back(col);
3489 }
3490 }
3491 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3492 const Fractional lower_bound = lp->variable_lower_bounds()[col];
3493 const Fractional upper_bound = lp->variable_upper_bounds()[col];
3494 if (upper_bound != kInfinity) {
3495 dual_status_correspondence_.push_back(
3496 upper_bound == lower_bound ? VariableStatus::FIXED_VALUE
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.
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
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 }
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
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
3857 RETURN_VALUE_IF_NULL(lp, false);
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
const DenseRow & objective_coefficients() const
Definition lp_data.h:230
void DeleteColumns(const DenseBooleanRow &columns_to_delete)
Definition lp_data.cc:1076
Fractional GetObjectiveCoefficientForMinimizationVersion(ColIndex col) const
Definition lp_data.cc:428
void SetObjectiveOffset(Fractional objective_offset)
Definition lp_data.cc:340
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition lp_data.cc:335
const DenseColumn & constraint_lower_bounds() const
Definition lp_data.h:222
const SparseMatrix & GetTransposeSparseMatrix() const
Definition lp_data.cc:385
void AddSlackVariablesWhereNecessary(bool detect_integer_constraints)
Definition lp_data.cc:708
const DenseRow & variable_lower_bounds() const
Definition lp_data.h:236
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition lp_data.cc:258
EntryIndex num_entries() const
Definition lp_data.h:218
Fractional ScaleBounds()
Definition lp_data.cc:1234
bool IsMaximizationProblem() const
Definition lp_data.h:178
SparseColumn * GetMutableSparseColumn(ColIndex col)
Definition lp_data.cc:422
const SparseMatrix & GetSparseMatrix() const
Definition lp_data.h:182
Fractional ScaleObjective(GlopParameters::CostScalingAlgorithm method)
Definition lp_data.cc:1199
void Swap(LinearProgram *linear_program)
Definition lp_data.cc:1042
void DeleteRows(const DenseBooleanColumn &rows_to_delete)
Definition lp_data.cc:1269
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition lp_data.cc:318
const DenseRow & variable_upper_bounds() const
Definition lp_data.h:239
const DenseColumn & constraint_upper_bounds() const
Definition lp_data.h:225
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition lp_data.cc:418
bool IsVariableInteger(ColIndex col) const
Definition lp_data.cc:304
ColIndex num_variables() const
Definition lp_data.h:212
ColIndex GetFirstSlackVariable() const
Definition lp_data.cc:762
Fractional objective_offset() const
Definition lp_data.h:267
RowIndex num_constraints() const
Definition lp_data.h:215
void PopulateFromDual(const LinearProgram &dual, RowToColMapping *duplicated_rows)
Definition lp_data.cc:775
const SparseColumn & column(ColIndex col) const
Definition sparse.h:193
RowIndex num_rows() const
Definition sparse.h:189
ColIndex num_cols() const
Definition sparse.h:190
void EnableLogging(bool enable)
Definition logging.h:51
void RecoverSolution(ProblemSolution *solution) const final
const DenseBooleanRow & GetMarkedColumns() const
void RestoreDeletedColumns(ProblemSolution *solution) const
void MarkColumnForDeletionWithState(ColIndex col, Fractional value, VariableStatus status)
const SparseColumn & SavedOrEmptyColumn(ColIndex col) const
void SaveColumn(ColIndex col, const SparseColumn &column)
const SparseColumn & SavedColumn(ColIndex col) const
void SaveColumnIfNotAlreadyDone(ColIndex col, const SparseColumn &column)
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
ProblemStatus ChangeStatusToDualStatus(ProblemStatus status) const
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
static constexpr SolverBehavior LET_SOLVER_DECIDE
static constexpr SolverBehavior NEVER_DO
::operations_research::glop::GlopParameters_SolverBehavior solve_dual_problem() const
::operations_research::glop::GlopParameters_CostScalingAlgorithm cost_scaling() const
::operations_research::glop::GlopParameters_ScalingAlgorithm scaling_method() const
void RecoverSolution(ProblemSolution *solution) const final
const DenseRow & objective_coefficients() const
Definition lp_data.h:230
void DeleteColumns(const DenseBooleanRow &columns_to_delete)
Definition lp_data.cc:1076
Fractional GetObjectiveCoefficientForMinimizationVersion(ColIndex col) const
Definition lp_data.cc:428
void SetObjectiveOffset(Fractional objective_offset)
Definition lp_data.cc:340
DenseColumn * mutable_constraint_upper_bounds()
Definition lp_data.h:563
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:222
const DenseRow & variable_lower_bounds() const
Definition lp_data.h:236
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition lp_data.cc:258
DenseColumn * mutable_constraint_lower_bounds()
Definition lp_data.h:560
SparseMatrix * GetMutableTransposeSparseMatrix()
Definition lp_data.cc:395
const SparseMatrix & GetSparseMatrix() const
Definition lp_data.h:182
void DeleteRows(const DenseBooleanColumn &rows_to_delete)
Definition lp_data.cc:1269
const DenseRow & variable_upper_bounds() const
Definition lp_data.h:239
const DenseColumn & constraint_upper_bounds() const
Definition lp_data.h:225
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition lp_data.cc:418
Fractional objective_scaling_factor() const
Definition lp_data.h:268
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)
std::unique_ptr< TimeLimit > infinite_time_limit_
bool IsSmallerWithinPreprocessorZeroTolerance(Fractional a, Fractional b) const
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
const DenseBooleanColumn & GetMarkedRows() const
void RestoreDeletedRows(ProblemSolution *solution) const
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
Definition sparse.h:193
SparseColumn * mutable_column(ColIndex col)
Definition sparse.h:194
void AddMultipleToSparseVectorAndDeleteCommonIndex(Fractional multiplier, Index removed_common_index, Fractional drop_tolerance, SparseVector *accumulator_vector) const
void MultiplyByConstant(Fractional factor)
Fractional LookUpCoefficient(Index index) const
void assign(IntType size, const T &v)
Definition lp_types.h:317
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RemoveZeroCostUnconstrainedVariable(ColIndex col, Fractional target_bound, LinearProgram *lp)
void swap(StrongVector &x) noexcept
void push_back(const value_type &val)
#define RUN_PREPROCESSOR(name)
double Fractional
Definition lp_types.h:81
SumWithOneMissing< true > SumWithPositiveInfiniteAndOneMissing
Definition lp_utils.h:395
RowIndex ColToRowIndex(ColIndex col)
Definition lp_types.h:57
constexpr Fractional kInfinity
Definition lp_types.h:87
SumWithOneMissing< false > SumWithNegativeInfiniteAndOneMissing
Definition lp_utils.h:396
ReverseView< Container > reversed_view(const Container &c)
constexpr ColIndex kInvalidCol(-1)
StrictITIVector< RowIndex, ColIndex > RowToColMapping
Definition lp_types.h:394
BeginEndReverseIteratorWrapper< Container > Reverse(const Container &c)
Definition iterators.h:115
StrictITIVector< RowIndex, bool > DenseBooleanColumn
Definition lp_types.h:383
StrictITIVector< ColIndex, ColIndex > ColMapping
Definition lp_types.h:357
StrictITIVector< RowIndex, ConstraintStatus > ConstraintStatusColumn
Definition lp_types.h:397
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
ColIndex RowToColIndex(RowIndex row)
Definition lp_types.h:54
bool IsFinite(Fractional value)
Definition lp_types.h:94
constexpr RowIndex kInvalidRow(-1)
RowIndex ColToRowIndex(ColIndex col)
Definition lp_types.h:57
StrictITIVector< ColIndex, VariableStatus > VariableStatusRow
Definition lp_types.h:372
void Scale(LinearProgram *lp, SparseMatrixScaler *scaler)
void FixConstraintWithFixedStatuses(const DenseColumn &row_lower_bounds, const DenseColumn &row_upper_bounds, ProblemSolution *solution)
constexpr Fractional kInfinity
Definition lp_types.h:87
StrictITIVector< RowIndex, Fractional > DenseColumn
Definition lp_types.h:380
StrictITIVector< ColIndex, Fractional > DenseRow
Definition lp_types.h:351
StrictITIVector< ColIndex, bool > DenseBooleanRow
Definition lp_types.h:354
ConstraintStatus VariableToConstraintStatus(VariableStatus status)
Definition lp_types.cc:113
ColMapping FindProportionalColumns(const SparseMatrix &matrix, Fractional tolerance)
std::string GetProblemStatusString(ProblemStatus problem_status)
Definition lp_types.cc:23
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition integer.h:1839
OR-Tools root namespace.
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition fp_utils.h:173
static constexpr double kInfinity
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
ClosedInterval::Iterator end(ClosedInterval interval)
util_intops::StrongVector< ColumnEntryIndex, ElementIndex > SparseColumn
Definition base_types.h:61
ClosedInterval::Iterator begin(ClosedInterval interval)
BeginEndReverseIteratorWrapper< Container > Reverse(const Container &c)
Definition iterators.h:115
#define RETURN_IF_NULL(x)
#define RETURN_VALUE_IF_NULL(x, v)
#define SCOPED_INSTRUCTION_COUNT(time_limit)
Definition stats.h:422
#define SOLVER_LOG(logger,...)
Definition logging.h:114