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