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