OR-Tools  9.6
integral_solver.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 
15 
16 #include <math.h>
17 
18 #include <algorithm>
19 #include <cmath>
20 #include <cstdint>
21 #include <limits>
22 #include <memory>
23 #include <string>
24 #include <vector>
25 
26 #include "ortools/bop/bop_solver.h"
28 
29 namespace operations_research {
30 namespace bop {
31 
32 using ::operations_research::glop::ColIndex;
36 using ::operations_research::glop::LinearProgram;
37 using ::operations_research::glop::LPDecomposer;
38 using ::operations_research::glop::RowIndex;
39 using ::operations_research::glop::SparseColumn;
40 using ::operations_research::glop::SparseMatrix;
41 using ::operations_research::sat::LinearBooleanConstraint;
42 using ::operations_research::sat::LinearBooleanProblem;
43 using ::operations_research::sat::LinearObjective;
44 
45 namespace {
46 // TODO(user): Use an existing one or move it to util.
48  const double kTolerance = 1e-10;
49  return std::abs(x - round(x)) <= kTolerance;
50 }
51 
52 // Returns true when all the variables of the problem are Boolean, and all the
53 // constraints have integer coefficients.
54 // TODO(user): Move to SAT util.
55 bool ProblemIsBooleanAndHasOnlyIntegralConstraints(
56  const LinearProgram& linear_problem) {
57  const glop::SparseMatrix& matrix = linear_problem.GetSparseMatrix();
58 
59  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
60  const Fractional lower_bound = linear_problem.variable_lower_bounds()[col];
61  const Fractional upper_bound = linear_problem.variable_upper_bounds()[col];
62 
63  if (lower_bound <= -1.0 || upper_bound >= 2.0) {
64  // Integral variable.
65  return false;
66  }
67 
68  for (const SparseColumn::Entry e : matrix.column(col)) {
69  if (!IsIntegerWithinTolerance(e.coefficient())) {
70  // Floating coefficient.
71  return false;
72  }
73  }
74  }
75  return true;
76 }
77 
78 // Builds a LinearBooleanProblem based on a LinearProgram with all the variables
79 // being booleans and all the constraints having only integral coefficients.
80 // TODO(user): Move to SAT util.
81 void BuildBooleanProblemWithIntegralConstraints(
82  const LinearProgram& linear_problem, const DenseRow& initial_solution,
83  LinearBooleanProblem* boolean_problem,
84  std::vector<bool>* boolean_initial_solution) {
85  CHECK(boolean_problem != nullptr);
86  boolean_problem->Clear();
87 
88  const glop::SparseMatrix& matrix = linear_problem.GetSparseMatrix();
89  // Create Boolean variables.
90  for (ColIndex col(0); col < matrix.num_cols(); ++col) {
91  boolean_problem->add_var_names(linear_problem.GetVariableName(col));
92  }
93  boolean_problem->set_num_variables(matrix.num_cols().value());
94  boolean_problem->set_name(linear_problem.name());
95 
96  // Create constraints.
97  for (RowIndex row(0); row < matrix.num_rows(); ++row) {
98  LinearBooleanConstraint* const constraint =
99  boolean_problem->add_constraints();
100  constraint->set_name(linear_problem.GetConstraintName(row));
101  if (linear_problem.constraint_lower_bounds()[row] != -kInfinity) {
102  constraint->set_lower_bound(
103  linear_problem.constraint_lower_bounds()[row]);
104  }
105  if (linear_problem.constraint_upper_bounds()[row] != kInfinity) {
106  constraint->set_upper_bound(
107  linear_problem.constraint_upper_bounds()[row]);
108  }
109  }
110 
111  // Store the constraint coefficients.
112  for (ColIndex col(0); col < matrix.num_cols(); ++col) {
113  for (const SparseColumn::Entry e : matrix.column(col)) {
114  LinearBooleanConstraint* const constraint =
115  boolean_problem->mutable_constraints(e.row().value());
116  constraint->add_literals(col.value() + 1);
117  constraint->add_coefficients(e.coefficient());
118  }
119  }
120 
121  // Add the unit constraints to fix the variables since the variable bounds
122  // are always [0, 1] in a BooleanLinearProblem.
123  for (ColIndex col(0); col < matrix.num_cols(); ++col) {
124  // TODO(user): double check the rounding, and add unit test for this.
125  const int lb = std::round(linear_problem.variable_lower_bounds()[col]);
126  const int ub = std::round(linear_problem.variable_upper_bounds()[col]);
127  if (lb == ub) {
128  LinearBooleanConstraint* ct = boolean_problem->add_constraints();
129  ct->set_lower_bound(ub);
130  ct->set_upper_bound(ub);
131  ct->add_literals(col.value() + 1);
132  ct->add_coefficients(1.0);
133  }
134  }
135 
136  // Create the minimization objective.
137  std::vector<double> coefficients;
138  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
139  const Fractional coeff = linear_problem.objective_coefficients()[col];
140  if (coeff != 0.0) coefficients.push_back(coeff);
141  }
142  double scaling_factor = 0.0;
143  double relative_error = 0.0;
146  &scaling_factor, &relative_error);
147  const int64_t gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
148  LinearObjective* const objective = boolean_problem->mutable_objective();
149  objective->set_offset(linear_problem.objective_offset() * scaling_factor /
150  gcd);
151 
152  // Note that here we set the scaling factor for the inverse operation of
153  // getting the "true" objective value from the scaled one. Hence the inverse.
154  objective->set_scaling_factor(1.0 / scaling_factor * gcd);
155  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
156  const Fractional coeff = linear_problem.objective_coefficients()[col];
157  const int64_t value =
158  static_cast<int64_t>(round(coeff * scaling_factor)) / gcd;
159  if (value != 0) {
160  objective->add_literals(col.value() + 1);
161  objective->add_coefficients(value);
162  }
163  }
164 
165  // If the problem was a maximization one, we need to modify the objective.
166  if (linear_problem.IsMaximizationProblem()) {
167  sat::ChangeOptimizationDirection(boolean_problem);
168  }
169 
170  // Fill the Boolean initial solution.
171  if (!initial_solution.empty()) {
172  CHECK(boolean_initial_solution != nullptr);
173  CHECK_EQ(boolean_problem->num_variables(), initial_solution.size());
174  boolean_initial_solution->assign(boolean_problem->num_variables(), false);
175  for (int i = 0; i < initial_solution.size(); ++i) {
176  (*boolean_initial_solution)[i] = (initial_solution[ColIndex(i)] != 0);
177  }
178  }
179 }
180 
181 //------------------------------------------------------------------------------
182 // IntegralVariable
183 //------------------------------------------------------------------------------
184 // Model an integral variable using Boolean variables.
185 // TODO(user): Enable discrete representation by value, i.e. use three Boolean
186 // variables when only possible values are 10, 12, 32.
187 // In the same way, when only two consecutive values are possible
188 // use only one Boolean variable with an offset.
189 class IntegralVariable {
190  public:
191  IntegralVariable();
192 
193  // Creates the minimal number of Boolean variables to represent an integral
194  // variable with range [lower_bound, upper_bound]. start_var_index corresponds
195  // to the next available Boolean variable index. If three Boolean variables
196  // are needed to model the integral variable, the used variables will have
197  // indices start_var_index, start_var_index +1, and start_var_index +2.
198  void BuildFromRange(int start_var_index, Fractional lower_bound,
200 
201  void Clear();
202  void set_offset(int64_t offset) { offset_ = offset; }
203  void set_weight(VariableIndex var, int64_t weight);
204 
205  int GetNumberOfBooleanVariables() const { return bits_.size(); }
206 
207  const std::vector<VariableIndex>& bits() const { return bits_; }
208  const std::vector<int64_t>& weights() const { return weights_; }
209  int64_t offset() const { return offset_; }
210 
211  // Returns the value of the integral variable based on the Boolean conversion
212  // and the Boolean solution to the problem.
213  int64_t GetSolutionValue(const BopSolution& solution) const;
214 
215  // Returns the values of the Boolean variables based on the Boolean conversion
216  // and the integral value of this variable. This only works for variables that
217  // were constructed using BuildFromRange() (for which can_be_reversed_ is
218  // true).
219  std::vector<bool> GetBooleanSolutionValues(int64_t integral_value) const;
220 
221  std::string DebugString() const;
222 
223  private:
224  // The value of the integral variable is expressed as
225  // sum_i(weights[i] * Value(bits[i])) + offset.
226  // Note that weights can be negative to represent negative values.
227  std::vector<VariableIndex> bits_;
228  std::vector<int64_t> weights_;
229  int64_t offset_;
230  // True if the values of the boolean variables representing this integral
231  // variable can be deduced from the integral variable's value. Namely, this is
232  // true for variables built using BuildFromRange() but usually false for
233  // variables built using set_weight().
234  bool can_be_reversed_;
235 };
236 
237 IntegralVariable::IntegralVariable()
238  : bits_(), weights_(), offset_(0), can_be_reversed_(true) {}
239 
240 void IntegralVariable::BuildFromRange(int start_var_index,
243  Clear();
244 
245  // Integral variable. Split the variable into the minimum number of bits
246  // required to model the upper bound.
247  CHECK_NE(-kInfinity, lower_bound);
248  CHECK_NE(kInfinity, upper_bound);
249 
250  const int64_t integral_lower_bound = static_cast<int64_t>(ceil(lower_bound));
251  const int64_t integral_upper_bound = static_cast<int64_t>(floor(upper_bound));
252  offset_ = integral_lower_bound;
253  const int64_t delta = integral_upper_bound - integral_lower_bound;
254  const int num_used_bits = MostSignificantBitPosition64(delta) + 1;
255  for (int i = 0; i < num_used_bits; ++i) {
256  bits_.push_back(VariableIndex(start_var_index + i));
257  weights_.push_back(1ULL << i);
258  }
259 }
260 
261 void IntegralVariable::Clear() {
262  bits_.clear();
263  weights_.clear();
264  offset_ = 0;
265  can_be_reversed_ = true;
266 }
267 
268 void IntegralVariable::set_weight(VariableIndex var, int64_t weight) {
269  bits_.push_back(var);
270  weights_.push_back(weight);
271  can_be_reversed_ = false;
272 }
273 
274 int64_t IntegralVariable::GetSolutionValue(const BopSolution& solution) const {
275  int64_t value = offset_;
276  for (int i = 0; i < bits_.size(); ++i) {
277  value += weights_[i] * solution.Value(bits_[i]);
278  }
279  return value;
280 }
281 
282 std::vector<bool> IntegralVariable::GetBooleanSolutionValues(
283  int64_t integral_value) const {
284  if (can_be_reversed_) {
285  DCHECK(std::is_sorted(weights_.begin(), weights_.end()));
286  std::vector<bool> boolean_values(weights_.size(), false);
287  int64_t remaining_value = integral_value - offset_;
288  for (int i = weights_.size() - 1; i >= 0; --i) {
289  if (remaining_value >= weights_[i]) {
290  boolean_values[i] = true;
291  remaining_value -= weights_[i];
292  }
293  }
294  CHECK_EQ(0, remaining_value)
295  << "Couldn't map integral value to boolean variables.";
296  return boolean_values;
297  }
298  return std::vector<bool>();
299 }
300 
301 std::string IntegralVariable::DebugString() const {
302  std::string str;
303  CHECK_EQ(bits_.size(), weights_.size());
304  for (int i = 0; i < bits_.size(); ++i) {
305  str += absl::StrFormat("%d [%d] ", weights_[i], bits_[i].value());
306  }
307  str += absl::StrFormat(" Offset: %d", offset_);
308  return str;
309 }
310 
311 //------------------------------------------------------------------------------
312 // IntegralProblemConverter
313 //------------------------------------------------------------------------------
314 // This class is used to convert a LinearProblem containing integral variables
315 // into a LinearBooleanProblem that Bop can consume.
316 // The converter tries to reuse existing Boolean variables as much as possible,
317 // but there are no guarantees to model all integral variables using the total
318 // minimal number of Boolean variables.
319 // Consider for instance the constraint "x - 2 * y = 0".
320 // Depending on the declaration order, two different outcomes are possible:
321 // - When x is considered first, the converter will generate new variables
322 // for both x and y as we only consider integral weights, i.e. y = x / 2.
323 // - When y is considered first, the converter will reuse Boolean variables
324 // from y to model x as x = 2 * y (integral weight).
325 //
326 // Note that the converter only deals with integral variables, i.e. no
327 // continuous variables.
328 class IntegralProblemConverter {
329  public:
330  IntegralProblemConverter();
331 
332  // Converts the LinearProgram into a LinearBooleanProblem. If an initial
333  // solution is given (i.e. if its size is not zero), converts it into a
334  // Boolean solution.
335  // Returns false when the conversion fails.
336  bool ConvertToBooleanProblem(const LinearProgram& linear_problem,
337  const DenseRow& initial_solution,
338  LinearBooleanProblem* boolean_problem,
339  std::vector<bool>* boolean_initial_solution);
340 
341  // Returns the value of a variable of the original problem based on the
342  // Boolean conversion and the Boolean solution to the problem.
343  int64_t GetSolutionValue(ColIndex global_col,
344  const BopSolution& solution) const;
345 
346  private:
347  // Returns true when the linear_problem_ can be converted into a Boolean
348  // problem. Note that floating weights and continuous variables are not
349  // supported.
350  bool CheckProblem(const LinearProgram& linear_problem) const;
351 
352  // Initializes the type of each variable of the linear_problem_.
353  void InitVariableTypes(const LinearProgram& linear_problem,
354  LinearBooleanProblem* boolean_problem);
355 
356  // Converts all variables of the problem.
357  void ConvertAllVariables(const LinearProgram& linear_problem,
358  LinearBooleanProblem* boolean_problem);
359 
360  // Adds all variables constraints, i.e. lower and upper bounds of variables.
361  void AddVariableConstraints(const LinearProgram& linear_problem,
362  LinearBooleanProblem* boolean_problem);
363 
364  // Converts all constraints from LinearProgram to LinearBooleanProblem.
365  void ConvertAllConstraints(const LinearProgram& linear_problem,
366  LinearBooleanProblem* boolean_problem);
367 
368  // Converts the objective from LinearProgram to LinearBooleanProblem.
369  void ConvertObjective(const LinearProgram& linear_problem,
370  LinearBooleanProblem* boolean_problem);
371 
372  // Converts the integral variable represented by col in the linear_problem_
373  // into an IntegralVariable using existing Boolean variables.
374  // Returns false when existing Boolean variables are not enough to model
375  // the integral variable.
376  bool ConvertUsingExistingBooleans(const LinearProgram& linear_problem,
377  ColIndex col,
378  IntegralVariable* integral_var);
379 
380  // Creates the integral_var using the given linear_problem_ constraint.
381  // The constraint is an equality constraint and contains only one integral
382  // variable (already the case in the model or thanks to previous
383  // booleanization of other integral variables), i.e.
384  // bound <= w * integral_var + sum(w_i * b_i) <= bound
385  // The remaining integral variable can then be expressed:
386  // integral_var == (bound + sum(-w_i * b_i)) / w
387  // Note that all divisions by w have to be integral as Bop only deals with
388  // integral coefficients.
389  bool CreateVariableUsingConstraint(const LinearProgram& linear_problem,
390  RowIndex constraint,
391  IntegralVariable* integral_var);
392 
393  // Adds weighted integral variable represented by col to the current dense
394  // constraint.
395  Fractional AddWeightedIntegralVariable(
396  ColIndex col, Fractional weight,
398 
399  // Scales weights and adds all non-zero scaled weights and literals to t.
400  // t is a constraint or the objective.
401  // Returns the bound error due to the scaling.
402  // The weight is scaled using:
403  // static_cast<int64_t>(round(weight * scaling_factor)) / gcd;
404  template <class T>
405  double ScaleAndSparsifyWeights(
406  double scaling_factor, int64_t gcd,
407  const absl::StrongVector<VariableIndex, Fractional>& dense_weights, T* t);
408 
409  // Returns true when at least one element is non-zero.
410  bool HasNonZeroWeights(
411  const absl::StrongVector<VariableIndex, Fractional>& dense_weights) const;
412 
413  bool problem_is_boolean_and_has_only_integral_constraints_;
414 
415  // global_to_boolean_[i] represents the Boolean variable index in Bop; when
416  // negative -global_to_boolean_[i] - 1 represents the index of the
417  // integral variable in integral_variables_.
418  absl::StrongVector</*global_col*/ glop::ColIndex, /*boolean_col*/ int>
419  global_to_boolean_;
420  std::vector<IntegralVariable> integral_variables_;
421  std::vector<ColIndex> integral_indices_;
422  int num_boolean_variables_;
423 
424  enum VariableType { BOOLEAN, INTEGRAL, INTEGRAL_EXPRESSED_AS_BOOLEAN };
426 };
427 
428 IntegralProblemConverter::IntegralProblemConverter()
429  : global_to_boolean_(),
430  integral_variables_(),
431  integral_indices_(),
432  num_boolean_variables_(0),
433  variable_types_() {}
434 
435 bool IntegralProblemConverter::ConvertToBooleanProblem(
436  const LinearProgram& linear_problem, const DenseRow& initial_solution,
437  LinearBooleanProblem* boolean_problem,
438  std::vector<bool>* boolean_initial_solution) {
439  bool use_initial_solution = (initial_solution.size() > 0);
440  if (use_initial_solution) {
441  CHECK_EQ(initial_solution.size(), linear_problem.num_variables())
442  << "The initial solution should have the same number of variables as "
443  "the LinearProgram.";
444  CHECK(boolean_initial_solution != nullptr);
445  }
446  if (!CheckProblem(linear_problem)) {
447  return false;
448  }
449 
450  problem_is_boolean_and_has_only_integral_constraints_ =
451  ProblemIsBooleanAndHasOnlyIntegralConstraints(linear_problem);
452  if (problem_is_boolean_and_has_only_integral_constraints_) {
453  BuildBooleanProblemWithIntegralConstraints(linear_problem, initial_solution,
454  boolean_problem,
455  boolean_initial_solution);
456  return true;
457  }
458 
459  InitVariableTypes(linear_problem, boolean_problem);
460  ConvertAllVariables(linear_problem, boolean_problem);
461  boolean_problem->set_num_variables(num_boolean_variables_);
462  boolean_problem->set_name(linear_problem.name());
463 
464  AddVariableConstraints(linear_problem, boolean_problem);
465  ConvertAllConstraints(linear_problem, boolean_problem);
466  ConvertObjective(linear_problem, boolean_problem);
467 
468  // A BooleanLinearProblem is always in the minimization form.
469  if (linear_problem.IsMaximizationProblem()) {
470  sat::ChangeOptimizationDirection(boolean_problem);
471  }
472 
473  if (use_initial_solution) {
474  boolean_initial_solution->assign(boolean_problem->num_variables(), false);
475  for (ColIndex global_col(0); global_col < global_to_boolean_.size();
476  ++global_col) {
477  const int col = global_to_boolean_[global_col];
478  if (col >= 0) {
479  (*boolean_initial_solution)[col] = (initial_solution[global_col] != 0);
480  } else {
481  const IntegralVariable& integral_variable =
482  integral_variables_[-col - 1];
483  const std::vector<VariableIndex>& boolean_cols =
484  integral_variable.bits();
485  const std::vector<bool>& boolean_values =
486  integral_variable.GetBooleanSolutionValues(
487  round(initial_solution[global_col]));
488  if (!boolean_values.empty()) {
489  CHECK_EQ(boolean_cols.size(), boolean_values.size());
490  for (int i = 0; i < boolean_values.size(); ++i) {
491  const int boolean_col = boolean_cols[i].value();
492  (*boolean_initial_solution)[boolean_col] = boolean_values[i];
493  }
494  }
495  }
496  }
497  }
498 
499  return true;
500 }
501 
502 int64_t IntegralProblemConverter::GetSolutionValue(
503  ColIndex global_col, const BopSolution& solution) const {
504  if (problem_is_boolean_and_has_only_integral_constraints_) {
505  return solution.Value(VariableIndex(global_col.value()));
506  }
507 
508  const int pos = global_to_boolean_[global_col];
509  return pos >= 0 ? solution.Value(VariableIndex(pos))
510  : integral_variables_[-pos - 1].GetSolutionValue(solution);
511 }
512 
513 bool IntegralProblemConverter::CheckProblem(
514  const LinearProgram& linear_problem) const {
515  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
516  if (!linear_problem.IsVariableInteger(col)) {
517  LOG(ERROR) << "Variable " << linear_problem.GetVariableName(col)
518  << " is continuous. This is not supported by BOP.";
519  return false;
520  }
521  if (linear_problem.variable_lower_bounds()[col] == -kInfinity) {
522  LOG(ERROR) << "Variable " << linear_problem.GetVariableName(col)
523  << " has no lower bound. This is not supported by BOP.";
524  return false;
525  }
526  if (linear_problem.variable_upper_bounds()[col] == kInfinity) {
527  LOG(ERROR) << "Variable " << linear_problem.GetVariableName(col)
528  << " has no upper bound. This is not supported by BOP.";
529  return false;
530  }
531  }
532  return true;
533 }
534 
535 void IntegralProblemConverter::InitVariableTypes(
536  const LinearProgram& linear_problem,
537  LinearBooleanProblem* boolean_problem) {
538  global_to_boolean_.assign(linear_problem.num_variables().value(), 0);
539  variable_types_.assign(linear_problem.num_variables().value(), INTEGRAL);
540  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
541  const Fractional lower_bound = linear_problem.variable_lower_bounds()[col];
542  const Fractional upper_bound = linear_problem.variable_upper_bounds()[col];
543 
544  if (lower_bound > -1.0 && upper_bound < 2.0) {
545  // Boolean variable.
546  variable_types_[col] = BOOLEAN;
547  global_to_boolean_[col] = num_boolean_variables_;
548  ++num_boolean_variables_;
549  boolean_problem->add_var_names(linear_problem.GetVariableName(col));
550  } else {
551  // Integral variable.
552  variable_types_[col] = INTEGRAL;
553  integral_indices_.push_back(col);
554  }
555  }
556 }
557 
558 void IntegralProblemConverter::ConvertAllVariables(
559  const LinearProgram& linear_problem,
560  LinearBooleanProblem* boolean_problem) {
561  for (const ColIndex col : integral_indices_) {
562  CHECK_EQ(INTEGRAL, variable_types_[col]);
563  IntegralVariable integral_var;
564  if (!ConvertUsingExistingBooleans(linear_problem, col, &integral_var)) {
565  const Fractional lower_bound =
566  linear_problem.variable_lower_bounds()[col];
567  const Fractional upper_bound =
568  linear_problem.variable_upper_bounds()[col];
569  integral_var.BuildFromRange(num_boolean_variables_, lower_bound,
570  upper_bound);
571  num_boolean_variables_ += integral_var.GetNumberOfBooleanVariables();
572  const std::string var_name = linear_problem.GetVariableName(col);
573  for (int i = 0; i < integral_var.bits().size(); ++i) {
574  boolean_problem->add_var_names(var_name + absl::StrFormat("_%d", i));
575  }
576  }
577  integral_variables_.push_back(integral_var);
578  global_to_boolean_[col] = -integral_variables_.size();
579  variable_types_[col] = INTEGRAL_EXPRESSED_AS_BOOLEAN;
580  }
581 }
582 
583 void IntegralProblemConverter::ConvertAllConstraints(
584  const LinearProgram& linear_problem,
585  LinearBooleanProblem* boolean_problem) {
586  // TODO(user): This is the way it's done in glop/proto_utils.cc but having
587  // to transpose looks unnecessary costly.
588  glop::SparseMatrix transpose;
589  transpose.PopulateFromTranspose(linear_problem.GetSparseMatrix());
590 
591  double max_relative_error = 0.0;
592  double max_bound_error = 0.0;
593  double max_scaling_factor = 0.0;
594  double relative_error = 0.0;
595  double scaling_factor = 0.0;
596  std::vector<double> coefficients;
597  for (RowIndex row(0); row < linear_problem.num_constraints(); ++row) {
598  Fractional offset = 0.0;
600  num_boolean_variables_, 0.0);
601  for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
602  // Cast in ColIndex due to the transpose.
603  offset += AddWeightedIntegralVariable(RowToColIndex(e.row()),
604  e.coefficient(), &dense_weights);
605  }
606  if (!HasNonZeroWeights(dense_weights)) {
607  continue;
608  }
609 
610  // Compute the scaling for non-integral weights.
611  coefficients.clear();
612  for (VariableIndex var(0); var < num_boolean_variables_; ++var) {
613  if (dense_weights[var] != 0.0) {
614  coefficients.push_back(dense_weights[var]);
615  }
616  }
619  &scaling_factor, &relative_error);
620  const int64_t gcd =
621  ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
622  max_relative_error = std::max(relative_error, max_relative_error);
623  max_scaling_factor = std::max(scaling_factor / gcd, max_scaling_factor);
624 
625  LinearBooleanConstraint* constraint = boolean_problem->add_constraints();
626  constraint->set_name(linear_problem.GetConstraintName(row));
627  const double bound_error =
628  ScaleAndSparsifyWeights(scaling_factor, gcd, dense_weights, constraint);
629  max_bound_error = std::max(max_bound_error, bound_error);
630 
631  const Fractional lower_bound =
632  linear_problem.constraint_lower_bounds()[row];
633  if (lower_bound != -kInfinity) {
634  const Fractional offset_lower_bound = lower_bound - offset;
635  const double offset_scaled_lower_bound =
636  round(offset_lower_bound * scaling_factor - bound_error);
637  if (offset_scaled_lower_bound >=
638  static_cast<double>(std::numeric_limits<int64_t>::max())) {
639  LOG(WARNING) << "A constraint is trivially unsatisfiable.";
640  return;
641  }
642  if (offset_scaled_lower_bound >
643  -static_cast<double>(std::numeric_limits<int64_t>::max())) {
644  // Otherwise, the constraint is not needed.
645  constraint->set_lower_bound(
646  static_cast<int64_t>(offset_scaled_lower_bound) / gcd);
647  }
648  }
649  const Fractional upper_bound =
650  linear_problem.constraint_upper_bounds()[row];
651  if (upper_bound != kInfinity) {
652  const Fractional offset_upper_bound = upper_bound - offset;
653  const double offset_scaled_upper_bound =
654  round(offset_upper_bound * scaling_factor + bound_error);
655  if (offset_scaled_upper_bound <=
656  -static_cast<double>(std::numeric_limits<int64_t>::max())) {
657  LOG(WARNING) << "A constraint is trivially unsatisfiable.";
658  return;
659  }
660  if (offset_scaled_upper_bound <
661  static_cast<double>(std::numeric_limits<int64_t>::max())) {
662  // Otherwise, the constraint is not needed.
663  constraint->set_upper_bound(
664  static_cast<int64_t>(offset_scaled_upper_bound) / gcd);
665  }
666  }
667  }
668 }
669 
670 void IntegralProblemConverter::ConvertObjective(
671  const LinearProgram& linear_problem,
672  LinearBooleanProblem* boolean_problem) {
673  LinearObjective* objective = boolean_problem->mutable_objective();
674  Fractional offset = 0.0;
676  num_boolean_variables_, 0.0);
677  // Compute the objective weights for the binary variable model.
678  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
679  offset += AddWeightedIntegralVariable(
680  col, linear_problem.objective_coefficients()[col], &dense_weights);
681  }
682 
683  // Compute the scaling for non-integral weights.
684  std::vector<double> coefficients;
685  for (VariableIndex var(0); var < num_boolean_variables_; ++var) {
686  if (dense_weights[var] != 0.0) {
687  coefficients.push_back(dense_weights[var]);
688  }
689  }
690  double scaling_factor = 0.0;
691  double max_relative_error = 0.0;
692  double relative_error = 0.0;
695  &scaling_factor, &relative_error);
696  const int64_t gcd = ComputeGcdOfRoundedDoubles(coefficients, scaling_factor);
697  max_relative_error = std::max(relative_error, max_relative_error);
698  VLOG(1) << "objective relative error: " << relative_error;
699  VLOG(1) << "objective scaling factor: " << scaling_factor / gcd;
700 
701  ScaleAndSparsifyWeights(scaling_factor, gcd, dense_weights, objective);
702 
703  // Note that here we set the scaling factor for the inverse operation of
704  // getting the "true" objective value from the scaled one. Hence the inverse.
705  objective->set_scaling_factor(1.0 / scaling_factor * gcd);
706  objective->set_offset((linear_problem.objective_offset() + offset) *
707  scaling_factor / gcd);
708 }
709 
710 void IntegralProblemConverter::AddVariableConstraints(
711  const LinearProgram& linear_problem,
712  LinearBooleanProblem* boolean_problem) {
713  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
714  const Fractional lower_bound = linear_problem.variable_lower_bounds()[col];
715  const Fractional upper_bound = linear_problem.variable_upper_bounds()[col];
716  const int pos = global_to_boolean_[col];
717  if (pos >= 0) {
718  // Boolean variable.
719  CHECK_EQ(BOOLEAN, variable_types_[col]);
720  const bool is_fixed = (lower_bound > -1.0 && upper_bound < 1.0) ||
721  (lower_bound > 0.0 && upper_bound < 2.0);
722  if (is_fixed) {
723  // Set the variable.
724  const int fixed_value = lower_bound > -1.0 && upper_bound < 1.0 ? 0 : 1;
725  LinearBooleanConstraint* constraint =
726  boolean_problem->add_constraints();
727  constraint->set_lower_bound(fixed_value);
728  constraint->set_upper_bound(fixed_value);
729  constraint->add_literals(pos + 1);
730  constraint->add_coefficients(1);
731  }
732  } else {
733  CHECK_EQ(INTEGRAL_EXPRESSED_AS_BOOLEAN, variable_types_[col]);
734  // Integral variable.
735  if (lower_bound != -kInfinity || upper_bound != kInfinity) {
736  const IntegralVariable& integral_var = integral_variables_[-pos - 1];
737  LinearBooleanConstraint* constraint =
738  boolean_problem->add_constraints();
739  for (int i = 0; i < integral_var.bits().size(); ++i) {
740  constraint->add_literals(integral_var.bits()[i].value() + 1);
741  constraint->add_coefficients(integral_var.weights()[i]);
742  }
743  if (lower_bound != -kInfinity) {
744  constraint->set_lower_bound(static_cast<int64_t>(ceil(lower_bound)) -
745  integral_var.offset());
746  }
747  if (upper_bound != kInfinity) {
748  constraint->set_upper_bound(static_cast<int64_t>(floor(upper_bound)) -
749  integral_var.offset());
750  }
751  }
752  }
753  }
754 }
755 
756 bool IntegralProblemConverter::ConvertUsingExistingBooleans(
757  const LinearProgram& linear_problem, ColIndex col,
758  IntegralVariable* integral_var) {
759  CHECK(nullptr != integral_var);
760  CHECK_EQ(INTEGRAL, variable_types_[col]);
761 
762  const SparseMatrix& matrix = linear_problem.GetSparseMatrix();
763  const SparseMatrix& transpose = linear_problem.GetTransposeSparseMatrix();
764  for (const SparseColumn::Entry var_entry : matrix.column(col)) {
765  const RowIndex constraint = var_entry.row();
766  const Fractional lb = linear_problem.constraint_lower_bounds()[constraint];
767  const Fractional ub = linear_problem.constraint_upper_bounds()[constraint];
768  if (lb != ub) {
769  // To replace an integral variable by a weighted sum of Boolean variables,
770  // the constraint has to be an equality.
771  continue;
772  }
773 
774  if (transpose.column(RowToColIndex(constraint)).num_entries() <= 1) {
775  // Can't replace the integer variable by Boolean variables when there are
776  // no Boolean variables.
777  // TODO(user): We could actually simplify the problem when the variable
778  // is constant, but this should be done by the preprocessor,
779  // not here. Consider activating the MIP preprocessing.
780  continue;
781  }
782 
783  bool only_one_integral_variable = true;
784  for (const SparseColumn::Entry constraint_entry :
785  transpose.column(RowToColIndex(constraint))) {
786  const ColIndex var_index = RowToColIndex(constraint_entry.row());
787  if (var_index != col && variable_types_[var_index] == INTEGRAL) {
788  only_one_integral_variable = false;
789  break;
790  }
791  }
792  if (only_one_integral_variable &&
793  CreateVariableUsingConstraint(linear_problem, constraint,
794  integral_var)) {
795  return true;
796  }
797  }
798 
799  integral_var->Clear();
800  return false;
801 }
802 
803 bool IntegralProblemConverter::CreateVariableUsingConstraint(
804  const LinearProgram& linear_problem, RowIndex constraint,
805  IntegralVariable* integral_var) {
806  CHECK(nullptr != integral_var);
807  integral_var->Clear();
808 
809  const SparseMatrix& transpose = linear_problem.GetTransposeSparseMatrix();
811  num_boolean_variables_, 0.0);
812  Fractional scale = 1.0;
813  int64_t variable_offset = 0;
814  for (const SparseColumn::Entry constraint_entry :
815  transpose.column(RowToColIndex(constraint))) {
816  const ColIndex col = RowToColIndex(constraint_entry.row());
817  if (variable_types_[col] == INTEGRAL) {
818  scale = constraint_entry.coefficient();
819  } else if (variable_types_[col] == BOOLEAN) {
820  const int pos = global_to_boolean_[col];
821  CHECK_LE(0, pos);
822  dense_weights[VariableIndex(pos)] -= constraint_entry.coefficient();
823  } else {
824  CHECK_EQ(INTEGRAL_EXPRESSED_AS_BOOLEAN, variable_types_[col]);
825  const int pos = global_to_boolean_[col];
826  CHECK_GT(0, pos);
827  const IntegralVariable& local_integral_var =
828  integral_variables_[-pos - 1];
829  variable_offset -=
830  constraint_entry.coefficient() * local_integral_var.offset();
831  for (int i = 0; i < local_integral_var.bits().size(); ++i) {
832  dense_weights[local_integral_var.bits()[i]] -=
833  constraint_entry.coefficient() * local_integral_var.weights()[i];
834  }
835  }
836  }
837 
838  // Rescale using the weight of the integral variable.
839  const Fractional lb = linear_problem.constraint_lower_bounds()[constraint];
840  const Fractional offset = (lb + variable_offset) / scale;
841  if (!IsIntegerWithinTolerance(offset)) {
842  return false;
843  }
844  integral_var->set_offset(static_cast<int64_t>(offset));
845 
846  for (VariableIndex var(0); var < dense_weights.size(); ++var) {
847  if (dense_weights[var] != 0.0) {
848  const Fractional weight = dense_weights[var] / scale;
850  return false;
851  }
852  integral_var->set_weight(var, static_cast<int64_t>(weight));
853  }
854  }
855 
856  return true;
857 }
858 
859 Fractional IntegralProblemConverter::AddWeightedIntegralVariable(
860  ColIndex col, Fractional weight,
862  CHECK(nullptr != dense_weights);
863 
864  if (weight == 0.0) {
865  return 0;
866  }
867 
868  Fractional offset = 0;
869  const int pos = global_to_boolean_[col];
870  if (pos >= 0) {
871  // Boolean variable.
872  (*dense_weights)[VariableIndex(pos)] += weight;
873  } else {
874  // Integral variable.
875  const IntegralVariable& integral_var = integral_variables_[-pos - 1];
876  for (int i = 0; i < integral_var.bits().size(); ++i) {
877  (*dense_weights)[integral_var.bits()[i]] +=
878  integral_var.weights()[i] * weight;
879  }
880  offset += weight * integral_var.offset();
881  }
882  return offset;
883 }
884 
885 template <class T>
886 double IntegralProblemConverter::ScaleAndSparsifyWeights(
887  double scaling_factor, int64_t gcd,
888  const absl::StrongVector<VariableIndex, Fractional>& dense_weights, T* t) {
889  CHECK(nullptr != t);
890 
891  double bound_error = 0.0;
892  for (VariableIndex var(0); var < dense_weights.size(); ++var) {
893  if (dense_weights[var] != 0.0) {
894  const double scaled_weight = dense_weights[var] * scaling_factor;
895  bound_error += fabs(round(scaled_weight) - scaled_weight);
896  t->add_literals(var.value() + 1);
897  t->add_coefficients(static_cast<int64_t>(round(scaled_weight)) / gcd);
898  }
899  }
900 
901  return bound_error;
902 }
903 bool IntegralProblemConverter::HasNonZeroWeights(
904  const absl::StrongVector<VariableIndex, Fractional>& dense_weights) const {
905  for (const Fractional weight : dense_weights) {
906  if (weight != 0.0) {
907  return true;
908  }
909  }
910  return false;
911 }
912 
913 bool CheckSolution(const LinearProgram& linear_problem,
914  const glop::DenseRow& variable_values) {
915  glop::DenseColumn constraint_values(linear_problem.num_constraints(), 0);
916 
917  const SparseMatrix& matrix = linear_problem.GetSparseMatrix();
918  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
919  const Fractional lower_bound = linear_problem.variable_lower_bounds()[col];
920  const Fractional upper_bound = linear_problem.variable_upper_bounds()[col];
921  const Fractional value = variable_values[col];
922  if (lower_bound > value || upper_bound < value) {
923  LOG(ERROR) << "Variable " << col << " out of bound: " << value
924  << " should be in " << lower_bound << " .. " << upper_bound;
925  return false;
926  }
927 
928  for (const SparseColumn::Entry entry : matrix.column(col)) {
929  constraint_values[entry.row()] += entry.coefficient() * value;
930  }
931  }
932 
933  for (RowIndex row(0); row < linear_problem.num_constraints(); ++row) {
934  const Fractional lb = linear_problem.constraint_lower_bounds()[row];
935  const Fractional ub = linear_problem.constraint_upper_bounds()[row];
936  const Fractional value = constraint_values[row];
937  if (lb > value || ub < value) {
938  LOG(ERROR) << "Constraint " << row << " out of bound: " << value
939  << " should be in " << lb << " .. " << ub;
940  return false;
941  }
942  }
943 
944  return true;
945 }
946 
947 // Solves the given linear program and returns the solve status.
948 BopSolveStatus InternalSolve(const LinearProgram& linear_problem,
949  const BopParameters& parameters,
950  const DenseRow& initial_solution,
951  TimeLimit* time_limit, DenseRow* variable_values,
953  Fractional* best_bound) {
954  CHECK(variable_values != nullptr);
955  CHECK(objective_value != nullptr);
956  CHECK(best_bound != nullptr);
957  const bool use_initial_solution = (initial_solution.size() > 0);
958  if (use_initial_solution) {
959  CHECK_EQ(initial_solution.size(), linear_problem.num_variables());
960  }
961 
962  // Those values will only make sense when a solution is found, however we
963  // resize here such that one can access the values even if they don't mean
964  // anything.
965  variable_values->resize(linear_problem.num_variables(), 0);
966 
967  LinearBooleanProblem boolean_problem;
968  std::vector<bool> boolean_initial_solution;
969  IntegralProblemConverter converter;
970  if (!converter.ConvertToBooleanProblem(linear_problem, initial_solution,
971  &boolean_problem,
972  &boolean_initial_solution)) {
973  return BopSolveStatus::INVALID_PROBLEM;
974  }
975 
976  BopSolver bop_solver(boolean_problem);
977  bop_solver.SetParameters(parameters);
978  BopSolveStatus status = BopSolveStatus::NO_SOLUTION_FOUND;
979  if (use_initial_solution) {
980  BopSolution bop_solution(boolean_problem, "InitialSolution");
981  CHECK_EQ(boolean_initial_solution.size(), boolean_problem.num_variables());
982  for (int i = 0; i < boolean_initial_solution.size(); ++i) {
983  bop_solution.SetValue(VariableIndex(i), boolean_initial_solution[i]);
984  }
985  status = bop_solver.SolveWithTimeLimit(bop_solution, time_limit);
986  } else {
987  status = bop_solver.SolveWithTimeLimit(time_limit);
988  }
989  if (status == BopSolveStatus::OPTIMAL_SOLUTION_FOUND ||
990  status == BopSolveStatus::FEASIBLE_SOLUTION_FOUND) {
991  // Compute objective value.
992  const BopSolution& solution = bop_solver.best_solution();
993  CHECK(solution.IsFeasible());
994 
995  *objective_value = linear_problem.objective_offset();
996  for (ColIndex col(0); col < linear_problem.num_variables(); ++col) {
997  const int64_t value = converter.GetSolutionValue(col, solution);
998  (*variable_values)[col] = value;
999  *objective_value += value * linear_problem.objective_coefficients()[col];
1000  }
1001 
1002  CheckSolution(linear_problem, *variable_values);
1003 
1004  // TODO(user): Check that the scaled best bound from Bop is a valid one
1005  // even after conversion. If yes, remove the optimality test.
1006  *best_bound = status == BopSolveStatus::OPTIMAL_SOLUTION_FOUND
1007  ? *objective_value
1008  : bop_solver.GetScaledBestBound();
1009  }
1010  return status;
1011 }
1012 
1013 void RunOneBop(const BopParameters& parameters, int problem_index,
1014  const DenseRow& initial_solution, TimeLimit* time_limit,
1015  LPDecomposer* decomposer, DenseRow* variable_values,
1016  Fractional* objective_value, Fractional* best_bound,
1018  CHECK(decomposer != nullptr);
1019  CHECK(variable_values != nullptr);
1020  CHECK(objective_value != nullptr);
1021  CHECK(best_bound != nullptr);
1022  CHECK(status != nullptr);
1023 
1024  LinearProgram problem;
1025  decomposer->ExtractLocalProblem(problem_index, &problem);
1026  DenseRow local_initial_solution;
1027  if (initial_solution.size() > 0) {
1028  local_initial_solution =
1029  decomposer->ExtractLocalAssignment(problem_index, initial_solution);
1030  }
1031  // TODO(user): Investigate a better approximation of the time needed to
1032  // solve the problem than just the number of variables.
1033  const double total_num_variables = std::max(
1034  1.0, static_cast<double>(
1035  decomposer->original_problem().num_variables().value()));
1036  const double time_per_variable =
1037  parameters.max_time_in_seconds() / total_num_variables;
1038  const double deterministic_time_per_variable =
1039  parameters.max_deterministic_time() / total_num_variables;
1040  const int local_num_variables = std::max(1, problem.num_variables().value());
1041 
1042  NestedTimeLimit subproblem_time_limit(
1043  time_limit,
1044  std::max(time_per_variable * local_num_variables,
1045  parameters.decomposed_problem_min_time_in_seconds()),
1046  deterministic_time_per_variable * local_num_variables);
1047 
1048  *status = InternalSolve(problem, parameters, local_initial_solution,
1049  subproblem_time_limit.GetTimeLimit(), variable_values,
1050  objective_value, best_bound);
1051 }
1052 } // anonymous namespace
1053 
1054 IntegralSolver::IntegralSolver()
1055  : parameters_(), variable_values_(), objective_value_(0.0) {}
1056 
1057 BopSolveStatus IntegralSolver::Solve(const LinearProgram& linear_problem) {
1058  return Solve(linear_problem, DenseRow());
1059 }
1060 
1062  const LinearProgram& linear_problem, TimeLimit* time_limit) {
1063  return SolveWithTimeLimit(linear_problem, DenseRow(), time_limit);
1064 }
1065 
1067  const LinearProgram& linear_problem,
1068  const DenseRow& user_provided_initial_solution) {
1069  std::unique_ptr<TimeLimit> time_limit =
1070  TimeLimit::FromParameters(parameters_);
1071  return SolveWithTimeLimit(linear_problem, user_provided_initial_solution,
1072  time_limit.get());
1073 }
1074 
1076  const LinearProgram& linear_problem,
1077  const DenseRow& user_provided_initial_solution, TimeLimit* time_limit) {
1078  // We make a copy so that we can clear it if the presolve is active.
1079  DenseRow initial_solution = user_provided_initial_solution;
1080  if (initial_solution.size() > 0) {
1081  CHECK_EQ(initial_solution.size(), linear_problem.num_variables())
1082  << "The initial solution should have the same number of variables as "
1083  "the LinearProgram.";
1084  }
1085 
1086  // Some code path requires to copy the given linear_problem. When this
1087  // happens, we will simply change the target of this pointer.
1088  LinearProgram const* lp = &linear_problem;
1089 
1091  if (lp->num_variables() >= parameters_.decomposer_num_variables_threshold()) {
1092  LPDecomposer decomposer;
1093  decomposer.Decompose(lp);
1094  const int num_sub_problems = decomposer.GetNumberOfProblems();
1095  VLOG(1) << "Problem is decomposable into " << num_sub_problems
1096  << " components!";
1097  if (num_sub_problems > 1) {
1098  // The problem can be decomposed. Solve each sub-problem and aggregate the
1099  // result.
1100  std::vector<DenseRow> variable_values(num_sub_problems);
1101  std::vector<Fractional> objective_values(num_sub_problems,
1102  Fractional(0.0));
1103  std::vector<Fractional> best_bounds(num_sub_problems, Fractional(0.0));
1104  std::vector<BopSolveStatus> statuses(num_sub_problems,
1106 
1107  for (int i = 0; i < num_sub_problems; ++i) {
1108  RunOneBop(parameters_, i, initial_solution, time_limit, &decomposer,
1109  &(variable_values[i]), &(objective_values[i]),
1110  &(best_bounds[i]), &(statuses[i]));
1111  }
1112 
1113  // Aggregate results.
1115  objective_value_ = lp->objective_offset();
1116  best_bound_ = 0.0;
1117  for (int i = 0; i < num_sub_problems; ++i) {
1118  objective_value_ += objective_values[i];
1119  best_bound_ += best_bounds[i];
1120  if (statuses[i] == BopSolveStatus::NO_SOLUTION_FOUND ||
1121  statuses[i] == BopSolveStatus::INFEASIBLE_PROBLEM ||
1122  statuses[i] == BopSolveStatus::INVALID_PROBLEM) {
1123  return statuses[i];
1124  }
1125 
1126  if (statuses[i] == BopSolveStatus::FEASIBLE_SOLUTION_FOUND) {
1128  }
1129  }
1130  variable_values_ = decomposer.AggregateAssignments(variable_values);
1131  CheckSolution(*lp, variable_values_);
1132  } else {
1133  status =
1134  InternalSolve(*lp, parameters_, initial_solution, time_limit,
1135  &variable_values_, &objective_value_, &best_bound_);
1136  }
1137  } else {
1138  status = InternalSolve(*lp, parameters_, initial_solution, time_limit,
1139  &variable_values_, &objective_value_, &best_bound_);
1140  }
1141 
1142  return status;
1143 }
1144 
1145 } // namespace bop
1146 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
size_type size() const
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
static std::unique_ptr< TimeLimit > FromParameters(const Parameters &parameters)
Creates a time limit object initialized from an object that provides methods max_time_in_seconds() an...
Definition: time_limit.h:159
ABSL_MUST_USE_RESULT BopSolveStatus SolveWithTimeLimit(const glop::LinearProgram &linear_problem, TimeLimit *time_limit)
const glop::DenseRow & variable_values() const
ABSL_MUST_USE_RESULT BopSolveStatus Solve(const glop::LinearProgram &linear_problem)
SatParameters parameters
ModelSharedTimeLimit * time_limit
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:41
absl::Span< const double > coefficients
const int64_t offset_
Definition: interval.cc:2109
ColIndex col
Definition: markowitz.cc:186
RowIndex row
Definition: markowitz.cc:185
bool CheckSolution(const Model &model, const std::function< int64_t(Variable *)> &evaluator, SolverLogger *logger)
Definition: checker.cc:1238
StrictITIVector< ColIndex, Fractional > DenseRow
Definition: lp_types.h:341
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:53
StrictITIVector< RowIndex, Fractional > DenseColumn
Definition: lp_types.h:370
void ChangeOptimizationDirection(LinearBooleanProblem *problem)
Collection of objects used to extend the Constraint Solver library.
bool IsIntegerWithinTolerance(FloatType x, FloatType tolerance)
Definition: fp_utils.h:165
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
int MostSignificantBitPosition64(uint64_t n)
Definition: bitset.h:232
int64_t weight
Definition: pack.cc:510
EntryIndex num_entries
int64_t delta
Definition: resource.cc:1695
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
double max_scaling_factor
constexpr double kInfinity
double objective_value
#define VLOG(verboselevel)
Definition: vlog.h:39