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