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