Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
feasibility_pump.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/log/check.h"
25#include "absl/meta/type_traits.h"
28#include "ortools/glop/parameters.pb.h"
30#include "ortools/glop/status.h"
36#include "ortools/sat/integer.h"
39#include "ortools/sat/model.h"
41#include "ortools/sat/sat_parameters.pb.h"
44#include "ortools/sat/util.h"
49
50namespace operations_research {
51namespace sat {
52
53using glop::ColIndex;
56using glop::RowIndex;
57
58const double FeasibilityPump::kCpEpsilon = 1e-4;
59
61 : sat_parameters_(*(model->GetOrCreate<SatParameters>())),
62 time_limit_(model->GetOrCreate<TimeLimit>()),
63 integer_trail_(model->GetOrCreate<IntegerTrail>()),
64 trail_(model->GetOrCreate<Trail>()),
65 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
66 incomplete_solutions_(model->Mutable<SharedIncompleteSolutionManager>()),
67 sat_solver_(model->GetOrCreate<SatSolver>()),
68 domains_(model->GetOrCreate<IntegerDomains>()),
69 mapping_(model->Get<CpModelMapping>()) {
70 // Tweak the default parameters to make the solve incremental.
71 glop::GlopParameters parameters;
72 // Note(user): Primal simplex does better here since we have a limit on
73 // simplex iterations. So dual simplex sometimes fails to find a LP feasible
74 // solution.
75 parameters.set_use_dual_simplex(false);
76 parameters.set_max_number_of_iterations(2000);
77 simplex_.SetParameters(parameters);
78 lp_data_.Clear();
79 integer_lp_.clear();
80}
81
83 VLOG(1) << "Feasibility Pump Total number of simplex iterations: "
84 << total_num_simplex_iterations_;
85}
86
88 // We still create the mirror variable right away though.
89 for (const IntegerVariable var : ct.VarsAsSpan()) {
90 GetOrCreateMirrorVariable(PositiveVariable(var));
91 }
92
93 integer_lp_.push_back(LinearConstraintInternal());
94 LinearConstraintInternal& new_ct = integer_lp_.back();
95 new_ct.lb = ct.lb;
96 new_ct.ub = ct.ub;
97 CHECK_LE(ct.lb, ct.ub);
98 for (int i = 0; i < ct.num_terms; ++i) {
99 // We only use positive variable inside this class.
100 IntegerVariable var = ct.vars[i];
101 IntegerValue coeff = ct.coeffs[i];
102 if (!VariableIsPositive(var)) {
103 var = NegationOf(var);
104 coeff = -coeff;
105 }
106 new_ct.terms.push_back({GetOrCreateMirrorVariable(var), coeff});
107 }
108 // Important to keep lp_data_ "clean".
109 std::sort(new_ct.terms.begin(), new_ct.terms.end());
110}
111
113 IntegerValue coeff) {
114 objective_is_defined_ = true;
115 const IntegerVariable pos_var =
116 VariableIsPositive(ivar) ? ivar : NegationOf(ivar);
117 if (ivar != pos_var) coeff = -coeff;
118
119 const auto it = mirror_lp_variable_.find(pos_var);
120 if (it == mirror_lp_variable_.end()) return;
121 const ColIndex col = it->second;
122 integer_objective_.push_back({col, coeff});
123 objective_infinity_norm_ =
124 std::max(objective_infinity_norm_, IntTypeAbs(coeff));
125}
126
127ColIndex FeasibilityPump::GetOrCreateMirrorVariable(
128 IntegerVariable positive_variable) {
129 DCHECK(VariableIsPositive(positive_variable));
130
131 const auto it = mirror_lp_variable_.find(positive_variable);
132 if (it == mirror_lp_variable_.end()) {
133 const int model_var =
134 mapping_->GetProtoVariableFromIntegerVariable(positive_variable);
135 model_vars_size_ = std::max(model_vars_size_, model_var + 1);
136
137 const ColIndex col(integer_variables_.size());
138 mirror_lp_variable_[positive_variable] = col;
139 integer_variables_.push_back(positive_variable);
140 var_is_binary_.push_back(false);
141 lp_solution_.push_back(std::numeric_limits<double>::infinity());
142 integer_solution_.push_back(0);
143
144 return col;
145 }
146 return it->second;
147}
148
149void FeasibilityPump::PrintStats() {
150 if (lp_solution_is_set_) {
151 VLOG(2) << "Fractionality: " << lp_solution_fractionality_;
152 } else {
153 VLOG(2) << "Fractionality: NA";
154 VLOG(2) << "simplex status: " << simplex_.GetProblemStatus();
155 }
156
157 if (integer_solution_is_set_) {
158 VLOG(2) << "#Infeasible const: " << num_infeasible_constraints_;
159 VLOG(2) << "Infeasibility: " << integer_solution_infeasibility_;
160 } else {
161 VLOG(2) << "Infeasibility: NA";
162 }
163}
164
166 if (lp_data_.num_variables() == 0) {
167 InitializeWorkingLP();
168 }
169 UpdateBoundsOfLpVariables();
170 lp_solution_is_set_ = false;
171 integer_solution_is_set_ = false;
172
173 // Restore the original objective
174 for (ColIndex col(0); col < lp_data_.num_variables(); ++col) {
175 lp_data_.SetObjectiveCoefficient(col, 0.0);
176 }
177 for (const auto& term : integer_objective_) {
178 lp_data_.SetObjectiveCoefficient(term.first, ToDouble(term.second));
179 }
180
181 mixing_factor_ = 1.0;
182 for (int i = 0; i < max_fp_iterations_; ++i) {
183 if (time_limit_->LimitReached()) break;
184 L1DistanceMinimize();
185 if (!SolveLp()) break;
186 if (lp_solution_is_integer_) break;
187 if (!Round()) break;
188 // We don't end this loop if the integer solutions is feasible in hope to
189 // get better solution.
190 if (integer_solution_is_feasible_) MaybePushToRepo();
191 }
192
193 if (sat_solver_->ModelIsUnsat()) return false;
194
195 PrintStats();
196 MaybePushToRepo();
197 return true;
198}
199
200void FeasibilityPump::MaybePushToRepo() {
201 if (incomplete_solutions_ == nullptr) return;
202 const double kInfinity = std::numeric_limits<double>::infinity();
203
204 // TODO(user): Consider adding solutions that have low fractionality.
205 if (lp_solution_is_integer_) {
206 // Fill the solution using LP solution values.
207 tmp_solution_.assign(model_vars_size_, kInfinity);
208 for (const IntegerVariable positive_var : integer_variables_) {
209 const int model_var =
210 mapping_->GetProtoVariableFromIntegerVariable(positive_var);
211 if (model_var >= 0 && model_var < model_vars_size_) {
212 tmp_solution_[model_var] = GetLPSolutionValue(positive_var);
213 }
214 }
215 incomplete_solutions_->AddSolution(tmp_solution_);
216 }
217
218 if (integer_solution_is_feasible_) {
219 // Fill the solution using Integer solution values.
220 tmp_solution_.assign(model_vars_size_, kInfinity);
221 for (const IntegerVariable positive_var : integer_variables_) {
222 const int model_var =
223 mapping_->GetProtoVariableFromIntegerVariable(positive_var);
224 if (model_var >= 0 && model_var < model_vars_size_) {
225 tmp_solution_[model_var] = GetIntegerSolutionValue(positive_var);
226 }
227 }
228 incomplete_solutions_->AddSolution(tmp_solution_);
229 }
230}
231
232// ----------------------------------------------------------------
233// -------------------LPSolving------------------------------------
234// ----------------------------------------------------------------
235
236void FeasibilityPump::InitializeWorkingLP() {
237 lp_data_.Clear();
238 // Create variables.
239 for (int i = 0; i < integer_variables_.size(); ++i) {
240 CHECK_EQ(ColIndex(i), lp_data_.CreateNewVariable());
241 lp_data_.SetVariableType(ColIndex(i),
243 }
244
245 // Add constraints.
246 for (const LinearConstraintInternal& ct : integer_lp_) {
247 const ConstraintIndex row = lp_data_.CreateNewConstraint();
248 lp_data_.SetConstraintBounds(row, ToDouble(ct.lb), ToDouble(ct.ub));
249 for (const auto& term : ct.terms) {
250 lp_data_.SetCoefficient(row, term.first, ToDouble(term.second));
251 }
252 }
253
254 // Add objective.
255 for (const auto& term : integer_objective_) {
256 lp_data_.SetObjectiveCoefficient(term.first, ToDouble(term.second));
257 }
258
259 const int num_vars = integer_variables_.size();
260 for (int i = 0; i < num_vars; i++) {
261 const IntegerVariable cp_var = integer_variables_[i];
262 const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
263 const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
264 lp_data_.SetVariableBounds(ColIndex(i), lb, ub);
265 }
266
267 objective_normalization_factor_ = 0.0;
268 glop::ColIndexVector integer_variables;
269 const ColIndex num_cols = lp_data_.num_variables();
270 for (ColIndex col : lp_data_.IntegerVariablesList()) {
271 var_is_binary_[col.value()] = lp_data_.IsVariableBinary(col);
272 if (!var_is_binary_[col.value()]) {
273 integer_variables.push_back(col);
274 }
275
276 // The aim of this normalization value is to compute a coefficient of the
277 // d_i variables that should be minimized.
278 objective_normalization_factor_ +=
279 std::abs(lp_data_.GetObjectiveCoefficientForMinimizationVersion(col));
280 }
281 CHECK_GT(lp_data_.IntegerVariablesList().size(), 0);
282 objective_normalization_factor_ =
283 objective_normalization_factor_ / lp_data_.IntegerVariablesList().size();
284
285 if (!integer_variables.empty()) {
286 // Update the LpProblem with norm variables and constraints.
287 norm_variables_.assign(num_cols, ColIndex(-1));
288 norm_lhs_constraints_.assign(num_cols, RowIndex(-1));
289 norm_rhs_constraints_.assign(num_cols, RowIndex(-1));
290 // For each integer non-binary variable x_i we introduce one new variable
291 // d_i subject to two new constraints:
292 // d_i - x_i >= -round(x'_i)
293 // d_i + x_i >= +round(x'_i)
294 // That's round(x'_i) - d_i <= x_i <= round(x'_i) + d_i, where d_i is an
295 // unbounded non-negative, and x'_i is the value of variable i from the
296 // previous solution obtained during the projection step. Consequently
297 // coefficients of the constraints are set here, but bounds of the
298 // constraints are updated at each iteration of the feasibility pump. Also
299 // coefficients of the objective are set here: x_i's are not present in the
300 // objective (i.e., coefficients set to 0.0), and d_i's are present in the
301 // objective with coefficients set to 1.0.
302 // Note that the treatment of integer non-binary variables is different
303 // from the treatment of binary variables. Binary variables do not impose
304 // any extra variables, nor extra constraints, but their objective
305 // coefficients are changed in the linear projection steps.
306 for (const ColIndex col : integer_variables) {
307 const ColIndex norm_variable = lp_data_.CreateNewVariable();
308 norm_variables_[col] = norm_variable;
309 lp_data_.SetVariableBounds(norm_variable, 0.0, glop::kInfinity);
310 const RowIndex row_a = lp_data_.CreateNewConstraint();
311 norm_lhs_constraints_[col] = row_a;
312 lp_data_.SetCoefficient(row_a, norm_variable, 1.0);
313 lp_data_.SetCoefficient(row_a, col, -1.0);
314 const RowIndex row_b = lp_data_.CreateNewConstraint();
315 norm_rhs_constraints_[col] = row_b;
316 lp_data_.SetCoefficient(row_b, norm_variable, 1.0);
317 lp_data_.SetCoefficient(row_b, col, 1.0);
318 }
319 }
320
321 scaler_.Scale(&lp_data_);
322 lp_data_.AddSlackVariablesWhereNecessary(
323 /*detect_integer_constraints=*/false);
324}
325
326void FeasibilityPump::L1DistanceMinimize() {
327 std::vector<double> new_obj_coeffs(lp_data_.num_variables().value(), 0.0);
328
329 // Set the original subobjective. The coefficients are scaled by mixing factor
330 // and the offset remains at 0 (because it does not affect the solution).
331 const ColIndex num_cols(lp_data_.objective_coefficients().size());
332 for (ColIndex col(0); col < num_cols; ++col) {
333 new_obj_coeffs[col.value()] =
334 mixing_factor_ * lp_data_.objective_coefficients()[col];
335 }
336
337 // Set the norm subobjective. The coefficients are scaled by 1 - mixing factor
338 // and the offset remains at 0 (because it does not affect the solution).
339 for (const ColIndex col : lp_data_.IntegerVariablesList()) {
340 if (var_is_binary_[col.value()]) {
341 const Fractional objective_coefficient =
342 mixing_factor_ * lp_data_.objective_coefficients()[col] +
343 (1 - mixing_factor_) * objective_normalization_factor_ *
344 (1 - 2 * integer_solution_[col.value()]);
345 new_obj_coeffs[col.value()] = objective_coefficient;
346 } else { // The variable is integer.
347 // Update the bounds of the constraints added in
348 // InitializeIntegerVariables() (see there for more details):
349 // d_i - x_i >= -round(x'_i)
350 // d_i + x_i >= +round(x'_i)
351
352 // TODO(user): We change both the objective and the bounds, thus
353 // breaking the incrementality. Handle integer variables differently,
354 // e.g., intensify rounding, or use soft fixing from: Fischetti, Lodi,
355 // "Local Branching", Math Program Ser B 98:23-47 (2003).
356 const Fractional objective_coefficient =
357 (1 - mixing_factor_) * objective_normalization_factor_;
358 new_obj_coeffs[norm_variables_[col].value()] = objective_coefficient;
359 // At this point, constraint bounds have already been transformed into
360 // bounds of slack variables. Instead of updating the constraints, we need
361 // to update the slack variables corresponding to them.
362 const ColIndex norm_lhs_slack_variable =
363 lp_data_.GetSlackVariable(norm_lhs_constraints_[col]);
364 const double lhs_scaling_factor =
365 scaler_.VariableScalingFactorWithSlack(norm_lhs_slack_variable);
366 lp_data_.SetVariableBounds(
367 norm_lhs_slack_variable, -glop::kInfinity,
368 lhs_scaling_factor * integer_solution_[col.value()]);
369 const ColIndex norm_rhs_slack_variable =
370 lp_data_.GetSlackVariable(norm_rhs_constraints_[col]);
371 const double rhs_scaling_factor =
372 scaler_.VariableScalingFactorWithSlack(norm_rhs_slack_variable);
373 lp_data_.SetVariableBounds(
374 norm_rhs_slack_variable, -glop::kInfinity,
375 -rhs_scaling_factor * integer_solution_[col.value()]);
376 }
377 }
378 for (ColIndex col(0); col < lp_data_.num_variables(); ++col) {
379 lp_data_.SetObjectiveCoefficient(col, new_obj_coeffs[col.value()]);
380 }
381 // TODO(user): Tune this or expose as parameter.
382 mixing_factor_ *= 0.8;
383}
384
385bool FeasibilityPump::SolveLp() {
386 const int num_vars = integer_variables_.size();
387 VLOG(3) << "LP relaxation: " << lp_data_.GetDimensionString() << ".";
388
389 const auto status = simplex_.Solve(lp_data_, time_limit_);
390 total_num_simplex_iterations_ += simplex_.GetNumberOfIterations();
391 if (!status.ok()) {
392 VLOG(1) << "The LP solver encountered an error: " << status.error_message();
393 simplex_.ClearStateForNextSolve();
394 return false;
395 }
396
397 // TODO(user): This shouldn't really happen except if the problem is UNSAT.
398 // But we can't just rely on a potentially imprecise LP to close the problem.
399 // The rest of the solver should do that with exact precision.
400 VLOG(3) << "simplex status: " << simplex_.GetProblemStatus();
401 if (simplex_.GetProblemStatus() == glop::ProblemStatus::PRIMAL_INFEASIBLE) {
402 return false;
403 }
404
405 lp_solution_fractionality_ = 0.0;
406 if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL ||
407 simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE ||
408 simplex_.GetProblemStatus() == glop::ProblemStatus::PRIMAL_FEASIBLE ||
409 simplex_.GetProblemStatus() == glop::ProblemStatus::IMPRECISE) {
410 lp_solution_is_set_ = true;
411 for (int i = 0; i < num_vars; i++) {
412 const double value = GetVariableValueAtCpScale(ColIndex(i));
413 lp_solution_[i] = value;
414 lp_solution_fractionality_ = std::max(
415 lp_solution_fractionality_, std::abs(value - std::round(value)));
416 }
417
418 // Compute the objective value.
419 lp_objective_ = 0;
420 for (const auto& term : integer_objective_) {
421 lp_objective_ += lp_solution_[term.first.value()] * term.second.value();
422 }
423 lp_solution_is_integer_ = lp_solution_fractionality_ < kCpEpsilon;
424 }
425 return true;
426}
427
428void FeasibilityPump::UpdateBoundsOfLpVariables() {
429 const int num_vars = integer_variables_.size();
430 for (int i = 0; i < num_vars; i++) {
431 const IntegerVariable cp_var = integer_variables_[i];
432 const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(cp_var));
433 const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(cp_var));
434 const double factor = scaler_.VariableScalingFactor(ColIndex(i));
435 lp_data_.SetVariableBounds(ColIndex(i), lb * factor, ub * factor);
436 }
437}
438
439double FeasibilityPump::GetLPSolutionValue(IntegerVariable variable) const {
440 return lp_solution_[mirror_lp_variable_.at(variable).value()];
442
443double FeasibilityPump::GetVariableValueAtCpScale(ColIndex var) {
444 return scaler_.UnscaleVariableValue(var, simplex_.GetVariableValue(var));
445}
446
447// ----------------------------------------------------------------
448// -------------------Rounding-------------------------------------
449// ----------------------------------------------------------------
450
452 IntegerVariable variable) const {
453 return integer_solution_[mirror_lp_variable_.at(variable).value()];
454}
456bool FeasibilityPump::Round() {
457 bool rounding_successful = true;
458 if (sat_parameters_.fp_rounding() == SatParameters::NEAREST_INTEGER) {
459 rounding_successful = NearestIntegerRounding();
460 } else if (sat_parameters_.fp_rounding() == SatParameters::LOCK_BASED) {
461 rounding_successful = LockBasedRounding();
462 } else if (sat_parameters_.fp_rounding() ==
463 SatParameters::ACTIVE_LOCK_BASED) {
464 rounding_successful = ActiveLockBasedRounding();
465 } else if (sat_parameters_.fp_rounding() ==
466 SatParameters::PROPAGATION_ASSISTED) {
467 rounding_successful = PropagationRounding();
468 }
469 if (!rounding_successful) return false;
470 FillIntegerSolutionStats();
471 return true;
472}
473
474bool FeasibilityPump::NearestIntegerRounding() {
475 if (!lp_solution_is_set_) return false;
476 for (int i = 0; i < lp_solution_.size(); ++i) {
477 integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
478 }
479 integer_solution_is_set_ = true;
480 return true;
481}
482
483bool FeasibilityPump::LockBasedRounding() {
484 if (!lp_solution_is_set_) return false;
485 const int num_vars = integer_variables_.size();
486
487 // We compute the number of locks based on variable coefficient in constraints
488 // and constraint bounds. This doesn't change over time so we cache it.
489 if (var_up_locks_.empty()) {
490 var_up_locks_.resize(num_vars, 0);
491 var_down_locks_.resize(num_vars, 0);
492 for (int i = 0; i < num_vars; ++i) {
493 for (const auto entry : lp_data_.GetSparseColumn(ColIndex(i))) {
494 ColIndex slack = lp_data_.GetSlackVariable(entry.row());
495 const bool constraint_upper_bounded =
496 lp_data_.variable_lower_bounds()[slack] > -glop::kInfinity;
497
498 const bool constraint_lower_bounded =
499 lp_data_.variable_upper_bounds()[slack] < glop::kInfinity;
500
501 if (entry.coefficient() > 0) {
502 var_up_locks_[i] += constraint_upper_bounded;
503 var_down_locks_[i] += constraint_lower_bounded;
504 } else {
505 var_up_locks_[i] += constraint_lower_bounded;
506 var_down_locks_[i] += constraint_upper_bounded;
507 }
508 }
509 }
510 }
511
512 for (int i = 0; i < lp_solution_.size(); ++i) {
513 if (std::abs(lp_solution_[i] - std::round(lp_solution_[i])) < 0.1 ||
514 var_up_locks_[i] == var_down_locks_[i]) {
515 integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
516 } else if (var_up_locks_[i] > var_down_locks_[i]) {
517 integer_solution_[i] = static_cast<int64_t>(std::floor(lp_solution_[i]));
518 } else {
519 integer_solution_[i] = static_cast<int64_t>(std::ceil(lp_solution_[i]));
520 }
521 }
522 integer_solution_is_set_ = true;
523 return true;
524}
525
526bool FeasibilityPump::ActiveLockBasedRounding() {
527 if (!lp_solution_is_set_) return false;
528 const int num_vars = integer_variables_.size();
529
530 // We compute the number of locks based on variable coefficient in constraints
531 // and constraint bounds of active constraints. We consider the bound of the
532 // constraint that is tight for the current lp solution.
533 for (int i = 0; i < num_vars; ++i) {
534 if (std::abs(lp_solution_[i] - std::round(lp_solution_[i])) < 0.1) {
535 integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
536 }
537
538 int up_locks = 0;
539 int down_locks = 0;
540 for (const auto entry : lp_data_.GetSparseColumn(ColIndex(i))) {
541 const ConstraintStatus row_status =
542 simplex_.GetConstraintStatus(entry.row());
543 if (row_status == ConstraintStatus::AT_LOWER_BOUND) {
544 if (entry.coefficient() > 0) {
545 down_locks++;
546 } else {
547 up_locks++;
548 }
549 } else if (row_status == ConstraintStatus::AT_UPPER_BOUND) {
550 if (entry.coefficient() > 0) {
551 up_locks++;
552 } else {
553 down_locks++;
554 }
555 }
556 }
557 if (up_locks == down_locks) {
558 integer_solution_[i] = static_cast<int64_t>(std::round(lp_solution_[i]));
559 } else if (up_locks > down_locks) {
560 integer_solution_[i] = static_cast<int64_t>(std::floor(lp_solution_[i]));
561 } else {
562 integer_solution_[i] = static_cast<int64_t>(std::ceil(lp_solution_[i]));
563 }
564 }
565
566 integer_solution_is_set_ = true;
567 return true;
568}
569
570bool FeasibilityPump::PropagationRounding() {
571 if (!lp_solution_is_set_) return false;
572 if (!sat_solver_->ResetToLevelZero()) return false;
573
574 // Compute an order in which we will fix variables and do the propagation.
575 std::vector<int> rounding_order;
576 {
577 std::vector<std::pair<double, int>> binary_fractionality_vars;
578 std::vector<std::pair<double, int>> general_fractionality_vars;
579 for (int i = 0; i < lp_solution_.size(); ++i) {
580 const double fractionality =
581 std::abs(std::round(lp_solution_[i]) - lp_solution_[i]);
582 if (var_is_binary_[i]) {
583 binary_fractionality_vars.push_back({fractionality, i});
584 } else {
585 general_fractionality_vars.push_back({fractionality, i});
586 }
587 }
588 std::sort(binary_fractionality_vars.begin(),
589 binary_fractionality_vars.end());
590 std::sort(general_fractionality_vars.begin(),
591 general_fractionality_vars.end());
592
593 for (int i = 0; i < binary_fractionality_vars.size(); ++i) {
594 rounding_order.push_back(binary_fractionality_vars[i].second);
595 }
596 for (int i = 0; i < general_fractionality_vars.size(); ++i) {
597 rounding_order.push_back(general_fractionality_vars[i].second);
598 }
599 }
600
601 for (const int var_index : rounding_order) {
602 if (time_limit_->LimitReached()) return false;
603 // Get the bounds of the variable.
604 const IntegerVariable var = integer_variables_[var_index];
605 CHECK(VariableIsPositive(var));
606 const Domain& domain = (*domains_)[GetPositiveOnlyIndex(var)];
607
608 const IntegerValue lb = integer_trail_->LowerBound(var);
609 const IntegerValue ub = integer_trail_->UpperBound(var);
610 if (lb == ub) {
611 integer_solution_[var_index] = lb.value();
612 continue;
613 }
614
615 const int64_t rounded_value =
616 SafeDoubleToInt64(std::round(lp_solution_[var_index]));
617 const int64_t floor_value =
618 SafeDoubleToInt64(std::floor(lp_solution_[var_index]));
619 const int64_t ceil_value =
620 SafeDoubleToInt64(std::ceil(lp_solution_[var_index]));
621
622 const bool floor_is_in_domain =
623 (domain.Contains(floor_value) && lb.value() <= floor_value);
624 const bool ceil_is_in_domain =
625 (domain.Contains(ceil_value) && ub.value() >= ceil_value);
626 if (domain.IsEmpty()) {
627 integer_solution_[var_index] = rounded_value;
628 sat_solver_->NotifyThatModelIsUnsat();
629 return false;
630 }
631
632 if (ceil_value < lb.value()) {
633 integer_solution_[var_index] = lb.value();
634 } else if (floor_value > ub.value()) {
635 integer_solution_[var_index] = ub.value();
636 } else if (ceil_is_in_domain && floor_is_in_domain) {
637 DCHECK(domain.Contains(rounded_value));
638 integer_solution_[var_index] = rounded_value;
639 } else if (ceil_is_in_domain) {
640 integer_solution_[var_index] = ceil_value;
641 } else if (floor_is_in_domain) {
642 integer_solution_[var_index] = floor_value;
643 } else {
644 const std::pair<IntegerLiteral, IntegerLiteral> values_in_domain =
645 integer_encoder_->Canonicalize(
646 IntegerLiteral::GreaterOrEqual(var, IntegerValue(rounded_value)));
647 const int64_t lower_value = values_in_domain.first.bound.value();
648 const int64_t higher_value = -values_in_domain.second.bound.value();
649 const int64_t distance_from_lower_value =
650 std::abs(lower_value - rounded_value);
651 const int64_t distance_from_higher_value =
652 std::abs(higher_value - rounded_value);
653
654 integer_solution_[var_index] =
655 (distance_from_lower_value < distance_from_higher_value)
656 ? lower_value
657 : higher_value;
658 }
659
660 CHECK(domain.Contains(integer_solution_[var_index]));
661 CHECK_GE(integer_solution_[var_index], lb);
662 CHECK_LE(integer_solution_[var_index], ub);
663
664 // Propagate the value.
665 //
666 // When we want to fix the variable at its lb or ub, we do not create an
667 // equality literal to minimize the number of new literal we create. This
668 // is because creating an "== value" literal will implicitly also create
669 // a ">= value" and a "<= value" literals.
670 Literal to_enqueue;
671 const IntegerValue value(integer_solution_[var_index]);
672 if (value == lb) {
673 to_enqueue = integer_encoder_->GetOrCreateAssociatedLiteral(
674 IntegerLiteral::LowerOrEqual(var, value));
675 } else if (value == ub) {
676 to_enqueue = integer_encoder_->GetOrCreateAssociatedLiteral(
678 } else {
679 to_enqueue =
680 integer_encoder_->GetOrCreateLiteralAssociatedToEquality(var, value);
681 }
682
683 if (!sat_solver_->FinishPropagation()) return false;
684 sat_solver_->EnqueueDecisionAndBacktrackOnConflict(to_enqueue);
685 if (sat_solver_->ModelIsUnsat()) return false;
686 }
687 integer_solution_is_set_ = true;
688 return sat_solver_->ResetToLevelZero();
689}
690
691void FeasibilityPump::FillIntegerSolutionStats() {
692 // Compute the objective value.
693 integer_solution_objective_ = 0;
694 for (const auto& term : integer_objective_) {
695 integer_solution_objective_ +=
696 integer_solution_[term.first.value()] * term.second.value();
697 }
698
699 integer_solution_is_feasible_ = true;
700 num_infeasible_constraints_ = 0;
701 integer_solution_infeasibility_ = 0;
702 for (RowIndex i(0); i < integer_lp_.size(); ++i) {
703 int64_t activity = 0;
704 for (const auto& term : integer_lp_[i].terms) {
705 const int64_t prod =
706 CapProd(integer_solution_[term.first.value()], term.second.value());
707 if (prod <= std::numeric_limits<int64_t>::min() ||
708 prod >= std::numeric_limits<int64_t>::max()) {
709 activity = prod;
710 break;
711 }
712 activity = CapAdd(activity, prod);
713 if (activity <= std::numeric_limits<int64_t>::min() ||
714 activity >= std::numeric_limits<int64_t>::max())
715 break;
716 }
717 if (activity > integer_lp_[i].ub || activity < integer_lp_[i].lb) {
718 integer_solution_is_feasible_ = false;
719 num_infeasible_constraints_++;
720 const int64_t ub_infeasibility =
721 activity > integer_lp_[i].ub.value()
722 ? activity - integer_lp_[i].ub.value()
723 : 0;
724 const int64_t lb_infeasibility =
725 activity < integer_lp_[i].lb.value()
726 ? integer_lp_[i].lb.value() - activity
727 : 0;
728 integer_solution_infeasibility_ =
729 std::max(integer_solution_infeasibility_,
730 std::max(ub_infeasibility, lb_infeasibility));
731 }
732 }
733}
734
735} // namespace sat
736} // namespace operations_research
@ INTEGER
The variable must only take integer values.
Definition lp_data.h:66
const DenseRow & variable_lower_bounds() const
Definition lp_data.h:237
ColIndex GetSlackVariable(RowIndex row) const
Definition lp_data.cc:766
const DenseRow & variable_upper_bounds() const
Definition lp_data.h:240
const SparseColumn & GetSparseColumn(ColIndex col) const
Definition lp_data.cc:418
Fractional UnscaleVariableValue(ColIndex col, Fractional value) const
Transforms corresponding value from the scaled domain to the original one.
Fractional GetVariableValue(ColIndex col) const
int GetProtoVariableFromIntegerVariable(IntegerVariable var) const
void SetObjectiveCoefficient(IntegerVariable ivar, IntegerValue coeff)
int64_t GetIntegerSolutionValue(IntegerVariable variable) const
bool Solve()
Returns false if the model is proven to be infeasible.
void AddLinearConstraint(const LinearConstraint &ct)
Add a new linear constraint to this LP.
double GetLPSolutionValue(IntegerVariable variable) const
void AddSolution(const std::vector< double > &lp_solution)
constexpr double kInfinity
Infinity for type Fractional.
Definition lp_types.h:87
std::vector< ColIndex > ColIndexVector
Vector of row or column indices. Useful to list the non-zero positions.
Definition lp_types.h:360
constexpr double kInfinity
Infinity for type Fractional.
Definition lp_types.h:87
IntType IntTypeAbs(IntType t)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
Definition integer.cc:52
IntegerVariable PositiveVariable(IntegerVariable i)
int64_t SafeDoubleToInt64(double value)
Definition util.h:728
PositiveOnlyIndex GetPositiveOnlyIndex(IntegerVariable var)
bool VariableIsPositive(IntegerVariable i)
double ToDouble(IntegerValue value)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
bool Contains(int64_t value) const
Various inclusion tests on a domain.
Definition model.cc:363
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
std::unique_ptr< IntegerValue[]> coeffs
std::unique_ptr< IntegerVariable[]> vars
absl::Span< const IntegerVariable > VarsAsSpan() const