OR-Tools  9.6
reduced_costs.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 #ifndef OR_TOOLS_GLOP_REDUCED_COSTS_H_
15 #define OR_TOOLS_GLOP_REDUCED_COSTS_H_
16 
17 #include <string>
18 #include <vector>
19 
20 #include "absl/random/bit_gen_ref.h"
22 #include "ortools/glop/parameters.pb.h"
23 #include "ortools/glop/pricing.h"
25 #include "ortools/glop/status.h"
31 #include "ortools/util/stats.h"
32 
33 namespace operations_research {
34 namespace glop {
35 
36 // Maintains the reduced costs of the non-basic variables and some related
37 // quantities.
38 //
39 // Terminology:
40 // - To each non-basic column 'a' of A, we can associate an "edge" in the
41 // kernel of A equal to 1.0 on the index of 'a' and '-B^{-1}.a' on the basic
42 // variables.
43 // - 'B^{-1}.a' is called the "right inverse" of 'a'.
44 // - The reduced cost of a column is equal to the scalar product of this
45 // column's edge with the cost vector (objective_), and corresponds to the
46 // variation in the objective function when we add this edge to the current
47 // solution.
48 // - The dual values are the "left inverse" of the basic objective by B.
49 // That is 'basic_objective_.B^{-1}'
50 // - The reduced cost of a column is also equal to the scalar product of this
51 // column with the vector of the dual values.
52 class ReducedCosts {
53  public:
54  // Takes references to the linear program data we need.
55  ReducedCosts(const CompactSparseMatrix& matrix_, const DenseRow& objective,
56  const RowToColMapping& basis,
57  const VariablesInfo& variables_info,
58  const BasisFactorization& basis_factorization,
59  absl::BitGenRef random);
60 
61  // If this is true, then the caller must re-factorize the basis before the
62  // next call to GetReducedCosts().
63  bool NeedsBasisRefactorization() const;
64 
65  // Checks the precision of the entering variable choice now that the direction
66  // is computed. Returns its precise version. This will also trigger a
67  // reduced cost recomputation if it was deemed too imprecise.
68  Fractional TestEnteringReducedCostPrecision(ColIndex entering_col,
69  const ScatteredColumn& direction);
70 
71  // Computes the current dual residual and infeasibility. Note that these
72  // functions are not really fast (many scalar products will be computed) and
73  // shouldn't be called at each iteration.
74  //
75  // These function will compute the reduced costs if needed.
76  // ComputeMaximumDualResidual() also needs ComputeBasicObjectiveLeftInverse()
77  // and do not depends on reduced costs.
81 
82  // Same as ComputeMaximumDualInfeasibility() but ignore boxed variables.
83  // Because we can always switch bounds of boxed variables, if this is under
84  // the dual tolerance, then we can easily have a dual feasible solution and do
85  // not need to run a dual phase I algorithm.
87 
88  // Updates any internal data BEFORE the given simplex pivot is applied to B.
89  // Note that no updates are needed in case of a bound flip.
90  // The arguments are in order:
91  // - The index of the entering non-basic column of A.
92  // - The index in B of the leaving basic variable.
93  // - The 'direction', i.e. the right inverse of the entering column.
94  void UpdateBeforeBasisPivot(ColIndex entering_col, RowIndex leaving_row,
95  const ScatteredColumn& direction,
96  UpdateRow* update_row);
97 
98  // Sets the cost of the given non-basic variable to zero and updates its
99  // reduced cost. Note that changing the cost of a non-basic variable only
100  // impacts its reduced cost and not the one of any other variables.
101  // The current_cost pointer must be equal to the address of objective[col]
102  // where objective is the DenseRow passed at construction.
103  void SetNonBasicVariableCostToZero(ColIndex col, Fractional* current_cost);
104 
105  // Sets the pricing parameters. This does not change the pricing rule.
106  void SetParameters(const GlopParameters& parameters);
107 
108  // Returns true if the current reduced costs are computed with maximum
109  // precision.
110  bool AreReducedCostsPrecise() { return are_reduced_costs_precise_; }
111 
112  // Returns true if the current reduced costs where just recomputed or will be
113  // on the next call to GetReducedCosts().
115  return recompute_reduced_costs_ || are_reduced_costs_recomputed_;
116  }
117 
118  // Makes sure the next time the reduced cost are needed, they will be
119  // recomputed with maximum precision (i.e. from scratch with a basis
120  // refactorization first).
122 
123  // Randomly perturb the costs. Both Koberstein and Huangfu recommend doing
124  // that before the dual simplex starts in their Phd thesis.
125  //
126  // The perturbation follows what is explained in Huangfu Q (2013) "High
127  // performance simplex solver", Ph.D, dissertation, University of Edinburgh,
128  // section 3.2.3, page 58.
129  void PerturbCosts();
130 
131  // Shifts the cost of the given non-basic column such that its current reduced
132  // cost becomes 0.0. Actually, this shifts the cost a bit more according to
133  // the positive_direction parameter.
134  //
135  // This is explained in Koberstein's thesis (section 6.2.2.3) and helps on
136  // degenerate problems. As of july 2013, this allowed to pass dano3mip and
137  // dbic1 without cycling forever. Note that contrary to what is explained in
138  // the thesis, we do not shift any other variable costs. If any becomes
139  // infeasible, it will be selected and shifted in subsequent iterations.
140  void ShiftCostIfNeeded(bool increasing_rc_is_needed, ColIndex col);
141 
142  // Returns true if ShiftCostIfNeeded() was applied since the last
143  // ClearAndRemoveCostShifts().
144  bool HasCostShift() const { return has_cost_shift_; }
145 
146  // Returns true if this step direction make the given column even more
147  // infeasible. This is just used for reporting stats.
148  bool StepIsDualDegenerate(bool increasing_rc_is_needed, ColIndex col);
149 
150  // Removes any cost shift and cost perturbation. This also lazily forces a
151  // recomputation of all the derived quantities. This effectively resets the
152  // class to its initial state.
154 
155  // Invalidates all internal structure that depends on the objective function.
156  void ResetForNewObjective();
157 
158  // Invalidates the data that depends on the order of the column in basis_.
160 
161  // Returns the current reduced costs. If AreReducedCostsPrecise() is true,
162  // then for basic columns, this gives the error between 'c_B' and 'y.B' and
163  // for non-basic columns, this is the classic reduced cost. If it is false,
164  // then this is defined only for the columns in
165  // variables_info_.GetIsRelevantBitRow().
166  const DenseRow& GetReducedCosts();
167 
168  // Same as GetReducedCosts() but trigger a recomputation if not already done
169  // to have access to the reduced costs on all positions, not just the relevant
170  // one.
171  const DenseRow& GetFullReducedCosts();
172 
173  // Returns the dual values associated to the current basis.
174  const DenseColumn& GetDualValues();
175 
176  // Stats related functions.
177  std::string StatString() const { return stats_.StatString(); }
178 
179  // Returns the current dual feasibility tolerance.
181  return dual_feasibility_tolerance_;
182  }
183 
184  // Does basic checking of an entering candidate.
185  bool IsValidPrimalEnteringCandidate(ColIndex col) const;
186 
187  // Visible for testing.
188  const DenseRow& GetCostPerturbations() const { return cost_perturbations_; }
189 
190  // The deterministic time used by this class.
191  double DeterministicTime() const { return deterministic_time_; }
192 
193  // Registers a boolean that will be set to true each time the reduced costs
194  // are or will be recomputed. This allows anyone that depends on this to know
195  // that it cannot just assume an incremental changes and needs to updates its
196  // data. Important: UpdateBeforeBasisPivot() will not trigger this.
197  void AddRecomputationWatcher(bool* watcher) { watchers_.push_back(watcher); }
198 
199  private:
200  // Statistics about this class.
201  struct Stats : public StatsGroup {
202  Stats()
203  : StatsGroup("ReducedCosts"),
204  basic_objective_left_inverse_density(
205  "basic_objective_left_inverse_density", this),
206  reduced_costs_accuracy("reduced_costs_accuracy", this),
207  cost_shift("cost_shift", this) {}
208  RatioDistribution basic_objective_left_inverse_density;
209  DoubleDistribution reduced_costs_accuracy;
210  DoubleDistribution cost_shift;
211  };
212 
213  // All these Compute() functions fill the corresponding DenseRow using
214  // the current problem data.
215  void ComputeBasicObjective();
216  void ComputeReducedCosts();
217  void ComputeBasicObjectiveLeftInverse();
218 
219  // Updates reduced_costs_ according to the given pivot. This adds a multiple
220  // of the vector equal to 1.0 on the leaving column and given by
221  // ComputeUpdateRow() on the non-basic columns. The multiple is such that the
222  // new leaving reduced cost is zero.
223  void UpdateReducedCosts(ColIndex entering_col, ColIndex leaving_col,
224  RowIndex leaving_row, Fractional pivot,
225  UpdateRow* update_row);
226 
227  // Updates basic_objective_ according to the given pivot.
228  void UpdateBasicObjective(ColIndex entering_col, RowIndex leaving_row);
229 
230  // All places that do 'recompute_reduced_costs_ = true' must go through here.
231  void SetRecomputeReducedCostsAndNotifyWatchers();
232 
233  // Problem data that should be updated from outside.
234  const CompactSparseMatrix& matrix_;
235  const DenseRow& objective_;
236  const RowToColMapping& basis_;
237  const VariablesInfo& variables_info_;
238  const BasisFactorization& basis_factorization_;
239  absl::BitGenRef random_;
240 
241  // Internal data.
242  GlopParameters parameters_;
243  mutable Stats stats_;
244 
245  // Booleans to control what happens on the next ChooseEnteringColumn() call.
246  bool must_refactorize_basis_;
247  bool recompute_basic_objective_left_inverse_;
248  bool recompute_basic_objective_;
249  bool recompute_reduced_costs_;
250 
251  // Indicates if we have computed the reduced costs with a good precision.
252  bool are_reduced_costs_precise_;
253  bool are_reduced_costs_recomputed_;
254 
255  bool has_cost_shift_ = false;
256 
257  // Values of the objective on the columns of the basis. The order is given by
258  // the basis_ mapping. It is usually denoted as 'c_B' in the literature .
259  DenseRow basic_objective_;
260 
261  // Perturbations to the objective function. This may be introduced to
262  // counter degenerecency. It will be removed at the end of the algorithm.
263  DenseRow cost_perturbations_;
264 
265  // Reduced costs of the relevant columns of A.
266  DenseRow reduced_costs_;
267 
268  // Left inverse by B of the basic_objective_. This is known as 'y' or 'pi' in
269  // the literature. Its scalar product with a column 'a' of A gives the value
270  // of the scalar product of the basic objective with the right inverse of 'a'.
271  //
272  // TODO(user): using the unit_row_left_inverse_, we can update the
273  // basic_objective_left_inverse_ at each iteration, this is not needed for the
274  // algorithm, but may gives us a good idea of the current precision of our
275  // estimates. It is also faster to compute the unit_row_left_inverse_ because
276  // of sparsity.
277  ScatteredRow basic_objective_left_inverse_;
278 
279  // This is usually parameters_.dual_feasibility_tolerance() except when the
280  // dual residual error |y.B - c_B| is higher than it and we have to increase
281  // the tolerance.
282  Fractional dual_feasibility_tolerance_;
283 
284  // Boolean(s) to set to false when the reduced cost are changed outside of the
285  // UpdateBeforeBasisPivot() function.
286  std::vector<bool*> watchers_;
287 
288  double deterministic_time_ = 0.0;
289 
290  DISALLOW_COPY_AND_ASSIGN(ReducedCosts);
291 };
292 
293 // Maintains the list of dual infeasible positions and their associated prices.
294 //
295 // TODO(user): Not high priority but should probably be moved to its own file.
297  public:
298  // Takes references to what we need.
299  // TODO(user): Switch to a model based API like in CP-SAT.
300  PrimalPrices(absl::BitGenRef random, const VariablesInfo& variables_info,
301  PrimalEdgeNorms* primal_edge_norms, ReducedCosts* reduced_costs);
302 
303  // Returns the best candidate out of the dual infeasible positions to enter
304  // the basis during a primal simplex iterations.
305  ColIndex GetBestEnteringColumn();
306 
307  // Similar to the other UpdateBeforeBasisPivot() functions.
308  //
309  // Important: Both the primal norms and reduced costs must have been updated
310  // before this is called.
311  void UpdateBeforeBasisPivot(ColIndex entering_col, UpdateRow* update_row);
312 
313  // Triggers a recomputation of the price at the given column only.
314  void RecomputePriceAt(ColIndex col);
315 
316  // Same than RecomputePriceAt() for the case where we know the position is
317  // dual feasible.
319 
320  // If the incremental updates are not properly called for a while, then it is
321  // important to make sure that the prices will be recomputed the next time
322  // GetBestEnteringColumn() is called.
323  void ForceRecomputation() { recompute_ = true; }
324 
325  private:
326  // Recomputes the primal prices but only for the given column indices. If
327  // from_clean_state is true, then we assume that there is currently no
328  // candidates in prices_.
329  template <bool from_clean_state, typename ColumnsToUpdate>
330  void UpdateEnteringCandidates(const ColumnsToUpdate& cols);
331 
332  bool recompute_ = true;
333  DynamicMaximum<ColIndex> prices_;
334 
335  const VariablesInfo& variables_info_;
336  PrimalEdgeNorms* primal_edge_norms_;
337  ReducedCosts* reduced_costs_;
338 };
339 
340 } // namespace glop
341 } // namespace operations_research
342 
343 #endif // OR_TOOLS_GLOP_REDUCED_COSTS_H_
void SetAndDebugCheckThatColumnIsDualFeasible(ColIndex col)
void UpdateBeforeBasisPivot(ColIndex entering_col, UpdateRow *update_row)
PrimalPrices(absl::BitGenRef random, const VariablesInfo &variables_info, PrimalEdgeNorms *primal_edge_norms, ReducedCosts *reduced_costs)
ReducedCosts(const CompactSparseMatrix &matrix_, const DenseRow &objective, const RowToColMapping &basis, const VariablesInfo &variables_info, const BasisFactorization &basis_factorization, absl::BitGenRef random)
Fractional TestEnteringReducedCostPrecision(ColIndex entering_col, const ScatteredColumn &direction)
bool IsValidPrimalEnteringCandidate(ColIndex col) const
void SetNonBasicVariableCostToZero(ColIndex col, Fractional *current_cost)
Fractional ComputeMaximumDualInfeasibilityOnNonBoxedVariables()
bool StepIsDualDegenerate(bool increasing_rc_is_needed, ColIndex col)
const DenseRow & GetCostPerturbations() const
void UpdateBeforeBasisPivot(ColIndex entering_col, RowIndex leaving_row, const ScatteredColumn &direction, UpdateRow *update_row)
Fractional GetDualFeasibilityTolerance() const
void ShiftCostIfNeeded(bool increasing_rc_is_needed, ColIndex col)
void SetParameters(const GlopParameters &parameters)
SatParameters parameters
ColIndex col
Definition: markowitz.cc:186
StrictITIVector< ColIndex, Fractional > DenseRow
Definition: lp_types.h:341
StrictITIVector< RowIndex, ColIndex > RowToColMapping
Definition: lp_types.h:384
Collection of objects used to extend the Constraint Solver library.