OR-Tools  9.3
entering_variable.cc
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
15
16#include <queue>
17
18#include "ortools/base/timer.h"
21
22namespace operations_research {
23namespace glop {
24
26 absl::BitGenRef random,
27 ReducedCosts* reduced_costs)
28 : variables_info_(variables_info),
29 random_(random),
30 reduced_costs_(reduced_costs),
31 parameters_() {}
32
34 bool nothing_to_recompute, const UpdateRow& update_row,
35 Fractional cost_variation, std::vector<ColIndex>* bound_flip_candidates,
36 ColIndex* entering_col) {
37 GLOP_RETURN_ERROR_IF_NULL(entering_col);
38 const DenseRow& update_coefficient = update_row.GetCoefficients();
39 const DenseRow& reduced_costs = reduced_costs_->GetReducedCosts();
40 SCOPED_TIME_STAT(&stats_);
41
42 breakpoints_.clear();
43 breakpoints_.reserve(update_row.GetNonZeroPositions().size());
44 const DenseBitRow& can_decrease = variables_info_.GetCanDecreaseBitRow();
45 const DenseBitRow& can_increase = variables_info_.GetCanIncreaseBitRow();
46 const DenseBitRow& is_boxed = variables_info_.GetNonBasicBoxedVariables();
47
48 // If everything has the best possible precision currently, we ignore
49 // low coefficients. This make sure we will never choose a pivot too small. It
50 // however can degrade the dual feasibility of the solution, but we can always
51 // fix that later.
52 //
53 // TODO(user): It is unclear if this is a good idea, but the primal simplex
54 // have pretty good/stable behavior with a similar logic. Experiment seems
55 // to show that this works well with the dual too.
56 const Fractional threshold = nothing_to_recompute
57 ? parameters_.minimum_acceptable_pivot()
58 : parameters_.ratio_test_zero_threshold();
59
60 // Harris ratio test. See below for more explanation. Here this is used to
61 // prune the first pass by not enqueueing ColWithRatio for columns that have
62 // a ratio greater than the current harris_ratio.
63 const Fractional harris_tolerance =
64 parameters_.harris_tolerance_ratio() *
65 reduced_costs_->GetDualFeasibilityTolerance();
67
68 // Like for the primal, we always allow a positive ministep, even if a
69 // variable is already infeasible by more than the tolerance.
70 const Fractional minimum_delta =
71 parameters_.degenerate_ministep_factor() *
72 reduced_costs_->GetDualFeasibilityTolerance();
73
74 num_operations_ += 10 * update_row.GetNonZeroPositions().size();
75 for (const ColIndex col : update_row.GetNonZeroPositions()) {
76 // We will add ratio * coeff to this column with a ratio positive or zero.
77 // cost_variation makes sure the leaving variable will be dual-feasible
78 // (its update coeff is sign(cost_variation) * 1.0).
79 const Fractional coeff = (cost_variation > 0.0) ? update_coefficient[col]
80 : -update_coefficient[col];
81
82 // In this case, at some point the reduced cost will be positive if not
83 // already, and the column will be dual-infeasible.
84 if (can_decrease.IsSet(col) && coeff > threshold) {
85 if (!is_boxed[col]) {
86 if (-reduced_costs[col] > harris_ratio * coeff) continue;
87 harris_ratio = std::min(
88 harris_ratio, (-reduced_costs[col] + harris_tolerance) / coeff);
89 harris_ratio = std::max(minimum_delta / coeff, harris_ratio);
90 }
91 breakpoints_.push_back(ColWithRatio(col, -reduced_costs[col], coeff));
92 continue;
93 }
94
95 // In this case, at some point the reduced cost will be negative if not
96 // already, and the column will be dual-infeasible.
97 if (can_increase.IsSet(col) && coeff < -threshold) {
98 if (!is_boxed[col]) {
99 if (reduced_costs[col] > harris_ratio * -coeff) continue;
100 harris_ratio = std::min(
101 harris_ratio, (reduced_costs[col] + harris_tolerance) / -coeff);
102 harris_ratio = std::max(minimum_delta / -coeff, harris_ratio);
103 }
104 breakpoints_.push_back(ColWithRatio(col, reduced_costs[col], -coeff));
105 continue;
106 }
107 }
108
109 // Process the breakpoints in priority order as suggested by Maros in
110 // I. Maros, "A generalized dual phase-2 simplex algorithm", European Journal
111 // of Operational Research, 149(1):1-16, 2003.
112 // We use directly make_heap() to avoid a copy of breakpoints, benchmark shows
113 // that it is slightly faster.
114 std::make_heap(breakpoints_.begin(), breakpoints_.end());
115
116 // Harris ratio test. Since we process the breakpoints by increasing ratio, we
117 // do not need a two-pass algorithm as described in the literature. Each time
118 // we process a new breakpoint, we update the harris_ratio of all the
119 // processed breakpoints. For the first new breakpoint with a ratio greater
120 // than the current harris_ratio we know that:
121 // - All the unprocessed breakpoints will have a ratio greater too, so they
122 // will not contribute to the minimum Harris ratio.
123 // - We thus have the actual harris_ratio.
124 // - We have processed all breakpoints with a ratio smaller than it.
126
127 *entering_col = kInvalidCol;
128 bound_flip_candidates->clear();
129 Fractional step = 0.0;
130 Fractional best_coeff = -1.0;
131 Fractional variation_magnitude = std::abs(cost_variation);
132 equivalent_entering_choices_.clear();
133 while (!breakpoints_.empty()) {
134 const ColWithRatio top = breakpoints_.front();
135 if (top.ratio > harris_ratio) break;
136
137 // If the column is boxed, we can just switch its bounds and
138 // ignore the breakpoint! But we need to see if the entering row still
139 // improve the objective. This is called the bound flipping ratio test in
140 // the literature. See for instance:
141 // http://www.mpi-inf.mpg.de/conferences/adfocs-03/Slides/Bixby_2.pdf
142 //
143 // For each bound flip, |cost_variation| decreases by
144 // |upper_bound - lower_bound| times |coeff|.
145 //
146 // Note that the actual flipping will be done afterwards by
147 // MakeBoxedVariableDualFeasible() in revised_simplex.cc.
148 if (variation_magnitude > threshold) {
149 if (is_boxed[top.col]) {
150 variation_magnitude -=
151 variables_info_.GetBoundDifference(top.col) * top.coeff_magnitude;
152 if (variation_magnitude > threshold) {
153 bound_flip_candidates->push_back(top.col);
154 std::pop_heap(breakpoints_.begin(), breakpoints_.end());
155 breakpoints_.pop_back();
156 continue;
157 }
158 }
159 }
160
161 // TODO(user): We want to maximize both the ratio (objective improvement)
162 // and the coeff_magnitude (stable pivot), so we have to make some
163 // trade-offs. Investigate alternative strategies.
164 if (top.coeff_magnitude >= best_coeff) {
165 // Update harris_ratio. Note that because we process ratio in order, the
166 // harris ratio can only get smaller if the coeff_magnitude is bigger
167 // than the one of the best coefficient.
168 harris_ratio = std::min(
169 harris_ratio, top.ratio + harris_tolerance / top.coeff_magnitude);
170
171 // If the dual infeasibility is too high, the harris_ratio can be
172 // negative. In this case we set it to 0.0, allowing any infeasible
173 // position to enter the basis. This is quite important because its
174 // helps in the choice of a stable pivot.
175 harris_ratio =
176 std::max(harris_ratio, minimum_delta / top.coeff_magnitude);
177
178 if (top.coeff_magnitude == best_coeff && top.ratio == step) {
179 DCHECK_NE(*entering_col, kInvalidCol);
180 equivalent_entering_choices_.push_back(top.col);
181 } else {
182 equivalent_entering_choices_.clear();
183 best_coeff = top.coeff_magnitude;
184 *entering_col = top.col;
185
186 // Note that the step is not directly used, so it is okay to leave it
187 // negative.
188 step = top.ratio;
189 }
190 }
191
192 // Remove the top breakpoint and maintain the heap structure.
193 // This is the same as doing a pop() on a priority_queue.
194 std::pop_heap(breakpoints_.begin(), breakpoints_.end());
195 breakpoints_.pop_back();
196 }
197
198 // Break the ties randomly.
199 if (!equivalent_entering_choices_.empty()) {
200 equivalent_entering_choices_.push_back(*entering_col);
201 *entering_col =
202 equivalent_entering_choices_[std::uniform_int_distribution<int>(
203 0, equivalent_entering_choices_.size() - 1)(random_)];
205 stats_.num_perfect_ties.Add(equivalent_entering_choices_.size()));
206 }
207
208 if (*entering_col == kInvalidCol) return Status::OK();
209
210 // If best_coeff is small and they are potential bound flips, we can take a
211 // smaller step but use a good pivot.
212 const Fractional pivot_limit = parameters_.minimum_acceptable_pivot();
213 if (best_coeff < pivot_limit && !bound_flip_candidates->empty()) {
214 // Note that it is okay to leave more candidate than necessary in the
215 // returned bound_flip_candidates vector.
216 for (int i = bound_flip_candidates->size() - 1; i >= 0; --i) {
217 const ColIndex col = (*bound_flip_candidates)[i];
218 if (std::abs(update_coefficient[col]) < pivot_limit) continue;
219
220 VLOG(1) << "Used bound flip to avoid bad pivot. Before: " << best_coeff
221 << " now: " << std::abs(update_coefficient[col]);
222 *entering_col = col;
223 break;
224 }
225 }
226
227 return Status::OK();
228}
229
231 bool nothing_to_recompute, const UpdateRow& update_row,
232 Fractional cost_variation, ColIndex* entering_col) {
233 GLOP_RETURN_ERROR_IF_NULL(entering_col);
234 const DenseRow& update_coefficient = update_row.GetCoefficients();
235 const DenseRow& reduced_costs = reduced_costs_->GetReducedCosts();
236 SCOPED_TIME_STAT(&stats_);
237
238 // List of breakpoints where a variable change from feasibility to
239 // infeasibility or the opposite.
240 breakpoints_.clear();
241 breakpoints_.reserve(update_row.GetNonZeroPositions().size());
242
243 const Fractional threshold = nothing_to_recompute
244 ? parameters_.minimum_acceptable_pivot()
245 : parameters_.ratio_test_zero_threshold();
246 const Fractional dual_feasibility_tolerance =
247 reduced_costs_->GetDualFeasibilityTolerance();
248 const Fractional harris_tolerance =
249 parameters_.harris_tolerance_ratio() * dual_feasibility_tolerance;
250 const Fractional minimum_delta =
251 parameters_.degenerate_ministep_factor() * dual_feasibility_tolerance;
252
253 const DenseBitRow& can_decrease = variables_info_.GetCanDecreaseBitRow();
254 const DenseBitRow& can_increase = variables_info_.GetCanIncreaseBitRow();
255 const VariableTypeRow& variable_type = variables_info_.GetTypeRow();
256 num_operations_ += 10 * update_row.GetNonZeroPositions().size();
257 for (const ColIndex col : update_row.GetNonZeroPositions()) {
258 // Boxed variables shouldn't be in the update position list because they
259 // will be dealt with afterwards by MakeBoxedVariableDualFeasible().
261
262 // Fixed variable shouldn't be in the update position list.
264
265 // Skip if the coeff is too small to be a numerically stable pivot.
266 if (std::abs(update_coefficient[col]) < threshold) continue;
267
268 // We will add ratio * coeff to this column. cost_variation makes sure
269 // the leaving variable will be dual-feasible (its update coeff is
270 // sign(cost_variation) * 1.0).
271 //
272 // TODO(user): This is the same in DualChooseEnteringColumn(), remove
273 // duplication?
274 const Fractional coeff = (cost_variation > 0.0) ? update_coefficient[col]
275 : -update_coefficient[col];
276
277 // Only proceed if there is a transition, note that if reduced_costs[col]
278 // is close to zero, then the variable is counted as dual-feasible.
279 if (std::abs(reduced_costs[col]) <= dual_feasibility_tolerance) {
280 // Continue if the variation goes in the dual-feasible direction.
281 if (coeff > 0 && !can_decrease.IsSet(col)) continue;
282 if (coeff < 0 && !can_increase.IsSet(col)) continue;
283
284 // For an already dual-infeasible variable, we allow to push it until
285 // the harris_tolerance. But if it is past that or close to it, we also
286 // always enforce a minimum push.
287 if (coeff * reduced_costs[col] > 0.0) {
288 breakpoints_.push_back(ColWithRatio(
289 col,
290 std::max(minimum_delta,
291 harris_tolerance - std::abs(reduced_costs[col])),
292 std::abs(coeff)));
293 continue;
294 }
295 } else {
296 // If the two are of the same sign, there is no transition, skip.
297 if (coeff * reduced_costs[col] > 0.0) continue;
298 }
299
300 // We are sure there is a transition, add it to the set of breakpoints.
301 breakpoints_.push_back(ColWithRatio(
302 col, std::abs(reduced_costs[col]) + harris_tolerance, std::abs(coeff)));
303 }
304
305 // Process the breakpoints in priority order.
306 std::make_heap(breakpoints_.begin(), breakpoints_.end());
307
308 // Because of our priority queue, it is easy to choose a sub-optimal step to
309 // have a stable pivot. The pivot with the highest magnitude and that reduces
310 // the infeasibility the most is chosen.
311 Fractional pivot_magnitude = 0.0;
312
313 // Select the last breakpoint that still improves the infeasibility and has a
314 // numerically stable pivot.
315 *entering_col = kInvalidCol;
316 Fractional step = -1.0;
317 Fractional improvement = std::abs(cost_variation);
318 while (!breakpoints_.empty()) {
319 const ColWithRatio top = breakpoints_.front();
320
321 // We keep the greatest coeff_magnitude for the same ratio.
322 DCHECK(top.ratio > step ||
323 (top.ratio == step && top.coeff_magnitude <= pivot_magnitude));
324 if (top.ratio > step && top.coeff_magnitude >= pivot_magnitude) {
325 *entering_col = top.col;
326 step = top.ratio;
327 pivot_magnitude = top.coeff_magnitude;
328 }
329 improvement -= top.coeff_magnitude;
330
331 // If the variable is free, then not only do we loose the infeasibility
332 // improvment, we also render it worse if we keep going in the same
333 // direction.
334 if (can_decrease.IsSet(top.col) && can_increase.IsSet(top.col) &&
335 std::abs(reduced_costs[top.col]) > threshold) {
336 improvement -= top.coeff_magnitude;
337 }
338
339 if (improvement <= 0.0) break;
340 std::pop_heap(breakpoints_.begin(), breakpoints_.end());
341 breakpoints_.pop_back();
342 }
343 return Status::OK();
344}
345
346void EnteringVariable::SetParameters(const GlopParameters& parameters) {
347 parameters_ = parameters;
348}
349
350} // namespace glop
351} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define DCHECK(condition)
Definition: base/logging.h:890
#define VLOG(verboselevel)
Definition: base/logging.h:984
bool IsSet(IndexType i) const
Definition: bitset.h:485
EnteringVariable(const VariablesInfo &variables_info, absl::BitGenRef random, ReducedCosts *reduced_costs)
ABSL_MUST_USE_RESULT Status DualPhaseIChooseEnteringColumn(bool nothing_to_recompute, const UpdateRow &update_row, Fractional cost_variation, ColIndex *entering_col)
void SetParameters(const GlopParameters &parameters)
ABSL_MUST_USE_RESULT Status DualChooseEnteringColumn(bool nothing_to_recompute, const UpdateRow &update_row, Fractional cost_variation, std::vector< ColIndex > *bound_flip_candidates, ColIndex *entering_col)
Fractional GetDualFeasibilityTolerance() const
static const Status OK()
Definition: status.h:56
const DenseRow & GetCoefficients() const
Definition: update_row.cc:165
const ColIndexVector & GetNonZeroPositions() const
Definition: update_row.cc:167
const DenseBitRow & GetNonBasicBoxedVariables() const
Fractional GetBoundDifference(ColIndex col) const
const DenseBitRow & GetCanIncreaseBitRow() const
const DenseBitRow & GetCanDecreaseBitRow() const
const VariableTypeRow & GetTypeRow() const
SatParameters parameters
ColIndex col
Definition: markowitz.cc:183
const ColIndex kInvalidCol(-1)
Collection of objects used to extend the Constraint Solver library.
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:437
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:438
#define GLOP_RETURN_ERROR_IF_NULL(arg)
Definition: status.h:87
const double coeff