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