24#include "absl/log/check.h"
25#include "absl/strings/str_format.h"
37#include "ortools/sat/boolean_problem.pb.h"
46using ::operations_research::glop::ColIndex;
47using ::operations_research::glop::DenseRow;
48using ::operations_research::glop::Fractional;
49using ::operations_research::glop::kInfinity;
50using ::operations_research::glop::LinearProgram;
51using ::operations_research::glop::LPDecomposer;
52using ::operations_research::glop::RowIndex;
53using ::operations_research::glop::SparseColumn;
54using ::operations_research::glop::SparseMatrix;
55using ::operations_research::sat::LinearBooleanConstraint;
56using ::operations_research::sat::LinearBooleanProblem;
57using ::operations_research::sat::LinearObjective;
69bool ProblemIsBooleanAndHasOnlyIntegralConstraints(
70 const LinearProgram& linear_problem) {
71 const glop::SparseMatrix& matrix = linear_problem.GetSparseMatrix();
73 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
82 for (
const SparseColumn::Entry e : matrix.column(
col)) {
95void BuildBooleanProblemWithIntegralConstraints(
96 const LinearProgram& linear_problem,
const DenseRow& initial_solution,
97 LinearBooleanProblem* boolean_problem,
98 std::vector<bool>* boolean_initial_solution) {
99 CHECK(boolean_problem !=
nullptr);
100 boolean_problem->Clear();
102 const glop::SparseMatrix& matrix = linear_problem.GetSparseMatrix();
104 for (ColIndex
col(0);
col < matrix.num_cols(); ++
col) {
105 boolean_problem->add_var_names(linear_problem.GetVariableName(
col));
107 boolean_problem->set_num_variables(matrix.num_cols().value());
108 boolean_problem->set_name(linear_problem.name());
111 for (RowIndex
row(0);
row < matrix.num_rows(); ++
row) {
112 LinearBooleanConstraint*
const constraint =
113 boolean_problem->add_constraints();
114 constraint->set_name(linear_problem.GetConstraintName(
row));
115 if (linear_problem.constraint_lower_bounds()[
row] != -kInfinity) {
116 constraint->set_lower_bound(
117 linear_problem.constraint_lower_bounds()[
row]);
119 if (linear_problem.constraint_upper_bounds()[
row] != kInfinity) {
120 constraint->set_upper_bound(
121 linear_problem.constraint_upper_bounds()[
row]);
126 for (ColIndex
col(0);
col < matrix.num_cols(); ++
col) {
127 for (
const SparseColumn::Entry e : matrix.column(
col)) {
128 LinearBooleanConstraint*
const constraint =
129 boolean_problem->mutable_constraints(e.row().value());
130 constraint->add_literals(
col.value() + 1);
131 constraint->add_coefficients(e.coefficient());
137 for (ColIndex
col(0);
col < matrix.num_cols(); ++
col) {
139 const int lb = std::round(linear_problem.variable_lower_bounds()[
col]);
140 const int ub = std::round(linear_problem.variable_upper_bounds()[
col]);
142 LinearBooleanConstraint*
ct = boolean_problem->add_constraints();
143 ct->set_lower_bound(ub);
144 ct->set_upper_bound(ub);
145 ct->add_literals(
col.value() + 1);
146 ct->add_coefficients(1.0);
152 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
153 const Fractional coeff = linear_problem.objective_coefficients()[
col];
156 double scaling_factor = 0.0;
157 double relative_error = 0.0;
159 std::numeric_limits<int64_t>::max(),
160 &scaling_factor, &relative_error);
162 LinearObjective*
const objective = boolean_problem->mutable_objective();
163 objective->set_offset(linear_problem.objective_offset() * scaling_factor /
168 objective->set_scaling_factor(1.0 / scaling_factor * gcd);
169 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
170 const Fractional coeff = linear_problem.objective_coefficients()[
col];
171 const int64_t
value =
172 static_cast<int64_t
>(round(coeff * scaling_factor)) / gcd;
174 objective->add_literals(
col.value() + 1);
175 objective->add_coefficients(
value);
180 if (linear_problem.IsMaximizationProblem()) {
185 if (!initial_solution.empty()) {
186 CHECK(boolean_initial_solution !=
nullptr);
187 CHECK_EQ(boolean_problem->num_variables(), initial_solution.size());
188 boolean_initial_solution->assign(boolean_problem->num_variables(),
false);
189 for (
int i = 0;
i < initial_solution.size(); ++
i) {
190 (*boolean_initial_solution)[
i] = (initial_solution[ColIndex(i)] != 0);
203class IntegralVariable {
212 void BuildFromRange(
int start_var_index, Fractional
lower_bound,
216 void set_offset(int64_t offset) { offset_ = offset; }
217 void set_weight(VariableIndex
var, int64_t
weight);
219 int GetNumberOfBooleanVariables()
const {
return bits_.size(); }
221 const std::vector<VariableIndex>& bits()
const {
return bits_; }
222 const std::vector<int64_t>& weights()
const {
return weights_; }
223 int64_t offset()
const {
return offset_; }
227 int64_t GetSolutionValue(
const BopSolution&
solution)
const;
233 std::vector<bool> GetBooleanSolutionValues(int64_t integral_value)
const;
235 std::string DebugString()
const;
241 std::vector<VariableIndex> bits_;
242 std::vector<int64_t> weights_;
248 bool can_be_reversed_;
251IntegralVariable::IntegralVariable()
252 : bits_(), weights_(), offset_(0), can_be_reversed_(
true) {}
254void IntegralVariable::BuildFromRange(
int start_var_index,
264 const int64_t integral_lower_bound =
static_cast<int64_t
>(ceil(
lower_bound));
265 const int64_t integral_upper_bound =
static_cast<int64_t
>(
floor(
upper_bound));
266 offset_ = integral_lower_bound;
267 const int64_t
delta = integral_upper_bound - integral_lower_bound;
269 for (
int i = 0;
i < num_used_bits; ++
i) {
270 bits_.push_back(VariableIndex(start_var_index + i));
271 weights_.push_back(1ULL << i);
275void IntegralVariable::Clear() {
279 can_be_reversed_ =
true;
282void IntegralVariable::set_weight(VariableIndex
var, int64_t
weight) {
283 bits_.push_back(
var);
284 weights_.push_back(
weight);
285 can_be_reversed_ =
false;
288int64_t IntegralVariable::GetSolutionValue(
const BopSolution&
solution)
const {
289 int64_t
value = offset_;
290 for (
int i = 0;
i < bits_.size(); ++
i) {
296std::vector<bool> IntegralVariable::GetBooleanSolutionValues(
297 int64_t integral_value)
const {
298 if (can_be_reversed_) {
299 DCHECK(std::is_sorted(weights_.begin(), weights_.end()));
300 std::vector<bool> boolean_values(weights_.size(),
false);
301 int64_t remaining_value = integral_value - offset_;
302 for (
int i = weights_.size() - 1; i >= 0; --i) {
303 if (remaining_value >= weights_[i]) {
304 boolean_values[
i] =
true;
305 remaining_value -= weights_[
i];
308 CHECK_EQ(0, remaining_value)
309 <<
"Couldn't map integral value to boolean variables.";
310 return boolean_values;
312 return std::vector<bool>();
315std::string IntegralVariable::DebugString()
const {
317 CHECK_EQ(bits_.size(), weights_.size());
318 for (
int i = 0;
i < bits_.size(); ++
i) {
319 str += absl::StrFormat(
"%d [%d] ", weights_[i], bits_[i].
value());
321 str += absl::StrFormat(
" Offset: %d", offset_);
342class IntegralProblemConverter {
344 IntegralProblemConverter();
350 bool ConvertToBooleanProblem(
const LinearProgram& linear_problem,
351 const DenseRow& initial_solution,
352 LinearBooleanProblem* boolean_problem,
353 std::vector<bool>* boolean_initial_solution);
357 int64_t GetSolutionValue(ColIndex global_col,
364 bool CheckProblem(
const LinearProgram& linear_problem)
const;
367 void InitVariableTypes(
const LinearProgram& linear_problem,
368 LinearBooleanProblem* boolean_problem);
371 void ConvertAllVariables(
const LinearProgram& linear_problem,
372 LinearBooleanProblem* boolean_problem);
375 void AddVariableConstraints(
const LinearProgram& linear_problem,
376 LinearBooleanProblem* boolean_problem);
379 void ConvertAllConstraints(
const LinearProgram& linear_problem,
380 LinearBooleanProblem* boolean_problem);
383 void ConvertObjective(
const LinearProgram& linear_problem,
384 LinearBooleanProblem* boolean_problem);
390 bool ConvertUsingExistingBooleans(
const LinearProgram& linear_problem,
392 IntegralVariable* integral_var);
403 bool CreateVariableUsingConstraint(
const LinearProgram& linear_problem,
405 IntegralVariable* integral_var);
419 double ScaleAndSparsifyWeights(
420 double scaling_factor, int64_t gcd,
425 bool HasNonZeroWeights(
429 bool problem_is_boolean_and_has_only_integral_constraints_;
436 std::vector<IntegralVariable> integral_variables_;
437 std::vector<ColIndex> integral_indices_;
438 int num_boolean_variables_;
440 enum VariableType { BOOLEAN, INTEGRAL, INTEGRAL_EXPRESSED_AS_BOOLEAN };
444IntegralProblemConverter::IntegralProblemConverter()
445 : global_to_boolean_(),
446 integral_variables_(),
448 num_boolean_variables_(0),
451bool IntegralProblemConverter::ConvertToBooleanProblem(
452 const LinearProgram& linear_problem,
const DenseRow& initial_solution,
453 LinearBooleanProblem* boolean_problem,
454 std::vector<bool>* boolean_initial_solution) {
455 bool use_initial_solution = (initial_solution.size() > 0);
456 if (use_initial_solution) {
457 CHECK_EQ(initial_solution.size(), linear_problem.num_variables())
458 <<
"The initial solution should have the same number of variables as "
459 "the LinearProgram.";
460 CHECK(boolean_initial_solution !=
nullptr);
462 if (!CheckProblem(linear_problem)) {
466 problem_is_boolean_and_has_only_integral_constraints_ =
467 ProblemIsBooleanAndHasOnlyIntegralConstraints(linear_problem);
468 if (problem_is_boolean_and_has_only_integral_constraints_) {
469 BuildBooleanProblemWithIntegralConstraints(linear_problem, initial_solution,
471 boolean_initial_solution);
475 InitVariableTypes(linear_problem, boolean_problem);
476 ConvertAllVariables(linear_problem, boolean_problem);
477 boolean_problem->set_num_variables(num_boolean_variables_);
478 boolean_problem->set_name(linear_problem.name());
480 AddVariableConstraints(linear_problem, boolean_problem);
481 ConvertAllConstraints(linear_problem, boolean_problem);
482 ConvertObjective(linear_problem, boolean_problem);
485 if (linear_problem.IsMaximizationProblem()) {
486 sat::ChangeOptimizationDirection(boolean_problem);
489 if (use_initial_solution) {
490 boolean_initial_solution->assign(boolean_problem->num_variables(),
false);
491 for (ColIndex global_col(0); global_col < global_to_boolean_.size();
493 const int col = global_to_boolean_[global_col];
495 (*boolean_initial_solution)[
col] = (initial_solution[global_col] != 0);
497 const IntegralVariable& integral_variable =
498 integral_variables_[-
col - 1];
499 const std::vector<VariableIndex>& boolean_cols =
500 integral_variable.bits();
501 const std::vector<bool>& boolean_values =
502 integral_variable.GetBooleanSolutionValues(
503 round(initial_solution[global_col]));
504 if (!boolean_values.empty()) {
505 CHECK_EQ(boolean_cols.size(), boolean_values.size());
506 for (
int i = 0;
i < boolean_values.size(); ++
i) {
507 const int boolean_col = boolean_cols[
i].value();
508 (*boolean_initial_solution)[boolean_col] = boolean_values[
i];
518int64_t IntegralProblemConverter::GetSolutionValue(
519 ColIndex global_col,
const BopSolution&
solution)
const {
520 if (problem_is_boolean_and_has_only_integral_constraints_) {
521 return solution.Value(VariableIndex(global_col.value()));
524 const int pos = global_to_boolean_[global_col];
525 return pos >= 0 ?
solution.Value(VariableIndex(pos))
526 : integral_variables_[-pos - 1].GetSolutionValue(
solution);
529bool IntegralProblemConverter::CheckProblem(
530 const LinearProgram& linear_problem)
const {
531 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
532 if (!linear_problem.IsVariableInteger(
col)) {
533 LOG(ERROR) <<
"Variable " << linear_problem.GetVariableName(
col)
534 <<
" is continuous. This is not supported by BOP.";
537 if (linear_problem.variable_lower_bounds()[
col] == -kInfinity) {
538 LOG(ERROR) <<
"Variable " << linear_problem.GetVariableName(
col)
539 <<
" has no lower bound. This is not supported by BOP.";
542 if (linear_problem.variable_upper_bounds()[
col] == kInfinity) {
543 LOG(ERROR) <<
"Variable " << linear_problem.GetVariableName(
col)
544 <<
" has no upper bound. This is not supported by BOP.";
551void IntegralProblemConverter::InitVariableTypes(
552 const LinearProgram& linear_problem,
553 LinearBooleanProblem* boolean_problem) {
554 global_to_boolean_.assign(linear_problem.num_variables().value(), 0);
555 variable_types_.assign(linear_problem.num_variables().value(), INTEGRAL);
556 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
562 variable_types_[
col] = BOOLEAN;
563 global_to_boolean_[
col] = num_boolean_variables_;
564 ++num_boolean_variables_;
565 boolean_problem->add_var_names(linear_problem.GetVariableName(
col));
568 variable_types_[
col] = INTEGRAL;
569 integral_indices_.push_back(
col);
574void IntegralProblemConverter::ConvertAllVariables(
575 const LinearProgram& linear_problem,
576 LinearBooleanProblem* boolean_problem) {
577 for (
const ColIndex
col : integral_indices_) {
578 CHECK_EQ(INTEGRAL, variable_types_[
col]);
579 IntegralVariable integral_var;
580 if (!ConvertUsingExistingBooleans(linear_problem,
col, &integral_var)) {
582 linear_problem.variable_lower_bounds()[
col];
584 linear_problem.variable_upper_bounds()[
col];
585 integral_var.BuildFromRange(num_boolean_variables_,
lower_bound,
587 num_boolean_variables_ += integral_var.GetNumberOfBooleanVariables();
588 const std::string var_name = linear_problem.GetVariableName(
col);
589 for (
int i = 0;
i < integral_var.bits().
size(); ++
i) {
590 boolean_problem->add_var_names(var_name + absl::StrFormat(
"_%d", i));
593 integral_variables_.push_back(integral_var);
594 global_to_boolean_[
col] = -integral_variables_.size();
595 variable_types_[
col] = INTEGRAL_EXPRESSED_AS_BOOLEAN;
599void IntegralProblemConverter::ConvertAllConstraints(
600 const LinearProgram& linear_problem,
601 LinearBooleanProblem* boolean_problem) {
604 glop::SparseMatrix transpose;
605 transpose.PopulateFromTranspose(linear_problem.GetSparseMatrix());
607 double max_relative_error = 0.0;
608 double max_bound_error = 0.0;
610 double relative_error = 0.0;
611 double scaling_factor = 0.0;
613 for (RowIndex
row(0);
row < linear_problem.num_constraints(); ++
row) {
616 num_boolean_variables_, 0.0);
619 offset += AddWeightedIntegralVariable(
RowToColIndex(e.row()),
620 e.coefficient(), &dense_weights);
622 if (!HasNonZeroWeights(dense_weights)) {
628 for (VariableIndex
var(0);
var < num_boolean_variables_; ++
var) {
629 if (dense_weights[
var] != 0.0) {
634 std::numeric_limits<int64_t>::max(),
635 &scaling_factor, &relative_error);
638 max_relative_error = std::max(relative_error, max_relative_error);
641 LinearBooleanConstraint* constraint = boolean_problem->add_constraints();
642 constraint->set_name(linear_problem.GetConstraintName(
row));
643 const double bound_error =
644 ScaleAndSparsifyWeights(scaling_factor, gcd, dense_weights, constraint);
645 max_bound_error = std::max(max_bound_error, bound_error);
648 linear_problem.constraint_lower_bounds()[
row];
651 const double offset_scaled_lower_bound =
652 round(offset_lower_bound * scaling_factor - bound_error);
653 if (offset_scaled_lower_bound >=
654 static_cast<double>(std::numeric_limits<int64_t>::max())) {
655 LOG(WARNING) <<
"A constraint is trivially unsatisfiable.";
658 if (offset_scaled_lower_bound >
659 -
static_cast<double>(std::numeric_limits<int64_t>::max())) {
661 constraint->set_lower_bound(
662 static_cast<int64_t
>(offset_scaled_lower_bound) / gcd);
666 linear_problem.constraint_upper_bounds()[
row];
669 const double offset_scaled_upper_bound =
670 round(offset_upper_bound * scaling_factor + bound_error);
671 if (offset_scaled_upper_bound <=
672 -
static_cast<double>(std::numeric_limits<int64_t>::max())) {
673 LOG(WARNING) <<
"A constraint is trivially unsatisfiable.";
676 if (offset_scaled_upper_bound <
677 static_cast<double>(std::numeric_limits<int64_t>::max())) {
679 constraint->set_upper_bound(
680 static_cast<int64_t
>(offset_scaled_upper_bound) / gcd);
686void IntegralProblemConverter::ConvertObjective(
687 const LinearProgram& linear_problem,
688 LinearBooleanProblem* boolean_problem) {
689 LinearObjective* objective = boolean_problem->mutable_objective();
692 num_boolean_variables_, 0.0);
694 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
695 offset += AddWeightedIntegralVariable(
696 col, linear_problem.objective_coefficients()[
col], &dense_weights);
701 for (VariableIndex
var(0);
var < num_boolean_variables_; ++
var) {
702 if (dense_weights[
var] != 0.0) {
706 double scaling_factor = 0.0;
707 double max_relative_error = 0.0;
708 double relative_error = 0.0;
710 std::numeric_limits<int64_t>::max(),
711 &scaling_factor, &relative_error);
713 max_relative_error = std::max(relative_error, max_relative_error);
714 VLOG(1) <<
"objective relative error: " << relative_error;
715 VLOG(1) <<
"objective scaling factor: " << scaling_factor / gcd;
717 ScaleAndSparsifyWeights(scaling_factor, gcd, dense_weights, objective);
721 objective->set_scaling_factor(1.0 / scaling_factor * gcd);
722 objective->set_offset((linear_problem.objective_offset() + offset) *
723 scaling_factor / gcd);
726void IntegralProblemConverter::AddVariableConstraints(
727 const LinearProgram& linear_problem,
728 LinearBooleanProblem* boolean_problem) {
729 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
732 const int pos = global_to_boolean_[
col];
735 CHECK_EQ(BOOLEAN, variable_types_[
col]);
741 LinearBooleanConstraint* constraint =
742 boolean_problem->add_constraints();
743 constraint->set_lower_bound(fixed_value);
744 constraint->set_upper_bound(fixed_value);
745 constraint->add_literals(pos + 1);
746 constraint->add_coefficients(1);
749 CHECK_EQ(INTEGRAL_EXPRESSED_AS_BOOLEAN, variable_types_[
col]);
752 const IntegralVariable& integral_var = integral_variables_[-pos - 1];
753 LinearBooleanConstraint* constraint =
754 boolean_problem->add_constraints();
755 for (
int i = 0;
i < integral_var.bits().
size(); ++
i) {
756 constraint->add_literals(integral_var.bits()[i].value() + 1);
757 constraint->add_coefficients(integral_var.weights()[i]);
760 constraint->set_lower_bound(
static_cast<int64_t
>(ceil(
lower_bound)) -
761 integral_var.offset());
765 integral_var.offset());
772bool IntegralProblemConverter::ConvertUsingExistingBooleans(
773 const LinearProgram& linear_problem, ColIndex
col,
774 IntegralVariable* integral_var) {
775 CHECK(
nullptr != integral_var);
776 CHECK_EQ(INTEGRAL, variable_types_[
col]);
778 const SparseMatrix& matrix = linear_problem.GetSparseMatrix();
779 const SparseMatrix& transpose = linear_problem.GetTransposeSparseMatrix();
780 for (
const SparseColumn::Entry var_entry : matrix.column(
col)) {
781 const RowIndex constraint = var_entry.row();
782 const Fractional lb = linear_problem.constraint_lower_bounds()[constraint];
783 const Fractional ub = linear_problem.constraint_upper_bounds()[constraint];
790 if (transpose.column(
RowToColIndex(constraint)).num_entries() <= 1) {
799 bool only_one_integral_variable =
true;
800 for (
const SparseColumn::Entry constraint_entry :
804 only_one_integral_variable =
false;
808 if (only_one_integral_variable &&
809 CreateVariableUsingConstraint(linear_problem, constraint,
815 integral_var->Clear();
819bool IntegralProblemConverter::CreateVariableUsingConstraint(
820 const LinearProgram& linear_problem, RowIndex constraint,
821 IntegralVariable* integral_var) {
822 CHECK(
nullptr != integral_var);
823 integral_var->Clear();
825 const SparseMatrix& transpose = linear_problem.GetTransposeSparseMatrix();
827 num_boolean_variables_, 0.0);
829 int64_t variable_offset = 0;
830 for (
const SparseColumn::Entry constraint_entry :
833 if (variable_types_[
col] == INTEGRAL) {
834 scale = constraint_entry.coefficient();
835 }
else if (variable_types_[
col] == BOOLEAN) {
836 const int pos = global_to_boolean_[
col];
838 dense_weights[VariableIndex(pos)] -= constraint_entry.coefficient();
840 CHECK_EQ(INTEGRAL_EXPRESSED_AS_BOOLEAN, variable_types_[
col]);
841 const int pos = global_to_boolean_[
col];
843 const IntegralVariable& local_integral_var =
844 integral_variables_[-pos - 1];
846 constraint_entry.coefficient() * local_integral_var.offset();
847 for (
int i = 0;
i < local_integral_var.bits().
size(); ++
i) {
848 dense_weights[local_integral_var.bits()[
i]] -=
849 constraint_entry.coefficient() * local_integral_var.weights()[
i];
855 const Fractional lb = linear_problem.constraint_lower_bounds()[constraint];
856 const Fractional offset = (lb + variable_offset) / scale;
860 integral_var->set_offset(
static_cast<int64_t
>(offset));
862 for (VariableIndex
var(0);
var < dense_weights.size(); ++
var) {
863 if (dense_weights[
var] != 0.0) {
868 integral_var->set_weight(
var,
static_cast<int64_t
>(
weight));
875Fractional IntegralProblemConverter::AddWeightedIntegralVariable(
878 CHECK(
nullptr != dense_weights);
885 const int pos = global_to_boolean_[
col];
888 (*dense_weights)[VariableIndex(pos)] +=
weight;
891 const IntegralVariable& integral_var = integral_variables_[-pos - 1];
892 for (
int i = 0;
i < integral_var.bits().
size(); ++
i) {
893 (*dense_weights)[integral_var.bits()[
i]] +=
894 integral_var.weights()[
i] *
weight;
896 offset +=
weight * integral_var.offset();
902double IntegralProblemConverter::ScaleAndSparsifyWeights(
903 double scaling_factor, int64_t gcd,
908 double bound_error = 0.0;
909 for (VariableIndex
var(0);
var < dense_weights.size(); ++
var) {
910 if (dense_weights[
var] != 0.0) {
911 const double scaled_weight = dense_weights[
var] * scaling_factor;
912 bound_error += fabs(round(scaled_weight) - scaled_weight);
913 t->add_literals(
var.value() + 1);
914 t->add_coefficients(
static_cast<int64_t
>(round(scaled_weight)) / gcd);
920bool IntegralProblemConverter::HasNonZeroWeights(
923 for (
const Fractional
weight : dense_weights) {
932 const glop::DenseRow& variable_values) {
933 glop::DenseColumn constraint_values(linear_problem.num_constraints(), 0);
935 const SparseMatrix& matrix = linear_problem.GetSparseMatrix();
936 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
941 LOG(ERROR) <<
"Variable " <<
col <<
" out of bound: " <<
value
946 for (
const SparseColumn::Entry entry : matrix.column(
col)) {
947 constraint_values[entry.row()] += entry.coefficient() *
value;
951 for (RowIndex
row(0);
row < linear_problem.num_constraints(); ++
row) {
952 const Fractional lb = linear_problem.constraint_lower_bounds()[
row];
953 const Fractional ub = linear_problem.constraint_upper_bounds()[
row];
956 LOG(ERROR) <<
"Constraint " <<
row <<
" out of bound: " <<
value
957 <<
" should be in " << lb <<
" .. " << ub;
968 const DenseRow& initial_solution,
969 TimeLimit*
time_limit, DenseRow* variable_values,
971 Fractional* best_bound) {
972 CHECK(variable_values !=
nullptr);
974 CHECK(best_bound !=
nullptr);
975 const bool use_initial_solution = (initial_solution.size() > 0);
976 if (use_initial_solution) {
977 CHECK_EQ(initial_solution.size(), linear_problem.num_variables());
983 variable_values->resize(linear_problem.num_variables(), 0);
985 LinearBooleanProblem boolean_problem;
986 std::vector<bool> boolean_initial_solution;
987 IntegralProblemConverter converter;
988 if (!converter.ConvertToBooleanProblem(linear_problem, initial_solution,
990 &boolean_initial_solution)) {
991 return BopSolveStatus::INVALID_PROBLEM;
994 BopSolver bop_solver(boolean_problem);
997 if (use_initial_solution) {
998 BopSolution bop_solution(boolean_problem,
"InitialSolution");
999 CHECK_EQ(boolean_initial_solution.size(), boolean_problem.num_variables());
1000 for (
int i = 0;
i < boolean_initial_solution.size(); ++
i) {
1001 bop_solution.SetValue(VariableIndex(i), boolean_initial_solution[i]);
1007 if (
status == BopSolveStatus::OPTIMAL_SOLUTION_FOUND ||
1008 status == BopSolveStatus::FEASIBLE_SOLUTION_FOUND) {
1010 const BopSolution&
solution = bop_solver.best_solution();
1014 for (ColIndex
col(0);
col < linear_problem.num_variables(); ++
col) {
1024 *best_bound =
status == BopSolveStatus::OPTIMAL_SOLUTION_FOUND
1026 : bop_solver.GetScaledBestBound();
1031void RunOneBop(
const BopParameters&
parameters,
int problem_index,
1032 const DenseRow& initial_solution, TimeLimit*
time_limit,
1033 LPDecomposer* decomposer, DenseRow* variable_values,
1035 BopSolveStatus*
status) {
1036 CHECK(decomposer !=
nullptr);
1037 CHECK(variable_values !=
nullptr);
1039 CHECK(best_bound !=
nullptr);
1040 CHECK(
status !=
nullptr);
1042 LinearProgram problem;
1043 decomposer->ExtractLocalProblem(problem_index, &problem);
1045 if (initial_solution.size() > 0) {
1046 local_initial_solution =
1047 decomposer->ExtractLocalAssignment(problem_index, initial_solution);
1051 const double total_num_variables = std::max(
1052 1.0,
static_cast<double>(
1053 decomposer->original_problem().num_variables().value()));
1054 const double time_per_variable =
1055 parameters.max_time_in_seconds() / total_num_variables;
1056 const double deterministic_time_per_variable =
1057 parameters.max_deterministic_time() / total_num_variables;
1058 const int local_num_variables = std::max(1, problem.num_variables().value());
1060 NestedTimeLimit subproblem_time_limit(
1062 std::max(time_per_variable * local_num_variables,
1063 parameters.decomposed_problem_min_time_in_seconds()),
1064 deterministic_time_per_variable * local_num_variables);
1067 subproblem_time_limit.GetTimeLimit(), variable_values,
1072IntegralSolver::IntegralSolver()
1073 : parameters_(), variable_values_(), objective_value_(0.0) {}
1075BopSolveStatus IntegralSolver::Solve(
const LinearProgram& linear_problem) {
1076 return Solve(linear_problem, DenseRow());
1081 return SolveWithTimeLimit(linear_problem, DenseRow(),
time_limit);
1084BopSolveStatus IntegralSolver::Solve(
1085 const LinearProgram& linear_problem,
1086 const DenseRow& user_provided_initial_solution) {
1088 TimeLimit::FromParameters(parameters_);
1089 return SolveWithTimeLimit(linear_problem, user_provided_initial_solution,
1093BopSolveStatus IntegralSolver::SolveWithTimeLimit(
1094 const LinearProgram& linear_problem,
1095 const DenseRow& user_provided_initial_solution, TimeLimit*
time_limit) {
1097 DenseRow initial_solution = user_provided_initial_solution;
1098 if (initial_solution.size() > 0) {
1099 CHECK_EQ(initial_solution.size(), linear_problem.num_variables())
1100 <<
"The initial solution should have the same number of variables as "
1101 "the LinearProgram.";
1106 LinearProgram
const* lp = &linear_problem;
1109 if (lp->num_variables() >= parameters_.decomposer_num_variables_threshold()) {
1110 LPDecomposer decomposer;
1111 decomposer.Decompose(lp);
1112 const int num_sub_problems = decomposer.GetNumberOfProblems();
1113 VLOG(1) <<
"Problem is decomposable into " << num_sub_problems
1115 if (num_sub_problems > 1) {
1118 std::vector<DenseRow> variable_values(num_sub_problems);
1119 std::vector<Fractional> objective_values(num_sub_problems,
1121 std::vector<Fractional> best_bounds(num_sub_problems,
Fractional(0.0));
1122 std::vector<BopSolveStatus> statuses(num_sub_problems,
1123 BopSolveStatus::INVALID_PROBLEM);
1125 for (
int i = 0;
i < num_sub_problems; ++
i) {
1126 RunOneBop(parameters_, i, initial_solution,
time_limit, &decomposer,
1127 &(variable_values[i]), &(objective_values[i]),
1128 &(best_bounds[i]), &(statuses[i]));
1132 status = BopSolveStatus::OPTIMAL_SOLUTION_FOUND;
1133 objective_value_ = lp->objective_offset();
1135 for (
int i = 0;
i < num_sub_problems; ++
i) {
1136 objective_value_ += objective_values[
i];
1137 best_bound_ += best_bounds[
i];
1138 if (statuses[i] == BopSolveStatus::NO_SOLUTION_FOUND ||
1139 statuses[i] == BopSolveStatus::INFEASIBLE_PROBLEM ||
1140 statuses[i] == BopSolveStatus::INVALID_PROBLEM) {
1144 if (statuses[i] == BopSolveStatus::FEASIBLE_SOLUTION_FOUND) {
1145 status = BopSolveStatus::FEASIBLE_SOLUTION_FOUND;
1148 variable_values_ = decomposer.AggregateAssignments(variable_values);
1152 InternalSolve(*lp, parameters_, initial_solution,
time_limit,
1153 &variable_values_, &objective_value_, &best_bound_);
1157 &variable_values_, &objective_value_, &best_bound_);
absl::Span< const double > coefficients
BopSolveStatus
Status of the solve of Bop.
bool CheckSolution(const Model &model, const std::function< int64_t(Variable *)> &evaluator, SolverLogger *logger)
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
VariableType
Different types of variables.
StrictITIVector< ColIndex, Fractional > DenseRow
Row-vector types. Row-vector types are indexed by a column index.
constexpr double kTolerance
void ChangeOptimizationDirection(LinearBooleanProblem *problem)
In SWIG mode, we don't want anything besides these top-level includes.
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
double GetBestScalingOfDoublesToInt64(absl::Span< const double > input, absl::Span< const double > lb, absl::Span< const double > ub, int64_t max_absolute_sum)
int64_t ComputeGcdOfRoundedDoubles(absl::Span< const double > x, double scaling_factor)
int MostSignificantBitPosition64(uint64_t n)
double max_scaling_factor
double objective_value
The value objective_vector^T * (solution - center_point).