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