OR-Tools  8.0
sat/lp_utils.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 <limits>
21 #include <string>
22 #include <vector>
23 
24 #include "absl/strings/str_cat.h"
25 #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  for (MPConstraintProto& mp_constraint : *mp_model->mutable_constraint()) {
76  ScaleConstraint(var_scaling, &mp_constraint);
77  }
78  for (MPGeneralConstraintProto& general_constraint :
79  *mp_model->mutable_general_constraint()) {
80  switch (general_constraint.general_constraint_case()) {
81  case MPGeneralConstraintProto::kIndicatorConstraint:
82  ScaleConstraint(var_scaling,
83  general_constraint.mutable_indicator_constraint()
84  ->mutable_constraint());
85  break;
86  case MPGeneralConstraintProto::kAndConstraint:
87  case MPGeneralConstraintProto::kOrConstraint:
88  // These constraints have only Boolean variables and no constants. They
89  // don't need scaling.
90  break;
91  default:
92  LOG(FATAL) << "Scaling unsupported for general constraint of type "
93  << general_constraint.general_constraint_case();
94  }
95  }
96 }
97 
98 } // namespace
99 
100 std::vector<double> ScaleContinuousVariables(double scaling, double max_bound,
101  MPModelProto* mp_model) {
102  const int num_variables = mp_model->variable_size();
103  std::vector<double> var_scaling(num_variables, 1.0);
104  for (int i = 0; i < num_variables; ++i) {
105  if (mp_model->variable(i).is_integer()) continue;
106  const double lb = mp_model->variable(i).lower_bound();
107  const double ub = mp_model->variable(i).upper_bound();
108  const double magnitude = std::max(std::abs(lb), std::abs(ub));
109  if (magnitude == 0 || magnitude > max_bound) continue;
110  var_scaling[i] = std::min(scaling, max_bound / magnitude);
111  }
112  ApplyVarScaling(var_scaling, mp_model);
113  return var_scaling;
114 }
115 
116 // This uses the best rational approximation of x via continuous fractions. It
117 // is probably not the best implementation, but according to the unit test, it
118 // seems to do the job.
119 int FindRationalFactor(double x, int limit, double tolerance) {
120  const double initial_x = x;
121  x = std::abs(x);
122  x -= std::floor(x);
123  int q = 1;
124  int prev_q = 0;
125  while (q < limit) {
126  if (std::abs(q * initial_x - std::round(q * initial_x)) < q * tolerance) {
127  return q;
128  }
129  x = 1 / x;
130  const int new_q = prev_q + static_cast<int>(std::floor(x)) * q;
131  prev_q = q;
132  q = new_q;
133  x -= std::floor(x);
134  }
135  return 0;
136 }
137 
138 std::vector<double> DetectImpliedIntegers(MPModelProto* mp_model) {
139  const int num_variables = mp_model->variable_size();
140  std::vector<double> var_scaling(num_variables, 1.0);
141 
142  int num_integers = 0;
143  for (int i = 0; i < num_variables; ++i) {
144  if (mp_model->variable(i).is_integer()) ++num_integers;
145  }
146  VLOG(1) << "Initial num_integers: " << num_integers << " / " << num_variables;
147 
148  // We will process all equality constraints with exactly one non-integer.
149  const double tolerance = 1e-6;
150  std::vector<int> constraint_queue;
151 
152  const int num_constraints = mp_model->constraint_size();
153  std::vector<int> constraint_to_num_non_integer(num_constraints, 0);
154  std::vector<std::vector<int>> var_to_constraints(num_variables);
155  for (int i = 0; i < num_constraints; ++i) {
156  const MPConstraintProto& mp_constraint = mp_model->constraint(i);
157 
158  // Ignore non-equality for now.
159  //
160  // TODO(user): If a variable is only bounded in one direction (or if it
161  // is the objective variable) we should be able to deal with inequality.
162  // See for instance b-ball.mps where only the objective is non-integer. We
163  // should really be able to deal with this case. But maybe for the
164  // objective, we should just "expand" it before the conversion.
165  if (mp_constraint.lower_bound() + tolerance < mp_constraint.upper_bound()) {
166  continue;
167  }
168 
169  for (const int var : mp_constraint.var_index()) {
170  if (!mp_model->variable(var).is_integer()) {
171  var_to_constraints[var].push_back(i);
172  constraint_to_num_non_integer[i]++;
173  }
174  }
175  if (constraint_to_num_non_integer[i] == 1) {
176  constraint_queue.push_back(i);
177  }
178  }
179  VLOG(1) << "Initial constraint queue: " << constraint_queue.size() << " / "
180  << num_constraints;
181 
182  int num_detected = 0;
183  int num_fail_due_to_rhs = 0;
184  int num_fail_due_to_large_multiplier = 0;
185  int num_processed_constraints = 0;
186  while (!constraint_queue.empty()) {
187  const int top_ct_index = constraint_queue.back();
188  constraint_queue.pop_back();
189 
190  // The non integer variable was already made integer by one other
191  // constraint.
192  if (constraint_to_num_non_integer[top_ct_index] == 0) continue;
193  ++num_processed_constraints;
194 
195  // This will be set to the unique non-integer term of this constraint.
196  int var = -1;
197  double var_coeff;
198 
199  // We are looking for a "multiplier" so that the unique non-integer term
200  // in this constraint (i.e. var * var_coeff) times this multiplier is an
201  // integer.
202  //
203  // If this is set to zero or becomes too large, we fail to detect a new
204  // implied integer and ignore this constraint.
205  double multiplier = 1.0;
206  const double max_multiplier = 1e4;
207 
208  const MPConstraintProto& ct = mp_model->constraint(top_ct_index);
209  const double rhs = ct.lower_bound();
210  for (int i = 0; i < ct.var_index().size(); ++i) {
211  if (!mp_model->variable(ct.var_index(i)).is_integer()) {
212  CHECK_EQ(var, -1);
213  var = ct.var_index(i);
214  var_coeff = ct.coefficient(i);
215  } else {
216  // This actually compute the smallest multiplier to make all other
217  // terms in the constraint integer.
218  const double coeff =
219  multiplier * ct.coefficient(i) * var_scaling[ct.var_index(i)];
220  multiplier *=
221  FindRationalFactor(coeff, /*limit=*/100, multiplier * tolerance);
222  if (multiplier == 0 || multiplier > max_multiplier) {
223  break;
224  }
225  }
226  }
227 
228  if (multiplier == 0 || multiplier > max_multiplier) {
229  ++num_fail_due_to_large_multiplier;
230  continue;
231  }
232 
233  // These "rhs" fail could be handled by shifting the variable.
234  if (std::abs(std::round(rhs * multiplier) - rhs * multiplier) >
235  tolerance * multiplier) {
236  ++num_fail_due_to_rhs;
237  continue;
238  }
239 
240  // We want to multiply the variable so that it is integer. We know that
241  // coeff * multiplier is an integer, so we just multiply by that.
242  const double scaling = std::abs(var_coeff * multiplier);
243  ++num_detected;
244  CHECK_NE(var, -1);
245 
246  VLOG_IF(2, scaling != 1.0) << "Scaled var by " << scaling;
247 
248  // Scale the variable right away and mark it as implied integer.
249  // Note that the constraints will be scaled later.
250  var_scaling[var] = scaling;
251  mp_model->mutable_variable(var)->set_is_integer(true);
252 
253  // Update the queue of constraints with a single non-integer.
254  for (const int ct_index : var_to_constraints[var]) {
255  constraint_to_num_non_integer[ct_index]--;
256  if (constraint_to_num_non_integer[ct_index] == 1) {
257  constraint_queue.push_back(ct_index);
258  }
259  }
260  }
261 
262  VLOG(1) << "num_new_integer: " << num_detected
263  << " num_processed_constraints: " << num_processed_constraints
264  << " num_rhs_fail: " << num_fail_due_to_rhs
265  << " num_multiplier_fail: " << num_fail_due_to_large_multiplier;
266 
267  ApplyVarScaling(var_scaling, mp_model);
268  return var_scaling;
269 }
270 
271 namespace {
272 
273 // We use a class to reuse the temporay memory.
274 struct ConstraintScaler {
275  // Scales an individual constraint.
276  ConstraintProto* AddConstraint(const MPModelProto& mp_model,
277  const MPConstraintProto& mp_constraint,
278  CpModelProto* cp_model);
279 
281  double max_sum_error = 0.0;
282  double max_scaling_factor = 0.0;
283 
284  double wanted_precision = 1e-6;
286  std::vector<int> var_indices;
287  std::vector<double> coefficients;
288  std::vector<double> lower_bounds;
289  std::vector<double> upper_bounds;
290 };
291 
292 namespace {
293 
294 double FindFractionalScaling(const std::vector<double>& coefficients,
295  double tolerance) {
296  double multiplier = 1.0;
297  for (const double coeff : coefficients) {
298  multiplier *=
299  FindRationalFactor(coeff, /*limit=*/1e8, multiplier * tolerance);
300  if (multiplier == 0.0) break;
301  }
302  return multiplier;
303 }
304 
305 } // namespace
306 
307 ConstraintProto* ConstraintScaler::AddConstraint(
308  const MPModelProto& mp_model, const MPConstraintProto& mp_constraint,
309  CpModelProto* cp_model) {
310  if (mp_constraint.lower_bound() == -kInfinity &&
311  mp_constraint.upper_bound() == kInfinity) {
312  return nullptr;
313  }
314 
315  auto* constraint = cp_model->add_constraints();
316  constraint->set_name(mp_constraint.name());
317  auto* arg = constraint->mutable_linear();
318 
319  // First scale the coefficients of the constraints so that the constraint
320  // sum can always be computed without integer overflow.
321  var_indices.clear();
322  coefficients.clear();
323  lower_bounds.clear();
324  upper_bounds.clear();
325  const int num_coeffs = mp_constraint.coefficient_size();
326  for (int i = 0; i < num_coeffs; ++i) {
327  const auto& var_proto = cp_model->variables(mp_constraint.var_index(i));
328  const int64 lb = var_proto.domain(0);
329  const int64 ub = var_proto.domain(var_proto.domain_size() - 1);
330  if (lb == 0 && ub == 0) continue;
331  if (mp_constraint.coefficient(i) == 0.0) continue;
332 
333  var_indices.push_back(mp_constraint.var_index(i));
334  coefficients.push_back(mp_constraint.coefficient(i));
335  lower_bounds.push_back(lb);
336  upper_bounds.push_back(ub);
337  }
338 
339  // We use an absolute precision if the constraint domain contains a point in
340  // [-1, 1], otherwise we use a relative error to the minimum absolute value
341  // in the domain.
342  Fractional lb = mp_constraint.lower_bound();
343  Fractional ub = mp_constraint.upper_bound();
344  double relative_ref = 1.0;
345  if (lb > 1.0) relative_ref = lb;
346  if (ub < -1.0) relative_ref = -ub;
347 
348  double scaling_factor = GetBestScalingOfDoublesToInt64(
350 
351  // Returns the smallest factor of the form 2^i that gives us a relative sum
352  // error of wanted_precision and still make sure we will have no integer
353  // overflow.
354  //
355  // TODO(user): Make this faster.
356  double x = std::min(scaling_factor, 1.0);
357  double relative_coeff_error;
358  double scaled_sum_error;
359  for (; x <= scaling_factor; x *= 2) {
361  &relative_coeff_error, &scaled_sum_error);
362  if (scaled_sum_error < wanted_precision * x * relative_ref) break;
363  }
364  scaling_factor = x;
365 
366  // Because we deal with an approximate input, scaling with a power of 2 might
367  // not be the best choice. It is also possible user used rational coeff and
368  // then converted them to double (1/2, 1/3, 4/5, etc...). This scaling will
369  // recover such rational input and might result in a smaller overall
370  // coefficient which is good.
371  const double integer_factor = FindFractionalScaling(coefficients, 1e-8);
372  if (integer_factor != 0 && integer_factor < scaling_factor) {
374  &relative_coeff_error, &scaled_sum_error);
375  if (scaled_sum_error < wanted_precision * integer_factor * relative_ref) {
376  scaling_factor = integer_factor;
377  }
378  }
379 
380  const int64 gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
382  std::max(relative_coeff_error, max_relative_coeff_error);
383  max_scaling_factor = std::max(scaling_factor / gcd, max_scaling_factor);
384 
385  // We do not relax the constraint bound if all variables are integer and
386  // we made no error at all during our scaling.
387  bool relax_bound = scaled_sum_error > 0;
388 
389  for (int i = 0; i < coefficients.size(); ++i) {
390  const double scaled_value = coefficients[i] * scaling_factor;
391  const int64 value = static_cast<int64>(std::round(scaled_value)) / gcd;
392  if (value != 0) {
393  if (!mp_model.variable(var_indices[i]).is_integer()) {
394  relax_bound = true;
395  }
396  arg->add_vars(var_indices[i]);
397  arg->add_coeffs(value);
398  }
399  }
401  scaled_sum_error / (scaling_factor * relative_ref));
402 
403  // Add the constraint bounds. Because we are sure the scaled constraint fit
404  // on an int64, if the scaled bounds are too large, the constraint is either
405  // always true or always false.
406  if (relax_bound) {
407  lb -= std::max(1.0, std::abs(lb)) * wanted_precision;
408  }
409  const Fractional scaled_lb = std::ceil(lb * scaling_factor);
410  if (lb == -kInfinity || scaled_lb <= kint64min) {
411  arg->add_domain(kint64min);
412  } else {
413  arg->add_domain(CeilRatio(IntegerValue(static_cast<int64>(scaled_lb)),
414  IntegerValue(gcd))
415  .value());
416  }
417 
418  if (relax_bound) {
419  ub += std::max(1.0, std::abs(ub)) * wanted_precision;
420  }
421  const Fractional scaled_ub = std::floor(ub * scaling_factor);
422  if (ub == kInfinity || scaled_ub >= kint64max) {
423  arg->add_domain(kint64max);
424  } else {
425  arg->add_domain(FloorRatio(IntegerValue(static_cast<int64>(scaled_ub)),
426  IntegerValue(gcd))
427  .value());
428  }
429 
430  return constraint;
431 }
432 
433 } // namespace
434 
435 bool ConvertMPModelProtoToCpModelProto(const SatParameters& params,
436  const MPModelProto& mp_model,
437  CpModelProto* cp_model) {
438  CHECK(cp_model != nullptr);
439  cp_model->Clear();
440  cp_model->set_name(mp_model.name());
441 
442  // To make sure we cannot have integer overflow, we use this bound for any
443  // unbounded variable.
444  //
445  // TODO(user): This could be made larger if needed, so be smarter if we have
446  // MIP problem that we cannot "convert" because of this. Note however than we
447  // cannot go that much further because we need to make sure we will not run
448  // into overflow if we add a big linear combination of such variables. It
449  // should always be possible for a user to scale its problem so that all
450  // relevant quantities are a couple of millions. A LP/MIP solver have a
451  // similar condition in disguise because problem with a difference of more
452  // than 6 magnitudes between the variable values will likely run into numeric
453  // trouble.
454  const int64 kMaxVariableBound = static_cast<int64>(params.mip_max_bound());
455 
456  int num_truncated_bounds = 0;
457  int num_small_domains = 0;
458  const int64 kSmallDomainSize = 1000;
459  const double kWantedPrecision = params.mip_wanted_precision();
460 
461  // Add the variables.
462  const int num_variables = mp_model.variable_size();
463  for (int i = 0; i < num_variables; ++i) {
464  const MPVariableProto& mp_var = mp_model.variable(i);
465  IntegerVariableProto* cp_var = cp_model->add_variables();
466  cp_var->set_name(mp_var.name());
467 
468  // Deal with the corner case of a domain far away from zero.
469  //
470  // TODO(user): We should deal with these case by shifting the domain so
471  // that it includes zero instead of just fixing the variable. But that is a
472  // bit of work as it requires some postsolve.
473  if (mp_var.lower_bound() > kMaxVariableBound) {
474  // Fix var to its lower bound.
475  ++num_truncated_bounds;
476  const int64 value = static_cast<int64>(std::round(mp_var.lower_bound()));
477  cp_var->add_domain(value);
478  cp_var->add_domain(value);
479  continue;
480  } else if (mp_var.upper_bound() < -kMaxVariableBound) {
481  // Fix var to its upper_bound.
482  ++num_truncated_bounds;
483  const int64 value = static_cast<int64>(std::round(mp_var.upper_bound()));
484  cp_var->add_domain(value);
485  cp_var->add_domain(value);
486  continue;
487  }
488 
489  // Note that we must process the lower bound first.
490  for (const bool lower : {true, false}) {
491  const double bound = lower ? mp_var.lower_bound() : mp_var.upper_bound();
492  if (std::abs(bound) >= kMaxVariableBound) {
493  ++num_truncated_bounds;
494  cp_var->add_domain(bound < 0 ? -kMaxVariableBound : kMaxVariableBound);
495  continue;
496  }
497 
498  // Note that the cast is "perfect" because we forbid large values.
499  cp_var->add_domain(
500  static_cast<int64>(lower ? std::ceil(bound - kWantedPrecision)
501  : std::floor(bound + kWantedPrecision)));
502  }
503 
504  // Notify if a continuous variable has a small domain as this is likely to
505  // make an all integer solution far from a continuous one.
506  if (!mp_var.is_integer() && cp_var->domain(0) != cp_var->domain(1) &&
507  cp_var->domain(1) - cp_var->domain(0) < kSmallDomainSize) {
508  ++num_small_domains;
509  }
510  }
511 
512  LOG_IF(WARNING, num_truncated_bounds > 0)
513  << num_truncated_bounds << " bounds were truncated to "
514  << kMaxVariableBound << ".";
515  LOG_IF(WARNING, num_small_domains > 0)
516  << num_small_domains << " continuous variable domain with fewer than "
517  << kSmallDomainSize << " values.";
518 
519  ConstraintScaler scaler;
520  const int64 kScalingTarget = int64{1} << params.mip_max_activity_exponent();
521  scaler.wanted_precision = kWantedPrecision;
522  scaler.scaling_target = kScalingTarget;
523 
524  // Add the constraints. We scale each of them individually.
525  for (const MPConstraintProto& mp_constraint : mp_model.constraint()) {
526  scaler.AddConstraint(mp_model, mp_constraint, cp_model);
527  }
528  for (const MPGeneralConstraintProto& general_constraint :
529  mp_model.general_constraint()) {
530  switch (general_constraint.general_constraint_case()) {
531  case MPGeneralConstraintProto::kIndicatorConstraint: {
532  const auto& indicator_constraint =
533  general_constraint.indicator_constraint();
534  const MPConstraintProto& mp_constraint =
535  indicator_constraint.constraint();
536  ConstraintProto* ct =
537  scaler.AddConstraint(mp_model, mp_constraint, cp_model);
538  if (ct == nullptr) continue;
539 
540  // Add the indicator.
541  const int var = indicator_constraint.var_index();
542  const int value = indicator_constraint.var_value();
543  ct->add_enforcement_literal(value == 1 ? var : NegatedRef(var));
544  break;
545  }
546  case MPGeneralConstraintProto::kAndConstraint: {
547  const auto& and_constraint = general_constraint.and_constraint();
548  const std::string& name = general_constraint.name();
549 
550  ConstraintProto* ct_pos = cp_model->add_constraints();
551  ct_pos->set_name(name.empty() ? "" : absl::StrCat(name, "_pos"));
552  ct_pos->add_enforcement_literal(and_constraint.resultant_var_index());
553  *ct_pos->mutable_bool_and()->mutable_literals() =
554  and_constraint.var_index();
555 
556  ConstraintProto* ct_neg = cp_model->add_constraints();
557  ct_neg->set_name(name.empty() ? "" : absl::StrCat(name, "_neg"));
558  ct_neg->add_enforcement_literal(
559  NegatedRef(and_constraint.resultant_var_index()));
560  for (const int var_index : and_constraint.var_index()) {
561  ct_neg->mutable_bool_or()->add_literals(NegatedRef(var_index));
562  }
563  break;
564  }
565  case MPGeneralConstraintProto::kOrConstraint: {
566  const auto& or_constraint = general_constraint.or_constraint();
567  const std::string& name = general_constraint.name();
568 
569  ConstraintProto* ct_pos = cp_model->add_constraints();
570  ct_pos->set_name(name.empty() ? "" : absl::StrCat(name, "_pos"));
571  ct_pos->add_enforcement_literal(or_constraint.resultant_var_index());
572  *ct_pos->mutable_bool_or()->mutable_literals() =
573  or_constraint.var_index();
574 
575  ConstraintProto* ct_neg = cp_model->add_constraints();
576  ct_neg->set_name(name.empty() ? "" : absl::StrCat(name, "_neg"));
577  ct_neg->add_enforcement_literal(
578  NegatedRef(or_constraint.resultant_var_index()));
579  for (const int var_index : or_constraint.var_index()) {
580  ct_neg->mutable_bool_and()->add_literals(NegatedRef(var_index));
581  }
582  break;
583  }
584  default:
585  LOG(ERROR) << "Can't convert general constraints of type "
586  << general_constraint.general_constraint_case()
587  << " to CpModelProto.";
588  return false;
589  }
590  }
591 
592  double max_relative_coeff_error = scaler.max_relative_coeff_error;
593  double max_sum_error = scaler.max_sum_error;
594  double max_scaling_factor = scaler.max_scaling_factor;
595 
596  // Display the error/scaling without taking into account the objective first.
597  VLOG(1) << "Maximum constraint coefficient relative error: "
599  VLOG(1) << "Maximum constraint worst-case sum absolute error: "
600  << max_sum_error;
601  VLOG(1) << "Maximum constraint scaling factor: " << max_scaling_factor;
602 
603  // Add the objective.
604  std::vector<int> var_indices;
605  std::vector<double> coefficients;
606  std::vector<double> lower_bounds;
607  std::vector<double> upper_bounds;
608  for (int i = 0; i < num_variables; ++i) {
609  const MPVariableProto& mp_var = mp_model.variable(i);
610  if (mp_var.objective_coefficient() == 0.0) continue;
611 
612  const auto& var_proto = cp_model->variables(i);
613  const int64 lb = var_proto.domain(0);
614  const int64 ub = var_proto.domain(var_proto.domain_size() - 1);
615  if (lb == 0 && ub == 0) continue;
616 
617  var_indices.push_back(i);
618  coefficients.push_back(mp_var.objective_coefficient());
619  lower_bounds.push_back(lb);
620  upper_bounds.push_back(ub);
621  }
622  if (!coefficients.empty() || mp_model.objective_offset() != 0.0) {
623  double scaling_factor = GetBestScalingOfDoublesToInt64(
624  coefficients, lower_bounds, upper_bounds, kScalingTarget);
625 
626  // Returns the smallest factor of the form 2^i that gives us an absolute
627  // error of kWantedPrecision and still make sure we will have no integer
628  // overflow.
629  //
630  // TODO(user): Make this faster.
631  double x = std::min(scaling_factor, 1.0);
632  double relative_coeff_error;
633  double scaled_sum_error;
634  for (; x <= scaling_factor; x *= 2) {
636  &relative_coeff_error, &scaled_sum_error);
637  if (scaled_sum_error < kWantedPrecision * x) break;
638  }
639  scaling_factor = x;
640 
641  // Same remark as for the constraint.
642  // TODO(user): Extract common code.
643  const double integer_factor = FindFractionalScaling(coefficients, 1e-8);
644  if (integer_factor != 0 && integer_factor < scaling_factor) {
646  &relative_coeff_error, &scaled_sum_error);
647  if (scaled_sum_error < kWantedPrecision * integer_factor) {
648  scaling_factor = integer_factor;
649  }
650  }
651 
652  const int64 gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
654  std::max(relative_coeff_error, max_relative_coeff_error);
655 
656  // Display the objective error/scaling.
657  VLOG(1) << "objective coefficient relative error: " << relative_coeff_error;
658  VLOG(1) << "objective worst-case absolute error: "
659  << scaled_sum_error / scaling_factor;
660  VLOG(1) << "objective scaling factor: " << scaling_factor / gcd;
661 
662  // Note that here we set the scaling factor for the inverse operation of
663  // getting the "true" objective value from the scaled one. Hence the
664  // inverse.
665  auto* objective = cp_model->mutable_objective();
666  const int mult = mp_model.maximize() ? -1 : 1;
667  objective->set_offset(mp_model.objective_offset() * scaling_factor / gcd *
668  mult);
669  objective->set_scaling_factor(1.0 / scaling_factor * gcd * mult);
670  for (int i = 0; i < coefficients.size(); ++i) {
671  const int64 value =
672  static_cast<int64>(std::round(coefficients[i] * scaling_factor)) /
673  gcd;
674  if (value != 0) {
675  objective->add_vars(var_indices[i]);
676  objective->add_coeffs(value * mult);
677  }
678  }
679  }
680 
681  // Test the precision of the conversion.
682  const double allowed_error =
683  std::max(params.mip_check_precision(), params.mip_wanted_precision());
684  if (max_sum_error > allowed_error) {
685  LOG(WARNING) << "The relative error during double -> int64 conversion "
686  << "is too high! error:" << max_sum_error
687  << " check_tolerance:" << allowed_error;
688  return false;
689  }
690  return true;
691 }
692 
693 bool ConvertBinaryMPModelProtoToBooleanProblem(const MPModelProto& mp_model,
694  LinearBooleanProblem* problem) {
695  CHECK(problem != nullptr);
696  problem->Clear();
697  problem->set_name(mp_model.name());
698  const int num_variables = mp_model.variable_size();
699  problem->set_num_variables(num_variables);
700 
701  // Test if the variables are binary variables.
702  // Add constraints for the fixed variables.
703  for (int var_id(0); var_id < num_variables; ++var_id) {
704  const MPVariableProto& mp_var = mp_model.variable(var_id);
705  problem->add_var_names(mp_var.name());
706 
707  // This will be changed to false as soon as we detect the variable to be
708  // non-binary. This is done this way so we can display a nice error message
709  // before aborting the function and returning false.
710  bool is_binary = mp_var.is_integer();
711 
712  const Fractional lb = mp_var.lower_bound();
713  const Fractional ub = mp_var.upper_bound();
714  if (lb <= -1.0) is_binary = false;
715  if (ub >= 2.0) is_binary = false;
716  if (is_binary) {
717  // 4 cases.
718  if (lb <= 0.0 && ub >= 1.0) {
719  // Binary variable. Ok.
720  } else if (lb <= 1.0 && ub >= 1.0) {
721  // Fixed variable at 1.
722  LinearBooleanConstraint* constraint = problem->add_constraints();
723  constraint->set_lower_bound(1);
724  constraint->set_upper_bound(1);
725  constraint->add_literals(var_id + 1);
726  constraint->add_coefficients(1);
727  } else if (lb <= 0.0 && ub >= 0.0) {
728  // Fixed variable at 0.
729  LinearBooleanConstraint* constraint = problem->add_constraints();
730  constraint->set_lower_bound(0);
731  constraint->set_upper_bound(0);
732  constraint->add_literals(var_id + 1);
733  constraint->add_coefficients(1);
734  } else {
735  // No possible integer value!
736  is_binary = false;
737  }
738  }
739 
740  // Abort if the variable is not binary.
741  if (!is_binary) {
742  LOG(WARNING) << "The variable #" << var_id << " with name "
743  << mp_var.name() << " is not binary. "
744  << "lb: " << lb << " ub: " << ub;
745  return false;
746  }
747  }
748 
749  // Variables needed to scale the double coefficients into int64.
750  const int64 kInt64Max = std::numeric_limits<int64>::max();
751  double max_relative_error = 0.0;
752  double max_bound_error = 0.0;
753  double max_scaling_factor = 0.0;
754  double relative_error = 0.0;
755  double scaling_factor = 0.0;
756  std::vector<double> coefficients;
757 
758  // Add all constraints.
759  for (const MPConstraintProto& mp_constraint : mp_model.constraint()) {
760  LinearBooleanConstraint* constraint = problem->add_constraints();
761  constraint->set_name(mp_constraint.name());
762 
763  // First scale the coefficients of the constraints.
764  coefficients.clear();
765  const int num_coeffs = mp_constraint.coefficient_size();
766  for (int i = 0; i < num_coeffs; ++i) {
767  coefficients.push_back(mp_constraint.coefficient(i));
768  }
769  GetBestScalingOfDoublesToInt64(coefficients, kInt64Max, &scaling_factor,
770  &relative_error);
771  const int64 gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
772  max_relative_error = std::max(relative_error, max_relative_error);
773  max_scaling_factor = std::max(scaling_factor / gcd, max_scaling_factor);
774 
775  double bound_error = 0.0;
776  for (int i = 0; i < num_coeffs; ++i) {
777  const double scaled_value = mp_constraint.coefficient(i) * scaling_factor;
778  bound_error += std::abs(round(scaled_value) - scaled_value);
779  const int64 value = static_cast<int64>(round(scaled_value)) / gcd;
780  if (value != 0) {
781  constraint->add_literals(mp_constraint.var_index(i) + 1);
782  constraint->add_coefficients(value);
783  }
784  }
785  max_bound_error = std::max(max_bound_error, bound_error);
786 
787  // Add the bounds. Note that we do not pass them to
788  // GetBestScalingOfDoublesToInt64() because we know that the sum of absolute
789  // coefficients of the constraint fit on an int64. If one of the scaled
790  // bound overflows, we don't care by how much because in this case the
791  // constraint is just trivial or unsatisfiable.
792  const Fractional lb = mp_constraint.lower_bound();
793  if (lb != -kInfinity) {
794  if (lb * scaling_factor > static_cast<double>(kInt64Max)) {
795  LOG(WARNING) << "A constraint is trivially unsatisfiable.";
796  return false;
797  }
798  if (lb * scaling_factor > -static_cast<double>(kInt64Max)) {
799  // Otherwise, the constraint is not needed.
800  constraint->set_lower_bound(
801  static_cast<int64>(round(lb * scaling_factor - bound_error)) / gcd);
802  }
803  }
804  const Fractional ub = mp_constraint.upper_bound();
805  if (ub != kInfinity) {
806  if (ub * scaling_factor < -static_cast<double>(kInt64Max)) {
807  LOG(WARNING) << "A constraint is trivially unsatisfiable.";
808  return false;
809  }
810  if (ub * scaling_factor < static_cast<double>(kInt64Max)) {
811  // Otherwise, the constraint is not needed.
812  constraint->set_upper_bound(
813  static_cast<int64>(round(ub * scaling_factor + bound_error)) / gcd);
814  }
815  }
816  }
817 
818  // Display the error/scaling without taking into account the objective first.
819  LOG(INFO) << "Maximum constraint relative error: " << max_relative_error;
820  LOG(INFO) << "Maximum constraint bound error: " << max_bound_error;
821  LOG(INFO) << "Maximum constraint scaling factor: " << max_scaling_factor;
822 
823  // Add the objective.
824  coefficients.clear();
825  for (int var_id = 0; var_id < num_variables; ++var_id) {
826  const MPVariableProto& mp_var = mp_model.variable(var_id);
827  coefficients.push_back(mp_var.objective_coefficient());
828  }
829  GetBestScalingOfDoublesToInt64(coefficients, kInt64Max, &scaling_factor,
830  &relative_error);
831  const int64 gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
832  max_relative_error = std::max(relative_error, max_relative_error);
833 
834  // Display the objective error/scaling.
835  LOG(INFO) << "objective relative error: " << relative_error;
836  LOG(INFO) << "objective scaling factor: " << scaling_factor / gcd;
837 
838  LinearObjective* objective = problem->mutable_objective();
839  objective->set_offset(mp_model.objective_offset() * scaling_factor / gcd);
840 
841  // Note that here we set the scaling factor for the inverse operation of
842  // getting the "true" objective value from the scaled one. Hence the inverse.
843  objective->set_scaling_factor(1.0 / scaling_factor * gcd);
844  for (int var_id = 0; var_id < num_variables; ++var_id) {
845  const MPVariableProto& mp_var = mp_model.variable(var_id);
846  const int64 value = static_cast<int64>(round(
847  mp_var.objective_coefficient() * scaling_factor)) /
848  gcd;
849  if (value != 0) {
850  objective->add_literals(var_id + 1);
851  objective->add_coefficients(value);
852  }
853  }
854 
855  // If the problem was a maximization one, we need to modify the objective.
856  if (mp_model.maximize()) ChangeOptimizationDirection(problem);
857 
858  // Test the precision of the conversion.
859  const double kRelativeTolerance = 1e-8;
860  if (max_relative_error > kRelativeTolerance) {
861  LOG(WARNING) << "The relative error during double -> int64 conversion "
862  << "is too high!";
863  return false;
864  }
865  return true;
866 }
867 
868 void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem& problem,
869  glop::LinearProgram* lp) {
870  lp->Clear();
871  for (int i = 0; i < problem.num_variables(); ++i) {
872  const ColIndex col = lp->CreateNewVariable();
874  lp->SetVariableBounds(col, 0.0, 1.0);
875  }
876 
877  // Variables name are optional.
878  if (problem.var_names_size() != 0) {
879  CHECK_EQ(problem.var_names_size(), problem.num_variables());
880  for (int i = 0; i < problem.num_variables(); ++i) {
881  lp->SetVariableName(ColIndex(i), problem.var_names(i));
882  }
883  }
884 
885  for (const LinearBooleanConstraint& constraint : problem.constraints()) {
886  const RowIndex constraint_index = lp->CreateNewConstraint();
887  lp->SetConstraintName(constraint_index, constraint.name());
888  double sum = 0.0;
889  for (int i = 0; i < constraint.literals_size(); ++i) {
890  const int literal = constraint.literals(i);
891  const double coeff = constraint.coefficients(i);
892  const ColIndex variable_index = ColIndex(abs(literal) - 1);
893  if (literal < 0) {
894  sum += coeff;
895  lp->SetCoefficient(constraint_index, variable_index, -coeff);
896  } else {
897  lp->SetCoefficient(constraint_index, variable_index, coeff);
898  }
899  }
901  constraint_index,
902  constraint.has_lower_bound() ? constraint.lower_bound() - sum
903  : -kInfinity,
904  constraint.has_upper_bound() ? constraint.upper_bound() - sum
905  : kInfinity);
906  }
907 
908  // Objective.
909  {
910  double sum = 0.0;
911  const LinearObjective& objective = problem.objective();
912  const double scaling_factor = objective.scaling_factor();
913  for (int i = 0; i < objective.literals_size(); ++i) {
914  const int literal = objective.literals(i);
915  const double coeff =
916  static_cast<double>(objective.coefficients(i)) * scaling_factor;
917  const ColIndex variable_index = ColIndex(abs(literal) - 1);
918  if (literal < 0) {
919  sum += coeff;
920  lp->SetObjectiveCoefficient(variable_index, -coeff);
921  } else {
922  lp->SetObjectiveCoefficient(variable_index, coeff);
923  }
924  }
925  lp->SetObjectiveOffset((sum + objective.offset()) * scaling_factor);
926  lp->SetMaximizationProblem(scaling_factor < 0);
927  }
928 
929  lp->CleanUp();
930 }
931 
933  int num_fixed_variables = 0;
934  const Trail& trail = solver.LiteralTrail();
935  for (int i = 0; i < trail.Index(); ++i) {
936  const BooleanVariable var = trail[i].Variable();
937  const int value = trail[i].IsPositive() ? 1.0 : 0.0;
938  if (trail.Info(var).level == 0) {
939  ++num_fixed_variables;
940  lp->SetVariableBounds(ColIndex(var.value()), value, value);
941  }
942  }
943  return num_fixed_variables;
944 }
945 
947  const glop::LinearProgram& lp, SatSolver* sat_solver,
948  double max_time_in_seconds) {
949  glop::LPSolver solver;
950  glop::GlopParameters glop_parameters;
951  glop_parameters.set_max_time_in_seconds(max_time_in_seconds);
952  solver.SetParameters(glop_parameters);
953  const glop::ProblemStatus& status = solver.Solve(lp);
954  if (status != glop::ProblemStatus::OPTIMAL &&
955  status != glop::ProblemStatus::IMPRECISE &&
957  return false;
958  }
959  for (ColIndex col(0); col < lp.num_variables(); ++col) {
960  const Fractional& value = solver.variable_values()[col];
961  sat_solver->SetAssignmentPreference(
962  Literal(BooleanVariable(col.value()), round(value) == 1),
963  1 - std::abs(value - round(value)));
964  }
965  return true;
966 }
967 
969  LinearBooleanProblem* problem) {
970  glop::LPSolver solver;
971  const glop::ProblemStatus& status = solver.Solve(lp);
972  if (status != glop::ProblemStatus::OPTIMAL &&
974  return false;
975  int num_variable_fixed = 0;
976  for (ColIndex col(0); col < lp.num_variables(); ++col) {
977  const Fractional tolerance = 1e-5;
978  const Fractional& value = solver.variable_values()[col];
979  if (value > 1 - tolerance) {
980  ++num_variable_fixed;
981  LinearBooleanConstraint* constraint = problem->add_constraints();
982  constraint->set_lower_bound(1);
983  constraint->set_upper_bound(1);
984  constraint->add_coefficients(1);
985  constraint->add_literals(col.value() + 1);
986  } else if (value < tolerance) {
987  ++num_variable_fixed;
988  LinearBooleanConstraint* constraint = problem->add_constraints();
989  constraint->set_lower_bound(0);
990  constraint->set_upper_bound(0);
991  constraint->add_coefficients(1);
992  constraint->add_literals(col.value() + 1);
993  }
994  }
995  LOG(INFO) << "LNS with " << num_variable_fixed << " fixed variables.";
996  return true;
997 }
998 
999 } // namespace sat
1000 } // namespace operations_research
operations_research::glop::ProblemStatus::PRIMAL_FEASIBLE
@ PRIMAL_FEASIBLE
operations_research::sat::Trail
Definition: sat_base.h:233
var
IntVar * var
Definition: expr_array.cc:1858
min
int64 min
Definition: alldiff_cst.cc:138
integral_types.h
operations_research::sat::FloorRatio
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:101
max
int64 max
Definition: alldiff_cst.cc:139
operations_research::glop::LPSolver::SetParameters
void SetParameters(const GlopParameters &parameters)
Definition: lp_solver.cc:111
coefficients
std::vector< double > coefficients
Definition: sat/lp_utils.cc:287
bound
int64 bound
Definition: routing_search.cc:972
operations_research::ComputeGcdOfRoundedDoubles
int64 ComputeGcdOfRoundedDoubles(const std::vector< double > &x, double scaling_factor)
Definition: fp_utils.cc:189
operations_research::glop::LinearProgram::SetVariableName
void SetVariableName(ColIndex col, const std::string &name)
Definition: lp_data.cc:230
operations_research::glop::ProblemStatus::IMPRECISE
@ IMPRECISE
operations_research::sat::CeilRatio
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:92
wanted_precision
double wanted_precision
Definition: sat/lp_utils.cc:284
operations_research::glop::LinearProgram::CreateNewConstraint
RowIndex CreateNewConstraint()
Definition: lp_data.cc:189
operations_research::MPModelProto
operations_research::glop::LinearProgram::CleanUp
void CleanUp()
Definition: lp_data.cc:345
operations_research::sat::AssignmentInfo::level
uint32 level
Definition: sat_base.h:199
logging.h
operations_research::glop::LinearProgram::SetObjectiveCoefficient
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:324
operations_research::sat::FindRationalFactor
int FindRationalFactor(double x, int limit, double tolerance)
Definition: sat/lp_utils.cc:119
var_indices
std::vector< int > var_indices
Definition: sat/lp_utils.cc:286
value
int64 value
Definition: demon_profiler.cc:43
operations_research::glop::LinearProgram::SetConstraintName
void SetConstraintName(RowIndex row, const std::string &name)
Definition: lp_data.cc:243
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
operations_research::sat::Trail::Index
int Index() const
Definition: sat_base.h:378
operations_research::sat::SatSolver::SetAssignmentPreference
void SetAssignmentPreference(Literal literal, double weight)
Definition: sat_solver.h:151
operations_research::glop::LinearProgram
Definition: lp_data.h:55
kint64min
static const int64 kint64min
Definition: integral_types.h:60
operations_research::MPConstraintProto
int64
int64_t int64
Definition: integral_types.h:34
operations_research::glop::LPSolver
Definition: lp_solver.h:29
operations_research::sat::ConvertBooleanProblemToLinearProgram
void ConvertBooleanProblemToLinearProgram(const LinearBooleanProblem &problem, glop::LinearProgram *lp)
Definition: sat/lp_utils.cc:868
operations_research::glop::LinearProgram::SetConstraintBounds
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:307
operations_research::glop::LinearProgram::num_variables
ColIndex num_variables() const
Definition: lp_data.h:205
sat_base.h
max_scaling_factor
double max_scaling_factor
Definition: sat/lp_utils.cc:282
operations_research::sat::ChangeOptimizationDirection
void ChangeOptimizationDirection(LinearBooleanProblem *problem)
Definition: boolean_problem.cc:208
operations_research::glop::LinearProgram::SetMaximizationProblem
void SetMaximizationProblem(bool maximize)
Definition: lp_data.cc:341
operations_research::sat::SatSolver::LiteralTrail
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
operations_research::glop::Fractional
double Fractional
Definition: lp_types.h:77
operations_research::sat::SatSolver
Definition: sat_solver.h:58
operations_research::sat::Literal
Definition: sat_base.h:64
operations_research::glop::kInfinity
const double kInfinity
Definition: lp_types.h:83
max_sum_error
double max_sum_error
Definition: sat/lp_utils.cc:281
operations_research::glop::LinearProgram::SetCoefficient
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Definition: lp_data.cc:315
operations_research::glop::LPSolver::Solve
ABSL_MUST_USE_RESULT ProblemStatus Solve(const LinearProgram &lp)
Definition: lp_solver.cc:119
operations_research::MPVariableProto
int_type.h
fp_utils.h
operations_research::ComputeScalingErrors
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:159
int_type_indexed_vector.h
operations_research::glop::LinearProgram::SetVariableBounds
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:247
ct
const Constraint * ct
Definition: demon_profiler.cc:42
operations_research::sat::DetectImpliedIntegers
std::vector< double > DetectImpliedIntegers(MPModelProto *mp_model)
Definition: sat/lp_utils.cc:138
operations_research::sat::NegatedRef
int NegatedRef(int ref)
Definition: cp_model_utils.h:32
operations_research::sat::Trail::Info
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:381
scaling_target
int64 scaling_target
Definition: sat/lp_utils.cc:285
operations_research::sat::ScaleContinuousVariables
std::vector< double > ScaleContinuousVariables(double scaling, double max_bound, MPModelProto *mp_model)
Definition: sat/lp_utils.cc:100
operations_research::sat::ConvertMPModelProtoToCpModelProto
bool ConvertMPModelProtoToCpModelProto(const SatParameters &params, const MPModelProto &mp_model, CpModelProto *cp_model)
Definition: sat/lp_utils.cc:435
operations_research::sat::SolveLpAndUseSolutionForSatAssignmentPreference
bool SolveLpAndUseSolutionForSatAssignmentPreference(const glop::LinearProgram &lp, SatSolver *sat_solver, double max_time_in_seconds)
Definition: sat/lp_utils.cc:946
operations_research::glop::ProblemStatus::OPTIMAL
@ OPTIMAL
col
ColIndex col
Definition: markowitz.cc:176
operations_research::GetBestScalingOfDoublesToInt64
double GetBestScalingOfDoublesToInt64(const std::vector< double > &input, const std::vector< double > &lb, const std::vector< double > &ub, int64 max_absolute_sum)
Definition: fp_utils.cc:168
operations_research::glop::ProblemStatus
ProblemStatus
Definition: lp_types.h:101
operations_research::glop::LinearProgram::VariableType::INTEGER
@ INTEGER
lower_bounds
std::vector< double > lower_bounds
Definition: sat/lp_utils.cc:288
lp_utils.h
boolean_problem.h
operations_research::glop::LinearProgram::SetObjectiveOffset
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:329
operations_research::sat::SolveLpAndUseIntegerVariableToStartLNS
bool SolveLpAndUseIntegerVariableToStartLNS(const glop::LinearProgram &lp, LinearBooleanProblem *problem)
Definition: sat/lp_utils.cc:968
operations_research::glop::LPSolver::variable_values
const DenseRow & variable_values() const
Definition: lp_solver.h:100
lp_types.h
operations_research::sat::ConvertBinaryMPModelProtoToBooleanProblem
bool ConvertBinaryMPModelProtoToBooleanProblem(const MPModelProto &mp_model, LinearBooleanProblem *problem)
Definition: sat/lp_utils.cc:693
max_relative_coeff_error
double max_relative_coeff_error
Definition: sat/lp_utils.cc:280
upper_bounds
std::vector< double > upper_bounds
Definition: sat/lp_utils.cc:289
literal
Literal literal
Definition: optimization.cc:84
operations_research::glop::LinearProgram::CreateNewVariable
ColIndex CreateNewVariable()
Definition: lp_data.cc:160
operations_research::glop::LinearProgram::SetVariableType
void SetVariableType(ColIndex col, VariableType type)
Definition: lp_data.cc:234
operations_research::sat::FixVariablesFromSat
int FixVariablesFromSat(const SatSolver &solver, glop::LinearProgram *lp)
Definition: sat/lp_utils.cc:932
integer.h
name
const std::string name
Definition: default_search.cc:807
parameters.pb.h
kint64max
static const int64 kint64max
Definition: integral_types.h:62
lp_solver.h
cp_model_utils.h
operations_research::glop::LinearProgram::Clear
void Clear()
Definition: lp_data.cc:132