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