OR-Tools  9.0
preprocessor.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 <cstdint>
17 #include <limits>
18 
19 #include "absl/strings/str_format.h"
22 #include "ortools/glop/status.h"
27 
28 namespace operations_research {
29 namespace glop {
30 
32 
33 namespace {
34 // Returns an interval as an human readable string for debugging.
35 std::string IntervalString(Fractional lb, Fractional ub) {
36  return absl::StrFormat("[%g, %g]", lb, ub);
37 }
38 
39 #if defined(_MSC_VER)
40 double trunc(double d) { return d > 0 ? floor(d) : ceil(d); }
41 #endif
42 } // namespace
43 
44 // --------------------------------------------------------
45 // Preprocessor
46 // --------------------------------------------------------
47 Preprocessor::Preprocessor(const GlopParameters* parameters)
48  : status_(ProblemStatus::INIT),
49  parameters_(*parameters),
50  in_mip_context_(false),
51  infinite_time_limit_(TimeLimit::Infinite()),
52  time_limit_(infinite_time_limit_.get()) {}
54 
55 // --------------------------------------------------------
56 // MainLpPreprocessor
57 // --------------------------------------------------------
58 
59 #define RUN_PREPROCESSOR(name) \
60  RunAndPushIfRelevant(std::unique_ptr<Preprocessor>(new name(&parameters_)), \
61  #name, time_limit_, lp)
62 
64  RETURN_VALUE_IF_NULL(lp, false);
65  initial_num_rows_ = lp->num_constraints();
66  initial_num_cols_ = lp->num_variables();
67  initial_num_entries_ = lp->num_entries();
68  if (parameters_.use_preprocessing()) {
70 
71  // We run it a few times because running one preprocessor may allow another
72  // one to remove more stuff.
73  const int kMaxNumPasses = 20;
74  for (int i = 0; i < kMaxNumPasses; ++i) {
75  const int old_stack_size = preprocessors_.size();
84 
85  // Abort early if none of the preprocessors did something. Technically
86  // this is true if none of the preprocessors above needs postsolving,
87  // which has exactly the same meaning for these particular preprocessors.
88  if (preprocessors_.size() == old_stack_size) {
89  // We use i here because the last pass did nothing.
90  if (parameters_.log_search_progress() || VLOG_IS_ON(1)) {
91  LOG(INFO) << "Reached fixed point after presolve pass #" << i;
92  }
93  break;
94  }
95  }
98 
99  // TODO(user): Run them in the loop above if the effect on the running time
100  // is good. This needs more investigation.
103 
104  // If DualizerPreprocessor was run, we need to do some extra preprocessing.
105  // This is because it currently adds a lot of zero-cost singleton columns.
106  const int old_stack_size = preprocessors_.size();
107 
108  // TODO(user): We probably want to scale the costs before and after this
109  // preprocessor so that the rhs/objective of the dual are with a good
110  // magnitude.
112  if (old_stack_size != preprocessors_.size()) {
118  }
119 
121  }
122 
123  // The scaling is controled by use_scaling, not use_preprocessing.
125 
126  // This one must always run. It is needed by the revised simplex code.
128  return !preprocessors_.empty();
129 }
130 
131 #undef RUN_PREPROCESSOR
132 
133 void MainLpPreprocessor::RunAndPushIfRelevant(
134  std::unique_ptr<Preprocessor> preprocessor, const std::string& name,
136  RETURN_IF_NULL(preprocessor);
138  if (status_ != ProblemStatus::INIT || time_limit->LimitReached()) return;
139 
140  const double start_time = time_limit->GetElapsedTime();
141  preprocessor->SetTimeLimit(time_limit);
142 
143  // No need to run the preprocessor if the lp is empty.
144  // TODO(user): without this test, the code is failing as of 2013-03-18.
145  if (lp->num_variables() == 0 && lp->num_constraints() == 0) {
147  return;
148  }
149 
150  const bool log_info = parameters_.log_search_progress() || VLOG_IS_ON(1);
151  if (preprocessor->Run(lp)) {
152  const EntryIndex new_num_entries = lp->num_entries();
153  const double preprocess_time = time_limit->GetElapsedTime() - start_time;
154  if (log_info) {
155  LOG(INFO) << absl::StrFormat(
156  "%s(%fs): %d(%d) rows, %d(%d) columns, %d(%d) entries.", name,
157  preprocess_time, lp->num_constraints().value(),
158  (lp->num_constraints() - initial_num_rows_).value(),
159  lp->num_variables().value(),
160  (lp->num_variables() - initial_num_cols_).value(),
161  // static_cast<int64_t> is needed because the Android port uses
162  // int32_t.
163  static_cast<int64_t>(new_num_entries.value()),
164  static_cast<int64_t>(new_num_entries.value() -
165  initial_num_entries_.value()));
166  }
167  status_ = preprocessor->status();
168  preprocessors_.push_back(std::move(preprocessor));
169  return;
170  } else {
171  // Even if a preprocessor returns false (i.e. no need for postsolve), it
172  // can detect an issue with the problem.
173  status_ = preprocessor->status();
174  if (status_ != ProblemStatus::INIT && log_info) {
175  LOG(INFO) << name << " detected that the problem is "
177  }
178  }
179 }
180 
183  while (!preprocessors_.empty()) {
184  preprocessors_.back()->RecoverSolution(solution);
185  preprocessors_.pop_back();
186  }
187 }
188 
189 // --------------------------------------------------------
190 // ColumnDeletionHelper
191 // --------------------------------------------------------
192 
193 void ColumnsSaver::SaveColumn(ColIndex col, const SparseColumn& column) {
194  const int index = saved_columns_.size();
195  CHECK(saved_columns_index_.insert({col, index}).second);
196  saved_columns_.push_back(column);
197 }
198 
200  const SparseColumn& column) {
201  const int index = saved_columns_.size();
202  const bool inserted = saved_columns_index_.insert({col, index}).second;
203  if (inserted) saved_columns_.push_back(column);
204 }
205 
207  const auto it = saved_columns_index_.find(col);
208  CHECK(it != saved_columns_index_.end());
209  return saved_columns_[it->second];
210 }
211 
213  const auto it = saved_columns_index_.find(col);
214  return it == saved_columns_index_.end() ? empty_column_
215  : saved_columns_[it->second];
216 }
217 
219  is_column_deleted_.clear();
220  stored_value_.clear();
221 }
222 
225 }
226 
228  ColIndex col, Fractional fixed_value, VariableStatus status) {
229  DCHECK_GE(col, 0);
230  if (col >= is_column_deleted_.size()) {
231  is_column_deleted_.resize(col + 1, false);
232  stored_value_.resize(col + 1, 0.0);
233  stored_status_.resize(col + 1, VariableStatus::FREE);
234  }
235  is_column_deleted_[col] = true;
236  stored_value_[col] = fixed_value;
237  stored_status_[col] = status;
238 }
239 
241  ProblemSolution* solution) const {
242  DenseRow new_primal_values;
243  VariableStatusRow new_variable_statuses;
244  ColIndex old_index(0);
245  for (ColIndex col(0); col < is_column_deleted_.size(); ++col) {
246  if (is_column_deleted_[col]) {
247  new_primal_values.push_back(stored_value_[col]);
248  new_variable_statuses.push_back(stored_status_[col]);
249  } else {
250  new_primal_values.push_back(solution->primal_values[old_index]);
251  new_variable_statuses.push_back(solution->variable_statuses[old_index]);
252  ++old_index;
253  }
254  }
255 
256  // Copy the end of the vectors and swap them with the ones in solution.
257  const ColIndex num_cols = solution->primal_values.size();
258  DCHECK_EQ(num_cols, solution->variable_statuses.size());
259  for (; old_index < num_cols; ++old_index) {
260  new_primal_values.push_back(solution->primal_values[old_index]);
261  new_variable_statuses.push_back(solution->variable_statuses[old_index]);
262  }
263  new_primal_values.swap(solution->primal_values);
264  new_variable_statuses.swap(solution->variable_statuses);
265 }
266 
267 // --------------------------------------------------------
268 // RowDeletionHelper
269 // --------------------------------------------------------
270 
271 void RowDeletionHelper::Clear() { is_row_deleted_.clear(); }
272 
274  DCHECK_GE(row, 0);
275  if (row >= is_row_deleted_.size()) {
276  is_row_deleted_.resize(row + 1, false);
277  }
278  is_row_deleted_[row] = true;
279 }
280 
282  if (row >= is_row_deleted_.size()) return;
283  is_row_deleted_[row] = false;
284 }
285 
287  return is_row_deleted_;
288 }
289 
291  DenseColumn new_dual_values;
292  ConstraintStatusColumn new_constraint_statuses;
293  RowIndex old_index(0);
294  const RowIndex end = is_row_deleted_.size();
295  for (RowIndex row(0); row < end; ++row) {
296  if (is_row_deleted_[row]) {
297  new_dual_values.push_back(0.0);
298  new_constraint_statuses.push_back(ConstraintStatus::BASIC);
299  } else {
300  new_dual_values.push_back(solution->dual_values[old_index]);
301  new_constraint_statuses.push_back(
302  solution->constraint_statuses[old_index]);
303  ++old_index;
304  }
305  }
306 
307  // Copy the end of the vectors and swap them with the ones in solution.
308  const RowIndex num_rows = solution->dual_values.size();
309  DCHECK_EQ(num_rows, solution->constraint_statuses.size());
310  for (; old_index < num_rows; ++old_index) {
311  new_dual_values.push_back(solution->dual_values[old_index]);
312  new_constraint_statuses.push_back(solution->constraint_statuses[old_index]);
313  }
314  new_dual_values.swap(solution->dual_values);
315  new_constraint_statuses.swap(solution->constraint_statuses);
316 }
317 
318 // --------------------------------------------------------
319 // EmptyColumnPreprocessor
320 // --------------------------------------------------------
321 
322 namespace {
323 
324 // Computes the status of a variable given its value and bounds. This only works
325 // with a value exactly at one of the bounds, or a value of 0.0 for free
326 // variables.
327 VariableStatus ComputeVariableStatus(Fractional value, Fractional lower_bound,
329  if (lower_bound == upper_bound) {
333  }
334  if (value == lower_bound) {
337  }
338  if (value == upper_bound) {
341  }
342 
343  // TODO(user): restrict this to unbounded variables with a value of zero.
344  // We can't do that when postsolving infeasible problem. Don't call postsolve
345  // on an infeasible problem?
346  return VariableStatus::FREE;
347 }
348 
349 // Returns the input with the smallest magnitude or zero if both are infinite.
350 Fractional MinInMagnitudeOrZeroIfInfinite(Fractional a, Fractional b) {
351  const Fractional value = std::abs(a) < std::abs(b) ? a : b;
352  return IsFinite(value) ? value : 0.0;
353 }
354 
355 Fractional MagnitudeOrZeroIfInfinite(Fractional value) {
356  return IsFinite(value) ? std::abs(value) : 0.0;
357 }
358 
359 // Returns the maximum magnitude of the finite variable bounds of the given
360 // linear program.
361 Fractional ComputeMaxVariableBoundsMagnitude(const LinearProgram& lp) {
362  Fractional max_bounds_magnitude = 0.0;
363  const ColIndex num_cols = lp.num_variables();
364  for (ColIndex col(0); col < num_cols; ++col) {
365  max_bounds_magnitude = std::max(
366  max_bounds_magnitude,
367  std::max(MagnitudeOrZeroIfInfinite(lp.variable_lower_bounds()[col]),
368  MagnitudeOrZeroIfInfinite(lp.variable_upper_bounds()[col])));
369  }
370  return max_bounds_magnitude;
371 }
372 
373 } // namespace
374 
377  RETURN_VALUE_IF_NULL(lp, false);
378  column_deletion_helper_.Clear();
379  const ColIndex num_cols = lp->num_variables();
380  for (ColIndex col(0); col < num_cols; ++col) {
381  if (lp->GetSparseColumn(col).IsEmpty()) {
384  const Fractional objective_coefficient =
387  if (objective_coefficient == 0) {
388  // Any feasible value will do.
389  if (upper_bound != kInfinity) {
390  value = upper_bound;
391  } else {
392  if (lower_bound != -kInfinity) {
393  value = lower_bound;
394  } else {
395  value = Fractional(0.0);
396  }
397  }
398  } else {
399  value = objective_coefficient > 0 ? lower_bound : upper_bound;
400  if (!IsFinite(value)) {
401  VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, empty column " << col
402  << " has a minimization cost of " << objective_coefficient
403  << " and bounds"
404  << " [" << lower_bound << "," << upper_bound << "]";
406  return false;
407  }
409  value * lp->objective_coefficients()[col]);
410  }
411  column_deletion_helper_.MarkColumnForDeletionWithState(
412  col, value, ComputeVariableStatus(value, lower_bound, upper_bound));
413  }
414  }
415  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
416  return !column_deletion_helper_.IsEmpty();
417 }
418 
421  RETURN_IF_NULL(solution);
422  column_deletion_helper_.RestoreDeletedColumns(solution);
423 }
424 
425 // --------------------------------------------------------
426 // ProportionalColumnPreprocessor
427 // --------------------------------------------------------
428 
429 namespace {
430 
431 // Subtracts 'multiple' times the column col of the given linear program from
432 // the constraint bounds. That is, for a non-zero entry of coefficient c,
433 // c * multiple is substracted from both the constraint upper and lower bound.
434 void SubtractColumnMultipleFromConstraintBound(ColIndex col,
435  Fractional multiple,
436  LinearProgram* lp) {
439  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
440  const RowIndex row = e.row();
441  const Fractional delta = multiple * e.coefficient();
442  (*lbs)[row] -= delta;
443  (*ubs)[row] -= delta;
444  }
445  // While not needed for correctness, this allows the presolved problem to
446  // have the same objective value as the original one.
448  lp->objective_coefficients()[col] * multiple);
449 }
450 
451 // Struct used to detect proportional columns with the same cost. For that, a
452 // vector of such struct will be sorted, and only the columns that end up
453 // together need to be compared.
454 struct ColumnWithRepresentativeAndScaledCost {
455  ColumnWithRepresentativeAndScaledCost(ColIndex _col, ColIndex _representative,
456  Fractional _scaled_cost)
457  : col(_col), representative(_representative), scaled_cost(_scaled_cost) {}
458  ColIndex col;
459  ColIndex representative;
461 
462  bool operator<(const ColumnWithRepresentativeAndScaledCost& other) const {
463  if (representative == other.representative) {
464  if (scaled_cost == other.scaled_cost) {
465  return col < other.col;
466  }
467  return scaled_cost < other.scaled_cost;
468  }
469  return representative < other.representative;
470  }
471 };
472 
473 } // namespace
474 
477  RETURN_VALUE_IF_NULL(lp, false);
479  lp->GetSparseMatrix(), parameters_.preprocessor_zero_tolerance());
480 
481  // Compute some statistics and make each class representative point to itself
482  // in the mapping. Also store the columns that are proportional to at least
483  // another column in proportional_columns to iterate on them more efficiently.
484  //
485  // TODO(user): Change FindProportionalColumns for this?
486  int num_proportionality_classes = 0;
487  std::vector<ColIndex> proportional_columns;
488  for (ColIndex col(0); col < mapping.size(); ++col) {
489  const ColIndex representative = mapping[col];
490  if (representative != kInvalidCol) {
491  if (mapping[representative] == kInvalidCol) {
492  proportional_columns.push_back(representative);
493  ++num_proportionality_classes;
494  mapping[representative] = representative;
495  }
496  proportional_columns.push_back(col);
497  }
498  }
499  if (proportional_columns.empty()) return false;
500  VLOG(1) << "The problem contains " << proportional_columns.size()
501  << " columns which belong to " << num_proportionality_classes
502  << " proportionality classes.";
503 
504  // Note(user): using the first coefficient may not give the best precision.
505  const ColIndex num_cols = lp->num_variables();
506  column_factors_.assign(num_cols, 0.0);
507  for (const ColIndex col : proportional_columns) {
508  const SparseColumn& column = lp->GetSparseColumn(col);
509  column_factors_[col] = column.GetFirstCoefficient();
510  }
511 
512  // This is only meaningful for column representative.
513  //
514  // The reduced cost of a column is 'cost - dual_values.column' and we know
515  // that for all proportional columns, 'dual_values.column /
516  // column_factors_[col]' is the same. Here, we bound this quantity which is
517  // related to the cost 'slope' of a proportional column:
518  // cost / column_factors_[col].
519  DenseRow slope_lower_bound(num_cols, -kInfinity);
520  DenseRow slope_upper_bound(num_cols, +kInfinity);
521  for (const ColIndex col : proportional_columns) {
522  const ColIndex representative = mapping[col];
523 
524  // We reason in terms of a minimization problem here.
525  const bool is_rc_positive_or_zero =
526  (lp->variable_upper_bounds()[col] == kInfinity);
527  const bool is_rc_negative_or_zero =
528  (lp->variable_lower_bounds()[col] == -kInfinity);
529  bool is_slope_upper_bounded = is_rc_positive_or_zero;
530  bool is_slope_lower_bounded = is_rc_negative_or_zero;
531  if (column_factors_[col] < 0.0) {
532  std::swap(is_slope_lower_bounded, is_slope_upper_bounded);
533  }
534  const Fractional slope =
536  column_factors_[col];
537  if (is_slope_lower_bounded) {
538  slope_lower_bound[representative] =
539  std::max(slope_lower_bound[representative], slope);
540  }
541  if (is_slope_upper_bounded) {
542  slope_upper_bound[representative] =
543  std::min(slope_upper_bound[representative], slope);
544  }
545  }
546 
547  // Deal with empty slope intervals.
548  for (const ColIndex col : proportional_columns) {
549  const ColIndex representative = mapping[col];
550 
551  // This is only needed for class representative columns.
552  if (representative == col) {
554  slope_lower_bound[representative],
555  slope_upper_bound[representative])) {
556  VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, no feasible dual values"
557  << " can satisfy the constraints of the proportional columns"
558  << " with representative " << representative << "."
559  << " the associated quantity must be in ["
560  << slope_lower_bound[representative] << ","
561  << slope_upper_bound[representative] << "].";
563  return false;
564  }
565  }
566  }
567 
568  // Now, fix the columns that can be fixed to one of their bounds.
569  for (const ColIndex col : proportional_columns) {
570  const ColIndex representative = mapping[col];
571  const Fractional slope =
573  column_factors_[col];
574 
575  // The scaled reduced cost is slope - quantity.
576  bool variable_can_be_fixed = false;
577  Fractional target_bound = 0.0;
578 
581  if (!IsSmallerWithinFeasibilityTolerance(slope_lower_bound[representative],
582  slope)) {
583  // The scaled reduced cost is < 0.
584  variable_can_be_fixed = true;
585  target_bound = (column_factors_[col] >= 0.0) ? upper_bound : lower_bound;
587  slope, slope_upper_bound[representative])) {
588  // The scaled reduced cost is > 0.
589  variable_can_be_fixed = true;
590  target_bound = (column_factors_[col] >= 0.0) ? lower_bound : upper_bound;
591  }
592 
593  if (variable_can_be_fixed) {
594  // Clear mapping[col] so this column will not be considered for the next
595  // stage.
596  mapping[col] = kInvalidCol;
597  if (!IsFinite(target_bound)) {
598  VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED.";
600  return false;
601  } else {
602  SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
603  column_deletion_helper_.MarkColumnForDeletionWithState(
604  col, target_bound,
605  ComputeVariableStatus(target_bound, lower_bound, upper_bound));
606  }
607  }
608  }
609 
610  // Merge the variables with the same scaled cost.
611  std::vector<ColumnWithRepresentativeAndScaledCost> sorted_columns;
612  for (const ColIndex col : proportional_columns) {
613  const ColIndex representative = mapping[col];
614 
615  // This test is needed because we already removed some columns.
616  if (mapping[col] != kInvalidCol) {
617  sorted_columns.push_back(ColumnWithRepresentativeAndScaledCost(
619  lp->objective_coefficients()[col] / column_factors_[col]));
620  }
621  }
622  std::sort(sorted_columns.begin(), sorted_columns.end());
623 
624  // All this will be needed during postsolve.
625  merged_columns_.assign(num_cols, kInvalidCol);
626  lower_bounds_.assign(num_cols, -kInfinity);
627  upper_bounds_.assign(num_cols, kInfinity);
628  new_lower_bounds_.assign(num_cols, -kInfinity);
629  new_upper_bounds_.assign(num_cols, kInfinity);
630 
631  for (int i = 0; i < sorted_columns.size();) {
632  const ColIndex target_col = sorted_columns[i].col;
633  const ColIndex target_representative = sorted_columns[i].representative;
634  const Fractional target_scaled_cost = sorted_columns[i].scaled_cost;
635 
636  // Save the initial bounds before modifying them.
637  lower_bounds_[target_col] = lp->variable_lower_bounds()[target_col];
638  upper_bounds_[target_col] = lp->variable_upper_bounds()[target_col];
639 
640  int num_merged = 0;
641  for (++i; i < sorted_columns.size(); ++i) {
642  if (sorted_columns[i].representative != target_representative) break;
643  if (std::abs(sorted_columns[i].scaled_cost - target_scaled_cost) >=
644  parameters_.preprocessor_zero_tolerance()) {
645  break;
646  }
647  ++num_merged;
648  const ColIndex col = sorted_columns[i].col;
651  lower_bounds_[col] = lower_bound;
652  upper_bounds_[col] = upper_bound;
653  merged_columns_[col] = target_col;
654 
655  // This is a bit counter intuitive, but when a column is divided by x,
656  // the corresponding bounds have to be multiplied by x.
657  const Fractional bound_factor =
658  column_factors_[col] / column_factors_[target_col];
659 
660  // We need to shift the variable so that a basic solution of the new
661  // problem can easily be converted to a basic solution of the original
662  // problem.
663 
664  // A feasible value for the variable must be chosen, and the variable must
665  // be shifted by this value. This is done to make sure that it will be
666  // possible to recreate a basic solution of the original problem from a
667  // basic solution of the pre-solved problem during post-solve.
668  const Fractional target_value =
669  MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
670  Fractional lower_diff = (lower_bound - target_value) * bound_factor;
671  Fractional upper_diff = (upper_bound - target_value) * bound_factor;
672  if (bound_factor < 0.0) {
673  std::swap(lower_diff, upper_diff);
674  }
675  lp->SetVariableBounds(
676  target_col, lp->variable_lower_bounds()[target_col] + lower_diff,
677  lp->variable_upper_bounds()[target_col] + upper_diff);
678  SubtractColumnMultipleFromConstraintBound(col, target_value, lp);
679  column_deletion_helper_.MarkColumnForDeletionWithState(
680  col, target_value,
681  ComputeVariableStatus(target_value, lower_bound, upper_bound));
682  }
683 
684  // If at least one column was merged, the target column must be shifted like
685  // the other columns in the same equivalence class for the same reason (see
686  // above).
687  if (num_merged > 0) {
688  merged_columns_[target_col] = target_col;
689  const Fractional target_value = MinInMagnitudeOrZeroIfInfinite(
690  lower_bounds_[target_col], upper_bounds_[target_col]);
691  lp->SetVariableBounds(
692  target_col, lp->variable_lower_bounds()[target_col] - target_value,
693  lp->variable_upper_bounds()[target_col] - target_value);
694  SubtractColumnMultipleFromConstraintBound(target_col, target_value, lp);
695  new_lower_bounds_[target_col] = lp->variable_lower_bounds()[target_col];
696  new_upper_bounds_[target_col] = lp->variable_upper_bounds()[target_col];
697  }
698  }
699 
700  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
701  return !column_deletion_helper_.IsEmpty();
702 }
703 
705  ProblemSolution* solution) const {
707  RETURN_IF_NULL(solution);
708  column_deletion_helper_.RestoreDeletedColumns(solution);
709 
710  // The rest of this function is to unmerge the columns so that the solution be
711  // primal-feasible.
712  const ColIndex num_cols = merged_columns_.size();
713  DenseBooleanRow is_representative_basic(num_cols, false);
714  DenseBooleanRow is_distance_to_upper_bound(num_cols, false);
715  DenseRow distance_to_bound(num_cols, 0.0);
716  DenseRow wanted_value(num_cols, 0.0);
717 
718  // The first pass is a loop over the representatives to compute the current
719  // distance to the new bounds.
720  for (ColIndex col(0); col < num_cols; ++col) {
721  if (merged_columns_[col] == col) {
722  const Fractional value = solution->primal_values[col];
723  const Fractional distance_to_upper_bound = new_upper_bounds_[col] - value;
724  const Fractional distance_to_lower_bound = value - new_lower_bounds_[col];
725  if (distance_to_upper_bound < distance_to_lower_bound) {
726  distance_to_bound[col] = distance_to_upper_bound;
727  is_distance_to_upper_bound[col] = true;
728  } else {
729  distance_to_bound[col] = distance_to_lower_bound;
730  is_distance_to_upper_bound[col] = false;
731  }
732  is_representative_basic[col] =
734 
735  // Restore the representative value to a feasible value of the initial
736  // variable. Now all the merged variable are at a feasible value.
737  wanted_value[col] = value;
738  solution->primal_values[col] = MinInMagnitudeOrZeroIfInfinite(
739  lower_bounds_[col], upper_bounds_[col]);
740  solution->variable_statuses[col] = ComputeVariableStatus(
741  solution->primal_values[col], lower_bounds_[col], upper_bounds_[col]);
742  }
743  }
744 
745  // Second pass to correct the values.
746  for (ColIndex col(0); col < num_cols; ++col) {
747  const ColIndex representative = merged_columns_[col];
748  if (representative != kInvalidCol) {
749  if (IsFinite(distance_to_bound[representative])) {
750  // If the distance is finite, then each variable is set to its
751  // corresponding bound (the one from which the distance is computed) and
752  // is then changed by as much as possible until the distance is zero.
753  const Fractional bound_factor =
754  column_factors_[col] / column_factors_[representative];
755  const Fractional scaled_distance =
756  distance_to_bound[representative] / std::abs(bound_factor);
757  const Fractional width = upper_bounds_[col] - lower_bounds_[col];
758  const bool to_upper_bound =
759  (bound_factor > 0.0) == is_distance_to_upper_bound[representative];
760  if (width <= scaled_distance) {
761  solution->primal_values[col] =
762  to_upper_bound ? lower_bounds_[col] : upper_bounds_[col];
763  solution->variable_statuses[col] =
764  ComputeVariableStatus(solution->primal_values[col],
765  lower_bounds_[col], upper_bounds_[col]);
766  distance_to_bound[representative] -= width * std::abs(bound_factor);
767  } else {
768  solution->primal_values[col] =
769  to_upper_bound ? upper_bounds_[col] - scaled_distance
770  : lower_bounds_[col] + scaled_distance;
771  solution->variable_statuses[col] =
772  is_representative_basic[representative]
774  : ComputeVariableStatus(solution->primal_values[col],
775  lower_bounds_[col],
776  upper_bounds_[col]);
777  distance_to_bound[representative] = 0.0;
778  is_representative_basic[representative] = false;
779  }
780  } else {
781  // If the distance is not finite, then only one variable needs to be
782  // changed from its current feasible value in order to have a
783  // primal-feasible solution.
784  const Fractional error = wanted_value[representative];
785  if (error == 0.0) {
786  if (is_representative_basic[representative]) {
788  is_representative_basic[representative] = false;
789  }
790  } else {
791  const Fractional bound_factor =
792  column_factors_[col] / column_factors_[representative];
793  const bool use_this_variable =
794  (error * bound_factor > 0.0) ? (upper_bounds_[col] == kInfinity)
795  : (lower_bounds_[col] == -kInfinity);
796  if (use_this_variable) {
797  wanted_value[representative] = 0.0;
798  solution->primal_values[col] += error / bound_factor;
799  if (is_representative_basic[representative]) {
801  is_representative_basic[representative] = false;
802  } else {
803  // This should not happen on an OPTIMAL or FEASIBLE solution.
804  DCHECK(solution->status != ProblemStatus::OPTIMAL &&
807  }
808  }
809  }
810  }
811  }
812  }
813 }
814 
815 // --------------------------------------------------------
816 // ProportionalRowPreprocessor
817 // --------------------------------------------------------
818 
821  RETURN_VALUE_IF_NULL(lp, false);
822  const RowIndex num_rows = lp->num_constraints();
823  const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
824 
825  // Use the first coefficient of each row to compute the proportionality
826  // factor. Note that the sign is important.
827  //
828  // Note(user): using the first coefficient may not give the best precision.
829  row_factors_.assign(num_rows, 0.0);
830  for (RowIndex row(0); row < num_rows; ++row) {
831  const SparseColumn& row_transpose = transpose.column(RowToColIndex(row));
832  if (!row_transpose.IsEmpty()) {
833  row_factors_[row] = row_transpose.GetFirstCoefficient();
834  }
835  }
836 
837  // The new row bounds (only meaningful for the proportional rows).
838  DenseColumn lower_bounds(num_rows, -kInfinity);
839  DenseColumn upper_bounds(num_rows, +kInfinity);
840 
841  // Where the new bounds are coming from. Only for the constraints that stay
842  // in the lp and are modified, kInvalidRow otherwise.
843  upper_bound_sources_.assign(num_rows, kInvalidRow);
844  lower_bound_sources_.assign(num_rows, kInvalidRow);
845 
846  // Initialization.
847  // We need the first representative of each proportional row class to point to
848  // itself for the loop below. TODO(user): Already return such a mapping from
849  // FindProportionalColumns()?
851  transpose, parameters_.preprocessor_zero_tolerance());
852  DenseBooleanColumn is_a_representative(num_rows, false);
853  int num_proportional_rows = 0;
854  for (RowIndex row(0); row < num_rows; ++row) {
855  const ColIndex representative_row_as_col = mapping[RowToColIndex(row)];
856  if (representative_row_as_col != kInvalidCol) {
857  mapping[representative_row_as_col] = representative_row_as_col;
858  is_a_representative[ColToRowIndex(representative_row_as_col)] = true;
859  ++num_proportional_rows;
860  }
861  }
862 
863  // Compute the bound of each representative as implied by the rows
864  // which are proportional to it. Also keep the source row of each bound.
865  for (RowIndex row(0); row < num_rows; ++row) {
866  const ColIndex row_as_col = RowToColIndex(row);
867  if (mapping[row_as_col] != kInvalidCol) {
868  // For now, delete all the rows that are proportional to another one.
869  // Note that we will unmark the final representative of this class later.
870  row_deletion_helper_.MarkRowForDeletion(row);
871  const RowIndex representative_row = ColToRowIndex(mapping[row_as_col]);
872 
873  const Fractional factor =
874  row_factors_[representative_row] / row_factors_[row];
875  Fractional implied_lb = factor * lp->constraint_lower_bounds()[row];
876  Fractional implied_ub = factor * lp->constraint_upper_bounds()[row];
877  if (factor < 0.0) {
878  std::swap(implied_lb, implied_ub);
879  }
880 
881  // TODO(user): if the bounds are equal, use the largest row in magnitude?
882  if (implied_lb >= lower_bounds[representative_row]) {
883  lower_bounds[representative_row] = implied_lb;
884  lower_bound_sources_[representative_row] = row;
885  }
886  if (implied_ub <= upper_bounds[representative_row]) {
887  upper_bounds[representative_row] = implied_ub;
888  upper_bound_sources_[representative_row] = row;
889  }
890  }
891  }
892 
893  // For maximum precision, and also to simplify the postsolve, we choose
894  // a representative for each class of proportional columns that has at least
895  // one of the two tightest bounds.
896  for (RowIndex row(0); row < num_rows; ++row) {
897  if (!is_a_representative[row]) continue;
898  const RowIndex lower_source = lower_bound_sources_[row];
899  const RowIndex upper_source = upper_bound_sources_[row];
900  lower_bound_sources_[row] = kInvalidRow;
901  upper_bound_sources_[row] = kInvalidRow;
902  DCHECK_NE(lower_source, kInvalidRow);
903  DCHECK_NE(upper_source, kInvalidRow);
904  if (lower_source == upper_source) {
905  // In this case, a simple change of representative is enough.
906  // The constraint bounds of the representative will not change.
907  DCHECK_NE(lower_source, kInvalidRow);
908  row_deletion_helper_.UnmarkRow(lower_source);
909  } else {
910  // Report ProblemStatus::PRIMAL_INFEASIBLE if the new lower bound is not
911  // lower than the new upper bound modulo the default tolerance.
913  upper_bounds[row])) {
915  return false;
916  }
917 
918  // Special case for fixed rows.
919  if (lp->constraint_lower_bounds()[lower_source] ==
920  lp->constraint_upper_bounds()[lower_source]) {
921  row_deletion_helper_.UnmarkRow(lower_source);
922  continue;
923  }
924  if (lp->constraint_lower_bounds()[upper_source] ==
925  lp->constraint_upper_bounds()[upper_source]) {
926  row_deletion_helper_.UnmarkRow(upper_source);
927  continue;
928  }
929 
930  // This is the only case where a more complex postsolve is needed.
931  // To maximize precision, the class representative is changed to either
932  // upper_source or lower_source depending of which row has the largest
933  // proportionality factor.
934  RowIndex new_representative = lower_source;
935  RowIndex other = upper_source;
936  if (std::abs(row_factors_[new_representative]) <
937  std::abs(row_factors_[other])) {
938  std::swap(new_representative, other);
939  }
940 
941  // Initialize the new bounds with the implied ones.
942  const Fractional factor =
943  row_factors_[new_representative] / row_factors_[other];
944  Fractional new_lb = factor * lp->constraint_lower_bounds()[other];
945  Fractional new_ub = factor * lp->constraint_upper_bounds()[other];
946  if (factor < 0.0) {
947  std::swap(new_lb, new_ub);
948  }
949 
950  lower_bound_sources_[new_representative] = new_representative;
951  upper_bound_sources_[new_representative] = new_representative;
952 
953  if (new_lb > lp->constraint_lower_bounds()[new_representative]) {
954  lower_bound_sources_[new_representative] = other;
955  } else {
956  new_lb = lp->constraint_lower_bounds()[new_representative];
957  }
958  if (new_ub < lp->constraint_upper_bounds()[new_representative]) {
959  upper_bound_sources_[new_representative] = other;
960  } else {
961  new_ub = lp->constraint_upper_bounds()[new_representative];
962  }
963  const RowIndex new_lower_source =
964  lower_bound_sources_[new_representative];
965  if (new_lower_source == upper_bound_sources_[new_representative]) {
966  row_deletion_helper_.UnmarkRow(new_lower_source);
967  lower_bound_sources_[new_representative] = kInvalidRow;
968  upper_bound_sources_[new_representative] = kInvalidRow;
969  continue;
970  }
971 
972  // Take care of small numerical imprecision by making sure that lb <= ub.
973  // Note that if the imprecision was greater than the tolerance, the code
974  // at the beginning of this block would have reported
975  // ProblemStatus::PRIMAL_INFEASIBLE.
977  if (new_lb > new_ub) {
978  if (lower_bound_sources_[new_representative] == new_representative) {
979  new_ub = lp->constraint_lower_bounds()[new_representative];
980  } else {
981  new_lb = lp->constraint_upper_bounds()[new_representative];
982  }
983  }
984  row_deletion_helper_.UnmarkRow(new_representative);
985  lp->SetConstraintBounds(new_representative, new_lb, new_ub);
986  }
987  }
988 
989  lp_is_maximization_problem_ = lp->IsMaximizationProblem();
990  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
991  return !row_deletion_helper_.IsEmpty();
992 }
993 
995  ProblemSolution* solution) const {
997  RETURN_IF_NULL(solution);
998  row_deletion_helper_.RestoreDeletedRows(solution);
999 
1000  // Make sure that all non-zero dual values on the proportional rows are
1001  // assigned to the correct row with the correct sign and that the statuses
1002  // are correct.
1003  const RowIndex num_rows = solution->dual_values.size();
1004  for (RowIndex row(0); row < num_rows; ++row) {
1005  const RowIndex lower_source = lower_bound_sources_[row];
1006  const RowIndex upper_source = upper_bound_sources_[row];
1007  if (lower_source == kInvalidRow && upper_source == kInvalidRow) continue;
1008  DCHECK_NE(lower_source, upper_source);
1009  DCHECK(lower_source == row || upper_source == row);
1010 
1011  // If the representative is ConstraintStatus::BASIC, then all rows in this
1012  // class will be ConstraintStatus::BASIC and there is nothing to do.
1014  if (status == ConstraintStatus::BASIC) continue;
1015 
1016  // If the row is FIXED it will behave as a row
1017  // ConstraintStatus::AT_UPPER_BOUND or
1018  // ConstraintStatus::AT_LOWER_BOUND depending on the corresponding dual
1019  // variable sign.
1021  const Fractional corrected_dual_value = lp_is_maximization_problem_
1022  ? -solution->dual_values[row]
1023  : solution->dual_values[row];
1024  if (corrected_dual_value != 0.0) {
1025  status = corrected_dual_value > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1027  }
1028  }
1029 
1030  // If one of the two conditions below are true, set the row status to
1031  // ConstraintStatus::BASIC.
1032  // Note that the source which is not row can't be FIXED (see presolve).
1033  if (lower_source != row && status == ConstraintStatus::AT_LOWER_BOUND) {
1034  DCHECK_EQ(0.0, solution->dual_values[lower_source]);
1035  const Fractional factor = row_factors_[row] / row_factors_[lower_source];
1036  solution->dual_values[lower_source] = factor * solution->dual_values[row];
1037  solution->dual_values[row] = 0.0;
1039  solution->constraint_statuses[lower_source] =
1040  factor > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1042  }
1043  if (upper_source != row && status == ConstraintStatus::AT_UPPER_BOUND) {
1044  DCHECK_EQ(0.0, solution->dual_values[upper_source]);
1045  const Fractional factor = row_factors_[row] / row_factors_[upper_source];
1046  solution->dual_values[upper_source] = factor * solution->dual_values[row];
1047  solution->dual_values[row] = 0.0;
1049  solution->constraint_statuses[upper_source] =
1050  factor > 0.0 ? ConstraintStatus::AT_UPPER_BOUND
1052  }
1053 
1054  // If the row status is still ConstraintStatus::FIXED_VALUE, we need to
1055  // relax its status.
1057  solution->constraint_statuses[row] =
1058  lower_source != row ? ConstraintStatus::AT_UPPER_BOUND
1060  }
1061  }
1062 }
1063 
1064 // --------------------------------------------------------
1065 // FixedVariablePreprocessor
1066 // --------------------------------------------------------
1067 
1070  RETURN_VALUE_IF_NULL(lp, false);
1071  const ColIndex num_cols = lp->num_variables();
1072  for (ColIndex col(0); col < num_cols; ++col) {
1075  if (lower_bound == upper_bound) {
1076  const Fractional fixed_value = lower_bound;
1077  DCHECK(IsFinite(fixed_value));
1078 
1079  // We need to change the constraint bounds.
1080  SubtractColumnMultipleFromConstraintBound(col, fixed_value, lp);
1081  column_deletion_helper_.MarkColumnForDeletionWithState(
1082  col, fixed_value, VariableStatus::FIXED_VALUE);
1083  }
1084  }
1085 
1086  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1087  return !column_deletion_helper_.IsEmpty();
1088 }
1089 
1091  ProblemSolution* solution) const {
1093  RETURN_IF_NULL(solution);
1094  column_deletion_helper_.RestoreDeletedColumns(solution);
1095 }
1096 
1097 // --------------------------------------------------------
1098 // ForcingAndImpliedFreeConstraintPreprocessor
1099 // --------------------------------------------------------
1100 
1103  RETURN_VALUE_IF_NULL(lp, false);
1104  const RowIndex num_rows = lp->num_constraints();
1105 
1106  // Compute the implied constraint bounds from the variable bounds.
1107  DenseColumn implied_lower_bounds(num_rows, 0);
1108  DenseColumn implied_upper_bounds(num_rows, 0);
1109  const ColIndex num_cols = lp->num_variables();
1110  StrictITIVector<RowIndex, int> row_degree(num_rows, 0);
1111  for (ColIndex col(0); col < num_cols; ++col) {
1112  const Fractional lower = lp->variable_lower_bounds()[col];
1113  const Fractional upper = lp->variable_upper_bounds()[col];
1114  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1115  const RowIndex row = e.row();
1116  const Fractional coeff = e.coefficient();
1117  if (coeff > 0.0) {
1118  implied_lower_bounds[row] += lower * coeff;
1119  implied_upper_bounds[row] += upper * coeff;
1120  } else {
1121  implied_lower_bounds[row] += upper * coeff;
1122  implied_upper_bounds[row] += lower * coeff;
1123  }
1124  ++row_degree[row];
1125  }
1126  }
1127 
1128  // Note that the ScalingPreprocessor is currently executed last, so here the
1129  // problem has not been scaled yet.
1130  int num_implied_free_constraints = 0;
1131  int num_forcing_constraints = 0;
1132  is_forcing_up_.assign(num_rows, false);
1133  DenseBooleanColumn is_forcing_down(num_rows, false);
1134  for (RowIndex row(0); row < num_rows; ++row) {
1135  if (row_degree[row] == 0) continue;
1136  Fractional lower = lp->constraint_lower_bounds()[row];
1137  Fractional upper = lp->constraint_upper_bounds()[row];
1138 
1139  // Check for infeasibility.
1141  implied_upper_bounds[row]) ||
1142  !IsSmallerWithinFeasibilityTolerance(implied_lower_bounds[row],
1143  upper)) {
1144  VLOG(1) << "implied bound " << implied_lower_bounds[row] << " "
1145  << implied_upper_bounds[row];
1146  VLOG(1) << "constraint bound " << lower << " " << upper;
1148  return false;
1149  }
1150 
1151  // Check if the constraint is forcing. That is, all the variables that
1152  // appear in it must be at one of their bounds.
1153  if (IsSmallerWithinPreprocessorZeroTolerance(implied_upper_bounds[row],
1154  lower)) {
1155  is_forcing_down[row] = true;
1156  ++num_forcing_constraints;
1157  continue;
1158  }
1160  implied_lower_bounds[row])) {
1161  is_forcing_up_[row] = true;
1162  ++num_forcing_constraints;
1163  continue;
1164  }
1165 
1166  // We relax the constraint bounds only if the constraint is implied to be
1167  // free. Such constraints will later be deleted by the
1168  // FreeConstraintPreprocessor.
1169  //
1170  // Note that we could relax only one of the two bounds, but the impact this
1171  // would have on the revised simplex algorithm is unclear at this point.
1173  implied_lower_bounds[row]) &&
1174  IsSmallerWithinPreprocessorZeroTolerance(implied_upper_bounds[row],
1175  upper)) {
1177  ++num_implied_free_constraints;
1178  }
1179  }
1180 
1181  if (num_implied_free_constraints > 0) {
1182  VLOG(1) << num_implied_free_constraints << " implied free constraints.";
1183  }
1184 
1185  if (num_forcing_constraints > 0) {
1186  VLOG(1) << num_forcing_constraints << " forcing constraints.";
1187  lp_is_maximization_problem_ = lp->IsMaximizationProblem();
1188  costs_.resize(num_cols, 0.0);
1189  for (ColIndex col(0); col < num_cols; ++col) {
1190  const SparseColumn& column = lp->GetSparseColumn(col);
1191  const Fractional lower = lp->variable_lower_bounds()[col];
1192  const Fractional upper = lp->variable_upper_bounds()[col];
1193  bool is_forced = false;
1194  Fractional target_bound = 0.0;
1195  for (const SparseColumn::Entry e : column) {
1196  if (is_forcing_down[e.row()]) {
1197  const Fractional candidate = e.coefficient() < 0.0 ? lower : upper;
1198  if (is_forced && candidate != target_bound) {
1199  // The bounds are really close, so we fix to the bound with
1200  // the lowest magnitude. As of 2019/11/19, this is "better" than
1201  // fixing to the mid-point, because at postsolve, we always put
1202  // non-basic variables to their exact bounds (so, with mid-point
1203  // there would be a difference of epsilon/2 between the inner
1204  // solution and the postsolved one, which might cause issues).
1205  if (IsSmallerWithinPreprocessorZeroTolerance(upper, lower)) {
1206  target_bound = std::abs(lower) < std::abs(upper) ? lower : upper;
1207  continue;
1208  }
1209  VLOG(1) << "A variable is forced in both directions! bounds: ["
1210  << std::fixed << std::setprecision(10) << lower << ", "
1211  << upper << "]. coeff:" << e.coefficient();
1213  return false;
1214  }
1215  target_bound = candidate;
1216  is_forced = true;
1217  }
1218  if (is_forcing_up_[e.row()]) {
1219  const Fractional candidate = e.coefficient() < 0.0 ? upper : lower;
1220  if (is_forced && candidate != target_bound) {
1221  // The bounds are really close, so we fix to the bound with
1222  // the lowest magnitude.
1223  if (IsSmallerWithinPreprocessorZeroTolerance(upper, lower)) {
1224  target_bound = std::abs(lower) < std::abs(upper) ? lower : upper;
1225  continue;
1226  }
1227  VLOG(1) << "A variable is forced in both directions! bounds: ["
1228  << std::fixed << std::setprecision(10) << lower << ", "
1229  << upper << "]. coeff:" << e.coefficient();
1231  return false;
1232  }
1233  target_bound = candidate;
1234  is_forced = true;
1235  }
1236  }
1237  if (is_forced) {
1238  // Fix the variable, update the constraint bounds and save this column
1239  // and its cost for the postsolve.
1240  SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
1241  column_deletion_helper_.MarkColumnForDeletionWithState(
1242  col, target_bound,
1243  ComputeVariableStatus(target_bound, lower, upper));
1244  columns_saver_.SaveColumn(col, column);
1245  costs_[col] = lp->objective_coefficients()[col];
1246  }
1247  }
1248  for (RowIndex row(0); row < num_rows; ++row) {
1249  // In theory, an M exists such that for any magnitude >= M, we will be at
1250  // an optimal solution. However, because of numerical errors, if the value
1251  // is too large, it causes problem when verifying the solution. So we
1252  // select the smallest such M (at least a resonably small one) during
1253  // postsolve. It is the reason why we need to store the columns that were
1254  // fixed.
1255  if (is_forcing_down[row] || is_forcing_up_[row]) {
1256  row_deletion_helper_.MarkRowForDeletion(row);
1257  }
1258  }
1259  }
1260 
1261  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1262  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1263  return !column_deletion_helper_.IsEmpty();
1264 }
1265 
1267  ProblemSolution* solution) const {
1269  RETURN_IF_NULL(solution);
1270  column_deletion_helper_.RestoreDeletedColumns(solution);
1271  row_deletion_helper_.RestoreDeletedRows(solution);
1272 
1273  struct DeletionEntry {
1274  RowIndex row;
1275  ColIndex col;
1277  };
1278  std::vector<DeletionEntry> entries;
1279 
1280  // Compute for each deleted columns the last deleted row in which it appears.
1281  const ColIndex size = column_deletion_helper_.GetMarkedColumns().size();
1282  for (ColIndex col(0); col < size; ++col) {
1283  if (!column_deletion_helper_.IsColumnMarked(col)) continue;
1284 
1285  RowIndex last_row = kInvalidRow;
1286  Fractional last_coefficient;
1287  for (const SparseColumn::Entry e : columns_saver_.SavedColumn(col)) {
1288  const RowIndex row = e.row();
1289  if (row_deletion_helper_.IsRowMarked(row)) {
1290  last_row = row;
1291  last_coefficient = e.coefficient();
1292  }
1293  }
1294  if (last_row != kInvalidRow) {
1295  entries.push_back({last_row, col, last_coefficient});
1296  }
1297  }
1298 
1299  // Sort by row first and then col.
1300  std::sort(entries.begin(), entries.end(),
1301  [](const DeletionEntry& a, const DeletionEntry& b) {
1302  if (a.row == b.row) return a.col < b.col;
1303  return a.row < b.row;
1304  });
1305 
1306  // For each deleted row (in order), compute a bound on the dual values so
1307  // that all the deleted columns for which this row is the last deleted row are
1308  // dual-feasible. Note that for the other columns, it will always be possible
1309  // to make them dual-feasible with a later row.
1310  // There are two possible outcomes:
1311  // - The dual value stays 0.0, and nothing changes.
1312  // - The bounds enforce a non-zero dual value, and one column will have a
1313  // reduced cost of 0.0. This column becomes VariableStatus::BASIC, and the
1314  // constraint status is changed to ConstraintStatus::AT_LOWER_BOUND,
1315  // ConstraintStatus::AT_UPPER_BOUND or ConstraintStatus::FIXED_VALUE.
1316  for (int i = 0; i < entries.size();) {
1317  const RowIndex row = entries[i].row;
1318  DCHECK(row_deletion_helper_.IsRowMarked(row));
1319 
1320  // Process column with this last deleted row.
1321  Fractional new_dual_value = 0.0;
1322  ColIndex new_basic_column = kInvalidCol;
1323  for (; i < entries.size(); ++i) {
1324  if (entries[i].row != row) break;
1325  const ColIndex col = entries[i].col;
1326 
1327  const Fractional scalar_product =
1328  ScalarProduct(solution->dual_values, columns_saver_.SavedColumn(col));
1329  const Fractional reduced_cost = costs_[col] - scalar_product;
1330  const Fractional bound = reduced_cost / entries[i].coefficient;
1331  if (is_forcing_up_[row] == !lp_is_maximization_problem_) {
1332  if (bound < new_dual_value) {
1333  new_dual_value = bound;
1334  new_basic_column = col;
1335  }
1336  } else {
1337  if (bound > new_dual_value) {
1338  new_dual_value = bound;
1339  new_basic_column = col;
1340  }
1341  }
1342  }
1343  if (new_basic_column != kInvalidCol) {
1344  solution->dual_values[row] = new_dual_value;
1345  solution->variable_statuses[new_basic_column] = VariableStatus::BASIC;
1346  solution->constraint_statuses[row] =
1347  is_forcing_up_[row] ? ConstraintStatus::AT_UPPER_BOUND
1349  }
1350  }
1351 }
1352 
1353 // --------------------------------------------------------
1354 // ImpliedFreePreprocessor
1355 // --------------------------------------------------------
1356 
1357 namespace {
1358 struct ColWithDegree {
1359  ColIndex col;
1360  EntryIndex num_entries;
1361  ColWithDegree(ColIndex c, EntryIndex n) : col(c), num_entries(n) {}
1362  bool operator<(const ColWithDegree& other) const {
1363  if (num_entries == other.num_entries) {
1364  return col < other.col;
1365  }
1366  return num_entries < other.num_entries;
1367  }
1368 };
1369 } // namespace
1370 
1373  RETURN_VALUE_IF_NULL(lp, false);
1374  const RowIndex num_rows = lp->num_constraints();
1375  const ColIndex num_cols = lp->num_variables();
1376 
1377  // For each constraint with n entries and each of its variable, we want the
1378  // bounds implied by the (n - 1) other variables and the constraint. We
1379  // use two handy utility classes that allow us to do that efficiently while
1380  // dealing properly with infinite bounds.
1381  const int size = num_rows.value();
1382  // TODO(user) : Replace SumWithNegativeInfiniteAndOneMissing and
1383  // SumWithPositiveInfiniteAndOneMissing with IntervalSumWithOneMissing.
1385  size);
1387  size);
1388 
1389  // Initialize the sums by adding all the bounds of the variables.
1390  for (ColIndex col(0); col < num_cols; ++col) {
1393  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1394  Fractional entry_lb = e.coefficient() * lower_bound;
1395  Fractional entry_ub = e.coefficient() * upper_bound;
1396  if (e.coefficient() < 0.0) std::swap(entry_lb, entry_ub);
1397  lb_sums[e.row()].Add(entry_lb);
1398  ub_sums[e.row()].Add(entry_ub);
1399  }
1400  }
1401 
1402  // The inequality
1403  // constraint_lb <= sum(entries) <= constraint_ub
1404  // can be rewritten as:
1405  // sum(entries) + (-activity) = 0,
1406  // where (-activity) has bounds [-constraint_ub, -constraint_lb].
1407  // We use this latter convention to simplify our code.
1408  for (RowIndex row(0); row < num_rows; ++row) {
1409  lb_sums[row].Add(-lp->constraint_upper_bounds()[row]);
1410  ub_sums[row].Add(-lp->constraint_lower_bounds()[row]);
1411  }
1412 
1413  // Once a variable is freed, none of the rows in which it appears can be
1414  // used to make another variable free.
1415  DenseBooleanColumn used_rows(num_rows, false);
1416  postsolve_status_of_free_variables_.assign(num_cols, VariableStatus::FREE);
1417  variable_offsets_.assign(num_cols, 0.0);
1418 
1419  // It is better to process columns with a small degree first:
1420  // - Degree-two columns make it possible to remove a row from the problem.
1421  // - This way there is more chance to make more free columns.
1422  // - It is better to have low degree free columns since a free column will
1423  // always end up in the simplex basis (except if there is more than the
1424  // number of rows in the problem).
1425  //
1426  // TODO(user): Only process degree-two so in subsequent passes more degree-two
1427  // columns could be made free. And only when no other reduction can be
1428  // applied, process the higher degree column?
1429  //
1430  // TODO(user): Be smarter about the order that maximizes the number of free
1431  // column. For instance if we have 3 doubleton columns that use the rows (1,2)
1432  // (2,3) and (3,4) then it is better not to make (2,3) free so the two other
1433  // two can be made free.
1434  std::vector<ColWithDegree> col_by_degree;
1435  for (ColIndex col(0); col < num_cols; ++col) {
1436  col_by_degree.push_back(
1437  ColWithDegree(col, lp->GetSparseColumn(col).num_entries()));
1438  }
1439  std::sort(col_by_degree.begin(), col_by_degree.end());
1440 
1441  // Now loop over the columns in order and make all implied-free columns free.
1442  int num_already_free_variables = 0;
1443  int num_implied_free_variables = 0;
1444  int num_fixed_variables = 0;
1445  for (ColWithDegree col_with_degree : col_by_degree) {
1446  const ColIndex col = col_with_degree.col;
1447 
1448  // If the variable is alreay free or fixed, we do nothing.
1452  ++num_already_free_variables;
1453  continue;
1454  }
1455  if (lower_bound == upper_bound) continue;
1456 
1457  // Detect if the variable is implied free.
1458  Fractional overall_implied_lb = -kInfinity;
1459  Fractional overall_implied_ub = kInfinity;
1460  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1461  // If the row contains another implied free variable, then the bounds
1462  // implied by it will just be [-kInfinity, kInfinity] so we can skip it.
1463  if (used_rows[e.row()]) continue;
1464 
1465  // This is the contribution of this column to the sum above.
1466  const Fractional coeff = e.coefficient();
1467  Fractional entry_lb = coeff * lower_bound;
1468  Fractional entry_ub = coeff * upper_bound;
1469  if (coeff < 0.0) std::swap(entry_lb, entry_ub);
1470 
1471  // If X is the variable with index col and Y the sum of all the other
1472  // variables and of (-activity), then coeff * X + Y = 0. Since Y's bounds
1473  // are [lb_sum without X, ub_sum without X], it is easy to derive the
1474  // implied bounds on X.
1475  //
1476  // Important: If entry_lb (resp. entry_ub) are large, we cannot have a
1477  // good precision on the sum without. So we do add a defensive tolerance
1478  // that depends on these magnitude.
1479  const Fractional implied_lb =
1480  coeff > 0.0 ? -ub_sums[e.row()].SumWithoutUb(entry_ub) / coeff
1481  : -lb_sums[e.row()].SumWithoutLb(entry_lb) / coeff;
1482  const Fractional implied_ub =
1483  coeff > 0.0 ? -lb_sums[e.row()].SumWithoutLb(entry_lb) / coeff
1484  : -ub_sums[e.row()].SumWithoutUb(entry_ub) / coeff;
1485 
1486  overall_implied_lb = std::max(overall_implied_lb, implied_lb);
1487  overall_implied_ub = std::min(overall_implied_ub, implied_ub);
1488  }
1489 
1490  // Detect infeasible cases.
1491  if (!IsSmallerWithinFeasibilityTolerance(overall_implied_lb, upper_bound) ||
1492  !IsSmallerWithinFeasibilityTolerance(lower_bound, overall_implied_ub) ||
1493  !IsSmallerWithinFeasibilityTolerance(overall_implied_lb,
1494  overall_implied_ub)) {
1496  return false;
1497  }
1498 
1499  // Detect fixed variable cases (there are two kinds).
1500  // Note that currently we don't do anything here except counting them.
1502  overall_implied_lb) ||
1503  IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1504  lower_bound)) {
1505  // This case is already dealt with by the
1506  // ForcingAndImpliedFreeConstraintPreprocessor since it means that (at
1507  // least) one of the row is forcing.
1508  ++num_fixed_variables;
1509  continue;
1510  } else if (IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1511  overall_implied_lb)) {
1512  // TODO(user): As of July 2013, with our preprocessors this case is never
1513  // triggered on the Netlib. Note however that if it appears it can have a
1514  // big impact since by fixing the variable, the two involved constraints
1515  // are forcing and can be removed too (with all the variables they touch).
1516  // The postsolve step is quite involved though.
1517  ++num_fixed_variables;
1518  continue;
1519  }
1520 
1521  // Is the variable implied free? Note that for an infinite lower_bound or
1522  // upper_bound the respective inequality is always true.
1524  overall_implied_lb) &&
1525  IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1526  upper_bound)) {
1527  ++num_implied_free_variables;
1529  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1530  used_rows[e.row()] = true;
1531  }
1532 
1533  // This is a tricky part. We're freeing this variable, which means that
1534  // after solve, the modified variable will have status either
1535  // VariableStatus::FREE or VariableStatus::BASIC. In the former case
1536  // (VariableStatus::FREE, value = 0.0), we need to "fix" the
1537  // status (technically, our variable isn't free!) to either
1538  // VariableStatus::AT_LOWER_BOUND or VariableStatus::AT_UPPER_BOUND
1539  // (note that we skipped fixed variables), and "fix" the value to that
1540  // bound's value as well. We make the decision and the precomputation
1541  // here: we simply offset the variable by one of its bounds, and store
1542  // which bound that was. Note that if the modified variable turns out to
1543  // be VariableStatus::BASIC, we'll simply un-offset its value too;
1544  // and let the status be VariableStatus::BASIC.
1545  //
1546  // TODO(user): This trick is already used in the DualizerPreprocessor,
1547  // maybe we should just have a preprocessor that shifts all the variables
1548  // bounds to have at least one of them at 0.0, will that improve precision
1549  // and speed of the simplex? One advantage is that we can compute the
1550  // new constraint bounds with better precision using AccurateSum.
1552  const Fractional offset =
1553  MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
1554  if (offset != 0.0) {
1555  variable_offsets_[col] = offset;
1556  SubtractColumnMultipleFromConstraintBound(col, offset, lp);
1557  }
1558  postsolve_status_of_free_variables_[col] =
1559  ComputeVariableStatus(offset, lower_bound, upper_bound);
1560  }
1561  }
1562  VLOG(1) << num_already_free_variables << " free variables in the problem.";
1563  VLOG(1) << num_implied_free_variables << " implied free columns.";
1564  VLOG(1) << num_fixed_variables << " variables can be fixed.";
1565 
1566  return num_implied_free_variables > 0;
1567 }
1568 
1571  RETURN_IF_NULL(solution);
1572  const ColIndex num_cols = solution->variable_statuses.size();
1573  for (ColIndex col(0); col < num_cols; ++col) {
1574  // Skip variables that the preprocessor didn't change.
1575  if (postsolve_status_of_free_variables_[col] == VariableStatus::FREE) {
1576  DCHECK_EQ(0.0, variable_offsets_[col]);
1577  continue;
1578  }
1579  if (solution->variable_statuses[col] == VariableStatus::FREE) {
1580  solution->variable_statuses[col] =
1581  postsolve_status_of_free_variables_[col];
1582  } else {
1584  }
1585  solution->primal_values[col] += variable_offsets_[col];
1586  }
1587 }
1588 
1589 // --------------------------------------------------------
1590 // DoubletonFreeColumnPreprocessor
1591 // --------------------------------------------------------
1592 
1595  RETURN_VALUE_IF_NULL(lp, false);
1596  // We will modify the matrix transpose and then push the change to the linear
1597  // program by calling lp->UseTransposeMatrixAsReference(). Note
1598  // that original_matrix will not change during this preprocessor run.
1599  const SparseMatrix& original_matrix = lp->GetSparseMatrix();
1600  SparseMatrix* transpose = lp->GetMutableTransposeSparseMatrix();
1601 
1602  const ColIndex num_cols(lp->num_variables());
1603  for (ColIndex doubleton_col(0); doubleton_col < num_cols; ++doubleton_col) {
1604  // Only consider doubleton free columns.
1605  if (original_matrix.column(doubleton_col).num_entries() != 2) continue;
1606  if (lp->variable_lower_bounds()[doubleton_col] != -kInfinity) continue;
1607  if (lp->variable_upper_bounds()[doubleton_col] != kInfinity) continue;
1608 
1609  // Collect the two column items. Note that we skip a column involving a
1610  // deleted row since it is no longer a doubleton then.
1611  RestoreInfo r;
1612  r.col = doubleton_col;
1613  r.objective_coefficient = lp->objective_coefficients()[r.col];
1614  int index = 0;
1615  for (const SparseColumn::Entry e : original_matrix.column(r.col)) {
1616  if (row_deletion_helper_.IsRowMarked(e.row())) break;
1617  r.row[index] = e.row();
1618  r.coeff[index] = e.coefficient();
1619  DCHECK_NE(0.0, e.coefficient());
1620  ++index;
1621  }
1622  if (index != NUM_ROWS) continue;
1623 
1624  // Since the column didn't touch any previously deleted row, we are sure
1625  // that the coefficients were left untouched.
1626  DCHECK_EQ(r.coeff[DELETED], transpose->column(RowToColIndex(r.row[DELETED]))
1627  .LookUpCoefficient(ColToRowIndex(r.col)));
1628  DCHECK_EQ(r.coeff[MODIFIED],
1629  transpose->column(RowToColIndex(r.row[MODIFIED]))
1630  .LookUpCoefficient(ColToRowIndex(r.col)));
1631 
1632  // We prefer deleting the row with the larger coefficient magnitude because
1633  // we will divide by this magnitude. TODO(user): Impact?
1634  if (std::abs(r.coeff[DELETED]) < std::abs(r.coeff[MODIFIED])) {
1635  std::swap(r.coeff[DELETED], r.coeff[MODIFIED]);
1636  std::swap(r.row[DELETED], r.row[MODIFIED]);
1637  }
1638 
1639  // Save the deleted row for postsolve. Note that we remove it from the
1640  // transpose at the same time. This last operation is not strictly needed,
1641  // but it is faster to do it this way (both here and later when we will take
1642  // the transpose of the final transpose matrix).
1643  r.deleted_row_as_column.Swap(
1644  transpose->mutable_column(RowToColIndex(r.row[DELETED])));
1645 
1646  // Move the bound of the deleted constraint to the initially free variable.
1647  {
1648  Fractional new_variable_lb =
1649  lp->constraint_lower_bounds()[r.row[DELETED]];
1650  Fractional new_variable_ub =
1651  lp->constraint_upper_bounds()[r.row[DELETED]];
1652  new_variable_lb /= r.coeff[DELETED];
1653  new_variable_ub /= r.coeff[DELETED];
1654  if (r.coeff[DELETED] < 0.0) std::swap(new_variable_lb, new_variable_ub);
1655  lp->SetVariableBounds(r.col, new_variable_lb, new_variable_ub);
1656  }
1657 
1658  // Add a multiple of the deleted row to the modified row except on
1659  // column r.col where the coefficient will be left unchanged.
1660  r.deleted_row_as_column.AddMultipleToSparseVectorAndIgnoreCommonIndex(
1661  -r.coeff[MODIFIED] / r.coeff[DELETED], ColToRowIndex(r.col),
1662  parameters_.drop_tolerance(),
1663  transpose->mutable_column(RowToColIndex(r.row[MODIFIED])));
1664 
1665  // We also need to correct the objective value of the variables involved in
1666  // the deleted row.
1667  if (r.objective_coefficient != 0.0) {
1668  for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1669  const ColIndex col = RowToColIndex(e.row());
1670  if (col == r.col) continue;
1671  const Fractional new_objective =
1672  lp->objective_coefficients()[col] -
1673  e.coefficient() * r.objective_coefficient / r.coeff[DELETED];
1674 
1675  // This detects if the objective should actually be zero, but because of
1676  // the numerical error in the formula above, we have a really low
1677  // objective instead. The logic is the same as in
1678  // AddMultipleToSparseVectorAndIgnoreCommonIndex().
1679  if (std::abs(new_objective) > parameters_.drop_tolerance()) {
1680  lp->SetObjectiveCoefficient(col, new_objective);
1681  } else {
1682  lp->SetObjectiveCoefficient(col, 0.0);
1683  }
1684  }
1685  }
1686  row_deletion_helper_.MarkRowForDeletion(r.row[DELETED]);
1687  restore_stack_.push_back(r);
1688  }
1689 
1690  if (!row_deletion_helper_.IsEmpty()) {
1691  // The order is important.
1693  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1694  return true;
1695  }
1696  return false;
1697 }
1698 
1700  ProblemSolution* solution) const {
1702  row_deletion_helper_.RestoreDeletedRows(solution);
1703  for (const RestoreInfo& r : Reverse(restore_stack_)) {
1704  // Correct the constraint status.
1705  switch (solution->variable_statuses[r.col]) {
1707  solution->constraint_statuses[r.row[DELETED]] =
1709  break;
1711  solution->constraint_statuses[r.row[DELETED]] =
1712  r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_UPPER_BOUND
1714  break;
1716  solution->constraint_statuses[r.row[DELETED]] =
1717  r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1719  break;
1720  case VariableStatus::FREE:
1721  solution->constraint_statuses[r.row[DELETED]] = ConstraintStatus::FREE;
1722  break;
1723  case VariableStatus::BASIC:
1724  // The default is good here:
1725  DCHECK_EQ(solution->constraint_statuses[r.row[DELETED]],
1727  break;
1728  }
1729 
1730  // Correct the primal variable value.
1731  {
1732  Fractional new_variable_value = solution->primal_values[r.col];
1733  for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1734  const ColIndex col = RowToColIndex(e.row());
1735  if (col == r.col) continue;
1736  new_variable_value -= (e.coefficient() / r.coeff[DELETED]) *
1737  solution->primal_values[RowToColIndex(e.row())];
1738  }
1739  solution->primal_values[r.col] = new_variable_value;
1740  }
1741 
1742  // In all cases, we will make the variable r.col VariableStatus::BASIC, so
1743  // we need to adjust the dual value of the deleted row so that the variable
1744  // reduced cost is zero. Note that there is nothing to do if the variable
1745  // was already basic.
1746  if (solution->variable_statuses[r.col] != VariableStatus::BASIC) {
1747  solution->variable_statuses[r.col] = VariableStatus::BASIC;
1748  Fractional current_reduced_cost =
1749  r.objective_coefficient -
1750  r.coeff[MODIFIED] * solution->dual_values[r.row[MODIFIED]];
1751  // We want current_reduced_cost - dual * coeff = 0, so:
1752  solution->dual_values[r.row[DELETED]] =
1753  current_reduced_cost / r.coeff[DELETED];
1754  } else {
1755  DCHECK_EQ(solution->dual_values[r.row[DELETED]], 0.0);
1756  }
1757  }
1758 }
1759 
1760 // --------------------------------------------------------
1761 // UnconstrainedVariablePreprocessor
1762 // --------------------------------------------------------
1763 
1764 namespace {
1765 
1766 // Does the constraint block the variable to go to infinity in the given
1767 // direction? direction is either positive or negative and row is the index of
1768 // the constraint.
1769 bool IsConstraintBlockingVariable(const LinearProgram& lp, Fractional direction,
1770  RowIndex row) {
1771  return direction > 0.0 ? lp.constraint_upper_bounds()[row] != kInfinity
1773 }
1774 
1775 } // namespace
1776 
1778  ColIndex col, Fractional target_bound, LinearProgram* lp) {
1779  DCHECK_EQ(0.0, lp->objective_coefficients()[col]);
1780  if (rhs_.empty()) {
1781  rhs_.resize(lp->num_constraints(), 0.0);
1782  activity_sign_correction_.resize(lp->num_constraints(), 1.0);
1783  is_unbounded_.resize(lp->num_variables(), false);
1784  }
1785  const bool is_unbounded_up = (target_bound == kInfinity);
1786  const SparseColumn& column = lp->GetSparseColumn(col);
1787  for (const SparseColumn::Entry e : column) {
1788  const RowIndex row = e.row();
1789  if (!row_deletion_helper_.IsRowMarked(row)) {
1790  row_deletion_helper_.MarkRowForDeletion(row);
1791  rows_saver_.SaveColumn(
1792  RowToColIndex(row),
1794  }
1795  const bool is_constraint_upper_bound_relevant =
1796  e.coefficient() > 0.0 ? !is_unbounded_up : is_unbounded_up;
1797  activity_sign_correction_[row] =
1798  is_constraint_upper_bound_relevant ? 1.0 : -1.0;
1799  rhs_[row] = is_constraint_upper_bound_relevant
1800  ? lp->constraint_upper_bounds()[row]
1801  : lp->constraint_lower_bounds()[row];
1802 
1803  // TODO(user): Here, we may render the row free, so subsequent columns
1804  // processed by the columns loop in Run() have more chance to be removed.
1805  // However, we need to be more careful during the postsolve() if we do that.
1806  }
1807  is_unbounded_[col] = true;
1808  Fractional initial_feasible_value = MinInMagnitudeOrZeroIfInfinite(
1810  column_deletion_helper_.MarkColumnForDeletionWithState(
1811  col, initial_feasible_value,
1812  ComputeVariableStatus(initial_feasible_value,
1813  lp->variable_lower_bounds()[col],
1814  lp->variable_upper_bounds()[col]));
1815 }
1816 
1819  RETURN_VALUE_IF_NULL(lp, false);
1820 
1821  // To simplify the problem if something is almost zero, we use the low
1822  // tolerance (1e-9 by default) to be defensive. But to detect an infeasibility
1823  // we want to be sure (especially since the problem is not scaled in the
1824  // presolver) so we use an higher tolerance.
1825  //
1826  // TODO(user): Expose it as a parameter. We could rename both to
1827  // preprocessor_low_tolerance and preprocessor_high_tolerance.
1828  const Fractional low_tolerance = parameters_.preprocessor_zero_tolerance();
1829  const Fractional high_tolerance = 1e-4;
1830 
1831  // We start by the dual variable bounds from the constraints.
1832  const RowIndex num_rows = lp->num_constraints();
1833  dual_lb_.assign(num_rows, -kInfinity);
1834  dual_ub_.assign(num_rows, kInfinity);
1835  for (RowIndex row(0); row < num_rows; ++row) {
1836  if (lp->constraint_lower_bounds()[row] == -kInfinity) {
1837  dual_ub_[row] = 0.0;
1838  }
1839  if (lp->constraint_upper_bounds()[row] == kInfinity) {
1840  dual_lb_[row] = 0.0;
1841  }
1842  }
1843 
1844  const ColIndex num_cols = lp->num_variables();
1845  may_have_participated_lb_.assign(num_cols, false);
1846  may_have_participated_ub_.assign(num_cols, false);
1847 
1848  // We maintain a queue of columns to process.
1849  std::deque<ColIndex> columns_to_process;
1850  DenseBooleanRow in_columns_to_process(num_cols, true);
1851  std::vector<RowIndex> changed_rows;
1852  for (ColIndex col(0); col < num_cols; ++col) {
1853  columns_to_process.push_back(col);
1854  }
1855 
1856  // Arbitrary limit to avoid corner cases with long loops.
1857  // TODO(user): expose this as a parameter? IMO it isn't really needed as we
1858  // shouldn't reach this limit except in corner cases.
1859  const int limit = 5 * num_cols.value();
1860  for (int count = 0; !columns_to_process.empty() && count < limit; ++count) {
1861  const ColIndex col = columns_to_process.front();
1862  columns_to_process.pop_front();
1863  in_columns_to_process[col] = false;
1864  if (column_deletion_helper_.IsColumnMarked(col)) continue;
1865 
1866  const SparseColumn& column = lp->GetSparseColumn(col);
1867  const Fractional col_cost =
1869  const Fractional col_lb = lp->variable_lower_bounds()[col];
1870  const Fractional col_ub = lp->variable_upper_bounds()[col];
1871 
1872  // Compute the bounds on the reduced costs of this column.
1875  rc_lb.Add(col_cost);
1876  rc_ub.Add(col_cost);
1877  for (const SparseColumn::Entry e : column) {
1878  if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1879  const Fractional coeff = e.coefficient();
1880  if (coeff > 0.0) {
1881  rc_lb.Add(-coeff * dual_ub_[e.row()]);
1882  rc_ub.Add(-coeff * dual_lb_[e.row()]);
1883  } else {
1884  rc_lb.Add(-coeff * dual_lb_[e.row()]);
1885  rc_ub.Add(-coeff * dual_ub_[e.row()]);
1886  }
1887  }
1888 
1889  // If the reduced cost domain do not contain zero (modulo the tolerance), we
1890  // can move the variable to its corresponding bound. Note that we need to be
1891  // careful that this variable didn't participate in creating the used
1892  // reduced cost bound in the first place.
1893  bool can_be_removed = false;
1895  bool rc_is_away_from_zero;
1896  if (rc_ub.Sum() <= low_tolerance) {
1897  can_be_removed = true;
1898  target_bound = col_ub;
1899  rc_is_away_from_zero = rc_ub.Sum() <= -high_tolerance;
1900  can_be_removed = !may_have_participated_ub_[col];
1901  }
1902  if (rc_lb.Sum() >= -low_tolerance) {
1903  // The second condition is here for the case we can choose one of the two
1904  // directions.
1905  if (!can_be_removed || !IsFinite(target_bound)) {
1906  can_be_removed = true;
1907  target_bound = col_lb;
1908  rc_is_away_from_zero = rc_lb.Sum() >= high_tolerance;
1909  can_be_removed = !may_have_participated_lb_[col];
1910  }
1911  }
1912 
1913  if (can_be_removed) {
1914  if (IsFinite(target_bound)) {
1915  // Note that in MIP context, this assumes that the bounds of an integer
1916  // variable are integer.
1917  column_deletion_helper_.MarkColumnForDeletionWithState(
1918  col, target_bound,
1919  ComputeVariableStatus(target_bound, col_lb, col_ub));
1920  continue;
1921  }
1922 
1923  // If the target bound is infinite and the reduced cost bound is non-zero,
1924  // then the problem is ProblemStatus::INFEASIBLE_OR_UNBOUNDED.
1925  if (rc_is_away_from_zero) {
1926  VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, variable " << col
1927  << " can move to " << target_bound
1928  << " and its reduced cost is in [" << rc_lb.Sum() << ", "
1929  << rc_ub.Sum() << "]";
1931  return false;
1932  } else {
1933  // We can remove this column and all its constraints! We just need to
1934  // choose proper variable values during the call to RecoverSolution()
1935  // that make all the constraints satisfiable. Unfortunately, this is not
1936  // so easy to do in the general case, so we only deal with a simpler
1937  // case when the cost of the variable is zero, and the constraints do
1938  // not block it in one direction.
1939  //
1940  // TODO(user): deal with the more generic case.
1941  if (col_cost != 0.0) continue;
1942  bool skip = false;
1943  for (const SparseColumn::Entry e : column) {
1944  // Note that it is important to check the rows that are already
1945  // deleted here, otherwise the post-solve will not work.
1946  if (IsConstraintBlockingVariable(*lp, e.coefficient(), e.row())) {
1947  skip = true;
1948  break;
1949  }
1950  }
1951  if (skip) continue;
1952 
1953  // TODO(user): this also works if the variable is integer, but we must
1954  // choose an integer value during the post-solve. Implement this.
1955  if (in_mip_context_) continue;
1957  continue;
1958  }
1959  }
1960 
1961  // The rest of the code will update the dual bounds. There is no need to do
1962  // it if the column was removed or if it is not unconstrained in some
1963  // direction.
1964  DCHECK(!can_be_removed);
1965  if (col_lb != -kInfinity && col_ub != kInfinity) continue;
1966 
1967  // For MIP, we only exploit the constraints. TODO(user): It should probably
1968  // work with only small modification, investigate.
1969  if (in_mip_context_) continue;
1970 
1971  changed_rows.clear();
1972  for (const SparseColumn::Entry e : column) {
1973  if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1974  const Fractional c = e.coefficient();
1975  const RowIndex row = e.row();
1976  if (col_ub == kInfinity) {
1977  if (c > 0.0) {
1978  const Fractional candidate =
1979  rc_ub.SumWithoutUb(-c * dual_lb_[row]) / c;
1980  if (candidate < dual_ub_[row]) {
1981  dual_ub_[row] = candidate;
1982  may_have_participated_lb_[col] = true;
1983  changed_rows.push_back(row);
1984  }
1985  } else {
1986  const Fractional candidate =
1987  rc_ub.SumWithoutUb(-c * dual_ub_[row]) / c;
1988  if (candidate > dual_lb_[row]) {
1989  dual_lb_[row] = candidate;
1990  may_have_participated_lb_[col] = true;
1991  changed_rows.push_back(row);
1992  }
1993  }
1994  }
1995  if (col_lb == -kInfinity) {
1996  if (c > 0.0) {
1997  const Fractional candidate =
1998  rc_lb.SumWithoutLb(-c * dual_ub_[row]) / c;
1999  if (candidate > dual_lb_[row]) {
2000  dual_lb_[row] = candidate;
2001  may_have_participated_ub_[col] = true;
2002  changed_rows.push_back(row);
2003  }
2004  } else {
2005  const Fractional candidate =
2006  rc_lb.SumWithoutLb(-c * dual_lb_[row]) / c;
2007  if (candidate < dual_ub_[row]) {
2008  dual_ub_[row] = candidate;
2009  may_have_participated_ub_[col] = true;
2010  changed_rows.push_back(row);
2011  }
2012  }
2013  }
2014  }
2015 
2016  if (!changed_rows.empty()) {
2017  const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2018  for (const RowIndex row : changed_rows) {
2019  for (const SparseColumn::Entry entry :
2020  transpose.column(RowToColIndex(row))) {
2021  const ColIndex col = RowToColIndex(entry.row());
2022  if (!in_columns_to_process[col]) {
2023  columns_to_process.push_back(col);
2024  in_columns_to_process[col] = true;
2025  }
2026  }
2027  }
2028  }
2029  }
2030 
2031  // Change the rhs to reflect the fixed variables. Note that is important to do
2032  // that after all the calls to RemoveZeroCostUnconstrainedVariable() because
2033  // RemoveZeroCostUnconstrainedVariable() needs to store the rhs before this
2034  // modification!
2035  const ColIndex end = column_deletion_helper_.GetMarkedColumns().size();
2036  for (ColIndex col(0); col < end; ++col) {
2037  if (column_deletion_helper_.IsColumnMarked(col)) {
2038  const Fractional target_bound =
2039  column_deletion_helper_.GetStoredValue()[col];
2040  SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
2041  }
2042  }
2043 
2044  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2045  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2046  return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2047 }
2048 
2050  ProblemSolution* solution) const {
2052  RETURN_IF_NULL(solution);
2053  column_deletion_helper_.RestoreDeletedColumns(solution);
2054  row_deletion_helper_.RestoreDeletedRows(solution);
2055 
2056  struct DeletionEntry {
2057  RowIndex row;
2058  ColIndex col;
2060  };
2061  std::vector<DeletionEntry> entries;
2062 
2063  // Compute the last deleted column index for each deleted rows.
2064  const RowIndex num_rows = solution->dual_values.size();
2065  RowToColMapping last_deleted_column(num_rows, kInvalidCol);
2066  for (RowIndex row(0); row < num_rows; ++row) {
2067  if (!row_deletion_helper_.IsRowMarked(row)) continue;
2068 
2069  ColIndex last_col = kInvalidCol;
2070  Fractional last_coefficient;
2071  for (const SparseColumn::Entry e :
2072  rows_saver_.SavedColumn(RowToColIndex(row))) {
2073  const ColIndex col = RowToColIndex(e.row());
2074  if (is_unbounded_[col]) {
2075  last_col = col;
2076  last_coefficient = e.coefficient();
2077  }
2078  }
2079  if (last_col != kInvalidCol) {
2080  entries.push_back({row, last_col, last_coefficient});
2081  }
2082  }
2083 
2084  // Sort by col first and then row.
2085  std::sort(entries.begin(), entries.end(),
2086  [](const DeletionEntry& a, const DeletionEntry& b) {
2087  if (a.col == b.col) return a.row < b.row;
2088  return a.col < b.col;
2089  });
2090 
2091  // Note that this will be empty if there were no deleted rows.
2092  for (int i = 0; i < entries.size();) {
2093  const ColIndex col = entries[i].col;
2094  CHECK(is_unbounded_[col]);
2095 
2096  Fractional primal_value_shift = 0.0;
2097  RowIndex row_at_bound = kInvalidRow;
2098  for (; i < entries.size(); ++i) {
2099  if (entries[i].col != col) break;
2100  const RowIndex row = entries[i].row;
2101 
2102  // This is for VariableStatus::FREE rows.
2103  //
2104  // TODO(user): In presense of free row, we must move them to 0.
2105  // Note that currently VariableStatus::FREE rows should be removed before
2106  // this is called.
2107  DCHECK(IsFinite(rhs_[row]));
2108  if (!IsFinite(rhs_[row])) continue;
2109 
2110  const SparseColumn& row_as_column =
2111  rows_saver_.SavedColumn(RowToColIndex(row));
2112  const Fractional activity =
2113  rhs_[row] - ScalarProduct(solution->primal_values, row_as_column);
2114 
2115  // activity and sign correction must have the same sign or be zero. If
2116  // not, we find the first unbounded variable and change it accordingly.
2117  // Note that by construction, the variable value will move towards its
2118  // unbounded direction.
2119  if (activity * activity_sign_correction_[row] < 0.0) {
2120  const Fractional bound = activity / entries[i].coefficient;
2121  if (std::abs(bound) > std::abs(primal_value_shift)) {
2122  primal_value_shift = bound;
2123  row_at_bound = row;
2124  }
2125  }
2126  }
2127  solution->primal_values[col] += primal_value_shift;
2128  if (row_at_bound != kInvalidRow) {
2130  solution->constraint_statuses[row_at_bound] =
2131  activity_sign_correction_[row_at_bound] == 1.0
2134  }
2135  }
2136 }
2137 
2138 // --------------------------------------------------------
2139 // FreeConstraintPreprocessor
2140 // --------------------------------------------------------
2141 
2144  RETURN_VALUE_IF_NULL(lp, false);
2145  const RowIndex num_rows = lp->num_constraints();
2146  for (RowIndex row(0); row < num_rows; ++row) {
2149  if (lower_bound == -kInfinity && upper_bound == kInfinity) {
2150  row_deletion_helper_.MarkRowForDeletion(row);
2151  }
2152  }
2153  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2154  return !row_deletion_helper_.IsEmpty();
2155 }
2156 
2158  ProblemSolution* solution) const {
2160  RETURN_IF_NULL(solution);
2161  row_deletion_helper_.RestoreDeletedRows(solution);
2162 }
2163 
2164 // --------------------------------------------------------
2165 // EmptyConstraintPreprocessor
2166 // --------------------------------------------------------
2167 
2170  RETURN_VALUE_IF_NULL(lp, false);
2171  const RowIndex num_rows(lp->num_constraints());
2172  const ColIndex num_cols(lp->num_variables());
2173 
2174  // Compute degree.
2175  StrictITIVector<RowIndex, int> degree(num_rows, 0);
2176  for (ColIndex col(0); col < num_cols; ++col) {
2177  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
2178  ++degree[e.row()];
2179  }
2180  }
2181 
2182  // Delete degree 0 rows.
2183  for (RowIndex row(0); row < num_rows; ++row) {
2184  if (degree[row] == 0) {
2185  // We need to check that 0.0 is allowed by the constraint bounds,
2186  // otherwise, the problem is ProblemStatus::PRIMAL_INFEASIBLE.
2188  lp->constraint_lower_bounds()[row], 0) ||
2190  0, lp->constraint_upper_bounds()[row])) {
2191  VLOG(1) << "Problem PRIMAL_INFEASIBLE, constraint " << row
2192  << " is empty and its range ["
2193  << lp->constraint_lower_bounds()[row] << ","
2194  << lp->constraint_upper_bounds()[row] << "] doesn't contain 0.";
2196  return false;
2197  }
2198  row_deletion_helper_.MarkRowForDeletion(row);
2199  }
2200  }
2201  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2202  return !row_deletion_helper_.IsEmpty();
2203 }
2204 
2206  ProblemSolution* solution) const {
2208  RETURN_IF_NULL(solution);
2209  row_deletion_helper_.RestoreDeletedRows(solution);
2210 }
2211 
2212 // --------------------------------------------------------
2213 // SingletonPreprocessor
2214 // --------------------------------------------------------
2215 
2217  MatrixEntry e, ConstraintStatus status)
2218  : type_(type),
2219  is_maximization_(lp.IsMaximizationProblem()),
2220  e_(e),
2221  cost_(lp.objective_coefficients()[e.col]),
2222  variable_lower_bound_(lp.variable_lower_bounds()[e.col]),
2223  variable_upper_bound_(lp.variable_upper_bounds()[e.col]),
2224  constraint_lower_bound_(lp.constraint_lower_bounds()[e.row]),
2225  constraint_upper_bound_(lp.constraint_upper_bounds()[e.row]),
2226  constraint_status_(status) {}
2227 
2228 void SingletonUndo::Undo(const GlopParameters& parameters,
2229  const SparseColumn& saved_column,
2230  const SparseColumn& saved_row,
2231  ProblemSolution* solution) const {
2232  switch (type_) {
2233  case SINGLETON_ROW:
2234  SingletonRowUndo(saved_column, solution);
2235  break;
2237  ZeroCostSingletonColumnUndo(parameters, saved_row, solution);
2238  break;
2240  SingletonColumnInEqualityUndo(parameters, saved_row, solution);
2241  break;
2243  MakeConstraintAnEqualityUndo(solution);
2244  break;
2245  }
2246 }
2247 
2248 void SingletonPreprocessor::DeleteSingletonRow(MatrixEntry e,
2249  LinearProgram* lp) {
2250  Fractional implied_lower_bound =
2251  lp->constraint_lower_bounds()[e.row] / e.coeff;
2252  Fractional implied_upper_bound =
2253  lp->constraint_upper_bounds()[e.row] / e.coeff;
2254  if (e.coeff < 0.0) {
2255  std::swap(implied_lower_bound, implied_upper_bound);
2256  }
2257 
2258  const Fractional old_lower_bound = lp->variable_lower_bounds()[e.col];
2259  const Fractional old_upper_bound = lp->variable_upper_bounds()[e.col];
2260 
2261  const Fractional potential_error =
2262  std::abs(parameters_.preprocessor_zero_tolerance() / e.coeff);
2263  Fractional new_lower_bound =
2264  implied_lower_bound - potential_error > old_lower_bound
2265  ? implied_lower_bound
2266  : old_lower_bound;
2267  Fractional new_upper_bound =
2268  implied_upper_bound + potential_error < old_upper_bound
2269  ? implied_upper_bound
2270  : old_upper_bound;
2271 
2272  if (new_upper_bound < new_lower_bound) {
2273  if (!IsSmallerWithinFeasibilityTolerance(new_lower_bound,
2274  new_upper_bound)) {
2275  VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2276  "row causes the bound of the variable "
2277  << e.col << " to be infeasible by "
2278  << new_lower_bound - new_upper_bound;
2280  return;
2281  }
2282  // Otherwise, fix the variable to one of its bounds.
2283  if (new_lower_bound == lp->variable_lower_bounds()[e.col]) {
2284  new_upper_bound = new_lower_bound;
2285  }
2286  if (new_upper_bound == lp->variable_upper_bounds()[e.col]) {
2287  new_lower_bound = new_upper_bound;
2288  }
2289  DCHECK_EQ(new_lower_bound, new_upper_bound);
2290  }
2291  row_deletion_helper_.MarkRowForDeletion(e.row);
2292  undo_stack_.push_back(SingletonUndo(SingletonUndo::SINGLETON_ROW, *lp, e,
2294  columns_saver_.SaveColumnIfNotAlreadyDone(e.col, lp->GetSparseColumn(e.col));
2295 
2296  lp->SetVariableBounds(e.col, new_lower_bound, new_upper_bound);
2297 }
2298 
2299 // The dual value of the row needs to be corrected to stay at the optimal.
2300 void SingletonUndo::SingletonRowUndo(const SparseColumn& saved_column,
2301  ProblemSolution* solution) const {
2302  DCHECK_EQ(0, solution->dual_values[e_.row]);
2303 
2304  // If the variable is basic or free, we can just keep the constraint
2305  // VariableStatus::BASIC and
2306  // 0.0 as the dual value.
2307  const VariableStatus status = solution->variable_statuses[e_.col];
2308  if (status == VariableStatus::BASIC || status == VariableStatus::FREE) return;
2309 
2310  // Compute whether or not the variable bounds changed.
2311  Fractional implied_lower_bound = constraint_lower_bound_ / e_.coeff;
2312  Fractional implied_upper_bound = constraint_upper_bound_ / e_.coeff;
2313  if (e_.coeff < 0.0) {
2314  std::swap(implied_lower_bound, implied_upper_bound);
2315  }
2316  const bool lower_bound_changed = implied_lower_bound > variable_lower_bound_;
2317  const bool upper_bound_changed = implied_upper_bound < variable_upper_bound_;
2318 
2319  if (!lower_bound_changed && !upper_bound_changed) return;
2320  if (status == VariableStatus::AT_LOWER_BOUND && !lower_bound_changed) return;
2321  if (status == VariableStatus::AT_UPPER_BOUND && !upper_bound_changed) return;
2322 
2323  // This is the reduced cost of the variable before the singleton constraint is
2324  // added back.
2325  const Fractional reduced_cost =
2326  cost_ - ScalarProduct(solution->dual_values, saved_column);
2327  const Fractional reduced_cost_for_minimization =
2328  is_maximization_ ? -reduced_cost : reduced_cost;
2329 
2330  if (status == VariableStatus::FIXED_VALUE) {
2331  DCHECK(lower_bound_changed || upper_bound_changed);
2332  if (reduced_cost_for_minimization >= 0.0 && !lower_bound_changed) {
2333  solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2334  return;
2335  }
2336  if (reduced_cost_for_minimization <= 0.0 && !upper_bound_changed) {
2337  solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2338  return;
2339  }
2340  }
2341 
2342  // If one of the variable bounds changes, and the variable is no longer at one
2343  // of its bounds, then its reduced cost needs to be set to 0.0 and the
2344  // variable becomes a basic variable. This is what the line below do, since
2345  // the new reduced cost of the variable will be equal to:
2346  // old_reduced_cost - coeff * solution->dual_values[row]
2347  solution->dual_values[e_.row] = reduced_cost / e_.coeff;
2348  ConstraintStatus new_constraint_status = VariableToConstraintStatus(status);
2349  if (status == VariableStatus::FIXED_VALUE &&
2350  (!lower_bound_changed || !upper_bound_changed)) {
2351  new_constraint_status = lower_bound_changed
2354  }
2355  if (e_.coeff < 0.0) {
2356  if (new_constraint_status == ConstraintStatus::AT_LOWER_BOUND) {
2357  new_constraint_status = ConstraintStatus::AT_UPPER_BOUND;
2358  } else if (new_constraint_status == ConstraintStatus::AT_UPPER_BOUND) {
2359  new_constraint_status = ConstraintStatus::AT_LOWER_BOUND;
2360  }
2361  }
2362  solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2363  solution->constraint_statuses[e_.row] = new_constraint_status;
2364 }
2365 
2366 void SingletonPreprocessor::UpdateConstraintBoundsWithVariableBounds(
2367  MatrixEntry e, LinearProgram* lp) {
2368  Fractional lower_delta = -e.coeff * lp->variable_upper_bounds()[e.col];
2369  Fractional upper_delta = -e.coeff * lp->variable_lower_bounds()[e.col];
2370  if (e.coeff < 0.0) {
2371  std::swap(lower_delta, upper_delta);
2372  }
2373  lp->SetConstraintBounds(e.row,
2374  lp->constraint_lower_bounds()[e.row] + lower_delta,
2375  lp->constraint_upper_bounds()[e.row] + upper_delta);
2376 }
2377 
2378 bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
2379  const MatrixEntry& matrix_entry, const LinearProgram& lp) const {
2381  DCHECK(lp.IsVariableInteger(matrix_entry.col));
2382  const SparseMatrix& transpose = lp.GetTransposeSparseMatrix();
2383  for (const SparseColumn::Entry entry :
2384  transpose.column(RowToColIndex(matrix_entry.row))) {
2385  // Check if the variable is integer.
2386  if (!lp.IsVariableInteger(RowToColIndex(entry.row()))) {
2387  return false;
2388  }
2389 
2390  const Fractional coefficient = entry.coefficient();
2391  const Fractional coefficient_ratio = coefficient / matrix_entry.coeff;
2392  // Check if coefficient_ratio is integer.
2394  coefficient_ratio, parameters_.solution_feasibility_tolerance())) {
2395  return false;
2396  }
2397  }
2398  const Fractional constraint_lb =
2399  lp.constraint_lower_bounds()[matrix_entry.row];
2400  if (IsFinite(constraint_lb)) {
2401  const Fractional lower_bound_ratio = constraint_lb / matrix_entry.coeff;
2403  lower_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2404  return false;
2405  }
2406  }
2407  const Fractional constraint_ub =
2408  lp.constraint_upper_bounds()[matrix_entry.row];
2409  if (IsFinite(constraint_ub)) {
2410  const Fractional upper_bound_ratio = constraint_ub / matrix_entry.coeff;
2412  upper_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2413  return false;
2414  }
2415  }
2416  return true;
2417 }
2418 
2419 void SingletonPreprocessor::DeleteZeroCostSingletonColumn(
2420  const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2421  const ColIndex transpose_col = RowToColIndex(e.row);
2422  undo_stack_.push_back(SingletonUndo(SingletonUndo::ZERO_COST_SINGLETON_COLUMN,
2423  *lp, e, ConstraintStatus::FREE));
2424  const SparseColumn& row_as_col = transpose.column(transpose_col);
2425  rows_saver_.SaveColumnIfNotAlreadyDone(RowToColIndex(e.row), row_as_col);
2426  UpdateConstraintBoundsWithVariableBounds(e, lp);
2427  column_deletion_helper_.MarkColumnForDeletion(e.col);
2428 }
2429 
2430 // We need to restore the variable value in order to satisfy the constraint.
2431 void SingletonUndo::ZeroCostSingletonColumnUndo(
2432  const GlopParameters& parameters, const SparseColumn& saved_row,
2433  ProblemSolution* solution) const {
2434  // If the variable was fixed, this is easy. Note that this is the only
2435  // possible case if the current constraint status is FIXED.
2436  if (variable_upper_bound_ == variable_lower_bound_) {
2437  solution->primal_values[e_.col] = variable_lower_bound_;
2438  solution->variable_statuses[e_.col] = VariableStatus::FIXED_VALUE;
2439  return;
2440  }
2441 
2442  const ConstraintStatus ct_status = solution->constraint_statuses[e_.row];
2444  if (ct_status == ConstraintStatus::AT_LOWER_BOUND ||
2445  ct_status == ConstraintStatus::AT_UPPER_BOUND) {
2446  if ((ct_status == ConstraintStatus::AT_UPPER_BOUND && e_.coeff > 0.0) ||
2447  (ct_status == ConstraintStatus::AT_LOWER_BOUND && e_.coeff < 0.0)) {
2448  DCHECK(IsFinite(variable_lower_bound_));
2449  solution->primal_values[e_.col] = variable_lower_bound_;
2450  solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2451  } else {
2452  DCHECK(IsFinite(variable_upper_bound_));
2453  solution->primal_values[e_.col] = variable_upper_bound_;
2454  solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2455  }
2456  if (constraint_upper_bound_ == constraint_lower_bound_) {
2457  solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2458  }
2459  return;
2460  }
2461 
2462  // This is the activity of the constraint before the singleton variable is
2463  // added back to it.
2464  const Fractional activity = ScalarProduct(solution->primal_values, saved_row);
2465 
2466  // First we try to fix the variable at its lower or upper bound and leave the
2467  // constraint VariableStatus::BASIC. Note that we use the same logic as in
2468  // Preprocessor::IsSmallerWithinPreprocessorZeroTolerance() which we can't use
2469  // here because we are not deriving from the Preprocessor class.
2470  const Fractional tolerance = parameters.preprocessor_zero_tolerance();
2471  const auto is_smaller_with_tolerance = [tolerance](Fractional a,
2472  Fractional b) {
2474  };
2475  if (variable_lower_bound_ != -kInfinity) {
2476  const Fractional activity_at_lb =
2477  activity + e_.coeff * variable_lower_bound_;
2478  if (is_smaller_with_tolerance(constraint_lower_bound_, activity_at_lb) &&
2479  is_smaller_with_tolerance(activity_at_lb, constraint_upper_bound_)) {
2480  solution->primal_values[e_.col] = variable_lower_bound_;
2481  solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2482  return;
2483  }
2484  }
2485  if (variable_upper_bound_ != kInfinity) {
2486  const Fractional actibity_at_ub =
2487  activity + e_.coeff * variable_upper_bound_;
2488  if (is_smaller_with_tolerance(constraint_lower_bound_, actibity_at_ub) &&
2489  is_smaller_with_tolerance(actibity_at_ub, constraint_upper_bound_)) {
2490  solution->primal_values[e_.col] = variable_upper_bound_;
2491  solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2492  return;
2493  }
2494  }
2495 
2496  // If the current constraint is UNBOUNDED, then the variable is too
2497  // because of the two cases above. We just set its status to
2498  // VariableStatus::FREE.
2499  if (constraint_lower_bound_ == -kInfinity &&
2500  constraint_upper_bound_ == kInfinity) {
2501  solution->primal_values[e_.col] = 0.0;
2502  solution->variable_statuses[e_.col] = VariableStatus::FREE;
2503  return;
2504  }
2505 
2506  // If the previous cases didn't apply, the constraint will be fixed to its
2507  // bounds and the variable will be made VariableStatus::BASIC.
2508  solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2509  if (constraint_lower_bound_ == constraint_upper_bound_) {
2510  solution->primal_values[e_.col] =
2511  (constraint_lower_bound_ - activity) / e_.coeff;
2512  solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2513  return;
2514  }
2515 
2516  bool set_constraint_to_lower_bound;
2517  if (constraint_lower_bound_ == -kInfinity) {
2518  set_constraint_to_lower_bound = false;
2519  } else if (constraint_upper_bound_ == kInfinity) {
2520  set_constraint_to_lower_bound = true;
2521  } else {
2522  // In this case we select the value that is the most inside the variable
2523  // bound.
2524  const Fractional to_lb = (constraint_lower_bound_ - activity) / e_.coeff;
2525  const Fractional to_ub = (constraint_upper_bound_ - activity) / e_.coeff;
2526  set_constraint_to_lower_bound =
2527  std::max(variable_lower_bound_ - to_lb, to_lb - variable_upper_bound_) <
2528  std::max(variable_lower_bound_ - to_ub, to_ub - variable_upper_bound_);
2529  }
2530 
2531  if (set_constraint_to_lower_bound) {
2532  solution->primal_values[e_.col] =
2533  (constraint_lower_bound_ - activity) / e_.coeff;
2534  solution->constraint_statuses[e_.row] = ConstraintStatus::AT_LOWER_BOUND;
2535  } else {
2536  solution->primal_values[e_.col] =
2537  (constraint_upper_bound_ - activity) / e_.coeff;
2538  solution->constraint_statuses[e_.row] = ConstraintStatus::AT_UPPER_BOUND;
2539  }
2540 }
2541 
2542 void SingletonPreprocessor::DeleteSingletonColumnInEquality(
2543  const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2544  // Save information for the undo.
2545  const ColIndex transpose_col = RowToColIndex(e.row);
2546  const SparseColumn& row_as_column = transpose.column(transpose_col);
2547  undo_stack_.push_back(
2548  SingletonUndo(SingletonUndo::SINGLETON_COLUMN_IN_EQUALITY, *lp, e,
2550  rows_saver_.SaveColumnIfNotAlreadyDone(RowToColIndex(e.row), row_as_column);
2551 
2552  // Update the objective function using the equality constraint. We have
2553  // v_col*coeff + expression = rhs,
2554  // so the contribution of this variable to the cost function (v_col * cost)
2555  // can be rewrited as:
2556  // (rhs * cost - expression * cost) / coeff.
2557  const Fractional rhs = lp->constraint_upper_bounds()[e.row];
2558  const Fractional cost = lp->objective_coefficients()[e.col];
2559  const Fractional multiplier = cost / e.coeff;
2560  lp->SetObjectiveOffset(lp->objective_offset() + rhs * multiplier);
2561  for (const SparseColumn::Entry e : row_as_column) {
2562  const ColIndex col = RowToColIndex(e.row());
2563  if (!column_deletion_helper_.IsColumnMarked(col)) {
2564  Fractional new_cost =
2565  lp->objective_coefficients()[col] - e.coefficient() * multiplier;
2566 
2567  // TODO(user): It is important to avoid having non-zero costs which are
2568  // the result of numerical error. This is because we still miss some
2569  // tolerances in a few preprocessors. Like an empty column with a cost of
2570  // 1e-17 and unbounded towards infinity is currently implying that the
2571  // problem is unbounded. This will need fixing.
2572  if (std::abs(new_cost) < parameters_.preprocessor_zero_tolerance()) {
2573  new_cost = 0.0;
2574  }
2575  lp->SetObjectiveCoefficient(col, new_cost);
2576  }
2577  }
2578 
2579  // Now delete the column like a singleton column without cost.
2580  UpdateConstraintBoundsWithVariableBounds(e, lp);
2581  column_deletion_helper_.MarkColumnForDeletion(e.col);
2582 }
2583 
2584 void SingletonUndo::SingletonColumnInEqualityUndo(
2585  const GlopParameters& parameters, const SparseColumn& saved_row,
2586  ProblemSolution* solution) const {
2587  // First do the same as a zero-cost singleton column.
2588  ZeroCostSingletonColumnUndo(parameters, saved_row, solution);
2589 
2590  // Then, restore the dual optimal value taking into account the cost
2591  // modification.
2592  solution->dual_values[e_.row] += cost_ / e_.coeff;
2593  if (solution->constraint_statuses[e_.row] == ConstraintStatus::BASIC) {
2594  solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2595  solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2596  }
2597 }
2598 
2599 void SingletonUndo::MakeConstraintAnEqualityUndo(
2600  ProblemSolution* solution) const {
2601  if (solution->constraint_statuses[e_.row] == ConstraintStatus::FIXED_VALUE) {
2602  solution->constraint_statuses[e_.row] = constraint_status_;
2603  }
2604 }
2605 
2606 bool SingletonPreprocessor::MakeConstraintAnEqualityIfPossible(
2607  const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2608  // TODO(user): We could skip early if the relevant constraint bound is
2609  // infinity.
2610  const Fractional cst_lower_bound = lp->constraint_lower_bounds()[e.row];
2611  const Fractional cst_upper_bound = lp->constraint_upper_bounds()[e.row];
2612  if (cst_lower_bound == cst_upper_bound) return true;
2613 
2614  // To be efficient, we only process a row once and cache the domain that an
2615  // "artificial" extra variable x with coefficient 1.0 could take while still
2616  // making the constraint feasible. The domain bounds for the constraint e.row
2617  // will be stored in row_lb_sum_[e.row] and row_ub_sum_[e.row].
2618  const DenseRow& variable_ubs = lp->variable_upper_bounds();
2619  const DenseRow& variable_lbs = lp->variable_lower_bounds();
2620  if (e.row >= row_sum_is_cached_.size() || !row_sum_is_cached_[e.row]) {
2621  if (e.row >= row_sum_is_cached_.size()) {
2622  const int new_size = e.row.value() + 1;
2623  row_sum_is_cached_.resize(new_size);
2624  row_lb_sum_.resize(new_size);
2625  row_ub_sum_.resize(new_size);
2626  }
2627  row_sum_is_cached_[e.row] = true;
2628  row_lb_sum_[e.row].Add(cst_lower_bound);
2629  row_ub_sum_[e.row].Add(cst_upper_bound);
2630  for (const SparseColumn::Entry entry :
2631  transpose.column(RowToColIndex(e.row))) {
2632  const ColIndex row_as_col = RowToColIndex(entry.row());
2633 
2634  // Tricky: Even if later more columns are deleted, these "cached" sums
2635  // will actually still be valid because we only delete columns in a
2636  // compatible way.
2637  //
2638  // TODO(user): Find a more robust way? it seems easy to add new deletion
2639  // rules that may break this assumption.
2640  if (column_deletion_helper_.IsColumnMarked(row_as_col)) continue;
2641  if (entry.coefficient() > 0.0) {
2642  row_lb_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2643  row_ub_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2644  } else {
2645  row_lb_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2646  row_ub_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2647  }
2648 
2649  // TODO(user): Abort early if both sums contain more than 1 infinity?
2650  }
2651  }
2652 
2653  // Now that the lb/ub sum for the row is cached, we can use it to compute the
2654  // implied bounds on the variable from this constraint and the other
2655  // variables.
2656  const Fractional c = e.coeff;
2657  const Fractional lb =
2658  c > 0.0 ? row_lb_sum_[e.row].SumWithoutLb(-c * variable_ubs[e.col]) / c
2659  : row_ub_sum_[e.row].SumWithoutUb(-c * variable_ubs[e.col]) / c;
2660  const Fractional ub =
2661  c > 0.0 ? row_ub_sum_[e.row].SumWithoutUb(-c * variable_lbs[e.col]) / c
2662  : row_lb_sum_[e.row].SumWithoutLb(-c * variable_lbs[e.col]) / c;
2663 
2664  // Note that we could do the same for singleton variables with a cost of
2665  // 0.0, but such variable are already dealt with by
2666  // DeleteZeroCostSingletonColumn() so there is no point.
2667  const Fractional cost =
2668  lp->GetObjectiveCoefficientForMinimizationVersion(e.col);
2669  DCHECK_NE(cost, 0.0);
2670 
2671  // Note that some of the tests below will be always true if the bounds of
2672  // the column of index col are infinite. This is the desired behavior.
2675  ub, lp->variable_upper_bounds()[e.col])) {
2676  if (e.coeff > 0) {
2677  if (cst_upper_bound == kInfinity) {
2679  } else {
2680  relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2681  lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2682  }
2683  } else {
2684  if (cst_lower_bound == -kInfinity) {
2686  } else {
2687  relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2688  lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2689  }
2690  }
2691 
2693  DCHECK_EQ(ub, kInfinity);
2694  VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2695  "variable "
2696  << e.col << " has a cost (for minimization) of " << cost
2697  << " and is unbounded towards kInfinity.";
2698  return false;
2699  }
2700 
2701  // This is important but tricky: The upper bound of the variable needs to
2702  // be relaxed. This is valid because the implied bound is lower than the
2703  // original upper bound here. This is needed, so that the optimal
2704  // primal/dual values of the new problem will also be optimal of the
2705  // original one.
2706  //
2707  // Let's prove the case coeff > 0.0 for a minimization problem. In the new
2708  // problem, because the variable is unbounded towards +infinity, its
2709  // reduced cost must satisfy at optimality rc = cost - coeff * dual_v >=
2710  // 0. But this implies dual_v <= cost / coeff <= 0. This is exactly what
2711  // is needed for the optimality of the initial problem since the
2712  // constraint will be at its upper bound, and the corresponding slack
2713  // condition is that the dual value needs to be <= 0.
2714  lp->SetVariableBounds(e.col, lp->variable_lower_bounds()[e.col], kInfinity);
2715  }
2717  lp->variable_lower_bounds()[e.col], lb)) {
2718  if (e.coeff > 0) {
2719  if (cst_lower_bound == -kInfinity) {
2721  } else {
2722  relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2723  lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2724  }
2725  } else {
2726  if (cst_upper_bound == kInfinity) {
2728  } else {
2729  relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2730  lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2731  }
2732  }
2733 
2735  DCHECK_EQ(lb, -kInfinity);
2736  VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2737  "variable "
2738  << e.col << " has a cost (for minimization) of " << cost
2739  << " and is unbounded towards -kInfinity.";
2740  return false;
2741  }
2742 
2743  // Same remark as above for a lower bounded variable this time.
2744  lp->SetVariableBounds(e.col, -kInfinity,
2745  lp->variable_upper_bounds()[e.col]);
2746  }
2747 
2748  if (lp->constraint_lower_bounds()[e.row] ==
2749  lp->constraint_upper_bounds()[e.row]) {
2750  undo_stack_.push_back(SingletonUndo(
2751  SingletonUndo::MAKE_CONSTRAINT_AN_EQUALITY, *lp, e, relaxed_status));
2752  return true;
2753  }
2754  return false;
2755 }
2756 
2759  RETURN_VALUE_IF_NULL(lp, false);
2760  const SparseMatrix& matrix = lp->GetSparseMatrix();
2761  const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2762 
2763  // Initialize column_to_process with the current singleton columns.
2764  ColIndex num_cols(matrix.num_cols());
2765  RowIndex num_rows(matrix.num_rows());
2766  StrictITIVector<ColIndex, EntryIndex> column_degree(num_cols, EntryIndex(0));
2767  std::vector<ColIndex> column_to_process;
2768  for (ColIndex col(0); col < num_cols; ++col) {
2769  column_degree[col] = matrix.column(col).num_entries();
2770  if (column_degree[col] == 1) {
2771  column_to_process.push_back(col);
2772  }
2773  }
2774 
2775  // Initialize row_to_process with the current singleton rows.
2776  StrictITIVector<RowIndex, EntryIndex> row_degree(num_rows, EntryIndex(0));
2777  std::vector<RowIndex> row_to_process;
2778  for (RowIndex row(0); row < num_rows; ++row) {
2779  row_degree[row] = transpose.column(RowToColIndex(row)).num_entries();
2780  if (row_degree[row] == 1) {
2781  row_to_process.push_back(row);
2782  }
2783  }
2784 
2785  // Process current singleton rows/columns and enqueue new ones.
2786  while (status_ == ProblemStatus::INIT &&
2787  (!column_to_process.empty() || !row_to_process.empty())) {
2788  while (status_ == ProblemStatus::INIT && !column_to_process.empty()) {
2789  const ColIndex col = column_to_process.back();
2790  column_to_process.pop_back();
2791  if (column_degree[col] <= 0) continue;
2792  const MatrixEntry e = GetSingletonColumnMatrixEntry(col, matrix);
2793  if (in_mip_context_ && lp->IsVariableInteger(e.col) &&
2794  !IntegerSingletonColumnIsRemovable(e, *lp)) {
2795  continue;
2796  }
2797 
2798  // TODO(user): It seems better to process all the singleton columns with
2799  // a cost of zero first.
2800  if (lp->objective_coefficients()[col] == 0.0) {
2801  DeleteZeroCostSingletonColumn(transpose, e, lp);
2802  } else if (MakeConstraintAnEqualityIfPossible(transpose, e, lp)) {
2803  DeleteSingletonColumnInEquality(transpose, e, lp);
2804  } else {
2805  continue;
2806  }
2807  --row_degree[e.row];
2808  if (row_degree[e.row] == 1) {
2809  row_to_process.push_back(e.row);
2810  }
2811  }
2812  while (status_ == ProblemStatus::INIT && !row_to_process.empty()) {
2813  const RowIndex row = row_to_process.back();
2814  row_to_process.pop_back();
2815  if (row_degree[row] <= 0) continue;
2816  const MatrixEntry e = GetSingletonRowMatrixEntry(row, transpose);
2817 
2818  DeleteSingletonRow(e, lp);
2819  --column_degree[e.col];
2820  if (column_degree[e.col] == 1) {
2821  column_to_process.push_back(e.col);
2822  }
2823  }
2824  }
2825 
2826  if (status_ != ProblemStatus::INIT) return false;
2827  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2828  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2829  return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2830 }
2831 
2834  RETURN_IF_NULL(solution);
2835 
2836  // Note that the two deletion helpers must restore 0.0 values in the positions
2837  // that will be used during Undo(). That is, all the calls done by this class
2838  // to MarkColumnForDeletion() should be done with 0.0 as the value to restore
2839  // (which is already the case when using MarkRowForDeletion()).
2840  // This is important because the various Undo() functions assume that a
2841  // primal/dual variable value which isn't restored yet has the value of 0.0.
2842  column_deletion_helper_.RestoreDeletedColumns(solution);
2843  row_deletion_helper_.RestoreDeletedRows(solution);
2844 
2845  // It is important to undo the operations in the correct order, i.e. in the
2846  // reverse order in which they were done.
2847  for (int i = undo_stack_.size() - 1; i >= 0; --i) {
2848  const SparseColumn& saved_col =
2849  columns_saver_.SavedOrEmptyColumn(undo_stack_[i].Entry().col);
2850  const SparseColumn& saved_row = rows_saver_.SavedOrEmptyColumn(
2851  RowToColIndex(undo_stack_[i].Entry().row));
2852  undo_stack_[i].Undo(parameters_, saved_col, saved_row, solution);
2853  }
2854 }
2855 
2856 MatrixEntry SingletonPreprocessor::GetSingletonColumnMatrixEntry(
2857  ColIndex col, const SparseMatrix& matrix) {
2858  for (const SparseColumn::Entry e : matrix.column(col)) {
2859  if (!row_deletion_helper_.IsRowMarked(e.row())) {
2860  DCHECK_NE(0.0, e.coefficient());
2861  return MatrixEntry(e.row(), col, e.coefficient());
2862  }
2863  }
2864  // This shouldn't happen.
2865  LOG(DFATAL) << "No unmarked entry in a column that is supposed to have one.";
2867  return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2868 }
2869 
2870 MatrixEntry SingletonPreprocessor::GetSingletonRowMatrixEntry(
2871  RowIndex row, const SparseMatrix& transpose) {
2872  for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
2873  const ColIndex col = RowToColIndex(e.row());
2874  if (!column_deletion_helper_.IsColumnMarked(col)) {
2875  DCHECK_NE(0.0, e.coefficient());
2876  return MatrixEntry(row, col, e.coefficient());
2877  }
2878  }
2879  // This shouldn't happen.
2880  LOG(DFATAL) << "No unmarked entry in a row that is supposed to have one.";
2882  return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2883 }
2884 
2885 // --------------------------------------------------------
2886 // RemoveNearZeroEntriesPreprocessor
2887 // --------------------------------------------------------
2888 
2891  RETURN_VALUE_IF_NULL(lp, false);
2892  const ColIndex num_cols = lp->num_variables();
2893  if (num_cols == 0) return false;
2894 
2895  // We will use a different threshold for each row depending on its degree.
2896  // We use Fractionals for convenience since they will be used as such below.
2897  const RowIndex num_rows = lp->num_constraints();
2898  DenseColumn row_degree(num_rows, 0.0);
2899  Fractional num_non_zero_objective_coefficients = 0.0;
2900  for (ColIndex col(0); col < num_cols; ++col) {
2901  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
2902  row_degree[e.row()] += 1.0;
2903  }
2904  if (lp->objective_coefficients()[col] != 0.0) {
2905  num_non_zero_objective_coefficients += 1.0;
2906  }
2907  }
2908 
2909  // To not have too many parameters, we use the preprocessor_zero_tolerance.
2910  const Fractional allowed_impact = parameters_.preprocessor_zero_tolerance();
2911 
2912  // TODO(user): Our criteria ensure that during presolve a primal feasible
2913  // solution will stay primal feasible. However, we have no guarantee on the
2914  // dual-feasibility (because the dual variable values range is not taken into
2915  // account). Fix that? or find a better criteria since it seems that on all
2916  // our current problems, this preprocessor helps and doesn't introduce errors.
2917  const EntryIndex initial_num_entries = lp->num_entries();
2918  int num_zeroed_objective_coefficients = 0;
2919  for (ColIndex col(0); col < num_cols; ++col) {
2922 
2923  // TODO(user): Write a small class that takes a matrix, its transpose, row
2924  // and column bounds, and "propagate" the bounds as much as possible so we
2925  // can use this better estimate here and remove more near-zero entries.
2926  const Fractional max_magnitude =
2927  std::max(std::abs(lower_bound), std::abs(upper_bound));
2928  if (max_magnitude == kInfinity || max_magnitude == 0) continue;
2929  const Fractional threshold = allowed_impact / max_magnitude;
2931  threshold, row_degree);
2932 
2933  if (lp->objective_coefficients()[col] != 0.0 &&
2934  num_non_zero_objective_coefficients *
2935  std::abs(lp->objective_coefficients()[col]) <
2936  threshold) {
2937  lp->SetObjectiveCoefficient(col, 0.0);
2938  ++num_zeroed_objective_coefficients;
2939  }
2940  }
2941 
2942  const EntryIndex num_entries = lp->num_entries();
2943  if (num_entries != initial_num_entries) {
2944  VLOG(1) << "Removed " << initial_num_entries - num_entries
2945  << " near-zero entries.";
2946  }
2947  if (num_zeroed_objective_coefficients > 0) {
2948  VLOG(1) << "Removed " << num_zeroed_objective_coefficients
2949  << " near-zero objective coefficients.";
2950  }
2951 
2952  // No post-solve is required.
2953  return false;
2954 }
2955 
2957  ProblemSolution* solution) const {}
2958 
2959 // --------------------------------------------------------
2960 // SingletonColumnSignPreprocessor
2961 // --------------------------------------------------------
2962 
2965  RETURN_VALUE_IF_NULL(lp, false);
2966  const ColIndex num_cols = lp->num_variables();
2967  if (num_cols == 0) return false;
2968 
2969  changed_columns_.clear();
2970  int num_singletons = 0;
2971  for (ColIndex col(0); col < num_cols; ++col) {
2972  SparseColumn* sparse_column = lp->GetMutableSparseColumn(col);
2973  const Fractional cost = lp->objective_coefficients()[col];
2974  if (sparse_column->num_entries() == 1) {
2975  ++num_singletons;
2976  }
2977  if (sparse_column->num_entries() == 1 &&
2978  sparse_column->GetFirstCoefficient() < 0) {
2979  sparse_column->MultiplyByConstant(-1.0);
2981  -lp->variable_lower_bounds()[col]);
2983  changed_columns_.push_back(col);
2984  }
2985  }
2986  VLOG(1) << "Changed the sign of " << changed_columns_.size() << " columns.";
2987  VLOG(1) << num_singletons << " singleton columns left.";
2988  return !changed_columns_.empty();
2989 }
2990 
2992  ProblemSolution* solution) const {
2994  RETURN_IF_NULL(solution);
2995  for (int i = 0; i < changed_columns_.size(); ++i) {
2996  const ColIndex col = changed_columns_[i];
2997  solution->primal_values[col] = -solution->primal_values[col];
2998  const VariableStatus status = solution->variable_statuses[col];
3001  } else if (status == VariableStatus::AT_LOWER_BOUND) {
3003  }
3004  }
3005 }
3006 
3007 // --------------------------------------------------------
3008 // DoubletonEqualityRowPreprocessor
3009 // --------------------------------------------------------
3010 
3013  RETURN_VALUE_IF_NULL(lp, false);
3014 
3015  // This is needed at postsolve.
3016  //
3017  // TODO(user): Get rid of the FIXED status instead to avoid spending
3018  // time/memory for no good reason here.
3019  saved_row_lower_bounds_ = lp->constraint_lower_bounds();
3020  saved_row_upper_bounds_ = lp->constraint_upper_bounds();
3021 
3022  // This is needed for postsolving dual.
3023  saved_objective_ = lp->objective_coefficients();
3024 
3025  // Note that we don't update the transpose during this preprocessor run.
3026  const SparseMatrix& original_transpose = lp->GetTransposeSparseMatrix();
3027 
3028  // Heuristic: We try to subtitute sparse columns first to avoid a complexity
3029  // explosion. Note that if we do long chain of substitution, we can still end
3030  // up with a complexity of O(num_rows x num_cols) instead of O(num_entries).
3031  //
3032  // TODO(user): There is probably some more robust ways.
3033  std::vector<std::pair<int64_t, RowIndex>> sorted_rows;
3034  const RowIndex num_rows(lp->num_constraints());
3035  for (RowIndex row(0); row < num_rows; ++row) {
3036  const SparseColumn& original_row =
3037  original_transpose.column(RowToColIndex(row));
3038  if (original_row.num_entries() != 2 ||
3039  lp->constraint_lower_bounds()[row] !=
3040  lp->constraint_upper_bounds()[row]) {
3041  continue;
3042  }
3043  int64_t score = 0;
3044  for (const SparseColumn::Entry e : original_row) {
3045  const ColIndex col = RowToColIndex(e.row());
3046  score += lp->GetSparseColumn(col).num_entries().value();
3047  }
3048  sorted_rows.push_back({score, row});
3049  }
3050  std::sort(sorted_rows.begin(), sorted_rows.end());
3051 
3052  // Iterate over the rows that were already doubletons before this preprocessor
3053  // run, and whose items don't belong to a column that we deleted during this
3054  // run. This implies that the rows are only ever touched once per run, because
3055  // we only modify rows that have an item on a deleted column.
3056  for (const auto p : sorted_rows) {
3057  const RowIndex row = p.second;
3058  const SparseColumn& original_row =
3059  original_transpose.column(RowToColIndex(row));
3060 
3061  // Collect the two row items. Skip the ones involving a deleted column.
3062  // Note: we filled r.col[] and r.coeff[] by item order, and currently we
3063  // always pick the first column as the to-be-deleted one.
3064  // TODO(user): make a smarter choice of which column to delete, and
3065  // swap col[] and coeff[] accordingly.
3066  RestoreInfo r; // Use a short name since we're using it everywhere.
3067  int entry_index = 0;
3068  for (const SparseColumn::Entry e : original_row) {
3069  const ColIndex col = RowToColIndex(e.row());
3070  if (column_deletion_helper_.IsColumnMarked(col)) continue;
3071  r.col[entry_index] = col;
3072  r.coeff[entry_index] = e.coefficient();
3073  DCHECK_NE(0.0, r.coeff[entry_index]);
3074  ++entry_index;
3075  }
3076 
3077  // Discard some cases that will be treated by other preprocessors, or by
3078  // another run of this one.
3079  // 1) One or two of the items were in a deleted column.
3080  if (entry_index < 2) continue;
3081 
3082  // Fill the RestoreInfo, even if we end up not using it (because we
3083  // give up on preprocessing this row): it has a bunch of handy shortcuts.
3084  r.row = row;
3085  r.rhs = lp->constraint_lower_bounds()[row];
3086  for (int col_choice = 0; col_choice < NUM_DOUBLETON_COLS; ++col_choice) {
3087  const ColIndex col = r.col[col_choice];
3088  r.lb[col_choice] = lp->variable_lower_bounds()[col];
3089  r.ub[col_choice] = lp->variable_upper_bounds()[col];
3090  r.objective_coefficient[col_choice] = lp->objective_coefficients()[col];
3091  }
3092 
3093  // 2) One of the columns is fixed: don't bother, it will be treated
3094  // by the FixedVariablePreprocessor.
3095  if (r.lb[DELETED] == r.ub[DELETED] || r.lb[MODIFIED] == r.ub[MODIFIED]) {
3096  continue;
3097  }
3098 
3099  // Look at the bounds of both variables and exit early if we can delegate
3100  // to another pre-processor; otherwise adjust the bounds of the remaining
3101  // variable as necessary.
3102  // If the current row is: aX + bY = c, then the bounds of Y must be
3103  // adjusted to satisfy Y = c/b + (-a/b)X
3104  //
3105  // Note: when we compute the coefficients of these equations, we can cause
3106  // underflows/overflows that could be avoided if we did the computations
3107  // more carefully; but for now we just treat those cases as
3108  // ProblemStatus::ABNORMAL.
3109  // TODO(user): consider skipping the problematic rows in this preprocessor,
3110  // or trying harder to avoid the under/overflow.
3111  {
3112  const Fractional carry_over_offset = r.rhs / r.coeff[MODIFIED];
3113  const Fractional carry_over_factor =
3114  -r.coeff[DELETED] / r.coeff[MODIFIED];
3115  if (!IsFinite(carry_over_offset) || !IsFinite(carry_over_factor) ||
3116  carry_over_factor == 0.0) {
3118  break;
3119  }
3120 
3121  Fractional lb = r.lb[MODIFIED];
3122  Fractional ub = r.ub[MODIFIED];
3123  Fractional carried_over_lb =
3124  r.lb[DELETED] * carry_over_factor + carry_over_offset;
3125  Fractional carried_over_ub =
3126  r.ub[DELETED] * carry_over_factor + carry_over_offset;
3127  if (carry_over_factor < 0) {
3128  std::swap(carried_over_lb, carried_over_ub);
3129  }
3130  if (carried_over_lb <= lb) {
3131  // Default (and simplest) case: the lower bound didn't change.
3132  r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3133  MODIFIED, VariableStatus::AT_LOWER_BOUND, lb);
3134  } else {
3135  lb = carried_over_lb;
3136  r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3137  DELETED,
3138  carry_over_factor > 0 ? VariableStatus::AT_LOWER_BOUND
3140  carry_over_factor > 0 ? r.lb[DELETED] : r.ub[DELETED]);
3141  }
3142  if (carried_over_ub >= ub) {
3143  // Default (and simplest) case: the upper bound didn't change.
3144  r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3145  MODIFIED, VariableStatus::AT_UPPER_BOUND, ub);
3146  } else {
3147  ub = carried_over_ub;
3148  r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3149  DELETED,
3150  carry_over_factor > 0 ? VariableStatus::AT_UPPER_BOUND
3152  carry_over_factor > 0 ? r.ub[DELETED] : r.lb[DELETED]);
3153  }
3154  // 3) If the new bounds are fixed (the domain is a singleton) or
3155  // infeasible, then we let the
3156  // ForcingAndImpliedFreeConstraintPreprocessor do the work.
3157  if (IsSmallerWithinPreprocessorZeroTolerance(ub, lb)) continue;
3158  lp->SetVariableBounds(r.col[MODIFIED], lb, ub);
3159  }
3160 
3161  restore_stack_.push_back(r);
3162 
3163  // Now, perform the substitution. If the current row is: aX + bY = c
3164  // then any other row containing 'X' with coefficient x can remove the
3165  // entry in X, and instead add an entry on 'Y' with coefficient x(-b/a)
3166  // and a constant offset x(c/a).
3167  // Looking at the matrix, this translates into colY += (-b/a) colX.
3168  DCHECK_NE(r.coeff[DELETED], 0.0);
3169  const Fractional substitution_factor =
3170  -r.coeff[MODIFIED] / r.coeff[DELETED]; // -b/a
3171  const Fractional constant_offset_factor = r.rhs / r.coeff[DELETED]; // c/a
3172  // Again we don't bother too much with over/underflows.
3173  if (!IsFinite(substitution_factor) || substitution_factor == 0.0 ||
3174  !IsFinite(constant_offset_factor)) {
3176  break;
3177  }
3178 
3179  // Note that we do not save again a saved column, so that we only save
3180  // columns from the initial LP. This is important to limit the memory usage.
3181  // It complexify a bit the postsolve though.
3182  for (const int col_choice : {DELETED, MODIFIED}) {
3183  const ColIndex col = r.col[col_choice];
3184  columns_saver_.SaveColumnIfNotAlreadyDone(col, lp->GetSparseColumn(col));
3185  }
3186 
3187  lp->GetSparseColumn(r.col[DELETED])
3189  substitution_factor, r.row, parameters_.drop_tolerance(),
3190  lp->GetMutableSparseColumn(r.col[MODIFIED]));
3191 
3192  // Apply similar operations on the objective coefficients.
3193  // Note that the offset is being updated by
3194  // SubtractColumnMultipleFromConstraintBound() below.
3195  {
3196  const Fractional new_objective =
3197  r.objective_coefficient[MODIFIED] +
3198  substitution_factor * r.objective_coefficient[DELETED];
3199  if (std::abs(new_objective) > parameters_.drop_tolerance()) {
3200  lp->SetObjectiveCoefficient(r.col[MODIFIED], new_objective);
3201  } else {
3202  lp->SetObjectiveCoefficient(r.col[MODIFIED], 0.0);
3203  }
3204  }
3205 
3206  // Carry over the constant factor of the substitution as well.
3207  // TODO(user): rename that method to reflect the fact that it also updates
3208  // the objective offset, in the other direction.
3209  SubtractColumnMultipleFromConstraintBound(r.col[DELETED],
3210  constant_offset_factor, lp);
3211 
3212  // If we keep substituing the same "dense" columns over and over, we can
3213  // have a memory in O(num_rows * num_cols) which can be order of magnitude
3214  // larger than the original problem. It is important to reclaim the memory
3215  // of the deleted column right away.
3216  lp->GetMutableSparseColumn(r.col[DELETED])->ClearAndRelease();
3217 
3218  // Mark the column and the row for deletion.
3219  column_deletion_helper_.MarkColumnForDeletion(r.col[DELETED]);
3220  row_deletion_helper_.MarkRowForDeletion(r.row);
3221  }
3222  if (status_ != ProblemStatus::INIT) return false;
3223  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
3224  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
3225 
3226  return !column_deletion_helper_.IsEmpty();
3227 }
3228 
3230  ProblemSolution* solution) const {
3232  RETURN_IF_NULL(solution);
3233  column_deletion_helper_.RestoreDeletedColumns(solution);
3234  row_deletion_helper_.RestoreDeletedRows(solution);
3235 
3236  const ColIndex num_cols = solution->variable_statuses.size();
3237  StrictITIVector<ColIndex, bool> new_basic_columns(num_cols, false);
3238 
3239  for (const RestoreInfo& r : Reverse(restore_stack_)) {
3240  switch (solution->variable_statuses[r.col[MODIFIED]]) {
3242  LOG(DFATAL) << "FIXED variable produced by DoubletonPreprocessor!";
3243  // In non-fastbuild mode, we rely on the rest of the code producing an
3244  // ProblemStatus::ABNORMAL status here.
3245  break;
3246  // When the modified variable is either basic or free, we keep it as is,
3247  // and simply make the deleted one basic.
3248  case VariableStatus::FREE:
3249  ABSL_FALLTHROUGH_INTENDED;
3250  case VariableStatus::BASIC:
3251  // Several code paths set the deleted column as basic. The code that
3252  // sets its value in that case is below, after the switch() block.
3253  solution->variable_statuses[r.col[DELETED]] = VariableStatus::BASIC;
3254  new_basic_columns[r.col[DELETED]] = true;
3255  break;
3257  ABSL_FALLTHROUGH_INTENDED;
3259  // The bound was induced by a bound of one of the two original
3260  // variables. Put that original variable at its bound, and make
3261  // the other one basic.
3262  const RestoreInfo::ColChoiceAndStatus& bound_backtracking =
3263  solution->variable_statuses[r.col[MODIFIED]] ==
3265  ? r.bound_backtracking_at_lower_bound
3266  : r.bound_backtracking_at_upper_bound;
3267  const ColIndex bounded_var = r.col[bound_backtracking.col_choice];
3268  const ColIndex basic_var =
3269  r.col[OtherColChoice(bound_backtracking.col_choice)];
3270  solution->variable_statuses[bounded_var] = bound_backtracking.status;
3271  solution->primal_values[bounded_var] = bound_backtracking.value;
3272  solution->variable_statuses[basic_var] = VariableStatus::BASIC;
3273  new_basic_columns[basic_var] = true;
3274  // If the modified column is VariableStatus::BASIC, then its value is
3275  // already set correctly. If it's the deleted column that is basic, its
3276  // value is set below the switch() block.
3277  }
3278  }
3279 
3280  // Restore the value of the deleted column if it is VariableStatus::BASIC.
3281  if (solution->variable_statuses[r.col[DELETED]] == VariableStatus::BASIC) {
3282  solution->primal_values[r.col[DELETED]] =
3283  (r.rhs -
3284  solution->primal_values[r.col[MODIFIED]] * r.coeff[MODIFIED]) /
3285  r.coeff[DELETED];
3286  }
3287 
3288  // Make the deleted constraint status FIXED.
3290  }
3291 
3292  // Now we need to reconstruct the dual. This is a bit tricky and is basically
3293  // the same as inverting a really structed and easy to invert matrix. For n
3294  // doubleton rows, looking only at the new_basic_columns, there is exactly n
3295  // by construction (one per row). We consider only this n x n matrix, and we
3296  // must choose dual row values so that we make the reduced costs zero on all
3297  // these columns.
3298  //
3299  // There is always an order that make this matrix triangular. We start with a
3300  // singleton column which fix its corresponding row and then work on the
3301  // square submatrix left. We can always start and continue, because if we take
3302  // the first substitued row of the current submatrix, if its deleted column
3303  // was in the submatrix we have a singleton column. If it is outside, we have
3304  // 2 n - 1 entries for a matrix with n columns, so one must be singleton.
3305  //
3306  // Note(user): Another advantage of working on the "original" matrix before
3307  // this presolve is an increased precision.
3308  //
3309  // TODO(user): We can probably use something better than a vector of set,
3310  // but the number of entry is really sparse though. And the size of a set<int>
3311  // is 24 bytes, same as a std::vector<int>.
3312  StrictITIVector<ColIndex, std::set<int>> col_to_index(num_cols);
3313  for (int i = 0; i < restore_stack_.size(); ++i) {
3314  const RestoreInfo& r = restore_stack_[i];
3315  col_to_index[r.col[MODIFIED]].insert(i);
3316  col_to_index[r.col[DELETED]].insert(i);
3317  }
3318  std::vector<ColIndex> singleton_col;
3319  for (ColIndex col(0); col < num_cols; ++col) {
3320  if (!new_basic_columns[col]) continue;
3321  if (col_to_index[col].size() == 1) singleton_col.push_back(col);
3322  }
3323  while (!singleton_col.empty()) {
3324  const ColIndex col = singleton_col.back();
3325  singleton_col.pop_back();
3326  if (!new_basic_columns[col]) continue;
3327  if (col_to_index[col].empty()) continue;
3328  CHECK_EQ(col_to_index[col].size(), 1);
3329  const int index = *col_to_index[col].begin();
3330  const RestoreInfo& r = restore_stack_[index];
3331 
3332  const ColChoice col_choice = r.col[MODIFIED] == col ? MODIFIED : DELETED;
3333 
3334  // Adjust the dual value of the deleted constraint so that col have a
3335  // reduced costs of zero.
3336  CHECK_EQ(solution->dual_values[r.row], 0.0);
3337  const SparseColumn& saved_col =
3338  columns_saver_.SavedColumn(r.col[col_choice]);
3339  const Fractional current_reduced_cost =
3340  saved_objective_[r.col[col_choice]] -
3341  PreciseScalarProduct(solution->dual_values, saved_col);
3342  solution->dual_values[r.row] = current_reduced_cost / r.coeff[col_choice];
3343 
3344  // Update singleton
3345  col_to_index[r.col[DELETED]].erase(index);
3346  col_to_index[r.col[MODIFIED]].erase(index);
3347  if (col_to_index[r.col[DELETED]].size() == 1) {
3348  singleton_col.push_back(r.col[DELETED]);
3349  }
3350  if (col_to_index[r.col[MODIFIED]].size() == 1) {
3351  singleton_col.push_back(r.col[MODIFIED]);
3352  }
3353  }
3354 
3355  // Fix potential bad ConstraintStatus::FIXED_VALUE statuses.
3356  FixConstraintWithFixedStatuses(saved_row_lower_bounds_,
3357  saved_row_upper_bounds_, solution);
3358 }
3359 
3360 void FixConstraintWithFixedStatuses(const DenseColumn& row_lower_bounds,
3361  const DenseColumn& row_upper_bounds,
3362  ProblemSolution* solution) {
3363  const RowIndex num_rows = solution->constraint_statuses.size();
3364  DCHECK_EQ(row_lower_bounds.size(), num_rows);
3365  DCHECK_EQ(row_upper_bounds.size(), num_rows);
3366  for (RowIndex row(0); row < num_rows; ++row) {
3368  continue;
3369  }
3370  if (row_lower_bounds[row] == row_upper_bounds[row]) continue;
3371 
3372  // We need to fix the status and we just need to make sure that the bound we
3373  // choose satisfies the LP optimality conditions.
3374  if (solution->dual_values[row] > 0) {
3376  } else {
3378  }
3379  }
3380 }
3381 
3382 void DoubletonEqualityRowPreprocessor::
3383  SwapDeletedAndModifiedVariableRestoreInfo(RestoreInfo* r) {
3384  using std::swap;
3385  swap(r->col[DELETED], r->col[MODIFIED]);
3386  swap(r->coeff[DELETED], r->coeff[MODIFIED]);
3387  swap(r->lb[DELETED], r->lb[MODIFIED]);
3388  swap(r->ub[DELETED], r->ub[MODIFIED]);
3389  swap(r->objective_coefficient[DELETED], r->objective_coefficient[MODIFIED]);
3390 }
3391 
3392 // --------------------------------------------------------
3393 // DualizerPreprocessor
3394 // --------------------------------------------------------
3395 
3398  RETURN_VALUE_IF_NULL(lp, false);
3399  if (parameters_.solve_dual_problem() == GlopParameters::NEVER_DO) {
3400  return false;
3401  }
3402 
3403  // Store the original problem size and direction.
3404  primal_num_cols_ = lp->num_variables();
3405  primal_num_rows_ = lp->num_constraints();
3406  primal_is_maximization_problem_ = lp->IsMaximizationProblem();
3407 
3408  // If we need to decide whether or not to take the dual, we only take it when
3409  // the matrix has more rows than columns. The number of rows of a linear
3410  // program gives the size of the square matrices we need to invert and the
3411  // order of iterations of the simplex method. So solving a program with less
3412  // rows is likely a better alternative. Note that the number of row of the
3413  // dual is the number of column of the primal.
3414  //
3415  // Note however that the default is a conservative factor because if the
3416  // user gives us a primal program, we assume he knows what he is doing and
3417  // sometimes a problem is a lot faster to solve in a given formulation
3418  // even if its dimension would say otherwise.
3419  //
3420  // Another reason to be conservative, is that the number of columns of the
3421  // dual is the number of rows of the primal plus up to two times the number of
3422  // columns of the primal.
3423  //
3424  // TODO(user): This effect can be lowered if we use some of the extra
3425  // variables as slack variable which we are not doing at this point.
3426  if (parameters_.solve_dual_problem() == GlopParameters::LET_SOLVER_DECIDE) {
3427  if (1.0 * primal_num_rows_.value() <
3428  parameters_.dualizer_threshold() * primal_num_cols_.value()) {
3429  return false;
3430  }
3431  }
3432 
3433  // Save the linear program bounds.
3434  // Also make sure that all the bounded variable have at least one bound set to
3435  // zero. This will be needed to post-solve a dual-basic solution into a
3436  // primal-basic one.
3437  const ColIndex num_cols = lp->num_variables();
3438  variable_lower_bounds_.assign(num_cols, 0.0);
3439  variable_upper_bounds_.assign(num_cols, 0.0);
3440  for (ColIndex col(0); col < num_cols; ++col) {
3441  const Fractional lower = lp->variable_lower_bounds()[col];
3442  const Fractional upper = lp->variable_upper_bounds()[col];
3443 
3444  // We need to shift one of the bound to zero.
3445  variable_lower_bounds_[col] = lower;
3446  variable_upper_bounds_[col] = upper;
3447  const Fractional value = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3448  if (value != 0.0) {
3449  lp->SetVariableBounds(col, lower - value, upper - value);
3450  SubtractColumnMultipleFromConstraintBound(col, value, lp);
3451  }
3452  }
3453 
3454  // Fill the information that will be needed during postsolve.
3455  //
3456  // TODO(user): This will break if PopulateFromDual() is changed. so document
3457  // the convention or make the function fill these vectors?
3458  dual_status_correspondence_.clear();
3459  for (RowIndex row(0); row < primal_num_rows_; ++row) {
3462  if (lower_bound == upper_bound) {
3463  dual_status_correspondence_.push_back(VariableStatus::FIXED_VALUE);
3464  } else if (upper_bound != kInfinity) {
3465  dual_status_correspondence_.push_back(VariableStatus::AT_UPPER_BOUND);
3466  } else if (lower_bound != -kInfinity) {
3467  dual_status_correspondence_.push_back(VariableStatus::AT_LOWER_BOUND);
3468  } else {
3469  LOG(DFATAL) << "There should be no free constraint in this lp.";
3470  }
3471  }
3472  slack_or_surplus_mapping_.clear();
3473  for (ColIndex col(0); col < primal_num_cols_; ++col) {
3476  if (lower_bound != -kInfinity) {
3477  dual_status_correspondence_.push_back(
3480  slack_or_surplus_mapping_.push_back(col);
3481  }
3482  }
3483  for (ColIndex col(0); col < primal_num_cols_; ++col) {
3486  if (upper_bound != kInfinity) {
3487  dual_status_correspondence_.push_back(
3490  slack_or_surplus_mapping_.push_back(col);
3491  }
3492  }
3493 
3494  // TODO(user): There are two different ways to deal with ranged rows when
3495  // taking the dual. The default way is to duplicate such rows, see
3496  // PopulateFromDual() for details. Another way is to call
3497  // lp->AddSlackVariablesForFreeAndBoxedRows() before calling
3498  // PopulateFromDual(). Adds an option to switch between the two as this may
3499  // change the running time?
3500  //
3501  // Note however that the default algorithm is likely to result in a faster
3502  // solving time because the dual program will have less rows.
3503  LinearProgram dual;
3504  dual.PopulateFromDual(*lp, &duplicated_rows_);
3505  dual.Swap(lp);
3506  return true;
3507 }
3508 
3509 // Note(user): This assumes that LinearProgram.PopulateFromDual() uses
3510 // the first ColIndex and RowIndex for the rows and columns of the given
3511 // problem.
3514  RETURN_IF_NULL(solution);
3515 
3516  DenseRow new_primal_values(primal_num_cols_, 0.0);
3517  VariableStatusRow new_variable_statuses(primal_num_cols_,
3519  DCHECK_LE(primal_num_cols_, RowToColIndex(solution->dual_values.size()));
3520  for (ColIndex col(0); col < primal_num_cols_; ++col) {
3521  RowIndex row = ColToRowIndex(col);
3522  const Fractional lower = variable_lower_bounds_[col];
3523  const Fractional upper = variable_upper_bounds_[col];
3524 
3525  // The new variable value corresponds to the dual value of the dual.
3526  // The shift applied during presolve needs to be removed.
3527  const Fractional shift = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3528  new_primal_values[col] = solution->dual_values[row] + shift;
3529 
3530  // A variable will be VariableStatus::BASIC if the dual constraint is not.
3531  if (solution->constraint_statuses[row] != ConstraintStatus::BASIC) {
3532  new_variable_statuses[col] = VariableStatus::BASIC;
3533  } else {
3534  // Otherwise, the dual value must be zero (if the solution is feasible),
3535  // and the variable is at an exact bound or zero if it is
3536  // VariableStatus::FREE. Note that this works because the bounds are
3537  // shifted to 0.0 in the presolve!
3538  new_variable_statuses[col] = ComputeVariableStatus(shift, lower, upper);
3539  }
3540  }
3541 
3542  // A basic variable that corresponds to slack/surplus variable is the same as
3543  // a basic row. The new variable status (that was just set to
3544  // VariableStatus::BASIC above)
3545  // needs to be corrected and depends on the variable type (slack/surplus).
3546  const ColIndex begin = RowToColIndex(primal_num_rows_);
3547  const ColIndex end = dual_status_correspondence_.size();
3548  DCHECK_GE(solution->variable_statuses.size(), end);
3549  DCHECK_EQ(end - begin, slack_or_surplus_mapping_.size());
3550  for (ColIndex index(begin); index < end; ++index) {
3551  if (solution->variable_statuses[index] == VariableStatus::BASIC) {
3552  const ColIndex col = slack_or_surplus_mapping_[index - begin];
3553  const VariableStatus status = dual_status_correspondence_[index];
3554 
3555  // The new variable value is set to its exact bound because the dual
3556  // variable value can be imprecise.
3557  new_variable_statuses[col] = status;
3560  new_primal_values[col] = variable_upper_bounds_[col];
3561  } else {
3563  new_primal_values[col] = variable_lower_bounds_[col];
3564  }
3565  }
3566  }
3567 
3568  // Note the <= in the DCHECK, since we may need to add variables when taking
3569  // the dual.
3570  DCHECK_LE(primal_num_rows_, ColToRowIndex(solution->primal_values.size()));
3571  DenseColumn new_dual_values(primal_num_rows_, 0.0);
3572  ConstraintStatusColumn new_constraint_statuses(primal_num_rows_,
3574 
3575  // Note that the sign need to be corrected because of the special behavior of
3576  // PopulateFromDual() on a maximization problem, see the comment in the
3577  // declaration of PopulateFromDual().
3578  Fractional sign = primal_is_maximization_problem_ ? -1 : 1;
3579  for (RowIndex row(0); row < primal_num_rows_; ++row) {
3580  const ColIndex col = RowToColIndex(row);
3581  new_dual_values[row] = sign * solution->primal_values[col];
3582 
3583  // A constraint will be ConstraintStatus::BASIC if the dual variable is not.
3584  if (solution->variable_statuses[col] != VariableStatus::BASIC) {
3585  new_constraint_statuses[row] = ConstraintStatus::BASIC;
3586  if (duplicated_rows_[row] != kInvalidCol) {
3587  if (solution->variable_statuses[duplicated_rows_[row]] ==
3589  // The duplicated row is always about the lower bound.
3590  new_constraint_statuses[row] = ConstraintStatus::AT_LOWER_BOUND;
3591  }
3592  }
3593  } else {
3594  // ConstraintStatus::AT_LOWER_BOUND/ConstraintStatus::AT_UPPER_BOUND/
3595  // ConstraintStatus::FIXED depend on the type of the constraint at this
3596  // position.
3597  new_constraint_statuses[row] =
3598  VariableToConstraintStatus(dual_status_correspondence_[col]);
3599  }
3600 
3601  // If the original row was duplicated, we need to take into account the
3602  // value of the corresponding dual column.
3603  if (duplicated_rows_[row] != kInvalidCol) {
3604  new_dual_values[row] +=
3605  sign * solution->primal_values[duplicated_rows_[row]];
3606  }
3607 
3608  // Because non-basic variable values are exactly at one of their bounds, a
3609  // new basic constraint will have a dual value exactly equal to zero.
3610  DCHECK(new_dual_values[row] == 0 ||
3611  new_constraint_statuses[row] != ConstraintStatus::BASIC);
3612  }
3613 
3614  solution->status = ChangeStatusToDualStatus(solution->status);
3615  new_primal_values.swap(solution->primal_values);
3616  new_dual_values.swap(solution->dual_values);
3617  new_variable_statuses.swap(solution->variable_statuses);
3618  new_constraint_statuses.swap(solution->constraint_statuses);
3619 }
3620 
3622  ProblemStatus status) const {
3623  switch (status) {
3636  default:
3637  return status;
3638  }
3639 }
3640 
3641 // --------------------------------------------------------
3642 // ShiftVariableBoundsPreprocessor
3643 // --------------------------------------------------------
3644 
3647  RETURN_VALUE_IF_NULL(lp, false);
3648 
3649  // Save the linear program bounds before shifting them.
3650  bool all_variable_domains_contain_zero = true;
3651  const ColIndex num_cols = lp->num_variables();
3652  variable_initial_lbs_.assign(num_cols, 0.0);
3653  variable_initial_ubs_.assign(num_cols, 0.0);
3654  for (ColIndex col(0); col < num_cols; ++col) {
3655  variable_initial_lbs_[col] = lp->variable_lower_bounds()[col];
3656  variable_initial_ubs_[col] = lp->variable_upper_bounds()[col];
3657  if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3658  all_variable_domains_contain_zero = false;
3659  }
3660  }
3661  VLOG(1) << "Maximum variable bounds magnitude (before shift): "
3662  << ComputeMaxVariableBoundsMagnitude(*lp);
3663 
3664  // Abort early if there is nothing to do.
3665  if (all_variable_domains_contain_zero) return false;
3666 
3667  // Shift the variable bounds and compute the changes to the constraint bounds
3668  // and objective offset in a precise way.
3669  int num_bound_shifts = 0;
3670  const RowIndex num_rows = lp->num_constraints();
3671  KahanSum objective_offset;
3672  absl::StrongVector<RowIndex, KahanSum> row_offsets(num_rows.value());
3673  offsets_.assign(num_cols, 0.0);
3674  for (ColIndex col(0); col < num_cols; ++col) {
3675  if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3676  Fractional offset = MinInMagnitudeOrZeroIfInfinite(
3677  variable_initial_lbs_[col], variable_initial_ubs_[col]);
3678  if (in_mip_context_ && lp->IsVariableInteger(col)) {
3679  // In the integer case, we truncate the number because if for instance
3680  // the lower bound is a positive integer + epsilon, we only want to
3681  // shift by the integer and leave the lower bound at epsilon.
3682  //
3683  // TODO(user): This would not be needed, if we always make the bound
3684  // of an integer variable integer before applying this preprocessor.
3685  offset = trunc(offset);
3686  } else {
3687  DCHECK_NE(offset, 0.0);
3688  }
3689  offsets_[col] = offset;
3690  lp->SetVariableBounds(col, variable_initial_lbs_[col] - offset,
3691  variable_initial_ubs_[col] - offset);
3692  const SparseColumn& sparse_column = lp->GetSparseColumn(col);
3693  for (const SparseColumn::Entry e : sparse_column) {
3694  row_offsets[e.row()].Add(e.coefficient() * offset);
3695  }
3696  objective_offset.Add(lp->objective_coefficients()[col] * offset);
3697  ++num_bound_shifts;
3698  }
3699  }
3700  VLOG(1) << "Maximum variable bounds magnitude (after " << num_bound_shifts
3701  << " shifts): " << ComputeMaxVariableBoundsMagnitude(*lp);
3702 
3703  // Apply the changes to the constraint bound and objective offset.
3704  for (RowIndex row(0); row < num_rows; ++row) {
3705  lp->SetConstraintBounds(
3706  row, lp->constraint_lower_bounds()[row] - row_offsets[row].Value(),
3707  lp->constraint_upper_bounds()[row] - row_offsets[row].Value());
3708  }
3709  lp->SetObjectiveOffset(lp->objective_offset() + objective_offset.Value());
3710  return true;
3711 }
3712 
3714  ProblemSolution* solution) const {
3716  RETURN_IF_NULL(solution);
3717  const ColIndex num_cols = solution->variable_statuses.size();
3718  for (ColIndex col(0); col < num_cols; ++col) {
3719  if (in_mip_context_) {
3720  solution->primal_values[col] += offsets_[col];
3721  } else {
3722  switch (solution->variable_statuses[col]) {
3724  ABSL_FALLTHROUGH_INTENDED;
3726  solution->primal_values[col] = variable_initial_lbs_[col];
3727  break;
3729  solution->primal_values[col] = variable_initial_ubs_[col];
3730  break;
3731  case VariableStatus::BASIC:
3732  solution->primal_values[col] += offsets_[col];
3733  break;
3734  case VariableStatus::FREE:
3735  break;
3736  }
3737  }
3738  }
3739 }
3740 
3741 // --------------------------------------------------------
3742 // ScalingPreprocessor
3743 // --------------------------------------------------------
3744 
3747  RETURN_VALUE_IF_NULL(lp, false);
3748  if (!parameters_.use_scaling()) return false;
3749 
3750  // Save the linear program bounds before scaling them.
3751  const ColIndex num_cols = lp->num_variables();
3752  variable_lower_bounds_.assign(num_cols, 0.0);
3753  variable_upper_bounds_.assign(num_cols, 0.0);
3754  for (ColIndex col(0); col < num_cols; ++col) {
3755  variable_lower_bounds_[col] = lp->variable_lower_bounds()[col];
3756  variable_upper_bounds_[col] = lp->variable_upper_bounds()[col];
3757  }
3758 
3759  // See the doc of these functions for more details.
3760  // It is important to call Scale() before the other two.
3761  Scale(lp, &scaler_, parameters_.scaling_method());
3762  cost_scaling_factor_ = lp->ScaleObjective(parameters_.cost_scaling());
3763  bound_scaling_factor_ = lp->ScaleBounds();
3764 
3765  return true;
3766 }
3767 
3770  RETURN_IF_NULL(solution);
3771 
3772  scaler_.ScaleRowVector(false, &(solution->primal_values));
3773  for (ColIndex col(0); col < solution->primal_values.size(); ++col) {
3774  solution->primal_values[col] *= bound_scaling_factor_;
3775  }
3776 
3777  scaler_.ScaleColumnVector(false, &(solution->dual_values));
3778  for (RowIndex row(0); row < solution->dual_values.size(); ++row) {
3779  solution->dual_values[row] *= cost_scaling_factor_;
3780  }
3781 
3782  // Make sure the variable are at they exact bounds according to their status.
3783  // This just remove a really low error (about 1e-15) but allows to keep the
3784  // variables at their exact bounds.
3785  const ColIndex num_cols = solution->primal_values.size();
3786  for (ColIndex col(0); col < num_cols; ++col) {
3787  switch (solution->variable_statuses[col]) {
3789  ABSL_FALLTHROUGH_INTENDED;
3791  solution->primal_values[col] = variable_upper_bounds_[col];
3792  break;
3794  solution->primal_values[col] = variable_lower_bounds_[col];
3795  break;
3796  case VariableStatus::FREE:
3797  ABSL_FALLTHROUGH_INTENDED;
3798  case VariableStatus::BASIC:
3799  break;
3800  }
3801  }
3802 }
3803 
3804 // --------------------------------------------------------
3805 // ToMinimizationPreprocessor
3806 // --------------------------------------------------------
3807 
3810  RETURN_VALUE_IF_NULL(lp, false);
3811  if (lp->IsMaximizationProblem()) {
3812  for (ColIndex col(0); col < lp->num_variables(); ++col) {
3813  const Fractional coeff = lp->objective_coefficients()[col];
3814  if (coeff != 0.0) {
3815  lp->SetObjectiveCoefficient(col, -coeff);
3816  }
3817  }
3818  lp->SetMaximizationProblem(false);
3821  }
3822  return false;
3823 }
3824 
3826  ProblemSolution* solution) const {}
3827 
3828 // --------------------------------------------------------
3829 // AddSlackVariablesPreprocessor
3830 // --------------------------------------------------------
3831 
3834  RETURN_VALUE_IF_NULL(lp, false);
3836  /*detect_integer_constraints=*/true);
3837  first_slack_col_ = lp->GetFirstSlackVariable();
3838  return true;
3839 }
3840 
3842  ProblemSolution* solution) const {
3844  RETURN_IF_NULL(solution);
3845 
3846  // Compute constraint statuses from statuses of slack variables.
3847  const RowIndex num_rows = solution->dual_values.size();
3848  for (RowIndex row(0); row < num_rows; ++row) {
3849  const ColIndex slack_col = first_slack_col_ + RowToColIndex(row);
3850  const VariableStatus variable_status =
3851  solution->variable_statuses[slack_col];
3852  ConstraintStatus constraint_status = ConstraintStatus::FREE;
3853  // The slack variables have reversed bounds - if the value of the variable
3854  // is at one bound, the value of the constraint is at the opposite bound.
3855  switch (variable_status) {
3857  constraint_status = ConstraintStatus::AT_UPPER_BOUND;
3858  break;
3860  constraint_status = ConstraintStatus::AT_LOWER_BOUND;
3861  break;
3862  default:
3863  constraint_status = VariableToConstraintStatus(variable_status);
3864  break;
3865  }
3866  solution->constraint_statuses[row] = constraint_status;
3867  }
3868 
3869  // Drop the primal values and variable statuses for slack variables.
3870  solution->primal_values.resize(first_slack_col_, 0.0);
3871  solution->variable_statuses.resize(first_slack_col_, VariableStatus::FREE);
3872 }
3873 
3874 } // namespace glop
3875 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:895
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:894
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:897
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
#define VLOG(verboselevel)
Definition: base/logging.h:986
iterator erase(const_iterator pos)
iterator insert(const_iterator pos, const value_type &x)
void resize(size_type new_size)
size_type size() const
bool empty() const
void push_back(const value_type &x)
void swap(StrongVector &x)
void Add(const FpNumber &value)
Definition: accurate_sum.h:29
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
void RecoverSolution(ProblemSolution *solution) const final
void MarkColumnForDeletionWithState(ColIndex col, Fractional value, VariableStatus status)
const DenseBooleanRow & GetMarkedColumns() const
Definition: preprocessor.h:193
void RestoreDeletedColumns(ProblemSolution *solution) const
const SparseColumn & SavedOrEmptyColumn(ColIndex col) const
void SaveColumnIfNotAlreadyDone(ColIndex col, const SparseColumn &column)
void SaveColumn(ColIndex col, const SparseColumn &column)
const SparseColumn & SavedColumn(ColIndex col) const
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
ProblemStatus ChangeStatusToDualStatus(ProblemStatus status) const
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
SparseMatrix * GetMutableTransposeSparseMatrix()
Definition: lp_data.cc:386
void SetObjectiveScalingFactor(Fractional objective_scaling_factor)
Definition: lp_data.cc:336
DenseColumn * mutable_constraint_upper_bounds()
Definition: lp_data.h:553
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:249
const SparseMatrix & GetTransposeSparseMatrix() const
Definition: lp_data.cc:376
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:331
const SparseMatrix & GetSparseMatrix() const
Definition: lp_data.h:175
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
const DenseColumn & constraint_lower_bounds() const
Definition: lp_data.h:215
Fractional ScaleObjective(GlopParameters::CostScalingAlgorithm method)
Definition: lp_data.cc:1188
const DenseRow & objective_coefficients() const
Definition: lp_data.h:223
Fractional GetObjectiveCoefficientForMinimizationVersion(ColIndex col) const
Definition: lp_data.cc:419
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:309
void Swap(LinearProgram *linear_program)
Definition: lp_data.cc:1031
SparseColumn * GetMutableSparseColumn(ColIndex col)
Definition: lp_data.cc:413
void AddSlackVariablesWhereNecessary(bool detect_integer_constraints)
Definition: lp_data.cc:697
const DenseColumn & constraint_upper_bounds() const
Definition: lp_data.h:218
bool IsVariableInteger(ColIndex col) const
Definition: lp_data.cc:295
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:326
void DeleteRows(const DenseBooleanColumn &rows_to_delete)
Definition: lp_data.cc:1258
void DeleteColumns(const DenseBooleanRow &columns_to_delete)
Definition: lp_data.cc:1065
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
void PopulateFromDual(const LinearProgram &dual, RowToColMapping *duplicated_rows)
Definition: lp_data.cc:764
Fractional objective_scaling_factor() const
Definition: lp_data.h:261
void SetMaximizationProblem(bool maximize)
Definition: lp_data.cc:343
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition: lp_data.cc:409
DenseColumn * mutable_constraint_lower_bounds()
Definition: lp_data.h:550
void RecoverSolution(ProblemSolution *solution) const override
bool IsSmallerWithinPreprocessorZeroTolerance(Fractional a, Fractional b) const
Definition: preprocessor.h:84
Preprocessor(const GlopParameters *parameters)
Definition: preprocessor.cc:47
const GlopParameters & parameters_
Definition: preprocessor.h:92
bool IsSmallerWithinFeasibilityTolerance(Fractional a, Fractional b) const
Definition: preprocessor.h:80
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RestoreDeletedRows(ProblemSolution *solution) const
const DenseBooleanColumn & GetMarkedRows() const
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void RecoverSolution(ProblemSolution *solution) const final
void Undo(const GlopParameters &parameters, const SparseColumn &saved_column, const SparseColumn &saved_row, ProblemSolution *solution) const
SingletonUndo(OperationType type, const LinearProgram &lp, MatrixEntry e, ConstraintStatus status)
SparseColumn * mutable_column(ColIndex col)
Definition: sparse.h:181
const SparseColumn & column(ColIndex col) const
Definition: sparse.h:180
void ScaleColumnVector(bool up, DenseColumn *column_vector) const
void ScaleRowVector(bool up, DenseRow *row_vector) const
Fractional LookUpCoefficient(Index index) const
void RemoveNearZeroEntriesWithWeights(Fractional threshold, const DenseVector &weights)
void AddMultipleToSparseVectorAndDeleteCommonIndex(Fractional multiplier, Index removed_common_index, Fractional drop_tolerance, SparseVector *accumulator_vector) const
void MultiplyByConstant(Fractional factor)
void assign(IntType size, const T &v)
Definition: lp_types.h:275
Fractional SumWithoutUb(Fractional c) const
Fractional SumWithoutLb(Fractional c) const
void RecoverSolution(ProblemSolution *solution) const final
void RemoveZeroCostUnconstrainedVariable(ColIndex col, Fractional target_bound, LinearProgram *lp)
void RecoverSolution(ProblemSolution *solution) const final
int64_t b
int64_t a
SatParameters parameters
SharedTimeLimit * time_limit
const std::string name
int64_t value
double upper_bound
double lower_bound
const int INFO
Definition: log_severity.h:31
RowIndex row
Definition: markowitz.cc:182
const RowIndex kInvalidRow(-1)
Fractional ScalarProduct(const DenseRowOrColumn1 &u, const DenseRowOrColumn2 &v)
Fractional PreciseScalarProduct(const DenseRowOrColumn &u, const DenseRowOrColumn2 &v)
StrictITIVector< ColIndex, Fractional > DenseRow
Definition: lp_types.h:300
std::string GetProblemStatusString(ProblemStatus problem_status)
Definition: lp_types.cc:19
void FixConstraintWithFixedStatuses(const DenseColumn &row_lower_bounds, const DenseColumn &row_upper_bounds, ProblemSolution *solution)
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
bool IsFinite(Fractional value)
Definition: lp_types.h:91
RowIndex ColToRowIndex(ColIndex col)
Definition: lp_types.h:52
ConstraintStatus VariableToConstraintStatus(VariableStatus status)
Definition: lp_types.cc:109
ColMapping FindProportionalColumns(const SparseMatrix &matrix, Fractional tolerance)
void Scale(LinearProgram *lp, SparseMatrixScaler *scaler)
const double kInfinity
Definition: lp_types.h:84
const ColIndex kInvalidCol(-1)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
Collection of objects used to extend the Constraint Solver library.
bool IsSmallerWithinTolerance(FloatType x, FloatType y, FloatType tolerance)
Definition: fp_utils.h:153
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition: fp_utils.h:161
BeginEndReverseIteratorWrapper< Container > Reverse(const Container &c)
Definition: iterators.h:98
int index
Definition: pack.cc:509
EntryIndex num_entries
Fractional scaled_cost
ColIndex col
ColIndex representative
#define RUN_PREPROCESSOR(name)
Definition: preprocessor.cc:59
int64_t delta
Definition: resource.cc:1692
#define RETURN_IF_NULL(x)
Definition: return_macros.h:20
#define RETURN_VALUE_IF_NULL(x, v)
Definition: return_macros.h:26
Fractional target_bound
int64_t bound
int64_t coefficient
int64_t cost
std::vector< double > lower_bounds
std::vector< double > upper_bounds
#define SCOPED_INSTRUCTION_COUNT(time_limit)
Definition: stats.h:439
ConstraintStatusColumn constraint_statuses
Definition: lp_data.h:686
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41