25 #include "absl/strings/str_cat.h"
29 #include "ortools/glop/parameters.pb.h"
30 #include "ortools/linear_solver/linear_solver.pb.h"
34 #include "ortools/sat/boolean_problem.pb.h"
35 #include "ortools/sat/cp_model.pb.h"
38 #include "ortools/sat/sat_parameters.pb.h"
51 using operations_research::MPConstraintProto;
52 using operations_research::MPModelProto;
53 using operations_research::MPVariableProto;
57 void ScaleConstraint(
const std::vector<double>& var_scaling,
58 MPConstraintProto* mp_constraint) {
59 const int num_terms = mp_constraint->coefficient_size();
60 for (
int i = 0; i < num_terms; ++i) {
61 const int var_index = mp_constraint->var_index(i);
62 mp_constraint->set_coefficient(
63 i, mp_constraint->coefficient(i) / var_scaling[var_index]);
67 void ApplyVarScaling(
const std::vector<double>& var_scaling,
68 MPModelProto* mp_model) {
69 const int num_variables = mp_model->variable_size();
70 for (
int i = 0; i < num_variables; ++i) {
71 const double scaling = var_scaling[i];
72 const MPVariableProto& mp_var = mp_model->variable(i);
73 const double old_lb = mp_var.lower_bound();
74 const double old_ub = mp_var.upper_bound();
75 const double old_obj = mp_var.objective_coefficient();
76 mp_model->mutable_variable(i)->set_lower_bound(old_lb * scaling);
77 mp_model->mutable_variable(i)->set_upper_bound(old_ub * scaling);
78 mp_model->mutable_variable(i)->set_objective_coefficient(old_obj / scaling);
82 for (MPConstraintProto& mp_constraint : *mp_model->mutable_constraint()) {
83 ScaleConstraint(var_scaling, &mp_constraint);
85 for (MPGeneralConstraintProto& general_constraint :
86 *mp_model->mutable_general_constraint()) {
87 switch (general_constraint.general_constraint_case()) {
88 case MPGeneralConstraintProto::kIndicatorConstraint:
89 ScaleConstraint(var_scaling,
90 general_constraint.mutable_indicator_constraint()
91 ->mutable_constraint());
93 case MPGeneralConstraintProto::kAndConstraint:
94 case MPGeneralConstraintProto::kOrConstraint:
99 LOG(FATAL) <<
"Scaling unsupported for general constraint of type "
100 << general_constraint.general_constraint_case();
108 MPModelProto* mp_model) {
109 const int num_variables = mp_model->variable_size();
110 std::vector<double> var_scaling(num_variables, 1.0);
111 for (
int i = 0; i < num_variables; ++i) {
112 if (mp_model->variable(i).is_integer())
continue;
113 if (max_bound == std::numeric_limits<double>::infinity()) {
114 var_scaling[i] = scaling;
117 const double lb = mp_model->variable(i).lower_bound();
118 const double ub = mp_model->variable(i).upper_bound();
119 const double magnitude =
std::max(std::abs(lb), std::abs(ub));
120 if (magnitude == 0 || magnitude > max_bound)
continue;
121 var_scaling[i] =
std::min(scaling, max_bound / magnitude);
123 ApplyVarScaling(var_scaling, mp_model);
131 const double initial_x = x;
134 int64_t current_q = 1;
136 while (current_q < limit) {
137 const double q =
static_cast<double>(current_q);
138 const double qx = q * initial_x;
139 const double qtolerance = q * tolerance;
140 if (std::abs(qx - std::round(qx)) < qtolerance) {
144 const double floored_x = std::floor(x);
148 const int64_t new_q =
149 CapAdd(prev_q,
CapProd(
static_cast<int64_t
>(floored_x), current_q));
163 double GetIntegralityMultiplier(
const MPModelProto& mp_model,
164 const std::vector<double>& var_scaling,
int var,
165 int ct_index,
double tolerance) {
166 DCHECK(!mp_model.variable(
var).is_integer());
167 const MPConstraintProto&
ct = mp_model.constraint(ct_index);
168 double multiplier = 1.0;
169 double var_coeff = 0.0;
170 const double max_multiplier = 1e4;
171 for (
int i = 0; i <
ct.var_index().size(); ++i) {
172 if (
var ==
ct.var_index(i)) {
173 var_coeff =
ct.coefficient(i);
177 DCHECK(mp_model.variable(
ct.var_index(i)).is_integer());
181 multiplier *
ct.coefficient(i) / var_scaling[
ct.var_index(i)];
184 if (multiplier == 0 || multiplier > max_multiplier)
return 0.0;
186 DCHECK_NE(var_coeff, 0.0);
189 for (
const double bound : {
ct.lower_bound(),
ct.upper_bound()}) {
190 if (!std::isfinite(
bound))
continue;
191 if (std::abs(std::round(
bound * multiplier) -
bound * multiplier) >
192 tolerance * multiplier) {
196 return std::abs(multiplier * var_coeff);
202 MPModelProto* mp_model,
204 const int num_variables = mp_model->variable_size();
205 const double tolerance = params.mip_wanted_precision();
206 int64_t num_changes = 0;
207 for (
int i = 0; i < num_variables; ++i) {
208 const MPVariableProto& mp_var = mp_model->variable(i);
209 if (!mp_var.is_integer())
continue;
211 const double lb = mp_var.lower_bound();
212 const double new_lb = std::isfinite(lb) ? std::ceil(lb - tolerance) : lb;
215 mp_model->mutable_variable(i)->set_lower_bound(new_lb);
218 const double ub = mp_var.upper_bound();
219 const double new_ub = std::isfinite(ub) ? std::floor(ub + tolerance) : ub;
222 mp_model->mutable_variable(i)->set_upper_bound(new_ub);
225 if (new_ub < new_lb) {
226 SOLVER_LOG(logger,
"Empty domain for integer variable #", i,
": [", lb,
232 if (num_changes > 0) {
234 " bounds of integer variables to integer values");
243 double max_dropped = 0.0;
244 const double drop = params.mip_drop_tolerance();
245 const int num_variables = mp_model->variable_size();
246 for (
int i = 0; i < num_variables; ++i) {
247 MPVariableProto*
var = mp_model->mutable_variable(i);
248 if (
var->lower_bound() != 0.0 && std::abs(
var->lower_bound()) < drop) {
250 max_dropped =
std::max(max_dropped, std::abs(
var->lower_bound()));
251 var->set_lower_bound(0.0);
253 if (
var->upper_bound() != 0.0 && std::abs(
var->upper_bound()) < drop) {
255 max_dropped =
std::max(max_dropped, std::abs(
var->upper_bound()));
256 var->set_upper_bound(0.0);
259 const int num_constraints = mp_model->constraint_size();
260 for (
int i = 0; i < num_constraints; ++i) {
261 MPConstraintProto*
ct = mp_model->mutable_constraint(i);
262 if (
ct->lower_bound() != 0.0 && std::abs(
ct->lower_bound()) < drop) {
264 max_dropped =
std::max(max_dropped, std::abs(
ct->lower_bound()));
265 ct->set_lower_bound(0.0);
267 if (
ct->upper_bound() != 0.0 && std::abs(
ct->upper_bound()) < drop) {
269 max_dropped =
std::max(max_dropped, std::abs(
ct->upper_bound()));
270 ct->set_upper_bound(0.0);
273 if (num_dropped > 0) {
274 SOLVER_LOG(logger,
"Set to zero ", num_dropped,
275 " variable or constraint bounds with largest magnitude ",
282 std::vector<double> max_bounds(num_variables);
283 for (
int i = 0; i < num_variables; ++i) {
284 double value = std::abs(mp_model->variable(i).lower_bound());
287 max_bounds[i] =
value;
292 double largest_removed = 0.0;
299 int64_t num_removed = 0;
300 for (
int c = 0; c < num_constraints; ++c) {
301 MPConstraintProto*
ct = mp_model->mutable_constraint(c);
303 const int size =
ct->var_index().size();
304 if (size == 0)
continue;
305 const double threshold =
306 params.mip_wanted_precision() /
static_cast<double>(size);
307 for (
int i = 0; i < size; ++i) {
308 const int var =
ct->var_index(i);
309 const double coeff =
ct->coefficient(i);
310 if (std::abs(coeff) * max_bounds[
var] < threshold) {
311 if (max_bounds[
var] != 0) {
312 largest_removed =
std::max(largest_removed, std::abs(coeff));
316 ct->set_var_index(new_size,
var);
317 ct->set_coefficient(new_size, coeff);
320 num_removed += size - new_size;
321 ct->mutable_var_index()->Truncate(new_size);
322 ct->mutable_coefficient()->Truncate(new_size);
326 if (num_variables > 0) {
327 const double threshold =
328 params.mip_wanted_precision() /
static_cast<double>(num_variables);
329 for (
int var = 0;
var < num_variables; ++
var) {
330 const double coeff = mp_model->variable(
var).objective_coefficient();
331 if (coeff == 0.0)
continue;
332 if (std::abs(coeff) * max_bounds[
var] < threshold) {
334 if (max_bounds[
var] != 0) {
335 largest_removed =
std::max(largest_removed, std::abs(coeff));
337 mp_model->mutable_variable(
var)->clear_objective_coefficient();
342 if (num_removed > 0) {
344 " near zero terms with largest magnitude of ", largest_removed,
350 const MPModelProto& mp_model,
353 for (
const MPGeneralConstraintProto& general_constraint :
354 mp_model.general_constraint()) {
355 switch (general_constraint.general_constraint_case()) {
356 case MPGeneralConstraintProto::kIndicatorConstraint:
358 case MPGeneralConstraintProto::kAndConstraint:
360 case MPGeneralConstraintProto::kOrConstraint:
363 SOLVER_LOG(logger,
"General constraints of type ",
364 general_constraint.general_constraint_case(),
365 " are not supported.");
371 const double threshold = params.mip_max_valid_magnitude();
372 const int num_variables = mp_model.variable_size();
373 for (
int i = 0; i < num_variables; ++i) {
374 const MPVariableProto&
var = mp_model.variable(i);
375 if ((std::isfinite(
var.lower_bound()) &&
376 std::abs(
var.lower_bound()) > threshold) ||
377 (std::isfinite(
var.upper_bound()) &&
378 std::abs(
var.upper_bound()) > threshold)) {
379 SOLVER_LOG(logger,
"Variable bounds are too large [",
var.lower_bound(),
380 ",",
var.upper_bound(),
"]");
383 if (std::abs(
var.objective_coefficient()) > threshold) {
384 SOLVER_LOG(logger,
"Objective coefficient is too large: ",
385 var.objective_coefficient());
391 const int num_constraints = mp_model.constraint_size();
392 for (
int c = 0; c < num_constraints; ++c) {
393 const MPConstraintProto&
ct = mp_model.constraint(c);
394 if ((std::isfinite(
ct.lower_bound()) &&
395 std::abs(
ct.lower_bound()) > threshold) ||
396 (std::isfinite(
ct.upper_bound()) &&
397 std::abs(
ct.upper_bound()) > threshold)) {
398 SOLVER_LOG(logger,
"Constraint bounds are too large [",
ct.lower_bound(),
399 ",",
ct.upper_bound(),
"]");
402 for (
const double coeff :
ct.coefficient()) {
403 if (std::abs(coeff) > threshold) {
404 SOLVER_LOG(logger,
"Constraint coefficient is too large: ", coeff);
415 const int num_variables = mp_model->variable_size();
416 std::vector<double> var_scaling(num_variables, 1.0);
418 int initial_num_integers = 0;
419 for (
int i = 0; i < num_variables; ++i) {
420 if (mp_model->variable(i).is_integer()) ++initial_num_integers;
422 VLOG(1) <<
"Initial num integers: " << initial_num_integers;
425 const double tolerance = 1e-6;
426 std::vector<int> constraint_queue;
428 const int num_constraints = mp_model->constraint_size();
429 std::vector<int> constraint_to_num_non_integer(num_constraints, 0);
430 std::vector<std::vector<int>> var_to_constraints(num_variables);
431 for (
int i = 0; i < num_constraints; ++i) {
432 const MPConstraintProto& mp_constraint = mp_model->constraint(i);
434 for (
const int var : mp_constraint.var_index()) {
435 if (!mp_model->variable(
var).is_integer()) {
436 var_to_constraints[
var].push_back(i);
437 constraint_to_num_non_integer[i]++;
440 if (constraint_to_num_non_integer[i] == 1) {
441 constraint_queue.push_back(i);
444 VLOG(1) <<
"Initial constraint queue: " << constraint_queue.size() <<
" / "
447 int num_detected = 0;
448 double max_scaling = 0.0;
449 auto scale_and_mark_as_integer = [&](
int var,
double scaling)
mutable {
451 CHECK(!mp_model->variable(
var).is_integer());
452 CHECK_EQ(var_scaling[
var], 1.0);
453 if (scaling != 1.0) {
454 VLOG(2) <<
"Scaled " <<
var <<
" by " << scaling;
458 max_scaling =
std::max(max_scaling, scaling);
462 var_scaling[
var] = scaling;
463 mp_model->mutable_variable(
var)->set_is_integer(
true);
466 for (
const int ct_index : var_to_constraints[
var]) {
467 constraint_to_num_non_integer[ct_index]--;
468 if (constraint_to_num_non_integer[ct_index] == 1) {
469 constraint_queue.push_back(ct_index);
474 int num_fail_due_to_rhs = 0;
475 int num_fail_due_to_large_multiplier = 0;
476 int num_processed_constraints = 0;
477 while (!constraint_queue.empty()) {
478 const int top_ct_index = constraint_queue.back();
479 constraint_queue.pop_back();
483 if (constraint_to_num_non_integer[top_ct_index] == 0)
continue;
486 const MPConstraintProto&
ct = mp_model->constraint(top_ct_index);
487 if (
ct.lower_bound() + tolerance <
ct.upper_bound())
continue;
489 ++num_processed_constraints;
501 double multiplier = 1.0;
502 const double max_multiplier = 1e4;
504 for (
int i = 0; i <
ct.var_index().size(); ++i) {
505 if (!mp_model->variable(
ct.var_index(i)).is_integer()) {
507 var =
ct.var_index(i);
508 var_coeff =
ct.coefficient(i);
513 multiplier *
ct.coefficient(i) / var_scaling[
ct.var_index(i)];
516 if (multiplier == 0 || multiplier > max_multiplier) {
522 if (multiplier == 0 || multiplier > max_multiplier) {
523 ++num_fail_due_to_large_multiplier;
528 const double rhs =
ct.lower_bound();
529 if (std::abs(std::round(rhs * multiplier) - rhs * multiplier) >
530 tolerance * multiplier) {
531 ++num_fail_due_to_rhs;
541 double best_scaling = std::abs(var_coeff * multiplier);
542 for (
const int ct_index : var_to_constraints[
var]) {
543 if (ct_index == top_ct_index)
continue;
544 if (constraint_to_num_non_integer[ct_index] != 1)
continue;
547 const MPConstraintProto&
ct = mp_model->constraint(top_ct_index);
548 if (
ct.lower_bound() + tolerance <
ct.upper_bound())
continue;
550 const double multiplier = GetIntegralityMultiplier(
551 *mp_model, var_scaling,
var, ct_index, tolerance);
552 if (multiplier != 0.0 && multiplier < best_scaling) {
553 best_scaling = multiplier;
557 scale_and_mark_as_integer(
var, best_scaling);
565 int num_in_inequalities = 0;
566 int num_to_be_handled = 0;
567 for (
int var = 0;
var < num_variables; ++
var) {
568 if (mp_model->variable(
var).is_integer())
continue;
571 if (var_to_constraints[
var].empty())
continue;
574 for (
const int ct_index : var_to_constraints[
var]) {
575 if (constraint_to_num_non_integer[ct_index] != 1) {
582 std::vector<double> scaled_coeffs;
583 for (
const int ct_index : var_to_constraints[
var]) {
584 const double multiplier = GetIntegralityMultiplier(
585 *mp_model, var_scaling,
var, ct_index, tolerance);
586 if (multiplier == 0.0) {
590 scaled_coeffs.push_back(multiplier);
599 double scaling = scaled_coeffs[0];
600 for (
const double c : scaled_coeffs) {
603 CHECK_GT(scaling, 0.0);
604 for (
const double c : scaled_coeffs) {
605 const double fraction = c / scaling;
606 if (std::abs(std::round(fraction) - fraction) > tolerance) {
619 mp_model->variable(
var).upper_bound()}) {
620 if (!std::isfinite(
bound))
continue;
621 if (std::abs(std::round(
bound * scaling) -
bound * scaling) >
622 tolerance * scaling) {
634 ++num_in_inequalities;
635 scale_and_mark_as_integer(
var, scaling);
637 VLOG(1) <<
"num_new_integer: " << num_detected
638 <<
" num_processed_constraints: " << num_processed_constraints
639 <<
" num_rhs_fail: " << num_fail_due_to_rhs
640 <<
" num_multiplier_fail: " << num_fail_due_to_large_multiplier;
642 if (num_to_be_handled > 0) {
643 SOLVER_LOG(logger,
"Missed ", num_to_be_handled,
644 " potential implied integer.");
647 const int num_integers = initial_num_integers + num_detected;
648 SOLVER_LOG(logger,
"Num integers: ", num_integers,
"/", num_variables,
649 " (implied: ", num_detected,
650 " in_inequalities: ", num_in_inequalities,
651 " max_scaling: ", max_scaling,
")",
652 (num_integers == num_variables ?
" [IP] " :
" [MIP] "));
654 ApplyVarScaling(var_scaling, mp_model);
661 struct ConstraintScaler {
663 ConstraintProto* AddConstraint(
const MPModelProto& mp_model,
664 const MPConstraintProto& mp_constraint,
665 CpModelProto* cp_model);
679 ConstraintProto* ConstraintScaler::AddConstraint(
680 const MPModelProto& mp_model,
const MPConstraintProto& mp_constraint,
681 CpModelProto* cp_model) {
682 if (mp_constraint.lower_bound() == -
kInfinity &&
683 mp_constraint.upper_bound() ==
kInfinity) {
687 auto* constraint = cp_model->add_constraints();
688 constraint->set_name(mp_constraint.name());
689 auto* arg = constraint->mutable_linear();
697 const int num_coeffs = mp_constraint.coefficient_size();
698 for (
int i = 0; i < num_coeffs; ++i) {
699 const auto& var_proto = cp_model->variables(mp_constraint.var_index(i));
700 const int64_t lb = var_proto.domain(0);
701 const int64_t ub = var_proto.domain(var_proto.domain_size() - 1);
702 if (lb == 0 && ub == 0)
continue;
704 const double coeff = mp_constraint.coefficient(i);
705 if (coeff == 0.0)
continue;
713 double relative_coeff_error;
714 double scaled_sum_error;
718 if (scaling_factor == 0.0) {
722 LOG(DFATAL) <<
"Scaling factor of zero while scaling constraint: "
723 << mp_constraint.ShortDebugString();
733 const double scaled_value =
coefficients[i] * scaling_factor;
734 const int64_t
value =
static_cast<int64_t
>(std::round(scaled_value)) / gcd;
737 arg->add_coeffs(
value);
757 const Fractional scaled_lb = std::ceil(lb * scaling_factor);
765 arg->add_domain(
CeilRatio(IntegerValue(
static_cast<int64_t
>(scaled_lb)),
770 const Fractional scaled_ub = std::floor(ub * scaling_factor);
778 arg->add_domain(
FloorRatio(IntegerValue(
static_cast<int64_t
>(scaled_ub)),
787 double FindFractionalScaling(
const std::vector<double>&
coefficients,
789 double multiplier = 1.0;
792 multiplier * tolerance);
793 if (multiplier == 0.0)
break;
803 const std::vector<double>&
upper_bounds, int64_t max_absolute_activity,
804 double wanted_absolute_activity_precision,
double* relative_coeff_error,
805 double* scaled_sum_error) {
809 if (scaling_factor == 0.0)
return scaling_factor;
816 double x =
std::min(scaling_factor, 1.0);
817 for (; x <= scaling_factor; x *= 2) {
819 relative_coeff_error, scaled_sum_error);
820 if (*scaled_sum_error < wanted_absolute_activity_precision * x)
break;
832 const double integer_factor = FindFractionalScaling(
coefficients, 1e-8);
833 if (integer_factor != 0 && integer_factor < scaling_factor) {
834 double local_relative_coeff_error;
835 double local_scaled_sum_error;
837 integer_factor, &local_relative_coeff_error,
838 &local_scaled_sum_error);
839 if (local_scaled_sum_error * scaling_factor <=
840 *scaled_sum_error * integer_factor ||
841 local_scaled_sum_error <
842 wanted_absolute_activity_precision * integer_factor) {
843 *relative_coeff_error = local_relative_coeff_error;
844 *scaled_sum_error = local_scaled_sum_error;
845 scaling_factor = integer_factor;
849 return scaling_factor;
853 const MPModelProto& mp_model,
854 CpModelProto* cp_model,
856 CHECK(cp_model !=
nullptr);
858 cp_model->set_name(mp_model.name());
872 const int64_t kMaxVariableBound =
873 static_cast<int64_t
>(params.mip_max_bound());
875 int num_truncated_bounds = 0;
876 int num_small_domains = 0;
877 const int64_t kSmallDomainSize = 1000;
878 const double kWantedPrecision = params.mip_wanted_precision();
881 const int num_variables = mp_model.variable_size();
882 for (
int i = 0; i < num_variables; ++i) {
883 const MPVariableProto& mp_var = mp_model.variable(i);
884 IntegerVariableProto* cp_var = cp_model->add_variables();
885 cp_var->set_name(mp_var.name());
893 if (mp_var.lower_bound() >
static_cast<double>(kMaxVariableBound) ||
894 mp_var.upper_bound() <
static_cast<double>(-kMaxVariableBound)) {
895 SOLVER_LOG(logger,
"Error: variable ", mp_var,
896 " is outside [-mip_max_bound..mip_max_bound]");
901 for (
const bool lower : {
true,
false}) {
902 const double bound =
lower ? mp_var.lower_bound() : mp_var.upper_bound();
903 if (std::abs(
bound) + kWantedPrecision >=
904 static_cast<double>(kMaxVariableBound)) {
905 ++num_truncated_bounds;
906 cp_var->add_domain(
bound < 0 ? -kMaxVariableBound : kMaxVariableBound);
912 static_cast<int64_t
>(
lower ? std::ceil(
bound - kWantedPrecision)
913 : std::floor(
bound + kWantedPrecision)));
916 if (cp_var->domain(0) > cp_var->domain(1)) {
917 LOG(WARNING) <<
"Variable #" << i <<
" cannot take integer value. "
918 << mp_var.ShortDebugString();
924 if (!mp_var.is_integer()) {
925 const double diff = mp_var.upper_bound() - mp_var.lower_bound();
926 if (diff > kWantedPrecision && diff < kSmallDomainSize) {
932 if (num_truncated_bounds > 0) {
933 SOLVER_LOG(logger,
"Warning: ", num_truncated_bounds,
934 " bounds were truncated to ", kMaxVariableBound,
".");
936 if (num_small_domains > 0) {
937 SOLVER_LOG(logger,
"Warning: ", num_small_domains,
938 " continuous variable domain with fewer than ", kSmallDomainSize,
942 ConstraintScaler scaler;
943 const int64_t kScalingTarget = int64_t{1}
944 << params.mip_max_activity_exponent();
945 scaler.wanted_precision = kWantedPrecision;
946 scaler.scaling_target = kScalingTarget;
949 for (
const MPConstraintProto& mp_constraint : mp_model.constraint()) {
950 scaler.AddConstraint(mp_model, mp_constraint, cp_model);
952 for (
const MPGeneralConstraintProto& general_constraint :
953 mp_model.general_constraint()) {
954 switch (general_constraint.general_constraint_case()) {
955 case MPGeneralConstraintProto::kIndicatorConstraint: {
956 const auto& indicator_constraint =
957 general_constraint.indicator_constraint();
958 const MPConstraintProto& mp_constraint =
959 indicator_constraint.constraint();
960 ConstraintProto*
ct =
961 scaler.AddConstraint(mp_model, mp_constraint, cp_model);
962 if (
ct ==
nullptr)
continue;
965 const int var = indicator_constraint.var_index();
966 const int value = indicator_constraint.var_value();
970 case MPGeneralConstraintProto::kAndConstraint: {
971 const auto& and_constraint = general_constraint.and_constraint();
972 const std::string&
name = general_constraint.name();
974 ConstraintProto* ct_pos = cp_model->add_constraints();
975 ct_pos->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_pos"));
976 ct_pos->add_enforcement_literal(and_constraint.resultant_var_index());
977 *ct_pos->mutable_bool_and()->mutable_literals() =
978 and_constraint.var_index();
980 ConstraintProto* ct_neg = cp_model->add_constraints();
981 ct_neg->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_neg"));
982 ct_neg->add_enforcement_literal(
983 NegatedRef(and_constraint.resultant_var_index()));
984 for (
const int var_index : and_constraint.var_index()) {
985 ct_neg->mutable_bool_or()->add_literals(
NegatedRef(var_index));
989 case MPGeneralConstraintProto::kOrConstraint: {
990 const auto& or_constraint = general_constraint.or_constraint();
991 const std::string&
name = general_constraint.name();
993 ConstraintProto* ct_pos = cp_model->add_constraints();
994 ct_pos->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_pos"));
995 ct_pos->add_enforcement_literal(or_constraint.resultant_var_index());
996 *ct_pos->mutable_bool_or()->mutable_literals() =
997 or_constraint.var_index();
999 ConstraintProto* ct_neg = cp_model->add_constraints();
1000 ct_neg->set_name(
name.empty() ?
"" : absl::StrCat(
name,
"_neg"));
1001 ct_neg->add_enforcement_literal(
1002 NegatedRef(or_constraint.resultant_var_index()));
1003 for (
const int var_index : or_constraint.var_index()) {
1004 ct_neg->mutable_bool_and()->add_literals(
NegatedRef(var_index));
1009 LOG(ERROR) <<
"Can't convert general constraints of type "
1010 << general_constraint.general_constraint_case()
1011 <<
" to CpModelProto.";
1017 SOLVER_LOG(logger,
"Maximum constraint coefficient relative error: ",
1018 scaler.max_relative_coeff_error);
1019 SOLVER_LOG(logger,
"Maximum constraint worst-case activity error: ",
1020 scaler.max_absolute_rhs_error,
1021 (scaler.max_absolute_rhs_error > params.mip_check_precision()
1022 ?
" [Potentially IMPRECISE]"
1025 "Maximum constraint scaling factor: ", scaler.max_scaling_factor);
1030 auto* float_objective = cp_model->mutable_floating_point_objective();
1031 float_objective->set_maximize(mp_model.maximize());
1032 float_objective->set_offset(mp_model.objective_offset());
1033 for (
int i = 0; i < num_variables; ++i) {
1034 const MPVariableProto& mp_var = mp_model.variable(i);
1035 if (mp_var.objective_coefficient() != 0.0) {
1036 float_objective->add_vars(i);
1037 float_objective->add_coeffs(mp_var.objective_coefficient());
1042 if (float_objective->offset() == 0 && float_objective->vars().empty()) {
1043 cp_model->clear_floating_point_objective();
1050 int AppendSumOfLiteral(absl::Span<const int> literals, MPConstraintProto* out) {
1052 for (
const int ref : literals) {
1054 out->add_coefficient(1);
1055 out->add_var_index(ref);
1057 out->add_coefficient(-1);
1068 MPModelProto* output) {
1069 CHECK(output !=
nullptr);
1073 const int num_vars =
input.variables().size();
1074 for (
int v = 0; v < num_vars; ++v) {
1075 if (
input.variables(v).domain().size() != 2) {
1076 VLOG(1) <<
"Cannot convert " <<
input.variables(v).ShortDebugString();
1080 MPVariableProto*
var = output->add_variable();
1081 var->set_is_integer(
true);
1082 var->set_lower_bound(
input.variables(v).domain(0));
1083 var->set_upper_bound(
input.variables(v).domain(1));
1087 if (
input.has_objective()) {
1088 double factor =
input.objective().scaling_factor();
1089 if (factor == 0.0) factor = 1.0;
1090 const int num_terms =
input.objective().vars().size();
1091 for (
int i = 0; i < num_terms; ++i) {
1092 const int var =
input.objective().vars(i);
1093 if (
var < 0)
return false;
1094 CHECK_EQ(output->variable(
var).objective_coefficient(), 0.0);
1095 output->mutable_variable(
var)->set_objective_coefficient(
1096 factor *
input.objective().coeffs(i));
1098 output->set_objective_offset(factor *
input.objective().offset());
1099 }
else if (
input.has_floating_point_objective()) {
1100 const int num_terms =
input.floating_point_objective().vars().size();
1101 for (
int i = 0; i < num_terms; ++i) {
1102 const int var =
input.floating_point_objective().vars(i);
1103 if (
var < 0)
return false;
1104 CHECK_EQ(output->variable(
var).objective_coefficient(), 0.0);
1105 output->mutable_variable(
var)->set_objective_coefficient(
1106 input.floating_point_objective().coeffs(i));
1108 output->set_objective_offset(
input.floating_point_objective().offset());
1110 if (output->objective_offset() == 0.0) {
1111 output->clear_objective_offset();
1115 const int num_constraints =
input.constraints().size();
1116 std::vector<int> tmp_literals;
1117 for (
int c = 0; c < num_constraints; ++c) {
1118 const ConstraintProto&
ct =
input.constraints(c);
1119 if (!
ct.enforcement_literal().empty() &&
1120 (
ct.constraint_case() != ConstraintProto::kBoolAnd &&
1121 ct.constraint_case() != ConstraintProto::kLinear)) {
1123 VLOG(1) <<
"Cannot convert constraint: " <<
ct.DebugString();
1126 switch (
ct.constraint_case()) {
1127 case ConstraintProto::kExactlyOne: {
1128 MPConstraintProto* out = output->add_constraint();
1129 const int shift = AppendSumOfLiteral(
ct.exactly_one().literals(), out);
1130 out->set_lower_bound(1 - shift);
1131 out->set_upper_bound(1 - shift);
1134 case ConstraintProto::kAtMostOne: {
1135 MPConstraintProto* out = output->add_constraint();
1136 const int shift = AppendSumOfLiteral(
ct.at_most_one().literals(), out);
1138 out->set_upper_bound(1 - shift);
1141 case ConstraintProto::kBoolOr: {
1142 MPConstraintProto* out = output->add_constraint();
1143 const int shift = AppendSumOfLiteral(
ct.bool_or().literals(), out);
1144 out->set_lower_bound(1 - shift);
1148 case ConstraintProto::kBoolAnd: {
1149 tmp_literals.clear();
1150 for (
const int ref :
ct.enforcement_literal()) {
1153 for (
const int ref :
ct.bool_and().literals()) {
1154 MPConstraintProto* out = output->add_constraint();
1155 tmp_literals.push_back(ref);
1156 const int shift = AppendSumOfLiteral(tmp_literals, out);
1157 out->set_lower_bound(1 - shift);
1159 tmp_literals.pop_back();
1163 case ConstraintProto::kLinear: {
1164 if (
ct.linear().domain().size() != 2) {
1165 VLOG(1) <<
"Cannot convert constraint: " <<
ct.ShortDebugString();
1170 int64_t min_activity = 0;
1171 int64_t max_activity = 0;
1172 const int num_terms =
ct.linear().vars().size();
1173 for (
int i = 0; i < num_terms; ++i) {
1174 const int var =
ct.linear().vars(i);
1175 if (
var < 0)
return false;
1176 DCHECK_EQ(
input.variables(
var).domain().size(), 2);
1177 const int64_t coeff =
ct.linear().coeffs(i);
1179 min_activity += coeff *
input.variables(
var).domain(0);
1180 max_activity += coeff *
input.variables(
var).domain(1);
1182 min_activity += coeff *
input.variables(
var).domain(1);
1183 max_activity += coeff *
input.variables(
var).domain(0);
1187 if (
ct.enforcement_literal().empty()) {
1188 MPConstraintProto* out_ct = output->add_constraint();
1189 if (min_activity <
ct.linear().domain(0)) {
1190 out_ct->set_lower_bound(
ct.linear().domain(0));
1194 if (max_activity >
ct.linear().domain(1)) {
1195 out_ct->set_upper_bound(
ct.linear().domain(1));
1199 for (
int i = 0; i < num_terms; ++i) {
1200 const int var =
ct.linear().vars(i);
1201 if (
var < 0)
return false;
1202 out_ct->add_var_index(
var);
1203 out_ct->add_coefficient(
ct.linear().coeffs(i));
1208 std::vector<MPConstraintProto*> out_cts;
1209 if (
ct.linear().domain(1) < max_activity) {
1210 MPConstraintProto* high_out_ct = output->add_constraint();
1211 high_out_ct->set_lower_bound(-
kInfinity);
1212 int64_t ub =
ct.linear().domain(1);
1213 const int64_t coeff = max_activity -
ct.linear().domain(1);
1214 for (
const int lit :
ct.enforcement_literal()) {
1217 high_out_ct->add_var_index(lit);
1218 high_out_ct->add_coefficient(coeff);
1222 high_out_ct->add_coefficient(-coeff);
1225 high_out_ct->set_upper_bound(ub);
1226 out_cts.push_back(high_out_ct);
1228 if (
ct.linear().domain(0) > min_activity) {
1229 MPConstraintProto* low_out_ct = output->add_constraint();
1231 int64_t lb =
ct.linear().domain(0);
1232 int64_t coeff = min_activity -
ct.linear().domain(0);
1233 for (
const int lit :
ct.enforcement_literal()) {
1236 low_out_ct->add_var_index(lit);
1237 low_out_ct->add_coefficient(coeff);
1241 low_out_ct->add_coefficient(-coeff);
1244 low_out_ct->set_lower_bound(lb);
1245 out_cts.push_back(low_out_ct);
1247 for (MPConstraintProto* out_ct : out_cts) {
1248 for (
int i = 0; i < num_terms; ++i) {
1249 const int var =
ct.linear().vars(i);
1250 if (
var < 0)
return false;
1251 out_ct->add_var_index(
var);
1252 out_ct->add_coefficient(
ct.linear().coeffs(i));
1258 VLOG(1) <<
"Cannot convert constraint: " <<
ct.DebugString();
1267 const std::vector<std::pair<int, double>>& objective,
1268 double objective_offset,
bool maximize,
1271 cp_model->clear_objective();
1278 double min_magnitude = std::numeric_limits<double>::infinity();
1279 double max_magnitude = 0.0;
1280 double l1_norm = 0.0;
1281 for (
const auto& [
var, coeff] : objective) {
1282 const auto& var_proto = cp_model->variables(
var);
1283 const int64_t lb = var_proto.domain(0);
1284 const int64_t ub = var_proto.domain(var_proto.domain_size() - 1);
1286 if (lb != 0) objective_offset += lb * coeff;
1294 min_magnitude =
std::min(min_magnitude, std::abs(coeff));
1295 max_magnitude =
std::max(max_magnitude, std::abs(coeff));
1296 l1_norm += std::abs(coeff);
1299 if (
coefficients.empty() && objective_offset == 0.0)
return true;
1302 const double average_magnitude =
1304 SOLVER_LOG(logger,
"[Scaling] Floating point objective has ",
1305 coefficients.size(),
" terms with magnitude in [", min_magnitude,
1306 ", ", max_magnitude,
"] average = ", average_magnitude);
1310 const int64_t max_absolute_activity = int64_t{1}
1311 << params.mip_max_activity_exponent();
1313 std::max(params.mip_wanted_precision(), params.absolute_gap_limit());
1315 double relative_coeff_error;
1316 double scaled_sum_error;
1320 if (scaling_factor == 0.0) {
1321 LOG(ERROR) <<
"Scaling factor of zero while scaling objective! This "
1322 "likely indicate an infinite coefficient in the objective.";
1329 SOLVER_LOG(logger,
"[Scaling] Objective coefficient relative error: ",
1330 relative_coeff_error);
1331 SOLVER_LOG(logger,
"[Scaling] Objective worst-case absolute error: ",
1332 scaled_sum_error / scaling_factor);
1334 "[Scaling] Objective scaling factor: ", scaling_factor / gcd);
1338 "[Scaling] Warning: the worst-case absolute error is greater "
1339 "than the wanted precision (",
1341 "). Try to increase mip_max_activity_exponent (default = ",
1342 params.mip_max_activity_exponent(),
1343 ") or reduced your variables range and/or objective "
1344 "coefficient. We will continue the solve, but the final "
1345 "objective value might be off.");
1351 auto* objective_proto = cp_model->mutable_objective();
1352 const int64_t mult = maximize ? -1 : 1;
1353 objective_proto->set_offset(objective_offset * scaling_factor / gcd * mult);
1354 objective_proto->set_scaling_factor(1.0 / scaling_factor * gcd * mult);
1356 const int64_t
value =
1357 static_cast<int64_t
>(std::round(
coefficients[i] * scaling_factor)) /
1361 objective_proto->add_coeffs(
value * mult);
1365 if (scaled_sum_error == 0.0) {
1366 objective_proto->set_scaling_was_exact(
true);
1373 LinearBooleanProblem* problem) {
1374 CHECK(problem !=
nullptr);
1376 problem->set_name(mp_model.name());
1377 const int num_variables = mp_model.variable_size();
1378 problem->set_num_variables(num_variables);
1382 for (
int var_id(0); var_id < num_variables; ++var_id) {
1383 const MPVariableProto& mp_var = mp_model.variable(var_id);
1384 problem->add_var_names(mp_var.name());
1389 bool is_binary = mp_var.is_integer();
1393 if (lb <= -1.0) is_binary =
false;
1394 if (ub >= 2.0) is_binary =
false;
1397 if (lb <= 0.0 && ub >= 1.0) {
1399 }
else if (lb <= 1.0 && ub >= 1.0) {
1401 LinearBooleanConstraint* constraint = problem->add_constraints();
1402 constraint->set_lower_bound(1);
1403 constraint->set_upper_bound(1);
1404 constraint->add_literals(var_id + 1);
1405 constraint->add_coefficients(1);
1406 }
else if (lb <= 0.0 && ub >= 0.0) {
1408 LinearBooleanConstraint* constraint = problem->add_constraints();
1409 constraint->set_lower_bound(0);
1410 constraint->set_upper_bound(0);
1411 constraint->add_literals(var_id + 1);
1412 constraint->add_coefficients(1);
1421 LOG(WARNING) <<
"The variable #" << var_id <<
" with name "
1422 << mp_var.name() <<
" is not binary. "
1423 <<
"lb: " << lb <<
" ub: " << ub;
1430 double max_relative_error = 0.0;
1431 double max_bound_error = 0.0;
1433 double relative_error = 0.0;
1434 double scaling_factor = 0.0;
1438 for (
const MPConstraintProto& mp_constraint : mp_model.constraint()) {
1439 LinearBooleanConstraint* constraint = problem->add_constraints();
1440 constraint->set_name(mp_constraint.name());
1444 const int num_coeffs = mp_constraint.coefficient_size();
1445 for (
int i = 0; i < num_coeffs; ++i) {
1452 max_relative_error =
std::max(relative_error, max_relative_error);
1455 double bound_error = 0.0;
1456 for (
int i = 0; i < num_coeffs; ++i) {
1457 const double scaled_value = mp_constraint.coefficient(i) * scaling_factor;
1458 bound_error += std::abs(round(scaled_value) - scaled_value);
1459 const int64_t
value =
static_cast<int64_t
>(round(scaled_value)) / gcd;
1461 constraint->add_literals(mp_constraint.var_index(i) + 1);
1462 constraint->add_coefficients(
value);
1465 max_bound_error =
std::max(max_bound_error, bound_error);
1472 const Fractional lb = mp_constraint.lower_bound();
1474 if (lb * scaling_factor >
static_cast<double>(kInt64Max)) {
1475 LOG(WARNING) <<
"A constraint is trivially unsatisfiable.";
1478 if (lb * scaling_factor > -
static_cast<double>(kInt64Max)) {
1480 constraint->set_lower_bound(
1481 static_cast<int64_t
>(round(lb * scaling_factor - bound_error)) /
1485 const Fractional ub = mp_constraint.upper_bound();
1487 if (ub * scaling_factor < -
static_cast<double>(kInt64Max)) {
1488 LOG(WARNING) <<
"A constraint is trivially unsatisfiable.";
1491 if (ub * scaling_factor <
static_cast<double>(kInt64Max)) {
1493 constraint->set_upper_bound(
1494 static_cast<int64_t
>(round(ub * scaling_factor + bound_error)) /
1501 LOG(INFO) <<
"Maximum constraint relative error: " << max_relative_error;
1502 LOG(INFO) <<
"Maximum constraint bound error: " << max_bound_error;
1507 for (
int var_id = 0; var_id < num_variables; ++var_id) {
1508 const MPVariableProto& mp_var = mp_model.variable(var_id);
1509 coefficients.push_back(mp_var.objective_coefficient());
1514 max_relative_error =
std::max(relative_error, max_relative_error);
1517 LOG(INFO) <<
"objective relative error: " << relative_error;
1518 LOG(INFO) <<
"objective scaling factor: " << scaling_factor / gcd;
1520 LinearObjective* objective = problem->mutable_objective();
1521 objective->set_offset(mp_model.objective_offset() * scaling_factor / gcd);
1525 objective->set_scaling_factor(1.0 / scaling_factor * gcd);
1526 for (
int var_id = 0; var_id < num_variables; ++var_id) {
1527 const MPVariableProto& mp_var = mp_model.variable(var_id);
1528 const int64_t
value =
1529 static_cast<int64_t
>(
1530 round(mp_var.objective_coefficient() * scaling_factor)) /
1533 objective->add_literals(var_id + 1);
1534 objective->add_coefficients(
value);
1542 const double kRelativeTolerance = 1e-8;
1543 if (max_relative_error > kRelativeTolerance) {
1544 LOG(WARNING) <<
"The relative error during double -> int64_t conversion "
1554 for (
int i = 0; i < problem.num_variables(); ++i) {
1561 if (problem.var_names_size() != 0) {
1562 CHECK_EQ(problem.var_names_size(), problem.num_variables());
1563 for (
int i = 0; i < problem.num_variables(); ++i) {
1568 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
1572 for (
int i = 0; i < constraint.literals_size(); ++i) {
1573 const int literal = constraint.literals(i);
1574 const double coeff = constraint.coefficients(i);
1575 const ColIndex variable_index = ColIndex(abs(
literal) - 1);
1585 constraint.has_lower_bound() ? constraint.lower_bound() - sum
1587 constraint.has_upper_bound() ? constraint.upper_bound() - sum
1594 const LinearObjective& objective = problem.objective();
1595 const double scaling_factor = objective.scaling_factor();
1596 for (
int i = 0; i < objective.literals_size(); ++i) {
1597 const int literal = objective.literals(i);
1598 const double coeff =
1599 static_cast<double>(objective.coefficients(i)) * scaling_factor;
1600 const ColIndex variable_index = ColIndex(abs(
literal) - 1);
1616 const CpModelProto& model_proto_with_floating_point_objective,
1617 const CpObjectiveProto& integer_objective,
1618 const int64_t inner_integer_objective_lower_bound) {
1621 const CpModelProto&
proto = model_proto_with_floating_point_objective;
1622 for (
int i = 0; i <
proto.variables().size(); ++i) {
1623 const auto& domain =
proto.variables(i).domain();
1625 static_cast<double>(domain[domain.size() - 1]));
1630 const FloatObjectiveProto& float_obj =
proto.floating_point_objective();
1633 for (
int i = 0; i < float_obj.vars().size(); ++i) {
1634 const glop::ColIndex
col(float_obj.vars(i));
1642 ct,
static_cast<double>(inner_integer_objective_lower_bound),
1643 std::numeric_limits<double>::infinity());
1644 for (
int i = 0; i < integer_objective.vars().size(); ++i) {
1646 static_cast<double>(integer_objective.coeffs(i)));
1654 glop::GlopParameters glop_parameters;
1655 glop_parameters.set_max_number_of_iterations(100 *
proto.variables().size());
1656 glop_parameters.set_change_status_to_imprecise(
false);
1664 return float_obj.maximize() ? std::numeric_limits<double>::infinity()
1665 : -std::numeric_limits<double>::infinity();
Fractional GetObjectiveValue() const
ABSL_MUST_USE_RESULT ProblemStatus Solve(const LinearProgram &lp)
void SetParameters(const GlopParameters ¶meters)
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
void SetConstraintName(RowIndex row, absl::string_view name)
void SetObjectiveOffset(Fractional objective_offset)
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
void SetVariableName(ColIndex col, absl::string_view name)
const DenseRow & objective_coefficients() const
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
ColIndex CreateNewVariable()
void SetVariableType(ColIndex col, VariableType type)
void SetObjectiveCoefficient(ColIndex col, Fractional value)
RowIndex CreateNewConstraint()
void SetMaximizationProblem(bool maximize)
constexpr double kInfinity
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
bool ConvertCpModelProtoToMPModelProto(const CpModelProto &input, MPModelProto *output)
bool RefIsPositive(int ref)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem &problem, glop::LinearProgram *lp)
bool ConvertBinaryMPModelProtoToBooleanProblem(const MPModelProto &mp_model, LinearBooleanProblem *problem)
void RemoveNearZeroTerms(const SatParameters ¶ms, MPModelProto *mp_model, SolverLogger *logger)
bool ConvertMPModelProtoToCpModelProto(const SatParameters ¶ms, const MPModelProto &mp_model, CpModelProto *cp_model, SolverLogger *logger)
bool MPModelProtoValidationBeforeConversion(const SatParameters ¶ms, const MPModelProto &mp_model, SolverLogger *logger)
bool ScaleAndSetObjective(const SatParameters ¶ms, const std::vector< std::pair< int, double >> &objective, double objective_offset, bool maximize, CpModelProto *cp_model, SolverLogger *logger)
int64_t FindRationalFactor(double x, int64_t limit, double tolerance)
void ChangeOptimizationDirection(LinearBooleanProblem *problem)
bool MakeBoundsOfIntegerVariablesInteger(const SatParameters ¶ms, MPModelProto *mp_model, SolverLogger *logger)
double ComputeTrueObjectiveLowerBound(const CpModelProto &model_proto_with_floating_point_objective, const CpObjectiveProto &integer_objective, const int64_t inner_integer_objective_lower_bound)
std::vector< double > ScaleContinuousVariables(double scaling, double max_bound, MPModelProto *mp_model)
double FindBestScalingAndComputeErrors(const std::vector< double > &coefficients, const std::vector< double > &lower_bounds, const std::vector< double > &upper_bounds, int64_t max_absolute_activity, double wanted_absolute_activity_precision, double *relative_coeff_error, double *scaled_sum_error)
std::vector< double > DetectImpliedIntegers(MPModelProto *mp_model, SolverLogger *logger)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
void ComputeScalingErrors(const std::vector< double > &input, const std::vector< double > &lb, const std::vector< double > &ub, double scaling_factor, double *max_relative_coeff_error, double *max_scaled_sum_error)
int64_t CapProd(int64_t x, int64_t y)
int64_t ComputeGcdOfRoundedDoubles(const std::vector< double > &x, double scaling_factor)
double GetBestScalingOfDoublesToInt64(const std::vector< double > &input, const std::vector< double > &lb, const std::vector< double > &ub, int64_t max_absolute_sum)
static int input(yyscan_t yyscanner)
double max_scaling_factor
double max_relative_coeff_error
std::vector< double > lower_bounds
std::vector< int > var_indices
std::vector< double > upper_bounds
std::vector< double > coefficients
double max_absolute_rhs_error
constexpr double kInfinity
#define SOLVER_LOG(logger,...)
#define VLOG(verboselevel)