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