OR-Tools  8.1
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.
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.
1331  size);
1333  size);
1334 
1335  // Initialize the sums by adding all the bounds of the variables.
1336  for (ColIndex col(0); col < num_cols; ++col) {
1337  const Fractional lower_bound = lp->variable_lower_bounds()[col];
1338  const Fractional upper_bound = lp->variable_upper_bounds()[col];
1339  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1340  Fractional entry_lb = e.coefficient() * lower_bound;
1341  Fractional entry_ub = e.coefficient() * upper_bound;
1342  if (e.coefficient() < 0.0) std::swap(entry_lb, entry_ub);
1343  lb_sums[e.row()].Add(entry_lb);
1344  ub_sums[e.row()].Add(entry_ub);
1345  }
1346  }
1347 
1348  // The inequality
1349  // constraint_lb <= sum(entries) <= constraint_ub
1350  // can be rewritten as:
1351  // sum(entries) + (-activity) = 0,
1352  // where (-activity) has bounds [-constraint_ub, -constraint_lb].
1353  // We use this latter convention to simplify our code.
1354  for (RowIndex row(0); row < num_rows; ++row) {
1355  lb_sums[row].Add(-lp->constraint_upper_bounds()[row]);
1356  ub_sums[row].Add(-lp->constraint_lower_bounds()[row]);
1357  }
1358 
1359  // Once a variable is freed, none of the rows in which it appears can be
1360  // used to make another variable free.
1361  DenseBooleanColumn used_rows(num_rows, false);
1362  postsolve_status_of_free_variables_.assign(num_cols, VariableStatus::FREE);
1363  variable_offsets_.assign(num_cols, 0.0);
1364 
1365  // It is better to process columns with a small degree first:
1366  // - Degree-two columns make it possible to remove a row from the problem.
1367  // - This way there is more chance to make more free columns.
1368  // - It is better to have low degree free columns since a free column will
1369  // always end up in the simplex basis (except if there is more than the
1370  // number of rows in the problem).
1371  //
1372  // TODO(user): Only process degree-two so in subsequent passes more degree-two
1373  // columns could be made free. And only when no other reduction can be
1374  // applied, process the higher degree column?
1375  //
1376  // TODO(user): Be smarter about the order that maximizes the number of free
1377  // column. For instance if we have 3 doubleton columns that use the rows (1,2)
1378  // (2,3) and (3,4) then it is better not to make (2,3) free so the two other
1379  // two can be made free.
1380  std::vector<ColWithDegree> col_by_degree;
1381  for (ColIndex col(0); col < num_cols; ++col) {
1382  col_by_degree.push_back(
1383  ColWithDegree(col, lp->GetSparseColumn(col).num_entries()));
1384  }
1385  std::sort(col_by_degree.begin(), col_by_degree.end());
1386 
1387  // Now loop over the columns in order and make all implied-free columns free.
1388  int num_already_free_variables = 0;
1389  int num_implied_free_variables = 0;
1390  int num_fixed_variables = 0;
1391  for (ColWithDegree col_with_degree : col_by_degree) {
1392  const ColIndex col = col_with_degree.col;
1393 
1394  // If the variable is alreay free or fixed, we do nothing.
1395  const Fractional lower_bound = lp->variable_lower_bounds()[col];
1396  const Fractional upper_bound = lp->variable_upper_bounds()[col];
1397  if (!IsFinite(lower_bound) && !IsFinite(upper_bound)) {
1398  ++num_already_free_variables;
1399  continue;
1400  }
1401  if (lower_bound == upper_bound) continue;
1402 
1403  // Detect if the variable is implied free.
1404  Fractional overall_implied_lb = -kInfinity;
1405  Fractional overall_implied_ub = kInfinity;
1406  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1407  // If the row contains another implied free variable, then the bounds
1408  // implied by it will just be [-kInfinity, kInfinity] so we can skip it.
1409  if (used_rows[e.row()]) continue;
1410 
1411  // This is the contribution of this column to the sum above.
1412  const Fractional coeff = e.coefficient();
1413  Fractional entry_lb = coeff * lower_bound;
1414  Fractional entry_ub = coeff * upper_bound;
1415  if (coeff < 0.0) std::swap(entry_lb, entry_ub);
1416 
1417  // If X is the variable with index col and Y the sum of all the other
1418  // variables and of (-activity), then coeff * X + Y = 0. Since Y's bounds
1419  // are [lb_sum without X, ub_sum without X], it is easy to derive the
1420  // implied bounds on X.
1421  Fractional implied_lb = -ub_sums[e.row()].SumWithout(entry_ub) / coeff;
1422  Fractional implied_ub = -lb_sums[e.row()].SumWithout(entry_lb) / coeff;
1423  if (coeff < 0.0) std::swap(implied_lb, implied_ub);
1424  overall_implied_lb = std::max(overall_implied_lb, implied_lb);
1425  overall_implied_ub = std::min(overall_implied_ub, implied_ub);
1426  }
1427 
1428  // Detect infeasible cases.
1429  if (!IsSmallerWithinFeasibilityTolerance(overall_implied_lb, upper_bound) ||
1430  !IsSmallerWithinFeasibilityTolerance(lower_bound, overall_implied_ub) ||
1431  !IsSmallerWithinFeasibilityTolerance(overall_implied_lb,
1432  overall_implied_ub)) {
1433  status_ = ProblemStatus::PRIMAL_INFEASIBLE;
1434  return false;
1435  }
1436 
1437  // Detect fixed variable cases (there are two kinds).
1438  // Note that currently we don't do anything here except counting them.
1440  overall_implied_lb) ||
1441  IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1442  lower_bound)) {
1443  // This case is already dealt with by the
1444  // ForcingAndImpliedFreeConstraintPreprocessor since it means that (at
1445  // least) one of the row is forcing.
1446  ++num_fixed_variables;
1447  continue;
1448  } else if (IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1449  overall_implied_lb)) {
1450  // TODO(user): As of July 2013, with our preprocessors this case is never
1451  // triggered on the Netlib. Note however that if it appears it can have a
1452  // big impact since by fixing the variable, the two involved constraints
1453  // are forcing and can be removed too (with all the variables they touch).
1454  // The postsolve step is quite involved though.
1455  ++num_fixed_variables;
1456  continue;
1457  }
1458 
1459  // Is the variable implied free? Note that for an infinite lower_bound or
1460  // upper_bound the respective inequality is always true.
1462  overall_implied_lb) &&
1463  IsSmallerWithinPreprocessorZeroTolerance(overall_implied_ub,
1464  upper_bound)) {
1465  ++num_implied_free_variables;
1467  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
1468  used_rows[e.row()] = true;
1469  }
1470 
1471  // This is a tricky part. We're freeing this variable, which means that
1472  // after solve, the modified variable will have status either
1473  // VariableStatus::FREE or VariableStatus::BASIC. In the former case
1474  // (VariableStatus::FREE, value = 0.0), we need to "fix" the
1475  // status (technically, our variable isn't free!) to either
1476  // VariableStatus::AT_LOWER_BOUND or VariableStatus::AT_UPPER_BOUND
1477  // (note that we skipped fixed variables), and "fix" the value to that
1478  // bound's value as well. We make the decision and the precomputation
1479  // here: we simply offset the variable by one of its bounds, and store
1480  // which bound that was. Note that if the modified variable turns out to
1481  // be VariableStatus::BASIC, we'll simply un-offset its value too;
1482  // and let the status be VariableStatus::BASIC.
1483  //
1484  // TODO(user): This trick is already used in the DualizerPreprocessor,
1485  // maybe we should just have a preprocessor that shifts all the variables
1486  // bounds to have at least one of them at 0.0, will that improve precision
1487  // and speed of the simplex? One advantage is that we can compute the
1488  // new constraint bounds with better precision using AccurateSum.
1489  DCHECK_NE(lower_bound, upper_bound);
1490  const Fractional offset =
1491  MinInMagnitudeOrZeroIfInfinite(lower_bound, upper_bound);
1492  if (offset != 0.0) {
1493  variable_offsets_[col] = offset;
1494  SubtractColumnMultipleFromConstraintBound(col, offset, lp);
1495  }
1496  postsolve_status_of_free_variables_[col] =
1497  ComputeVariableStatus(offset, lower_bound, upper_bound);
1498  }
1499  }
1500  VLOG(1) << num_already_free_variables << " free variables in the problem.";
1501  VLOG(1) << num_implied_free_variables << " implied free columns.";
1502  VLOG(1) << num_fixed_variables << " variables can be fixed.";
1503  return num_implied_free_variables > 0;
1504 }
1505 
1508  RETURN_IF_NULL(solution);
1509  const ColIndex num_cols = solution->variable_statuses.size();
1510  for (ColIndex col(0); col < num_cols; ++col) {
1511  // Skip variables that the preprocessor didn't change.
1512  if (postsolve_status_of_free_variables_[col] == VariableStatus::FREE) {
1513  DCHECK_EQ(0.0, variable_offsets_[col]);
1514  continue;
1515  }
1516  if (solution->variable_statuses[col] == VariableStatus::FREE) {
1517  solution->variable_statuses[col] =
1518  postsolve_status_of_free_variables_[col];
1519  } else {
1521  }
1522  solution->primal_values[col] += variable_offsets_[col];
1523  }
1524 }
1525 
1526 // --------------------------------------------------------
1527 // DoubletonFreeColumnPreprocessor
1528 // --------------------------------------------------------
1529 
1532  RETURN_VALUE_IF_NULL(lp, false);
1533  // We will modify the matrix transpose and then push the change to the linear
1534  // program by calling lp->UseTransposeMatrixAsReference(). Note
1535  // that original_matrix will not change during this preprocessor run.
1536  const SparseMatrix& original_matrix = lp->GetSparseMatrix();
1537  SparseMatrix* transpose = lp->GetMutableTransposeSparseMatrix();
1538 
1539  const ColIndex num_cols(lp->num_variables());
1540  for (ColIndex doubleton_col(0); doubleton_col < num_cols; ++doubleton_col) {
1541  // Only consider doubleton free columns.
1542  if (original_matrix.column(doubleton_col).num_entries() != 2) continue;
1543  if (lp->variable_lower_bounds()[doubleton_col] != -kInfinity) continue;
1544  if (lp->variable_upper_bounds()[doubleton_col] != kInfinity) continue;
1545 
1546  // Collect the two column items. Note that we skip a column involving a
1547  // deleted row since it is no longer a doubleton then.
1548  RestoreInfo r;
1549  r.col = doubleton_col;
1550  r.objective_coefficient = lp->objective_coefficients()[r.col];
1551  int index = 0;
1552  for (const SparseColumn::Entry e : original_matrix.column(r.col)) {
1553  if (row_deletion_helper_.IsRowMarked(e.row())) break;
1554  r.row[index] = e.row();
1555  r.coeff[index] = e.coefficient();
1556  DCHECK_NE(0.0, e.coefficient());
1557  ++index;
1558  }
1559  if (index != NUM_ROWS) continue;
1560 
1561  // Since the column didn't touch any previously deleted row, we are sure
1562  // that the coefficients were left untouched.
1563  DCHECK_EQ(r.coeff[DELETED], transpose->column(RowToColIndex(r.row[DELETED]))
1564  .LookUpCoefficient(ColToRowIndex(r.col)));
1565  DCHECK_EQ(r.coeff[MODIFIED],
1566  transpose->column(RowToColIndex(r.row[MODIFIED]))
1567  .LookUpCoefficient(ColToRowIndex(r.col)));
1568 
1569  // We prefer deleting the row with the larger coefficient magnitude because
1570  // we will divide by this magnitude. TODO(user): Impact?
1571  if (std::abs(r.coeff[DELETED]) < std::abs(r.coeff[MODIFIED])) {
1572  std::swap(r.coeff[DELETED], r.coeff[MODIFIED]);
1573  std::swap(r.row[DELETED], r.row[MODIFIED]);
1574  }
1575 
1576  // Save the deleted row for postsolve. Note that we remove it from the
1577  // transpose at the same time. This last operation is not strictly needed,
1578  // but it is faster to do it this way (both here and later when we will take
1579  // the transpose of the final transpose matrix).
1580  r.deleted_row_as_column.Swap(
1581  transpose->mutable_column(RowToColIndex(r.row[DELETED])));
1582 
1583  // Move the bound of the deleted constraint to the initially free variable.
1584  {
1585  Fractional new_variable_lb =
1586  lp->constraint_lower_bounds()[r.row[DELETED]];
1587  Fractional new_variable_ub =
1588  lp->constraint_upper_bounds()[r.row[DELETED]];
1589  new_variable_lb /= r.coeff[DELETED];
1590  new_variable_ub /= r.coeff[DELETED];
1591  if (r.coeff[DELETED] < 0.0) std::swap(new_variable_lb, new_variable_ub);
1592  lp->SetVariableBounds(r.col, new_variable_lb, new_variable_ub);
1593  }
1594 
1595  // Add a multiple of the deleted row to the modified row except on
1596  // column r.col where the coefficient will be left unchanged.
1597  r.deleted_row_as_column.AddMultipleToSparseVectorAndIgnoreCommonIndex(
1598  -r.coeff[MODIFIED] / r.coeff[DELETED], ColToRowIndex(r.col),
1599  parameters_.drop_tolerance(),
1600  transpose->mutable_column(RowToColIndex(r.row[MODIFIED])));
1601 
1602  // We also need to correct the objective value of the variables involved in
1603  // the deleted row.
1604  if (r.objective_coefficient != 0.0) {
1605  for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1606  const ColIndex col = RowToColIndex(e.row());
1607  if (col == r.col) continue;
1608  const Fractional new_objective =
1609  lp->objective_coefficients()[col] -
1610  e.coefficient() * r.objective_coefficient / r.coeff[DELETED];
1611 
1612  // This detects if the objective should actually be zero, but because of
1613  // the numerical error in the formula above, we have a really low
1614  // objective instead. The logic is the same as in
1615  // AddMultipleToSparseVectorAndIgnoreCommonIndex().
1616  if (std::abs(new_objective) > parameters_.drop_tolerance()) {
1617  lp->SetObjectiveCoefficient(col, new_objective);
1618  } else {
1619  lp->SetObjectiveCoefficient(col, 0.0);
1620  }
1621  }
1622  }
1623  row_deletion_helper_.MarkRowForDeletion(r.row[DELETED]);
1624  restore_stack_.push_back(r);
1625  }
1626 
1627  if (!row_deletion_helper_.IsEmpty()) {
1628  // The order is important.
1630  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1631  return true;
1632  }
1633  return false;
1634 }
1635 
1637  ProblemSolution* solution) const {
1639  row_deletion_helper_.RestoreDeletedRows(solution);
1640  for (const RestoreInfo& r : Reverse(restore_stack_)) {
1641  // Correct the constraint status.
1642  switch (solution->variable_statuses[r.col]) {
1644  solution->constraint_statuses[r.row[DELETED]] =
1646  break;
1648  solution->constraint_statuses[r.row[DELETED]] =
1649  r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_UPPER_BOUND
1651  break;
1653  solution->constraint_statuses[r.row[DELETED]] =
1654  r.coeff[DELETED] > 0.0 ? ConstraintStatus::AT_LOWER_BOUND
1656  break;
1657  case VariableStatus::FREE:
1658  solution->constraint_statuses[r.row[DELETED]] = ConstraintStatus::FREE;
1659  break;
1660  case VariableStatus::BASIC:
1661  // The default is good here:
1662  DCHECK_EQ(solution->constraint_statuses[r.row[DELETED]],
1664  break;
1665  }
1666 
1667  // Correct the primal variable value.
1668  {
1669  Fractional new_variable_value = solution->primal_values[r.col];
1670  for (const SparseColumn::Entry e : r.deleted_row_as_column) {
1671  const ColIndex col = RowToColIndex(e.row());
1672  if (col == r.col) continue;
1673  new_variable_value -= (e.coefficient() / r.coeff[DELETED]) *
1674  solution->primal_values[RowToColIndex(e.row())];
1675  }
1676  solution->primal_values[r.col] = new_variable_value;
1677  }
1678 
1679  // In all cases, we will make the variable r.col VariableStatus::BASIC, so
1680  // we need to adjust the dual value of the deleted row so that the variable
1681  // reduced cost is zero. Note that there is nothing to do if the variable
1682  // was already basic.
1683  if (solution->variable_statuses[r.col] != VariableStatus::BASIC) {
1684  solution->variable_statuses[r.col] = VariableStatus::BASIC;
1685  Fractional current_reduced_cost =
1686  r.objective_coefficient -
1687  r.coeff[MODIFIED] * solution->dual_values[r.row[MODIFIED]];
1688  // We want current_reduced_cost - dual * coeff = 0, so:
1689  solution->dual_values[r.row[DELETED]] =
1690  current_reduced_cost / r.coeff[DELETED];
1691  } else {
1692  DCHECK_EQ(solution->dual_values[r.row[DELETED]], 0.0);
1693  }
1694  }
1695 }
1696 
1697 // --------------------------------------------------------
1698 // UnconstrainedVariablePreprocessor
1699 // --------------------------------------------------------
1700 
1701 namespace {
1702 
1703 // Does the constraint block the variable to go to infinity in the given
1704 // direction? direction is either positive or negative and row is the index of
1705 // the constraint.
1706 bool IsConstraintBlockingVariable(const LinearProgram& lp, Fractional direction,
1707  RowIndex row) {
1708  return direction > 0.0 ? lp.constraint_upper_bounds()[row] != kInfinity
1710 }
1711 
1712 } // namespace
1713 
1715  ColIndex col, Fractional target_bound, LinearProgram* lp) {
1716  DCHECK_EQ(0.0, lp->objective_coefficients()[col]);
1717  if (deleted_rows_as_column_.IsEmpty()) {
1718  deleted_columns_.PopulateFromZero(lp->num_constraints(),
1719  lp->num_variables());
1720  deleted_rows_as_column_.PopulateFromZero(
1723  rhs_.resize(lp->num_constraints(), 0.0);
1724  activity_sign_correction_.resize(lp->num_constraints(), 1.0);
1725  is_unbounded_.resize(lp->num_variables(), false);
1726  }
1727  const bool is_unbounded_up = (target_bound == kInfinity);
1728  const SparseColumn& column = lp->GetSparseColumn(col);
1729  for (const SparseColumn::Entry e : column) {
1730  const RowIndex row = e.row();
1731  if (!row_deletion_helper_.IsRowMarked(row)) {
1732  row_deletion_helper_.MarkRowForDeletion(row);
1733  const ColIndex row_as_col = RowToColIndex(row);
1734  deleted_rows_as_column_.mutable_column(row_as_col)
1736  lp->GetTransposeSparseMatrix().column(row_as_col));
1737  }
1738  const bool is_constraint_upper_bound_relevant =
1739  e.coefficient() > 0.0 ? !is_unbounded_up : is_unbounded_up;
1740  activity_sign_correction_[row] =
1741  is_constraint_upper_bound_relevant ? 1.0 : -1.0;
1742  rhs_[row] = is_constraint_upper_bound_relevant
1743  ? lp->constraint_upper_bounds()[row]
1744  : lp->constraint_lower_bounds()[row];
1745 
1746  // TODO(user): Here, we may render the row free, so subsequent columns
1747  // processed by the columns loop in Run() have more chance to be removed.
1748  // However, we need to be more careful during the postsolve() if we do that.
1749  }
1750  is_unbounded_[col] = true;
1751  Fractional initial_feasible_value = MinInMagnitudeOrZeroIfInfinite(
1753  deleted_columns_.mutable_column(col)->PopulateFromSparseVector(column);
1754  column_deletion_helper_.MarkColumnForDeletionWithState(
1755  col, initial_feasible_value,
1756  ComputeVariableStatus(initial_feasible_value,
1757  lp->variable_lower_bounds()[col],
1758  lp->variable_upper_bounds()[col]));
1759 }
1760 
1763  RETURN_VALUE_IF_NULL(lp, false);
1764 
1765  // To simplify the problem if something is almost zero, we use the low
1766  // tolerance (1e-9 by default) to be defensive. But to detect an infeasibility
1767  // we want to be sure (especially since the problem is not scaled in the
1768  // presolver) so we use an higher tolerance.
1769  //
1770  // TODO(user): Expose it as a parameter. We could rename both to
1771  // preprocessor_low_tolerance and preprocessor_high_tolerance.
1772  const Fractional low_tolerance = parameters_.preprocessor_zero_tolerance();
1773  const Fractional high_tolerance = 1e-4;
1774 
1775  // We start by the dual variable bounds from the constraints.
1776  const RowIndex num_rows = lp->num_constraints();
1777  dual_lb_.assign(num_rows, -kInfinity);
1778  dual_ub_.assign(num_rows, kInfinity);
1779  for (RowIndex row(0); row < num_rows; ++row) {
1780  if (lp->constraint_lower_bounds()[row] == -kInfinity) {
1781  dual_ub_[row] = 0.0;
1782  }
1783  if (lp->constraint_upper_bounds()[row] == kInfinity) {
1784  dual_lb_[row] = 0.0;
1785  }
1786  }
1787 
1788  const ColIndex num_cols = lp->num_variables();
1789  may_have_participated_lb_.assign(num_cols, false);
1790  may_have_participated_ub_.assign(num_cols, false);
1791 
1792  // We maintain a queue of columns to process.
1793  std::deque<ColIndex> columns_to_process;
1794  DenseBooleanRow in_columns_to_process(num_cols, true);
1795  std::vector<RowIndex> changed_rows;
1796  for (ColIndex col(0); col < num_cols; ++col) {
1797  columns_to_process.push_back(col);
1798  }
1799 
1800  // Arbitrary limit to avoid corner cases with long loops.
1801  // TODO(user): expose this as a parameter? IMO it isn't really needed as we
1802  // shouldn't reach this limit except in corner cases.
1803  const int limit = 5 * num_cols.value();
1804  for (int count = 0; !columns_to_process.empty() && count < limit; ++count) {
1805  const ColIndex col = columns_to_process.front();
1806  columns_to_process.pop_front();
1807  in_columns_to_process[col] = false;
1808  if (column_deletion_helper_.IsColumnMarked(col)) continue;
1809 
1810  const SparseColumn& column = lp->GetSparseColumn(col);
1811  const Fractional col_cost =
1813  const Fractional col_lb = lp->variable_lower_bounds()[col];
1814  const Fractional col_ub = lp->variable_upper_bounds()[col];
1815 
1816  // Compute the bounds on the reduced costs of this column.
1819  rc_lb.Add(col_cost);
1820  rc_ub.Add(col_cost);
1821  for (const SparseColumn::Entry e : column) {
1822  if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1823  const Fractional coeff = e.coefficient();
1824  if (coeff > 0.0) {
1825  rc_lb.Add(-coeff * dual_ub_[e.row()]);
1826  rc_ub.Add(-coeff * dual_lb_[e.row()]);
1827  } else {
1828  rc_lb.Add(-coeff * dual_lb_[e.row()]);
1829  rc_ub.Add(-coeff * dual_ub_[e.row()]);
1830  }
1831  }
1832 
1833  // If the reduced cost domain do not contain zero (modulo the tolerance), we
1834  // can move the variable to its corresponding bound. Note that we need to be
1835  // careful that this variable didn't participate in creating the used
1836  // reduced cost bound in the first place.
1837  bool can_be_removed = false;
1839  bool rc_is_away_from_zero;
1840  if (rc_ub.Sum() <= low_tolerance) {
1841  can_be_removed = true;
1842  target_bound = col_ub;
1843  rc_is_away_from_zero = rc_ub.Sum() <= -high_tolerance;
1844  can_be_removed = !may_have_participated_ub_[col];
1845  }
1846  if (rc_lb.Sum() >= -low_tolerance) {
1847  // The second condition is here for the case we can choose one of the two
1848  // directions.
1849  if (!can_be_removed || !IsFinite(target_bound)) {
1850  can_be_removed = true;
1851  target_bound = col_lb;
1852  rc_is_away_from_zero = rc_lb.Sum() >= high_tolerance;
1853  can_be_removed = !may_have_participated_lb_[col];
1854  }
1855  }
1856 
1857  if (can_be_removed) {
1858  if (IsFinite(target_bound)) {
1859  // Note that in MIP context, this assumes that the bounds of an integer
1860  // variable are integer.
1861  column_deletion_helper_.MarkColumnForDeletionWithState(
1862  col, target_bound,
1863  ComputeVariableStatus(target_bound, col_lb, col_ub));
1864  continue;
1865  }
1866 
1867  // If the target bound is infinite and the reduced cost bound is non-zero,
1868  // then the problem is ProblemStatus::INFEASIBLE_OR_UNBOUNDED.
1869  if (rc_is_away_from_zero) {
1870  VLOG(1) << "Problem INFEASIBLE_OR_UNBOUNDED, variable " << col
1871  << " can move to " << target_bound
1872  << " and its reduced cost is in [" << rc_lb.Sum() << ", "
1873  << rc_ub.Sum() << "]";
1874  status_ = ProblemStatus::INFEASIBLE_OR_UNBOUNDED;
1875  return false;
1876  } else {
1877  // We can remove this column and all its constraints! We just need to
1878  // choose proper variable values during the call to RecoverSolution()
1879  // that make all the constraints satisfiable. Unfortunately, this is not
1880  // so easy to do in the general case, so we only deal with a simpler
1881  // case when the cost of the variable is zero, and the constraints do
1882  // not block it in one direction.
1883  //
1884  // TODO(user): deal with the more generic case.
1885  if (col_cost != 0.0) continue;
1886  bool skip = false;
1887  for (const SparseColumn::Entry e : column) {
1888  // Note that it is important to check the rows that are already
1889  // deleted here, otherwise the post-solve will not work.
1890  if (IsConstraintBlockingVariable(*lp, e.coefficient(), e.row())) {
1891  skip = true;
1892  break;
1893  }
1894  }
1895  if (skip) continue;
1896 
1897  // TODO(user): this also works if the variable is integer, but we must
1898  // choose an integer value during the post-solve. Implement this.
1899  if (in_mip_context_) continue;
1901  continue;
1902  }
1903  }
1904 
1905  // The rest of the code will update the dual bounds. There is no need to do
1906  // it if the column was removed or if it is not unconstrained in some
1907  // direction.
1908  DCHECK(!can_be_removed);
1909  if (col_lb != -kInfinity && col_ub != kInfinity) continue;
1910 
1911  // For MIP, we only exploit the constraints. TODO(user): It should probably
1912  // work with only small modification, investigate.
1913  if (in_mip_context_) continue;
1914 
1915  changed_rows.clear();
1916  for (const SparseColumn::Entry e : column) {
1917  if (row_deletion_helper_.IsRowMarked(e.row())) continue;
1918  const Fractional c = e.coefficient();
1919  const RowIndex row = e.row();
1920  if (col_ub == kInfinity) {
1921  if (c > 0.0) {
1922  const Fractional candidate = rc_ub.SumWithout(-c * dual_lb_[row]) / c;
1923  if (candidate < dual_ub_[row]) {
1924  dual_ub_[row] = candidate;
1925  may_have_participated_lb_[col] = true;
1926  changed_rows.push_back(row);
1927  }
1928  } else {
1929  const Fractional candidate = rc_ub.SumWithout(-c * dual_ub_[row]) / c;
1930  if (candidate > dual_lb_[row]) {
1931  dual_lb_[row] = candidate;
1932  may_have_participated_lb_[col] = true;
1933  changed_rows.push_back(row);
1934  }
1935  }
1936  }
1937  if (col_lb == -kInfinity) {
1938  if (c > 0.0) {
1939  const Fractional candidate = rc_lb.SumWithout(-c * dual_ub_[row]) / c;
1940  if (candidate > dual_lb_[row]) {
1941  dual_lb_[row] = candidate;
1942  may_have_participated_ub_[col] = true;
1943  changed_rows.push_back(row);
1944  }
1945  } else {
1946  const Fractional candidate = rc_lb.SumWithout(-c * dual_lb_[row]) / c;
1947  if (candidate < dual_ub_[row]) {
1948  dual_ub_[row] = candidate;
1949  may_have_participated_ub_[col] = true;
1950  changed_rows.push_back(row);
1951  }
1952  }
1953  }
1954  }
1955 
1956  if (!changed_rows.empty()) {
1957  const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
1958  for (const RowIndex row : changed_rows) {
1959  for (const SparseColumn::Entry entry :
1960  transpose.column(RowToColIndex(row))) {
1961  const ColIndex col = RowToColIndex(entry.row());
1962  if (!in_columns_to_process[col]) {
1963  columns_to_process.push_back(col);
1964  in_columns_to_process[col] = true;
1965  }
1966  }
1967  }
1968  }
1969  }
1970 
1971  // Change the rhs to reflect the fixed variables. Note that is important to do
1972  // that after all the calls to RemoveZeroCostUnconstrainedVariable() because
1973  // RemoveZeroCostUnconstrainedVariable() needs to store the rhs before this
1974  // modification!
1975  const ColIndex end = column_deletion_helper_.GetMarkedColumns().size();
1976  for (ColIndex col(0); col < end; ++col) {
1977  if (column_deletion_helper_.IsColumnMarked(col)) {
1978  const Fractional target_bound =
1979  column_deletion_helper_.GetStoredValue()[col];
1980  SubtractColumnMultipleFromConstraintBound(col, target_bound, lp);
1981  }
1982  }
1983 
1984  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
1985  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
1986  return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
1987 }
1988 
1990  ProblemSolution* solution) const {
1992  RETURN_IF_NULL(solution);
1993  column_deletion_helper_.RestoreDeletedColumns(solution);
1994  row_deletion_helper_.RestoreDeletedRows(solution);
1995 
1996  // Compute the last deleted column index for each deleted rows.
1997  const RowIndex num_rows = solution->dual_values.size();
1998  RowToColMapping last_deleted_column(num_rows, kInvalidCol);
1999  for (RowIndex row(0); row < num_rows; ++row) {
2000  if (row_deletion_helper_.IsRowMarked(row)) {
2001  for (const SparseColumn::Entry e :
2002  deleted_rows_as_column_.column(RowToColIndex(row))) {
2003  const ColIndex col = RowToColIndex(e.row());
2004  if (is_unbounded_[col]) {
2005  last_deleted_column[row] = col;
2006  }
2007  }
2008  }
2009  }
2010 
2011  // Note that this will be empty if there were no deleted rows.
2012  const ColIndex num_cols = is_unbounded_.size();
2013  for (ColIndex col(0); col < num_cols; ++col) {
2014  if (!is_unbounded_[col]) continue;
2015  Fractional primal_value_shift = 0.0;
2016  RowIndex row_at_bound = kInvalidRow;
2017  for (const SparseColumn::Entry e : deleted_columns_.column(col)) {
2018  const RowIndex row = e.row();
2019  // The second condition is for VariableStatus::FREE rows.
2020  // TODO(user): In presense of free row, we must move them to 0.
2021  // Note that currently VariableStatus::FREE rows should be removed before
2022  // this is called.
2023  DCHECK(IsFinite(rhs_[row]));
2024  if (last_deleted_column[row] != col || !IsFinite(rhs_[row])) continue;
2025  const SparseColumn& row_as_column =
2026  deleted_rows_as_column_.column(RowToColIndex(row));
2027  const Fractional activity =
2028  rhs_[row] - ScalarProduct(solution->primal_values, row_as_column);
2029 
2030  // activity and sign correction must have the same sign or be zero. If
2031  // not, we find the first unbounded variable and change it accordingly.
2032  // Note that by construction, the variable value will move towards its
2033  // unbounded direction.
2034  if (activity * activity_sign_correction_[row] < 0.0) {
2035  const Fractional bound = activity / e.coefficient();
2036  if (std::abs(bound) > std::abs(primal_value_shift)) {
2037  primal_value_shift = bound;
2038  row_at_bound = row;
2039  }
2040  }
2041  }
2042  solution->primal_values[col] += primal_value_shift;
2043  if (row_at_bound != kInvalidRow) {
2045  solution->constraint_statuses[row_at_bound] =
2046  activity_sign_correction_[row_at_bound] == 1.0
2049  }
2050  }
2051 }
2052 
2053 // --------------------------------------------------------
2054 // FreeConstraintPreprocessor
2055 // --------------------------------------------------------
2056 
2059  RETURN_VALUE_IF_NULL(lp, false);
2060  const RowIndex num_rows = lp->num_constraints();
2061  for (RowIndex row(0); row < num_rows; ++row) {
2062  const Fractional lower_bound = lp->constraint_lower_bounds()[row];
2063  const Fractional upper_bound = lp->constraint_upper_bounds()[row];
2064  if (lower_bound == -kInfinity && upper_bound == kInfinity) {
2065  row_deletion_helper_.MarkRowForDeletion(row);
2066  }
2067  }
2068  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2069  return !row_deletion_helper_.IsEmpty();
2070 }
2071 
2073  ProblemSolution* solution) const {
2075  RETURN_IF_NULL(solution);
2076  row_deletion_helper_.RestoreDeletedRows(solution);
2077 }
2078 
2079 // --------------------------------------------------------
2080 // EmptyConstraintPreprocessor
2081 // --------------------------------------------------------
2082 
2085  RETURN_VALUE_IF_NULL(lp, false);
2086  const RowIndex num_rows(lp->num_constraints());
2087  const ColIndex num_cols(lp->num_variables());
2088 
2089  // Compute degree.
2090  StrictITIVector<RowIndex, int> degree(num_rows, 0);
2091  for (ColIndex col(0); col < num_cols; ++col) {
2092  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
2093  ++degree[e.row()];
2094  }
2095  }
2096 
2097  // Delete degree 0 rows.
2098  for (RowIndex row(0); row < num_rows; ++row) {
2099  if (degree[row] == 0) {
2100  // We need to check that 0.0 is allowed by the constraint bounds,
2101  // otherwise, the problem is ProblemStatus::PRIMAL_INFEASIBLE.
2103  lp->constraint_lower_bounds()[row], 0) ||
2105  0, lp->constraint_upper_bounds()[row])) {
2106  VLOG(1) << "Problem PRIMAL_INFEASIBLE, constraint " << row
2107  << " is empty and its range ["
2108  << lp->constraint_lower_bounds()[row] << ","
2109  << lp->constraint_upper_bounds()[row] << "] doesn't contain 0.";
2110  status_ = ProblemStatus::PRIMAL_INFEASIBLE;
2111  return false;
2112  }
2113  row_deletion_helper_.MarkRowForDeletion(row);
2114  }
2115  }
2116  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2117  return !row_deletion_helper_.IsEmpty();
2118 }
2119 
2121  ProblemSolution* solution) const {
2123  RETURN_IF_NULL(solution);
2124  row_deletion_helper_.RestoreDeletedRows(solution);
2125 }
2126 
2127 // --------------------------------------------------------
2128 // SingletonPreprocessor
2129 // --------------------------------------------------------
2130 
2132  MatrixEntry e, ConstraintStatus status)
2133  : type_(type),
2134  is_maximization_(lp.IsMaximizationProblem()),
2135  e_(e),
2136  cost_(lp.objective_coefficients()[e.col]),
2137  variable_lower_bound_(lp.variable_lower_bounds()[e.col]),
2138  variable_upper_bound_(lp.variable_upper_bounds()[e.col]),
2139  constraint_lower_bound_(lp.constraint_lower_bounds()[e.row]),
2140  constraint_upper_bound_(lp.constraint_upper_bounds()[e.row]),
2141  constraint_status_(status) {}
2142 
2143 void SingletonUndo::Undo(const GlopParameters& parameters,
2144  const SparseMatrix& deleted_columns,
2145  const SparseMatrix& deleted_rows,
2146  ProblemSolution* solution) const {
2147  switch (type_) {
2148  case SINGLETON_ROW:
2149  SingletonRowUndo(deleted_columns, solution);
2150  break;
2152  ZeroCostSingletonColumnUndo(parameters, deleted_rows, solution);
2153  break;
2155  SingletonColumnInEqualityUndo(parameters, deleted_rows, solution);
2156  break;
2158  MakeConstraintAnEqualityUndo(solution);
2159  break;
2160  }
2161 }
2162 
2163 void SingletonPreprocessor::DeleteSingletonRow(MatrixEntry e,
2164  LinearProgram* lp) {
2165  Fractional implied_lower_bound =
2166  lp->constraint_lower_bounds()[e.row] / e.coeff;
2167  Fractional implied_upper_bound =
2168  lp->constraint_upper_bounds()[e.row] / e.coeff;
2169  if (e.coeff < 0.0) {
2170  std::swap(implied_lower_bound, implied_upper_bound);
2171  }
2172 
2173  const Fractional old_lower_bound = lp->variable_lower_bounds()[e.col];
2174  const Fractional old_upper_bound = lp->variable_upper_bounds()[e.col];
2175 
2176  const Fractional potential_error =
2177  std::abs(parameters_.preprocessor_zero_tolerance() / e.coeff);
2178  Fractional new_lower_bound =
2179  implied_lower_bound - potential_error > old_lower_bound
2180  ? implied_lower_bound
2181  : old_lower_bound;
2182  Fractional new_upper_bound =
2183  implied_upper_bound + potential_error < old_upper_bound
2184  ? implied_upper_bound
2185  : old_upper_bound;
2186 
2187  if (new_upper_bound < new_lower_bound) {
2188  if (!IsSmallerWithinFeasibilityTolerance(new_lower_bound,
2189  new_upper_bound)) {
2190  VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2191  "row causes the bound of the variable "
2192  << e.col << " to be infeasible by "
2193  << new_lower_bound - new_upper_bound;
2194  status_ = ProblemStatus::PRIMAL_INFEASIBLE;
2195  return;
2196  }
2197  // Otherwise, fix the variable to one of its bounds.
2198  if (new_lower_bound == lp->variable_lower_bounds()[e.col]) {
2199  new_upper_bound = new_lower_bound;
2200  }
2201  if (new_upper_bound == lp->variable_upper_bounds()[e.col]) {
2202  new_lower_bound = new_upper_bound;
2203  }
2204  DCHECK_EQ(new_lower_bound, new_upper_bound);
2205  }
2206  row_deletion_helper_.MarkRowForDeletion(e.row);
2207  undo_stack_.push_back(SingletonUndo(SingletonUndo::SINGLETON_ROW, *lp, e,
2209  if (deleted_columns_.column(e.col).IsEmpty()) {
2210  deleted_columns_.mutable_column(e.col)->PopulateFromSparseVector(
2211  lp->GetSparseColumn(e.col));
2212  }
2213 
2214  lp->SetVariableBounds(e.col, new_lower_bound, new_upper_bound);
2215 }
2216 
2217 // The dual value of the row needs to be corrected to stay at the optimal.
2218 void SingletonUndo::SingletonRowUndo(const SparseMatrix& deleted_columns,
2219  ProblemSolution* solution) const {
2220  DCHECK_EQ(0, solution->dual_values[e_.row]);
2221 
2222  // If the variable is basic or free, we can just keep the constraint
2223  // VariableStatus::BASIC and
2224  // 0.0 as the dual value.
2225  const VariableStatus status = solution->variable_statuses[e_.col];
2226  if (status == VariableStatus::BASIC || status == VariableStatus::FREE) return;
2227 
2228  // Compute whether or not the variable bounds changed.
2229  Fractional implied_lower_bound = constraint_lower_bound_ / e_.coeff;
2230  Fractional implied_upper_bound = constraint_upper_bound_ / e_.coeff;
2231  if (e_.coeff < 0.0) {
2232  std::swap(implied_lower_bound, implied_upper_bound);
2233  }
2234  const bool lower_bound_changed = implied_lower_bound > variable_lower_bound_;
2235  const bool upper_bound_changed = implied_upper_bound < variable_upper_bound_;
2236 
2237  if (!lower_bound_changed && !upper_bound_changed) return;
2238  if (status == VariableStatus::AT_LOWER_BOUND && !lower_bound_changed) return;
2239  if (status == VariableStatus::AT_UPPER_BOUND && !upper_bound_changed) return;
2240 
2241  // This is the reduced cost of the variable before the singleton constraint is
2242  // added back.
2243  const Fractional reduced_cost =
2244  cost_ -
2245  ScalarProduct(solution->dual_values, deleted_columns.column(e_.col));
2246  const Fractional reduced_cost_for_minimization =
2247  is_maximization_ ? -reduced_cost : reduced_cost;
2248 
2249  if (status == VariableStatus::FIXED_VALUE) {
2250  DCHECK(lower_bound_changed || upper_bound_changed);
2251  if (reduced_cost_for_minimization >= 0.0 && !lower_bound_changed) {
2252  solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2253  return;
2254  }
2255  if (reduced_cost_for_minimization <= 0.0 && !upper_bound_changed) {
2256  solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2257  return;
2258  }
2259  }
2260 
2261  // If one of the variable bounds changes, and the variable is no longer at one
2262  // of its bounds, then its reduced cost needs to be set to 0.0 and the
2263  // variable becomes a basic variable. This is what the line below do, since
2264  // the new reduced cost of the variable will be equal to:
2265  // old_reduced_cost - coeff * solution->dual_values[row]
2266  solution->dual_values[e_.row] = reduced_cost / e_.coeff;
2267  ConstraintStatus new_constraint_status = VariableToConstraintStatus(status);
2268  if (status == VariableStatus::FIXED_VALUE &&
2269  (!lower_bound_changed || !upper_bound_changed)) {
2270  new_constraint_status = lower_bound_changed
2273  }
2274  if (e_.coeff < 0.0) {
2275  if (new_constraint_status == ConstraintStatus::AT_LOWER_BOUND) {
2276  new_constraint_status = ConstraintStatus::AT_UPPER_BOUND;
2277  } else if (new_constraint_status == ConstraintStatus::AT_UPPER_BOUND) {
2278  new_constraint_status = ConstraintStatus::AT_LOWER_BOUND;
2279  }
2280  }
2281  solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2282  solution->constraint_statuses[e_.row] = new_constraint_status;
2283 }
2284 
2285 void SingletonPreprocessor::UpdateConstraintBoundsWithVariableBounds(
2286  MatrixEntry e, LinearProgram* lp) {
2287  Fractional lower_delta = -e.coeff * lp->variable_upper_bounds()[e.col];
2288  Fractional upper_delta = -e.coeff * lp->variable_lower_bounds()[e.col];
2289  if (e.coeff < 0.0) {
2290  std::swap(lower_delta, upper_delta);
2291  }
2292  lp->SetConstraintBounds(e.row,
2293  lp->constraint_lower_bounds()[e.row] + lower_delta,
2294  lp->constraint_upper_bounds()[e.row] + upper_delta);
2295 }
2296 
2297 bool SingletonPreprocessor::IntegerSingletonColumnIsRemovable(
2298  const MatrixEntry& matrix_entry, const LinearProgram& lp) const {
2300  DCHECK(lp.IsVariableInteger(matrix_entry.col));
2301  const SparseMatrix& transpose = lp.GetTransposeSparseMatrix();
2302  for (const SparseColumn::Entry entry :
2303  transpose.column(RowToColIndex(matrix_entry.row))) {
2304  // Check if the variable is integer.
2305  if (!lp.IsVariableInteger(RowToColIndex(entry.row()))) {
2306  return false;
2307  }
2308 
2309  const Fractional coefficient = entry.coefficient();
2310  const Fractional coefficient_ratio = coefficient / matrix_entry.coeff;
2311  // Check if coefficient_ratio is integer.
2313  coefficient_ratio, parameters_.solution_feasibility_tolerance())) {
2314  return false;
2315  }
2316  }
2317  const Fractional constraint_lb =
2318  lp.constraint_lower_bounds()[matrix_entry.row];
2319  if (IsFinite(constraint_lb)) {
2320  const Fractional lower_bound_ratio = constraint_lb / matrix_entry.coeff;
2322  lower_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2323  return false;
2324  }
2325  }
2326  const Fractional constraint_ub =
2327  lp.constraint_upper_bounds()[matrix_entry.row];
2328  if (IsFinite(constraint_ub)) {
2329  const Fractional upper_bound_ratio = constraint_ub / matrix_entry.coeff;
2331  upper_bound_ratio, parameters_.solution_feasibility_tolerance())) {
2332  return false;
2333  }
2334  }
2335  return true;
2336 }
2337 
2338 void SingletonPreprocessor::DeleteZeroCostSingletonColumn(
2339  const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2340  const ColIndex transpose_col = RowToColIndex(e.row);
2341  const SparseColumn& column = transpose.column(transpose_col);
2342  undo_stack_.push_back(SingletonUndo(SingletonUndo::ZERO_COST_SINGLETON_COLUMN,
2343  *lp, e, ConstraintStatus::FREE));
2344  if (deleted_rows_.column(transpose_col).IsEmpty()) {
2345  deleted_rows_.mutable_column(transpose_col)
2346  ->PopulateFromSparseVector(column);
2347  }
2348  UpdateConstraintBoundsWithVariableBounds(e, lp);
2349  column_deletion_helper_.MarkColumnForDeletion(e.col);
2350 }
2351 
2352 // We need to restore the variable value in order to satisfy the constraint.
2353 void SingletonUndo::ZeroCostSingletonColumnUndo(
2354  const GlopParameters& parameters, const SparseMatrix& deleted_rows,
2355  ProblemSolution* solution) const {
2356  // If the variable was fixed, this is easy. Note that this is the only
2357  // possible case if the current constraint status is FIXED.
2358  if (variable_upper_bound_ == variable_lower_bound_) {
2359  solution->primal_values[e_.col] = variable_lower_bound_;
2360  solution->variable_statuses[e_.col] = VariableStatus::FIXED_VALUE;
2361  return;
2362  }
2363 
2364  const ConstraintStatus ct_status = solution->constraint_statuses[e_.row];
2366  if (ct_status == ConstraintStatus::AT_LOWER_BOUND ||
2367  ct_status == ConstraintStatus::AT_UPPER_BOUND) {
2368  if ((ct_status == ConstraintStatus::AT_UPPER_BOUND && e_.coeff > 0.0) ||
2369  (ct_status == ConstraintStatus::AT_LOWER_BOUND && e_.coeff < 0.0)) {
2370  DCHECK(IsFinite(variable_lower_bound_));
2371  solution->primal_values[e_.col] = variable_lower_bound_;
2372  solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2373  } else {
2374  DCHECK(IsFinite(variable_upper_bound_));
2375  solution->primal_values[e_.col] = variable_upper_bound_;
2376  solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2377  }
2378  if (constraint_upper_bound_ == constraint_lower_bound_) {
2379  solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2380  }
2381  return;
2382  }
2383 
2384  // This is the activity of the constraint before the singleton variable is
2385  // added back to it.
2386  const ColIndex row_as_col = RowToColIndex(e_.row);
2387  const Fractional activity =
2388  ScalarProduct(solution->primal_values, deleted_rows.column(row_as_col));
2389 
2390  // First we try to fix the variable at its lower or upper bound and leave the
2391  // constraint VariableStatus::BASIC. Note that we use the same logic as in
2392  // Preprocessor::IsSmallerWithinPreprocessorZeroTolerance() which we can't use
2393  // here because we are not deriving from the Preprocessor class.
2394  const Fractional tolerance = parameters.preprocessor_zero_tolerance();
2395  const auto is_smaller_with_tolerance = [tolerance](Fractional a,
2396  Fractional b) {
2398  };
2399  if (variable_lower_bound_ != -kInfinity) {
2400  const Fractional activity_at_lb =
2401  activity + e_.coeff * variable_lower_bound_;
2402  if (is_smaller_with_tolerance(constraint_lower_bound_, activity_at_lb) &&
2403  is_smaller_with_tolerance(activity_at_lb, constraint_upper_bound_)) {
2404  solution->primal_values[e_.col] = variable_lower_bound_;
2405  solution->variable_statuses[e_.col] = VariableStatus::AT_LOWER_BOUND;
2406  return;
2407  }
2408  }
2409  if (variable_upper_bound_ != kInfinity) {
2410  const Fractional actibity_at_ub =
2411  activity + e_.coeff * variable_upper_bound_;
2412  if (is_smaller_with_tolerance(constraint_lower_bound_, actibity_at_ub) &&
2413  is_smaller_with_tolerance(actibity_at_ub, constraint_upper_bound_)) {
2414  solution->primal_values[e_.col] = variable_upper_bound_;
2415  solution->variable_statuses[e_.col] = VariableStatus::AT_UPPER_BOUND;
2416  return;
2417  }
2418  }
2419 
2420  // If the current constraint is UNBOUNDED, then the variable is too
2421  // because of the two cases above. We just set its status to
2422  // VariableStatus::FREE.
2423  if (constraint_lower_bound_ == -kInfinity &&
2424  constraint_upper_bound_ == kInfinity) {
2425  solution->primal_values[e_.col] = 0.0;
2426  solution->variable_statuses[e_.col] = VariableStatus::FREE;
2427  return;
2428  }
2429 
2430  // If the previous cases didn't apply, the constraint will be fixed to its
2431  // bounds and the variable will be made VariableStatus::BASIC.
2432  solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2433  if (constraint_lower_bound_ == constraint_upper_bound_) {
2434  solution->primal_values[e_.col] =
2435  (constraint_lower_bound_ - activity) / e_.coeff;
2436  solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2437  return;
2438  }
2439 
2440  bool set_constraint_to_lower_bound;
2441  if (constraint_lower_bound_ == -kInfinity) {
2442  set_constraint_to_lower_bound = false;
2443  } else if (constraint_upper_bound_ == kInfinity) {
2444  set_constraint_to_lower_bound = true;
2445  } else {
2446  // In this case we select the value that is the most inside the variable
2447  // bound.
2448  const Fractional to_lb = (constraint_lower_bound_ - activity) / e_.coeff;
2449  const Fractional to_ub = (constraint_upper_bound_ - activity) / e_.coeff;
2450  set_constraint_to_lower_bound =
2451  std::max(variable_lower_bound_ - to_lb, to_lb - variable_upper_bound_) <
2452  std::max(variable_lower_bound_ - to_ub, to_ub - variable_upper_bound_);
2453  }
2454 
2455  if (set_constraint_to_lower_bound) {
2456  solution->primal_values[e_.col] =
2457  (constraint_lower_bound_ - activity) / e_.coeff;
2458  solution->constraint_statuses[e_.row] = ConstraintStatus::AT_LOWER_BOUND;
2459  } else {
2460  solution->primal_values[e_.col] =
2461  (constraint_upper_bound_ - activity) / e_.coeff;
2462  solution->constraint_statuses[e_.row] = ConstraintStatus::AT_UPPER_BOUND;
2463  }
2464 }
2465 
2466 void SingletonPreprocessor::DeleteSingletonColumnInEquality(
2467  const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2468  // Save information for the undo.
2469  const ColIndex transpose_col = RowToColIndex(e.row);
2470  const SparseColumn& row_as_column = transpose.column(transpose_col);
2471  undo_stack_.push_back(
2472  SingletonUndo(SingletonUndo::SINGLETON_COLUMN_IN_EQUALITY, *lp, e,
2474  deleted_rows_.mutable_column(transpose_col)
2475  ->PopulateFromSparseVector(row_as_column);
2476 
2477  // Update the objective function using the equality constraint. We have
2478  // v_col*coeff + expression = rhs,
2479  // so the contribution of this variable to the cost function (v_col * cost)
2480  // can be rewrited as:
2481  // (rhs * cost - expression * cost) / coeff.
2482  const Fractional rhs = lp->constraint_upper_bounds()[e.row];
2483  const Fractional cost = lp->objective_coefficients()[e.col];
2484  const Fractional multiplier = cost / e.coeff;
2485  lp->SetObjectiveOffset(lp->objective_offset() + rhs * multiplier);
2486  for (const SparseColumn::Entry e : row_as_column) {
2487  const ColIndex col = RowToColIndex(e.row());
2488  if (!column_deletion_helper_.IsColumnMarked(col)) {
2489  Fractional new_cost =
2490  lp->objective_coefficients()[col] - e.coefficient() * multiplier;
2491 
2492  // TODO(user): It is important to avoid having non-zero costs which are
2493  // the result of numerical error. This is because we still miss some
2494  // tolerances in a few preprocessors. Like an empty column with a cost of
2495  // 1e-17 and unbounded towards infinity is currently implying that the
2496  // problem is unbounded. This will need fixing.
2497  if (std::abs(new_cost) < parameters_.preprocessor_zero_tolerance()) {
2498  new_cost = 0.0;
2499  }
2500  lp->SetObjectiveCoefficient(col, new_cost);
2501  }
2502  }
2503 
2504  // Now delete the column like a singleton column without cost.
2505  UpdateConstraintBoundsWithVariableBounds(e, lp);
2506  column_deletion_helper_.MarkColumnForDeletion(e.col);
2507 }
2508 
2509 void SingletonUndo::SingletonColumnInEqualityUndo(
2510  const GlopParameters& parameters, const SparseMatrix& deleted_rows,
2511  ProblemSolution* solution) const {
2512  // First do the same as a zero-cost singleton column.
2513  ZeroCostSingletonColumnUndo(parameters, deleted_rows, solution);
2514 
2515  // Then, restore the dual optimal value taking into account the cost
2516  // modification.
2517  solution->dual_values[e_.row] += cost_ / e_.coeff;
2518  if (solution->constraint_statuses[e_.row] == ConstraintStatus::BASIC) {
2519  solution->variable_statuses[e_.col] = VariableStatus::BASIC;
2520  solution->constraint_statuses[e_.row] = ConstraintStatus::FIXED_VALUE;
2521  }
2522 }
2523 
2524 void SingletonUndo::MakeConstraintAnEqualityUndo(
2525  ProblemSolution* solution) const {
2526  if (solution->constraint_statuses[e_.row] == ConstraintStatus::FIXED_VALUE) {
2527  solution->constraint_statuses[e_.row] = constraint_status_;
2528  }
2529 }
2530 
2531 bool SingletonPreprocessor::MakeConstraintAnEqualityIfPossible(
2532  const SparseMatrix& transpose, MatrixEntry e, LinearProgram* lp) {
2533  // TODO(user): We could skip early if the relevant constraint bound is
2534  // infinity.
2535  const Fractional cst_lower_bound = lp->constraint_lower_bounds()[e.row];
2536  const Fractional cst_upper_bound = lp->constraint_upper_bounds()[e.row];
2537  if (cst_lower_bound == cst_upper_bound) return true;
2538 
2539  // To be efficient, we only process a row once and cache the domain that an
2540  // "artificial" extra variable x with coefficient 1.0 could take while still
2541  // making the constraint feasible. The domain bounds for the constraint e.row
2542  // will be stored in row_lb_sum_[e.row] and row_ub_sum_[e.row].
2543  const DenseRow& variable_ubs = lp->variable_upper_bounds();
2544  const DenseRow& variable_lbs = lp->variable_lower_bounds();
2545  if (e.row >= row_sum_is_cached_.size() || !row_sum_is_cached_[e.row]) {
2546  if (e.row >= row_sum_is_cached_.size()) {
2547  const int new_size = e.row.value() + 1;
2548  row_sum_is_cached_.resize(new_size);
2549  row_lb_sum_.resize(new_size);
2550  row_ub_sum_.resize(new_size);
2551  }
2552  row_sum_is_cached_[e.row] = true;
2553  row_lb_sum_[e.row].Add(cst_lower_bound);
2554  row_ub_sum_[e.row].Add(cst_upper_bound);
2555  for (const SparseColumn::Entry entry :
2556  transpose.column(RowToColIndex(e.row))) {
2557  const ColIndex row_as_col = RowToColIndex(entry.row());
2558 
2559  // Tricky: Even if later more columns are deleted, these "cached" sums
2560  // will actually still be valid because we only delete columns in a
2561  // compatible way.
2562  //
2563  // TODO(user): Find a more robust way? it seems easy to add new deletion
2564  // rules that may break this assumption.
2565  if (column_deletion_helper_.IsColumnMarked(row_as_col)) continue;
2566  if (entry.coefficient() > 0.0) {
2567  row_lb_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2568  row_ub_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2569  } else {
2570  row_lb_sum_[e.row].Add(-entry.coefficient() * variable_lbs[row_as_col]);
2571  row_ub_sum_[e.row].Add(-entry.coefficient() * variable_ubs[row_as_col]);
2572  }
2573 
2574  // TODO(user): Abort early if both sums contain more than 1 infinity?
2575  }
2576  }
2577 
2578  // Now that the lb/ub sum for the row is cached, we can use it to compute the
2579  // implied bounds on the variable from this constraint and the other
2580  // variables.
2581  const Fractional c = e.coeff;
2582  const Fractional lb =
2583  c > 0.0 ? row_lb_sum_[e.row].SumWithout(-c * variable_ubs[e.col]) / c
2584  : row_ub_sum_[e.row].SumWithout(-c * variable_ubs[e.col]) / c;
2585  const Fractional ub =
2586  c > 0.0 ? row_ub_sum_[e.row].SumWithout(-c * variable_lbs[e.col]) / c
2587  : row_lb_sum_[e.row].SumWithout(-c * variable_lbs[e.col]) / c;
2588 
2589  // Note that we could do the same for singleton variables with a cost of
2590  // 0.0, but such variable are already dealt with by
2591  // DeleteZeroCostSingletonColumn() so there is no point.
2592  const Fractional cost =
2593  lp->GetObjectiveCoefficientForMinimizationVersion(e.col);
2594  DCHECK_NE(cost, 0.0);
2595 
2596  // Note that some of the tests below will be always true if the bounds of
2597  // the column of index col are infinite. This is the desired behavior.
2600  ub, lp->variable_upper_bounds()[e.col])) {
2601  if (e.coeff > 0) {
2602  if (cst_upper_bound == kInfinity) {
2603  status_ = ProblemStatus::INFEASIBLE_OR_UNBOUNDED;
2604  } else {
2605  relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2606  lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2607  }
2608  } else {
2609  if (cst_lower_bound == -kInfinity) {
2610  status_ = ProblemStatus::INFEASIBLE_OR_UNBOUNDED;
2611  } else {
2612  relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2613  lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2614  }
2615  }
2616 
2617  if (status_ == ProblemStatus::INFEASIBLE_OR_UNBOUNDED) {
2618  DCHECK_EQ(ub, kInfinity);
2619  VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2620  "variable "
2621  << e.col << " has a cost (for minimization) of " << cost
2622  << " and is unbounded towards kInfinity.";
2623  return false;
2624  }
2625 
2626  // This is important but tricky: The upper bound of the variable needs to
2627  // be relaxed. This is valid because the implied bound is lower than the
2628  // original upper bound here. This is needed, so that the optimal
2629  // primal/dual values of the new problem will also be optimal of the
2630  // original one.
2631  //
2632  // Let's prove the case coeff > 0.0 for a minimization problem. In the new
2633  // problem, because the variable is unbounded towards +infinity, its
2634  // reduced cost must satisfy at optimality rc = cost - coeff * dual_v >=
2635  // 0. But this implies dual_v <= cost / coeff <= 0. This is exactly what
2636  // is needed for the optimality of the initial problem since the
2637  // constraint will be at its upper bound, and the corresponding slack
2638  // condition is that the dual value needs to be <= 0.
2639  lp->SetVariableBounds(e.col, lp->variable_lower_bounds()[e.col], kInfinity);
2640  }
2642  lp->variable_lower_bounds()[e.col], lb)) {
2643  if (e.coeff > 0) {
2644  if (cst_lower_bound == -kInfinity) {
2645  status_ = ProblemStatus::INFEASIBLE_OR_UNBOUNDED;
2646  } else {
2647  relaxed_status = ConstraintStatus::AT_LOWER_BOUND;
2648  lp->SetConstraintBounds(e.row, cst_lower_bound, cst_lower_bound);
2649  }
2650  } else {
2651  if (cst_upper_bound == kInfinity) {
2652  status_ = ProblemStatus::INFEASIBLE_OR_UNBOUNDED;
2653  } else {
2654  relaxed_status = ConstraintStatus::AT_UPPER_BOUND;
2655  lp->SetConstraintBounds(e.row, cst_upper_bound, cst_upper_bound);
2656  }
2657  }
2658 
2659  if (status_ == ProblemStatus::INFEASIBLE_OR_UNBOUNDED) {
2660  DCHECK_EQ(lb, -kInfinity);
2661  VLOG(1) << "Problem ProblemStatus::INFEASIBLE_OR_UNBOUNDED, singleton "
2662  "variable "
2663  << e.col << " has a cost (for minimization) of " << cost
2664  << " and is unbounded towards -kInfinity.";
2665  return false;
2666  }
2667 
2668  // Same remark as above for a lower bounded variable this time.
2669  lp->SetVariableBounds(e.col, -kInfinity,
2670  lp->variable_upper_bounds()[e.col]);
2671  }
2672 
2673  if (lp->constraint_lower_bounds()[e.row] ==
2674  lp->constraint_upper_bounds()[e.row]) {
2675  undo_stack_.push_back(SingletonUndo(
2676  SingletonUndo::MAKE_CONSTRAINT_AN_EQUALITY, *lp, e, relaxed_status));
2677  return true;
2678  }
2679  return false;
2680 }
2681 
2684  RETURN_VALUE_IF_NULL(lp, false);
2685  const SparseMatrix& matrix = lp->GetSparseMatrix();
2686  const SparseMatrix& transpose = lp->GetTransposeSparseMatrix();
2687 
2688  // Initialize column_to_process with the current singleton columns.
2689  ColIndex num_cols(matrix.num_cols());
2690  RowIndex num_rows(matrix.num_rows());
2691  deleted_columns_.PopulateFromZero(num_rows, num_cols);
2692  deleted_rows_.PopulateFromZero(ColToRowIndex(num_cols),
2693  RowToColIndex(num_rows));
2694  StrictITIVector<ColIndex, EntryIndex> column_degree(num_cols, EntryIndex(0));
2695  std::vector<ColIndex> column_to_process;
2696  for (ColIndex col(0); col < num_cols; ++col) {
2697  column_degree[col] = matrix.column(col).num_entries();
2698  if (column_degree[col] == 1) {
2699  column_to_process.push_back(col);
2700  }
2701  }
2702 
2703  // Initialize row_to_process with the current singleton rows.
2704  StrictITIVector<RowIndex, EntryIndex> row_degree(num_rows, EntryIndex(0));
2705  std::vector<RowIndex> row_to_process;
2706  for (RowIndex row(0); row < num_rows; ++row) {
2707  row_degree[row] = transpose.column(RowToColIndex(row)).num_entries();
2708  if (row_degree[row] == 1) {
2709  row_to_process.push_back(row);
2710  }
2711  }
2712 
2713  // Process current singleton rows/columns and enqueue new ones.
2714  while (status_ == ProblemStatus::INIT &&
2715  (!column_to_process.empty() || !row_to_process.empty())) {
2716  while (status_ == ProblemStatus::INIT && !column_to_process.empty()) {
2717  const ColIndex col = column_to_process.back();
2718  column_to_process.pop_back();
2719  if (column_degree[col] <= 0) continue;
2720  const MatrixEntry e = GetSingletonColumnMatrixEntry(col, matrix);
2721  if (in_mip_context_ && lp->IsVariableInteger(e.col) &&
2722  !IntegerSingletonColumnIsRemovable(e, *lp)) {
2723  continue;
2724  }
2725 
2726  // TODO(user): It seems better to process all the singleton columns with
2727  // a cost of zero first.
2728  if (lp->objective_coefficients()[col] == 0.0) {
2729  DeleteZeroCostSingletonColumn(transpose, e, lp);
2730  } else if (MakeConstraintAnEqualityIfPossible(transpose, e, lp)) {
2731  DeleteSingletonColumnInEquality(transpose, e, lp);
2732  } else {
2733  continue;
2734  }
2735  --row_degree[e.row];
2736  if (row_degree[e.row] == 1) {
2737  row_to_process.push_back(e.row);
2738  }
2739  }
2740  while (status_ == ProblemStatus::INIT && !row_to_process.empty()) {
2741  const RowIndex row = row_to_process.back();
2742  row_to_process.pop_back();
2743  if (row_degree[row] <= 0) continue;
2744  const MatrixEntry e = GetSingletonRowMatrixEntry(row, transpose);
2745 
2746  DeleteSingletonRow(e, lp);
2747  --column_degree[e.col];
2748  if (column_degree[e.col] == 1) {
2749  column_to_process.push_back(e.col);
2750  }
2751  }
2752  }
2753 
2754  if (status_ != ProblemStatus::INIT) return false;
2755  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
2756  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
2757  return !column_deletion_helper_.IsEmpty() || !row_deletion_helper_.IsEmpty();
2758 }
2759 
2762  RETURN_IF_NULL(solution);
2763 
2764  // Note that the two deletion helpers must restore 0.0 values in the positions
2765  // that will be used during Undo(). That is, all the calls done by this class
2766  // to MarkColumnForDeletion() should be done with 0.0 as the value to restore
2767  // (which is already the case when using MarkRowForDeletion()).
2768  // This is important because the various Undo() functions assume that a
2769  // primal/dual variable value which isn't restored yet has the value of 0.0.
2770  column_deletion_helper_.RestoreDeletedColumns(solution);
2771  row_deletion_helper_.RestoreDeletedRows(solution);
2772 
2773  // It is important to undo the operations in the correct order, i.e. in the
2774  // reverse order in which they were done.
2775  for (int i = undo_stack_.size() - 1; i >= 0; --i) {
2776  undo_stack_[i].Undo(parameters_, deleted_columns_, deleted_rows_, solution);
2777  }
2778 }
2779 
2780 MatrixEntry SingletonPreprocessor::GetSingletonColumnMatrixEntry(
2781  ColIndex col, const SparseMatrix& matrix) {
2782  for (const SparseColumn::Entry e : matrix.column(col)) {
2783  if (!row_deletion_helper_.IsRowMarked(e.row())) {
2784  DCHECK_NE(0.0, e.coefficient());
2785  return MatrixEntry(e.row(), col, e.coefficient());
2786  }
2787  }
2788  // This shouldn't happen.
2789  LOG(DFATAL) << "No unmarked entry in a column that is supposed to have one.";
2791  return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2792 }
2793 
2794 MatrixEntry SingletonPreprocessor::GetSingletonRowMatrixEntry(
2795  RowIndex row, const SparseMatrix& transpose) {
2796  for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
2797  const ColIndex col = RowToColIndex(e.row());
2798  if (!column_deletion_helper_.IsColumnMarked(col)) {
2799  DCHECK_NE(0.0, e.coefficient());
2800  return MatrixEntry(row, col, e.coefficient());
2801  }
2802  }
2803  // This shouldn't happen.
2804  LOG(DFATAL) << "No unmarked entry in a row that is supposed to have one.";
2806  return MatrixEntry(RowIndex(0), ColIndex(0), 0.0);
2807 }
2808 
2809 // --------------------------------------------------------
2810 // RemoveNearZeroEntriesPreprocessor
2811 // --------------------------------------------------------
2812 
2815  RETURN_VALUE_IF_NULL(lp, false);
2816  const ColIndex num_cols = lp->num_variables();
2817  if (num_cols == 0) return false;
2818 
2819  // We will use a different threshold for each row depending on its degree.
2820  // We use Fractionals for convenience since they will be used as such below.
2821  const RowIndex num_rows = lp->num_constraints();
2822  DenseColumn row_degree(num_rows, 0.0);
2823  Fractional num_non_zero_objective_coefficients = 0.0;
2824  for (ColIndex col(0); col < num_cols; ++col) {
2825  for (const SparseColumn::Entry e : lp->GetSparseColumn(col)) {
2826  row_degree[e.row()] += 1.0;
2827  }
2828  if (lp->objective_coefficients()[col] != 0.0) {
2829  num_non_zero_objective_coefficients += 1.0;
2830  }
2831  }
2832 
2833  // To not have too many parameters, we use the preprocessor_zero_tolerance.
2834  const Fractional allowed_impact = parameters_.preprocessor_zero_tolerance();
2835 
2836  // TODO(user): Our criteria ensure that during presolve a primal feasible
2837  // solution will stay primal feasible. However, we have no guarantee on the
2838  // dual-feasibility (because the dual variable values range is not taken into
2839  // account). Fix that? or find a better criteria since it seems that on all
2840  // our current problems, this preprocessor helps and doesn't introduce errors.
2841  const EntryIndex initial_num_entries = lp->num_entries();
2842  int num_zeroed_objective_coefficients = 0;
2843  for (ColIndex col(0); col < num_cols; ++col) {
2844  const Fractional lower_bound = lp->variable_lower_bounds()[col];
2845  const Fractional upper_bound = lp->variable_upper_bounds()[col];
2846 
2847  // TODO(user): Write a small class that takes a matrix, its transpose, row
2848  // and column bounds, and "propagate" the bounds as much as possible so we
2849  // can use this better estimate here and remove more near-zero entries.
2850  const Fractional max_magnitude =
2851  std::max(std::abs(lower_bound), std::abs(upper_bound));
2852  if (max_magnitude == kInfinity || max_magnitude == 0) continue;
2853  const Fractional threshold = allowed_impact / max_magnitude;
2855  threshold, row_degree);
2856 
2857  if (lp->objective_coefficients()[col] != 0.0 &&
2858  num_non_zero_objective_coefficients *
2859  std::abs(lp->objective_coefficients()[col]) <
2860  threshold) {
2861  lp->SetObjectiveCoefficient(col, 0.0);
2862  ++num_zeroed_objective_coefficients;
2863  }
2864  }
2865 
2866  const EntryIndex num_entries = lp->num_entries();
2867  if (num_entries != initial_num_entries) {
2868  VLOG(1) << "Removed " << initial_num_entries - num_entries
2869  << " near-zero entries.";
2870  }
2871  if (num_zeroed_objective_coefficients > 0) {
2872  VLOG(1) << "Removed " << num_zeroed_objective_coefficients
2873  << " near-zero objective coefficients.";
2874  }
2875 
2876  // No post-solve is required.
2877  return false;
2878 }
2879 
2881  ProblemSolution* solution) const {}
2882 
2883 // --------------------------------------------------------
2884 // SingletonColumnSignPreprocessor
2885 // --------------------------------------------------------
2886 
2889  RETURN_VALUE_IF_NULL(lp, false);
2890  const ColIndex num_cols = lp->num_variables();
2891  if (num_cols == 0) return false;
2892 
2893  changed_columns_.clear();
2894  int num_singletons = 0;
2895  for (ColIndex col(0); col < num_cols; ++col) {
2896  SparseColumn* sparse_column = lp->GetMutableSparseColumn(col);
2897  const Fractional cost = lp->objective_coefficients()[col];
2898  if (sparse_column->num_entries() == 1) {
2899  ++num_singletons;
2900  }
2901  if (sparse_column->num_entries() == 1 &&
2902  sparse_column->GetFirstCoefficient() < 0) {
2903  sparse_column->MultiplyByConstant(-1.0);
2905  -lp->variable_lower_bounds()[col]);
2907  changed_columns_.push_back(col);
2908  }
2909  }
2910  VLOG(1) << "Changed the sign of " << changed_columns_.size() << " columns.";
2911  VLOG(1) << num_singletons << " singleton columns left.";
2912  return !changed_columns_.empty();
2913 }
2914 
2916  ProblemSolution* solution) const {
2918  RETURN_IF_NULL(solution);
2919  for (int i = 0; i < changed_columns_.size(); ++i) {
2920  const ColIndex col = changed_columns_[i];
2921  solution->primal_values[col] = -solution->primal_values[col];
2922  const VariableStatus status = solution->variable_statuses[col];
2925  } else if (status == VariableStatus::AT_LOWER_BOUND) {
2927  }
2928  }
2929 }
2930 
2931 // --------------------------------------------------------
2932 // DoubletonEqualityRowPreprocessor
2933 // --------------------------------------------------------
2934 
2937  RETURN_VALUE_IF_NULL(lp, false);
2938 
2939  // This is needed at postsolve.
2940  //
2941  // TODO(user): Get rid of the FIXED status instead to avoid spending
2942  // time/memory for no good reason here.
2943  saved_row_lower_bounds_ = lp->constraint_lower_bounds();
2944  saved_row_upper_bounds_ = lp->constraint_upper_bounds();
2945 
2946  // Note that we don't update the transpose during this preprocessor run.
2947  const SparseMatrix& original_transpose = lp->GetTransposeSparseMatrix();
2948 
2949  // Iterate over the rows that were already doubletons before this preprocessor
2950  // run, and whose items don't belong to a column that we deleted during this
2951  // run. This implies that the rows are only ever touched once per run, because
2952  // we only modify rows that have an item on a deleted column.
2953  const RowIndex num_rows(lp->num_constraints());
2954  for (RowIndex row(0); row < num_rows; ++row) {
2955  const SparseColumn& original_row =
2956  original_transpose.column(RowToColIndex(row));
2957  if (original_row.num_entries() != 2 ||
2958  lp->constraint_lower_bounds()[row] !=
2959  lp->constraint_upper_bounds()[row]) {
2960  continue;
2961  }
2962 
2963  // Collect the two row items. Skip the ones involving a deleted column.
2964  // Note: we filled r.col[] and r.coeff[] by item order, and currently we
2965  // always pick the first column as the to-be-deleted one.
2966  // TODO(user): make a smarter choice of which column to delete, and
2967  // swap col[] and coeff[] accordingly.
2968  RestoreInfo r; // Use a short name since we're using it everywhere.
2969  int entry_index = 0;
2970  for (const SparseColumn::Entry e : original_row) {
2971  const ColIndex col = RowToColIndex(e.row());
2972  if (column_deletion_helper_.IsColumnMarked(col)) continue;
2973  r.col[entry_index] = col;
2974  r.coeff[entry_index] = e.coefficient();
2975  DCHECK_NE(0.0, r.coeff[entry_index]);
2976  ++entry_index;
2977  }
2978 
2979  // Discard some cases that will be treated by other preprocessors, or by
2980  // another run of this one.
2981  // 1) One or two of the items were in a deleted column.
2982  if (entry_index < 2) continue;
2983 
2984  // Fill the RestoreInfo, even if we end up not using it (because we
2985  // give up on preprocessing this row): it has a bunch of handy shortcuts.
2986  r.row = row;
2987  r.rhs = lp->constraint_lower_bounds()[row];
2988  for (int col_choice = 0; col_choice < NUM_DOUBLETON_COLS; ++col_choice) {
2989  const ColIndex col = r.col[col_choice];
2990  r.lb[col_choice] = lp->variable_lower_bounds()[col];
2991  r.ub[col_choice] = lp->variable_upper_bounds()[col];
2992  r.objective_coefficient[col_choice] = lp->objective_coefficients()[col];
2993  r.column[col_choice].PopulateFromSparseVector(lp->GetSparseColumn(col));
2994  }
2995 
2996  // 2) One of the columns is fixed: don't bother, it will be treated
2997  // by the FixedVariablePreprocessor.
2998  if (r.lb[DELETED] == r.ub[DELETED] || r.lb[MODIFIED] == r.ub[MODIFIED]) {
2999  continue;
3000  }
3001 
3002  // Look at the bounds of both variables and exit early if we can delegate
3003  // to another pre-processor; otherwise adjust the bounds of the remaining
3004  // variable as necessary.
3005  // If the current row is: aX + bY = c, then the bounds of Y must be
3006  // adjusted to satisfy Y = c/b + (-a/b)X
3007  //
3008  // Note: when we compute the coefficients of these equations, we can cause
3009  // underflows/overflows that could be avoided if we did the computations
3010  // more carefully; but for now we just treat those cases as
3011  // ProblemStatus::ABNORMAL.
3012  // TODO(user): consider skipping the problematic rows in this preprocessor,
3013  // or trying harder to avoid the under/overflow.
3014  {
3015  const Fractional carry_over_offset = r.rhs / r.coeff[MODIFIED];
3016  const Fractional carry_over_factor =
3017  -r.coeff[DELETED] / r.coeff[MODIFIED];
3018  if (!IsFinite(carry_over_offset) || !IsFinite(carry_over_factor) ||
3019  carry_over_factor == 0.0) {
3021  break;
3022  }
3023 
3024  Fractional lb = r.lb[MODIFIED];
3025  Fractional ub = r.ub[MODIFIED];
3026  Fractional carried_over_lb =
3027  r.lb[DELETED] * carry_over_factor + carry_over_offset;
3028  Fractional carried_over_ub =
3029  r.ub[DELETED] * carry_over_factor + carry_over_offset;
3030  if (carry_over_factor < 0) {
3031  std::swap(carried_over_lb, carried_over_ub);
3032  }
3033  if (carried_over_lb <= lb) {
3034  // Default (and simplest) case: the lower bound didn't change.
3035  r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3036  MODIFIED, VariableStatus::AT_LOWER_BOUND, lb);
3037  } else {
3038  lb = carried_over_lb;
3039  r.bound_backtracking_at_lower_bound = RestoreInfo::ColChoiceAndStatus(
3040  DELETED,
3041  carry_over_factor > 0 ? VariableStatus::AT_LOWER_BOUND
3043  carry_over_factor > 0 ? r.lb[DELETED] : r.ub[DELETED]);
3044  }
3045  if (carried_over_ub >= ub) {
3046  // Default (and simplest) case: the upper bound didn't change.
3047  r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3048  MODIFIED, VariableStatus::AT_UPPER_BOUND, ub);
3049  } else {
3050  ub = carried_over_ub;
3051  r.bound_backtracking_at_upper_bound = RestoreInfo::ColChoiceAndStatus(
3052  DELETED,
3053  carry_over_factor > 0 ? VariableStatus::AT_UPPER_BOUND
3055  carry_over_factor > 0 ? r.ub[DELETED] : r.lb[DELETED]);
3056  }
3057  // 3) If the new bounds are fixed (the domain is a singleton) or
3058  // infeasible, then we let the
3059  // ForcingAndImpliedFreeConstraintPreprocessor do the work.
3060  if (IsSmallerWithinPreprocessorZeroTolerance(ub, lb)) continue;
3061  lp->SetVariableBounds(r.col[MODIFIED], lb, ub);
3062  }
3063 
3064  restore_stack_.push_back(r);
3065 
3066  // Now, perform the substitution. If the current row is: aX + bY = c
3067  // then any other row containing 'X' with coefficient x can remove the
3068  // entry in X, and instead add an entry on 'Y' with coefficient x(-b/a)
3069  // and a constant offset x(c/a).
3070  // Looking at the matrix, this translates into colY += (-b/a) colX.
3071  DCHECK_NE(r.coeff[DELETED], 0.0);
3072  const Fractional substitution_factor =
3073  -r.coeff[MODIFIED] / r.coeff[DELETED]; // -b/a
3074  const Fractional constant_offset_factor = r.rhs / r.coeff[DELETED]; // c/a
3075  // Again we don't bother too much with over/underflows.
3076  if (!IsFinite(substitution_factor) || substitution_factor == 0.0 ||
3077  !IsFinite(constant_offset_factor)) {
3079  break;
3080  }
3081  r.column[DELETED].AddMultipleToSparseVectorAndDeleteCommonIndex(
3082  substitution_factor, row, parameters_.drop_tolerance(),
3083  lp->GetMutableSparseColumn(r.col[MODIFIED]));
3084 
3085  // Apply similar operations on the objective coefficients.
3086  // Note that the offset is being updated by
3087  // SubtractColumnMultipleFromConstraintBound() below.
3088  {
3089  const Fractional new_objective =
3090  r.objective_coefficient[MODIFIED] +
3091  substitution_factor * r.objective_coefficient[DELETED];
3092  if (std::abs(new_objective) > parameters_.drop_tolerance()) {
3093  lp->SetObjectiveCoefficient(r.col[MODIFIED], new_objective);
3094  } else {
3095  lp->SetObjectiveCoefficient(r.col[MODIFIED], 0.0);
3096  }
3097  }
3098 
3099  // Carry over the constant factor of the substitution as well.
3100  // TODO(user): rename that method to reflect the fact that it also updates
3101  // the objective offset, in the other direction.
3102  SubtractColumnMultipleFromConstraintBound(r.col[DELETED],
3103  constant_offset_factor, lp);
3104 
3105  // Mark the column and the row for deletion.
3106  column_deletion_helper_.MarkColumnForDeletion(r.col[DELETED]);
3107  row_deletion_helper_.MarkRowForDeletion(row);
3108  }
3109  if (status_ != ProblemStatus::INIT) return false;
3110  lp->DeleteColumns(column_deletion_helper_.GetMarkedColumns());
3111  lp->DeleteRows(row_deletion_helper_.GetMarkedRows());
3112  return !column_deletion_helper_.IsEmpty();
3113 }
3114 
3116  ProblemSolution* solution) const {
3118  RETURN_IF_NULL(solution);
3119  column_deletion_helper_.RestoreDeletedColumns(solution);
3120  row_deletion_helper_.RestoreDeletedRows(solution);
3121  for (const RestoreInfo& r : Reverse(restore_stack_)) {
3122  switch (solution->variable_statuses[r.col[MODIFIED]]) {
3124  LOG(DFATAL) << "FIXED variable produced by DoubletonPreprocessor!";
3125  // In non-fastbuild mode, we rely on the rest of the code producing an
3126  // ProblemStatus::ABNORMAL status here.
3127  break;
3128  // When the modified variable is either basic or free, we keep it as is,
3129  // and simply make the deleted one basic.
3130  case VariableStatus::FREE:
3131  ABSL_FALLTHROUGH_INTENDED;
3132  case VariableStatus::BASIC:
3133  // Several code paths set the deleted column as basic. The code that
3134  // sets its value in that case is below, after the switch() block.
3135  solution->variable_statuses[r.col[DELETED]] = VariableStatus::BASIC;
3136  break;
3138  ABSL_FALLTHROUGH_INTENDED;
3140  // The bound was induced by a bound of one of the two original
3141  // variables. Put that original variable at its bound, and make
3142  // the other one basic.
3143  const RestoreInfo::ColChoiceAndStatus& bound_backtracking =
3144  solution->variable_statuses[r.col[MODIFIED]] ==
3146  ? r.bound_backtracking_at_lower_bound
3147  : r.bound_backtracking_at_upper_bound;
3148  const ColIndex bounded_var = r.col[bound_backtracking.col_choice];
3149  const ColIndex basic_var =
3150  r.col[OtherColChoice(bound_backtracking.col_choice)];
3151  solution->variable_statuses[bounded_var] = bound_backtracking.status;
3152  solution->primal_values[bounded_var] = bound_backtracking.value;
3153  solution->variable_statuses[basic_var] = VariableStatus::BASIC;
3154  // If the modified column is VariableStatus::BASIC, then its value is
3155  // already set
3156  // correctly. If it's the deleted column that is basic, its value is
3157  // set below the switch() block.
3158  }
3159  }
3160 
3161  // Restore the value of the deleted column if it is VariableStatus::BASIC.
3162  if (solution->variable_statuses[r.col[DELETED]] == VariableStatus::BASIC) {
3163  solution->primal_values[r.col[DELETED]] =
3164  (r.rhs -
3165  solution->primal_values[r.col[MODIFIED]] * r.coeff[MODIFIED]) /
3166  r.coeff[DELETED];
3167  }
3168 
3169  // Make the deleted constraint status FIXED.
3171 
3172  // Adjust the dual value of the deleted constraint so that the
3173  // VariableStatus::BASIC column have a reduced cost of zero (if the two
3174  // are VariableStatus::BASIC, just pick one).
3175  // The reduced cost of the other variable will then automatically be
3176  // correct: zero if it's VariableStatus::BASIC, and with the correct sign if
3177  // it's bounded.
3178  const ColChoice col_choice =
3179  solution->variable_statuses[r.col[DELETED]] == VariableStatus::BASIC
3180  ? DELETED
3181  : MODIFIED;
3182  // To compute the reduced cost properly (i.e. without the restored row).
3183  solution->dual_values[r.row] = 0.0;
3184  const Fractional current_reduced_cost =
3185  r.objective_coefficient[col_choice] -
3186  PreciseScalarProduct(solution->dual_values, r.column[col_choice]);
3187  solution->dual_values[r.row] = current_reduced_cost / r.coeff[col_choice];
3188  }
3189 
3190  // Fix potential bad ConstraintStatus::FIXED_VALUE statuses.
3191  FixConstraintWithFixedStatuses(saved_row_lower_bounds_,
3192  saved_row_upper_bounds_, solution);
3193 }
3194 
3195 void FixConstraintWithFixedStatuses(const DenseColumn& row_lower_bounds,
3196  const DenseColumn& row_upper_bounds,
3197  ProblemSolution* solution) {
3198  const RowIndex num_rows = solution->constraint_statuses.size();
3199  DCHECK_EQ(row_lower_bounds.size(), num_rows);
3200  DCHECK_EQ(row_upper_bounds.size(), num_rows);
3201  for (RowIndex row(0); row < num_rows; ++row) {
3203  continue;
3204  }
3205  if (row_lower_bounds[row] == row_upper_bounds[row]) continue;
3206 
3207  // We need to fix the status and we just need to make sure that the bound we
3208  // choose satisfies the LP optimality conditions.
3209  if (solution->dual_values[row] > 0) {
3211  } else {
3213  }
3214  }
3215 }
3216 
3217 void DoubletonEqualityRowPreprocessor::
3218  SwapDeletedAndModifiedVariableRestoreInfo(RestoreInfo* r) {
3219  using std::swap;
3220  swap(r->col[DELETED], r->col[MODIFIED]);
3221  swap(r->coeff[DELETED], r->coeff[MODIFIED]);
3222  swap(r->lb[DELETED], r->lb[MODIFIED]);
3223  swap(r->ub[DELETED], r->ub[MODIFIED]);
3224  swap(r->column[DELETED], r->column[MODIFIED]);
3225  swap(r->objective_coefficient[DELETED], r->objective_coefficient[MODIFIED]);
3226 }
3227 
3228 // --------------------------------------------------------
3229 // DualizerPreprocessor
3230 // --------------------------------------------------------
3231 
3234  RETURN_VALUE_IF_NULL(lp, false);
3235  if (parameters_.solve_dual_problem() == GlopParameters::NEVER_DO) {
3236  return false;
3237  }
3238 
3239  // Store the original problem size and direction.
3240  primal_num_cols_ = lp->num_variables();
3241  primal_num_rows_ = lp->num_constraints();
3242  primal_is_maximization_problem_ = lp->IsMaximizationProblem();
3243 
3244  // If we need to decide whether or not to take the dual, we only take it when
3245  // the matrix has more rows than columns. The number of rows of a linear
3246  // program gives the size of the square matrices we need to invert and the
3247  // order of iterations of the simplex method. So solving a program with less
3248  // rows is likely a better alternative. Note that the number of row of the
3249  // dual is the number of column of the primal.
3250  //
3251  // Note however that the default is a conservative factor because if the
3252  // user gives us a primal program, we assume he knows what he is doing and
3253  // sometimes a problem is a lot faster to solve in a given formulation
3254  // even if its dimension would say otherwise.
3255  //
3256  // Another reason to be conservative, is that the number of columns of the
3257  // dual is the number of rows of the primal plus up to two times the number of
3258  // columns of the primal.
3259  //
3260  // TODO(user): This effect can be lowered if we use some of the extra
3261  // variables as slack variable which we are not doing at this point.
3262  if (parameters_.solve_dual_problem() == GlopParameters::LET_SOLVER_DECIDE) {
3263  if (1.0 * primal_num_rows_.value() <
3264  parameters_.dualizer_threshold() * primal_num_cols_.value()) {
3265  return false;
3266  }
3267  }
3268 
3269  // Save the linear program bounds.
3270  // Also make sure that all the bounded variable have at least one bound set to
3271  // zero. This will be needed to post-solve a dual-basic solution into a
3272  // primal-basic one.
3273  const ColIndex num_cols = lp->num_variables();
3274  variable_lower_bounds_.assign(num_cols, 0.0);
3275  variable_upper_bounds_.assign(num_cols, 0.0);
3276  for (ColIndex col(0); col < num_cols; ++col) {
3277  const Fractional lower = lp->variable_lower_bounds()[col];
3278  const Fractional upper = lp->variable_upper_bounds()[col];
3279 
3280  // We need to shift one of the bound to zero.
3281  variable_lower_bounds_[col] = lower;
3282  variable_upper_bounds_[col] = upper;
3283  const Fractional value = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3284  if (value != 0.0) {
3285  lp->SetVariableBounds(col, lower - value, upper - value);
3286  SubtractColumnMultipleFromConstraintBound(col, value, lp);
3287  }
3288  }
3289 
3290  // Fill the information that will be needed during postsolve.
3291  //
3292  // TODO(user): This will break if PopulateFromDual() is changed. so document
3293  // the convention or make the function fill these vectors?
3294  dual_status_correspondence_.clear();
3295  for (RowIndex row(0); row < primal_num_rows_; ++row) {
3296  const Fractional lower_bound = lp->constraint_lower_bounds()[row];
3297  const Fractional upper_bound = lp->constraint_upper_bounds()[row];
3298  if (lower_bound == upper_bound) {
3299  dual_status_correspondence_.push_back(VariableStatus::FIXED_VALUE);
3300  } else if (upper_bound != kInfinity) {
3301  dual_status_correspondence_.push_back(VariableStatus::AT_UPPER_BOUND);
3302  } else if (lower_bound != -kInfinity) {
3303  dual_status_correspondence_.push_back(VariableStatus::AT_LOWER_BOUND);
3304  } else {
3305  LOG(DFATAL) << "There should be no free constraint in this lp.";
3306  }
3307  }
3308  slack_or_surplus_mapping_.clear();
3309  for (ColIndex col(0); col < primal_num_cols_; ++col) {
3310  const Fractional lower_bound = lp->variable_lower_bounds()[col];
3311  const Fractional upper_bound = lp->variable_upper_bounds()[col];
3312  if (lower_bound != -kInfinity) {
3313  dual_status_correspondence_.push_back(
3314  upper_bound == lower_bound ? VariableStatus::FIXED_VALUE
3316  slack_or_surplus_mapping_.push_back(col);
3317  }
3318  }
3319  for (ColIndex col(0); col < primal_num_cols_; ++col) {
3320  const Fractional lower_bound = lp->variable_lower_bounds()[col];
3321  const Fractional upper_bound = lp->variable_upper_bounds()[col];
3322  if (upper_bound != kInfinity) {
3323  dual_status_correspondence_.push_back(
3324  upper_bound == lower_bound ? VariableStatus::FIXED_VALUE
3326  slack_or_surplus_mapping_.push_back(col);
3327  }
3328  }
3329 
3330  // TODO(user): There are two different ways to deal with ranged rows when
3331  // taking the dual. The default way is to duplicate such rows, see
3332  // PopulateFromDual() for details. Another way is to call
3333  // lp->AddSlackVariablesForFreeAndBoxedRows() before calling
3334  // PopulateFromDual(). Adds an option to switch between the two as this may
3335  // change the running time?
3336  //
3337  // Note however that the default algorithm is likely to result in a faster
3338  // solving time because the dual program will have less rows.
3339  LinearProgram dual;
3340  dual.PopulateFromDual(*lp, &duplicated_rows_);
3341  dual.Swap(lp);
3342  return true;
3343 }
3344 
3345 // Note(user): This assumes that LinearProgram.PopulateFromDual() uses
3346 // the first ColIndex and RowIndex for the rows and columns of the given
3347 // problem.
3350  RETURN_IF_NULL(solution);
3351 
3352  DenseRow new_primal_values(primal_num_cols_, 0.0);
3353  VariableStatusRow new_variable_statuses(primal_num_cols_,
3355  DCHECK_LE(primal_num_cols_, RowToColIndex(solution->dual_values.size()));
3356  for (ColIndex col(0); col < primal_num_cols_; ++col) {
3357  RowIndex row = ColToRowIndex(col);
3358  const Fractional lower = variable_lower_bounds_[col];
3359  const Fractional upper = variable_upper_bounds_[col];
3360 
3361  // The new variable value corresponds to the dual value of the dual.
3362  // The shift applied during presolve needs to be removed.
3363  const Fractional shift = MinInMagnitudeOrZeroIfInfinite(lower, upper);
3364  new_primal_values[col] = solution->dual_values[row] + shift;
3365 
3366  // A variable will be VariableStatus::BASIC if the dual constraint is not.
3367  if (solution->constraint_statuses[row] != ConstraintStatus::BASIC) {
3368  new_variable_statuses[col] = VariableStatus::BASIC;
3369  } else {
3370  // Otherwise, the dual value must be zero (if the solution is feasible),
3371  // and the variable is at an exact bound or zero if it is
3372  // VariableStatus::FREE. Note that this works because the bounds are
3373  // shifted to 0.0 in the presolve!
3374  new_variable_statuses[col] = ComputeVariableStatus(shift, lower, upper);
3375  }
3376  }
3377 
3378  // A basic variable that corresponds to slack/surplus variable is the same as
3379  // a basic row. The new variable status (that was just set to
3380  // VariableStatus::BASIC above)
3381  // needs to be corrected and depends on the variable type (slack/surplus).
3382  const ColIndex begin = RowToColIndex(primal_num_rows_);
3383  const ColIndex end = dual_status_correspondence_.size();
3384  DCHECK_GE(solution->variable_statuses.size(), end);
3385  DCHECK_EQ(end - begin, slack_or_surplus_mapping_.size());
3386  for (ColIndex index(begin); index < end; ++index) {
3387  if (solution->variable_statuses[index] == VariableStatus::BASIC) {
3388  const ColIndex col = slack_or_surplus_mapping_[index - begin];
3389  const VariableStatus status = dual_status_correspondence_[index];
3390 
3391  // The new variable value is set to its exact bound because the dual
3392  // variable value can be imprecise.
3393  new_variable_statuses[col] = status;
3396  new_primal_values[col] = variable_upper_bounds_[col];
3397  } else {
3399  new_primal_values[col] = variable_lower_bounds_[col];
3400  }
3401  }
3402  }
3403 
3404  // Note the <= in the DCHECK, since we may need to add variables when taking
3405  // the dual.
3406  DCHECK_LE(primal_num_rows_, ColToRowIndex(solution->primal_values.size()));
3407  DenseColumn new_dual_values(primal_num_rows_, 0.0);
3408  ConstraintStatusColumn new_constraint_statuses(primal_num_rows_,
3410 
3411  // Note that the sign need to be corrected because of the special behavior of
3412  // PopulateFromDual() on a maximization problem, see the comment in the
3413  // declaration of PopulateFromDual().
3414  Fractional sign = primal_is_maximization_problem_ ? -1 : 1;
3415  for (RowIndex row(0); row < primal_num_rows_; ++row) {
3416  const ColIndex col = RowToColIndex(row);
3417  new_dual_values[row] = sign * solution->primal_values[col];
3418 
3419  // A constraint will be ConstraintStatus::BASIC if the dual variable is not.
3420  if (solution->variable_statuses[col] != VariableStatus::BASIC) {
3421  new_constraint_statuses[row] = ConstraintStatus::BASIC;
3422  if (duplicated_rows_[row] != kInvalidCol) {
3423  if (solution->variable_statuses[duplicated_rows_[row]] ==
3425  // The duplicated row is always about the lower bound.
3426  new_constraint_statuses[row] = ConstraintStatus::AT_LOWER_BOUND;
3427  }
3428  }
3429  } else {
3430  // ConstraintStatus::AT_LOWER_BOUND/ConstraintStatus::AT_UPPER_BOUND/
3431  // ConstraintStatus::FIXED depend on the type of the constraint at this
3432  // position.
3433  new_constraint_statuses[row] =
3434  VariableToConstraintStatus(dual_status_correspondence_[col]);
3435  }
3436 
3437  // If the original row was duplicated, we need to take into account the
3438  // value of the corresponding dual column.
3439  if (duplicated_rows_[row] != kInvalidCol) {
3440  new_dual_values[row] +=
3441  sign * solution->primal_values[duplicated_rows_[row]];
3442  }
3443 
3444  // Because non-basic variable values are exactly at one of their bounds, a
3445  // new basic constraint will have a dual value exactly equal to zero.
3446  DCHECK(new_dual_values[row] == 0 ||
3447  new_constraint_statuses[row] != ConstraintStatus::BASIC);
3448  }
3449 
3450  solution->status = ChangeStatusToDualStatus(solution->status);
3451  new_primal_values.swap(solution->primal_values);
3452  new_dual_values.swap(solution->dual_values);
3453  new_variable_statuses.swap(solution->variable_statuses);
3454  new_constraint_statuses.swap(solution->constraint_statuses);
3455 }
3456 
3458  ProblemStatus status) const {
3459  switch (status) {
3460  case ProblemStatus::PRIMAL_INFEASIBLE:
3461  return ProblemStatus::DUAL_INFEASIBLE;
3462  case ProblemStatus::DUAL_INFEASIBLE:
3463  return ProblemStatus::PRIMAL_INFEASIBLE;
3464  case ProblemStatus::PRIMAL_UNBOUNDED:
3465  return ProblemStatus::DUAL_UNBOUNDED;
3466  case ProblemStatus::DUAL_UNBOUNDED:
3467  return ProblemStatus::PRIMAL_UNBOUNDED;
3468  case ProblemStatus::PRIMAL_FEASIBLE:
3469  return ProblemStatus::DUAL_FEASIBLE;
3470  case ProblemStatus::DUAL_FEASIBLE:
3471  return ProblemStatus::PRIMAL_FEASIBLE;
3472  default:
3473  return status;
3474  }
3475 }
3476 
3477 // --------------------------------------------------------
3478 // ShiftVariableBoundsPreprocessor
3479 // --------------------------------------------------------
3480 
3483  RETURN_VALUE_IF_NULL(lp, false);
3484 
3485  // Save the linear program bounds before shifting them.
3486  bool all_variable_domains_contain_zero = true;
3487  const ColIndex num_cols = lp->num_variables();
3488  variable_initial_lbs_.assign(num_cols, 0.0);
3489  variable_initial_ubs_.assign(num_cols, 0.0);
3490  for (ColIndex col(0); col < num_cols; ++col) {
3491  variable_initial_lbs_[col] = lp->variable_lower_bounds()[col];
3492  variable_initial_ubs_[col] = lp->variable_upper_bounds()[col];
3493  if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3494  all_variable_domains_contain_zero = false;
3495  }
3496  }
3497  VLOG(1) << "Maximum variable bounds magnitude (before shift): "
3498  << ComputeMaxVariableBoundsMagnitude(*lp);
3499 
3500  // Abort early if there is nothing to do.
3501  if (all_variable_domains_contain_zero) return false;
3502 
3503  // Shift the variable bounds and compute the changes to the constraint bounds
3504  // and objective offset in a precise way.
3505  int num_bound_shifts = 0;
3506  const RowIndex num_rows = lp->num_constraints();
3507  KahanSum objective_offset;
3508  absl::StrongVector<RowIndex, KahanSum> row_offsets(num_rows.value());
3509  offsets_.assign(num_cols, 0.0);
3510  for (ColIndex col(0); col < num_cols; ++col) {
3511  if (0.0 < variable_initial_lbs_[col] || 0.0 > variable_initial_ubs_[col]) {
3512  Fractional offset = MinInMagnitudeOrZeroIfInfinite(
3513  variable_initial_lbs_[col], variable_initial_ubs_[col]);
3514  if (in_mip_context_ && lp->IsVariableInteger(col)) {
3515  // In the integer case, we truncate the number because if for instance
3516  // the lower bound is a positive integer + epsilon, we only want to
3517  // shift by the integer and leave the lower bound at epsilon.
3518  //
3519  // TODO(user): This would not be needed, if we always make the bound
3520  // of an integer variable integer before applying this preprocessor.
3521  offset = trunc(offset);
3522  } else {
3523  DCHECK_NE(offset, 0.0);
3524  }
3525  offsets_[col] = offset;
3526  lp->SetVariableBounds(col, variable_initial_lbs_[col] - offset,
3527  variable_initial_ubs_[col] - offset);
3528  const SparseColumn& sparse_column = lp->GetSparseColumn(col);
3529  for (const SparseColumn::Entry e : sparse_column) {
3530  row_offsets[e.row()].Add(e.coefficient() * offset);
3531  }
3532  objective_offset.Add(lp->objective_coefficients()[col] * offset);
3533  ++num_bound_shifts;
3534  }
3535  }
3536  VLOG(1) << "Maximum variable bounds magnitude (after " << num_bound_shifts
3537  << " shifts): " << ComputeMaxVariableBoundsMagnitude(*lp);
3538 
3539  // Apply the changes to the constraint bound and objective offset.
3540  for (RowIndex row(0); row < num_rows; ++row) {
3541  lp->SetConstraintBounds(
3542  row, lp->constraint_lower_bounds()[row] - row_offsets[row].Value(),
3543  lp->constraint_upper_bounds()[row] - row_offsets[row].Value());
3544  }
3545  lp->SetObjectiveOffset(lp->objective_offset() + objective_offset.Value());
3546  return true;
3547 }
3548 
3550  ProblemSolution* solution) const {
3552  RETURN_IF_NULL(solution);
3553  const ColIndex num_cols = solution->variable_statuses.size();
3554  for (ColIndex col(0); col < num_cols; ++col) {
3555  if (in_mip_context_) {
3556  solution->primal_values[col] += offsets_[col];
3557  } else {
3558  switch (solution->variable_statuses[col]) {
3560  ABSL_FALLTHROUGH_INTENDED;
3562  solution->primal_values[col] = variable_initial_lbs_[col];
3563  break;
3565  solution->primal_values[col] = variable_initial_ubs_[col];
3566  break;
3567  case VariableStatus::BASIC:
3568  solution->primal_values[col] += offsets_[col];
3569  break;
3570  case VariableStatus::FREE:
3571  break;
3572  }
3573  }
3574  }
3575 }
3576 
3577 // --------------------------------------------------------
3578 // ScalingPreprocessor
3579 // --------------------------------------------------------
3580 
3583  RETURN_VALUE_IF_NULL(lp, false);
3584  if (!parameters_.use_scaling()) return false;
3585 
3586  // Save the linear program bounds before scaling them.
3587  const ColIndex num_cols = lp->num_variables();
3588  variable_lower_bounds_.assign(num_cols, 0.0);
3589  variable_upper_bounds_.assign(num_cols, 0.0);
3590  for (ColIndex col(0); col < num_cols; ++col) {
3591  variable_lower_bounds_[col] = lp->variable_lower_bounds()[col];
3592  variable_upper_bounds_[col] = lp->variable_upper_bounds()[col];
3593  }
3594 
3595  // See the doc of these functions for more details.
3596  // It is important to call Scale() before the other two.
3597  Scale(lp, &scaler_, parameters_.scaling_method());
3598  cost_scaling_factor_ = lp->ScaleObjective(parameters_.cost_scaling());
3599  bound_scaling_factor_ = lp->ScaleBounds();
3600 
3601  return true;
3602 }
3603 
3606  RETURN_IF_NULL(solution);
3607 
3608  scaler_.ScaleRowVector(false, &(solution->primal_values));
3609  for (ColIndex col(0); col < solution->primal_values.size(); ++col) {
3610  solution->primal_values[col] *= bound_scaling_factor_;
3611  }
3612 
3613  scaler_.ScaleColumnVector(false, &(solution->dual_values));
3614  for (RowIndex row(0); row < solution->dual_values.size(); ++row) {
3615  solution->dual_values[row] *= cost_scaling_factor_;
3616  }
3617 
3618  // Make sure the variable are at they exact bounds according to their status.
3619  // This just remove a really low error (about 1e-15) but allows to keep the
3620  // variables at their exact bounds.
3621  const ColIndex num_cols = solution->primal_values.size();
3622  for (ColIndex col(0); col < num_cols; ++col) {
3623  switch (solution->variable_statuses[col]) {
3625  ABSL_FALLTHROUGH_INTENDED;
3627  solution->primal_values[col] = variable_upper_bounds_[col];
3628  break;
3630  solution->primal_values[col] = variable_lower_bounds_[col];
3631  break;
3632  case VariableStatus::FREE:
3633  ABSL_FALLTHROUGH_INTENDED;
3634  case VariableStatus::BASIC:
3635  break;
3636  }
3637  }
3638 }
3639 
3640 // --------------------------------------------------------
3641 // ToMinimizationPreprocessor
3642 // --------------------------------------------------------
3643 
3646  RETURN_VALUE_IF_NULL(lp, false);
3647  if (lp->IsMaximizationProblem()) {
3648  for (ColIndex col(0); col < lp->num_variables(); ++col) {
3649  const Fractional coeff = lp->objective_coefficients()[col];
3650  if (coeff != 0.0) {
3651  lp->SetObjectiveCoefficient(col, -coeff);
3652  }
3653  }
3654  lp->SetMaximizationProblem(false);
3657  }
3658  return false;
3659 }
3660 
3662  ProblemSolution* solution) const {}
3663 
3664 // --------------------------------------------------------
3665 // AddSlackVariablesPreprocessor
3666 // --------------------------------------------------------
3667 
3670  RETURN_VALUE_IF_NULL(lp, false);
3672  /*detect_integer_constraints=*/true);
3673  first_slack_col_ = lp->GetFirstSlackVariable();
3674  return true;
3675 }
3676 
3678  ProblemSolution* solution) const {
3680  RETURN_IF_NULL(solution);
3681 
3682  // Compute constraint statuses from statuses of slack variables.
3683  const RowIndex num_rows = solution->dual_values.size();
3684  for (RowIndex row(0); row < num_rows; ++row) {
3685  const ColIndex slack_col = first_slack_col_ + RowToColIndex(row);
3686  const VariableStatus variable_status =
3687  solution->variable_statuses[slack_col];
3688  ConstraintStatus constraint_status = ConstraintStatus::FREE;
3689  // The slack variables have reversed bounds - if the value of the variable
3690  // is at one bound, the value of the constraint is at the opposite bound.
3691  switch (variable_status) {
3693  constraint_status = ConstraintStatus::AT_UPPER_BOUND;
3694  break;
3696  constraint_status = ConstraintStatus::AT_LOWER_BOUND;
3697  break;
3698  default:
3699  constraint_status = VariableToConstraintStatus(variable_status);
3700  break;
3701  }
3702  solution->constraint_statuses[row] = constraint_status;
3703  }
3704 
3705  // Drop the primal values and variable statuses for slack variables.
3706  solution->primal_values.resize(first_slack_col_, 0.0);
3707  solution->variable_statuses.resize(first_slack_col_, VariableStatus::FREE);
3708 }
3709 
3710 } // namespace glop
3711 } // namespace operations_research
operations_research::glop::ImpliedFreePreprocessor
Definition: preprocessor.h:565
operations_research::glop::FreeConstraintPreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:2057
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:3481
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:1636
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:3549
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
VLOG
#define VLOG(verboselevel)
Definition: base/logging.h:978
absl::StrongVector::push_back
void push_back(const value_type &x)
Definition: strong_vector.h:158
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:519
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:971
LOG
#define LOG(severity)
Definition: base/logging.h:420
operations_research::glop::kInvalidCol
const ColIndex kInvalidCol(-1)
operations_research::glop::Preprocessor::parameters_
const GlopParameters & parameters_
Definition: preprocessor.h:91
absl::StrongVector::size
size_type size() const
Definition: strong_vector.h:147
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:3677
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:1027
operations_research::glop::LinearProgram::ScaleObjective
Fractional ScaleObjective(GlopParameters::CostScalingAlgorithm method)
Definition: lp_data.cc:1157
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
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:851
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:3604
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:735
operations_research::IsIntegerWithinTolerance
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition: fp_utils.h:161
operations_research::glop::DoubletonFreeColumnPreprocessor
Definition: preprocessor.h:611
operations_research::glop::SparseColumn
Definition: sparse_column.h:44
operations_research::glop::AddSlackVariablesPreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:3668
operations_research::glop::FreeConstraintPreprocessor::RecoverSolution
void RecoverSolution(ProblemSolution *solution) const final
Definition: preprocessor.cc:2072
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:782
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:2131
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:2887
DCHECK_NE
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:886
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:2682
operations_research::glop::UnconstrainedVariablePreprocessor
Definition: preprocessor.h:662
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:3581
operations_research::glop::LinearProgram::DeleteRows
void DeleteRows(const DenseBooleanColumn &rows_to_delete)
Definition: lp_data.cc:1227
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:1804
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:2103
operations_research::glop::DoubletonFreeColumnPreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:1530
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
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:2083
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:2915
operations_research::glop::DualizerPreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:3232
operations_research::glop::ToMinimizationPreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:3644
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:891
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
DCHECK
#define DCHECK(condition)
Definition: base/logging.h:884
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:852
operations_research::glop::RemoveNearZeroEntriesPreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:2813
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:3348
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:1989
operations_research::glop::ShiftVariableBoundsPreprocessor
Definition: preprocessor.h:949
operations_research::glop::UnconstrainedVariablePreprocessor::RemoveZeroCostUnconstrainedVariable
void RemoveZeroCostUnconstrainedVariable(ColIndex col, Fractional target_bound, LinearProgram *lp)
Definition: preprocessor.cc:1714
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:1192
operations_research::glop::Preprocessor::Preprocessor
Preprocessor(const GlopParameters *parameters)
Definition: preprocessor.cc:45
operations_research::glop::ToMinimizationPreprocessor::RecoverSolution
void RecoverSolution(ProblemSolution *solution) const final
Definition: preprocessor.cc:3661
operations_research::glop::SparseVector< RowIndex, SparseColumnIterator >::Entry
typename Iterator::Entry Entry
Definition: sparse_vector.h:91
DCHECK_GE
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:889
absl::StrongVector::clear
void clear()
Definition: strong_vector.h:170
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:484
operations_research::glop::Preprocessor::IsSmallerWithinFeasibilityTolerance
bool IsSmallerWithinFeasibilityTolerance(Fractional a, Fractional b) const
Definition: preprocessor.h:79
absl::StrongVector
Definition: strong_vector.h:76
operations_research::glop::ProblemStatus::OPTIMAL
@ OPTIMAL
coefficient
int64 coefficient
Definition: routing_search.cc:972
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:979
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:2935
operations_research::glop::LinearProgram
Definition: lp_data.h:55
operations_research::glop::DoubletonEqualityRowPreprocessor
Definition: preprocessor.h:804
absl::StrongVector::swap
void swap(StrongVector &x)
Definition: strong_vector.h:169
operations_research::glop::FreeConstraintPreprocessor
Definition: preprocessor.h:716
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:498
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:849
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:2143
DCHECK_EQ
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:885
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
absl::StrongVector::resize
void resize(size_type new_size)
Definition: strong_vector.h:150
operations_research::glop::UnconstrainedVariablePreprocessor::Run
bool Run(LinearProgram *lp) final
Definition: preprocessor.cc:1761
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
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:3195
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:850
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:499
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
DCHECK_LE
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:887
operations_research::glop::DualizerPreprocessor::ChangeStatusToDualStatus
ProblemStatus ChangeStatusToDualStatus(ProblemStatus status) const
Definition: preprocessor.cc:3457
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:3115
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:1506
operations_research::glop::SingletonPreprocessor
Definition: preprocessor.h:396
parameters
SatParameters parameters
Definition: cp_model_fz_solver.cc:108
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:808
status.h
operations_research::glop::SingletonPreprocessor::RecoverSolution
void RecoverSolution(ProblemSolution *solution) const final
Definition: preprocessor.cc:2760
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:2880
operations_research::glop::VariableStatus::AT_LOWER_BOUND
@ AT_LOWER_BOUND
operations_research::glop::EmptyConstraintPreprocessor::RecoverSolution
void RecoverSolution(ProblemSolution *solution) const final
Definition: preprocessor.cc:2120