OR-Tools  9.2
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 
22 namespace operations_research {
23 namespace 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.
125  harris_ratio = std::numeric_limits<Fractional>::max();
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.
263  DCHECK_NE(variable_type[col], VariableType::FIXED_VARIABLE);
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 
347  parameters_ = parameters;
348 }
349 
350 } // namespace glop
351 } // namespace operations_research
int64_t min
Definition: alldiff_cst.cc:139
const DenseRow & GetCoefficients() const
Definition: update_row.cc:165
const DenseBitRow & GetCanDecreaseBitRow() const
const DenseBitRow & GetCanIncreaseBitRow() const
#define VLOG(verboselevel)
Definition: base/logging.h:983
Fractional GetDualFeasibilityTolerance() const
const ColIndex kInvalidCol(-1)
ColIndex col
Definition: markowitz.cc:183
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:438
#define GLOP_RETURN_ERROR_IF_NULL(arg)
Definition: status.h:87
static const Status OK()
Definition: status.h:56
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)
int64_t max
Definition: alldiff_cst.cc:140
void SetParameters(const GlopParameters &parameters)
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:891
const VariableTypeRow & GetTypeRow() const
const DenseBitRow & GetNonBasicBoxedVariables() const
const ColIndexVector & GetNonZeroPositions() const
Definition: update_row.cc:167
ABSL_MUST_USE_RESULT Status DualPhaseIChooseEnteringColumn(bool nothing_to_recompute, const UpdateRow &update_row, Fractional cost_variation, ColIndex *entering_col)
#define DCHECK(condition)
Definition: base/logging.h:889
bool IsSet(IndexType i) const
Definition: bitset.h:485
EnteringVariable(const VariablesInfo &variables_info, absl::BitGenRef random, ReducedCosts *reduced_costs)
Collection of objects used to extend the Constraint Solver library.
Fractional GetBoundDifference(ColIndex col) const
SatParameters parameters
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:437