OR-Tools  9.3
sat/lp_utils.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 <cstdlib>
20#include <limits>
21#include <string>
22#include <utility>
23#include <vector>
24
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"
37#include "ortools/sat/integer.h"
38#include "ortools/sat/sat_parameters.pb.h"
42
43namespace operations_research {
44namespace sat {
45
46using glop::ColIndex;
48using glop::kInfinity;
49using glop::RowIndex;
50
51using operations_research::MPConstraintProto;
52using operations_research::MPModelProto;
53using operations_research::MPVariableProto;
54
55namespace {
56
57void 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]);
64 }
65}
66
67void 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);
79
80 // TODO(user): Make bounds of integer variable integer.
81 }
82 for (MPConstraintProto& mp_constraint : *mp_model->mutable_constraint()) {
83 ScaleConstraint(var_scaling, &mp_constraint);
84 }
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());
92 break;
93 case MPGeneralConstraintProto::kAndConstraint:
94 case MPGeneralConstraintProto::kOrConstraint:
95 // These constraints have only Boolean variables and no constants. They
96 // don't need scaling.
97 break;
98 default:
99 LOG(FATAL) << "Scaling unsupported for general constraint of type "
100 << general_constraint.general_constraint_case();
101 }
102 }
103}
104
105} // namespace
106
107std::vector<double> ScaleContinuousVariables(double scaling, double max_bound,
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 const double lb = mp_model->variable(i).lower_bound();
114 const double ub = mp_model->variable(i).upper_bound();
115 const double magnitude = std::max(std::abs(lb), std::abs(ub));
116 if (magnitude == 0 || magnitude > max_bound) continue;
117 var_scaling[i] = std::min(scaling, max_bound / magnitude);
118 }
119 ApplyVarScaling(var_scaling, mp_model);
120 return var_scaling;
121}
122
123// This uses the best rational approximation of x via continuous fractions. It
124// is probably not the best implementation, but according to the unit test, it
125// seems to do the job.
126int FindRationalFactor(double x, int limit, double tolerance) {
127 const double initial_x = x;
128 x = std::abs(x);
129 x -= std::floor(x);
130 int q = 1;
131 int prev_q = 0;
132 while (q < limit) {
133 if (std::abs(q * initial_x - std::round(q * initial_x)) < q * tolerance) {
134 return q;
135 }
136 x = 1 / x;
137 const int new_q = prev_q + static_cast<int>(std::floor(x)) * q;
138 prev_q = q;
139 q = new_q;
140 x -= std::floor(x);
141 }
142 return 0;
143}
144
145namespace {
146
147// Returns a factor such that factor * var only need to take integer values to
148// satisfy the given constraint. Return 0.0 if we didn't find such factor.
149//
150// Precondition: var must be the only non-integer in the given constraint.
151double GetIntegralityMultiplier(const MPModelProto& mp_model,
152 const std::vector<double>& var_scaling, int var,
153 int ct_index, double tolerance) {
154 DCHECK(!mp_model.variable(var).is_integer());
155 const MPConstraintProto& ct = mp_model.constraint(ct_index);
156 double multiplier = 1.0;
157 double var_coeff = 0.0;
158 const double max_multiplier = 1e4;
159 for (int i = 0; i < ct.var_index().size(); ++i) {
160 if (var == ct.var_index(i)) {
161 var_coeff = ct.coefficient(i);
162 continue;
163 }
164
165 DCHECK(mp_model.variable(ct.var_index(i)).is_integer());
166 // This actually compute the smallest multiplier to make all other
167 // terms in the constraint integer.
168 const double coeff =
169 multiplier * ct.coefficient(i) / var_scaling[ct.var_index(i)];
170 multiplier *=
171 FindRationalFactor(coeff, /*limit=*/100, multiplier * tolerance);
172 if (multiplier == 0 || multiplier > max_multiplier) return 0.0;
173 }
174 DCHECK_NE(var_coeff, 0.0);
175
176 // The constraint bound need to be infinite or integer.
177 for (const double bound : {ct.lower_bound(), ct.upper_bound()}) {
178 if (!std::isfinite(bound)) continue;
179 if (std::abs(std::round(bound * multiplier) - bound * multiplier) >
180 tolerance * multiplier) {
181 return 0.0;
182 }
183 }
184 return std::abs(multiplier * var_coeff);
185}
186
187} // namespace
188
189bool MakeBoundsOfIntegerVariablesInteger(const SatParameters& params,
190 MPModelProto* mp_model,
191 SolverLogger* logger) {
192 const int num_variables = mp_model->variable_size();
193 const double tolerance = params.mip_wanted_precision();
194 int64_t num_changes = 0;
195 for (int i = 0; i < num_variables; ++i) {
196 const MPVariableProto& mp_var = mp_model->variable(i);
197 if (!mp_var.is_integer()) continue;
198
199 const double lb = mp_var.lower_bound();
200 const double new_lb = std::isfinite(lb) ? std::ceil(lb - tolerance) : lb;
201 if (lb != new_lb) {
202 ++num_changes;
203 mp_model->mutable_variable(i)->set_lower_bound(new_lb);
204 }
205
206 const double ub = mp_var.upper_bound();
207 const double new_ub = std::isfinite(ub) ? std::floor(ub + tolerance) : ub;
208 if (ub != new_ub) {
209 ++num_changes;
210 mp_model->mutable_variable(i)->set_upper_bound(new_ub);
211 }
212
213 if (new_ub < new_lb) {
214 SOLVER_LOG(logger, "Empty domain for integer variable #", i, ": [", lb,
215 ",", ub, "]");
216 return false;
217 }
218 }
219
220 if (num_changes > 0) {
221 SOLVER_LOG(logger, "Changed ", num_changes,
222 " bounds of integer variables to integer values");
223 }
224 return true;
225}
226
227void RemoveNearZeroTerms(const SatParameters& params, MPModelProto* mp_model,
228 SolverLogger* logger) {
229 const int num_variables = mp_model->variable_size();
230
231 // Compute for each variable its current maximum magnitude. Note that we will
232 // only scale variable with a coefficient >= 1, so it is safe to use this
233 // bound.
234 std::vector<double> max_bounds(num_variables);
235 for (int i = 0; i < num_variables; ++i) {
236 double value = std::abs(mp_model->variable(i).lower_bound());
237 value = std::max(value, std::abs(mp_model->variable(i).upper_bound()));
238 value = std::min(value, params.mip_max_bound());
239 max_bounds[i] = value;
240 }
241
242 // We want the maximum absolute error while setting coefficients to zero to
243 // not exceed our mip wanted precision. So for a binary variable we might set
244 // to zero coefficient around 1e-7. But for large domain, we need lower coeff
245 // than that, around 1e-12 with the default params.mip_max_bound(). This also
246 // depends on the size of the constraint.
247 int64_t num_removed = 0;
248 double largest_removed = 0.0;
249 const int num_constraints = mp_model->constraint_size();
250 for (int c = 0; c < num_constraints; ++c) {
251 MPConstraintProto* ct = mp_model->mutable_constraint(c);
252 int new_size = 0;
253 const int size = ct->var_index().size();
254 if (size == 0) continue;
255 const double threshold =
256 params.mip_wanted_precision() / static_cast<double>(size);
257 for (int i = 0; i < size; ++i) {
258 const int var = ct->var_index(i);
259 const double coeff = ct->coefficient(i);
260 if (std::abs(coeff) * max_bounds[var] < threshold) {
261 largest_removed = std::max(largest_removed, std::abs(coeff));
262 continue;
263 }
264 ct->set_var_index(new_size, var);
265 ct->set_coefficient(new_size, coeff);
266 ++new_size;
267 }
268 num_removed += size - new_size;
269 ct->mutable_var_index()->Truncate(new_size);
270 ct->mutable_coefficient()->Truncate(new_size);
271 }
272
273 if (num_removed > 0) {
274 SOLVER_LOG(logger, "Removed ", num_removed,
275 " near zero terms with largest magnitude of ", largest_removed,
276 ".");
277 }
278}
279
280bool MPModelProtoValidationBeforeConversion(const SatParameters& params,
281 const MPModelProto& mp_model,
282 SolverLogger* logger) {
283 // Abort if there is constraint type we don't currently support.
284 for (const MPGeneralConstraintProto& general_constraint :
285 mp_model.general_constraint()) {
286 switch (general_constraint.general_constraint_case()) {
287 case MPGeneralConstraintProto::kIndicatorConstraint:
288 break;
289 case MPGeneralConstraintProto::kAndConstraint:
290 break;
291 case MPGeneralConstraintProto::kOrConstraint:
292 break;
293 default:
294 SOLVER_LOG(logger, "General constraints of type ",
295 general_constraint.general_constraint_case(),
296 " are not supported.");
297 return false;
298 }
299 }
300
301 // Abort if finite variable bounds or objective is too large.
302 const double threshold = params.mip_max_valid_magnitude();
303 const int num_variables = mp_model.variable_size();
304 for (int i = 0; i < num_variables; ++i) {
305 const MPVariableProto& var = mp_model.variable(i);
306 if ((std::isfinite(var.lower_bound()) &&
307 std::abs(var.lower_bound()) > threshold) ||
308 (std::isfinite(var.upper_bound()) &&
309 std::abs(var.upper_bound()) > threshold)) {
310 SOLVER_LOG(logger, "Variable bounds are too large [", var.lower_bound(),
311 ",", var.upper_bound(), "]");
312 return false;
313 }
314 if (std::abs(var.objective_coefficient()) > threshold) {
315 SOLVER_LOG(logger, "Objective coefficient is too large: ",
316 var.objective_coefficient());
317 return false;
318 }
319 }
320
321 // Abort if finite constraint bounds or coefficients are too large.
322 const int num_constraints = mp_model.constraint_size();
323 for (int c = 0; c < num_constraints; ++c) {
324 const MPConstraintProto& ct = mp_model.constraint(c);
325 if ((std::isfinite(ct.lower_bound()) &&
326 std::abs(ct.lower_bound()) > threshold) ||
327 (std::isfinite(ct.upper_bound()) &&
328 std::abs(ct.upper_bound()) > threshold)) {
329 SOLVER_LOG(logger, "Constraint bounds are too large [", ct.lower_bound(),
330 ",", ct.upper_bound(), "]");
331 return false;
332 }
333 for (const double coeff : ct.coefficient()) {
334 if (std::abs(coeff) > threshold) {
335 SOLVER_LOG(logger, "Constraint coefficient is too large: ", coeff);
336 return false;
337 }
338 }
339 }
340
341 return true;
342}
343
344std::vector<double> DetectImpliedIntegers(MPModelProto* mp_model,
345 SolverLogger* logger) {
346 const int num_variables = mp_model->variable_size();
347 std::vector<double> var_scaling(num_variables, 1.0);
348
349 int initial_num_integers = 0;
350 for (int i = 0; i < num_variables; ++i) {
351 if (mp_model->variable(i).is_integer()) ++initial_num_integers;
352 }
353 VLOG(1) << "Initial num integers: " << initial_num_integers;
354
355 // We will process all equality constraints with exactly one non-integer.
356 const double tolerance = 1e-6;
357 std::vector<int> constraint_queue;
358
359 const int num_constraints = mp_model->constraint_size();
360 std::vector<int> constraint_to_num_non_integer(num_constraints, 0);
361 std::vector<std::vector<int>> var_to_constraints(num_variables);
362 for (int i = 0; i < num_constraints; ++i) {
363 const MPConstraintProto& mp_constraint = mp_model->constraint(i);
364
365 for (const int var : mp_constraint.var_index()) {
366 if (!mp_model->variable(var).is_integer()) {
367 var_to_constraints[var].push_back(i);
368 constraint_to_num_non_integer[i]++;
369 }
370 }
371 if (constraint_to_num_non_integer[i] == 1) {
372 constraint_queue.push_back(i);
373 }
374 }
375 VLOG(1) << "Initial constraint queue: " << constraint_queue.size() << " / "
376 << num_constraints;
377
378 int num_detected = 0;
379 double max_scaling = 0.0;
380 auto scale_and_mark_as_integer = [&](int var, double scaling) mutable {
381 CHECK_NE(var, -1);
382 CHECK(!mp_model->variable(var).is_integer());
383 CHECK_EQ(var_scaling[var], 1.0);
384 if (scaling != 1.0) {
385 VLOG(2) << "Scaled " << var << " by " << scaling;
386 }
387
388 ++num_detected;
389 max_scaling = std::max(max_scaling, scaling);
390
391 // Scale the variable right away and mark it as implied integer.
392 // Note that the constraints will be scaled later.
393 var_scaling[var] = scaling;
394 mp_model->mutable_variable(var)->set_is_integer(true);
395
396 // Update the queue of constraints with a single non-integer.
397 for (const int ct_index : var_to_constraints[var]) {
398 constraint_to_num_non_integer[ct_index]--;
399 if (constraint_to_num_non_integer[ct_index] == 1) {
400 constraint_queue.push_back(ct_index);
401 }
402 }
403 };
404
405 int num_fail_due_to_rhs = 0;
406 int num_fail_due_to_large_multiplier = 0;
407 int num_processed_constraints = 0;
408 while (!constraint_queue.empty()) {
409 const int top_ct_index = constraint_queue.back();
410 constraint_queue.pop_back();
411
412 // The non integer variable was already made integer by one other
413 // constraint.
414 if (constraint_to_num_non_integer[top_ct_index] == 0) continue;
415
416 // Ignore non-equality here.
417 const MPConstraintProto& ct = mp_model->constraint(top_ct_index);
418 if (ct.lower_bound() + tolerance < ct.upper_bound()) continue;
419
420 ++num_processed_constraints;
421
422 // This will be set to the unique non-integer term of this constraint.
423 int var = -1;
424 double var_coeff;
425
426 // We are looking for a "multiplier" so that the unique non-integer term
427 // in this constraint (i.e. var * var_coeff) times this multiplier is an
428 // integer.
429 //
430 // If this is set to zero or becomes too large, we fail to detect a new
431 // implied integer and ignore this constraint.
432 double multiplier = 1.0;
433 const double max_multiplier = 1e4;
434
435 for (int i = 0; i < ct.var_index().size(); ++i) {
436 if (!mp_model->variable(ct.var_index(i)).is_integer()) {
437 CHECK_EQ(var, -1);
438 var = ct.var_index(i);
439 var_coeff = ct.coefficient(i);
440 } else {
441 // This actually compute the smallest multiplier to make all other
442 // terms in the constraint integer.
443 const double coeff =
444 multiplier * ct.coefficient(i) / var_scaling[ct.var_index(i)];
445 multiplier *=
446 FindRationalFactor(coeff, /*limit=*/100, multiplier * tolerance);
447 if (multiplier == 0 || multiplier > max_multiplier) {
448 break;
449 }
450 }
451 }
452
453 if (multiplier == 0 || multiplier > max_multiplier) {
454 ++num_fail_due_to_large_multiplier;
455 continue;
456 }
457
458 // These "rhs" fail could be handled by shifting the variable.
459 const double rhs = ct.lower_bound();
460 if (std::abs(std::round(rhs * multiplier) - rhs * multiplier) >
461 tolerance * multiplier) {
462 ++num_fail_due_to_rhs;
463 continue;
464 }
465
466 // We want to multiply the variable so that it is integer. We know that
467 // coeff * multiplier is an integer, so we just multiply by that.
468 //
469 // But if a variable appear in more than one equality, we want to find the
470 // smallest integrality factor! See diameterc-msts-v40a100d5i.mps
471 // for an instance of this.
472 double best_scaling = std::abs(var_coeff * multiplier);
473 for (const int ct_index : var_to_constraints[var]) {
474 if (ct_index == top_ct_index) continue;
475 if (constraint_to_num_non_integer[ct_index] != 1) continue;
476
477 // Ignore non-equality here.
478 const MPConstraintProto& ct = mp_model->constraint(top_ct_index);
479 if (ct.lower_bound() + tolerance < ct.upper_bound()) continue;
480
481 const double multiplier = GetIntegralityMultiplier(
482 *mp_model, var_scaling, var, ct_index, tolerance);
483 if (multiplier != 0.0 && multiplier < best_scaling) {
484 best_scaling = multiplier;
485 }
486 }
487
488 scale_and_mark_as_integer(var, best_scaling);
489 }
490
491 // Process continuous variables that only appear as the unique non integer
492 // in a set of non-equality constraints.
493 //
494 // Note that turning to integer such variable cannot in turn trigger new
495 // integer detection, so there is no point doing that in a loop.
496 int num_in_inequalities = 0;
497 int num_to_be_handled = 0;
498 for (int var = 0; var < num_variables; ++var) {
499 if (mp_model->variable(var).is_integer()) continue;
500
501 // This should be presolved and not happen.
502 if (var_to_constraints[var].empty()) continue;
503
504 bool ok = true;
505 for (const int ct_index : var_to_constraints[var]) {
506 if (constraint_to_num_non_integer[ct_index] != 1) {
507 ok = false;
508 break;
509 }
510 }
511 if (!ok) continue;
512
513 std::vector<double> scaled_coeffs;
514 for (const int ct_index : var_to_constraints[var]) {
515 const double multiplier = GetIntegralityMultiplier(
516 *mp_model, var_scaling, var, ct_index, tolerance);
517 if (multiplier == 0.0) {
518 ok = false;
519 break;
520 }
521 scaled_coeffs.push_back(multiplier);
522 }
523 if (!ok) continue;
524
525 // The situation is a bit tricky here, we have a bunch of coeffs c_i, and we
526 // know that X * c_i can take integer value without changing the constraint
527 // i meaning.
528 //
529 // For now we take the min, and scale only if all c_i / min are integer.
530 double scaling = scaled_coeffs[0];
531 for (const double c : scaled_coeffs) {
532 scaling = std::min(scaling, c);
533 }
534 CHECK_GT(scaling, 0.0);
535 for (const double c : scaled_coeffs) {
536 const double fraction = c / scaling;
537 if (std::abs(std::round(fraction) - fraction) > tolerance) {
538 ok = false;
539 break;
540 }
541 }
542 if (!ok) {
543 // TODO(user): be smarter! we should be able to handle these cases.
544 ++num_to_be_handled;
545 continue;
546 }
547
548 // Tricky, we also need the bound of the scaled variable to be integer.
549 for (const double bound : {mp_model->variable(var).lower_bound(),
550 mp_model->variable(var).upper_bound()}) {
551 if (!std::isfinite(bound)) continue;
552 if (std::abs(std::round(bound * scaling) - bound * scaling) >
553 tolerance * scaling) {
554 ok = false;
555 break;
556 }
557 }
558 if (!ok) {
559 // TODO(user): If we scale more we migth be able to turn it into an
560 // integer.
561 ++num_to_be_handled;
562 continue;
563 }
564
565 ++num_in_inequalities;
566 scale_and_mark_as_integer(var, scaling);
567 }
568 VLOG(1) << "num_new_integer: " << num_detected
569 << " num_processed_constraints: " << num_processed_constraints
570 << " num_rhs_fail: " << num_fail_due_to_rhs
571 << " num_multiplier_fail: " << num_fail_due_to_large_multiplier;
572
573 if (num_to_be_handled > 0) {
574 SOLVER_LOG(logger, "Missed ", num_to_be_handled,
575 " potential implied integer.");
576 }
577
578 const int num_integers = initial_num_integers + num_detected;
579 SOLVER_LOG(logger, "Num integers: ", num_integers, "/", num_variables,
580 " (implied: ", num_detected,
581 " in_inequalities: ", num_in_inequalities,
582 " max_scaling: ", max_scaling, ")",
583 (num_integers == num_variables ? " [IP] " : " [MIP] "));
584
585 ApplyVarScaling(var_scaling, mp_model);
586 return var_scaling;
587}
588
589namespace {
590
591// We use a class to reuse the temporary memory.
592struct ConstraintScaler {
593 // Scales an individual constraint.
594 ConstraintProto* AddConstraint(const MPModelProto& mp_model,
595 const MPConstraintProto& mp_constraint,
596 CpModelProto* cp_model);
597
600 double max_scaling_factor = 0.0;
601
602 double wanted_precision = 1e-6;
603 int64_t scaling_target = int64_t{1} << 50;
604 std::vector<int> var_indices;
605 std::vector<double> coefficients;
606 std::vector<double> lower_bounds;
607 std::vector<double> upper_bounds;
608};
609
610namespace {
611
612// TODO(user): unit test this.
613double FindFractionalScaling(const std::vector<double>& coefficients,
614 double tolerance) {
615 double multiplier = 1.0;
616 for (const double coeff : coefficients) {
617 multiplier *= FindRationalFactor(multiplier * coeff, /*limit=*/1e8,
618 multiplier * tolerance);
619 if (multiplier == 0.0) break;
620 }
621 return multiplier;
622}
623
624// TODO(user): unit test this and move to fp_utils.
625double FindBestScalingAndComputeErrors(
626 const std::vector<double>& coefficients,
627 const std::vector<double>& lower_bounds,
628 const std::vector<double>& upper_bounds, int64_t max_absolute_activity,
629 double wanted_absolute_activity_precision, double* relative_coeff_error,
630 double* scaled_sum_error) {
631 // Starts by computing the highest possible factor.
632 double scaling_factor = GetBestScalingOfDoublesToInt64(
633 coefficients, lower_bounds, upper_bounds, max_absolute_activity);
634 if (scaling_factor == 0.0) return scaling_factor;
635
636 // Returns the smallest factor of the form 2^i that gives us a relative sum
637 // error of wanted_absolute_activity_precision and still make sure we will
638 // have no integer overflow.
639 //
640 // TODO(user): Make this faster.
641 double x = std::min(scaling_factor, 1.0);
642 for (; x <= scaling_factor; x *= 2) {
644 relative_coeff_error, scaled_sum_error);
645 if (*scaled_sum_error < wanted_absolute_activity_precision * x) break;
646 }
647 scaling_factor = x;
648
649 // Because we deal with an approximate input, scaling with a power of 2 might
650 // not be the best choice. It is also possible user used rational coeff and
651 // then converted them to double (1/2, 1/3, 4/5, etc...). This scaling will
652 // recover such rational input and might result in a smaller overall
653 // coefficient which is good.
654 //
655 // Note that if our current precisions is already above the requested one,
656 // we choose integer scaling if we get a better precision.
657 const double integer_factor = FindFractionalScaling(coefficients, 1e-8);
658 if (integer_factor != 0 && integer_factor < scaling_factor) {
659 double local_relative_coeff_error;
660 double local_scaled_sum_error;
662 integer_factor, &local_relative_coeff_error,
663 &local_scaled_sum_error);
664 if (local_scaled_sum_error * scaling_factor <=
665 *scaled_sum_error * integer_factor ||
666 local_scaled_sum_error <
667 wanted_absolute_activity_precision * integer_factor) {
668 *relative_coeff_error = local_relative_coeff_error;
669 *scaled_sum_error = local_scaled_sum_error;
670 scaling_factor = integer_factor;
671 }
672 }
673
674 return scaling_factor;
675}
676
677} // namespace
678
679ConstraintProto* 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) {
684 return nullptr;
685 }
686
687 auto* constraint = cp_model->add_constraints();
688 constraint->set_name(mp_constraint.name());
689 auto* arg = constraint->mutable_linear();
690
691 // First scale the coefficients of the constraints so that the constraint
692 // sum can always be computed without integer overflow.
693 var_indices.clear();
694 coefficients.clear();
695 lower_bounds.clear();
696 upper_bounds.clear();
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;
703
704 const double coeff = mp_constraint.coefficient(i);
705 if (coeff == 0.0) continue;
706
707 var_indices.push_back(mp_constraint.var_index(i));
708 coefficients.push_back(coeff);
709 lower_bounds.push_back(lb);
710 upper_bounds.push_back(ub);
711 }
712
713 double relative_coeff_error;
714 double scaled_sum_error;
715 const double scaling_factor = FindBestScalingAndComputeErrors(
717 wanted_precision, &relative_coeff_error, &scaled_sum_error);
718 if (scaling_factor == 0.0) {
719 // TODO(user): Report error properly instead of ignoring constraint. Note
720 // however that this likely indicate a coefficient of inf in the constraint,
721 // so we should probably abort before reaching here.
722 LOG(DFATAL) << "Scaling factor of zero while scaling constraint: "
723 << mp_constraint.ShortDebugString();
724 return nullptr;
725 }
726
727 const int64_t gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
729 std::max(relative_coeff_error, max_relative_coeff_error);
730 max_scaling_factor = std::max(scaling_factor / gcd, max_scaling_factor);
731
732 for (int i = 0; i < coefficients.size(); ++i) {
733 const double scaled_value = coefficients[i] * scaling_factor;
734 const int64_t value = static_cast<int64_t>(std::round(scaled_value)) / gcd;
735 if (value != 0) {
736 arg->add_vars(var_indices[i]);
737 arg->add_coeffs(value);
738 }
739 }
741 std::max(max_absolute_rhs_error, scaled_sum_error / scaling_factor);
742
743 // We relax the constraint bound by the absolute value of the wanted_precision
744 // before scaling. Note that this is needed because now that the scaled
745 // constraint activity is integer, we will floor/ceil these bound.
746 //
747 // It might make more sense to use a relative precision here for large bounds,
748 // but absolute is usually what is used in the MIP world. Also if the problem
749 // was a pure integer problem, and a user asked for sum == 10k, we want to
750 // stay exact here.
751 const Fractional lb = mp_constraint.lower_bound() - wanted_precision;
752 const Fractional ub = mp_constraint.upper_bound() + wanted_precision;
753
754 // Add the constraint bounds. Because we are sure the scaled constraint fit
755 // on an int64_t, if the scaled bounds are too large, the constraint is either
756 // always true or always false.
757 const Fractional scaled_lb = std::ceil(lb * scaling_factor);
758 if (lb == kInfinity || scaled_lb >= std::numeric_limits<int64_t>::max()) {
759 // Corner case: infeasible model.
760 arg->add_domain(std::numeric_limits<int64_t>::max());
761 } else if (lb == -kInfinity ||
762 scaled_lb <= std::numeric_limits<int64_t>::min()) {
763 arg->add_domain(std::numeric_limits<int64_t>::min());
764 } else {
765 arg->add_domain(CeilRatio(IntegerValue(static_cast<int64_t>(scaled_lb)),
766 IntegerValue(gcd))
767 .value());
768 }
769
770 const Fractional scaled_ub = std::floor(ub * scaling_factor);
771 if (ub == -kInfinity || scaled_ub <= std::numeric_limits<int64_t>::min()) {
772 // Corner case: infeasible model.
773 arg->add_domain(std::numeric_limits<int64_t>::min());
774 } else if (ub == kInfinity ||
775 scaled_ub >= std::numeric_limits<int64_t>::max()) {
776 arg->add_domain(std::numeric_limits<int64_t>::max());
777 } else {
778 arg->add_domain(FloorRatio(IntegerValue(static_cast<int64_t>(scaled_ub)),
779 IntegerValue(gcd))
780 .value());
781 }
782
783 return constraint;
784}
785
786} // namespace
787
788bool ConvertMPModelProtoToCpModelProto(const SatParameters& params,
789 const MPModelProto& mp_model,
790 CpModelProto* cp_model,
791 SolverLogger* logger) {
792 CHECK(cp_model != nullptr);
793 cp_model->Clear();
794 cp_model->set_name(mp_model.name());
795
796 // To make sure we cannot have integer overflow, we use this bound for any
797 // unbounded variable.
798 //
799 // TODO(user): This could be made larger if needed, so be smarter if we have
800 // MIP problem that we cannot "convert" because of this. Note however than we
801 // cannot go that much further because we need to make sure we will not run
802 // into overflow if we add a big linear combination of such variables. It
803 // should always be possible for a user to scale its problem so that all
804 // relevant quantities are a couple of millions. A LP/MIP solver have a
805 // similar condition in disguise because problem with a difference of more
806 // than 6 magnitudes between the variable values will likely run into numeric
807 // trouble.
808 const int64_t kMaxVariableBound =
809 static_cast<int64_t>(params.mip_max_bound());
810
811 int num_truncated_bounds = 0;
812 int num_small_domains = 0;
813 const int64_t kSmallDomainSize = 1000;
814 const double kWantedPrecision = params.mip_wanted_precision();
815
816 // Add the variables.
817 const int num_variables = mp_model.variable_size();
818 for (int i = 0; i < num_variables; ++i) {
819 const MPVariableProto& mp_var = mp_model.variable(i);
820 IntegerVariableProto* cp_var = cp_model->add_variables();
821 cp_var->set_name(mp_var.name());
822
823 // Deal with the corner case of a domain far away from zero.
824 //
825 // TODO(user): We could avoid these cases by shifting the domain of
826 // all variables to contain zero. This should also lead to a better scaling,
827 // but it has some complications with integer variables and require some
828 // post-solve.
829 if (mp_var.lower_bound() > static_cast<double>(kMaxVariableBound) ||
830 mp_var.upper_bound() < static_cast<double>(-kMaxVariableBound)) {
831 SOLVER_LOG(logger, "Error: variable ", mp_var.DebugString(),
832 " is outside [-mip_max_bound..mip_max_bound]");
833 return false;
834 }
835
836 // Note that we must process the lower bound first.
837 for (const bool lower : {true, false}) {
838 const double bound = lower ? mp_var.lower_bound() : mp_var.upper_bound();
839 if (std::abs(bound) >= static_cast<double>(kMaxVariableBound)) {
840 ++num_truncated_bounds;
841 cp_var->add_domain(bound < 0 ? -kMaxVariableBound : kMaxVariableBound);
842 continue;
843 }
844
845 // Note that the cast is "perfect" because we forbid large values.
846 cp_var->add_domain(
847 static_cast<int64_t>(lower ? std::ceil(bound - kWantedPrecision)
848 : std::floor(bound + kWantedPrecision)));
849 }
850
851 if (cp_var->domain(0) > cp_var->domain(1)) {
852 LOG(WARNING) << "Variable #" << i << " cannot take integer value. "
853 << mp_var.ShortDebugString();
854 return false;
855 }
856
857 // Notify if a continuous variable has a small domain as this is likely to
858 // make an all integer solution far from a continuous one.
859 if (!mp_var.is_integer() && cp_var->domain(0) != cp_var->domain(1) &&
860 cp_var->domain(1) - cp_var->domain(0) < kSmallDomainSize) {
861 ++num_small_domains;
862 }
863 }
864
865 if (num_truncated_bounds > 0) {
866 SOLVER_LOG(logger, "Warning: ", num_truncated_bounds,
867 " bounds were truncated to ", kMaxVariableBound, ".");
868 }
869 if (num_small_domains > 0) {
870 SOLVER_LOG(logger, "Warning: ", num_small_domains,
871 " continuous variable domain with fewer than ", kSmallDomainSize,
872 " values.");
873 }
874
875 ConstraintScaler scaler;
876 const int64_t kScalingTarget = int64_t{1}
877 << params.mip_max_activity_exponent();
878 scaler.wanted_precision = kWantedPrecision;
879 scaler.scaling_target = kScalingTarget;
880
881 // Add the constraints. We scale each of them individually.
882 for (const MPConstraintProto& mp_constraint : mp_model.constraint()) {
883 scaler.AddConstraint(mp_model, mp_constraint, cp_model);
884 }
885 for (const MPGeneralConstraintProto& general_constraint :
886 mp_model.general_constraint()) {
887 switch (general_constraint.general_constraint_case()) {
888 case MPGeneralConstraintProto::kIndicatorConstraint: {
889 const auto& indicator_constraint =
890 general_constraint.indicator_constraint();
891 const MPConstraintProto& mp_constraint =
892 indicator_constraint.constraint();
893 ConstraintProto* ct =
894 scaler.AddConstraint(mp_model, mp_constraint, cp_model);
895 if (ct == nullptr) continue;
896
897 // Add the indicator.
898 const int var = indicator_constraint.var_index();
899 const int value = indicator_constraint.var_value();
900 ct->add_enforcement_literal(value == 1 ? var : NegatedRef(var));
901 break;
902 }
903 case MPGeneralConstraintProto::kAndConstraint: {
904 const auto& and_constraint = general_constraint.and_constraint();
905 const std::string& name = general_constraint.name();
906
907 ConstraintProto* ct_pos = cp_model->add_constraints();
908 ct_pos->set_name(name.empty() ? "" : absl::StrCat(name, "_pos"));
909 ct_pos->add_enforcement_literal(and_constraint.resultant_var_index());
910 *ct_pos->mutable_bool_and()->mutable_literals() =
911 and_constraint.var_index();
912
913 ConstraintProto* ct_neg = cp_model->add_constraints();
914 ct_neg->set_name(name.empty() ? "" : absl::StrCat(name, "_neg"));
915 ct_neg->add_enforcement_literal(
916 NegatedRef(and_constraint.resultant_var_index()));
917 for (const int var_index : and_constraint.var_index()) {
918 ct_neg->mutable_bool_or()->add_literals(NegatedRef(var_index));
919 }
920 break;
921 }
922 case MPGeneralConstraintProto::kOrConstraint: {
923 const auto& or_constraint = general_constraint.or_constraint();
924 const std::string& name = general_constraint.name();
925
926 ConstraintProto* ct_pos = cp_model->add_constraints();
927 ct_pos->set_name(name.empty() ? "" : absl::StrCat(name, "_pos"));
928 ct_pos->add_enforcement_literal(or_constraint.resultant_var_index());
929 *ct_pos->mutable_bool_or()->mutable_literals() =
930 or_constraint.var_index();
931
932 ConstraintProto* ct_neg = cp_model->add_constraints();
933 ct_neg->set_name(name.empty() ? "" : absl::StrCat(name, "_neg"));
934 ct_neg->add_enforcement_literal(
935 NegatedRef(or_constraint.resultant_var_index()));
936 for (const int var_index : or_constraint.var_index()) {
937 ct_neg->mutable_bool_and()->add_literals(NegatedRef(var_index));
938 }
939 break;
940 }
941 default:
942 LOG(ERROR) << "Can't convert general constraints of type "
943 << general_constraint.general_constraint_case()
944 << " to CpModelProto.";
945 return false;
946 }
947 }
948
949 // Display the error/scaling on the constraints.
950 SOLVER_LOG(logger, "Maximum constraint coefficient relative error: ",
951 scaler.max_relative_coeff_error);
952 SOLVER_LOG(logger, "Maximum constraint worst-case activity error: ",
953 scaler.max_absolute_rhs_error,
954 (scaler.max_absolute_rhs_error > params.mip_check_precision()
955 ? " [Potentially IMPRECISE]"
956 : ""));
957 SOLVER_LOG(logger,
958 "Maximum constraint scaling factor: ", scaler.max_scaling_factor);
959
960 // Since cp_model support a floating point objective, we use that. This will
961 // allow us to scale the objective a bit later so we can potentially do more
962 // domain reduction first.
963 auto* float_objective = cp_model->mutable_floating_point_objective();
964 float_objective->set_maximize(mp_model.maximize());
965 float_objective->set_offset(mp_model.objective_offset());
966 for (int i = 0; i < num_variables; ++i) {
967 const MPVariableProto& mp_var = mp_model.variable(i);
968 if (mp_var.objective_coefficient() != 0.0) {
969 float_objective->add_vars(i);
970 float_objective->add_coeffs(mp_var.objective_coefficient());
971 }
972 }
973
974 // If the objective is fixed to zero, we consider there is none.
975 if (float_objective->offset() == 0 && float_objective->vars().empty()) {
976 cp_model->clear_floating_point_objective();
977 }
978 return true;
979}
980
982 MPModelProto* output) {
983 CHECK(output != nullptr);
984 output->Clear();
985
986 // Copy variables.
987 const int num_vars = input.variables().size();
988 for (int v = 0; v < num_vars; ++v) {
989 if (input.variables(v).domain().size() != 2) {
990 VLOG(1) << "Cannot convert " << input.variables(v).ShortDebugString();
991 return false;
992 }
993
994 MPVariableProto* var = output->add_variable();
995 var->set_is_integer(true);
996 var->set_lower_bound(input.variables(v).domain(0));
997 var->set_upper_bound(input.variables(v).domain(1));
998 }
999
1000 // Copy integer or float objective.
1001 if (input.has_objective()) {
1002 double factor = input.objective().scaling_factor();
1003 if (factor == 0.0) factor = 1.0;
1004 const int num_terms = input.objective().vars().size();
1005 for (int i = 0; i < num_terms; ++i) {
1006 const int var = input.objective().vars(i);
1007 if (var < 0) return false;
1008 output->mutable_variable(var)->set_objective_coefficient(
1009 factor * input.objective().coeffs(i));
1010 }
1011 output->set_objective_offset(factor * input.objective().offset());
1012 } else if (input.has_floating_point_objective()) {
1013 const int num_terms = input.floating_point_objective().vars().size();
1014 for (int i = 0; i < num_terms; ++i) {
1015 const int var = input.floating_point_objective().vars(i);
1016 if (var < 0) return false;
1017 output->mutable_variable(var)->set_objective_coefficient(
1018 input.floating_point_objective().coeffs(i));
1019 }
1020 output->set_objective_offset(input.floating_point_objective().offset());
1021 }
1022 if (output->objective_offset() == 0.0) {
1023 output->clear_objective_offset();
1024 }
1025
1026 // Copy constraint.
1027 const int num_constraints = input.constraints().size();
1028 for (int c = 0; c < num_constraints; ++c) {
1029 const ConstraintProto& ct = input.constraints(c);
1030 switch (ct.constraint_case()) {
1031 case ConstraintProto::kLinear: {
1032 if (ct.linear().domain().size() != 2) {
1033 VLOG(1) << "Cannot convert constraint: " << ct.DebugString();
1034 return false;
1035 }
1036
1037 MPConstraintProto* out_ct = output->add_constraint();
1038 out_ct->set_lower_bound(ct.linear().domain(0));
1039 out_ct->set_upper_bound(ct.linear().domain(1));
1040
1041 const int num_terms = ct.linear().vars().size();
1042 for (int i = 0; i < num_terms; ++i) {
1043 const int var = ct.linear().vars(i);
1044 if (var < 0) return false;
1045 out_ct->add_var_index(var);
1046 out_ct->add_coefficient(ct.linear().coeffs(i));
1047 }
1048 break;
1049 }
1050 default:
1051 VLOG(1) << "Cannot convert constraint: " << ct.DebugString();
1052 return false;
1053 }
1054 }
1055
1056 return true;
1057}
1058
1059bool ScaleAndSetObjective(const SatParameters& params,
1060 const std::vector<std::pair<int, double>>& objective,
1061 double objective_offset, bool maximize,
1062 CpModelProto* cp_model, SolverLogger* logger) {
1063 // Make sure the objective is currently empty.
1064 cp_model->clear_objective();
1065
1066 // We filter constant terms and compute some needed quantities.
1067 std::vector<int> var_indices;
1068 std::vector<double> coefficients;
1069 std::vector<double> lower_bounds;
1070 std::vector<double> upper_bounds;
1071 double min_magnitude = std::numeric_limits<double>::infinity();
1072 double max_magnitude = 0.0;
1073 double l1_norm = 0.0;
1074 for (const auto& [var, coeff] : objective) {
1075 const auto& var_proto = cp_model->variables(var);
1076 const int64_t lb = var_proto.domain(0);
1077 const int64_t ub = var_proto.domain(var_proto.domain_size() - 1);
1078 if (lb == ub) {
1079 if (lb != 0) objective_offset += lb * coeff;
1080 continue;
1081 }
1082 var_indices.push_back(var);
1083 coefficients.push_back(coeff);
1084 lower_bounds.push_back(lb);
1085 upper_bounds.push_back(ub);
1086
1087 min_magnitude = std::min(min_magnitude, std::abs(coeff));
1088 max_magnitude = std::max(max_magnitude, std::abs(coeff));
1089 l1_norm += std::abs(coeff);
1090 }
1091
1092 if (coefficients.empty() && objective_offset == 0.0) return true;
1093
1094 if (!coefficients.empty()) {
1095 const double average_magnitude =
1096 l1_norm / static_cast<double>(coefficients.size());
1097 SOLVER_LOG(logger, "[Scaling] Floating point objective has ",
1098 coefficients.size(), " terms with magnitude in [", min_magnitude,
1099 ", ", max_magnitude, "] average = ", average_magnitude);
1100 }
1101
1102 // These are the parameters used for scaling the objective.
1103 const int64_t max_absolute_activity = int64_t{1}
1104 << params.mip_max_activity_exponent();
1105 const double wanted_precision =
1106 std::max(params.mip_wanted_precision(), params.absolute_gap_limit());
1107
1108 double relative_coeff_error;
1109 double scaled_sum_error;
1110 const double scaling_factor = FindBestScalingAndComputeErrors(
1111 coefficients, lower_bounds, upper_bounds, max_absolute_activity,
1112 wanted_precision, &relative_coeff_error, &scaled_sum_error);
1113 if (scaling_factor == 0.0) {
1114 LOG(ERROR) << "Scaling factor of zero while scaling objective! This "
1115 "likely indicate an infinite coefficient in the objective.";
1116 return false;
1117 }
1118
1119 const int64_t gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
1120
1121 // Display the objective error/scaling.
1122 SOLVER_LOG(logger, "[Scaling] Objective coefficient relative error: ",
1123 relative_coeff_error);
1124 SOLVER_LOG(logger, "[Scaling] Objective worst-case absolute error: ",
1125 scaled_sum_error / scaling_factor);
1126 SOLVER_LOG(logger,
1127 "[Scaling] Objective scaling factor: ", scaling_factor / gcd);
1128
1129 // Note that here we set the scaling factor for the inverse operation of
1130 // getting the "true" objective value from the scaled one. Hence the
1131 // inverse.
1132 auto* objective_proto = cp_model->mutable_objective();
1133 const int64_t mult = maximize ? -1 : 1;
1134 objective_proto->set_offset(objective_offset * scaling_factor / gcd * mult);
1135 objective_proto->set_scaling_factor(1.0 / scaling_factor * gcd * mult);
1136 for (int i = 0; i < coefficients.size(); ++i) {
1137 const int64_t value =
1138 static_cast<int64_t>(std::round(coefficients[i] * scaling_factor)) /
1139 gcd;
1140 if (value != 0) {
1141 objective_proto->add_vars(var_indices[i]);
1142 objective_proto->add_coeffs(value * mult);
1143 }
1144 }
1145
1146 if (scaled_sum_error == 0.0) {
1147 objective_proto->set_scaling_was_exact(true);
1148 }
1149
1150 return true;
1151}
1152
1153bool ConvertBinaryMPModelProtoToBooleanProblem(const MPModelProto& mp_model,
1154 LinearBooleanProblem* problem) {
1155 CHECK(problem != nullptr);
1156 problem->Clear();
1157 problem->set_name(mp_model.name());
1158 const int num_variables = mp_model.variable_size();
1159 problem->set_num_variables(num_variables);
1160
1161 // Test if the variables are binary variables.
1162 // Add constraints for the fixed variables.
1163 for (int var_id(0); var_id < num_variables; ++var_id) {
1164 const MPVariableProto& mp_var = mp_model.variable(var_id);
1165 problem->add_var_names(mp_var.name());
1166
1167 // This will be changed to false as soon as we detect the variable to be
1168 // non-binary. This is done this way so we can display a nice error message
1169 // before aborting the function and returning false.
1170 bool is_binary = mp_var.is_integer();
1171
1172 const Fractional lb = mp_var.lower_bound();
1173 const Fractional ub = mp_var.upper_bound();
1174 if (lb <= -1.0) is_binary = false;
1175 if (ub >= 2.0) is_binary = false;
1176 if (is_binary) {
1177 // 4 cases.
1178 if (lb <= 0.0 && ub >= 1.0) {
1179 // Binary variable. Ok.
1180 } else if (lb <= 1.0 && ub >= 1.0) {
1181 // Fixed variable at 1.
1182 LinearBooleanConstraint* constraint = problem->add_constraints();
1183 constraint->set_lower_bound(1);
1184 constraint->set_upper_bound(1);
1185 constraint->add_literals(var_id + 1);
1186 constraint->add_coefficients(1);
1187 } else if (lb <= 0.0 && ub >= 0.0) {
1188 // Fixed variable at 0.
1189 LinearBooleanConstraint* constraint = problem->add_constraints();
1190 constraint->set_lower_bound(0);
1191 constraint->set_upper_bound(0);
1192 constraint->add_literals(var_id + 1);
1193 constraint->add_coefficients(1);
1194 } else {
1195 // No possible integer value!
1196 is_binary = false;
1197 }
1198 }
1199
1200 // Abort if the variable is not binary.
1201 if (!is_binary) {
1202 LOG(WARNING) << "The variable #" << var_id << " with name "
1203 << mp_var.name() << " is not binary. "
1204 << "lb: " << lb << " ub: " << ub;
1205 return false;
1206 }
1207 }
1208
1209 // Variables needed to scale the double coefficients into int64_t.
1210 const int64_t kInt64Max = std::numeric_limits<int64_t>::max();
1211 double max_relative_error = 0.0;
1212 double max_bound_error = 0.0;
1213 double max_scaling_factor = 0.0;
1214 double relative_error = 0.0;
1215 double scaling_factor = 0.0;
1216 std::vector<double> coefficients;
1217
1218 // Add all constraints.
1219 for (const MPConstraintProto& mp_constraint : mp_model.constraint()) {
1220 LinearBooleanConstraint* constraint = problem->add_constraints();
1221 constraint->set_name(mp_constraint.name());
1222
1223 // First scale the coefficients of the constraints.
1224 coefficients.clear();
1225 const int num_coeffs = mp_constraint.coefficient_size();
1226 for (int i = 0; i < num_coeffs; ++i) {
1227 coefficients.push_back(mp_constraint.coefficient(i));
1228 }
1229 GetBestScalingOfDoublesToInt64(coefficients, kInt64Max, &scaling_factor,
1230 &relative_error);
1231 const int64_t gcd =
1233 max_relative_error = std::max(relative_error, max_relative_error);
1234 max_scaling_factor = std::max(scaling_factor / gcd, max_scaling_factor);
1235
1236 double bound_error = 0.0;
1237 for (int i = 0; i < num_coeffs; ++i) {
1238 const double scaled_value = mp_constraint.coefficient(i) * scaling_factor;
1239 bound_error += std::abs(round(scaled_value) - scaled_value);
1240 const int64_t value = static_cast<int64_t>(round(scaled_value)) / gcd;
1241 if (value != 0) {
1242 constraint->add_literals(mp_constraint.var_index(i) + 1);
1243 constraint->add_coefficients(value);
1244 }
1245 }
1246 max_bound_error = std::max(max_bound_error, bound_error);
1247
1248 // Add the bounds. Note that we do not pass them to
1249 // GetBestScalingOfDoublesToInt64() because we know that the sum of absolute
1250 // coefficients of the constraint fit on an int64_t. If one of the scaled
1251 // bound overflows, we don't care by how much because in this case the
1252 // constraint is just trivial or unsatisfiable.
1253 const Fractional lb = mp_constraint.lower_bound();
1254 if (lb != -kInfinity) {
1255 if (lb * scaling_factor > static_cast<double>(kInt64Max)) {
1256 LOG(WARNING) << "A constraint is trivially unsatisfiable.";
1257 return false;
1258 }
1259 if (lb * scaling_factor > -static_cast<double>(kInt64Max)) {
1260 // Otherwise, the constraint is not needed.
1261 constraint->set_lower_bound(
1262 static_cast<int64_t>(round(lb * scaling_factor - bound_error)) /
1263 gcd);
1264 }
1265 }
1266 const Fractional ub = mp_constraint.upper_bound();
1267 if (ub != kInfinity) {
1268 if (ub * scaling_factor < -static_cast<double>(kInt64Max)) {
1269 LOG(WARNING) << "A constraint is trivially unsatisfiable.";
1270 return false;
1271 }
1272 if (ub * scaling_factor < static_cast<double>(kInt64Max)) {
1273 // Otherwise, the constraint is not needed.
1274 constraint->set_upper_bound(
1275 static_cast<int64_t>(round(ub * scaling_factor + bound_error)) /
1276 gcd);
1277 }
1278 }
1279 }
1280
1281 // Display the error/scaling without taking into account the objective first.
1282 LOG(INFO) << "Maximum constraint relative error: " << max_relative_error;
1283 LOG(INFO) << "Maximum constraint bound error: " << max_bound_error;
1284 LOG(INFO) << "Maximum constraint scaling factor: " << max_scaling_factor;
1285
1286 // Add the objective.
1287 coefficients.clear();
1288 for (int var_id = 0; var_id < num_variables; ++var_id) {
1289 const MPVariableProto& mp_var = mp_model.variable(var_id);
1290 coefficients.push_back(mp_var.objective_coefficient());
1291 }
1292 GetBestScalingOfDoublesToInt64(coefficients, kInt64Max, &scaling_factor,
1293 &relative_error);
1294 const int64_t gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
1295 max_relative_error = std::max(relative_error, max_relative_error);
1296
1297 // Display the objective error/scaling.
1298 LOG(INFO) << "objective relative error: " << relative_error;
1299 LOG(INFO) << "objective scaling factor: " << scaling_factor / gcd;
1300
1301 LinearObjective* objective = problem->mutable_objective();
1302 objective->set_offset(mp_model.objective_offset() * scaling_factor / gcd);
1303
1304 // Note that here we set the scaling factor for the inverse operation of
1305 // getting the "true" objective value from the scaled one. Hence the inverse.
1306 objective->set_scaling_factor(1.0 / scaling_factor * gcd);
1307 for (int var_id = 0; var_id < num_variables; ++var_id) {
1308 const MPVariableProto& mp_var = mp_model.variable(var_id);
1309 const int64_t value =
1310 static_cast<int64_t>(
1311 round(mp_var.objective_coefficient() * scaling_factor)) /
1312 gcd;
1313 if (value != 0) {
1314 objective->add_literals(var_id + 1);
1315 objective->add_coefficients(value);
1316 }
1317 }
1318
1319 // If the problem was a maximization one, we need to modify the objective.
1320 if (mp_model.maximize()) ChangeOptimizationDirection(problem);
1321
1322 // Test the precision of the conversion.
1323 const double kRelativeTolerance = 1e-8;
1324 if (max_relative_error > kRelativeTolerance) {
1325 LOG(WARNING) << "The relative error during double -> int64_t conversion "
1326 << "is too high!";
1327 return false;
1328 }
1329 return true;
1330}
1331
1332void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem& problem,
1333 glop::LinearProgram* lp) {
1334 lp->Clear();
1335 for (int i = 0; i < problem.num_variables(); ++i) {
1336 const ColIndex col = lp->CreateNewVariable();
1338 lp->SetVariableBounds(col, 0.0, 1.0);
1339 }
1340
1341 // Variables name are optional.
1342 if (problem.var_names_size() != 0) {
1343 CHECK_EQ(problem.var_names_size(), problem.num_variables());
1344 for (int i = 0; i < problem.num_variables(); ++i) {
1345 lp->SetVariableName(ColIndex(i), problem.var_names(i));
1346 }
1347 }
1348
1349 for (const LinearBooleanConstraint& constraint : problem.constraints()) {
1350 const RowIndex constraint_index = lp->CreateNewConstraint();
1351 lp->SetConstraintName(constraint_index, constraint.name());
1352 double sum = 0.0;
1353 for (int i = 0; i < constraint.literals_size(); ++i) {
1354 const int literal = constraint.literals(i);
1355 const double coeff = constraint.coefficients(i);
1356 const ColIndex variable_index = ColIndex(abs(literal) - 1);
1357 if (literal < 0) {
1358 sum += coeff;
1359 lp->SetCoefficient(constraint_index, variable_index, -coeff);
1360 } else {
1361 lp->SetCoefficient(constraint_index, variable_index, coeff);
1362 }
1363 }
1365 constraint_index,
1366 constraint.has_lower_bound() ? constraint.lower_bound() - sum
1367 : -kInfinity,
1368 constraint.has_upper_bound() ? constraint.upper_bound() - sum
1369 : kInfinity);
1370 }
1371
1372 // Objective.
1373 {
1374 double sum = 0.0;
1375 const LinearObjective& objective = problem.objective();
1376 const double scaling_factor = objective.scaling_factor();
1377 for (int i = 0; i < objective.literals_size(); ++i) {
1378 const int literal = objective.literals(i);
1379 const double coeff =
1380 static_cast<double>(objective.coefficients(i)) * scaling_factor;
1381 const ColIndex variable_index = ColIndex(abs(literal) - 1);
1382 if (literal < 0) {
1383 sum += coeff;
1384 lp->SetObjectiveCoefficient(variable_index, -coeff);
1385 } else {
1386 lp->SetObjectiveCoefficient(variable_index, coeff);
1387 }
1388 }
1389 lp->SetObjectiveOffset((sum + objective.offset()) * scaling_factor);
1390 lp->SetMaximizationProblem(scaling_factor < 0);
1391 }
1392
1393 lp->CleanUp();
1394}
1395
1397 const CpModelProto& model_proto_with_floating_point_objective,
1398 const CpObjectiveProto& integer_objective,
1399 const int64_t inner_integer_objective_lower_bound) {
1400 // Create an LP with the correct variable domain.
1402 const CpModelProto& proto = model_proto_with_floating_point_objective;
1403 for (int i = 0; i < proto.variables().size(); ++i) {
1404 const auto& domain = proto.variables(i).domain();
1405 lp.SetVariableBounds(lp.CreateNewVariable(), static_cast<double>(domain[0]),
1406 static_cast<double>(domain[domain.size() - 1]));
1407 }
1408
1409 // Add the original problem floating point objective.
1410 // This is user given, so we do need to deal with duplicate entries.
1411 const FloatObjectiveProto& float_obj = proto.floating_point_objective();
1412 lp.SetObjectiveOffset(float_obj.offset());
1413 lp.SetMaximizationProblem(float_obj.maximize());
1414 for (int i = 0; i < float_obj.vars().size(); ++i) {
1415 const glop::ColIndex col(float_obj.vars(i));
1416 const double old_value = lp.objective_coefficients()[col];
1417 lp.SetObjectiveCoefficient(col, old_value + float_obj.coeffs(i));
1418 }
1419
1420 // Add a single constraint "integer_objective >= lower_bound".
1421 const glop::RowIndex ct = lp.CreateNewConstraint();
1423 ct, static_cast<double>(inner_integer_objective_lower_bound),
1424 std::numeric_limits<double>::infinity());
1425 for (int i = 0; i < integer_objective.vars().size(); ++i) {
1426 lp.SetCoefficient(ct, glop::ColIndex(integer_objective.vars(i)),
1427 static_cast<double>(integer_objective.coeffs(i)));
1428 }
1429
1430 lp.CleanUp();
1431
1432 // This should be fast.
1433 glop::LPSolver solver;
1434 glop::GlopParameters glop_parameters;
1435 glop_parameters.set_max_deterministic_time(10);
1436 glop_parameters.set_change_status_to_imprecise(false);
1437 solver.SetParameters(glop_parameters);
1438 const glop::ProblemStatus& status = solver.Solve(lp);
1440 return solver.GetObjectiveValue();
1441 }
1442
1443 // Error. Hoperfully this shouldn't happen.
1444 return float_obj.maximize() ? std::numeric_limits<double>::infinity()
1445 : -std::numeric_limits<double>::infinity();
1446}
1447
1448} // namespace sat
1449} // 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_NE(val1, val2)
Definition: base/logging.h:892
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
#define CHECK_NE(val1, val2)
Definition: base/logging.h:704
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define VLOG(verboselevel)
Definition: base/logging.h:984
Fractional GetObjectiveValue() const
Definition: lp_solver.cc:491
ABSL_MUST_USE_RESULT ProblemStatus Solve(const LinearProgram &lp)
Definition: lp_solver.cc:130
void SetParameters(const GlopParameters &parameters)
Definition: lp_solver.cc:112
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:249
void SetConstraintName(RowIndex row, absl::string_view name)
Definition: lp_data.cc:245
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:331
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Definition: lp_data.cc:317
void SetVariableName(ColIndex col, absl::string_view name)
Definition: lp_data.cc:232
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:309
void SetVariableType(ColIndex col, VariableType type)
Definition: lp_data.cc:236
const DenseRow & objective_coefficients() const
Definition: lp_data.h:223
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:326
void SetMaximizationProblem(bool maximize)
Definition: lp_data.cc:343
CpModelProto proto
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:35
double lower
Definition: glpk_solver.cc:75
double lower_bound
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
const int ERROR
Definition: log_severity.h:32
const int FATAL
Definition: log_severity.h:32
ColIndex col
Definition: markowitz.cc:183
const double kInfinity
Definition: lp_types.h:84
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:98
bool ScaleAndSetObjective(const SatParameters &params, const std::vector< std::pair< int, double > > &objective, double objective_offset, bool maximize, CpModelProto *cp_model, SolverLogger *logger)
bool ConvertCpModelProtoToMPModelProto(const CpModelProto &input, MPModelProto *output)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:89
void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem &problem, glop::LinearProgram *lp)
bool ConvertBinaryMPModelProtoToBooleanProblem(const MPModelProto &mp_model, LinearBooleanProblem *problem)
void RemoveNearZeroTerms(const SatParameters &params, MPModelProto *mp_model, SolverLogger *logger)
bool ConvertMPModelProtoToCpModelProto(const SatParameters &params, const MPModelProto &mp_model, CpModelProto *cp_model, SolverLogger *logger)
bool MPModelProtoValidationBeforeConversion(const SatParameters &params, const MPModelProto &mp_model, SolverLogger *logger)
void ChangeOptimizationDirection(LinearBooleanProblem *problem)
bool MakeBoundsOfIntegerVariablesInteger(const SatParameters &params, 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)
std::vector< double > DetectImpliedIntegers(MPModelProto *mp_model, SolverLogger *logger)
int FindRationalFactor(double x, int limit, double tolerance)
Collection of objects used to extend the Constraint Solver library.
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)
Definition: fp_utils.cc:170
int64_t ComputeGcdOfRoundedDoubles(const std::vector< double > &x, double scaling_factor)
Definition: fp_utils.cc:200
double GetBestScalingOfDoublesToInt64(const std::vector< double > &input, const std::vector< double > &lb, const std::vector< double > &ub, int64_t max_absolute_sum)
Definition: fp_utils.cc:179
Literal literal
Definition: optimization.cc:89
static int input(yyscan_t yyscanner)
int64_t bound
double max_scaling_factor
int64_t scaling_target
double max_relative_coeff_error
std::vector< double > lower_bounds
double wanted_precision
std::vector< int > var_indices
std::vector< double > upper_bounds
std::vector< double > coefficients
double max_absolute_rhs_error
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
const double coeff