24 #include "absl/strings/str_cat.h"
52 void ScaleConstraint(
const std::vector<double>& var_scaling,
53 MPConstraintProto* mp_constraint) {
54 const int num_terms = mp_constraint->coefficient_size();
55 for (
int i = 0; i < num_terms; ++i) {
56 const int var_index = mp_constraint->var_index(i);
57 mp_constraint->set_coefficient(
58 i, mp_constraint->coefficient(i) / var_scaling[var_index]);
62 void ApplyVarScaling(
const std::vector<double> var_scaling,
63 MPModelProto* mp_model) {
64 const int num_variables = mp_model->variable_size();
65 for (
int i = 0; i < num_variables; ++i) {
66 const double scaling = var_scaling[i];
67 const MPVariableProto& mp_var = mp_model->variable(i);
68 const double old_lb = mp_var.lower_bound();
69 const double old_ub = mp_var.upper_bound();
70 const double old_obj = mp_var.objective_coefficient();
71 mp_model->mutable_variable(i)->set_lower_bound(old_lb * scaling);
72 mp_model->mutable_variable(i)->set_upper_bound(old_ub * scaling);
73 mp_model->mutable_variable(i)->set_objective_coefficient(old_obj / scaling);
75 for (MPConstraintProto& mp_constraint : *mp_model->mutable_constraint()) {
76 ScaleConstraint(var_scaling, &mp_constraint);
78 for (MPGeneralConstraintProto& general_constraint :
79 *mp_model->mutable_general_constraint()) {
80 switch (general_constraint.general_constraint_case()) {
81 case MPGeneralConstraintProto::kIndicatorConstraint:
82 ScaleConstraint(var_scaling,
83 general_constraint.mutable_indicator_constraint()
84 ->mutable_constraint());
86 case MPGeneralConstraintProto::kAndConstraint:
87 case MPGeneralConstraintProto::kOrConstraint:
92 LOG(FATAL) <<
"Scaling unsupported for general constraint of type "
93 << general_constraint.general_constraint_case();
101 MPModelProto* mp_model) {
102 const int num_variables = mp_model->variable_size();
103 std::vector<double> var_scaling(num_variables, 1.0);
104 for (
int i = 0; i < num_variables; ++i) {
105 if (mp_model->variable(i).is_integer())
continue;
106 const double lb = mp_model->variable(i).lower_bound();
107 const double ub = mp_model->variable(i).upper_bound();
108 const double magnitude =
std::max(std::abs(lb), std::abs(ub));
109 if (magnitude == 0 || magnitude > max_bound)
continue;
110 var_scaling[i] =
std::min(scaling, max_bound / magnitude);
112 ApplyVarScaling(var_scaling, mp_model);
120 const double initial_x = x;
126 if (std::abs(q * initial_x - std::round(q * initial_x)) < q * tolerance) {
130 const int new_q = prev_q +
static_cast<int>(std::floor(x)) * q;
139 const int num_variables = mp_model->variable_size();
140 std::vector<double> var_scaling(num_variables, 1.0);
142 int num_integers = 0;
143 for (
int i = 0; i < num_variables; ++i) {
144 if (mp_model->variable(i).is_integer()) ++num_integers;
146 VLOG(1) <<
"Initial num_integers: " << num_integers <<
" / " << num_variables;
149 const double tolerance = 1e-6;
150 std::vector<int> constraint_queue;
152 const int num_constraints = mp_model->constraint_size();
153 std::vector<int> constraint_to_num_non_integer(num_constraints, 0);
154 std::vector<std::vector<int>> var_to_constraints(num_variables);
155 for (
int i = 0; i < num_constraints; ++i) {
156 const MPConstraintProto& mp_constraint = mp_model->constraint(i);
165 if (mp_constraint.lower_bound() + tolerance < mp_constraint.upper_bound()) {
169 for (
const int var : mp_constraint.var_index()) {
170 if (!mp_model->variable(
var).is_integer()) {
171 var_to_constraints[
var].push_back(i);
172 constraint_to_num_non_integer[i]++;
175 if (constraint_to_num_non_integer[i] == 1) {
176 constraint_queue.push_back(i);
179 VLOG(1) <<
"Initial constraint queue: " << constraint_queue.size() <<
" / "
182 int num_detected = 0;
183 int num_fail_due_to_rhs = 0;
184 int num_fail_due_to_large_multiplier = 0;
185 int num_processed_constraints = 0;
186 while (!constraint_queue.empty()) {
187 const int top_ct_index = constraint_queue.back();
188 constraint_queue.pop_back();
192 if (constraint_to_num_non_integer[top_ct_index] == 0)
continue;
193 ++num_processed_constraints;
205 double multiplier = 1.0;
206 const double max_multiplier = 1e4;
208 const MPConstraintProto&
ct = mp_model->constraint(top_ct_index);
209 const double rhs =
ct.lower_bound();
210 for (
int i = 0; i <
ct.var_index().size(); ++i) {
211 if (!mp_model->variable(
ct.var_index(i)).is_integer()) {
213 var =
ct.var_index(i);
214 var_coeff =
ct.coefficient(i);
219 multiplier *
ct.coefficient(i) * var_scaling[
ct.var_index(i)];
222 if (multiplier == 0 || multiplier > max_multiplier) {
228 if (multiplier == 0 || multiplier > max_multiplier) {
229 ++num_fail_due_to_large_multiplier;
234 if (std::abs(std::round(rhs * multiplier) - rhs * multiplier) >
235 tolerance * multiplier) {
236 ++num_fail_due_to_rhs;
242 const double scaling = std::abs(var_coeff * multiplier);
246 VLOG_IF(2, scaling != 1.0) <<
"Scaled var by " << scaling;
250 var_scaling[
var] = scaling;
251 mp_model->mutable_variable(
var)->set_is_integer(
true);
254 for (
const int ct_index : var_to_constraints[
var]) {
255 constraint_to_num_non_integer[ct_index]--;
256 if (constraint_to_num_non_integer[ct_index] == 1) {
257 constraint_queue.push_back(ct_index);
262 VLOG(1) <<
"num_new_integer: " << num_detected
263 <<
" num_processed_constraints: " << num_processed_constraints
264 <<
" num_rhs_fail: " << num_fail_due_to_rhs
265 <<
" num_multiplier_fail: " << num_fail_due_to_large_multiplier;
267 ApplyVarScaling(var_scaling, mp_model);
274 struct ConstraintScaler {
276 ConstraintProto* AddConstraint(
const MPModelProto& mp_model,
277 const MPConstraintProto& mp_constraint,
278 CpModelProto* cp_model);
294 double FindFractionalScaling(
const std::vector<double>&
coefficients,
296 double multiplier = 1.0;
300 if (multiplier == 0.0)
break;
307 ConstraintProto* ConstraintScaler::AddConstraint(
308 const MPModelProto& mp_model,
const MPConstraintProto& mp_constraint,
309 CpModelProto* cp_model) {
310 if (mp_constraint.lower_bound() == -
kInfinity &&
311 mp_constraint.upper_bound() ==
kInfinity) {
315 auto* constraint = cp_model->add_constraints();
316 constraint->set_name(mp_constraint.name());
317 auto* arg = constraint->mutable_linear();
325 const int num_coeffs = mp_constraint.coefficient_size();
326 for (
int i = 0; i < num_coeffs; ++i) {
327 const auto& var_proto = cp_model->variables(mp_constraint.var_index(i));
328 const int64 lb = var_proto.domain(0);
329 const int64 ub = var_proto.domain(var_proto.domain_size() - 1);
330 if (lb == 0 && ub == 0)
continue;
331 if (mp_constraint.coefficient(i) == 0.0)
continue;
344 double relative_ref = 1.0;
345 if (lb > 1.0) relative_ref = lb;
346 if (ub < -1.0) relative_ref = -ub;
356 double x =
std::min(scaling_factor, 1.0);
357 double relative_coeff_error;
358 double scaled_sum_error;
359 for (; x <= scaling_factor; x *= 2) {
361 &relative_coeff_error, &scaled_sum_error);
371 const double integer_factor = FindFractionalScaling(
coefficients, 1e-8);
372 if (integer_factor != 0 && integer_factor < scaling_factor) {
374 &relative_coeff_error, &scaled_sum_error);
376 scaling_factor = integer_factor;
387 bool relax_bound = scaled_sum_error > 0;
390 const double scaled_value =
coefficients[i] * scaling_factor;
391 const int64 value =
static_cast<int64>(std::round(scaled_value)) / gcd;
393 if (!mp_model.variable(
var_indices[i]).is_integer()) {
397 arg->add_coeffs(
value);
401 scaled_sum_error / (scaling_factor * relative_ref));
409 const Fractional scaled_lb = std::ceil(lb * scaling_factor);
413 arg->add_domain(
CeilRatio(IntegerValue(
static_cast<int64>(scaled_lb)),
421 const Fractional scaled_ub = std::floor(ub * scaling_factor);
425 arg->add_domain(
FloorRatio(IntegerValue(
static_cast<int64>(scaled_ub)),
436 const MPModelProto& mp_model,
437 CpModelProto* cp_model) {
438 CHECK(cp_model !=
nullptr);
440 cp_model->set_name(mp_model.name());
454 const int64 kMaxVariableBound =
static_cast<int64>(params.mip_max_bound());
456 int num_truncated_bounds = 0;
457 int num_small_domains = 0;
458 const int64 kSmallDomainSize = 1000;
459 const double kWantedPrecision = params.mip_wanted_precision();
462 const int num_variables = mp_model.variable_size();
463 for (
int i = 0; i < num_variables; ++i) {
464 const MPVariableProto& mp_var = mp_model.variable(i);
465 IntegerVariableProto* cp_var = cp_model->add_variables();
466 cp_var->set_name(mp_var.name());
473 if (mp_var.lower_bound() > kMaxVariableBound) {
475 ++num_truncated_bounds;
476 const int64 value =
static_cast<int64>(std::round(mp_var.lower_bound()));
477 cp_var->add_domain(
value);
478 cp_var->add_domain(
value);
480 }
else if (mp_var.upper_bound() < -kMaxVariableBound) {
482 ++num_truncated_bounds;
483 const int64 value =
static_cast<int64>(std::round(mp_var.upper_bound()));
484 cp_var->add_domain(
value);
485 cp_var->add_domain(
value);
490 for (
const bool lower : {
true,
false}) {
491 const double bound = lower ? mp_var.lower_bound() : mp_var.upper_bound();
492 if (std::abs(
bound) >= kMaxVariableBound) {
493 ++num_truncated_bounds;
494 cp_var->add_domain(
bound < 0 ? -kMaxVariableBound : kMaxVariableBound);
500 static_cast<int64>(lower ? std::ceil(
bound - kWantedPrecision)
501 : std::floor(
bound + kWantedPrecision)));
506 if (!mp_var.is_integer() && cp_var->domain(0) != cp_var->domain(1) &&
507 cp_var->domain(1) - cp_var->domain(0) < kSmallDomainSize) {
512 LOG_IF(WARNING, num_truncated_bounds > 0)
513 << num_truncated_bounds <<
" bounds were truncated to "
514 << kMaxVariableBound <<
".";
515 LOG_IF(WARNING, num_small_domains > 0)
516 << num_small_domains <<
" continuous variable domain with fewer than "
517 << kSmallDomainSize <<
" values.";
519 ConstraintScaler scaler;
520 const int64 kScalingTarget =
int64{1} << params.mip_max_activity_exponent();
521 scaler.wanted_precision = kWantedPrecision;
522 scaler.scaling_target = kScalingTarget;
525 for (
const MPConstraintProto& mp_constraint : mp_model.constraint()) {
526 scaler.AddConstraint(mp_model, mp_constraint, cp_model);
528 for (
const MPGeneralConstraintProto& general_constraint :
529 mp_model.general_constraint()) {
530 switch (general_constraint.general_constraint_case()) {
531 case MPGeneralConstraintProto::kIndicatorConstraint: {
532 const auto& indicator_constraint =
533 general_constraint.indicator_constraint();
534 const MPConstraintProto& mp_constraint =
535 indicator_constraint.constraint();
536 ConstraintProto*
ct =
537 scaler.AddConstraint(mp_model, mp_constraint, cp_model);
538 if (
ct ==
nullptr)
continue;
541 const int var = indicator_constraint.var_index();
542 const int value = indicator_constraint.var_value();
546 case MPGeneralConstraintProto::kAndConstraint: {
547 const auto& and_constraint = general_constraint.and_constraint();
548 const std::string&
name = general_constraint.name();
550 ConstraintProto* ct_pos = cp_model->add_constraints();
551 ct_pos->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_pos"));
552 ct_pos->add_enforcement_literal(and_constraint.resultant_var_index());
553 *ct_pos->mutable_bool_and()->mutable_literals() =
554 and_constraint.var_index();
556 ConstraintProto* ct_neg = cp_model->add_constraints();
557 ct_neg->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_neg"));
558 ct_neg->add_enforcement_literal(
559 NegatedRef(and_constraint.resultant_var_index()));
560 for (
const int var_index : and_constraint.var_index()) {
561 ct_neg->mutable_bool_or()->add_literals(
NegatedRef(var_index));
565 case MPGeneralConstraintProto::kOrConstraint: {
566 const auto& or_constraint = general_constraint.or_constraint();
567 const std::string&
name = general_constraint.name();
569 ConstraintProto* ct_pos = cp_model->add_constraints();
570 ct_pos->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_pos"));
571 ct_pos->add_enforcement_literal(or_constraint.resultant_var_index());
572 *ct_pos->mutable_bool_or()->mutable_literals() =
573 or_constraint.var_index();
575 ConstraintProto* ct_neg = cp_model->add_constraints();
576 ct_neg->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_neg"));
577 ct_neg->add_enforcement_literal(
578 NegatedRef(or_constraint.resultant_var_index()));
579 for (
const int var_index : or_constraint.var_index()) {
580 ct_neg->mutable_bool_and()->add_literals(
NegatedRef(var_index));
585 LOG(ERROR) <<
"Can't convert general constraints of type "
586 << general_constraint.general_constraint_case()
587 <<
" to CpModelProto.";
597 VLOG(1) <<
"Maximum constraint coefficient relative error: "
599 VLOG(1) <<
"Maximum constraint worst-case sum absolute error: "
608 for (
int i = 0; i < num_variables; ++i) {
609 const MPVariableProto& mp_var = mp_model.variable(i);
610 if (mp_var.objective_coefficient() == 0.0)
continue;
612 const auto& var_proto = cp_model->variables(i);
613 const int64 lb = var_proto.domain(0);
614 const int64 ub = var_proto.domain(var_proto.domain_size() - 1);
615 if (lb == 0 && ub == 0)
continue;
622 if (!
coefficients.empty() || mp_model.objective_offset() != 0.0) {
631 double x =
std::min(scaling_factor, 1.0);
632 double relative_coeff_error;
633 double scaled_sum_error;
634 for (; x <= scaling_factor; x *= 2) {
636 &relative_coeff_error, &scaled_sum_error);
637 if (scaled_sum_error < kWantedPrecision * x)
break;
643 const double integer_factor = FindFractionalScaling(
coefficients, 1e-8);
644 if (integer_factor != 0 && integer_factor < scaling_factor) {
646 &relative_coeff_error, &scaled_sum_error);
647 if (scaled_sum_error < kWantedPrecision * integer_factor) {
648 scaling_factor = integer_factor;
657 VLOG(1) <<
"objective coefficient relative error: " << relative_coeff_error;
658 VLOG(1) <<
"objective worst-case absolute error: "
659 << scaled_sum_error / scaling_factor;
660 VLOG(1) <<
"objective scaling factor: " << scaling_factor / gcd;
665 auto* objective = cp_model->mutable_objective();
666 const int mult = mp_model.maximize() ? -1 : 1;
667 objective->set_offset(mp_model.objective_offset() * scaling_factor / gcd *
669 objective->set_scaling_factor(1.0 / scaling_factor * gcd * mult);
676 objective->add_coeffs(
value * mult);
682 const double allowed_error =
683 std::max(params.mip_check_precision(), params.mip_wanted_precision());
685 LOG(WARNING) <<
"The relative error during double -> int64 conversion "
687 <<
" check_tolerance:" << allowed_error;
694 LinearBooleanProblem* problem) {
695 CHECK(problem !=
nullptr);
697 problem->set_name(mp_model.name());
698 const int num_variables = mp_model.variable_size();
699 problem->set_num_variables(num_variables);
703 for (
int var_id(0); var_id < num_variables; ++var_id) {
704 const MPVariableProto& mp_var = mp_model.variable(var_id);
705 problem->add_var_names(mp_var.name());
710 bool is_binary = mp_var.is_integer();
714 if (lb <= -1.0) is_binary =
false;
715 if (ub >= 2.0) is_binary =
false;
718 if (lb <= 0.0 && ub >= 1.0) {
720 }
else if (lb <= 1.0 && ub >= 1.0) {
722 LinearBooleanConstraint* constraint = problem->add_constraints();
723 constraint->set_lower_bound(1);
724 constraint->set_upper_bound(1);
725 constraint->add_literals(var_id + 1);
726 constraint->add_coefficients(1);
727 }
else if (lb <= 0.0 && ub >= 0.0) {
729 LinearBooleanConstraint* constraint = problem->add_constraints();
730 constraint->set_lower_bound(0);
731 constraint->set_upper_bound(0);
732 constraint->add_literals(var_id + 1);
733 constraint->add_coefficients(1);
742 LOG(WARNING) <<
"The variable #" << var_id <<
" with name "
743 << mp_var.name() <<
" is not binary. "
744 <<
"lb: " << lb <<
" ub: " << ub;
751 double max_relative_error = 0.0;
752 double max_bound_error = 0.0;
754 double relative_error = 0.0;
755 double scaling_factor = 0.0;
759 for (
const MPConstraintProto& mp_constraint : mp_model.constraint()) {
760 LinearBooleanConstraint* constraint = problem->add_constraints();
761 constraint->set_name(mp_constraint.name());
765 const int num_coeffs = mp_constraint.coefficient_size();
766 for (
int i = 0; i < num_coeffs; ++i) {
772 max_relative_error =
std::max(relative_error, max_relative_error);
775 double bound_error = 0.0;
776 for (
int i = 0; i < num_coeffs; ++i) {
777 const double scaled_value = mp_constraint.coefficient(i) * scaling_factor;
778 bound_error += std::abs(round(scaled_value) - scaled_value);
781 constraint->add_literals(mp_constraint.var_index(i) + 1);
782 constraint->add_coefficients(
value);
785 max_bound_error =
std::max(max_bound_error, bound_error);
792 const Fractional lb = mp_constraint.lower_bound();
794 if (lb * scaling_factor >
static_cast<double>(kInt64Max)) {
795 LOG(WARNING) <<
"A constraint is trivially unsatisfiable.";
798 if (lb * scaling_factor > -
static_cast<double>(kInt64Max)) {
800 constraint->set_lower_bound(
801 static_cast<int64>(round(lb * scaling_factor - bound_error)) / gcd);
804 const Fractional ub = mp_constraint.upper_bound();
806 if (ub * scaling_factor < -
static_cast<double>(kInt64Max)) {
807 LOG(WARNING) <<
"A constraint is trivially unsatisfiable.";
810 if (ub * scaling_factor <
static_cast<double>(kInt64Max)) {
812 constraint->set_upper_bound(
813 static_cast<int64>(round(ub * scaling_factor + bound_error)) / gcd);
819 LOG(INFO) <<
"Maximum constraint relative error: " << max_relative_error;
820 LOG(INFO) <<
"Maximum constraint bound error: " << max_bound_error;
825 for (
int var_id = 0; var_id < num_variables; ++var_id) {
826 const MPVariableProto& mp_var = mp_model.variable(var_id);
832 max_relative_error =
std::max(relative_error, max_relative_error);
835 LOG(INFO) <<
"objective relative error: " << relative_error;
836 LOG(INFO) <<
"objective scaling factor: " << scaling_factor / gcd;
838 LinearObjective* objective = problem->mutable_objective();
839 objective->set_offset(mp_model.objective_offset() * scaling_factor / gcd);
843 objective->set_scaling_factor(1.0 / scaling_factor * gcd);
844 for (
int var_id = 0; var_id < num_variables; ++var_id) {
845 const MPVariableProto& mp_var = mp_model.variable(var_id);
847 mp_var.objective_coefficient() * scaling_factor)) /
850 objective->add_literals(var_id + 1);
851 objective->add_coefficients(
value);
859 const double kRelativeTolerance = 1e-8;
860 if (max_relative_error > kRelativeTolerance) {
861 LOG(WARNING) <<
"The relative error during double -> int64 conversion "
871 for (
int i = 0; i < problem.num_variables(); ++i) {
878 if (problem.var_names_size() != 0) {
879 CHECK_EQ(problem.var_names_size(), problem.num_variables());
880 for (
int i = 0; i < problem.num_variables(); ++i) {
885 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
889 for (
int i = 0; i < constraint.literals_size(); ++i) {
890 const int literal = constraint.literals(i);
891 const double coeff = constraint.coefficients(i);
892 const ColIndex variable_index = ColIndex(abs(
literal) - 1);
902 constraint.has_lower_bound() ? constraint.lower_bound() - sum
904 constraint.has_upper_bound() ? constraint.upper_bound() - sum
911 const LinearObjective& objective = problem.objective();
912 const double scaling_factor = objective.scaling_factor();
913 for (
int i = 0; i < objective.literals_size(); ++i) {
914 const int literal = objective.literals(i);
916 static_cast<double>(objective.coefficients(i)) * scaling_factor;
917 const ColIndex variable_index = ColIndex(abs(
literal) - 1);
933 int num_fixed_variables = 0;
935 for (
int i = 0; i < trail.
Index(); ++i) {
936 const BooleanVariable
var = trail[i].Variable();
937 const int value = trail[i].IsPositive() ? 1.0 : 0.0;
939 ++num_fixed_variables;
943 return num_fixed_variables;
948 double max_time_in_seconds) {
950 glop::GlopParameters glop_parameters;
951 glop_parameters.set_max_time_in_seconds(max_time_in_seconds);
969 LinearBooleanProblem* problem) {
975 int num_variable_fixed = 0;
979 if (
value > 1 - tolerance) {
980 ++num_variable_fixed;
981 LinearBooleanConstraint* constraint = problem->add_constraints();
982 constraint->set_lower_bound(1);
983 constraint->set_upper_bound(1);
984 constraint->add_coefficients(1);
985 constraint->add_literals(
col.value() + 1);
986 }
else if (
value < tolerance) {
987 ++num_variable_fixed;
988 LinearBooleanConstraint* constraint = problem->add_constraints();
989 constraint->set_lower_bound(0);
990 constraint->set_upper_bound(0);
991 constraint->add_coefficients(1);
992 constraint->add_literals(
col.value() + 1);
995 LOG(INFO) <<
"LNS with " << num_variable_fixed <<
" fixed variables.";