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