Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
preprocessor.h
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//
15// This file contains the presolving code for a LinearProgram.
16//
17// A classical reference is:
18// E. D. Andersen, K. D. Andersen, "Presolving in linear programming.",
19// Mathematical Programming 71 (1995) 221-245.
20
21#ifndef OR_TOOLS_GLOP_PREPROCESSOR_H_
22#define OR_TOOLS_GLOP_PREPROCESSOR_H_
23
24#include <deque>
25#include <memory>
26#include <string>
27#include <vector>
28
29#include "absl/strings/string_view.h"
31#include "ortools/glop/parameters.pb.h"
36
37namespace operations_research {
38namespace glop {
39
40// --------------------------------------------------------
41// Preprocessor
42// --------------------------------------------------------
43// This is the base class for preprocessors.
44//
45// TODO(user): On most preprocessors, calling Run() more than once will not work
46// as expected. Fix? or document and crash in debug if this happens.
47class Preprocessor {
48 public:
49 explicit Preprocessor(const GlopParameters* parameters);
50 Preprocessor(const Preprocessor&) = delete;
51 Preprocessor& operator=(const Preprocessor&) = delete;
52 virtual ~Preprocessor();
54 // Runs the preprocessor by modifying the given linear program. Returns true
55 // if a postsolve step will be needed (i.e. RecoverSolution() is not the
56 // identity function). Also updates status_ to something different from
57 // ProblemStatus::INIT if the problem was solved (including bad statuses
58 // like ProblemStatus::ABNORMAL, ProblemStatus::INFEASIBLE, etc.).
59 virtual bool Run(LinearProgram* lp) = 0;
60
61 // Stores the optimal solution of the linear program that was passed to
62 // Run(). The given solution needs to be set to the optimal solution of the
63 // linear program "modified" by Run().
64 virtual void RecoverSolution(ProblemSolution* solution) const = 0;
65
66 // Returns the status of the preprocessor.
67 // A status different from ProblemStatus::INIT means that the problem is
68 // solved and there is not need to call subsequent preprocessors.
69 ProblemStatus status() const { return status_; }
70
71 // Some preprocessors only need minimal changes when used with integer
72 // variables in a MIP context. Setting this to true allows to consider integer
73 // variables as integer in these preprocessors.
74 //
75 // Not all preprocessors handle integer variables correctly, calling this
76 // function on them will cause a LOG(FATAL).
77 virtual void UseInMipContext() { in_mip_context_ = true; }
78
80
81 protected:
82 // Returns true if a is less than b (or slighlty greater than b with a given
83 // tolerance).
85 return ::operations_research::IsSmallerWithinTolerance(
86 a, b, parameters_.solution_feasibility_tolerance());
87 }
89 Fractional b) const {
90 // TODO(user): use an absolute tolerance here to be even more defensive?
91 return ::operations_research::IsSmallerWithinTolerance(
92 a, b, parameters_.preprocessor_zero_tolerance());
93 }
94
96 const GlopParameters& parameters_;
98 std::unique_ptr<TimeLimit> infinite_time_limit_;
102// --------------------------------------------------------
103// MainLpPreprocessor
104// --------------------------------------------------------
105// This is the main LP preprocessor responsible for calling all the other
106// preprocessors in this file, possibly more than once.
107class MainLpPreprocessor : public Preprocessor {
108 public:
109 explicit MainLpPreprocessor(const GlopParameters* parameters)
113 ~MainLpPreprocessor() override = default;
114
115 bool Run(LinearProgram* lp) final;
118 // Like RecoverSolution but destroys data structures as it goes to reduce peak
119 // RAM use. After calling this the MainLpPreprocessor object may no longer be
120 // used.
122
123 void SetLogger(SolverLogger* logger) { logger_ = logger; }
124
125 private:
126 // Runs the given preprocessor and push it on preprocessors_ for the postsolve
127 // step when needed.
128 void RunAndPushIfRelevant(std::unique_ptr<Preprocessor> preprocessor,
129 absl::string_view name, TimeLimit* time_limit,
130 LinearProgram* lp);
131
132 // Stack of preprocessors currently applied to the lp that needs postsolve.
133 std::vector<std::unique_ptr<Preprocessor>> preprocessors_;
134
135 // Helpers for logging during presolve.
136 SolverLogger default_logger_;
137 SolverLogger* logger_ = &default_logger_;
138
139 // Initial dimension of the lp given to Run(), for displaying purpose.
140 EntryIndex initial_num_entries_;
141 RowIndex initial_num_rows_;
142 ColIndex initial_num_cols_;
143};
144
145// --------------------------------------------------------
146// ColumnDeletionHelper
147// --------------------------------------------------------
148
149// Some preprocessors need to save columns/rows of the matrix for the postsolve.
150// This class helps them do that.
151//
152// Note that we used to simply use a SparseMatrix, which is like a vector of
153// SparseColumn. However on large problem with 10+ millions columns, each empty
154// SparseColumn take 48 bytes, so if we run like 10 presolve step that save as
155// little as 1 columns, we already are at 4GB memory for nothing!
156class ColumnsSaver {
157 public:
158 // Saves a column. The first version CHECKs that it is not already done.
159 void SaveColumn(ColIndex col, const SparseColumn& column);
160 void SaveColumnIfNotAlreadyDone(ColIndex col, const SparseColumn& column);
161
162 // Returns the saved column. The first version CHECKs that it was saved.
163 const SparseColumn& SavedColumn(ColIndex col) const;
164 const SparseColumn& SavedOrEmptyColumn(ColIndex col) const;
165
166 private:
167 SparseColumn empty_column_;
168 absl::flat_hash_map<ColIndex, int> saved_columns_index_;
169
170 // TODO(user): We could optimize further since all these are read only, we
171 // could use a CompactSparseMatrix instead.
172 std::deque<SparseColumn> saved_columns_;
173};
174
175// Help preprocessors deal with column deletion.
177 public:
178 ColumnDeletionHelper() = default;
181
182 // Remember the given column as "deleted" so that it can later be restored
183 // by RestoreDeletedColumns(). Optionally, the caller may indicate the
184 // value and status of the corresponding variable so that it is automatically
185 // restored; if they don't then the restored value and status will be junk
186 // and must be set by the caller.
187 //
188 // The actual deletion is done by LinearProgram::DeleteColumns().
189 void MarkColumnForDeletion(ColIndex col);
192
193 // From a solution omitting the deleted column, expands it and inserts the
194 // deleted columns. If values and statuses for the corresponding variables
195 // were saved, they'll be restored.
197
198 // Returns whether or not the given column is marked for deletion.
199 bool IsColumnMarked(ColIndex col) const {
200 return col < is_column_deleted_.size() && is_column_deleted_[col];
201 }
202
203 // Returns a Boolean vector of the column to be deleted.
204 const DenseBooleanRow& GetMarkedColumns() const { return is_column_deleted_; }
206 // Returns true if no columns have been marked for deletion.
207 bool IsEmpty() const { return is_column_deleted_.empty(); }
208
209 // Restores the class to its initial state.
210 void Clear();
211
212 // Returns the value that will be restored by
213 // RestoreDeletedColumnInSolution(). Note that only the marked position value
214 // make sense.
215 const DenseRow& GetStoredValue() const { return stored_value_; }
216
217 private:
218 DenseBooleanRow is_column_deleted_;
219
220 // Note that this vector has the same size as is_column_deleted_ and that
221 // the value of the variable corresponding to a deleted column col is stored
222 // at position col. Values of columns not deleted are not used. We use this
223 // data structure so columns can be deleted in any order if needed.
224 DenseRow stored_value_;
225 VariableStatusRow stored_status_;
226};
227
228// --------------------------------------------------------
229// RowDeletionHelper
230// --------------------------------------------------------
231// Help preprocessors deal with row deletion.
232class RowDeletionHelper {
233 public:
234 RowDeletionHelper() = default;
235 RowDeletionHelper(const RowDeletionHelper&) = delete;
237
238 // Returns true if no rows have been marked for deletion.
239 bool IsEmpty() const { return is_row_deleted_.empty(); }
241 // Restores the class to its initial state.
242 void Clear();
244 // Adds a deleted row to the helper.
245 void MarkRowForDeletion(RowIndex row);
246
247 // If the given row was marked for deletion, unmark it.
248 void UnmarkRow(RowIndex row);
249
250 // Returns a Boolean vector of the row to be deleted.
251 const DenseBooleanColumn& GetMarkedRows() const;
252
253 // Returns whether or not the given row is marked for deletion.
254 bool IsRowMarked(RowIndex row) const {
255 return row < is_row_deleted_.size() && is_row_deleted_[row];
256 }
257
258 // From a solution without the deleted rows, expand it by restoring
259 // the deleted rows to a VariableStatus::BASIC status with 0.0 value.
260 // This latter value is important, many preprocessors rely on it.
263 private:
264 DenseBooleanColumn is_row_deleted_;
265};
266
267// --------------------------------------------------------
268// EmptyColumnPreprocessor
269// --------------------------------------------------------
270// Removes the empty columns from the problem.
271class EmptyColumnPreprocessor final : public Preprocessor {
272 public:
273 explicit EmptyColumnPreprocessor(const GlopParameters* parameters)
277 ~EmptyColumnPreprocessor() final = default;
278 bool Run(LinearProgram* lp) final;
279 void RecoverSolution(ProblemSolution* solution) const final;
280
281 private:
282 ColumnDeletionHelper column_deletion_helper_;
284
285// --------------------------------------------------------
286// ProportionalColumnPreprocessor
287// --------------------------------------------------------
288// Removes the proportional columns from the problem when possible. Two columns
289// are proportional if one is a non-zero scalar multiple of the other.
290//
291// Note that in the linear programming literature, two proportional columns are
292// usually called duplicates. The notion is the same once the problem has been
293// scaled. However, during presolve the columns can't be assumed to be scaled,
294// so it makes sense to use the more general notion of proportional columns.
295class ProportionalColumnPreprocessor final : public Preprocessor {
296 public:
297 explicit ProportionalColumnPreprocessor(const GlopParameters* parameters)
300 delete;
302 const ProportionalColumnPreprocessor&) = delete;
303 ~ProportionalColumnPreprocessor() final = default;
304 bool Run(LinearProgram* lp) final;
305 void RecoverSolution(ProblemSolution* solution) const final;
306 void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
308 private:
309 // Postsolve information about proportional columns with the same scaled cost
310 // that were merged during presolve.
312 // The proportionality factor of each column. If two columns are proportional
313 // with factor p1 and p2 then p1 times the first column is the same as p2
314 // times the second column.
315 DenseRow column_factors_;
316
317 // If merged_columns_[col] != kInvalidCol, then column col has been merged
318 // into the column merged_columns_[col].
319 ColMapping merged_columns_;
320
321 // The old and new variable bounds.
322 DenseRow lower_bounds_;
323 DenseRow upper_bounds_;
324 DenseRow new_lower_bounds_;
325 DenseRow new_upper_bounds_;
326
327 ColumnDeletionHelper column_deletion_helper_;
328};
329
330// --------------------------------------------------------
331// ProportionalRowPreprocessor
332// --------------------------------------------------------
333// Removes the proportional rows from the problem.
334// The linear programming literature also calls such rows duplicates, see the
335// same remark above for columns in ProportionalColumnPreprocessor.
336class ProportionalRowPreprocessor final : public Preprocessor {
337 public:
338 explicit ProportionalRowPreprocessor(const GlopParameters* parameters)
340 ProportionalRowPreprocessor(const ProportionalRowPreprocessor&) = delete;
341 ProportionalRowPreprocessor& operator=(const ProportionalRowPreprocessor&) =
342 delete;
343 ~ProportionalRowPreprocessor() final = default;
344 bool Run(LinearProgram* lp) final;
345 void RecoverSolution(ProblemSolution* solution) const final;
346
347 private:
348 // Informations about proportional rows, only filled for such rows.
349 DenseColumn row_factors_;
350 RowMapping upper_bound_sources_;
351 RowMapping lower_bound_sources_;
353 bool lp_is_maximization_problem_;
354 RowDeletionHelper row_deletion_helper_;
356
357// --------------------------------------------------------
358// SingletonPreprocessor
359// --------------------------------------------------------
360// Removes as many singleton rows and singleton columns as possible from the
361// problem. Note that not all types of singleton columns can be removed. See the
362// comments below on the SingletonPreprocessor functions for more details.
363//
364// TODO(user): Generalize the design used in this preprocessor to a general
365// "propagation" framework in order to apply as many reductions as possible in
366// an efficient manner.
367
368// Holds a triplet (row, col, coefficient).
369struct MatrixEntry {
370 MatrixEntry(RowIndex _row, ColIndex _col, Fractional _coeff)
371 : row(_row), col(_col), coeff(_coeff) {}
372 RowIndex row;
373 ColIndex col;
374 Fractional coeff;
375};
376
377// Stores the information needed to undo a singleton row/column deletion.
378class SingletonUndo {
379 public:
380 // The type of a given operation.
381 typedef enum {
382 ZERO_COST_SINGLETON_COLUMN,
383 SINGLETON_ROW,
384 SINGLETON_COLUMN_IN_EQUALITY,
385 MAKE_CONSTRAINT_AN_EQUALITY,
386 } OperationType;
387
388 // Stores the information, which together with the field deleted_columns_ and
389 // deleted_rows_ of SingletonPreprocessor, are needed to undo an operation
390 // with the given type. Note that all the arguments must refer to the linear
391 // program BEFORE the operation is applied.
392 SingletonUndo(OperationType type, const LinearProgram& lp, MatrixEntry e,
395 // Undo the operation saved in this class, taking into account the saved
396 // column and row (at the row/col given by Entry()) passed by the calling
397 // instance of SingletonPreprocessor. Note that the operations must be undone
398 // in the reverse order of the one in which they were applied.
399 void Undo(const GlopParameters& parameters, const SparseColumn& saved_column,
400 const SparseColumn& saved_row, ProblemSolution* solution) const;
402 const MatrixEntry& Entry() const { return e_; }
403
404 private:
405 // Actual undo functions for each OperationType.
406 // Undo() just calls the correct one.
407 void SingletonRowUndo(const SparseColumn& saved_column,
409 void ZeroCostSingletonColumnUndo(const GlopParameters& parameters,
410 const SparseColumn& saved_row,
412 void SingletonColumnInEqualityUndo(const GlopParameters& parameters,
413 const SparseColumn& saved_row,
415 void MakeConstraintAnEqualityUndo(ProblemSolution* solution) const;
416
417 // All the information needed during undo.
419 bool is_maximization_;
420 MatrixEntry e_;
421 Fractional cost_;
422
423 // TODO(user): regroup the pair (lower bound, upper bound) in a bound class?
424 Fractional variable_lower_bound_;
425 Fractional variable_upper_bound_;
426 Fractional constraint_lower_bound_;
427 Fractional constraint_upper_bound_;
428
429 // This in only used with MAKE_CONSTRAINT_AN_EQUALITY undo.
430 // TODO(user): Clean that up using many Undo classes and virtual functions.
431 ConstraintStatus constraint_status_;
432};
433
434// Deletes as many singleton rows or singleton columns as possible. Note that
435// each time we delete a row or a column, new singletons may be created.
436class SingletonPreprocessor final : public Preprocessor {
437 public:
438 explicit SingletonPreprocessor(const GlopParameters* parameters)
440 SingletonPreprocessor(const SingletonPreprocessor&) = delete;
441 SingletonPreprocessor& operator=(const SingletonPreprocessor&) = delete;
442 ~SingletonPreprocessor() final = default;
443 bool Run(LinearProgram* lp) final;
444 void RecoverSolution(ProblemSolution* solution) const final;
445
446 private:
447 // Returns the MatrixEntry of the given singleton row or column, taking into
448 // account the rows and columns that were already deleted.
449 MatrixEntry GetSingletonColumnMatrixEntry(ColIndex col,
450 const SparseMatrix& matrix);
451 MatrixEntry GetSingletonRowMatrixEntry(RowIndex row,
452 const SparseMatrix& matrix_transpose);
453
454 // A singleton row can always be removed by changing the corresponding
455 // variable bounds to take into account the bounds on this singleton row.
456 void DeleteSingletonRow(MatrixEntry e, LinearProgram* lp);
458 // Internal operation when removing a zero-cost singleton column corresponding
459 // to the given entry. This modifies the constraint bounds to take into
460 // account the bounds of the corresponding variable.
461 void UpdateConstraintBoundsWithVariableBounds(MatrixEntry e,
462 LinearProgram* lp);
463
464 // Checks if all other variables in the constraint are integer and the
465 // coefficients are divisible by the coefficient of the singleton variable.
466 bool IntegerSingletonColumnIsRemovable(const MatrixEntry& matrix_entry,
467 const LinearProgram& lp) const;
468
469 // A singleton column with a cost of zero can always be removed by changing
470 // the corresponding constraint bounds to take into account the bound of this
471 // singleton column.
472 void DeleteZeroCostSingletonColumn(const SparseMatrix& matrix_transpose,
473 MatrixEntry e, LinearProgram* lp);
474
475 // Returns true if the constraint associated to the given singleton column was
476 // an equality or could be made one:
477 // If a singleton variable is free in a direction that improves the cost, then
478 // we can always move it as much as possible in this direction. Only the
479 // constraint will stop us, making it an equality. If the constraint doesn't
480 // stop us, then the program is unbounded (provided that there is a feasible
481 // solution).
482 //
483 // Note that this operation does not need any "undo" during the post-solve. At
484 // optimality, the dual value on the constraint row will be of the correct
485 // sign, and relaxing the constraint bound will not impact the dual
486 // feasibility of the solution.
487 //
488 // TODO(user): this operation can be generalized to columns with just one
489 // blocking constraint. Investigate how to use this. The 'reverse' can
490 // probably also be done, relaxing a constraint that is blocking a
491 // unconstrained variable.
492 bool MakeConstraintAnEqualityIfPossible(const SparseMatrix& matrix_transpose,
493 MatrixEntry e, LinearProgram* lp);
494
495 // If a singleton column appears in an equality, we can remove its cost by
496 // changing the other variables cost using the constraint. We can then delete
497 // the column like in DeleteZeroCostSingletonColumn().
498 void DeleteSingletonColumnInEquality(const SparseMatrix& matrix_transpose,
499 MatrixEntry e, LinearProgram* lp);
500
501 ColumnDeletionHelper column_deletion_helper_;
502 RowDeletionHelper row_deletion_helper_;
503 std::vector<SingletonUndo> undo_stack_;
504
505 // This is used as a "cache" by MakeConstraintAnEqualityIfPossible() to avoid
506 // scanning more than once each row. See the code to see how this is used.
507 util_intops::StrongVector<RowIndex, bool> row_sum_is_cached_;
509 row_lb_sum_;
511 row_ub_sum_;
512
513 // TODO(user): It is annoying that we need to store a part of the matrix that
514 // is not deleted here. This extra memory usage might show the limit of our
515 // presolve architecture that does not require a new matrix factorization on
516 // the original problem to reconstruct the solution.
517 ColumnsSaver columns_saver_;
518 ColumnsSaver rows_saver_;
519};
520
521// --------------------------------------------------------
522// FixedVariablePreprocessor
523// --------------------------------------------------------
524// Removes the fixed variables from the problem.
525class FixedVariablePreprocessor final : public Preprocessor {
526 public:
527 explicit FixedVariablePreprocessor(const GlopParameters* parameters)
531 delete;
532 ~FixedVariablePreprocessor() final = default;
533 bool Run(LinearProgram* lp) final;
534 void RecoverSolution(ProblemSolution* solution) const final;
535
536 private:
537 ColumnDeletionHelper column_deletion_helper_;
538};
539
540// --------------------------------------------------------
541// ForcingAndImpliedFreeConstraintPreprocessor
542// --------------------------------------------------------
543// This preprocessor computes for each constraint row the bounds that are
544// implied by the variable bounds and applies one of the following reductions:
546// * If the intersection of the implied bounds and the current constraint bounds
547// is empty (modulo some tolerance), the problem is INFEASIBLE.
549// * If the intersection of the implied bounds and the current constraint bounds
550// is a singleton (modulo some tolerance), then the constraint is said to be
551// forcing and all the variables that appear in it can be fixed to one of their
552// bounds. All these columns and the constraint row is removed.
553//
554// * If the implied bounds are included inside the current constraint bounds
555// (modulo some tolerance) then the constraint is said to be redundant or
556// implied free. Its bounds are relaxed and the constraint will be removed
557// later by the FreeConstraintPreprocessor.
558//
559// * Otherwise, wo do nothing.
561 public:
563 const GlopParameters* parameters)
570 bool Run(LinearProgram* lp) final;
571 void RecoverSolution(ProblemSolution* solution) const final;
572
573 private:
574 bool lp_is_maximization_problem_;
575 DenseRow costs_;
576 DenseBooleanColumn is_forcing_up_;
577 ColumnDeletionHelper column_deletion_helper_;
578 RowDeletionHelper row_deletion_helper_;
579 ColumnsSaver columns_saver_;
581
582// --------------------------------------------------------
583// ImpliedFreePreprocessor
584// --------------------------------------------------------
585// It is possible to compute "implied" bounds on a variable from the bounds of
586// all the other variables and the constraints in which this variable take
587// place. If such "implied" bounds are inside the variable bounds, then the
588// variable bounds can be relaxed and the variable is said to be "implied free".
590// This preprocessor detects the implied free variables and make as many as
591// possible free with a priority towards low-degree columns. This transformation
592// will make the simplex algorithm more efficient later, but will also make it
593// possible to reduce the problem by applying subsequent transformations:
594//
595// * The SingletonPreprocessor already deals with implied free singleton
596// variables and removes the columns and the rows in which they appear.
597//
598// * Any multiple of the column of a free variable can be added to any other
599// column without changing the linear program solution. This is the dual
600// counterpart of the fact that any multiple of an equality row can be added to
601// any row.
602//
603// TODO(user): Only process doubleton columns so we have more chance in the
604// later passes to create more doubleton columns? Such columns lead to a smaller
605// problem thanks to the DoubletonFreeColumnPreprocessor.
606class ImpliedFreePreprocessor final : public Preprocessor {
607 public:
608 explicit ImpliedFreePreprocessor(const GlopParameters* parameters)
611 ImpliedFreePreprocessor& operator=(const ImpliedFreePreprocessor&) = delete;
612 ~ImpliedFreePreprocessor() final = default;
613 bool Run(LinearProgram* lp) final;
614 void RecoverSolution(ProblemSolution* solution) const final;
615
616 private:
617 // This preprocessor adds fixed offsets to some variables. We remember those
618 // here to un-offset them in RecoverSolution().
619 DenseRow variable_offsets_;
620
621 // This preprocessor causes some variables who would normally be
622 // AT_{LOWER,UPPER}_BOUND to be VariableStatus::FREE. We store the restore
623 // value of these variables; which will only be used (eg. restored) if the
624 // variable actually turns out to be VariableStatus::FREE.
625 VariableStatusRow postsolve_status_of_free_variables_;
626};
627
628// --------------------------------------------------------
629// DoubletonFreeColumnPreprocessor
630// --------------------------------------------------------
631// This preprocessor removes one of the two rows in which a doubleton column of
632// a free variable appears. Since we can add any multiple of such a column to
633// any other column, the way this works is that we can always remove all the
634// entries on one row.
635//
636// Actually, we can remove all the entries except the one of the free column.
637// But we will be left with a singleton row that we can delete in the same way
638// as what is done in SingletonPreprocessor. That is by reporting the constraint
639// bounds into the one of the originally free variable. After this operation,
640// the doubleton free column will become a singleton and may or may not be
641// removed later by the SingletonPreprocessor.
642//
643// Note that this preprocessor can be seen as the dual of the
644// DoubletonEqualityRowPreprocessor since when taking the dual, an equality row
645// becomes a free variable and vice versa.
646//
647// Note(user): As far as I know, this doubleton free column procedure is more
648// general than what can be found in the research papers or in any of the linear
649// solver open source codes as of July 2013. All of them only process such
650// columns if one of the two rows is also an equality which is not actually
651// required. Most probably, commercial solvers do use it though.
653 public:
654 explicit DoubletonFreeColumnPreprocessor(const GlopParameters* parameters)
657 delete;
659 const DoubletonFreeColumnPreprocessor&) = delete;
660 ~DoubletonFreeColumnPreprocessor() final = default;
661 bool Run(LinearProgram* lp) final;
662 void RecoverSolution(ProblemSolution* solution) const final;
663
664 private:
665 enum RowChoice {
666 DELETED = 0,
667 MODIFIED = 1,
668 // This is just a constant for the number of rows in a doubleton column.
669 // That is 2, one will be DELETED, the other MODIFIED.
670 NUM_ROWS = 2,
671 };
672 struct RestoreInfo {
673 // The index of the original free doubleton column and its objective.
674 ColIndex col;
675 Fractional objective_coefficient;
677 // The row indices of the two involved rows and their coefficients on
678 // column col.
679 RowIndex row[NUM_ROWS];
680 Fractional coeff[NUM_ROWS];
681
682 // The deleted row as a column.
683 SparseColumn deleted_row_as_column;
684 };
685
686 std::vector<RestoreInfo> restore_stack_;
687 RowDeletionHelper row_deletion_helper_;
688};
689
690// --------------------------------------------------------
691// UnconstrainedVariablePreprocessor
692// --------------------------------------------------------
693// If for a given variable, none of the constraints block it in one direction
694// and this direction improves the objective, then this variable can be fixed to
695// its bound in this direction. If this bound is infinite and the variable cost
696// is non-zero, then the problem is unbounded.
697//
698// More generally, by using the constraints and the variables that are unbounded
699// on one side, one can derive bounds on the dual values. These can be
700// translated into bounds on the reduced costs or the columns, which may force
701// variables to their bounds. This is called forcing and dominated columns in
702// the Andersen & Andersen paper.
703class UnconstrainedVariablePreprocessor final : public Preprocessor {
704 public:
705 explicit UnconstrainedVariablePreprocessor(const GlopParameters* parameters)
707 UnconstrainedVariablePreprocessor(const UnconstrainedVariablePreprocessor&) =
708 delete;
709 UnconstrainedVariablePreprocessor& operator=(
710 const UnconstrainedVariablePreprocessor&) = delete;
711 ~UnconstrainedVariablePreprocessor() final = default;
712 bool Run(LinearProgram* lp) final;
713 void RecoverSolution(ProblemSolution* solution) const final;
714
715 // Removes the given variable and all the rows in which it appears: If a
716 // variable is unconstrained with a zero cost, then all the constraints in
717 // which it appears can be made free! More precisely, during postsolve, if
718 // such a variable is unconstrained towards +kInfinity, for any activity value
719 // of the involved constraints, an M exists such that for each value of the
720 // variable >= M the problem will be feasible.
721 //
722 // The algorithm during postsolve is to find a feasible value for all such
723 // variables while trying to keep their magnitudes small (for better numerical
724 // behavior). target_bound should take only two possible values: +/-kInfinity.
725 void RemoveZeroCostUnconstrainedVariable(ColIndex col,
726 Fractional target_bound,
727 LinearProgram* lp);
728
729 private:
730 // Lower/upper bounds on the feasible dual value. We use constraints and
731 // variables unbounded in one direction to derive these bounds. We use these
732 // bounds to compute bounds on the reduced costs of the problem variables.
733 // Note that any finite bounds on a reduced cost means that the variable
734 // (ignoring its domain) can move freely in one direction.
735 DenseColumn dual_lb_;
736 DenseColumn dual_ub_;
738 // Indicates if a given column may have participated in the current lb/ub
739 // on the reduced cost of the same column.
740 DenseBooleanRow may_have_participated_ub_;
741 DenseBooleanRow may_have_participated_lb_;
742
743 ColumnDeletionHelper column_deletion_helper_;
744 RowDeletionHelper row_deletion_helper_;
745 ColumnsSaver rows_saver_;
746 DenseColumn rhs_;
747 DenseColumn activity_sign_correction_;
748 DenseBooleanRow is_unbounded_;
749};
750
751// --------------------------------------------------------
752// FreeConstraintPreprocessor
753// --------------------------------------------------------
754// Removes the constraints with no bounds from the problem.
755class FreeConstraintPreprocessor final : public Preprocessor {
756 public:
757 explicit FreeConstraintPreprocessor(const GlopParameters* parameters)
761 delete;
762 ~FreeConstraintPreprocessor() final = default;
763 bool Run(LinearProgram* lp) final;
764 void RecoverSolution(ProblemSolution* solution) const final;
765
766 private:
767 RowDeletionHelper row_deletion_helper_;
768};
769
770// --------------------------------------------------------
771// EmptyConstraintPreprocessor
772// --------------------------------------------------------
773// Removes the constraints with no coefficients from the problem.
774class EmptyConstraintPreprocessor final : public Preprocessor {
775 public:
776 explicit EmptyConstraintPreprocessor(const GlopParameters* parameters)
778 EmptyConstraintPreprocessor(const EmptyConstraintPreprocessor&) = delete;
779 EmptyConstraintPreprocessor& operator=(const EmptyConstraintPreprocessor&) =
780 delete;
781 ~EmptyConstraintPreprocessor() final = default;
782 bool Run(LinearProgram* lp) final;
783 void RecoverSolution(ProblemSolution* solution) const final;
784
785 private:
786 RowDeletionHelper row_deletion_helper_;
789// --------------------------------------------------------
790// SingletonColumnSignPreprocessor
791// --------------------------------------------------------
792// Make sure that the only coefficient of all singleton columns (i.e. column
793// with only one entry) is positive. This is because this way the column will
794// be transformed in an identity column by the scaling. This will lead to more
795// efficient solve when this column is involved.
797 public:
798 explicit SingletonColumnSignPreprocessor(const GlopParameters* parameters)
801 delete;
803 const SingletonColumnSignPreprocessor&) = delete;
805 bool Run(LinearProgram* lp) final;
806 void RecoverSolution(ProblemSolution* solution) const final;
807
808 private:
809 std::vector<ColIndex> changed_columns_;
810};
812// --------------------------------------------------------
813// DoubletonEqualityRowPreprocessor
814// --------------------------------------------------------
815// Reduce equality constraints involving two variables (i.e. aX + bY = c),
816// by substitution (and thus removal) of one of the variables by the other
817// in all the constraints that it is involved in.
819 public:
820 explicit DoubletonEqualityRowPreprocessor(const GlopParameters* parameters)
823 delete;
825 const DoubletonEqualityRowPreprocessor&) = delete;
826 ~DoubletonEqualityRowPreprocessor() final = default;
827 bool Run(LinearProgram* lp) final;
828 void RecoverSolution(ProblemSolution* solution) const final;
829
830 private:
831 enum ColChoice {
832 DELETED = 0,
833 MODIFIED = 1,
834 // For `for()` loops iterating over the ColChoice values and/or arrays.
835 NUM_DOUBLETON_COLS = 2,
836 };
837 static ColChoice OtherColChoice(ColChoice x) {
838 return x == DELETED ? MODIFIED : DELETED;
839 }
840
841 ColumnDeletionHelper column_deletion_helper_;
842 RowDeletionHelper row_deletion_helper_;
843
844 struct RestoreInfo {
845 // The row index of the doubleton equality constraint, and its constant.
846 RowIndex row;
847 Fractional rhs; // The constant c in the equality aX + bY = c.
848
849 // The indices and the data of the two columns that we touched, exactly
850 // as they were beforehand.
851 ColIndex col[NUM_DOUBLETON_COLS];
852 Fractional coeff[NUM_DOUBLETON_COLS];
853 Fractional lb[NUM_DOUBLETON_COLS];
854 Fractional ub[NUM_DOUBLETON_COLS];
855 Fractional objective_coefficient[NUM_DOUBLETON_COLS];
857 // If the modified variable has status AT_[LOWER,UPPER]_BOUND, then we'll
858 // set one of the two original variables to one of its bounds, and set the
859 // other to VariableStatus::BASIC. We store this information (which variable
860 // will be set to one of its bounds, and which bound) for each possible
861 // outcome.
862 struct ColChoiceAndStatus {
863 ColChoice col_choice;
866 ColChoiceAndStatus() : col_choice(), status(), value(0.0) {}
867 ColChoiceAndStatus(ColChoice c, VariableStatus s, Fractional v)
868 : col_choice(c), status(s), value(v) {}
869 };
870 ColChoiceAndStatus bound_backtracking_at_lower_bound;
871 ColChoiceAndStatus bound_backtracking_at_upper_bound;
872 };
873 void SwapDeletedAndModifiedVariableRestoreInfo(RestoreInfo* r);
874
875 std::vector<RestoreInfo> restore_stack_;
876 DenseColumn saved_row_lower_bounds_;
877 DenseColumn saved_row_upper_bounds_;
878
879 ColumnsSaver columns_saver_;
880 DenseRow saved_objective_;
881};
882
883// Because of numerical imprecision, a preprocessor like
884// DoubletonEqualityRowPreprocessor can transform a constraint/variable domain
885// like [1, 1+1e-7] to a fixed domain (for ex by multiplying the above domain by
886// 1e9). This causes an issue because at postsolve, a FIXED_VALUE status now
887// needs to be transformed to a AT_LOWER_BOUND/AT_UPPER_BOUND status. This is
888// what this function is doing for the constraint statuses only.
889//
890// TODO(user): A better solution would simply be to get rid of the FIXED status
891// altogether, it is better to simply use AT_LOWER_BOUND/AT_UPPER_BOUND
892// depending on the constraining bound in the optimal solution. Note that we can
893// always at the end transform any variable/constraint with a fixed domain to
894// FIXED_VALUE if needed to keep the same external API.
895void FixConstraintWithFixedStatuses(const DenseColumn& row_lower_bounds,
896 const DenseColumn& row_upper_bounds,
899// --------------------------------------------------------
900// DualizerPreprocessor
901// --------------------------------------------------------
902// DualizerPreprocessor may change the given program to its dual depending
903// on the value of the parameter solve_dual_problem.
904//
905// IMPORTANT: FreeConstraintPreprocessor() must be called first since this
906// preprocessor does not deal correctly with free constraints.
907class DualizerPreprocessor final : public Preprocessor {
908 public:
909 explicit DualizerPreprocessor(const GlopParameters* parameters)
912 DualizerPreprocessor& operator=(const DualizerPreprocessor&) = delete;
913 ~DualizerPreprocessor() final = default;
914 bool Run(LinearProgram* lp) final;
915 void RecoverSolution(ProblemSolution* solution) const final;
916 void UseInMipContext() final {
917 LOG(FATAL) << "In the presence of integer variables, "
918 << "there is no notion of a dual problem.";
919 }
920
921 // Convert the given problem status to the one of its dual.
922 ProblemStatus ChangeStatusToDualStatus(ProblemStatus status) const;
923
924 private:
925 DenseRow variable_lower_bounds_;
926 DenseRow variable_upper_bounds_;
927
928 RowIndex primal_num_rows_;
929 ColIndex primal_num_cols_;
930 bool primal_is_maximization_problem_;
931 RowToColMapping duplicated_rows_;
932
933 // For postsolving the variable/constraint statuses.
934 VariableStatusRow dual_status_correspondence_;
935 ColMapping slack_or_surplus_mapping_;
936};
937
938// --------------------------------------------------------
939// ShiftVariableBoundsPreprocessor
940// --------------------------------------------------------
941// For each variable, inspects its bounds and "shift" them if necessary, so that
942// its domain contains zero. A variable that was shifted will always have at
943// least one of its bounds to zero. Doing it all at once allows to have a better
944// precision when modifying the constraint bounds by using an accurate summation
945// algorithm.
946//
947// Example:
948// - A variable with bound [1e10, infinity] will be shifted to [0, infinity].
949// - A variable with domain [-1e10, 1e10] will not be shifted. Note that
950// compared to the first case, doing so here may introduce unnecessary
951// numerical errors if the variable value in the final solution is close to
952// zero.
953//
954// The expected impact of this is:
955// - Better behavior of the scaling.
956// - Better precision and numerical accuracy of the simplex method.
957// - Slightly improved speed (because adding a column with a variable value of
958// zero takes no work later).
959//
960// TODO(user): Having for each variable one of their bounds at zero is a
961// requirement for the DualizerPreprocessor and for the implied free column in
962// the ImpliedFreePreprocessor. However, shifting a variable with a domain like
963// [-1e10, 1e10] may introduce numerical issues. Relax the definition of
964// a free variable so that only having a domain containing 0.0 is enough?
966 public:
967 explicit ShiftVariableBoundsPreprocessor(const GlopParameters* parameters)
970 delete;
972 const ShiftVariableBoundsPreprocessor&) = delete;
973 ~ShiftVariableBoundsPreprocessor() final = default;
974 bool Run(LinearProgram* lp) final;
975 void RecoverSolution(ProblemSolution* solution) const final;
976
977 const DenseRow& offsets() const { return offsets_; }
978
979 private:
980 // Contains for each variable by how much its bounds where shifted during
981 // presolve. Note that the shift was negative (new bound = initial bound -
982 // offset).
983 DenseRow offsets_;
984 // Contains the initial problem bounds. They are needed to get the perfect
985 // numerical accuracy for variables at their bound after postsolve.
986 DenseRow variable_initial_lbs_;
987 DenseRow variable_initial_ubs_;
988};
989
990// --------------------------------------------------------
991// ScalingPreprocessor
992// --------------------------------------------------------
993// Scales the SparseMatrix of the linear program using a SparseMatrixScaler.
994// This is only applied if the parameter use_scaling is true.
995class ScalingPreprocessor final : public Preprocessor {
996 public:
997 explicit ScalingPreprocessor(const GlopParameters* parameters)
999 ScalingPreprocessor(const ScalingPreprocessor&) = delete;
1000 ScalingPreprocessor& operator=(const ScalingPreprocessor&) = delete;
1001 ~ScalingPreprocessor() final = default;
1002 bool Run(LinearProgram* lp) final;
1003 void RecoverSolution(ProblemSolution* solution) const final;
1004 void UseInMipContext() final { LOG(FATAL) << "Not implemented."; }
1006 private:
1007 DenseRow variable_lower_bounds_;
1008 DenseRow variable_upper_bounds_;
1009 Fractional cost_scaling_factor_;
1010 Fractional bound_scaling_factor_;
1012};
1013
1014// --------------------------------------------------------
1015// ToMinimizationPreprocessor
1016// --------------------------------------------------------
1017// Changes the problem from maximization to minimization (if applicable).
1018class ToMinimizationPreprocessor final : public Preprocessor {
1019 public:
1020 explicit ToMinimizationPreprocessor(const GlopParameters* parameters)
1024 delete;
1025 ~ToMinimizationPreprocessor() final = default;
1026 bool Run(LinearProgram* lp) final;
1027 void RecoverSolution(ProblemSolution* solution) const final;
1028};
1029
1030// --------------------------------------------------------
1031// AddSlackVariablesPreprocessor
1032// --------------------------------------------------------
1033// Transforms the linear program to the equation form
1034// min c.x, s.t. A.x = 0. This is done by:
1035// 1. Introducing slack variables for all constraints; all these variables are
1036// introduced with coefficient 1.0, and their bounds are set to be negative
1037// bounds of the corresponding constraint.
1038// 2. Changing the bounds of all constraints to (0, 0) to make them an equality.
1040// As a consequence, the matrix of the linear program always has full row rank
1041// after this preprocessor. Note that the slack variables are always added last,
1042// so that the rightmost square sub-matrix is always the identity matrix.
1043//
1044// TODO(user): Do not require this step to talk to the revised simplex. On large
1045// LPs like supportcase11.mps, this step alone can add 1.5 GB to the solver peak
1046// memory for no good reason. The internal matrix representation used in glop is
1047// a lot more efficient, and there is no point keeping the slacks in
1048// LinearProgram. It is also bad for incrementaly modifying the LP.
1049class AddSlackVariablesPreprocessor final : public Preprocessor {
1050 public:
1051 explicit AddSlackVariablesPreprocessor(const GlopParameters* parameters)
1055 const AddSlackVariablesPreprocessor&) = delete;
1056 ~AddSlackVariablesPreprocessor() final = default;
1057 bool Run(LinearProgram* lp) final;
1058 void RecoverSolution(ProblemSolution* solution) const final;
1059
1060 private:
1061 ColIndex first_slack_col_;
1063
1064} // namespace glop
1065} // namespace operations_research
1066
1067#endif // OR_TOOLS_GLOP_PREPROCESSOR_H_
Help preprocessors deal with column deletion.
void Clear()
Restores the class to its initial state.
ColumnDeletionHelper & operator=(const ColumnDeletionHelper &)=delete
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)
Removes the empty columns from the problem.
EmptyColumnPreprocessor & operator=(const EmptyColumnPreprocessor &)=delete
EmptyColumnPreprocessor(const GlopParameters *parameters)
void RecoverSolution(ProblemSolution *solution) const final
Removes the constraints with no coefficients from the problem.
Removes the fixed variables from the problem.
Removes the constraints with no bounds from the problem.
MainLpPreprocessor & operator=(const MainLpPreprocessor &)=delete
void DestructiveRecoverSolution(ProblemSolution *solution)
void RecoverSolution(ProblemSolution *solution) const override
MainLpPreprocessor(const GlopParameters *parameters)
bool IsSmallerWithinFeasibilityTolerance(Fractional a, Fractional b) const
virtual void RecoverSolution(ProblemSolution *solution) const =0
Preprocessor(const GlopParameters *parameters)
std::unique_ptr< TimeLimit > infinite_time_limit_
virtual bool Run(LinearProgram *lp)=0
void SetTimeLimit(TimeLimit *time_limit)
bool IsSmallerWithinPreprocessorZeroTolerance(Fractional a, Fractional b) const
Preprocessor & operator=(const Preprocessor &)=delete
Help preprocessors deal with row deletion.
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.
RowDeletionHelper & operator=(const RowDeletionHelper &)=delete
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.
Stores the information needed to undo a singleton row/column deletion.
OperationType
The type of a given operation.
Changes the problem from maximization to minimization (if applicable).
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
SatParameters parameters
const std::string name
A name for logging purposes.
int64_t value
absl::Status status
Definition g_gurobi.cc:44
time_limit
Definition solve.cc:22
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
double solution
ProblemStatus
Different statuses for a given problem.
Definition lp_types.h:107
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
int column
glop::MainLpPreprocessor preprocessor
const Variable x
Definition qp_tests.cc:127
Fractional target_bound
Contains the solution of a LinearProgram as returned by a preprocessor.
Definition lp_data.h:671