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