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