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