Google OR-Tools v9.14
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
71bool MainLpPreprocessor::Run(LinearProgram* lp) {
72 RETURN_VALUE_IF_NULL(lp, false);
73
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);
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 =
402 lp->GetObjectiveCoefficientForMinimizationVersion(col);
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 }
425 lp->SetObjectiveOffset(lp->objective_offset() +
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)) {
1193 lp->SetConstraintBounds(row, -kInfinity, kInfinity);
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;
1530 lp->SetVariableBounds(col, -kInfinity, kInfinity);
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),
1795 lp->GetTransposeSparseMatrix().column(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]
1803 : lp->constraint_lower_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
1820bool UnconstrainedVariablePreprocessor::Run(LinearProgram* lp) {
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 =
1871 lp->GetObjectiveCoefficientForMinimizationVersion(col);
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 =
2731 lp->GetObjectiveCoefficientForMinimizationVersion(e.col);
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
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}
2948
2949MatrixEntry SingletonPreprocessor::GetSingletonRowMatrixEntry(
2950 RowIndex row, const SparseMatrix& transpose) {
2951 for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
2952 const ColIndex col = RowToColIndex(e.row());
2953 if (!column_deletion_helper_.IsColumnMarked(col)) {
2954 DCHECK_NE(0.0, e.coefficient());
2955 return MatrixEntry(row, col, e.coefficient());
2956 }
2957 }
2958
2959 // This shouldn't happen.
2960 LOG(DFATAL) << "No unmarked entry in a row that is supposed to have one.";
2962 return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2963}
2964
2965// --------------------------------------------------------
2966// SingletonColumnSignPreprocessor
2967// --------------------------------------------------------
2968
2971 RETURN_VALUE_IF_NULL(lp, false);
2972 const ColIndex num_cols = lp->num_variables();
2973 if (num_cols == 0) return false;
2974
2975 changed_columns_.clear();
2976 int num_singletons = 0;
2977 for (ColIndex col(0); col < num_cols; ++col) {
2978 SparseColumn* sparse_column = lp->GetMutableSparseColumn(col);
2979 const Fractional cost = lp->objective_coefficients()[col];
2980 if (sparse_column->num_entries() == 1) {
2981 ++num_singletons;
2982 }
2983 if (sparse_column->num_entries() == 1 &&
2984 sparse_column->GetFirstCoefficient() < 0) {
2985 sparse_column->MultiplyByConstant(-1.0);
2986 lp->SetVariableBounds(col, -lp->variable_upper_bounds()[col],
2987 -lp->variable_lower_bounds()[col]);
2988 lp->SetObjectiveCoefficient(col, -cost);
2989 changed_columns_.push_back(col);
2990 }
2991 }
2992 VLOG(1) << "Changed the sign of " << changed_columns_.size() << " columns.";
2993 VLOG(1) << num_singletons << " singleton columns left.";
2994 return !changed_columns_.empty();
2995}
2996
2998 ProblemSolution* solution) const {
3001 for (int i = 0; i < changed_columns_.size(); ++i) {
3002 const ColIndex col = changed_columns_[i];
3003 solution->primal_values[col] = -solution->primal_values[col];
3004 const VariableStatus status = solution->variable_statuses[col];
3006 solution->variable_statuses[col] = VariableStatus::AT_LOWER_BOUND;
3007 } else if (status == VariableStatus::AT_LOWER_BOUND) {
3008 solution->variable_statuses[col] = VariableStatus::AT_UPPER_BOUND;
3009 }
3010 }
3011}
3012
3013// --------------------------------------------------------
3014// DoubletonEqualityRowPreprocessor
3015// --------------------------------------------------------
3016
3017bool DoubletonEqualityRowPreprocessor::Run(LinearProgram* lp) {
3019 RETURN_VALUE_IF_NULL(lp, false);
3020
3021 // This is needed at postsolve.
3022 //
3023 // TODO(user): Get rid of the FIXED status instead to avoid spending
3024 // time/memory for no good reason here.
3025 saved_row_lower_bounds_ = lp->constraint_lower_bounds();
3026 saved_row_upper_bounds_ = lp->constraint_upper_bounds();
3027
3028 // This is needed for postsolving dual.
3029 saved_objective_ = lp->objective_coefficients();
3030
3031 // Note that we don't update the transpose during this preprocessor run.
3032 const SparseMatrix& original_transpose = lp->GetTransposeSparseMatrix();
3033
3034 // Heuristic: We try to subtitute sparse columns first to avoid a complexity
3035 // explosion. Note that if we do long chain of substitution, we can still end
3036 // up with a complexity of O(num_rows x num_cols) instead of O(num_entries).
3037 //
3038 // TODO(user): There is probably some more robust ways.
3039 std::vector<std::pair<int64_t, RowIndex>> sorted_rows;
3040 const RowIndex num_rows(lp->num_constraints());
3041 for (RowIndex row(0); row < num_rows; ++row) {
3042 const SparseColumn& original_row =
3043 original_transpose.column(RowToColIndex(row));
3044 if (original_row.num_entries() != 2 ||
3045 lp->constraint_lower_bounds()[row] !=
3046 lp->constraint_upper_bounds()[row]) {
3047 continue;
3048 }
3049 int64_t score = 0;
3050 for (const SparseColumn::Entry e : original_row) {
3051 const ColIndex col = RowToColIndex(e.row());
3052 score += lp->GetSparseColumn(col).num_entries().value();
3053 }
3054 sorted_rows.push_back({score, row});
3055 }
3056 std::sort(sorted_rows.begin(), sorted_rows.end());
3057
3058 // Iterate over the rows that were already doubletons before this preprocessor
3059 // run, and whose items don't belong to a column that we deleted during this
3060 // run. This implies that the rows are only ever touched once per run, because
3061 // we only modify rows that have an item on a deleted column.
3062 for (const auto p : sorted_rows) {
3063 const RowIndex row = p.second;
3064 const SparseColumn& original_row =
3065 original_transpose.column(RowToColIndex(row));
3066
3067 // Collect the two row items. Skip the ones involving a deleted column.
3068 // Note: we filled r.col[] and r.coeff[] by item order, and currently we
3069 // always pick the first column as the to-be-deleted one.
3070 // TODO(user): make a smarter choice of which column to delete, and
3071 // swap col[] and coeff[] accordingly.
3072 RestoreInfo r; // Use a short name since we're using it everywhere.
3073 int entry_index = 0;
3074 for (const SparseColumn::Entry e : original_row) {
3075 const ColIndex col = RowToColIndex(e.row());
3076 if (column_deletion_helper_.IsColumnMarked(col)) continue;
3077 r.col[entry_index] = col;
3078 r.coeff[entry_index] = e.coefficient();
3079 DCHECK_NE(0.0, r.coeff[entry_index]);
3080 ++entry_index;
3081 }
3082
3083 // Discard some cases that will be treated by other preprocessors, or by
3084 // another run of this one.
3085 // 1) One or two of the items were in a deleted column.
3086 if (entry_index < 2) continue;
3087
3088 // Fill the RestoreInfo, even if we end up not using it (because we
3089 // give up on preprocessing this row): it has a bunch of handy shortcuts.
3090 r.row = row;
3091 r.rhs = lp->constraint_lower_bounds()[row];
3092 for (int col_choice = 0; col_choice < NUM_DOUBLETON_COLS; ++col_choice) {
3093 const ColIndex col = r.col[col_choice];
3094 r.lb[col_choice] = lp->variable_lower_bounds()[col];
3095 r.ub[col_choice] = lp->variable_upper_bounds()[col];
3096 r.objective_coefficient[col_choice] = lp->objective_coefficients()[col];
3097 }
3098
3099 // 2) One of the columns is fixed: don't bother, it will be treated
3100 // by the FixedVariablePreprocessor.
3101 if (r.lb[DELETED] == r.ub[DELETED] || r.lb[MODIFIED] == r.ub[MODIFIED]) {
3102 continue;
3103 }
3104
3105 // Look at the bounds of both variables and exit early if we can delegate
3106 // to another pre-processor; otherwise adjust the bounds of the remaining
3107 // variable as necessary.
3108 // If the current row is: aX + bY = c, then the bounds of Y must be
3109 // adjusted to satisfy Y = c/b + (-a/b)X
3110 //
3111 // Note: when we compute the coefficients of these equations, we can cause
3112 // underflows/overflows that could be avoided if we did the computations
3113 // more carefully; but for now we just treat those cases as
3114 // ProblemStatus::ABNORMAL.
3115 // TODO(user): consider skipping the problematic rows in this preprocessor,
3116 // or trying harder to avoid the under/overflow.
3117 {
3118 const Fractional carry_over_offset = r.rhs / r.coeff[MODIFIED];
3119 const Fractional carry_over_factor =
3120 -r.coeff[DELETED] / r.coeff[MODIFIED];
3121 if (!IsFinite(carry_over_offset) || !IsFinite(carry_over_factor) ||
3122 carry_over_factor == 0.0) {
3124 break;
3125 }
3126
3127 Fractional lb = r.lb[MODIFIED];
3128 Fractional ub = r.ub[MODIFIED];
3129 Fractional carried_over_lb =
3130 r.lb[DELETED] * carry_over_factor + carry_over_offset;
3131 Fractional carried_over_ub =
3132 r.ub[DELETED] * carry_over_factor + carry_over_offset;
3133 if (carry_over_factor < 0) {
3134 std::swap(carried_over_lb, carried_over_ub);
3135 }
3136 if (carried_over_lb <= lb) {
3137 // Default (and simplest) case: the lower bound didn't change.
3138 r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3139 MODIFIED, VariableStatus::AT_LOWER_BOUND, lb);
3140 } else {
3141 lb = carried_over_lb;
3142 r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3143 DELETED,
3144 carry_over_factor > 0 ? VariableStatus::AT_LOWER_BOUND
3146 carry_over_factor > 0 ? r.lb[DELETED] : r.ub[DELETED]);
3147 }
3148 if (carried_over_ub >= ub) {
3149 // Default (and simplest) case: the upper bound didn't change.
3150 r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3151 MODIFIED, VariableStatus::AT_UPPER_BOUND, ub);
3152 } else {
3153 ub = carried_over_ub;
3154 r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3155 DELETED,
3156 carry_over_factor > 0 ? VariableStatus::AT_UPPER_BOUND
3158 carry_over_factor > 0 ? r.ub[DELETED] : r.lb[DELETED]);
3159 }
3160 // 3) If the new bounds are fixed (the domain is a singleton) or
3161 // infeasible, then we let the
3162 // ForcingAndImpliedFreeConstraintPreprocessor do the work.
3163 if (IsSmallerWithinPreprocessorZeroTolerance(ub, lb)) continue;
3164 lp->SetVariableBounds(r.col[MODIFIED], lb, ub);
3165 }
3166
3167 restore_stack_.push_back(r);
3168
3169 // Now, perform the substitution. If the current row is: aX + bY = c
3170 // then any other row containing 'X' with coefficient x can remove the
3171 // entry in X, and instead add an entry on 'Y' with coefficient x(-b/a)
3172 // and a constant offset x(c/a).
3173 // Looking at the matrix, this translates into colY += (-b/a) colX.
3174 DCHECK_NE(r.coeff[DELETED], 0.0);
3175 const Fractional substitution_factor =
3176 -r.coeff[MODIFIED] / r.coeff[DELETED]; // -b/a
3177 const Fractional constant_offset_factor = r.rhs / r.coeff[DELETED]; // c/a
3178 // Again we don't bother too much with over/underflows.
3179 if (!IsFinite(substitution_factor) || substitution_factor == 0.0 ||
3180 !IsFinite(constant_offset_factor)) {
3182 break;
3183 }
3184
3185 // Note that we do not save again a saved column, so that we only save
3186 // columns from the initial LP. This is important to limit the memory usage.
3187 // It complexify a bit the postsolve though.
3188 for (const int col_choice : {DELETED, MODIFIED}) {
3189 const ColIndex col = r.col[col_choice];
3190 columns_saver_.SaveColumnIfNotAlreadyDone(col, lp->GetSparseColumn(col));
3191 }
3192
3193 lp->GetSparseColumn(r.col[DELETED])
3194 .AddMultipleToSparseVectorAndDeleteCommonIndex(
3195 substitution_factor, r.row, parameters_.drop_tolerance(),
3196 lp->GetMutableSparseColumn(r.col[MODIFIED]));
3197
3198 // Apply similar operations on the objective coefficients.
3199 // Note that the offset is being updated by
3200 // SubtractColumnMultipleFromConstraintBound() below.
3201 {
3202 const Fractional new_objective =
3203 r.objective_coefficient[MODIFIED] +
3204 substitution_factor * r.objective_coefficient[DELETED];
3205 if (std::abs(new_objective) > parameters_.drop_tolerance()) {
3206 lp->SetObjectiveCoefficient(r.col[MODIFIED], new_objective);
3207 } else {
3208 lp->SetObjectiveCoefficient(r.col[MODIFIED], 0.0);
3209 }
3210 }
3211
3212 // Carry over the constant factor of the substitution as well.
3213 // TODO(user): rename that method to reflect the fact that it also updates
3214 // the objective offset, in the other direction.
3215 SubtractColumnMultipleFromConstraintBound(r.col[DELETED],
3216 constant_offset_factor, lp);
3217
3218 // If we keep substituing the same "dense" columns over and over, we can
3219 // have a memory in O(num_rows * num_cols) which can be order of magnitude
3220 // larger than the original problem. It is important to reclaim the memory
3221 // of the deleted column right away.
3222 lp->GetMutableSparseColumn(r.col[DELETED])->ClearAndRelease();
3223
3224 // Mark the column and the row for deletion.
3225 column_deletion_helper_.MarkColumnForDeletion(r.col[DELETED]);
3226 row_deletion_helper_.MarkRowForDeletion(r.row);
3227 }
3228 if (status_ != ProblemStatus::INIT) return false;
3229 lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
3230 lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
3231
3232 return !column_deletion_helper_.IsEmpty();
3233}
3234
3236 ProblemSolution* solution) const {
3239 column_deletion_helper_.RestoreDeletedColumns(solution);
3240 row_deletion_helper_.RestoreDeletedRows(solution);
3241
3242 const ColIndex num_cols = solution->variable_statuses.size();
3243 StrictITIVector<ColIndex, bool> new_basic_columns(num_cols, false);
3244
3245 for (const RestoreInfo& r : Reverse(restore_stack_)) {
3246 switch (solution->variable_statuses[r.col[MODIFIED]]) {
3248 LOG(DFATAL) << "FIXED variable produced by DoubletonPreprocessor!";
3249 // In non-fastbuild mode, we rely on the rest of the code producing an
3250 // ProblemStatus::ABNORMAL status here.
3251 break;
3252 // When the modified variable is either basic or free, we keep it as is,
3253 // and simply make the deleted one basic.
3255 ABSL_FALLTHROUGH_INTENDED;
3257 // Several code paths set the deleted column as basic. The code that
3258 // sets its value in that case is below, after the switch() block.
3259 solution->variable_statuses[r.col[DELETED]] = VariableStatus::BASIC;
3260 new_basic_columns[r.col[DELETED]] = true;
3261 break;
3263 ABSL_FALLTHROUGH_INTENDED;
3265 // The bound was induced by a bound of one of the two original
3266 // variables. Put that original variable at its bound, and make
3267 // the other one basic.
3268 const RestoreInfo::ColChoiceAndStatus& bound_backtracking =
3269 solution->variable_statuses[r.col[MODIFIED]] ==
3271 ? r.bound_backtracking_at_lower_bound
3272 : r.bound_backtracking_at_upper_bound;
3273 const ColIndex bounded_var = r.col[bound_backtracking.col_choice];
3274 const ColIndex basic_var =
3275 r.col[OtherColChoice(bound_backtracking.col_choice)];
3276 solution->variable_statuses[bounded_var] = bound_backtracking.status;
3277 solution->primal_values[bounded_var] = bound_backtracking.value;
3278 solution->variable_statuses[basic_var] = VariableStatus::BASIC;
3279 new_basic_columns[basic_var] = true;
3280 // If the modified column is VariableStatus::BASIC, then its value is
3281 // already set correctly. If it's the deleted column that is basic, its
3282 // value is set below the switch() block.
3283 }
3284 }
3285
3286 // Restore the value of the deleted column if it is VariableStatus::BASIC.
3287 if (solution->variable_statuses[r.col[DELETED]] == VariableStatus::BASIC) {
3288 solution->primal_values[r.col[DELETED]] =
3289 (r.rhs -
3290 solution->primal_values[r.col[MODIFIED]] * r.coeff[MODIFIED]) /
3291 r.coeff[DELETED];
3292 }
3293
3294 // Make the deleted constraint status FIXED.
3295 solution->constraint_statuses[r.row] = ConstraintStatus::FIXED_VALUE;
3296 }
3297
3298 // Now we need to reconstruct the dual. This is a bit tricky and is basically
3299 // the same as inverting a really structed and easy to invert matrix. For n
3300 // doubleton rows, looking only at the new_basic_columns, there is exactly n
3301 // by construction (one per row). We consider only this n x n matrix, and we
3302 // must choose dual row values so that we make the reduced costs zero on all
3303 // these columns.
3304 //
3305 // There is always an order that make this matrix triangular. We start with a
3306 // singleton column which fix its corresponding row and then work on the
3307 // square submatrix left. We can always start and continue, because if we take
3308 // the first substituted row of the current submatrix, if its deleted column
3309 // was in the submatrix we have a singleton column. If it is outside, we have
3310 // 2 n - 1 entries for a matrix with n columns, so one must be singleton.
3311 //
3312 // Note(user): Another advantage of working on the "original" matrix before
3313 // this presolve is an increased precision.
3314 //
3315 // TODO(user): We can probably use something better than a vector of set,
3316 // but the number of entry is really sparse though. And the size of a set<int>
3317 // is 24 bytes, same as a std::vector<int>.
3318 StrictITIVector<ColIndex, std::set<int>> col_to_index(num_cols);
3319 for (int i = 0; i < restore_stack_.size(); ++i) {
3320 const RestoreInfo& r = restore_stack_[i];
3321 col_to_index[r.col[MODIFIED]].insert(i);
3322 col_to_index[r.col[DELETED]].insert(i);
3323 }
3324 std::vector<ColIndex> singleton_col;
3325 for (ColIndex col(0); col < num_cols; ++col) {
3326 if (!new_basic_columns[col]) continue;
3327 if (col_to_index[col].size() == 1) singleton_col.push_back(col);
3328 }
3329 while (!singleton_col.empty()) {
3330 const ColIndex col = singleton_col.back();
3331 singleton_col.pop_back();
3332 if (!new_basic_columns[col]) continue;
3333 if (col_to_index[col].empty()) continue;
3334 CHECK_EQ(col_to_index[col].size(), 1);
3335 const int index = *col_to_index[col].begin();
3336 const RestoreInfo& r = restore_stack_[index];
3337
3338 const ColChoice col_choice = r.col[MODIFIED] == col ? MODIFIED : DELETED;
3339
3340 // Adjust the dual value of the deleted constraint so that col have a
3341 // reduced costs of zero.
3342 CHECK_EQ(solution->dual_values[r.row], 0.0);
3343 const SparseColumn& saved_col =
3344 columns_saver_.SavedColumn(r.col[col_choice]);
3345 const Fractional current_reduced_cost =
3346 saved_objective_[r.col[col_choice]] -
3347 PreciseScalarProduct(solution->dual_values, saved_col);
3348 solution->dual_values[r.row] = current_reduced_cost / r.coeff[col_choice];
3349
3350 // Update singleton
3351 col_to_index[r.col[DELETED]].erase(index);
3352 col_to_index[r.col[MODIFIED]].erase(index);
3353 if (col_to_index[r.col[DELETED]].size() == 1) {
3354 singleton_col.push_back(r.col[DELETED]);
3355 }
3356 if (col_to_index[r.col[MODIFIED]].size() == 1) {
3357 singleton_col.push_back(r.col[MODIFIED]);
3358 }
3359 }
3360
3361 // Fix potential bad ConstraintStatus::FIXED_VALUE statuses.
3362 FixConstraintWithFixedStatuses(saved_row_lower_bounds_,
3363 saved_row_upper_bounds_, solution);
3364}
3365
3366void FixConstraintWithFixedStatuses(const DenseColumn& row_lower_bounds,
3367 const DenseColumn& row_upper_bounds,
3369 const RowIndex num_rows = solution->constraint_statuses.size();
3370 DCHECK_EQ(row_lower_bounds.size(), num_rows);
3371 DCHECK_EQ(row_upper_bounds.size(), num_rows);
3372 for (RowIndex row(0); row < num_rows; ++row) {
3373 if (solution->constraint_statuses[row] != ConstraintStatus::FIXED_VALUE) {
3374 continue;
3375 }
3376 if (row_lower_bounds[row] == row_upper_bounds[row]) continue;
3377
3378 // We need to fix the status and we just need to make sure that the bound we
3379 // choose satisfies the LP optimality conditions.
3380 if (solution->dual_values[row] > 0) {
3381 solution->constraint_statuses[row] = ConstraintStatus::AT_LOWER_BOUND;
3382 } else {
3383 solution->constraint_statuses[row] = ConstraintStatus::AT_UPPER_BOUND;
3384 }
3385 }
3386}
3387
3388void DoubletonEqualityRowPreprocessor::
3389 SwapDeletedAndModifiedVariableRestoreInfo(RestoreInfo* r) {
3390 using std::swap;
3391 swap(r->col[DELETED], r->col[MODIFIED]);
3392 swap(r->coeff[DELETED], r->coeff[MODIFIED]);
3393 swap(r->lb[DELETED], r->lb[MODIFIED]);
3394 swap(r->ub[DELETED], r->ub[MODIFIED]);
3395 swap(r->objective_coefficient[DELETED], r->objective_coefficient[MODIFIED]);
3396}
3397
3398// --------------------------------------------------------
3399// DualizerPreprocessor
3400// --------------------------------------------------------
3401
3404 RETURN_VALUE_IF_NULL(lp, false);
3406 return false;
3407 }
3408
3409 // Store the original problem size and direction.
3410 primal_num_cols_ = lp->num_variables();
3411 primal_num_rows_ = lp->num_constraints();
3412 primal_is_maximization_problem_ = lp->IsMaximizationProblem();
3413
3414 // If we need to decide whether or not to take the dual, we only take it when
3415 // the matrix has more rows than columns. The number of rows of a linear
3416 // program gives the size of the square matrices we need to invert and the
3417 // order of iterations of the simplex method. So solving a program with less
3418 // rows is likely a better alternative. Note that the number of row of the
3419 // dual is the number of column of the primal.
3420 //
3421 // Note however that the default is a conservative factor because if the
3422 // user gives us a primal program, we assume he knows what he is doing and
3423 // sometimes a problem is a lot faster to solve in a given formulation
3424 // even if its dimension would say otherwise.
3425 //
3426 // Another reason to be conservative, is that the number of columns of the
3427 // dual is the number of rows of the primal plus up to two times the number of
3428 // columns of the primal.
3429 //
3430 // TODO(user): This effect can be lowered if we use some of the extra
3431 // variables as slack variable which we are not doing at this point.
3433 if (1.0 * primal_num_rows_.value() <
3434 parameters_.dualizer_threshold() * primal_num_cols_.value()) {
3435 return false;
3436 }
3437 }
3439 // Save the linear program bounds.
3440 // Also make sure that all the bounded variable have at least one bound set to
3441 // zero. This will be needed to post-solve a dual-basic solution into a
3442 // primal-basic one.
3443 const ColIndex num_cols = lp->num_variables();
3444 variable_lower_bounds_.assign(num_cols, 0.0);
3445 variable_upper_bounds_.assign(num_cols, 0.0);
3446 for (ColIndex col(0); col < num_cols; ++col) {
3447 const Fractional lower = lp->variable_lower_bounds()[col];
3448 const Fractional upper = lp->variable_upper_bounds()[col];
3449
3450 // We need to shift one of the bound to zero.
3451 variable_lower_bounds_[col] = lower;
3452 variable_upper_bounds_[col] = upper;
3453 const Fractional value = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3454 if (value != 0.0) {
3455 lp->SetVariableBounds(col, lower - value, upper - value);
3456 SubtractColumnMultipleFromConstraintBound(col, value, lp);
3457 }
3458 }
3459
3460 // Fill the information that will be needed during postsolve.
3461 //
3462 // TODO(user): This will break if PopulateFromDual() is changed. so document
3463 // the convention or make the function fill these vectors?
3464 dual_status_correspondence_.clear();
3465 for (RowIndex row(0); row < primal_num_rows_; ++row) {
3466 const Fractional lower_bound = lp->constraint_lower_bounds()[row];
3467 const Fractional upper_bound = lp->constraint_upper_bounds()[row];
3468 if (lower_bound == upper_bound) {
3469 dual_status_correspondence_.push_back(VariableStatus::FIXED_VALUE);
3470 } else if (upper_bound != kInfinity) {
3471 dual_status_correspondence_.push_back(VariableStatus::AT_UPPER_BOUND);
3472 } else if (lower_bound != -kInfinity) {
3473 dual_status_correspondence_.push_back(VariableStatus::AT_LOWER_BOUND);
3474 } else {
3475 LOG(DFATAL) << "There should be no free constraint in this lp.";
3476 }
3477 }
3478 slack_or_surplus_mapping_.clear();
3479 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3480 const Fractional lower_bound = lp->variable_lower_bounds()[col];
3481 const Fractional upper_bound = lp->variable_upper_bounds()[col];
3482 if (lower_bound != -kInfinity) {
3483 dual_status_correspondence_.push_back(
3484 upper_bound == lower_bound ? VariableStatus::FIXED_VALUE
3486 slack_or_surplus_mapping_.push_back(col);
3487 }
3488 }
3489 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3490 const Fractional lower_bound = lp->variable_lower_bounds()[col];
3491 const Fractional upper_bound = lp->variable_upper_bounds()[col];
3492 if (upper_bound != kInfinity) {
3493 dual_status_correspondence_.push_back(
3494 upper_bound == lower_bound ? VariableStatus::FIXED_VALUE
3496 slack_or_surplus_mapping_.push_back(col);
3497 }
3498 }
3499
3500 // TODO(user): There are two different ways to deal with ranged rows when
3501 // taking the dual. The default way is to duplicate such rows, see
3502 // PopulateFromDual() for details. Another way is to call
3503 // lp->AddSlackVariablesForFreeAndBoxedRows() before calling
3504 // PopulateFromDual(). Adds an option to switch between the two as this may
3505 // change the running time?
3506 //
3507 // Note however that the default algorithm is likely to result in a faster
3508 // solving time because the dual program will have less rows.
3509 LinearProgram dual;
3510 dual.PopulateFromDual(*lp, &duplicated_rows_);
3511 dual.Swap(lp);
3512 return true;
3513}
3514
3515// Note(user): This assumes that LinearProgram.PopulateFromDual() uses
3516// the first ColIndex and RowIndex for the rows and columns of the given
3517// problem.
3521
3522 DenseRow new_primal_values(primal_num_cols_, 0.0);
3523 VariableStatusRow new_variable_statuses(primal_num_cols_,
3525 DCHECK_LE(primal_num_cols_, RowToColIndex(solution->dual_values.size()));
3526 for (ColIndex col(0); col < primal_num_cols_; ++col) {
3527 RowIndex row = ColToRowIndex(col);
3528 const Fractional lower = variable_lower_bounds_[col];
3529 const Fractional upper = variable_upper_bounds_[col];
3530
3531 // The new variable value corresponds to the dual value of the dual.
3532 // The shift applied during presolve needs to be removed.
3533 const Fractional shift = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3534 new_primal_values[col] = solution->dual_values[row] + shift;
3535
3536 // A variable will be VariableStatus::BASIC if the dual constraint is not.
3537 if (solution->constraint_statuses[row] != ConstraintStatus::BASIC) {
3538 new_variable_statuses[col] = VariableStatus::BASIC;
3539 } else {
3540 // Otherwise, the dual value must be zero (if the solution is feasible),
3541 // and the variable is at an exact bound or zero if it is
3542 // VariableStatus::FREE. Note that this works because the bounds are
3543 // shifted to 0.0 in the presolve!
3544 new_variable_statuses[col] = ComputeVariableStatus(shift, lower, upper);
3545 }
3546 }
3547
3548 // A basic variable that corresponds to slack/surplus variable is the same as
3549 // a basic row. The new variable status (that was just set to
3550 // VariableStatus::BASIC above)
3551 // needs to be corrected and depends on the variable type (slack/surplus).
3552 const ColIndex begin = RowToColIndex(primal_num_rows_);
3553 const ColIndex end = dual_status_correspondence_.size();
3554 DCHECK_GE(solution->variable_statuses.size(), end);
3555 DCHECK_EQ(end - begin, slack_or_surplus_mapping_.size());
3556 for (ColIndex index(begin); index < end; ++index) {
3557 if (solution->variable_statuses[index] == VariableStatus::BASIC) {
3558 const ColIndex col = slack_or_surplus_mapping_[index - begin];
3559 const VariableStatus status = dual_status_correspondence_[index];
3560
3561 // The new variable value is set to its exact bound because the dual
3562 // variable value can be imprecise.
3563 new_variable_statuses[col] = status;
3566 new_primal_values[col] = variable_upper_bounds_[col];
3567 } else {
3569 new_primal_values[col] = variable_lower_bounds_[col];
3570 }
3571 }
3572 }
3573
3574 // Note the <= in the DCHECK, since we may need to add variables when taking
3575 // the dual.
3576 DCHECK_LE(primal_num_rows_, ColToRowIndex(solution->primal_values.size()));
3577 DenseColumn new_dual_values(primal_num_rows_, 0.0);
3578 ConstraintStatusColumn new_constraint_statuses(primal_num_rows_,
3580
3581 // Note that the sign need to be corrected because of the special behavior of
3582 // PopulateFromDual() on a maximization problem, see the comment in the
3583 // declaration of PopulateFromDual().
3584 Fractional sign = primal_is_maximization_problem_ ? -1 : 1;
3585 for (RowIndex row(0); row < primal_num_rows_; ++row) {
3586 const ColIndex col = RowToColIndex(row);
3587 new_dual_values[row] = sign * solution->primal_values[col];
3588
3589 // A constraint will be ConstraintStatus::BASIC if the dual variable is not.
3590 if (solution->variable_statuses[col] != VariableStatus::BASIC) {
3591 new_constraint_statuses[row] = ConstraintStatus::BASIC;
3592 if (duplicated_rows_[row] != kInvalidCol) {
3593 if (solution->variable_statuses[duplicated_rows_[row]] ==
3595 // The duplicated row is always about the lower bound.
3596 new_constraint_statuses[row] = ConstraintStatus::AT_LOWER_BOUND;
3597 }
3598 }
3599 } else {
3600 // ConstraintStatus::AT_LOWER_BOUND/ConstraintStatus::AT_UPPER_BOUND/
3601 // ConstraintStatus::FIXED depend on the type of the constraint at this
3602 // position.
3603 new_constraint_statuses[row] =
3604 VariableToConstraintStatus(dual_status_correspondence_[col]);
3605 }
3606
3607 // If the original row was duplicated, we need to take into account the
3608 // value of the corresponding dual column.
3609 if (duplicated_rows_[row] != kInvalidCol) {
3610 new_dual_values[row] +=
3611 sign * solution->primal_values[duplicated_rows_[row]];
3612 }
3613
3614 // Because non-basic variable values are exactly at one of their bounds, a
3615 // new basic constraint will have a dual value exactly equal to zero.
3616 DCHECK(new_dual_values[row] == 0 ||
3617 new_constraint_statuses[row] != ConstraintStatus::BASIC);
3618 }
3619
3620 solution->status = ChangeStatusToDualStatus(solution->status);
3621 new_primal_values.swap(solution->primal_values);
3622 new_dual_values.swap(solution->dual_values);
3623 new_variable_statuses.swap(solution->variable_statuses);
3624 new_constraint_statuses.swap(solution->constraint_statuses);
3625}
3626
3628 ProblemStatus status) const {
3629 switch (status) {
3642 default:
3643 return status;
3644 }
3645}
3646
3647// --------------------------------------------------------
3648// ShiftVariableBoundsPreprocessor
3649// --------------------------------------------------------
3650
3653 RETURN_VALUE_IF_NULL(lp, false);
3654
3655 // Save the linear program bounds before shifting them.
3656 bool all_variable_domains_contain_zero = true;
3657 const ColIndex num_cols = lp->num_variables();
3658 variable_initial_lbs_.assign(num_cols, 0.0);
3659 variable_initial_ubs_.assign(num_cols, 0.0);
3660 for (ColIndex col(0); col < num_cols; ++col) {
3661 variable_initial_lbs_[col] = lp->variable_lower_bounds()[col];
3662 variable_initial_ubs_[col] = lp->variable_upper_bounds()[col];
3663 if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3664 all_variable_domains_contain_zero = false;
3665 }
3666 }
3667 VLOG(1) << "Maximum variable bounds magnitude (before shift): "
3668 << ComputeMaxVariableBoundsMagnitude(*lp);
3669
3670 // Abort early if there is nothing to do.
3671 if (all_variable_domains_contain_zero) return false;
3672
3673 // Shift the variable bounds and compute the changes to the constraint bounds
3674 // and objective offset in a precise way.
3675 int num_bound_shifts = 0;
3676 const RowIndex num_rows = lp->num_constraints();
3677 KahanSum objective_offset;
3678 util_intops::StrongVector<RowIndex, KahanSum> row_offsets(num_rows.value());
3679 offsets_.assign(num_cols, 0.0);
3680 for (ColIndex col(0); col < num_cols; ++col) {
3681 if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3682 Fractional offset = MinInMagnitudeOrZeroIfInfinite(
3683 variable_initial_lbs_[col], variable_initial_ubs_[col]);
3684 if (in_mip_context_ && lp->IsVariableInteger(col)) {
3685 // In the integer case, we truncate the number because if for instance
3686 // the lower bound is a positive integer + epsilon, we only want to
3687 // shift by the integer and leave the lower bound at epsilon.
3688 //
3689 // TODO(user): This would not be needed, if we always make the bound
3690 // of an integer variable integer before applying this preprocessor.
3691 offset = trunc(offset);
3692 } else {
3693 DCHECK_NE(offset, 0.0);
3694 }
3695 offsets_[col] = offset;
3696 lp->SetVariableBounds(col, variable_initial_lbs_[col] - offset,
3697 variable_initial_ubs_[col] - offset);
3698 const SparseColumn& sparse_column = lp->GetSparseColumn(col);
3699 for (const SparseColumn::Entry e : sparse_column) {
3700 row_offsets[e.row()].Add(e.coefficient() * offset);
3701 }
3702 objective_offset.Add(lp->objective_coefficients()[col] * offset);
3703 ++num_bound_shifts;
3704 }
3705 }
3706 VLOG(1) << "Maximum variable bounds magnitude (after " << num_bound_shifts
3707 << " shifts): " << ComputeMaxVariableBoundsMagnitude(*lp);
3708
3709 // Apply the changes to the constraint bound and objective offset.
3710 for (RowIndex row(0); row < num_rows; ++row) {
3711 if (!std::isfinite(row_offsets[row].Value())) {
3712 // This can happen for bad input where we get floating point overflow.
3713 // We can even get nan if we have two overflow in opposite direction.
3714 VLOG(1) << "Shifting variable bounds causes a floating point overflow "
3715 "for constraint "
3716 << row << ".";
3718 return false;
3719 }
3720 lp->SetConstraintBounds(
3721 row, lp->constraint_lower_bounds()[row] - row_offsets[row].Value(),
3722 lp->constraint_upper_bounds()[row] - row_offsets[row].Value());
3723 }
3724 if (!std::isfinite(objective_offset.Value())) {
3725 VLOG(1) << "Shifting variable bounds causes a floating point overflow "
3726 "for the objective.";
3728 return false;
3729 }
3730 lp->SetObjectiveOffset(lp->objective_offset() + objective_offset.Value());
3731 return true;
3732}
3733
3735 ProblemSolution* solution) const {
3738 const ColIndex num_cols = solution->variable_statuses.size();
3739 for (ColIndex col(0); col < num_cols; ++col) {
3740 if (in_mip_context_) {
3741 solution->primal_values[col] += offsets_[col];
3742 } else {
3743 switch (solution->variable_statuses[col]) {
3745 ABSL_FALLTHROUGH_INTENDED;
3747 solution->primal_values[col] = variable_initial_lbs_[col];
3748 break;
3750 solution->primal_values[col] = variable_initial_ubs_[col];
3751 break;
3753 solution->primal_values[col] += offsets_[col];
3754 break;
3756 break;
3757 }
3758 }
3759 }
3760}
3761
3762// --------------------------------------------------------
3763// ScalingPreprocessor
3764// --------------------------------------------------------
3765
3768 RETURN_VALUE_IF_NULL(lp, false);
3769 if (!parameters_.use_scaling()) return false;
3770
3771 // Save the linear program bounds before scaling them.
3772 const ColIndex num_cols = lp->num_variables();
3773 variable_lower_bounds_.assign(num_cols, 0.0);
3774 variable_upper_bounds_.assign(num_cols, 0.0);
3775 for (ColIndex col(0); col < num_cols; ++col) {
3776 variable_lower_bounds_[col] = lp->variable_lower_bounds()[col];
3777 variable_upper_bounds_[col] = lp->variable_upper_bounds()[col];
3778 }
3779
3780 // See the doc of these functions for more details.
3781 // It is important to call Scale() before the other two.
3782 Scale(lp, &scaler_, parameters_.scaling_method());
3783 cost_scaling_factor_ = lp->ScaleObjective(parameters_.cost_scaling());
3784 bound_scaling_factor_ = lp->ScaleBounds();
3785
3786 return true;
3787}
3788
3789void ScalingPreprocessor::RecoverSolution(ProblemSolution* solution) const {
3792
3793 scaler_.ScaleRowVector(false, &(solution->primal_values));
3794 for (ColIndex col(0); col < solution->primal_values.size(); ++col) {
3795 solution->primal_values[col] *= bound_scaling_factor_;
3796 }
3797
3798 scaler_.ScaleColumnVector(false, &(solution->dual_values));
3799 for (RowIndex row(0); row < solution->dual_values.size(); ++row) {
3800 solution->dual_values[row] *= cost_scaling_factor_;
3801 }
3802
3803 // Make sure the variable are at they exact bounds according to their status.
3804 // This just remove a really low error (about 1e-15) but allows to keep the
3805 // variables at their exact bounds.
3806 const ColIndex num_cols = solution->primal_values.size();
3807 for (ColIndex col(0); col < num_cols; ++col) {
3808 switch (solution->variable_statuses[col]) {
3810 ABSL_FALLTHROUGH_INTENDED;
3812 solution->primal_values[col] = variable_upper_bounds_[col];
3813 break;
3815 solution->primal_values[col] = variable_lower_bounds_[col];
3816 break;
3818 ABSL_FALLTHROUGH_INTENDED;
3820 break;
3821 }
3822 }
3823}
3824
3825// --------------------------------------------------------
3826// ToMinimizationPreprocessor
3827// --------------------------------------------------------
3828
3831 RETURN_VALUE_IF_NULL(lp, false);
3832 if (lp->IsMaximizationProblem()) {
3833 for (ColIndex col(0); col < lp->num_variables(); ++col) {
3834 const Fractional coeff = lp->objective_coefficients()[col];
3835 if (coeff != 0.0) {
3836 lp->SetObjectiveCoefficient(col, -coeff);
3837 }
3838 }
3839 lp->SetMaximizationProblem(false);
3842 }
3843 return false;
3844}
3845
3847 ProblemSolution* solution) const {}
3848
3849// --------------------------------------------------------
3850// AddSlackVariablesPreprocessor
3851// --------------------------------------------------------
3852
3853bool AddSlackVariablesPreprocessor::Run(LinearProgram* lp) {
3855 RETURN_VALUE_IF_NULL(lp, false);
3856 lp->AddSlackVariablesWhereNecessary(
3857 /*detect_integer_constraints=*/true);
3858 first_slack_col_ = lp->GetFirstSlackVariable();
3859 return true;
3860}
3861
3863 ProblemSolution* solution) const {
3866
3867 // Compute constraint statuses from statuses of slack variables.
3868 const RowIndex num_rows = solution->dual_values.size();
3869 for (RowIndex row(0); row < num_rows; ++row) {
3870 const ColIndex slack_col = first_slack_col_ + RowToColIndex(row);
3871 const VariableStatus variable_status =
3872 solution->variable_statuses[slack_col];
3873 ConstraintStatus constraint_status = ConstraintStatus::FREE;
3874 // The slack variables have reversed bounds - if the value of the variable
3875 // is at one bound, the value of the constraint is at the opposite bound.
3876 switch (variable_status) {
3878 constraint_status = ConstraintStatus::AT_UPPER_BOUND;
3879 break;
3881 constraint_status = ConstraintStatus::AT_LOWER_BOUND;
3882 break;
3883 default:
3884 constraint_status = VariableToConstraintStatus(variable_status);
3885 break;
3886 }
3887 solution->constraint_statuses[row] = constraint_status;
3889
3890 // Drop the primal values and variable statuses for slack variables.
3891 solution->primal_values.resize(first_slack_col_, 0.0);
3892 solution->variable_statuses.resize(first_slack_col_, VariableStatus::FREE);
3893}
3894
3895} // namespace glop
3896} // namespace operations_research
void EnableLogging(bool enable)
Definition logging.h:47
void RecoverSolution(ProblemSolution *solution) const final
void Clear()
Restores the class to its initial state.
const DenseBooleanRow & GetMarkedColumns() const
Returns a Boolean vector of the column to be deleted.
void RestoreDeletedColumns(ProblemSolution *solution) const
bool IsEmpty() const
Returns true if no columns have been marked for deletion.
bool IsColumnMarked(ColIndex col) const
Returns whether or not the given column is marked for deletion.
void MarkColumnForDeletionWithState(ColIndex col, Fractional value, VariableStatus status)
const SparseColumn & SavedOrEmptyColumn(ColIndex col) const
void SaveColumn(ColIndex col, const SparseColumn &column)
Saves a column. The first version CHECKs that it is not already done.
const SparseColumn & SavedColumn(ColIndex col) const
Returns the saved column. The first version CHECKs that it was saved.
void SaveColumnIfNotAlreadyDone(ColIndex col, const SparseColumn &column)
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
ProblemStatus ChangeStatusToDualStatus(ProblemStatus status) const
Convert the given problem status to the one of its dual.
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
Removes the fixed variables from the problem.
void RecoverSolution(ProblemSolution *solution) const final
Removes the constraints with no bounds from the problem.
void RecoverSolution(ProblemSolution *solution) const final
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
Returns the objective coefficients (or cost) of variables as a row vector.
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
ColIndex num_variables() const
Returns the number of variables.
Definition lp_data.h:212
Fractional objective_offset() const
Returns the objective offset and scaling factor.
Definition lp_data.h:267
RowIndex num_constraints() const
Returns the number of constraints.
Definition lp_data.h:215
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
bool IsEmpty() const
Returns true if no rows have been marked for deletion.
void Clear()
Restores the class to its initial state.
void MarkRowForDeletion(RowIndex row)
Adds a deleted row to the helper.
bool IsRowMarked(RowIndex row) const
Returns whether or not the given row is marked for deletion.
const DenseBooleanColumn & GetMarkedRows() const
Returns a Boolean vector of the row to be deleted.
void RestoreDeletedRows(ProblemSolution *solution) const
void UnmarkRow(RowIndex row)
If the given row was marked for deletion, unmark it.
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
SingletonUndo(OperationType type, const LinearProgram &lp, MatrixEntry e, ConstraintStatus status)
void Undo(const GlopParameters &parameters, const SparseColumn &saved_column, const SparseColumn &saved_row, ProblemSolution *solution) const
void ScaleColumnVector(bool up, DenseColumn *column_vector) const
void ScaleRowVector(bool up, DenseRow *row_vector) const
const SparseColumn & column(ColIndex col) const
Access the underlying sparse columns.
Definition sparse.h:193
SparseColumn * mutable_column(ColIndex col)
Definition sparse.h:194
EntryIndex num_entries() const
Note this method can only be used when the vector has no duplicates.
Fractional LookUpCoefficient(Index index) const
void assign(IntType size, const T &v)
Definition lp_types.h:317
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RemoveZeroCostUnconstrainedVariable(ColIndex col, Fractional target_bound, LinearProgram *lp)
double coefficient(Variable variable) const
Returns 0.0 if the variable is not used in the constraint.
void swap(StrongVector &x) noexcept
void push_back(const value_type &val)
time_limit
Definition solve.cc:22
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
Column of booleans.
Definition lp_types.h:383
StrictITIVector< ColIndex, ColIndex > ColMapping
Row of column indices. Used to represent mappings between columns.
Definition lp_types.h:357
StrictITIVector< RowIndex, ConstraintStatus > ConstraintStatusColumn
Column of constraints (slack variables) statuses.
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
SumWithOneMissing< true > SumWithPositiveInfiniteAndOneMissing
Definition lp_utils.h:395
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
Definition lp_types.h:54
bool IsFinite(Fractional value)
Definition lp_types.h:94
constexpr RowIndex kInvalidRow(-1)
RowIndex ColToRowIndex(ColIndex col)
Get the RowIndex corresponding to the row # col.
Definition lp_types.h:57
StrictITIVector< ColIndex, VariableStatus > VariableStatusRow
Row of variable statuses.
Definition lp_types.h:372
void Scale(LinearProgram *lp, SparseMatrixScaler *scaler)
void FixConstraintWithFixedStatuses(const DenseColumn &row_lower_bounds, const DenseColumn &row_upper_bounds, ProblemSolution *solution)
constexpr Fractional kInfinity
Infinity for type Fractional.
Definition lp_types.h:87
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:380
StrictITIVector< ColIndex, Fractional > DenseRow
Row-vector types. Row-vector types are indexed by a column index.
Definition lp_types.h:351
StrictITIVector< ColIndex, bool > DenseBooleanRow
Row of booleans.
Definition lp_types.h:354
SumWithOneMissing< false > SumWithNegativeInfiniteAndOneMissing
Definition lp_utils.h:396
ProblemStatus
Different statuses for a given problem.
Definition lp_types.h:105
@ ABNORMAL
An error occurred during the solving process.
Definition lp_types.h:158
@ INVALID_PROBLEM
The input problem was invalid (see LinearProgram.IsValid()).
Definition lp_types.h:161
@ INIT
The solver didn't had a chance to prove anything.
Definition lp_types.h:146
ConstraintStatus VariableToConstraintStatus(VariableStatus status)
Returns the ConstraintStatus corresponding to a given VariableStatus.
Definition lp_types.cc:113
ColMapping FindProportionalColumns(const SparseMatrix &matrix, Fractional tolerance)
std::string GetProblemStatusString(ProblemStatus problem_status)
Returns the string representation of the ProblemStatus enum.
Definition lp_types.cc:23
std::function< int64_t(const Model &)> Value(IntegerVariable v)
This checks that the variable is fixed.
Definition integer.h:1601
In SWIG mode, we don't want anything besides these top-level includes.
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 RUN_PREPROCESSOR(name)
#define RETURN_IF_NULL(x)
#define RETURN_VALUE_IF_NULL(x, v)
#define SCOPED_INSTRUCTION_COUNT(time_limit)
Definition stats.h:424
Holds a triplet (row, col, coefficient).
Contains the solution of a LinearProgram as returned by a preprocessor.
Definition lp_data.h:670
#define SOLVER_LOG(logger,...)
Definition logging.h:110