OR-Tools  9.2
lp_data.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <string>
20#include <utility>
21
22#include "absl/container/flat_hash_map.h"
23#include "absl/strings/str_cat.h"
24#include "absl/strings/str_format.h"
31
32namespace operations_research {
33namespace glop {
34
35namespace {
36
37// This should be the same as DCHECK(AreBoundsValid()), but the DCHECK() are
38// split to give more meaningful information to the user in case of failure.
39void DebugCheckBoundsValid(Fractional lower_bound, Fractional upper_bound) {
40 DCHECK(!std::isnan(lower_bound));
41 DCHECK(!std::isnan(upper_bound));
46}
47
48// Returns true if the bounds are the ones of a free or boxed row. Note that
49// a fixed row is not counted as boxed.
50bool AreBoundsFreeOrBoxed(Fractional lower_bound, Fractional upper_bound) {
51 if (lower_bound == -kInfinity && upper_bound == kInfinity) return true;
54 return true;
55 }
56 return false;
57}
58
59template <class I, class T>
60double Average(const absl::StrongVector<I, T>& v) {
61 const size_t size = v.size();
62 DCHECK_LT(0, size);
63 double sum = 0.0;
64 double n = 0.0; // n is used in a calculation involving doubles.
65 for (I i(0); i < size; ++i) {
66 if (v[i] == 0.0) continue;
67 ++n;
68 sum += static_cast<double>(v[i].value());
69 }
70 return n == 0.0 ? 0.0 : sum / n;
71}
72
73template <class I, class T>
74double StandardDeviation(const absl::StrongVector<I, T>& v) {
75 const size_t size = v.size();
76 double n = 0.0; // n is used in a calculation involving doubles.
77 double sigma_square = 0.0;
78 double sigma = 0.0;
79 for (I i(0); i < size; ++i) {
80 double sample = static_cast<double>(v[i].value());
81 if (sample == 0.0) continue;
82 sigma_square += sample * sample;
83 sigma += sample;
84 ++n;
85 }
86 return n == 0.0 ? 0.0 : sqrt((sigma_square - sigma * sigma / n) / n);
87}
88
89// Returns 0 when the vector is empty.
90template <class I, class T>
91T GetMaxElement(const absl::StrongVector<I, T>& v) {
92 const size_t size = v.size();
93 if (size == 0) {
94 return T(0);
95 }
96
97 T max_index = v[I(0)];
98 for (I i(1); i < size; ++i) {
99 if (max_index < v[i]) {
100 max_index = v[i];
101 }
102 }
103 return max_index;
104}
105
106} // anonymous namespace
107
108// --------------------------------------------------------
109// LinearProgram
110// --------------------------------------------------------
112 : matrix_(),
113 transpose_matrix_(),
114 constraint_lower_bounds_(),
115 constraint_upper_bounds_(),
116 constraint_names_(),
117 objective_coefficients_(),
118 variable_lower_bounds_(),
119 variable_upper_bounds_(),
120 variable_names_(),
121 variable_types_(),
122 integer_variables_list_(),
123 variable_table_(),
124 constraint_table_(),
125 objective_offset_(0.0),
126 objective_scaling_factor_(1.0),
127 maximize_(false),
128 columns_are_known_to_be_clean_(true),
129 transpose_matrix_is_consistent_(true),
130 integer_variables_list_is_consistent_(true),
131 name_(),
132 first_slack_variable_(kInvalidCol) {}
133
135 matrix_.Clear();
136 transpose_matrix_.Clear();
137
138 constraint_lower_bounds_.clear();
139 constraint_upper_bounds_.clear();
140 constraint_names_.clear();
141
142 objective_coefficients_.clear();
143 variable_lower_bounds_.clear();
144 variable_upper_bounds_.clear();
145 variable_types_.clear();
146 integer_variables_list_.clear();
147 variable_names_.clear();
148
149 constraint_table_.clear();
150 variable_table_.clear();
151
152 maximize_ = false;
153 objective_offset_ = 0.0;
154 objective_scaling_factor_ = 1.0;
155 columns_are_known_to_be_clean_ = true;
156 transpose_matrix_is_consistent_ = true;
157 integer_variables_list_is_consistent_ = true;
158 name_.clear();
159 first_slack_variable_ = kInvalidCol;
160}
161
163 DCHECK_EQ(kInvalidCol, first_slack_variable_)
164 << "New variables can't be added to programs that already have slack "
165 "variables. Consider calling LinearProgram::DeleteSlackVariables() "
166 "before adding new variables to the problem.";
167 objective_coefficients_.push_back(0.0);
168 variable_lower_bounds_.push_back(0);
169 variable_upper_bounds_.push_back(kInfinity);
170 variable_types_.push_back(VariableType::CONTINUOUS);
171 variable_names_.push_back("");
172 transpose_matrix_is_consistent_ = false;
173 return matrix_.AppendEmptyColumn();
174}
175
176ColIndex LinearProgram::CreateNewSlackVariable(bool is_integer_slack_variable,
179 const std::string& name) {
180 objective_coefficients_.push_back(0.0);
181 variable_lower_bounds_.push_back(lower_bound);
182 variable_upper_bounds_.push_back(upper_bound);
183 variable_types_.push_back(is_integer_slack_variable
186 variable_names_.push_back(name);
187 transpose_matrix_is_consistent_ = false;
188 return matrix_.AppendEmptyColumn();
189}
190
192 DCHECK_EQ(kInvalidCol, first_slack_variable_)
193 << "New constraints can't be added to programs that already have slack "
194 "variables. Consider calling LinearProgram::DeleteSlackVariables() "
195 "before adding new variables to the problem.";
196 const RowIndex row(constraint_names_.size());
197 matrix_.SetNumRows(row + 1);
198 constraint_lower_bounds_.push_back(Fractional(0.0));
199 constraint_upper_bounds_.push_back(Fractional(0.0));
200 constraint_names_.push_back("");
201 transpose_matrix_is_consistent_ = false;
202 return row;
203}
204
205ColIndex LinearProgram::FindOrCreateVariable(const std::string& variable_id) {
206 const absl::flat_hash_map<std::string, ColIndex>::iterator it =
207 variable_table_.find(variable_id);
208 if (it != variable_table_.end()) {
209 return it->second;
210 } else {
211 const ColIndex col = CreateNewVariable();
212 variable_names_[col] = variable_id;
213 variable_table_[variable_id] = col;
214 return col;
215 }
216}
217
219 const std::string& constraint_id) {
220 const absl::flat_hash_map<std::string, RowIndex>::iterator it =
221 constraint_table_.find(constraint_id);
222 if (it != constraint_table_.end()) {
223 return it->second;
224 } else {
225 const RowIndex row = CreateNewConstraint();
226 constraint_names_[row] = constraint_id;
227 constraint_table_[constraint_id] = row;
228 return row;
229 }
230}
231
232void LinearProgram::SetVariableName(ColIndex col, absl::string_view name) {
233 variable_names_[col] = std::string(name);
234}
235
237 const bool var_was_integer = IsVariableInteger(col);
238 variable_types_[col] = type;
239 const bool var_is_integer = IsVariableInteger(col);
240 if (var_is_integer != var_was_integer) {
241 integer_variables_list_is_consistent_ = false;
242 }
243}
244
245void LinearProgram::SetConstraintName(RowIndex row, absl::string_view name) {
246 constraint_names_[row] = std::string(name);
247}
248
251 if (dcheck_bounds_) DebugCheckBoundsValid(lower_bound, upper_bound);
252 const bool var_was_binary = IsVariableBinary(col);
253 variable_lower_bounds_[col] = lower_bound;
254 variable_upper_bounds_[col] = upper_bound;
255 const bool var_is_binary = IsVariableBinary(col);
256 if (var_is_binary != var_was_binary) {
257 integer_variables_list_is_consistent_ = false;
258 }
259}
260
261void LinearProgram::UpdateAllIntegerVariableLists() const {
262 if (integer_variables_list_is_consistent_) return;
263 integer_variables_list_.clear();
264 binary_variables_list_.clear();
265 non_binary_variables_list_.clear();
266 const ColIndex num_cols = num_variables();
267 for (ColIndex col(0); col < num_cols; ++col) {
268 if (IsVariableInteger(col)) {
269 integer_variables_list_.push_back(col);
270 if (IsVariableBinary(col)) {
271 binary_variables_list_.push_back(col);
272 } else {
273 non_binary_variables_list_.push_back(col);
274 }
275 }
276 }
277 integer_variables_list_is_consistent_ = true;
278}
279
280const std::vector<ColIndex>& LinearProgram::IntegerVariablesList() const {
281 UpdateAllIntegerVariableLists();
282 return integer_variables_list_;
283}
284
285const std::vector<ColIndex>& LinearProgram::BinaryVariablesList() const {
286 UpdateAllIntegerVariableLists();
287 return binary_variables_list_;
288}
289
290const std::vector<ColIndex>& LinearProgram::NonBinaryVariablesList() const {
291 UpdateAllIntegerVariableLists();
292 return non_binary_variables_list_;
293}
294
296 return variable_types_[col] == VariableType::INTEGER ||
297 variable_types_[col] == VariableType::IMPLIED_INTEGER;
298}
299
301 // TODO(user): bounds of binary variables (and of integer ones) should
302 // be integer. Add a preprocessor for that.
303 return IsVariableInteger(col) && (variable_lower_bounds_[col] < kEpsilon) &&
304 (variable_lower_bounds_[col] > Fractional(-1)) &&
305 (variable_upper_bounds_[col] > Fractional(1) - kEpsilon) &&
306 (variable_upper_bounds_[col] < 2);
307}
308
311 if (dcheck_bounds_) DebugCheckBoundsValid(lower_bound, upper_bound);
312 ResizeRowsIfNeeded(row);
313 constraint_lower_bounds_[row] = lower_bound;
314 constraint_upper_bounds_[row] = upper_bound;
315}
316
317void LinearProgram::SetCoefficient(RowIndex row, ColIndex col,
320 ResizeRowsIfNeeded(row);
321 columns_are_known_to_be_clean_ = false;
322 transpose_matrix_is_consistent_ = false;
324}
325
328 objective_coefficients_[col] = value;
329}
330
333 objective_offset_ = objective_offset;
334}
335
337 Fractional objective_scaling_factor) {
340 objective_scaling_factor_ = objective_scaling_factor;
341}
342
344 maximize_ = maximize;
345}
346
348 if (columns_are_known_to_be_clean_) return;
349 matrix_.CleanUp();
350 columns_are_known_to_be_clean_ = true;
351 transpose_matrix_is_consistent_ = false;
352}
353
355 if (columns_are_known_to_be_clean_) return true;
356 columns_are_known_to_be_clean_ = matrix_.IsCleanedUp();
357 return columns_are_known_to_be_clean_;
358}
359
360std::string LinearProgram::GetVariableName(ColIndex col) const {
361 return col >= variable_names_.size() || variable_names_[col].empty()
362 ? absl::StrFormat("c%d", col.value())
363 : variable_names_[col];
364}
365
366std::string LinearProgram::GetConstraintName(RowIndex row) const {
367 return row >= constraint_names_.size() || constraint_names_[row].empty()
368 ? absl::StrFormat("r%d", row.value())
369 : constraint_names_[row];
370}
371
373 return variable_types_[col];
374}
375
377 if (!transpose_matrix_is_consistent_) {
378 transpose_matrix_.PopulateFromTranspose(matrix_);
379 transpose_matrix_is_consistent_ = true;
380 }
381 DCHECK_EQ(transpose_matrix_.num_rows().value(), matrix_.num_cols().value());
382 DCHECK_EQ(transpose_matrix_.num_cols().value(), matrix_.num_rows().value());
383 return transpose_matrix_;
384}
385
387 if (!transpose_matrix_is_consistent_) {
388 transpose_matrix_.PopulateFromTranspose(matrix_);
389 }
390 // This enables a client to start modifying the matrix and then abort and not
391 // call UseTransposeMatrixAsReference(). Then, the other client of
392 // GetTransposeSparseMatrix() will still see the correct matrix.
393 transpose_matrix_is_consistent_ = false;
394 return &transpose_matrix_;
395}
396
398 DCHECK_EQ(transpose_matrix_.num_rows().value(), matrix_.num_cols().value());
399 DCHECK_EQ(transpose_matrix_.num_cols().value(), matrix_.num_rows().value());
400 matrix_.PopulateFromTranspose(transpose_matrix_);
401 transpose_matrix_is_consistent_ = true;
402}
403
405 transpose_matrix_.Clear();
406 transpose_matrix_is_consistent_ = false;
407}
408
410 return matrix_.column(col);
411}
412
414 columns_are_known_to_be_clean_ = false;
415 transpose_matrix_is_consistent_ = false;
416 return matrix_.mutable_column(col);
417}
418
420 ColIndex col) const {
421 return maximize_ ? -objective_coefficients()[col]
423}
424
426 Fractional min_magnitude = 0.0;
427 Fractional max_magnitude = 0.0;
428 matrix_.ComputeMinAndMaxMagnitudes(&min_magnitude, &max_magnitude);
429 return absl::StrFormat(
430 "%d rows, %d columns, %d entries with magnitude in [%e, %e]",
432 // static_cast<int64_t> is needed because the Android port uses int32_t.
433 static_cast<int64_t>(num_entries().value()), min_magnitude,
434 max_magnitude);
435}
436
437namespace {
438
439template <typename FractionalValues>
440void UpdateStats(const FractionalValues& values, int64_t* num_non_zeros,
441 Fractional* min_value, Fractional* max_value) {
442 for (const Fractional v : values) {
443 if (v == 0 || v == kInfinity || v == -kInfinity) continue;
444 *min_value = std::min(*min_value, v);
445 *max_value = std::max(*max_value, v);
446 ++(*num_non_zeros);
447 }
448}
449
450} // namespace
451
453 int64_t num_non_zeros = 0;
454 Fractional min_value = +kInfinity;
455 Fractional max_value = -kInfinity;
456 UpdateStats(objective_coefficients_, &num_non_zeros, &min_value, &max_value);
457 if (num_non_zeros == 0) {
458 return "No objective term. This is a pure feasibility problem.";
459 } else {
460 return absl::StrFormat("%d non-zeros, range [%e, %e]", num_non_zeros,
461 min_value, max_value);
462 }
463}
464
466 int64_t num_non_zeros = 0;
467 Fractional min_value = +kInfinity;
468 Fractional max_value = -kInfinity;
469 UpdateStats(variable_lower_bounds_, &num_non_zeros, &min_value, &max_value);
470 UpdateStats(variable_upper_bounds_, &num_non_zeros, &min_value, &max_value);
471 UpdateStats(constraint_lower_bounds_, &num_non_zeros, &min_value, &max_value);
472 UpdateStats(constraint_upper_bounds_, &num_non_zeros, &min_value, &max_value);
473 if (num_non_zeros == 0) {
474 return "All variables/constraints bounds are zero or +/- infinity.";
475 } else {
476 return absl::StrFormat("%d non-zeros, range [%e, %e]", num_non_zeros,
477 min_value, max_value);
478 }
479}
480
482 const DenseRow& solution, Fractional absolute_tolerance) const {
483 DCHECK_EQ(solution.size(), num_variables());
484 if (solution.size() != num_variables()) return false;
485 const ColIndex num_cols = num_variables();
486 for (ColIndex col = ColIndex(0); col < num_cols; ++col) {
487 if (!IsFinite(solution[col])) return false;
488 const Fractional lb_error = variable_lower_bounds()[col] - solution[col];
489 const Fractional ub_error = solution[col] - variable_upper_bounds()[col];
490 if (lb_error > absolute_tolerance || ub_error > absolute_tolerance) {
491 return false;
492 }
493 }
494 return true;
495}
496
498 Fractional absolute_tolerance) const {
499 if (!SolutionIsWithinVariableBounds(solution, absolute_tolerance)) {
500 return false;
501 }
502 const SparseMatrix& transpose = GetTransposeSparseMatrix();
503 const RowIndex num_rows = num_constraints();
504 for (RowIndex row = RowIndex(0); row < num_rows; ++row) {
505 const Fractional sum =
506 ScalarProduct(solution, transpose.column(RowToColIndex(row)));
507 if (!IsFinite(sum)) return false;
508 const Fractional lb_error = constraint_lower_bounds()[row] - sum;
509 const Fractional ub_error = sum - constraint_upper_bounds()[row];
510 if (lb_error > absolute_tolerance || ub_error > absolute_tolerance) {
511 return false;
512 }
513 }
514 return true;
515}
516
518 Fractional absolute_tolerance) const {
519 DCHECK_EQ(solution.size(), num_variables());
520 if (solution.size() != num_variables()) return false;
521 for (ColIndex col : IntegerVariablesList()) {
522 if (!IsFinite(solution[col])) return false;
523 const Fractional fractionality = fabs(solution[col] - round(solution[col]));
524 if (fractionality > absolute_tolerance) return false;
525 }
526 return true;
527}
528
530 Fractional absolute_tolerance) const {
531 return SolutionIsLPFeasible(solution, absolute_tolerance) &&
532 SolutionIsInteger(solution, absolute_tolerance);
533}
534
536 CHECK(solution != nullptr);
537 const ColIndex num_cols = GetFirstSlackVariable();
538 const SparseMatrix& transpose = GetTransposeSparseMatrix();
539 const RowIndex num_rows = num_constraints();
540 CHECK_EQ(solution->size(), num_variables());
541 for (RowIndex row = RowIndex(0); row < num_rows; ++row) {
543 *solution, transpose.column(RowToColIndex(row)), num_cols.value());
544 const ColIndex slack_variable = GetSlackVariable(row);
545 CHECK_NE(slack_variable, kInvalidCol);
546 (*solution)[slack_variable] = -sum;
547 }
548}
549
551 Fractional value) const {
553}
554
556 Fractional value) const {
558}
559
560std::string LinearProgram::Dump() const {
561 // Objective line.
562 std::string output = maximize_ ? "max:" : "min:";
563 if (objective_offset_ != 0.0) {
564 output += Stringify(objective_offset_);
565 }
566 const ColIndex num_cols = num_variables();
567 for (ColIndex col(0); col < num_cols; ++col) {
569 if (coeff != 0.0) {
570 output += StringifyMonomial(coeff, GetVariableName(col), false);
571 }
572 }
573 output.append(";\n");
574
575 // Constraints.
576 const RowIndex num_rows = num_constraints();
577 for (RowIndex row(0); row < num_rows; ++row) {
580 output += GetConstraintName(row);
581 output += ":";
582 if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
583 output += " ";
584 output += Stringify(lower_bound);
585 output += " <=";
586 }
587 for (ColIndex col(0); col < num_cols; ++col) {
588 const Fractional coeff = matrix_.LookUpValue(row, col);
589 output += StringifyMonomial(coeff, GetVariableName(col), false);
590 }
591 if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
592 output += " <= ";
593 output += Stringify(upper_bound);
594 } else if (lower_bound == upper_bound) {
595 output += " = ";
596 output += Stringify(upper_bound);
597 } else if (lower_bound != -kInfinity) {
598 output += " >= ";
599 output += Stringify(lower_bound);
600 } else if (lower_bound != kInfinity) {
601 output += " <= ";
602 output += Stringify(upper_bound);
603 }
604 output += ";\n";
605 }
606
607 // Variables.
608 for (ColIndex col(0); col < num_cols; ++col) {
611 if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
612 output += Stringify(lower_bound);
613 output += " <= ";
614 }
615 output += GetVariableName(col);
616 if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
617 output += " <= ";
618 output += Stringify(upper_bound);
619 } else if (lower_bound == upper_bound) {
620 output += " = ";
621 output += Stringify(upper_bound);
622 } else if (lower_bound != -kInfinity) {
623 output += " >= ";
624 output += Stringify(lower_bound);
625 } else if (lower_bound != kInfinity) {
626 output += " <= ";
627 output += Stringify(upper_bound);
628 }
629 output += ";\n";
630 }
631
632 // Integer variables.
633 // TODO(user): if needed provide similar output for binary variables.
634 const std::vector<ColIndex>& integer_variables = IntegerVariablesList();
635 if (!integer_variables.empty()) {
636 output += "int";
637 for (ColIndex col : integer_variables) {
638 output += " ";
639 output += GetVariableName(col);
640 }
641 output += ";\n";
642 }
643
644 return output;
645}
646
647std::string LinearProgram::DumpSolution(const DenseRow& variable_values) const {
648 DCHECK_EQ(variable_values.size(), num_variables());
649 std::string output;
650 for (ColIndex col(0); col < variable_values.size(); ++col) {
651 if (!output.empty()) absl::StrAppend(&output, ", ");
652 absl::StrAppend(&output, GetVariableName(col), " = ",
653 (variable_values[col]));
654 }
655 return output;
656}
657
659 return ProblemStatFormatter(
660 "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,"
661 "%d,%d,%d,%d");
662}
663
665 return ProblemStatFormatter(
666 "Number of rows : %d\n"
667 "Number of variables in file : %d\n"
668 "Number of entries (non-zeros) : %d\n"
669 "Number of entries in the objective : %d\n"
670 "Number of entries in the right-hand side : %d\n"
671 "Number of <= constraints : %d\n"
672 "Number of >= constraints : %d\n"
673 "Number of = constraints : %d\n"
674 "Number of range constraints : %d\n"
675 "Number of non-negative variables : %d\n"
676 "Number of boxed variables : %d\n"
677 "Number of free variables : %d\n"
678 "Number of fixed variables : %d\n"
679 "Number of other variables : %d\n"
680 "Number of integer variables : %d\n"
681 "Number of binary variables : %d\n"
682 "Number of non-binary integer variables : %d\n"
683 "Number of continuous variables : %d\n");
684}
685
687 return NonZeroStatFormatter("%.2f%%,%d,%.2f,%.2f,%d,%.2f,%.2f");
688}
689
691 return NonZeroStatFormatter(
692 "Fill rate : %.2f%%\n"
693 "Entries in row (Max / average / std. dev.) : %d / %.2f / %.2f\n"
694 "Entries in column (Max / average / std. dev.): %d / %.2f / %.2f\n");
695}
696
698 bool detect_integer_constraints) {
699 // Clean up the matrix. We're going to add entries, but we'll only be adding
700 // them to new columns, and only one entry per column, which does not
701 // invalidate the "cleanness" of the matrix.
702 CleanUp();
703
704 // Detect which constraints produce an integer slack variable. A constraint
705 // has an integer slack variable, if it contains only integer variables with
706 // integer coefficients. We do not check the bounds of the constraints,
707 // because in such case, they will be tightened to integer values by the
708 // preprocessors.
709 //
710 // We don't use the transpose, because it might not be valid and it would be
711 // inefficient to update it and invalidate it again at the end of this
712 // preprocessor.
713 DenseBooleanColumn has_integer_slack_variable(num_constraints(),
714 detect_integer_constraints);
715 if (detect_integer_constraints) {
716 for (ColIndex col(0); col < num_variables(); ++col) {
717 const SparseColumn& column = matrix_.column(col);
718 const bool is_integer_variable = IsVariableInteger(col);
719 for (const SparseColumn::Entry& entry : column) {
720 const RowIndex row = entry.row();
721 has_integer_slack_variable[row] =
722 has_integer_slack_variable[row] && is_integer_variable &&
723 round(entry.coefficient()) == entry.coefficient();
724 }
725 }
726 }
727
728 // Extend the matrix of the problem with an identity matrix.
729 const ColIndex original_num_variables = num_variables();
730 for (RowIndex row(0); row < num_constraints(); ++row) {
731 ColIndex slack_variable_index = GetSlackVariable(row);
732 if (slack_variable_index != kInvalidCol &&
733 slack_variable_index < original_num_variables) {
734 // Slack variable is already present in this constraint.
735 continue;
736 }
737 const ColIndex slack_col = CreateNewSlackVariable(
738 has_integer_slack_variable[row], -constraint_upper_bounds_[row],
739 -constraint_lower_bounds_[row], absl::StrCat("s", row.value()));
740 SetCoefficient(row, slack_col, 1.0);
741 SetConstraintBounds(row, 0.0, 0.0);
742 }
743
744 columns_are_known_to_be_clean_ = true;
745 transpose_matrix_is_consistent_ = false;
746 if (first_slack_variable_ == kInvalidCol) {
747 first_slack_variable_ = original_num_variables;
748 }
749}
750
752 return first_slack_variable_;
753}
754
755ColIndex LinearProgram::GetSlackVariable(RowIndex row) const {
756 DCHECK_GE(row, RowIndex(0));
758 if (first_slack_variable_ == kInvalidCol) {
759 return kInvalidCol;
760 }
761 return first_slack_variable_ + RowToColIndex(row);
762}
763
765 RowToColMapping* duplicated_rows) {
766 const ColIndex dual_num_variables = dual.num_variables();
767 const RowIndex dual_num_constraints = dual.num_constraints();
768 Clear();
769
770 // We always take the dual in its minimization form thanks to the
771 // GetObjectiveCoefficientForMinimizationVersion() below, so this will always
772 // be a maximization problem.
774
775 // Taking the dual does not change the offset nor the objective scaling
776 // factor.
779
780 // Create the dual variables y, with bounds depending on the type
781 // of constraints in the primal.
782 for (RowIndex dual_row(0); dual_row < dual_num_constraints; ++dual_row) {
784 const ColIndex col = RowToColIndex(dual_row);
785 const Fractional lower_bound = dual.constraint_lower_bounds()[dual_row];
786 const Fractional upper_bound = dual.constraint_upper_bounds()[dual_row];
787 if (lower_bound == upper_bound) {
790 } else if (upper_bound != kInfinity) {
791 // Note that for a ranged constraint, the loop will be continued here.
792 // This is wanted because we want to deal with the lower_bound afterwards.
795 } else if (lower_bound != -kInfinity) {
798 } else {
799 // This code does not support free rows in linear_program.
800 LOG(DFATAL) << "PopulateFromDual() was called with a program "
801 << "containing free constraints.";
802 }
803 }
804 // Create the dual slack variables v.
805 for (ColIndex dual_col(0); dual_col < dual_num_variables; ++dual_col) {
806 const Fractional lower_bound = dual.variable_lower_bounds()[dual_col];
807 if (lower_bound != -kInfinity) {
808 const ColIndex col = CreateNewVariable();
811 const RowIndex row = ColToRowIndex(dual_col);
813 }
814 }
815 // Create the dual surplus variables w.
816 for (ColIndex dual_col(0); dual_col < dual_num_variables; ++dual_col) {
817 const Fractional upper_bound = dual.variable_upper_bounds()[dual_col];
818 if (upper_bound != kInfinity) {
819 const ColIndex col = CreateNewVariable();
822 const RowIndex row = ColToRowIndex(dual_col);
824 }
825 }
826 // Store the transpose of the matrix.
827 for (ColIndex dual_col(0); dual_col < dual_num_variables; ++dual_col) {
828 const RowIndex row = ColToRowIndex(dual_col);
829 const Fractional row_bound =
831 SetConstraintBounds(row, row_bound, row_bound);
832 for (const SparseColumn::Entry e : dual.GetSparseColumn(dual_col)) {
833 const RowIndex dual_row = e.row();
834 const ColIndex col = RowToColIndex(dual_row);
835 SetCoefficient(row, col, e.coefficient());
836 }
837 }
838
839 // Take care of ranged constraints.
840 duplicated_rows->assign(dual_num_constraints, kInvalidCol);
841 for (RowIndex dual_row(0); dual_row < dual_num_constraints; ++dual_row) {
842 const Fractional lower_bound = dual.constraint_lower_bounds()[dual_row];
843 const Fractional upper_bound = dual.constraint_upper_bounds()[dual_row];
844 if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
846
847 // upper_bound was done in a loop above, now do the lower_bound.
848 const ColIndex col = CreateNewVariable();
852 matrix_.column(RowToColIndex(dual_row)));
853 (*duplicated_rows)[dual_row] = col;
854 }
855 }
856
857 // We know that the columns are ordered by rows.
858 columns_are_known_to_be_clean_ = true;
859 transpose_matrix_is_consistent_ = false;
860}
861
863 const LinearProgram& linear_program) {
864 matrix_.PopulateFromSparseMatrix(linear_program.matrix_);
865 if (linear_program.transpose_matrix_is_consistent_) {
866 transpose_matrix_is_consistent_ = true;
867 transpose_matrix_.PopulateFromSparseMatrix(
868 linear_program.transpose_matrix_);
869 } else {
870 transpose_matrix_is_consistent_ = false;
871 transpose_matrix_.Clear();
872 }
873
874 constraint_lower_bounds_ = linear_program.constraint_lower_bounds_;
875 constraint_upper_bounds_ = linear_program.constraint_upper_bounds_;
876 constraint_names_ = linear_program.constraint_names_;
877 constraint_table_.clear();
878
879 PopulateNameObjectiveAndVariablesFromLinearProgram(linear_program);
880 first_slack_variable_ = linear_program.first_slack_variable_;
881}
882
884 const LinearProgram& lp, const RowPermutation& row_permutation,
885 const ColumnPermutation& col_permutation) {
886 DCHECK(lp.IsCleanedUp());
887 DCHECK_EQ(row_permutation.size(), lp.num_constraints());
888 DCHECK_EQ(col_permutation.size(), lp.num_variables());
890 Clear();
891
892 // Populate matrix coefficients.
893 ColumnPermutation inverse_col_permutation;
894 inverse_col_permutation.PopulateFromInverse(col_permutation);
895 matrix_.PopulateFromPermutedMatrix(lp.matrix_, row_permutation,
896 inverse_col_permutation);
898
899 // Populate constraints.
900 ApplyPermutation(row_permutation, lp.constraint_lower_bounds(),
901 &constraint_lower_bounds_);
902 ApplyPermutation(row_permutation, lp.constraint_upper_bounds(),
903 &constraint_upper_bounds_);
904
905 // Populate variables.
906 ApplyPermutation(col_permutation, lp.objective_coefficients(),
907 &objective_coefficients_);
908 ApplyPermutation(col_permutation, lp.variable_lower_bounds(),
909 &variable_lower_bounds_);
910 ApplyPermutation(col_permutation, lp.variable_upper_bounds(),
911 &variable_upper_bounds_);
912 ApplyPermutation(col_permutation, lp.variable_types(), &variable_types_);
913 integer_variables_list_is_consistent_ = false;
914
915 // There is no vector based accessor to names, because they may be created
916 // on the fly.
917 constraint_names_.resize(lp.num_constraints());
918 for (RowIndex old_row(0); old_row < lp.num_constraints(); ++old_row) {
919 const RowIndex new_row = row_permutation[old_row];
920 constraint_names_[new_row] = lp.constraint_names_[old_row];
921 }
922 variable_names_.resize(lp.num_variables());
923 for (ColIndex old_col(0); old_col < lp.num_variables(); ++old_col) {
924 const ColIndex new_col = col_permutation[old_col];
925 variable_names_[new_col] = lp.variable_names_[old_col];
926 }
927
928 // Populate singular fields.
929 maximize_ = lp.maximize_;
930 objective_offset_ = lp.objective_offset_;
931 objective_scaling_factor_ = lp.objective_scaling_factor_;
932 name_ = lp.name_;
933}
934
936 const LinearProgram& linear_program) {
937 matrix_.PopulateFromZero(RowIndex(0), linear_program.num_variables());
938 first_slack_variable_ = kInvalidCol;
939 transpose_matrix_is_consistent_ = false;
940 transpose_matrix_.Clear();
941
942 constraint_lower_bounds_.clear();
943 constraint_upper_bounds_.clear();
944 constraint_names_.clear();
945 constraint_table_.clear();
946
947 PopulateNameObjectiveAndVariablesFromLinearProgram(linear_program);
948}
949
950void LinearProgram::PopulateNameObjectiveAndVariablesFromLinearProgram(
951 const LinearProgram& linear_program) {
952 objective_coefficients_ = linear_program.objective_coefficients_;
953 variable_lower_bounds_ = linear_program.variable_lower_bounds_;
954 variable_upper_bounds_ = linear_program.variable_upper_bounds_;
955 variable_names_ = linear_program.variable_names_;
956 variable_types_ = linear_program.variable_types_;
957 integer_variables_list_is_consistent_ =
958 linear_program.integer_variables_list_is_consistent_;
959 integer_variables_list_ = linear_program.integer_variables_list_;
960 binary_variables_list_ = linear_program.binary_variables_list_;
961 non_binary_variables_list_ = linear_program.non_binary_variables_list_;
962 variable_table_.clear();
963
964 maximize_ = linear_program.maximize_;
965 objective_offset_ = linear_program.objective_offset_;
966 objective_scaling_factor_ = linear_program.objective_scaling_factor_;
967 columns_are_known_to_be_clean_ =
968 linear_program.columns_are_known_to_be_clean_;
969 name_ = linear_program.name_;
970}
971
973 const SparseMatrix& coefficients, const DenseColumn& left_hand_sides,
974 const DenseColumn& right_hand_sides,
976 const RowIndex num_new_constraints = coefficients.num_rows();
977 DCHECK_EQ(num_variables(), coefficients.num_cols());
978 DCHECK_EQ(num_new_constraints, left_hand_sides.size());
979 DCHECK_EQ(num_new_constraints, right_hand_sides.size());
980 DCHECK_EQ(num_new_constraints, names.size());
981
983 transpose_matrix_is_consistent_ = false;
984 transpose_matrix_.Clear();
985 columns_are_known_to_be_clean_ = false;
986
987 // Copy constraint bounds and names from linear_program.
988 constraint_lower_bounds_.insert(constraint_lower_bounds_.end(),
989 left_hand_sides.begin(),
990 left_hand_sides.end());
991 constraint_upper_bounds_.insert(constraint_upper_bounds_.end(),
992 right_hand_sides.begin(),
993 right_hand_sides.end());
994 constraint_names_.insert(constraint_names_.end(), names.begin(), names.end());
995}
996
998 const SparseMatrix& coefficients, const DenseColumn& left_hand_sides,
999 const DenseColumn& right_hand_sides,
1001 bool detect_integer_constraints_for_slack) {
1002 AddConstraints(coefficients, left_hand_sides, right_hand_sides, names);
1003 AddSlackVariablesWhereNecessary(detect_integer_constraints_for_slack);
1004}
1005
1007 const DenseRow& variable_lower_bounds,
1008 const DenseRow& variable_upper_bounds) {
1009 const ColIndex num_vars = num_variables();
1012
1013 DenseRow new_lower_bounds(num_vars, 0);
1014 DenseRow new_upper_bounds(num_vars, 0);
1015 for (ColIndex i(0); i < num_vars; ++i) {
1016 const Fractional new_lower_bound =
1017 std::max(variable_lower_bounds[i], variable_lower_bounds_[i]);
1018 const Fractional new_upper_bound =
1019 std::min(variable_upper_bounds[i], variable_upper_bounds_[i]);
1020 if (new_lower_bound > new_upper_bound) {
1021 return false;
1022 }
1023 new_lower_bounds[i] = new_lower_bound;
1024 new_upper_bounds[i] = new_upper_bound;
1025 }
1026 variable_lower_bounds_.swap(new_lower_bounds);
1027 variable_upper_bounds_.swap(new_upper_bounds);
1028 return true;
1029}
1030
1031void LinearProgram::Swap(LinearProgram* linear_program) {
1032 matrix_.Swap(&linear_program->matrix_);
1033 transpose_matrix_.Swap(&linear_program->transpose_matrix_);
1034
1035 constraint_lower_bounds_.swap(linear_program->constraint_lower_bounds_);
1036 constraint_upper_bounds_.swap(linear_program->constraint_upper_bounds_);
1037 constraint_names_.swap(linear_program->constraint_names_);
1038
1039 objective_coefficients_.swap(linear_program->objective_coefficients_);
1040 variable_lower_bounds_.swap(linear_program->variable_lower_bounds_);
1041 variable_upper_bounds_.swap(linear_program->variable_upper_bounds_);
1042 variable_names_.swap(linear_program->variable_names_);
1043 variable_types_.swap(linear_program->variable_types_);
1044 integer_variables_list_.swap(linear_program->integer_variables_list_);
1045 binary_variables_list_.swap(linear_program->binary_variables_list_);
1046 non_binary_variables_list_.swap(linear_program->non_binary_variables_list_);
1047
1048 variable_table_.swap(linear_program->variable_table_);
1049 constraint_table_.swap(linear_program->constraint_table_);
1050
1051 std::swap(maximize_, linear_program->maximize_);
1052 std::swap(objective_offset_, linear_program->objective_offset_);
1053 std::swap(objective_scaling_factor_,
1054 linear_program->objective_scaling_factor_);
1055 std::swap(columns_are_known_to_be_clean_,
1056 linear_program->columns_are_known_to_be_clean_);
1057 std::swap(transpose_matrix_is_consistent_,
1058 linear_program->transpose_matrix_is_consistent_);
1059 std::swap(integer_variables_list_is_consistent_,
1060 linear_program->integer_variables_list_is_consistent_);
1061 name_.swap(linear_program->name_);
1062 std::swap(first_slack_variable_, linear_program->first_slack_variable_);
1063}
1064
1065void LinearProgram::DeleteColumns(const DenseBooleanRow& columns_to_delete) {
1066 if (columns_to_delete.empty()) return;
1067 integer_variables_list_is_consistent_ = false;
1068 const ColIndex num_cols = num_variables();
1069 ColumnPermutation permutation(num_cols);
1070 ColIndex new_index(0);
1071 for (ColIndex col(0); col < num_cols; ++col) {
1072 permutation[col] = new_index;
1073 if (col >= columns_to_delete.size() || !columns_to_delete[col]) {
1074 objective_coefficients_[new_index] = objective_coefficients_[col];
1075 variable_lower_bounds_[new_index] = variable_lower_bounds_[col];
1076 variable_upper_bounds_[new_index] = variable_upper_bounds_[col];
1077 variable_names_[new_index] = variable_names_[col];
1078 variable_types_[new_index] = variable_types_[col];
1079 ++new_index;
1080 } else {
1081 permutation[col] = kInvalidCol;
1082 }
1083 }
1084
1085 matrix_.DeleteColumns(columns_to_delete);
1086 objective_coefficients_.resize(new_index, 0.0);
1087 variable_lower_bounds_.resize(new_index, 0.0);
1088 variable_upper_bounds_.resize(new_index, 0.0);
1089 variable_types_.resize(new_index, VariableType::CONTINUOUS);
1090 variable_names_.resize(new_index, "");
1091
1092 // Remove the id of the deleted columns and adjust the index of the other.
1093 absl::flat_hash_map<std::string, ColIndex>::iterator it =
1094 variable_table_.begin();
1095 while (it != variable_table_.end()) {
1096 const ColIndex col = it->second;
1097 if (col >= columns_to_delete.size() || !columns_to_delete[col]) {
1098 it->second = permutation[col];
1099 ++it;
1100 } else {
1101 // This safely deletes the entry and moves the iterator one step ahead.
1102 variable_table_.erase(it++);
1103 }
1104 }
1105
1106 // Eventually update transpose_matrix_.
1107 if (transpose_matrix_is_consistent_) {
1108 transpose_matrix_.DeleteRows(
1109 ColToRowIndex(new_index),
1110 reinterpret_cast<const RowPermutation&>(permutation));
1111 }
1112}
1113
1115 DCHECK_NE(first_slack_variable_, kInvalidCol);
1116 DenseBooleanRow slack_variables(matrix_.num_cols(), false);
1117 // Restore the bounds on the constraints corresponding to the slack variables.
1118 for (ColIndex slack_variable = first_slack_variable_;
1119 slack_variable < matrix_.num_cols(); ++slack_variable) {
1120 const SparseColumn& column = matrix_.column(slack_variable);
1121 // Slack variables appear only in the constraints for which they were
1122 // created. We can find this constraint by looking at the (only) entry in
1123 // the columnm of the slack variable.
1124 DCHECK_EQ(column.num_entries(), 1);
1125 const RowIndex row = column.EntryRow(EntryIndex(0));
1126 DCHECK_EQ(constraint_lower_bounds_[row], 0.0);
1127 DCHECK_EQ(constraint_upper_bounds_[row], 0.0);
1128 SetConstraintBounds(row, -variable_upper_bounds_[slack_variable],
1129 -variable_lower_bounds_[slack_variable]);
1130 slack_variables[slack_variable] = true;
1131 }
1132
1133 DeleteColumns(slack_variables);
1134 first_slack_variable_ = kInvalidCol;
1135}
1136
1137namespace {
1138
1139// Note that we ignore zeros and infinities because they do not matter from a
1140// scaling perspective where this function is used.
1141template <typename FractionalRange>
1142void UpdateMinAndMaxMagnitude(const FractionalRange& range,
1143 Fractional* min_magnitude,
1144 Fractional* max_magnitude) {
1145 for (const Fractional value : range) {
1146 const Fractional magnitude = std::abs(value);
1147 if (magnitude == 0 || magnitude == kInfinity) continue;
1148 *min_magnitude = std::min(*min_magnitude, magnitude);
1149 *max_magnitude = std::max(*max_magnitude, magnitude);
1150 }
1151}
1152
1153Fractional GetMedianScalingFactor(const DenseRow& range) {
1154 std::vector<Fractional> median;
1155 for (const Fractional value : range) {
1156 if (value == 0.0) continue;
1157 median.push_back(std::abs(value));
1158 }
1159 if (median.empty()) return 1.0;
1160 std::sort(median.begin(), median.end());
1161 return median[median.size() / 2];
1162}
1163
1164Fractional GetMeanScalingFactor(const DenseRow& range) {
1165 Fractional mean = 0.0;
1166 int num_non_zeros = 0;
1167 for (const Fractional value : range) {
1168 if (value == 0.0) continue;
1169 ++num_non_zeros;
1170 mean += std::abs(value);
1171 }
1172 if (num_non_zeros == 0.0) return 1.0;
1173 return mean / static_cast<Fractional>(num_non_zeros);
1174}
1175
1176Fractional ComputeDivisorSoThatRangeContainsOne(Fractional min_magnitude,
1177 Fractional max_magnitude) {
1178 if (min_magnitude > 1.0 && min_magnitude < kInfinity) {
1179 return min_magnitude;
1180 } else if (max_magnitude > 0.0 && max_magnitude < 1.0) {
1181 return max_magnitude;
1182 }
1183 return 1.0;
1184}
1185
1186} // namespace
1187
1190 Fractional min_magnitude = kInfinity;
1191 Fractional max_magnitude = 0.0;
1192 UpdateMinAndMaxMagnitude(objective_coefficients(), &min_magnitude,
1193 &max_magnitude);
1194 Fractional cost_scaling_factor = 1.0;
1195 switch (method) {
1197 break;
1199 cost_scaling_factor =
1200 ComputeDivisorSoThatRangeContainsOne(min_magnitude, max_magnitude);
1201 break;
1203 cost_scaling_factor = GetMeanScalingFactor(objective_coefficients());
1204 break;
1206 cost_scaling_factor = GetMedianScalingFactor(objective_coefficients());
1207 break;
1208 }
1209 if (cost_scaling_factor != 1.0) {
1210 for (ColIndex col(0); col < num_variables(); ++col) {
1211 if (objective_coefficients()[col] == 0.0) continue;
1213 col, objective_coefficients()[col] / cost_scaling_factor);
1214 }
1215 SetObjectiveScalingFactor(objective_scaling_factor() * cost_scaling_factor);
1216 SetObjectiveOffset(objective_offset() / cost_scaling_factor);
1217 }
1218 VLOG(1) << "Objective magnitude range is [" << min_magnitude << ", "
1219 << max_magnitude << "] (dividing by " << cost_scaling_factor << ").";
1220 return cost_scaling_factor;
1221}
1222
1224 Fractional min_magnitude = kInfinity;
1225 Fractional max_magnitude = 0.0;
1226 UpdateMinAndMaxMagnitude(variable_lower_bounds(), &min_magnitude,
1227 &max_magnitude);
1228 UpdateMinAndMaxMagnitude(variable_upper_bounds(), &min_magnitude,
1229 &max_magnitude);
1230 UpdateMinAndMaxMagnitude(constraint_lower_bounds(), &min_magnitude,
1231 &max_magnitude);
1232 UpdateMinAndMaxMagnitude(constraint_upper_bounds(), &min_magnitude,
1233 &max_magnitude);
1234 const Fractional bound_scaling_factor =
1235 ComputeDivisorSoThatRangeContainsOne(min_magnitude, max_magnitude);
1236 if (bound_scaling_factor != 1.0) {
1238 bound_scaling_factor);
1239 SetObjectiveOffset(objective_offset() / bound_scaling_factor);
1240 for (ColIndex col(0); col < num_variables(); ++col) {
1242 variable_lower_bounds()[col] / bound_scaling_factor,
1243 variable_upper_bounds()[col] / bound_scaling_factor);
1244 }
1245 for (RowIndex row(0); row < num_constraints(); ++row) {
1247 row, constraint_lower_bounds()[row] / bound_scaling_factor,
1248 constraint_upper_bounds()[row] / bound_scaling_factor);
1249 }
1250 }
1251
1252 VLOG(1) << "Bounds magnitude range is [" << min_magnitude << ", "
1253 << max_magnitude << "] (dividing bounds by " << bound_scaling_factor
1254 << ").";
1255 return bound_scaling_factor;
1256}
1257
1259 if (rows_to_delete.empty()) return;
1260
1261 // Deal with row-indexed data and construct the row mapping that will need to
1262 // be applied to every column entry.
1263 const RowIndex num_rows = num_constraints();
1264 RowPermutation permutation(num_rows);
1265 RowIndex new_index(0);
1266 for (RowIndex row(0); row < num_rows; ++row) {
1267 if (row >= rows_to_delete.size() || !rows_to_delete[row]) {
1268 constraint_lower_bounds_[new_index] = constraint_lower_bounds_[row];
1269 constraint_upper_bounds_[new_index] = constraint_upper_bounds_[row];
1270 constraint_names_[new_index].swap(constraint_names_[row]);
1271 permutation[row] = new_index;
1272 ++new_index;
1273 } else {
1274 permutation[row] = kInvalidRow;
1275 }
1276 }
1277 constraint_lower_bounds_.resize(new_index, 0.0);
1278 constraint_upper_bounds_.resize(new_index, 0.0);
1279 constraint_names_.resize(new_index, "");
1280
1281 // Remove the rows from the matrix.
1282 matrix_.DeleteRows(new_index, permutation);
1283
1284 // Remove the id of the deleted rows and adjust the index of the other.
1285 absl::flat_hash_map<std::string, RowIndex>::iterator it =
1286 constraint_table_.begin();
1287 while (it != constraint_table_.end()) {
1288 const RowIndex row = it->second;
1289 if (permutation[row] != kInvalidRow) {
1290 it->second = permutation[row];
1291 ++it;
1292 } else {
1293 // This safely deletes the entry and moves the iterator one step ahead.
1294 constraint_table_.erase(it++);
1295 }
1296 }
1297
1298 // Eventually update transpose_matrix_.
1299 if (transpose_matrix_is_consistent_) {
1300 transpose_matrix_.DeleteColumns(
1301 reinterpret_cast<const DenseBooleanRow&>(rows_to_delete));
1302 }
1303}
1304
1306 if (!IsFinite(objective_offset_)) return false;
1307 if (!IsFinite(objective_scaling_factor_)) return false;
1308 if (objective_scaling_factor_ == 0.0) return false;
1309 const ColIndex num_cols = num_variables();
1310 for (ColIndex col(0); col < num_cols; ++col) {
1313 return false;
1314 }
1316 return false;
1317 }
1318 for (const SparseColumn::Entry e : GetSparseColumn(col)) {
1319 if (!IsFinite(e.coefficient())) return false;
1320 }
1321 }
1322 if (constraint_upper_bounds_.size() != constraint_lower_bounds_.size()) {
1323 return false;
1324 }
1325 for (RowIndex row(0); row < constraint_lower_bounds_.size(); ++row) {
1328 return false;
1329 }
1330 }
1331 return true;
1332}
1333
1334std::string LinearProgram::ProblemStatFormatter(
1335 const absl::string_view format) const {
1336 int num_objective_non_zeros = 0;
1337 int num_non_negative_variables = 0;
1338 int num_boxed_variables = 0;
1339 int num_free_variables = 0;
1340 int num_fixed_variables = 0;
1341 int num_other_variables = 0;
1342 const ColIndex num_cols = num_variables();
1343 for (ColIndex col(0); col < num_cols; ++col) {
1344 if (objective_coefficients()[col] != 0.0) {
1345 ++num_objective_non_zeros;
1346 }
1347
1350 const bool lower_bounded = (lower_bound != -kInfinity);
1351 const bool upper_bounded = (upper_bound != kInfinity);
1352
1353 if (!lower_bounded && !upper_bounded) {
1354 ++num_free_variables;
1355 } else if (lower_bound == 0.0 && !upper_bounded) {
1356 ++num_non_negative_variables;
1357 } else if (!upper_bounded || !lower_bounded) {
1358 ++num_other_variables;
1359 } else if (lower_bound == upper_bound) {
1360 ++num_fixed_variables;
1361 } else {
1362 ++num_boxed_variables;
1363 }
1364 }
1365
1366 int num_range_constraints = 0;
1367 int num_less_than_constraints = 0;
1368 int num_greater_than_constraints = 0;
1369 int num_equal_constraints = 0;
1370 int num_rhs_non_zeros = 0;
1371 const RowIndex num_rows = num_constraints();
1372 for (RowIndex row(0); row < num_rows; ++row) {
1375 if (AreBoundsFreeOrBoxed(lower_bound, upper_bound)) {
1376 // TODO(user): we currently count a free row as a range constraint.
1377 // Add a new category?
1378 ++num_range_constraints;
1379 continue;
1380 }
1381 if (lower_bound == upper_bound) {
1382 ++num_equal_constraints;
1383 if (lower_bound != 0) {
1384 ++num_rhs_non_zeros;
1385 }
1386 continue;
1387 }
1388 if (lower_bound == -kInfinity) {
1389 ++num_less_than_constraints;
1390 if (upper_bound != 0) {
1391 ++num_rhs_non_zeros;
1392 }
1393 continue;
1394 }
1395 if (upper_bound == kInfinity) {
1396 ++num_greater_than_constraints;
1397 if (lower_bound != 0) {
1398 ++num_rhs_non_zeros;
1399 }
1400 continue;
1401 }
1402 LOG(DFATAL) << "There is a bug since all possible cases for the row bounds "
1403 "should have been accounted for. row="
1404 << row;
1405 }
1406
1407 const int num_integer_variables = IntegerVariablesList().size();
1408 const int num_binary_variables = BinaryVariablesList().size();
1409 const int num_non_binary_variables = NonBinaryVariablesList().size();
1410 const int num_continuous_variables =
1411 ColToIntIndex(num_variables()) - num_integer_variables;
1412 auto format_runtime =
1413 absl::ParsedFormat<'d', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd',
1414 'd', 'd', 'd', 'd', 'd', 'd', 'd'>::New(format);
1415 CHECK(format_runtime);
1416 return absl::StrFormat(
1417 *format_runtime, RowToIntIndex(num_constraints()),
1418 ColToIntIndex(num_variables()), matrix_.num_entries().value(),
1419 num_objective_non_zeros, num_rhs_non_zeros, num_less_than_constraints,
1420 num_greater_than_constraints, num_equal_constraints,
1421 num_range_constraints, num_non_negative_variables, num_boxed_variables,
1422 num_free_variables, num_fixed_variables, num_other_variables,
1423 num_integer_variables, num_binary_variables, num_non_binary_variables,
1424 num_continuous_variables);
1425}
1426
1427std::string LinearProgram::NonZeroStatFormatter(
1428 const absl::string_view format) const {
1429 StrictITIVector<RowIndex, EntryIndex> num_entries_in_row(num_constraints(),
1430 EntryIndex(0));
1431 StrictITIVector<ColIndex, EntryIndex> num_entries_in_column(num_variables(),
1432 EntryIndex(0));
1433 EntryIndex num_entries(0);
1434 const ColIndex num_cols = num_variables();
1435 for (ColIndex col(0); col < num_cols; ++col) {
1436 const SparseColumn& sparse_column = GetSparseColumn(col);
1437 num_entries += sparse_column.num_entries();
1438 num_entries_in_column[col] = sparse_column.num_entries();
1439 for (const SparseColumn::Entry e : sparse_column) {
1440 ++num_entries_in_row[e.row()];
1441 }
1442 }
1443
1444 // To avoid division by 0 if there are no columns or no rows, we set
1445 // height and width to be at least one.
1446 const int64_t height = std::max(RowToIntIndex(num_constraints()), 1);
1447 const int64_t width = std::max(ColToIntIndex(num_variables()), 1);
1448 const double fill_rate = 100.0 * static_cast<double>(num_entries.value()) /
1449 static_cast<double>(height * width);
1450
1451 auto format_runtime =
1452 absl::ParsedFormat<'f', 'd', 'f', 'f', 'd', 'f', 'f'>::New(format);
1453 return absl::StrFormat(
1454 *format_runtime, fill_rate, GetMaxElement(num_entries_in_row).value(),
1455 Average(num_entries_in_row), StandardDeviation(num_entries_in_row),
1456 GetMaxElement(num_entries_in_column).value(),
1457 Average(num_entries_in_column), StandardDeviation(num_entries_in_column));
1458}
1459
1460void LinearProgram::ResizeRowsIfNeeded(RowIndex row) {
1461 DCHECK_GE(row, 0);
1462 if (row >= num_constraints()) {
1463 transpose_matrix_is_consistent_ = false;
1464 matrix_.SetNumRows(row + 1);
1465 constraint_lower_bounds_.resize(row + 1, Fractional(0.0));
1466 constraint_upper_bounds_.resize(row + 1, Fractional(0.0));
1467 constraint_names_.resize(row + 1, "");
1468 }
1469}
1470
1472 for (RowIndex constraint(0); constraint < num_constraints(); ++constraint) {
1473 if (constraint_lower_bounds_[constraint] != 0.0 ||
1474 constraint_upper_bounds_[constraint] != 0.0) {
1475 return false;
1476 }
1477 }
1478 const ColIndex num_slack_variables =
1480 return num_constraints().value() == num_slack_variables.value() &&
1482}
1483
1485 Fractional tolerance) const {
1486 for (const ColIndex col : IntegerVariablesList()) {
1487 if ((IsFinite(variable_lower_bounds_[col]) &&
1488 !IsIntegerWithinTolerance(variable_lower_bounds_[col], tolerance)) ||
1489 (IsFinite(variable_upper_bounds_[col]) &&
1490 !IsIntegerWithinTolerance(variable_upper_bounds_[col], tolerance))) {
1491 VLOG(1) << "Bounds of variable " << col.value() << " are non-integer ("
1492 << variable_lower_bounds_[col] << ", "
1493 << variable_upper_bounds_[col] << ").";
1494 return false;
1495 }
1496 }
1497 return true;
1498}
1499
1501 Fractional tolerance) const {
1502 // Using transpose for this is faster (complexity = O(number of non zeros in
1503 // matrix)) than directly iterating through entries (complexity = O(number of
1504 // constraints * number of variables)).
1505 const SparseMatrix& transpose = GetTransposeSparseMatrix();
1506 for (RowIndex row = RowIndex(0); row < num_constraints(); ++row) {
1507 bool integer_constraint = true;
1508 for (const SparseColumn::Entry var : transpose.column(RowToColIndex(row))) {
1509 if (!IsVariableInteger(RowToColIndex(var.row()))) {
1510 integer_constraint = false;
1511 break;
1512 }
1513
1514 // To match what the IntegerBoundsPreprocessor is doing, we require all
1515 // coefficient to be EXACTLY integer here.
1516 if (std::round(var.coefficient()) != var.coefficient()) {
1517 integer_constraint = false;
1518 break;
1519 }
1520 }
1521 if (integer_constraint) {
1522 if ((IsFinite(constraint_lower_bounds_[row]) &&
1523 !IsIntegerWithinTolerance(constraint_lower_bounds_[row],
1524 tolerance)) ||
1525 (IsFinite(constraint_upper_bounds_[row]) &&
1526 !IsIntegerWithinTolerance(constraint_upper_bounds_[row],
1527 tolerance))) {
1528 VLOG(1) << "Bounds of constraint " << row.value()
1529 << " are non-integer (" << constraint_lower_bounds_[row] << ", "
1530 << constraint_upper_bounds_[row] << ").";
1531 return false;
1532 }
1533 }
1534 }
1535 return true;
1536}
1537
1538// --------------------------------------------------------
1539// ProblemSolution
1540// --------------------------------------------------------
1541std::string ProblemSolution::DebugString() const {
1542 std::string s = "Problem status: " + GetProblemStatusString(status);
1543 for (ColIndex col(0); col < primal_values.size(); ++col) {
1544 absl::StrAppendFormat(&s, "\n Var #%d: %s %g", col.value(),
1547 }
1548 s += "\n------------------------------";
1549 for (RowIndex row(0); row < dual_values.size(); ++row) {
1550 absl::StrAppendFormat(&s, "\n Constraint #%d: %s %g", row.value(),
1552 dual_values[row]);
1553 }
1554 return s;
1555}
1556
1557} // namespace glop
1558} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:892
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:891
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:894
#define CHECK_NE(val1, val2)
Definition: base/logging.h:703
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:893
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:889
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
#define VLOG(verboselevel)
Definition: base/logging.h:983
iterator insert(const_iterator pos, const value_type &x)
size_type size() const
bool empty() const
void push_back(const value_type &x)
void swap(StrongVector &x)
static constexpr CostScalingAlgorithm MEAN_COST_SCALING
static constexpr CostScalingAlgorithm NO_COST_SCALING
static constexpr CostScalingAlgorithm MEDIAN_COST_SCALING
static constexpr CostScalingAlgorithm CONTAIN_ONE_COST_SCALING
SparseMatrix * GetMutableTransposeSparseMatrix()
Definition: lp_data.cc:386
std::string GetObjectiveStatsString() const
Definition: lp_data.cc:452
void SetObjectiveScalingFactor(Fractional objective_scaling_factor)
Definition: lp_data.cc:336
void PopulateFromPermutedLinearProgram(const LinearProgram &lp, const RowPermutation &row_permutation, const ColumnPermutation &col_permutation)
Definition: lp_data.cc:883
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:249
std::string GetVariableName(ColIndex col) const
Definition: lp_data.cc:360
void SetConstraintName(RowIndex row, absl::string_view name)
Definition: lp_data.cc:245
const SparseMatrix & GetTransposeSparseMatrix() const
Definition: lp_data.cc:376
bool SolutionIsWithinVariableBounds(const DenseRow &solution, Fractional absolute_tolerance) const
Definition: lp_data.cc:481
const std::string & name() const
Definition: lp_data.h:75
bool BoundsOfIntegerConstraintsAreInteger(Fractional tolerance) const
Definition: lp_data.cc:1500
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:331
void PopulateFromLinearProgram(const LinearProgram &linear_program)
Definition: lp_data.cc:862
std::string GetPrettyProblemStats() const
Definition: lp_data.cc:664
bool SolutionIsMIPFeasible(const DenseRow &solution, Fractional absolute_tolerance) const
Definition: lp_data.cc:529
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Definition: lp_data.cc:317
bool BoundsOfIntegerVariablesAreInteger(Fractional tolerance) const
Definition: lp_data.cc:1484
void SetVariableName(ColIndex col, absl::string_view name)
Definition: lp_data.cc:232
std::string DumpSolution(const DenseRow &variable_values) const
Definition: lp_data.cc:647
ColIndex GetSlackVariable(RowIndex row) const
Definition: lp_data.cc:755
ColIndex FindOrCreateVariable(const std::string &variable_id)
Definition: lp_data.cc:205
std::string GetBoundsStatsString() const
Definition: lp_data.cc:465
Fractional ScaleObjective(GlopParameters::CostScalingAlgorithm method)
Definition: lp_data.cc:1188
const std::vector< ColIndex > & BinaryVariablesList() const
Definition: lp_data.cc:285
const DenseColumn & constraint_lower_bounds() const
Definition: lp_data.h:215
Fractional RemoveObjectiveScalingAndOffset(Fractional value) const
Definition: lp_data.cc:555
const std::vector< ColIndex > & IntegerVariablesList() const
Definition: lp_data.cc:280
Fractional GetObjectiveCoefficientForMinimizationVersion(ColIndex col) const
Definition: lp_data.cc:419
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:309
ColIndex CreateNewSlackVariable(bool is_integer_slack_variable, Fractional lower_bound, Fractional upper_bound, const std::string &name)
Definition: lp_data.cc:176
VariableType GetVariableType(ColIndex col) const
Definition: lp_data.cc:372
RowIndex FindOrCreateConstraint(const std::string &constraint_id)
Definition: lp_data.cc:218
void Swap(LinearProgram *linear_program)
Definition: lp_data.cc:1031
void AddConstraints(const SparseMatrix &coefficients, const DenseColumn &left_hand_sides, const DenseColumn &right_hand_sides, const StrictITIVector< RowIndex, std::string > &names)
Definition: lp_data.cc:972
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
std::string GetPrettyNonZeroStats() const
Definition: lp_data.cc:690
void SetVariableType(ColIndex col, VariableType type)
Definition: lp_data.cc:236
const std::vector< ColIndex > & NonBinaryVariablesList() const
Definition: lp_data.cc:290
bool SolutionIsInteger(const DenseRow &solution, Fractional absolute_tolerance) const
Definition: lp_data.cc:517
SparseColumn * GetMutableSparseColumn(ColIndex col)
Definition: lp_data.cc:413
std::string GetConstraintName(RowIndex row) const
Definition: lp_data.cc:366
const DenseColumn & constraint_upper_bounds() const
Definition: lp_data.h:218
const DenseRow & objective_coefficients() const
Definition: lp_data.h:223
void AddSlackVariablesWhereNecessary(bool detect_integer_constraints)
Definition: lp_data.cc:697
void ComputeSlackVariableValues(DenseRow *solution) const
Definition: lp_data.cc:535
bool SolutionIsLPFeasible(const DenseRow &solution, Fractional absolute_tolerance) const
Definition: lp_data.cc:497
bool IsVariableInteger(ColIndex col) const
Definition: lp_data.cc:295
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:326
bool IsVariableBinary(ColIndex col) const
Definition: lp_data.cc:300
const StrictITIVector< ColIndex, VariableType > variable_types() const
Definition: lp_data.h:237
Fractional ApplyObjectiveScalingAndOffset(Fractional value) const
Definition: lp_data.cc:550
void DeleteRows(const DenseBooleanColumn &rows_to_delete)
Definition: lp_data.cc:1258
void DeleteColumns(const DenseBooleanRow &columns_to_delete)
Definition: lp_data.cc:1065
bool UpdateVariableBoundsToIntersection(const DenseRow &variable_lower_bounds, const DenseRow &variable_upper_bounds)
Definition: lp_data.cc:1006
void PopulateFromDual(const LinearProgram &dual, RowToColMapping *duplicated_rows)
Definition: lp_data.cc:764
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
void PopulateFromLinearProgramVariables(const LinearProgram &linear_program)
Definition: lp_data.cc:935
std::string GetDimensionString() const
Definition: lp_data.cc:425
Fractional objective_scaling_factor() const
Definition: lp_data.h:261
void SetMaximizationProblem(bool maximize)
Definition: lp_data.cc:343
void AddConstraintsWithSlackVariables(const SparseMatrix &coefficients, const DenseColumn &left_hand_sides, const DenseColumn &right_hand_sides, const StrictITIVector< RowIndex, std::string > &names, bool detect_integer_constraints_for_slack)
Definition: lp_data.cc:997
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition: lp_data.cc:409
void PopulateFromInverse(const Permutation &inverse)
RowIndex EntryRow(EntryIndex i) const
Definition: sparse_column.h:51
void PopulateFromPermutedMatrix(const Matrix &a, const RowPermutation &row_perm, const ColumnPermutation &inverse_col_perm)
Definition: sparse.cc:212
void PopulateFromTranspose(const Matrix &input)
Definition: sparse.cc:181
void SetNumRows(RowIndex num_rows)
Definition: sparse.cc:143
SparseColumn * mutable_column(ColIndex col)
Definition: sparse.h:182
Fractional LookUpValue(RowIndex row, ColIndex col) const
Definition: sparse.cc:323
void Swap(SparseMatrix *matrix)
Definition: sparse.cc:158
void ComputeMinAndMaxMagnitudes(Fractional *min_magnitude, Fractional *max_magnitude) const
Definition: sparse.cc:369
const SparseColumn & column(ColIndex col) const
Definition: sparse.h:181
void DeleteRows(RowIndex num_rows, const RowPermutation &permutation)
Definition: sparse.cc:289
bool AppendRowsFromSparseMatrix(const SparseMatrix &matrix)
Definition: sparse.cc:302
void DeleteColumns(const DenseBooleanRow &columns_to_delete)
Definition: sparse.cc:276
void PopulateFromSparseMatrix(const SparseMatrix &matrix)
Definition: sparse.cc:206
void PopulateFromZero(RowIndex num_rows, ColIndex num_cols)
Definition: sparse.cc:164
void SetCoefficient(Index index, Fractional value)
void PopulateFromSparseVector(const SparseVector &sparse_vector)
void assign(IntType size, const T &v)
Definition: lp_types.h:278
const std::string name
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
double lower_bound
absl::Span< const double > coefficients
ColIndex col
Definition: markowitz.cc:183
RowIndex row
Definition: markowitz.cc:182
std::string StringifyMonomial(const Fractional a, const std::string &x, bool fraction)
bool IsRightMostSquareMatrixIdentity(const SparseMatrix &matrix)
bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.h:693
const RowIndex kInvalidRow(-1)
std::string Stringify(const Fractional x, bool fraction)
Fractional ScalarProduct(const DenseRowOrColumn1 &u, const DenseRowOrColumn2 &v)
StrictITIVector< ColIndex, Fractional > DenseRow
Definition: lp_types.h:303
std::string GetProblemStatusString(ProblemStatus problem_status)
Definition: lp_types.cc:19
Index ColToIntIndex(ColIndex col)
Definition: lp_types.h:55
std::string GetConstraintStatusString(ConstraintStatus status)
Definition: lp_types.cc:90
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
bool IsFinite(Fractional value)
Definition: lp_types.h:91
RowIndex ColToRowIndex(ColIndex col)
Definition: lp_types.h:52
const double kEpsilon
Definition: lp_types.h:87
void ApplyPermutation(const Permutation< IndexType > &perm, const ITIVectorType &b, ITIVectorType *result)
Fractional PartialScalarProduct(const DenseRowOrColumn &u, const SparseColumn &v, int max_index)
std::string GetVariableStatusString(VariableStatus status)
Definition: lp_types.cc:71
Index RowToIntIndex(RowIndex row)
Definition: lp_types.h:58
const double kInfinity
Definition: lp_types.h:84
const ColIndex kInvalidCol(-1)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
Collection of objects used to extend the Constraint Solver library.
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition: fp_utils.h:165
const bool maximize_
Definition: search.cc:2559
ConstraintStatusColumn constraint_statuses
Definition: lp_data.h:686
const double coeff