Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_programming_constraint.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 <array>
18#include <cmath>
19#include <cstddef>
20#include <cstdint>
21#include <functional>
22#include <limits>
23#include <memory>
24#include <numeric>
25#include <optional>
26#include <string>
27#include <utility>
28#include <vector>
29
30#include "absl/algorithm/container.h"
31#include "absl/container/flat_hash_map.h"
32#include "absl/log/check.h"
33#include "absl/log/log.h"
34#include "absl/log/vlog_is_on.h"
35#include "absl/numeric/int128.h"
36#include "absl/strings/str_cat.h"
37#include "absl/strings/str_format.h"
38#include "absl/strings/string_view.h"
39#include "absl/types/span.h"
46#include "ortools/glop/status.h"
53#include "ortools/sat/cuts.h"
55#include "ortools/sat/integer.h"
61#include "ortools/sat/model.h"
66#include "ortools/sat/util.h"
68#include "ortools/util/bitset.h"
69#include "ortools/util/rev.h"
73
74namespace operations_research {
75namespace sat {
76
77using glop::ColIndex;
79using glop::RowIndex;
80
82 if (is_sparse_) {
83 for (const glop::ColIndex col : non_zeros_) {
84 dense_vector_[col] = IntegerValue(0);
85 }
86 dense_vector_.resize(size, IntegerValue(0));
87 } else {
88 dense_vector_.assign(size, IntegerValue(0));
89 }
90 for (const glop::ColIndex col : non_zeros_) {
91 is_zeros_[col] = true;
92 }
93 is_zeros_.resize(size, true);
94 non_zeros_.clear();
95 is_sparse_ = true;
96}
97
98bool ScatteredIntegerVector::Add(glop::ColIndex col, IntegerValue value) {
99 const int64_t add = CapAdd(value.value(), dense_vector_[col].value());
100 if (add == std::numeric_limits<int64_t>::min() ||
101 add == std::numeric_limits<int64_t>::max())
102 return false;
103 dense_vector_[col] = IntegerValue(add);
104 if (is_sparse_ && is_zeros_[col]) {
105 is_zeros_[col] = false;
106 non_zeros_.push_back(col);
107 }
108 return true;
109}
110
111template <bool check_overflow>
113 const IntegerValue multiplier, absl::Span<const glop::ColIndex> cols,
114 absl::Span<const IntegerValue> coeffs, IntegerValue max_coeff_magnitude) {
115 // Since we have the norm, this avoid checking each products below.
116 if (check_overflow) {
117 const IntegerValue prod = CapProdI(max_coeff_magnitude, multiplier);
118 if (AtMinOrMaxInt64(prod.value())) return false;
119 }
120
121 IntegerValue* data = dense_vector_.data();
122 const double threshold = 0.1 * static_cast<double>(dense_vector_.size());
123 const int num_terms = cols.size();
124 if (is_sparse_ && static_cast<double>(num_terms) < threshold) {
125 for (int i = 0; i < num_terms; ++i) {
126 const glop::ColIndex col = cols[i];
127 if (is_zeros_[col]) {
128 is_zeros_[col] = false;
129 non_zeros_.push_back(col);
130 }
131 const IntegerValue product = multiplier * coeffs[i];
132 if (check_overflow) {
133 if (AddIntoOverflow(product.value(),
134 data[col.value()].mutable_value())) {
135 return false;
136 }
137 } else {
138 data[col.value()] += product;
139 }
140 }
141 if (static_cast<double>(non_zeros_.size()) > threshold) {
142 is_sparse_ = false;
143 }
144 } else {
145 is_sparse_ = false;
146 for (int i = 0; i < num_terms; ++i) {
147 const glop::ColIndex col = cols[i];
148 const IntegerValue product = multiplier * coeffs[i];
149 if (check_overflow) {
150 if (AddIntoOverflow(product.value(),
151 data[col.value()].mutable_value())) {
152 return false;
153 }
154 } else {
155 data[col.value()] += product;
156 }
157 }
158 }
159 return true;
160}
161
162// Force instantations for tests.
164 IntegerValue multiplier, absl::Span<const glop::ColIndex> cols,
165 absl::Span<const IntegerValue> coeffs, IntegerValue max_coeff_magnitude);
166
168 IntegerValue multiplier, absl::Span<const glop::ColIndex> cols,
169 absl::Span<const IntegerValue> coeffs, IntegerValue max_coeff_magnitude);
170
172 absl::Span<const IntegerVariable> integer_variables,
173 IntegerValue upper_bound,
174 std::optional<std::pair<IntegerVariable, IntegerValue>> extra_term) {
175 // We first do one pass to compute the exact size and not overallocate.
176 int final_size = 0;
177 if (is_sparse_) {
178 for (const glop::ColIndex col : non_zeros_) {
179 const IntegerValue coeff = dense_vector_[col];
180 if (coeff == 0) continue;
181 ++final_size;
182 }
183 } else {
184 for (const IntegerValue coeff : dense_vector_) {
185 if (coeff != 0) ++final_size;
186 }
187 }
188 if (extra_term != std::nullopt) ++final_size;
189
190 // Allocate once.
191 LinearConstraint result;
192 result.resize(final_size);
193
194 // Copy terms.
195 int new_size = 0;
196 if (is_sparse_) {
197 std::sort(non_zeros_.begin(), non_zeros_.end());
198 for (const glop::ColIndex col : non_zeros_) {
199 const IntegerValue coeff = dense_vector_[col];
200 if (coeff == 0) continue;
201 result.vars[new_size] = integer_variables[col.value()];
202 result.coeffs[new_size] = coeff;
203 ++new_size;
204 }
205 } else {
206 const int size = dense_vector_.size();
207 for (glop::ColIndex col(0); col < size; ++col) {
208 const IntegerValue coeff = dense_vector_[col];
209 if (coeff == 0) continue;
210 result.vars[new_size] = integer_variables[col.value()];
211 result.coeffs[new_size] = coeff;
212 ++new_size;
213 }
214 }
215
216 result.lb = kMinIntegerValue;
217 result.ub = upper_bound;
218
219 if (extra_term != std::nullopt) {
220 result.vars[new_size] += extra_term->first;
221 result.coeffs[new_size] += extra_term->second;
222 ++new_size;
223 }
224
225 CHECK_EQ(new_size, final_size);
226 DivideByGCD(&result);
227 return result;
228}
229
231 absl::int128 rhs, absl::Span<const IntegerVariable> integer_variables,
232 absl::Span<const double> lp_solution, IntegerTrail* integer_trail,
233 CutData* result) {
234 result->terms.clear();
235 result->rhs = rhs;
236 absl::Span<const IntegerValue> dense_vector = dense_vector_;
237 if (is_sparse_) {
238 std::sort(non_zeros_.begin(), non_zeros_.end());
239 for (const glop::ColIndex col : non_zeros_) {
240 const IntegerValue coeff = dense_vector[col.value()];
241 if (coeff == 0) continue;
242 const IntegerVariable var = integer_variables[col.value()];
243 CHECK(result->AppendOneTerm(var, coeff, lp_solution[col.value()],
244 integer_trail->LevelZeroLowerBound(var),
245 integer_trail->LevelZeroUpperBound(var)));
246 }
247 } else {
248 for (int col(0); col < dense_vector.size(); ++col) {
249 const IntegerValue coeff = dense_vector[col];
250 if (coeff == 0) continue;
251 const IntegerVariable var = integer_variables[col];
252 CHECK(result->AppendOneTerm(var, coeff, lp_solution[col],
253 integer_trail->LevelZeroLowerBound(var),
254 integer_trail->LevelZeroUpperBound(var)));
255 }
256 }
257}
258
259std::vector<std::pair<glop::ColIndex, IntegerValue>>
261 std::vector<std::pair<glop::ColIndex, IntegerValue>> result;
262 if (is_sparse_) {
263 std::sort(non_zeros_.begin(), non_zeros_.end());
264 for (const glop::ColIndex col : non_zeros_) {
265 const IntegerValue coeff = dense_vector_[col];
266 if (coeff != 0) result.push_back({col, coeff});
267 }
268 } else {
269 const int size = dense_vector_.size();
270 for (glop::ColIndex col(0); col < size; ++col) {
271 const IntegerValue coeff = dense_vector_[col];
272 if (coeff != 0) result.push_back({col, coeff});
273 }
274 }
275 return result;
276}
277
278// TODO(user): make SatParameters singleton too, otherwise changing them after
279// a constraint was added will have no effect on this class.
281 Model* model, absl::Span<const IntegerVariable> vars)
282 : constraint_manager_(model),
283 parameters_(*(model->GetOrCreate<SatParameters>())),
284 model_(model),
285 time_limit_(model->GetOrCreate<TimeLimit>()),
286 integer_trail_(model->GetOrCreate<IntegerTrail>()),
287 trail_(model->GetOrCreate<Trail>()),
288 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
289 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
290 product_detector_(model->GetOrCreate<ProductDetector>()),
291 objective_definition_(model->GetOrCreate<ObjectiveDefinition>()),
292 shared_stats_(model->GetOrCreate<SharedStatistics>()),
293 shared_response_manager_(model->GetOrCreate<SharedResponseManager>()),
294 random_(model->GetOrCreate<ModelRandomGenerator>()),
295 symmetrizer_(model->GetOrCreate<LinearConstraintSymmetrizer>()),
296 linear_propagator_(model->GetOrCreate<LinearPropagator>()),
297 rlt_cut_helper_(model),
298 implied_bounds_processor_({}, integer_trail_,
299 model->GetOrCreate<ImpliedBounds>()),
300 dispatcher_(model->GetOrCreate<LinearProgrammingDispatcher>()),
301 mirror_lp_variable_(*model->GetOrCreate<ModelLpVariableMapping>()),
302 expanded_lp_solution_(*model->GetOrCreate<ModelLpValues>()),
303 expanded_reduced_costs_(*model->GetOrCreate<ModelReducedCosts>()) {
304 // Tweak the default parameters to make the solve incremental.
305 simplex_params_.set_use_dual_simplex(true);
306 simplex_params_.set_primal_feasibility_tolerance(
307 parameters_.lp_primal_tolerance());
308 simplex_params_.set_dual_feasibility_tolerance(
309 parameters_.lp_dual_tolerance());
310 if (parameters_.use_exact_lp_reason()) {
311 simplex_params_.set_change_status_to_imprecise(false);
312 }
313 simplex_.SetParameters(simplex_params_);
314 // Warning: SetRandom() must be called after SetParameters().
315 simplex_.SetRandom(*random_);
316 if (parameters_.search_branching() == SatParameters::LP_SEARCH) {
317 compute_reduced_cost_averages_ = true;
318 }
319
320 // Register our local rev int repository.
321 integer_trail_->RegisterReversibleClass(&rc_rev_int_repository_);
322 mirror_lp_variable_.resize(integer_trail_->NumIntegerVariables(),
324
325 // Register SharedStatistics with the cut helpers.
326 auto* stats = model->GetOrCreate<SharedStatistics>();
327 integer_rounding_cut_helper_.SetSharedStatistics(stats);
328 cover_cut_helper_.SetSharedStatistics(stats);
329
330 // Initialize the IntegerVariable -> ColIndex mapping.
331 CHECK(std::is_sorted(vars.begin(), vars.end()));
332
333 // TODO(user): We shouldn't need to add variable from the orbit here in the
334 // presence of symmetry. However they can still appear in cut, so it is a
335 // bit tricky and require some refactoring to be tried.
336 ColIndex col{0};
337 for (const IntegerVariable positive_variable : vars) {
338 CHECK(VariableIsPositive(positive_variable));
339 implied_bounds_processor_.AddLpVariable(positive_variable);
340 (*dispatcher_)[positive_variable] = this;
341
342 if (!symmetrizer_->AppearInFoldedProblem(positive_variable)) continue;
343
344 integer_variables_.push_back(positive_variable);
345 extended_integer_variables_.push_back(positive_variable);
346 DCHECK_EQ(mirror_lp_variable_[positive_variable], glop::kInvalidCol);
347 mirror_lp_variable_[positive_variable] = col;
348 ++col;
349 }
350
351 // Complete the extended variables with the orbit afterwards.
352 if (symmetrizer_->HasSymmetry()) {
353 for (const IntegerVariable var : integer_variables_) {
354 if (!symmetrizer_->IsOrbitSumVar(var)) continue;
355 const int orbit_index = symmetrizer_->OrbitIndex(var);
356 for (const IntegerVariable var : symmetrizer_->Orbit(orbit_index)) {
357 extended_integer_variables_.push_back(var);
358 DCHECK_EQ(mirror_lp_variable_[var], glop::kInvalidCol);
359 mirror_lp_variable_[var] = col;
360 ++col;
361 }
362 }
363 }
364
365 // We use the "extended" vector for lp_solution_/lp_reduced_cost_.
366 CHECK_EQ(vars.size(), extended_integer_variables_.size());
367 lp_solution_.assign(vars.size(), std::numeric_limits<double>::infinity());
368 lp_reduced_cost_.assign(vars.size(), 0.0);
369
370 if (!vars.empty()) {
371 const IntegerVariable max_index = std::max(
372 NegationOf(vars.back()), integer_trail_->NumIntegerVariables() - 1);
373 if (max_index >= expanded_lp_solution_.size()) {
374 expanded_lp_solution_.assign(max_index.value() + 1, 0.0);
375 }
376 if (max_index >= expanded_reduced_costs_.size()) {
377 expanded_reduced_costs_.assign(max_index.value() + 1, 0.0);
378 }
379 }
380}
381
383 DCHECK(!lp_constraint_is_registered_);
384 bool added = false;
385 bool folded = false;
386 const auto index = constraint_manager_.Add(std::move(ct), &added, &folded);
387
388 // If we added a folded constraints, lets add it to the CP propagators.
389 // This is important as this should tighten the bounds of the orbit sum vars.
390 if (added && folded) {
391 const auto& info = constraint_manager_.AllConstraints()[index];
392 const LinearConstraint& new_ct = info.constraint;
393 const absl::Span<const IntegerVariable> vars = new_ct.VarsAsSpan();
394 if (!info.ub_is_trivial) {
395 if (!linear_propagator_->AddConstraint({}, vars, new_ct.CoeffsAsSpan(),
396 new_ct.ub)) {
397 return false;
398 }
399 }
400 if (!info.lb_is_trivial) {
401 tmp_vars_.assign(vars.begin(), vars.end());
402 for (IntegerVariable& var : tmp_vars_) var = NegationOf(var);
403 if (!linear_propagator_->AddConstraint(
404 {}, tmp_vars_, new_ct.CoeffsAsSpan(), -new_ct.lb)) {
405 return false;
406 }
407 }
408 }
409 return true;
410}
411
413 DCHECK(!lp_constraint_is_registered_);
414 lp_constraint_is_registered_ = true;
415 model->GetOrCreate<LinearProgrammingConstraintCollection>()->push_back(this);
416
417 // Copy objective data to the constraint_manager_.
418 //
419 // Note(user): the sort is not really needed but should lead to better cache
420 // locality.
421 std::sort(integer_objective_.begin(), integer_objective_.end());
422 objective_infinity_norm_ = 0;
423 for (const auto [col, coeff] : integer_objective_) {
424 constraint_manager_.SetObjectiveCoefficient(integer_variables_[col.value()],
425 coeff);
426 objective_infinity_norm_ =
427 std::max(objective_infinity_norm_, IntTypeAbs(coeff));
428 }
429
430 // Set the LP to its initial content.
431 //
432 // Note that we always add LP constraint lazily if we have A LOT of them.
433 // This is because currently on large problem with millions of constraints,
434 // our LP is usually not fast enough anyway.
435 if (!parameters_.add_lp_constraints_lazily() &&
436 constraint_manager_.num_constraints() < 1e6) {
437 constraint_manager_.AddAllConstraintsToLp();
438 }
439 if (!CreateLpFromConstraintManager()) {
440 model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
441 return;
442 }
443
444 watcher_id_ = watcher_->Register(this);
445 const int num_vars = integer_variables_.size();
446 orbit_indices_.clear();
447 for (int i = 0; i < num_vars; i++) {
448 const IntegerVariable pos_var = integer_variables_[i];
449 DCHECK(symmetrizer_->AppearInFoldedProblem(pos_var));
450 watcher_->WatchIntegerVariable(pos_var, watcher_id_, i);
451 if (symmetrizer_->IsOrbitSumVar(pos_var)) {
452 orbit_indices_.push_back(symmetrizer_->OrbitIndex(pos_var));
453 }
454 }
455 if (objective_is_defined_) {
456 watcher_->WatchUpperBound(objective_cp_, watcher_id_);
457 }
458 watcher_->SetPropagatorPriority(watcher_id_, 2);
459 watcher_->AlwaysCallAtLevelZero(watcher_id_);
460
461 // Registering it with the trail make sure this class is always in sync when
462 // it is used in the decision heuristics.
463 integer_trail_->RegisterReversibleClass(this);
464 watcher_->RegisterReversibleInt(watcher_id_, &rev_optimal_constraints_size_);
465}
466
467glop::ColIndex LinearProgrammingConstraint::GetMirrorVariable(
468 IntegerVariable positive_variable) {
469 DCHECK(VariableIsPositive(positive_variable));
470 return mirror_lp_variable_[positive_variable];
471}
472
474 IntegerValue coeff) {
475 CHECK(!lp_constraint_is_registered_);
476 objective_is_defined_ = true;
477 IntegerVariable pos_var = VariableIsPositive(ivar) ? ivar : NegationOf(ivar);
478 if (ivar != pos_var) coeff = -coeff;
479 integer_objective_.push_back({GetMirrorVariable(pos_var), coeff});
480}
481
482// TODO(user): As the search progress, some variables might get fixed. Exploit
483// this to reduce the number of variables in the LP and in the
484// ConstraintManager? We might also detect during the search that two variable
485// are equivalent.
486//
487// TODO(user): On TSP/VRP with a lot of cuts, this can take 20% of the overall
488// running time. We should be able to almost remove most of this from the
489// profile by being more incremental (modulo LP scaling).
490//
491// TODO(user): A longer term idea for LP with a lot of variables is to not
492// add all variables to each LP solve and do some "sifting". That can be useful
493// for TSP for instance where the number of edges is large, but only a small
494// fraction will be used in the optimal solution.
495bool LinearProgrammingConstraint::CreateLpFromConstraintManager() {
496 // Fill integer_lp_.
497 integer_lp_.clear();
498 integer_lp_cols_.clear();
499 integer_lp_coeffs_.clear();
500
501 infinity_norms_.clear();
502 const auto& all_constraints = constraint_manager_.AllConstraints();
503 for (const auto index : constraint_manager_.LpConstraints()) {
504 const LinearConstraint& ct = all_constraints[index].constraint;
505 if (ct.lb > ct.ub) {
506 VLOG(1) << "Trivial infeasible bound in an LP constraint";
507 return false;
508 }
509
510 integer_lp_.push_back(LinearConstraintInternal());
511 LinearConstraintInternal& new_ct = integer_lp_.back();
512 new_ct.lb = ct.lb;
513 new_ct.ub = ct.ub;
514 new_ct.lb_is_trivial = all_constraints[index].lb_is_trivial;
515 new_ct.ub_is_trivial = all_constraints[index].ub_is_trivial;
516
517 IntegerValue infinity_norm = 0;
518 infinity_norm = std::max(infinity_norm, IntTypeAbs(ct.lb));
519 infinity_norm = std::max(infinity_norm, IntTypeAbs(ct.ub));
520 new_ct.start_in_buffer = integer_lp_cols_.size();
521
522 // TODO(user): Make sure we don't have empty constraint!
523 // this currently can happen in some corner cases.
524 const int size = ct.num_terms;
525 new_ct.num_terms = size;
526 for (int i = 0; i < size; ++i) {
527 // We only use positive variable inside this class.
528 const IntegerVariable var = ct.vars[i];
529 const IntegerValue coeff = ct.coeffs[i];
530 infinity_norm = std::max(infinity_norm, IntTypeAbs(coeff));
531 integer_lp_cols_.push_back(GetMirrorVariable(var));
532 integer_lp_coeffs_.push_back(coeff);
533 }
534 infinity_norms_.push_back(infinity_norm);
535
536 // It is important to keep lp_data_ "clean".
537 DCHECK(std::is_sorted(
538 integer_lp_cols_.data() + new_ct.start_in_buffer,
539 integer_lp_cols_.data() + new_ct.start_in_buffer + new_ct.num_terms));
540 }
541
542 // We remove fixed variables from the objective. This should help the LP
543 // scaling, but also our integer reason computation.
544 int new_size = 0;
545 objective_infinity_norm_ = 0;
546 for (const auto& entry : integer_objective_) {
547 const IntegerVariable var = integer_variables_[entry.first.value()];
548 if (integer_trail_->IsFixedAtLevelZero(var)) {
549 integer_objective_offset_ +=
550 entry.second * integer_trail_->LevelZeroLowerBound(var);
551 continue;
552 }
553 objective_infinity_norm_ =
554 std::max(objective_infinity_norm_, IntTypeAbs(entry.second));
555 integer_objective_[new_size++] = entry;
556 }
557 integer_objective_.resize(new_size);
558 objective_infinity_norm_ =
559 std::max(objective_infinity_norm_, IntTypeAbs(integer_objective_offset_));
560
561 // Scale everything.
562 // TODO(user): As we have an idea of the LP optimal after the first solves,
563 // maybe we can adapt the scaling accordingly.
564 ComputeIntegerLpScalingFactors();
565
566 // Tricky: we use level zero bounds here for the second scaling step below.
567 FillLpData();
568
569 // Fills the helper.
570 scaler_.ConfigureFromFactors(row_factors_, col_factors_);
571 scaler_.AverageCostScaling(&obj_with_slack_);
572 scaler_.ContainOneBoundScaling(simplex_.MutableLowerBounds(),
573 simplex_.MutableUpperBounds());
574
575 // Since we used level zero bounds above, fix them.
576 UpdateBoundsOfLpVariables();
577
578 // Set the information for the step to polish the LP basis. All our variables
579 // are integer, but for now, we just try to minimize the fractionality of the
580 // binary variables.
581 if (parameters_.polish_lp_solution()) {
582 simplex_.ClearIntegralityScales();
583 const int num_vars = integer_variables_.size();
584 for (int i = 0; i < num_vars; ++i) {
585 const IntegerVariable cp_var = integer_variables_[i];
586 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(cp_var);
587 const IntegerValue ub = integer_trail_->LevelZeroUpperBound(cp_var);
588 if (lb != 0 || ub != 1) continue;
589 simplex_.SetIntegralityScale(
590 glop::ColIndex(i),
591 1.0 / scaler_.VariableScalingFactor(glop::ColIndex(i)));
592 }
593 }
594
595 VLOG(3) << "LP relaxation: " << integer_lp_.size() << " x "
596 << integer_variables_.size() << ". "
597 << constraint_manager_.AllConstraints().size()
598 << " Managed constraints.";
599 return true;
600}
601
602// TODO(user): This is a duplicate of glop scaling code, but it allows to
603// work directly on our representation...
604void LinearProgrammingConstraint::ComputeIntegerLpScalingFactors() {
605 const int num_rows = integer_lp_.size();
606 const int num_cols = integer_variables_.size();
607
608 // Assign vectors.
609 const double infinity = std::numeric_limits<double>::infinity();
610 row_factors_.assign(num_rows, 1.0);
611 col_factors_.assign(num_cols, 1.0);
612
613 // Cache pointers to avoid refetching them.
614 IntegerValue* coeffs = integer_lp_coeffs_.data();
615 glop::ColIndex* cols = integer_lp_cols_.data();
616 double* row_factors = row_factors_.data();
617 double* col_factors = col_factors_.data();
618
619 col_min_.assign(num_cols, infinity);
620 col_max_.assign(num_cols, 0.0);
621 double* col_min = col_min_.data();
622 double* col_max = col_max_.data();
623
624 for (int i = 0; i < 4; ++i) {
625 // Scale row geometrically.
626 for (int row = 0; row < num_rows; ++row) {
627 double min_scaled = +infinity;
628 double max_scaled = 0.0;
629 const LinearConstraintInternal& ct = integer_lp_[RowIndex(row)];
630 for (int i = 0; i < ct.num_terms; ++i) {
631 const int index = ct.start_in_buffer + i;
632 const int col = cols[index].value();
633 const double coeff = static_cast<double>(coeffs[index].value());
634 const double scaled_magnitude = col_factors[col] * std::abs(coeff);
635 min_scaled = std::min(min_scaled, scaled_magnitude);
636 max_scaled = std::max(max_scaled, scaled_magnitude);
637 }
638
639 if (ct.num_terms == 0) continue;
640 const Fractional factor(std::sqrt(max_scaled * min_scaled));
641 row_factors[row] = 1.0 / factor;
642 }
643
644 // Scale columns geometrically.
645 for (int row = 0; row < num_rows; ++row) {
646 const double row_factor = row_factors[row];
647 const LinearConstraintInternal& ct = integer_lp_[RowIndex(row)];
648 for (int i = 0; i < ct.num_terms; ++i) {
649 const int index = ct.start_in_buffer + i;
650 const int col = cols[index].value();
651 const double coeff = static_cast<double>(coeffs[index].value());
652 const double scaled_magnitude = row_factor * std::abs(coeff);
653 col_min[col] = std::min(col_min[col], scaled_magnitude);
654 col_max[col] = std::max(col_max[col], scaled_magnitude);
655 }
656 }
657 for (int col = 0; col < num_cols; ++col) {
658 if (col_min[col] == infinity) continue; // Empty.
659 col_factors[col] = 1.0 / std::sqrt(col_min[col] * col_max[col]);
660
661 // Reset, in case we have many fixed variable, faster than assign again.
662 col_min[col] = infinity;
663 col_max[col] = 0;
664 }
665 }
666
667 // Now we equilibrate (i.e. just divide by the max) the row
668 for (int row = 0; row < num_rows; ++row) {
669 double max_scaled = 0.0;
670 const LinearConstraintInternal& ct = integer_lp_[RowIndex(row)];
671 for (int i = 0; i < ct.num_terms; ++i) {
672 const int index = ct.start_in_buffer + i;
673 const int col = cols[index].value();
674 const double coeff = static_cast<double>(coeffs[index].value());
675 const double scaled_magnitude = col_factors[col] * std::abs(coeff);
676 max_scaled = std::max(max_scaled, scaled_magnitude);
677 }
678 if (ct.num_terms == 0) continue;
679 row_factors[row] = 1.0 / max_scaled;
680 }
681
682 // And finally the columns.
683 for (int row = 0; row < num_rows; ++row) {
684 const double row_factor = row_factors[row];
685 const LinearConstraintInternal& ct = integer_lp_[RowIndex(row)];
686 for (int i = 0; i < ct.num_terms; ++i) {
687 const int index = ct.start_in_buffer + i;
688 const int col = cols[index].value();
689 const double coeff = static_cast<double>(coeffs[index].value());
690 const double scaled_magnitude = row_factor * std::abs(coeff);
691 col_max[col] = std::max(col_max[col], scaled_magnitude);
692 }
693 }
694 for (int col = 0; col < num_cols; ++col) {
695 if (col_max[col] == 0) continue; // Empty.
696 col_factors[col] = 1.0 / col_max[col];
697 }
698}
699
700void LinearProgrammingConstraint::FillLpData() {
701 const int num_rows = integer_lp_.size();
702 const int num_cols = integer_variables_.size();
703 IntegerValue* coeffs = integer_lp_coeffs_.data();
704 glop::ColIndex* cols = integer_lp_cols_.data();
705 double* row_factors = row_factors_.data();
706 double* col_factors = col_factors_.data();
707
708 // Now fill the tranposed matrix
709 glop::CompactSparseMatrix* data = simplex_.MutableTransposedMatrixWithSlack();
710 data->Reset(glop::RowIndex(num_cols + num_rows));
711 for (int row = 0; row < num_rows; ++row) {
712 const LinearConstraintInternal& ct = integer_lp_[RowIndex(row)];
713 const double row_factor = row_factors[row];
714 for (int i = 0; i < ct.num_terms; ++i) {
715 const int index = ct.start_in_buffer + i;
716 const int col = cols[index].value();
717 const double coeff = static_cast<double>(coeffs[index].value());
718 const double scaled_coeff = row_factor * col_factors[col] * coeff;
719 data->AddEntryToCurrentColumn(RowIndex(col), scaled_coeff);
720 }
721
722 // Add slack.
723 data->AddEntryToCurrentColumn(RowIndex(num_cols + row), 1.0);
724
725 // Close column.
726 data->CloseCurrentColumn();
727 }
728
729 // Fill and scale the objective.
730 const glop::ColIndex num_cols_with_slacks(num_rows + num_cols);
731 obj_with_slack_.assign(num_cols_with_slacks, 0.0);
732 for (const auto [col, value] : integer_objective_) {
733 obj_with_slack_[col] = ToDouble(value) * col_factors[col.value()];
734 }
735
736 // Fill and scales the bound.
737 simplex_.MutableLowerBounds()->resize(num_cols_with_slacks);
738 simplex_.MutableUpperBounds()->resize(num_cols_with_slacks);
739 Fractional* lb_with_slack = simplex_.MutableLowerBounds()->data();
740 Fractional* ub_with_slack = simplex_.MutableUpperBounds()->data();
741 const double infinity = std::numeric_limits<double>::infinity();
742 for (int row = 0; row < integer_lp_.size(); ++row) {
743 const LinearConstraintInternal& ct = integer_lp_[glop::RowIndex(row)];
744
745 // TODO(user): Using trivial bound might be good for things like
746 // sum bool <= 1 since setting the slack in [0, 1] can lead to bound flip in
747 // the simplex. However if the bound is large, maybe it make more sense to
748 // use +/- infinity.
749 const double factor = row_factors[row];
750 lb_with_slack[num_cols + row] =
751 ct.ub_is_trivial ? -infinity : ToDouble(-ct.ub) * factor;
752 ub_with_slack[num_cols + row] =
753 ct.lb_is_trivial ? +infinity : ToDouble(-ct.lb) * factor;
754 }
755
756 // We scale the LP using the level zero bounds that we later override
757 // with the current ones.
758 //
759 // TODO(user): As part of the scaling, we may also want to shift the initial
760 // variable bounds so that each variable contain the value zero in their
761 // domain. Maybe just once and for all at the beginning.
762 const int num_vars = integer_variables_.size();
763 for (int i = 0; i < num_vars; i++) {
764 const IntegerVariable cp_var = integer_variables_[i];
765 const double factor = col_factors[i];
766 lb_with_slack[i] =
767 ToDouble(integer_trail_->LevelZeroLowerBound(cp_var)) * factor;
768 ub_with_slack[i] =
769 ToDouble(integer_trail_->LevelZeroUpperBound(cp_var)) * factor;
770 }
771}
772
773void LinearProgrammingConstraint::FillReducedCostReasonIn(
774 const glop::DenseRow& reduced_costs,
775 std::vector<IntegerLiteral>* integer_reason) {
776 integer_reason->clear();
777 const int num_vars = integer_variables_.size();
778 for (int i = 0; i < num_vars; i++) {
779 const double rc = reduced_costs[glop::ColIndex(i)];
780 if (rc > kLpEpsilon) {
781 integer_reason->push_back(
782 integer_trail_->LowerBoundAsLiteral(integer_variables_[i]));
783 } else if (rc < -kLpEpsilon) {
784 integer_reason->push_back(
785 integer_trail_->UpperBoundAsLiteral(integer_variables_[i]));
786 }
787 }
788
789 integer_trail_->RemoveLevelZeroBounds(integer_reason);
790}
791
793 // Get rid of all optimal constraint each time we go back to level zero.
794 if (level == 0) rev_optimal_constraints_size_ = 0;
795 optimal_constraints_.resize(rev_optimal_constraints_size_);
796 cumulative_optimal_constraint_sizes_.resize(rev_optimal_constraints_size_);
797 if (lp_solution_is_set_ && level < lp_solution_level_) {
798 lp_solution_is_set_ = false;
799 }
800
801 if (level < previous_level_) {
802 lp_at_optimal_ = false;
803 lp_objective_lower_bound_ = -std::numeric_limits<double>::infinity();
804 }
805 previous_level_ = level;
806
807 // Special case for level zero, we "reload" any previously known optimal
808 // solution from that level.
809 //
810 // TODO(user): Keep all optimal solution in the current branch?
811 // TODO(user): Still try to add cuts/constraints though!
812 // TODO(user): Reload the basis? This might cause issue with the basis
813 // saving/loading code in lb_tree_search.
814 if (level == 0 && !lp_solution_is_set_ && !level_zero_lp_solution_.empty()) {
815 lp_solution_is_set_ = true;
816 lp_solution_ = level_zero_lp_solution_;
817 lp_solution_level_ = 0;
818 // Add the fixed variables. They might have been skipped when we did the
819 // linear relaxation of the model, but cut generators expect all variables
820 // to have an LP value.
821 if (expanded_lp_solution_.size() < integer_trail_->NumIntegerVariables()) {
822 expanded_lp_solution_.resize(integer_trail_->NumIntegerVariables());
823 }
824 for (IntegerVariable i(0); i < integer_trail_->NumIntegerVariables(); ++i) {
825 if (integer_trail_->IsFixed(i)) {
826 expanded_lp_solution_[i] = ToDouble(integer_trail_->LowerBound(i));
827 }
828 }
829 for (int i = 0; i < lp_solution_.size(); i++) {
830 const IntegerVariable var = extended_integer_variables_[i];
831 expanded_lp_solution_[var] = lp_solution_[i];
832 expanded_lp_solution_[NegationOf(var)] = -lp_solution_[i];
833 }
834 }
835}
836
838 cut_generators_.push_back(std::move(generator));
839}
840
842 const std::vector<int>& watch_indices) {
843 if (!enabled_) return true;
844
845 // If we have a really deep branch, with a lot of LP explanation constraint,
846 // we could take a quadratic amount of memory: O(num_var) per number of
847 // propagation in that branch. To avoid that, once the memory starts to be
848 // over a few GB, we only propagate from time to time. This way we do not need
849 // to keep that many constraints around.
850 //
851 // Note that 100 Millions int32_t variables, with the int128 coefficients and
852 // extra propagation vector is already a few GB.
853 if (!cumulative_optimal_constraint_sizes_.empty()) {
854 const double current_size =
855 static_cast<double>(cumulative_optimal_constraint_sizes_.back());
856 const double low_limit = 1e7;
857 if (current_size > low_limit) {
858 // We only propagate if we use less that 100 times the number of current
859 // integer literal enqueued.
860 const double num_enqueues = static_cast<double>(integer_trail_->Index());
861 if ((current_size - low_limit) > 100 * num_enqueues) return true;
862 }
863 }
864
865 if (!lp_solution_is_set_ || num_force_lp_call_on_next_propagate_ > 0) {
866 return Propagate();
867 }
868
869 // At level zero, if there is still a chance to add cuts or lazy constraints,
870 // we re-run the LP.
871 if (trail_->CurrentDecisionLevel() == 0 && !lp_at_level_zero_is_final_) {
872 return Propagate();
873 }
874
875 // Check whether the change breaks the current LP solution. If it does, call
876 // Propagate() on the current LP.
877 for (const int index : watch_indices) {
878 const double lb =
879 ToDouble(integer_trail_->LowerBound(integer_variables_[index]));
880 const double ub =
881 ToDouble(integer_trail_->UpperBound(integer_variables_[index]));
882 const double value = lp_solution_[index];
883 if (value < lb - kCpEpsilon || value > ub + kCpEpsilon) return Propagate();
884 }
885
886 // TODO(user): The saved lp solution is still valid given the current variable
887 // bounds, so the LP optimal didn't change. However we might still want to add
888 // new cuts or new lazy constraints?
889 //
890 // TODO(user): Propagate the last optimal_constraint? Note that we need
891 // to be careful since the reversible int in IntegerSumLE are not registered.
892 // However, because we delete "optimalconstraints" on backtrack, we might not
893 // care.
894 //
895 // Remark: Note that if we do the sequence SetBasis() / Propagate() /
896 // GetAndSaveBasis() and we are in the case where the solution is still
897 // optimal, we should get the basis from when the lp solution was set which
898 // should be what we want. Even if we set garbage during SetBasis() it should
899 // be ignored. TODO(user): We might still have problem at level zero.
900 return true;
901}
902
903glop::Fractional LinearProgrammingConstraint::GetVariableValueAtCpScale(
904 glop::ColIndex var) {
905 return scaler_.UnscaleVariableValue(var, simplex_.GetVariableValue(var));
906}
907
909 IntegerVariable variable) const {
910 return lp_solution_[mirror_lp_variable_[variable].value()];
911}
912
913void LinearProgrammingConstraint::UpdateBoundsOfLpVariables() {
914 const int num_vars = integer_variables_.size();
915 Fractional* lb_with_slack = simplex_.MutableLowerBounds()->data();
916 Fractional* ub_with_slack = simplex_.MutableUpperBounds()->data();
917 for (int i = 0; i < num_vars; i++) {
918 const IntegerVariable cp_var = integer_variables_[i];
919 const double lb =
920 static_cast<double>(integer_trail_->LowerBound(cp_var).value());
921 const double ub =
922 static_cast<double>(integer_trail_->UpperBound(cp_var).value());
923 const double factor = scaler_.VariableScalingFactor(glop::ColIndex(i));
924 lb_with_slack[i] = lb * factor;
925 ub_with_slack[i] = ub * factor;
926 }
927}
928
929bool LinearProgrammingConstraint::SolveLp() {
930 const int level = trail_->CurrentDecisionLevel();
931 if (level == 0) {
932 lp_at_level_zero_is_final_ = false;
933 }
934
935 const double unscaling_factor = 1.0 / scaler_.ObjectiveScalingFactor();
936 const double offset_before_unscaling =
937 ToDouble(integer_objective_offset_) * scaler_.ObjectiveScalingFactor();
938 auto status = simplex_.MinimizeFromTransposedMatrixWithSlack(
939 obj_with_slack_, unscaling_factor, offset_before_unscaling, time_limit_);
940
941 // Lets resolve from scratch if we encounter this status.
942 if (simplex_.GetProblemStatus() == glop::ProblemStatus::ABNORMAL) {
943 VLOG(2) << "The LP solver returned abnormal, resolving from scratch";
944 simplex_.ClearStateForNextSolve();
945 status = simplex_.MinimizeFromTransposedMatrixWithSlack(
946 obj_with_slack_, unscaling_factor, offset_before_unscaling,
947 time_limit_);
948 }
949
950 state_ = simplex_.GetState();
951 total_num_simplex_iterations_ += simplex_.GetNumberOfIterations();
952 if (!status.ok()) {
953 VLOG(2) << "The LP solver encountered an error: " << status.error_message();
954 simplex_.ClearStateForNextSolve();
955 return false;
956 }
957 average_degeneracy_.AddData(CalculateDegeneracy());
958 if (average_degeneracy_.CurrentAverage() >= 1000.0) {
959 VLOG(2) << "High average degeneracy: "
960 << average_degeneracy_.CurrentAverage();
961 }
962
963 const int status_as_int = static_cast<int>(simplex_.GetProblemStatus());
964 if (status_as_int >= num_solves_by_status_.size()) {
965 num_solves_by_status_.resize(status_as_int + 1);
966 }
967 num_solves_++;
968 num_solves_by_status_[status_as_int]++;
969 VLOG(2) << DimensionString() << " lvl:" << trail_->CurrentDecisionLevel()
970 << " " << simplex_.GetProblemStatus()
971 << " iter:" << simplex_.GetNumberOfIterations()
972 << " obj:" << simplex_.GetObjectiveValue() << " scaled:"
973 << objective_definition_->ScaleObjective(
974 simplex_.GetObjectiveValue());
975
976 if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL ||
977 simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE) {
978 lp_objective_lower_bound_ = simplex_.GetObjectiveValue();
979 }
980 lp_at_optimal_ = simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL;
981
982 // If stop_after_root_propagation() is true, we still copy whatever we have as
983 // these values will be used for the local-branching lns heuristic.
984 if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL ||
985 parameters_.stop_after_root_propagation()) {
986 lp_solution_is_set_ = true;
987 lp_solution_level_ = trail_->CurrentDecisionLevel();
988 const int num_vars = integer_variables_.size();
989 const auto reduced_costs = simplex_.GetReducedCosts().const_view();
990 for (int i = 0; i < num_vars; i++) {
991 const glop::ColIndex col(i);
992 const IntegerVariable var = integer_variables_[i];
993
994 const glop::Fractional value = GetVariableValueAtCpScale(col);
995 lp_solution_[i] = value;
996 expanded_lp_solution_[var] = value;
997 expanded_lp_solution_[NegationOf(var)] = -value;
998
999 const glop::Fractional rc =
1000 scaler_.UnscaleReducedCost(col, reduced_costs[col]);
1001 lp_reduced_cost_[i] = rc;
1002 expanded_reduced_costs_[var] = rc;
1003 expanded_reduced_costs_[NegationOf(var)] = -rc;
1004 }
1005
1006 // Lets fix the result in case of symmetry since the variable in symmetry
1007 // are actually not part of the LP, they will just be at their bounds.
1008 for (const int orbit_index : orbit_indices_) {
1009 const IntegerVariable sum_var = symmetrizer_->OrbitSumVar(orbit_index);
1010 const double lp_value = expanded_lp_solution_[sum_var];
1011 const absl::Span<const IntegerVariable> orbit =
1012 symmetrizer_->Orbit(orbit_index);
1013
1014 // At level zero, we want to focus on the cuts, and we split the value
1015 // evently across all the orbit. however at positive level we don't do
1016 // cuts and we want values that make more sense to follow heuristically.
1017 // In particular, if the sum_var is at its lower/upper bounds, we want
1018 // the same for all variable from the orbit. This partially address the
1019 // TODO below.
1020 bool to_lower = false;
1021 bool to_upper = false;
1022 if (trail_->CurrentDecisionLevel() > 0) {
1023 const double lb =
1024 static_cast<double>(integer_trail_->LowerBound(sum_var).value());
1025 const double ub =
1026 static_cast<double>(integer_trail_->UpperBound(sum_var).value());
1027 if (std::abs(lp_value - lb) < 1e-2) {
1028 to_lower = true;
1029 } else if (std::abs(lp_value - ub) < 1e-2) {
1030 to_upper = true;
1031 }
1032 }
1033
1034 // We assign sum / orbit_size to each variables.
1035 // This is still an LP optimal, but not necessarily a good heuristic.
1036 //
1037 // TODO(user): using sum / orbit_size is good for the cut generation that
1038 // might still use these variables, any violated cuts on the original
1039 // problem where all variables in the orbit have the same value will
1040 // result in a violated cut for the folded problem. However it is probably
1041 // not so good for the heuristics that uses the LP values. In particular
1042 // it might result in LP value not even within the bounds of the
1043 // individual variable since as we branch, we don't have an identical
1044 // domain for all variables in an orbit. Maybe we can generate two
1045 // solutions vectors, one for the cuts and one for the heuristics, or we
1046 // can add custom code to the cuts so that they don't depend on this.
1047 const double even_split = lp_value / static_cast<double>(orbit.size());
1048
1049 // For the reduced costs, they are the same. There should be no
1050 // complication there.
1051 const double new_rc = expanded_reduced_costs_[sum_var];
1052
1053 for (const IntegerVariable var : orbit) {
1054 const glop::ColIndex col = GetMirrorVariable(var);
1055
1056 double new_value = even_split;
1057 if (to_lower) {
1058 new_value =
1059 static_cast<double>(integer_trail_->LowerBound(var).value());
1060 } else if (to_upper) {
1061 new_value =
1062 static_cast<double>(integer_trail_->UpperBound(var).value());
1063 }
1064
1065 lp_solution_[col.value()] = new_value;
1066 expanded_lp_solution_[var] = new_value;
1067 expanded_lp_solution_[NegationOf(var)] = -new_value;
1068
1069 lp_reduced_cost_[col.value()] = new_rc;
1070 expanded_reduced_costs_[var] = new_rc;
1071 expanded_reduced_costs_[NegationOf(var)] = -new_rc;
1072 }
1073 }
1074
1075 // Compute integrality.
1076 lp_solution_is_integer_ = true;
1077 for (const double value : lp_solution_) {
1078 if (std::abs(value - std::round(value)) > kCpEpsilon) {
1079 lp_solution_is_integer_ = false;
1080 break;
1081 }
1082 }
1083
1084 if (lp_solution_level_ == 0) {
1085 level_zero_lp_solution_ = lp_solution_;
1086 }
1087 }
1088
1089 return true;
1090}
1091
1092bool LinearProgrammingConstraint::AnalyzeLp() {
1093 // A dual-unbounded problem is infeasible. We use the dual ray reason.
1094 if (simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_UNBOUNDED) {
1095 if (parameters_.use_exact_lp_reason()) {
1096 return PropagateExactDualRay();
1097 }
1098 FillReducedCostReasonIn(simplex_.GetDualRayRowCombination(),
1099 &integer_reason_);
1100 return integer_trail_->ReportConflict(integer_reason_);
1101 }
1102
1103 // TODO(user): Update limits for DUAL_UNBOUNDED status as well.
1104 UpdateSimplexIterationLimit(/*min_iter=*/10, /*max_iter=*/1000);
1105
1106 // Optimality deductions if problem has an objective.
1107 // If there is no objective, then all duals will just be zero.
1108 if (objective_is_defined_ &&
1109 (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL ||
1110 simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE)) {
1111 // TODO(user): Maybe do a bit less computation when we cannot propagate
1112 // anything.
1113 if (parameters_.use_exact_lp_reason()) {
1114 if (!PropagateExactLpReason()) return false;
1115
1116 // Display when the inexact bound would have propagated more.
1117 if (VLOG_IS_ON(2)) {
1118 const double relaxed_optimal_objective = simplex_.GetObjectiveValue();
1119 const IntegerValue approximate_new_lb(static_cast<int64_t>(
1120 std::ceil(relaxed_optimal_objective - kCpEpsilon)));
1121 const IntegerValue propagated_lb =
1122 integer_trail_->LowerBound(objective_cp_);
1123 if (approximate_new_lb > propagated_lb) {
1124 VLOG(2) << "LP objective [ " << ToDouble(propagated_lb) << ", "
1125 << ToDouble(integer_trail_->UpperBound(objective_cp_))
1126 << " ] approx_lb += "
1127 << ToDouble(approximate_new_lb - propagated_lb) << " gap: "
1128 << integer_trail_->UpperBound(objective_cp_) - propagated_lb;
1129 }
1130 }
1131 } else {
1132 // Try to filter optimal objective value. Note that GetObjectiveValue()
1133 // already take care of the scaling so that it returns an objective in the
1134 // CP world.
1135 FillReducedCostReasonIn(simplex_.GetReducedCosts(), &integer_reason_);
1136 const double objective_cp_ub =
1137 ToDouble(integer_trail_->UpperBound(objective_cp_));
1138 const double relaxed_optimal_objective = simplex_.GetObjectiveValue();
1139 ReducedCostStrengtheningDeductions(objective_cp_ub -
1140 relaxed_optimal_objective);
1141 if (!deductions_.empty()) {
1142 deductions_reason_ = integer_reason_;
1143 deductions_reason_.push_back(
1144 integer_trail_->UpperBoundAsLiteral(objective_cp_));
1145 }
1146
1147 // Push new objective lb.
1148 const IntegerValue approximate_new_lb(static_cast<int64_t>(
1149 std::ceil(relaxed_optimal_objective - kCpEpsilon)));
1150 if (approximate_new_lb > integer_trail_->LowerBound(objective_cp_)) {
1151 const IntegerLiteral deduction =
1152 IntegerLiteral::GreaterOrEqual(objective_cp_, approximate_new_lb);
1153 if (!integer_trail_->Enqueue(deduction, {}, integer_reason_)) {
1154 return false;
1155 }
1156 }
1157
1158 // Push reduced cost strengthening bounds.
1159 if (!deductions_.empty()) {
1160 const int trail_index_with_same_reason = integer_trail_->Index();
1161 for (const IntegerLiteral deduction : deductions_) {
1162 if (!integer_trail_->Enqueue(deduction, {}, deductions_reason_,
1163 trail_index_with_same_reason)) {
1164 return false;
1165 }
1166 }
1167 }
1168 }
1169 }
1170
1171 // Copy more info about the current solution.
1172 if (compute_reduced_cost_averages_ &&
1173 simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
1174 CHECK(lp_solution_is_set_);
1175 UpdateAverageReducedCosts();
1176 }
1177
1178 // On some problem, LP solves and cut rounds can be slow, so we report
1179 // the current possible objective improvement in the middle of the
1180 // propagation, not just at the end.
1181 //
1182 // Note that we currently only do that if the LP is "full" and its objective
1183 // is the same as the one of the whole problem.
1184 if (objective_is_defined_ &&
1185 objective_definition_->objective_var == objective_cp_ &&
1186 trail_->CurrentDecisionLevel() == 0) {
1187 shared_response_manager_->UpdateInnerObjectiveBounds(
1188 model_->Name(), integer_trail_->LowerBound(objective_cp_),
1189 integer_trail_->UpperBound(objective_cp_));
1190 }
1191
1192 return true;
1193}
1194
1195// Note that since we call this on the constraint with slack, we actually have
1196// linear expression == rhs, we can use this to propagate more!
1197//
1198// TODO(user): Also propagate on -cut ? in practice we already do that in many
1199// places were we try to generate the cut on -cut... But we could do it sooner
1200// and more cleanly here.
1201bool LinearProgrammingConstraint::PreprocessCut(IntegerVariable first_slack,
1202 CutData* cut) {
1203 // Because of complement, all coeffs and all terms are positive after this.
1204 cut->ComplementForPositiveCoefficients();
1205 if (cut->rhs < 0) {
1206 problem_proven_infeasible_by_cuts_ = true;
1207 return false;
1208 }
1209
1210 // Limited DP to compute first few reachable values.
1211 // Note that all coeff are positive.
1212 reachable_.Reset();
1213 for (const CutTerm& term : cut->terms) {
1214 reachable_.Add(term.coeff.value());
1215 }
1216
1217 // Extra propag since we know it is actually an equality constraint.
1218 if (cut->rhs < absl::int128(reachable_.LastValue()) &&
1219 !reachable_.MightBeReachable(static_cast<int64_t>(cut->rhs))) {
1220 problem_proven_infeasible_by_cuts_ = true;
1221 return false;
1222 }
1223
1224 bool some_fixed_terms = false;
1225 bool some_fractional_positions = false;
1226 for (CutTerm& term : cut->terms) {
1227 const absl::int128 magnitude128 = term.coeff.value();
1228 const absl::int128 range =
1229 absl::int128(term.bound_diff.value()) * magnitude128;
1230
1231 IntegerValue new_diff = term.bound_diff;
1232 if (range > cut->rhs) {
1233 new_diff = static_cast<int64_t>(cut->rhs / magnitude128);
1234 }
1235 {
1236 // Extra propag since we know it is actually an equality constraint.
1237 absl::int128 rest128 =
1238 cut->rhs - absl::int128(new_diff.value()) * magnitude128;
1239 while (rest128 < absl::int128(reachable_.LastValue()) &&
1240 !reachable_.MightBeReachable(static_cast<int64_t>(rest128))) {
1241 ++total_num_eq_propagations_;
1242 CHECK_GT(new_diff, 0);
1243 --new_diff;
1244 rest128 += magnitude128;
1245 }
1246 }
1247
1248 if (new_diff < term.bound_diff) {
1249 term.bound_diff = new_diff;
1250
1251 const IntegerVariable var = term.expr_vars[0];
1252 if (var < first_slack) {
1253 // Normal variable.
1254 ++total_num_cut_propagations_;
1255
1256 // Note that at this stage we only have X - lb or ub - X.
1257 if (term.expr_coeffs[0] == 1) {
1258 // X + offset <= bound_diff
1259 if (!integer_trail_->Enqueue(
1261 var, term.bound_diff - term.expr_offset),
1262 {}, {})) {
1263 problem_proven_infeasible_by_cuts_ = true;
1264 return false;
1265 }
1266 } else {
1267 CHECK_EQ(term.expr_coeffs[0], -1);
1268 // offset - X <= bound_diff
1269 if (!integer_trail_->Enqueue(
1271 var, term.expr_offset - term.bound_diff),
1272 {}, {})) {
1273 problem_proven_infeasible_by_cuts_ = true;
1274 return false;
1275 }
1276 }
1277 } else {
1278 // This is a tighter bound on one of the constraint! like a cut. Note
1279 // that in some corner case, new cut can be merged and update the bounds
1280 // of the constraint before this code.
1281 const int slack_index = (var.value() - first_slack.value()) / 2;
1282 const glop::RowIndex row = tmp_slack_rows_[slack_index];
1283 if (term.expr_coeffs[0] == 1) {
1284 // slack = ct + offset <= bound_diff;
1285 const IntegerValue new_ub = term.bound_diff - term.expr_offset;
1286 if (constraint_manager_.UpdateConstraintUb(row, new_ub)) {
1287 integer_lp_[row].ub = new_ub;
1288 }
1289 } else {
1290 // slack = offset - ct <= bound_diff;
1291 CHECK_EQ(term.expr_coeffs[0], -1);
1292 const IntegerValue new_lb = term.expr_offset - term.bound_diff;
1293 if (constraint_manager_.UpdateConstraintLb(row, new_lb)) {
1294 integer_lp_[row].lb = new_lb;
1295 }
1296 }
1297 }
1298 }
1299
1300 if (term.bound_diff == 0) {
1301 some_fixed_terms = true;
1302 } else {
1303 if (term.IsFractional()) {
1304 some_fractional_positions = true;
1305 }
1306 }
1307 }
1308
1309 // Remove fixed terms if any.
1310 if (some_fixed_terms) {
1311 int new_size = 0;
1312 for (const CutTerm& term : cut->terms) {
1313 if (term.bound_diff == 0) continue;
1314 cut->terms[new_size++] = term;
1315 }
1316 cut->terms.resize(new_size);
1317 }
1318 return some_fractional_positions;
1319}
1320
1321bool LinearProgrammingConstraint::AddCutFromConstraints(
1322 absl::string_view name,
1323 absl::Span<const std::pair<RowIndex, IntegerValue>> integer_multipliers) {
1324 // This is initialized to a valid linear constraint (by taking linear
1325 // combination of the LP rows) and will be transformed into a cut if
1326 // possible.
1327 //
1328 // TODO(user): For CG cuts, Ideally this linear combination should have only
1329 // one fractional variable (basis_col). But because of imprecision, we can get
1330 // a bunch of fractional entry with small coefficient (relative to the one of
1331 // basis_col). We try to handle that in IntegerRoundingCut(), but it might be
1332 // better to add small multiple of the involved rows to get rid of them.
1333 IntegerValue cut_ub;
1334 if (!ComputeNewLinearConstraint(integer_multipliers, &tmp_scattered_vector_,
1335 &cut_ub)) {
1336 ++num_cut_overflows_;
1337 VLOG(1) << "Issue, overflow!";
1338 return false;
1339 }
1340
1341 // Important: because we use integer_multipliers below to create the slack,
1342 // we cannot try to adjust this linear combination easily.
1343 //
1344 // TODO(user): the conversion col_index -> IntegerVariable is slow and could
1345 // in principle be removed. Easy for cuts, but not so much for
1346 // implied_bounds_processor_. Note that in theory this could allow us to
1347 // use Literal directly without the need to have an IntegerVariable for them.
1348 tmp_scattered_vector_.ConvertToCutData(
1349 cut_ub.value(), extended_integer_variables_, lp_solution_, integer_trail_,
1350 &base_ct_);
1351
1352 // If there are no integer (all Booleans), no need to try implied bounds
1353 // heurititics. By setting this to nullptr, we are a bit faster.
1354 ImpliedBoundsProcessor* ib_processor = nullptr;
1355 {
1356 bool some_ints = false;
1357 bool some_fractional_positions = false;
1358 for (const CutTerm& term : base_ct_.terms) {
1359 if (term.bound_diff > 1) some_ints = true;
1360 if (term.IsFractional()) {
1361 some_fractional_positions = true;
1362 }
1363 }
1364
1365 // If all value are integer, we will not be able to cut anything.
1366 if (!some_fractional_positions) return false;
1367 if (some_ints) ib_processor = &implied_bounds_processor_;
1368 }
1369
1370 // Add constraint slack.
1371 // This is important for correctness here.
1372 const IntegerVariable first_slack(expanded_lp_solution_.size());
1373 CHECK_EQ(first_slack.value() % 2, 0);
1374 tmp_slack_rows_.clear();
1375 for (const auto [row, coeff] : integer_multipliers) {
1376 if (integer_lp_[row].lb == integer_lp_[row].ub) continue;
1377
1378 CutTerm entry;
1379 entry.coeff = coeff > 0 ? coeff : -coeff;
1380 entry.lp_value = 0.0;
1381 entry.bound_diff = integer_lp_[row].ub - integer_lp_[row].lb;
1382 entry.expr_vars[0] =
1383 first_slack + 2 * IntegerVariable(tmp_slack_rows_.size());
1384 entry.expr_coeffs[1] = 0;
1385 const double activity = scaler_.UnscaleConstraintActivity(
1386 row, simplex_.GetConstraintActivity(row));
1387 if (coeff > 0) {
1388 // Slack = ub - constraint;
1389 entry.lp_value = ToDouble(integer_lp_[row].ub) - activity;
1390 entry.expr_coeffs[0] = IntegerValue(-1);
1391 entry.expr_offset = integer_lp_[row].ub;
1392 } else {
1393 // Slack = constraint - lb;
1394 entry.lp_value = activity - ToDouble(integer_lp_[row].lb);
1395 entry.expr_coeffs[0] = IntegerValue(1);
1396 entry.expr_offset = -integer_lp_[row].lb;
1397 }
1398
1399 base_ct_.terms.push_back(entry);
1400 tmp_slack_rows_.push_back(row);
1401 }
1402
1403 // This also make all coefficients positive.
1404 if (!PreprocessCut(first_slack, &base_ct_)) return false;
1405
1406 // We cannot cut sum Bool <= 1.
1407 if (base_ct_.rhs == 1) return false;
1408
1409 // Constraint with slack should be tight.
1410 if (DEBUG_MODE) {
1411 double activity = 0.0;
1412 double norm = 0.0;
1413 for (const CutTerm& term : base_ct_.terms) {
1414 const double coeff = ToDouble(term.coeff);
1415 activity += term.lp_value * coeff;
1416 norm += coeff * coeff;
1417 }
1418 if (std::abs(activity - static_cast<double>(base_ct_.rhs)) / norm > 1e-4) {
1419 VLOG(1) << "Cut not tight " << activity
1420 << " <= " << static_cast<double>(base_ct_.rhs);
1421 return false;
1422 }
1423 }
1424
1425 bool at_least_one_added = false;
1426 DCHECK(base_ct_.AllCoefficientsArePositive());
1427
1428 // Try RLT cuts.
1429 //
1430 // TODO(user): try this for more than just "base" constraints?
1431 if (integer_multipliers.size() == 1 && parameters_.add_rlt_cuts()) {
1432 if (rlt_cut_helper_.TrySimpleSeparation(base_ct_)) {
1433 at_least_one_added |= PostprocessAndAddCut(
1434 absl::StrCat(name, "_RLT"), rlt_cut_helper_.Info(), first_slack,
1435 rlt_cut_helper_.cut());
1436 }
1437 }
1438
1439 // Note that the indexing will survive ComplementForSmallerLpValues() below.
1440 if (ib_processor != nullptr) {
1441 if (!ib_processor->CacheDataForCut(first_slack, &base_ct_)) {
1442 ib_processor = nullptr;
1443 }
1444 }
1445
1446 // Try cover approach to find cut.
1447 // TODO(user): Share common computation between kinds.
1448 {
1449 cover_cut_helper_.ClearCache();
1450
1451 if (cover_cut_helper_.TrySingleNodeFlow(base_ct_, ib_processor)) {
1452 at_least_one_added |= PostprocessAndAddCut(
1453 absl::StrCat(name, "_FF"), cover_cut_helper_.Info(), first_slack,
1454 cover_cut_helper_.cut());
1455 }
1456 if (cover_cut_helper_.TrySimpleKnapsack(base_ct_, ib_processor)) {
1457 at_least_one_added |= PostprocessAndAddCut(
1458 absl::StrCat(name, "_K"), cover_cut_helper_.Info(), first_slack,
1459 cover_cut_helper_.cut());
1460 }
1461
1462 // This one need to be called after TrySimpleKnapsack() in order to reuse
1463 // some cached data if possible.
1464 if (cover_cut_helper_.TryWithLetchfordSouliLifting(base_ct_,
1465 ib_processor)) {
1466 at_least_one_added |= PostprocessAndAddCut(
1467 absl::StrCat(name, "_KL"), cover_cut_helper_.Info(), first_slack,
1468 cover_cut_helper_.cut());
1469 }
1470 }
1471
1472 // Try integer rounding heuristic to find cut.
1473 {
1474 base_ct_.ComplementForSmallerLpValues();
1475
1476 RoundingOptions options;
1477 options.max_scaling = parameters_.max_integer_rounding_scaling();
1478 options.use_ib_before_heuristic = false;
1479 if (integer_rounding_cut_helper_.ComputeCut(options, base_ct_,
1480 ib_processor)) {
1481 at_least_one_added |= PostprocessAndAddCut(
1482 absl::StrCat(name, "_R"), integer_rounding_cut_helper_.Info(),
1483 first_slack, integer_rounding_cut_helper_.cut());
1484 }
1485
1486 options.use_ib_before_heuristic = true;
1487 options.prefer_positive_ib = false;
1488 if (ib_processor != nullptr && integer_rounding_cut_helper_.ComputeCut(
1489 options, base_ct_, ib_processor)) {
1490 at_least_one_added |= PostprocessAndAddCut(
1491 absl::StrCat(name, "_RB"), integer_rounding_cut_helper_.Info(),
1492 first_slack, integer_rounding_cut_helper_.cut());
1493 }
1494
1495 options.use_ib_before_heuristic = true;
1496 options.prefer_positive_ib = true;
1497 if (ib_processor != nullptr && integer_rounding_cut_helper_.ComputeCut(
1498 options, base_ct_, ib_processor)) {
1499 at_least_one_added |= PostprocessAndAddCut(
1500 absl::StrCat(name, "_RBP"), integer_rounding_cut_helper_.Info(),
1501 first_slack, integer_rounding_cut_helper_.cut());
1502 }
1503 }
1504
1505 return at_least_one_added;
1506}
1507
1508bool LinearProgrammingConstraint::PostprocessAndAddCut(
1509 const std::string& name, const std::string& info,
1510 IntegerVariable first_slack, const CutData& cut) {
1511 if (cut.rhs > absl::int128(std::numeric_limits<int64_t>::max()) ||
1512 cut.rhs < absl::int128(std::numeric_limits<int64_t>::min())) {
1513 VLOG(2) << "RHS overflow " << name << " " << info;
1514 ++num_cut_overflows_;
1515 return false;
1516 }
1517
1518 // We test this here to avoid doing the costly conversion below.
1519 //
1520 // TODO(user): Ideally we should detect this even earlier during the cut
1521 // generation.
1522 if (cut.ComputeViolation() < 1e-4) {
1523 VLOG(3) << "Bad cut " << name << " " << info;
1524 ++num_bad_cuts_;
1525 return false;
1526 }
1527
1528 // Substitute any slack left.
1529 tmp_scattered_vector_.ClearAndResize(extended_integer_variables_.size());
1530 IntegerValue cut_ub = static_cast<int64_t>(cut.rhs);
1531 for (const CutTerm& term : cut.terms) {
1532 if (term.coeff == 0) continue;
1533 if (!AddProductTo(-term.coeff, term.expr_offset, &cut_ub)) {
1534 VLOG(2) << "Overflow in conversion";
1535 ++num_cut_overflows_;
1536 return false;
1537 }
1538 for (int i = 0; i < 2; ++i) {
1539 if (term.expr_coeffs[i] == 0) continue;
1540 const IntegerVariable var = term.expr_vars[i];
1541 const IntegerValue coeff =
1542 CapProd(term.coeff.value(), term.expr_coeffs[i].value());
1543 if (AtMinOrMaxInt64(coeff.value())) {
1544 VLOG(2) << "Overflow in conversion";
1545 ++num_cut_overflows_;
1546 return false;
1547 }
1548
1549 // Simple copy for non-slack variables.
1550 if (var < first_slack) {
1551 const glop::ColIndex col = mirror_lp_variable_[PositiveVariable(var)];
1552 if (VariableIsPositive(var)) {
1553 tmp_scattered_vector_.Add(col, coeff);
1554 } else {
1555 tmp_scattered_vector_.Add(col, -coeff);
1556 }
1557 continue;
1558 } else {
1559 // Replace slack from LP constraints.
1560 const int slack_index = (var.value() - first_slack.value()) / 2;
1561 const glop::RowIndex row = tmp_slack_rows_[slack_index];
1562 if (!tmp_scattered_vector_.AddLinearExpressionMultiple(
1563 coeff, IntegerLpRowCols(row), IntegerLpRowCoeffs(row),
1564 infinity_norms_[row])) {
1565 VLOG(2) << "Overflow in slack removal";
1566 ++num_cut_overflows_;
1567 return false;
1568 }
1569 }
1570 }
1571 }
1572
1573 // TODO(user): avoid allocating memory if it turns out this is a duplicate of
1574 // something we already added. This tends to happen if the duplicate was
1575 // already a generated cut which is currently not part of the LP.
1576 LinearConstraint converted_cut =
1577 tmp_scattered_vector_.ConvertToLinearConstraint(
1578 extended_integer_variables_, cut_ub);
1579
1580 // TODO(user): We probably generate too many cuts, keep best one only?
1581 // Note that we do need duplicate removal and maybe some orthogonality here?
1582 if (/*DISABLES CODE*/ (false)) {
1583 top_n_cuts_.AddCut(std::move(converted_cut), name, expanded_lp_solution_);
1584 return true;
1585 }
1586 return constraint_manager_.AddCut(std::move(converted_cut), name, info);
1587}
1588
1589// TODO(user): This can be still too slow on some problems like
1590// 30_70_45_05_100.mps.gz. Not this actual function, but the set of computation
1591// it triggers. We should add heuristics to abort earlier if a cut is not
1592// promising. Or only test a few positions and not all rows.
1593void LinearProgrammingConstraint::AddCGCuts() {
1594 std::vector<std::pair<RowIndex, double>> sorted_columns;
1595 const RowIndex num_rows(integer_lp_.size());
1596 glop::DenseColumn::ConstView norms = simplex_.GetDualSquaredNorms();
1597 for (RowIndex index(0); index < num_rows; ++index) {
1598 const ColIndex basis_col = simplex_.GetBasis(index);
1599
1600 // We used to skip slack and also not to do "classical" gomory and instead
1601 // call IgnoreTrivialConstraintMultipliers() heuristic. It is usually faster
1602 // but on some problem like neos*creuse or neos-888544, this do not find
1603 // good cut though.
1604 //
1605 // TODO(user): Tune this. It seems better but we need to handle nicely the
1606 // extra amount of cuts this produces.
1607 if (basis_col >= integer_variables_.size()) continue;
1608
1609 // Get he variable value at cp-scale. Similar to GetVariableValueAtCpScale()
1610 // but this works for slack variable too.
1611 const Fractional lp_value =
1612 simplex_.GetVariableValue(basis_col) /
1613 scaler_.VariableScalingFactorWithSlack(basis_col);
1614
1615 // Only consider fractional basis element. We ignore element that are close
1616 // to an integer to reduce the amount of positions we try.
1617 //
1618 // TODO(user): We could just look at the diff with std::floor() in the hope
1619 // that when we are just under an integer, the exact computation below will
1620 // also be just under it.
1621 const double fractionality = std::abs(lp_value - std::round(lp_value));
1622 if (fractionality < 0.01) continue;
1623
1624 const double score = fractionality * (1.0 - fractionality) / norms[index];
1625 sorted_columns.push_back({index, score});
1626 }
1627 absl::c_sort(sorted_columns, [](const std::pair<RowIndex, double>& a,
1628 const std::pair<RowIndex, double>& b) {
1629 return a.second > b.second;
1630 });
1631
1632 int num_added = 0;
1633 for (const auto [index, _] : sorted_columns) {
1634 if (time_limit_->LimitReached()) return;
1635 // We multiply by row_factors_ directly, which might be slightly more
1636 // precise than dividing by 1/factor like UnscaleLeftSolveValue() does.
1637 //
1638 // TODO(user): Avoid code duplication between the sparse/dense path.
1639 tmp_lp_multipliers_.clear();
1640 const glop::ScatteredRow& lambda = simplex_.GetUnitRowLeftInverse(index);
1641 if (lambda.non_zeros.empty()) {
1642 for (RowIndex row(0); row < num_rows; ++row) {
1643 const double value = lambda.values[glop::RowToColIndex(row)];
1644 if (std::abs(value) < kZeroTolerance) continue;
1645 tmp_lp_multipliers_.push_back({row, row_factors_[row.value()] * value});
1646 }
1647 } else {
1648 for (const ColIndex col : lambda.non_zeros) {
1649 const RowIndex row = glop::ColToRowIndex(col);
1650 const double value = lambda.values[col];
1651 if (std::abs(value) < kZeroTolerance) continue;
1652 tmp_lp_multipliers_.push_back({row, row_factors_[row.value()] * value});
1653 }
1654 }
1655
1656 // Size 1 is already done with MIR.
1657 if (tmp_lp_multipliers_.size() <= 1) continue;
1658
1659 IntegerValue scaling;
1660 for (int i = 0; i < 2; ++i) {
1661 tmp_cg_multipliers_ = tmp_lp_multipliers_;
1662 if (i == 1) {
1663 // Try other sign.
1664 //
1665 // TODO(user): Maybe add an heuristic to know beforehand which sign to
1666 // use?
1667 for (std::pair<RowIndex, double>& p : tmp_cg_multipliers_) {
1668 p.second = -p.second;
1669 }
1670 }
1671
1672 // Remove constraints that shouldn't be helpful.
1673 //
1674 // In practice, because we can complement the slack, it might still be
1675 // useful to have some constraint with a trivial upper bound. Also
1676 // removing this seem to generate a lot more cuts, so we need to be more
1677 // efficient in dealing with them.
1678 if (true) {
1679 IgnoreTrivialConstraintMultipliers(&tmp_cg_multipliers_);
1680 if (tmp_cg_multipliers_.size() <= 1) continue;
1681 }
1682 ScaleMultipliers(tmp_cg_multipliers_,
1683 /*take_objective_into_account=*/false, &scaling,
1684 &tmp_integer_multipliers_);
1685 if (scaling != 0) {
1686 if (AddCutFromConstraints("CG", tmp_integer_multipliers_)) {
1687 ++num_added;
1688 }
1689 }
1690 }
1691
1692 // Stop if we already added more than 10 cuts this round.
1693 if (num_added > 10) break;
1694 }
1695}
1696
1697// Because we know the objective is integer, the constraint objective >= lb can
1698// sometime cut the current lp optimal, and it can make a big difference to add
1699// it. Or at least use it when constructing more advanced cuts. See
1700// 'multisetcover_batch_0_case_115_instance_0_small_subset_elements_3_sumreqs
1701// _1295_candidates_41.fzn'
1702//
1703// TODO(user): It might be better to just integrate this with the MIR code so
1704// that we not only consider MIR1 involving the objective but we also consider
1705// combining it with other constraints.
1706void LinearProgrammingConstraint::AddObjectiveCut() {
1707 if (integer_objective_.size() <= 1) return;
1708
1709 // We only try to add such cut if the LB objective is "far" from the current
1710 // objective lower bound. Note that this is in term of the "internal" integer
1711 // objective.
1712 const double obj_lp_value = simplex_.GetObjectiveValue();
1713 const IntegerValue obj_lower_bound =
1714 integer_trail_->LevelZeroLowerBound(objective_cp_);
1715 if (obj_lp_value + 1.0 >= ToDouble(obj_lower_bound)) return;
1716
1717 // We negate everything to have a <= base constraint.
1718 LinearConstraint objective_ct;
1719 objective_ct.lb = kMinIntegerValue;
1720 objective_ct.ub = integer_objective_offset_ -
1721 integer_trail_->LevelZeroLowerBound(objective_cp_);
1722 IntegerValue obj_coeff_magnitude(0);
1723 objective_ct.resize(integer_objective_.size());
1724 int i = 0;
1725 for (const auto& [col, coeff] : integer_objective_) {
1726 const IntegerVariable var = integer_variables_[col.value()];
1727 objective_ct.vars[i] = var;
1728 objective_ct.coeffs[i] = -coeff;
1729 obj_coeff_magnitude = std::max(obj_coeff_magnitude, IntTypeAbs(coeff));
1730 ++i;
1731 }
1732
1733 if (!base_ct_.FillFromLinearConstraint(objective_ct, expanded_lp_solution_,
1734 integer_trail_)) {
1735 return;
1736 }
1737
1738 // If the magnitude is small enough, just try to add the full objective. Other
1739 // cuts will be derived in subsequent passes. Otherwise, try normal cut
1740 // heuristic that should result in a cut with reasonable coefficients.
1741 if (obj_coeff_magnitude < 1e9 &&
1742 constraint_manager_.AddCut(std::move(objective_ct), "Objective")) {
1743 return;
1744 }
1745
1746 // If there are no integer (all Booleans), no need to try implied bounds
1747 // heurititics. By setting this to nullptr, we are a bit faster.
1748 ImpliedBoundsProcessor* ib_processor = nullptr;
1749 {
1750 bool some_ints = false;
1751 bool some_relevant_positions = false;
1752 for (const CutTerm& term : base_ct_.terms) {
1753 if (term.bound_diff > 1) some_ints = true;
1754 if (term.HasRelevantLpValue()) some_relevant_positions = true;
1755 }
1756
1757 // If all value are integer, we will not be able to cut anything.
1758 if (!some_relevant_positions) return;
1759 if (some_ints) ib_processor = &implied_bounds_processor_;
1760 }
1761
1762 // Note that the indexing will survive the complement of terms below.
1763 const IntegerVariable first_slack(
1764 std::numeric_limits<IntegerVariable::ValueType>::max());
1765 if (ib_processor != nullptr) {
1766 if (!ib_processor->CacheDataForCut(first_slack, &base_ct_)) {
1767 ib_processor = nullptr;
1768 }
1769 }
1770
1771 // Try knapsack.
1772 base_ct_.ComplementForPositiveCoefficients();
1773 cover_cut_helper_.ClearCache();
1774 if (cover_cut_helper_.TrySimpleKnapsack(base_ct_, ib_processor)) {
1775 PostprocessAndAddCut("Objective_K", cover_cut_helper_.Info(), first_slack,
1776 cover_cut_helper_.cut());
1777 }
1778
1779 // Try rounding.
1780 RoundingOptions options;
1781 options.max_scaling = parameters_.max_integer_rounding_scaling();
1782 base_ct_.ComplementForSmallerLpValues();
1783 if (integer_rounding_cut_helper_.ComputeCut(options, base_ct_,
1784 ib_processor)) {
1785 PostprocessAndAddCut("Objective_R", integer_rounding_cut_helper_.Info(),
1786 first_slack, integer_rounding_cut_helper_.cut());
1787 }
1788}
1789
1790void LinearProgrammingConstraint::AddMirCuts() {
1791 // Heuristic to generate MIR_n cuts by combining a small number of rows. This
1792 // works greedily and follow more or less the MIR cut description in the
1793 // literature. We have a current cut, and we add one more row to it while
1794 // eliminating a variable of the current cut whose LP value is far from its
1795 // bound.
1796 //
1797 // A notable difference is that we randomize the variable we eliminate and
1798 // the row we use to do so. We still have weights to indicate our preferred
1799 // choices. This allows to generate different cuts when called again and
1800 // again.
1801 //
1802 // TODO(user): We could combine n rows to make sure we eliminate n variables
1803 // far away from their bounds by solving exactly in integer small linear
1804 // system.
1805 util_intops::StrongVector<ColIndex, IntegerValue> dense_cut(
1806 integer_variables_.size(), IntegerValue(0));
1807 SparseBitset<ColIndex> non_zeros_(ColIndex(integer_variables_.size()));
1808
1809 // We compute all the rows that are tight, these will be used as the base row
1810 // for the MIR_n procedure below.
1811 const int num_cols = integer_variables_.size();
1812 const int num_rows = integer_lp_.size();
1813 std::vector<std::pair<RowIndex, IntegerValue>> base_rows;
1814 util_intops::StrongVector<RowIndex, double> row_weights(num_rows, 0.0);
1815 Fractional* lb_with_slack = simplex_.MutableLowerBounds()->data();
1816 Fractional* ub_with_slack = simplex_.MutableUpperBounds()->data();
1817 for (RowIndex row(0); row < num_rows; ++row) {
1818 // We only consider tight rows.
1819 // We use both the status and activity to have as much options as possible.
1820 //
1821 // TODO(user): shall we consider rows that are not tight?
1822 // TODO(user): Ignore trivial rows? Note that we do that for MIR1 since it
1823 // cannot be good.
1824 const auto status = simplex_.GetConstraintStatus(row);
1825 const double activity = simplex_.GetConstraintActivity(row);
1826 const double ct_lb = -ub_with_slack[num_cols + row.value()];
1827 const double ct_ub = -lb_with_slack[num_cols + row.value()];
1828 if (activity > ct_ub - 1e-4 ||
1831 base_rows.push_back({row, IntegerValue(1)});
1832 }
1833 if (activity < ct_lb + 1e-4 ||
1836 base_rows.push_back({row, IntegerValue(-1)});
1837 }
1838
1839 // For now, we use the dual values for the row "weights".
1840 //
1841 // Note that we use the dual at LP scale so that it make more sense when we
1842 // compare different rows since the LP has been scaled.
1843 //
1844 // TODO(user): In Kati Wolter PhD "Implementation of Cutting Plane
1845 // Separators for Mixed Integer Programs" which describe SCIP's MIR cuts
1846 // implementation (or at least an early version of it), a more complex score
1847 // is used.
1848 //
1849 // Note(user): Because we only consider tight rows under the current lp
1850 // solution (i.e. non-basic rows), most should have a non-zero dual values.
1851 // But there is some degenerate problem where these rows have a really low
1852 // weight (or even zero), and having only weight of exactly zero in
1853 // std::discrete_distribution will result in a crash.
1854 row_weights[row] =
1855 std::max(Fractional(1e-8), std::abs(simplex_.GetDualValue(row)));
1856 }
1857
1858 // The code here can be really slow, so we put a limit on the number of
1859 // entries we process. We randomize the base_rows so that on the next calls
1860 // we do not do exactly the same if we can't process many base row.
1861 int64_t dtime_num_entries = 0;
1862 std::shuffle(base_rows.begin(), base_rows.end(), *random_);
1863
1864 std::vector<double> weights;
1865 util_intops::StrongVector<RowIndex, bool> used_rows;
1866 std::vector<std::pair<RowIndex, IntegerValue>> integer_multipliers;
1867 const auto matrix = simplex_.MatrixWithSlack().view();
1868 for (const std::pair<RowIndex, IntegerValue>& entry : base_rows) {
1869 if (time_limit_->LimitReached()) break;
1870 if (dtime_num_entries > 1e7) break;
1871
1872 const glop::RowIndex base_row = entry.first;
1873 const IntegerValue multiplier = entry.second;
1874
1875 // First try to generate a cut directly from this base row (MIR1).
1876 //
1877 // Note(user): We abort on success like it seems to be done in the
1878 // literature. Note that we don't succeed that often in generating an
1879 // efficient cut, so I am not sure aborting will make a big difference
1880 // speedwise. We might generate similar cuts though, but hopefully the cut
1881 // management can deal with that.
1882 integer_multipliers = {entry};
1883 if ((multiplier > 0 && !integer_lp_[base_row].ub_is_trivial) ||
1884 (multiplier < 0 && !integer_lp_[base_row].lb_is_trivial)) {
1885 if (AddCutFromConstraints("MIR_1", integer_multipliers)) continue;
1886 }
1887
1888 // Cleanup.
1889 for (const ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1890 dense_cut[col] = IntegerValue(0);
1891 }
1892 non_zeros_.ResetAllToFalse();
1893
1894 // Copy cut.
1895 const LinearConstraintInternal& ct = integer_lp_[entry.first];
1896 for (int i = 0; i < ct.num_terms; ++i) {
1897 const int index = ct.start_in_buffer + i;
1898 const ColIndex col = integer_lp_cols_[index];
1899 const IntegerValue coeff = integer_lp_coeffs_[index];
1900 non_zeros_.Set(col);
1901 dense_cut[col] += coeff * multiplier;
1902 }
1903
1904 used_rows.assign(num_rows, false);
1905 used_rows[entry.first] = true;
1906
1907 // We will aggregate at most kMaxAggregation more rows.
1908 //
1909 // TODO(user): optim + tune.
1910 const int kMaxAggregation = 5;
1911 for (int i = 0; i < kMaxAggregation; ++i) {
1912 // First pick a variable to eliminate. We currently pick a random one with
1913 // a weight that depend on how far it is from its closest bound.
1914 IntegerValue max_magnitude(0);
1915 weights.clear();
1916 std::vector<ColIndex> col_candidates;
1917 for (const ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
1918 if (dense_cut[col] == 0) continue;
1919
1920 max_magnitude = std::max(max_magnitude, IntTypeAbs(dense_cut[col]));
1921 const int col_degree = matrix.ColumnNumEntries(col).value();
1922 if (col_degree <= 1) continue;
1923 if (simplex_.GetVariableStatus(col) != glop::VariableStatus::BASIC) {
1924 continue;
1925 }
1926
1927 const IntegerVariable var = integer_variables_[col.value()];
1928 const double lp_value = expanded_lp_solution_[var];
1929 const double lb = ToDouble(integer_trail_->LevelZeroLowerBound(var));
1930 const double ub = ToDouble(integer_trail_->LevelZeroUpperBound(var));
1931 const double bound_distance = std::min(ub - lp_value, lp_value - lb);
1932 if (bound_distance > 1e-2) {
1933 weights.push_back(bound_distance);
1934 col_candidates.push_back(col);
1935 }
1936 }
1937 if (col_candidates.empty()) break;
1938
1939 const ColIndex var_to_eliminate =
1940 col_candidates[WeightedPick(weights, *random_)];
1941
1942 // What rows can we add to eliminate var_to_eliminate?
1943 std::vector<RowIndex> possible_rows;
1944 weights.clear();
1945 for (const auto entry_index : matrix.Column(var_to_eliminate)) {
1946 const RowIndex row = matrix.EntryRow(entry_index);
1947 const glop::Fractional coeff = matrix.EntryCoefficient(entry_index);
1948
1949 // We disallow all the rows that contain a variable that we already
1950 // eliminated (or are about to). This mean that we choose rows that
1951 // form a "triangular" matrix on the position we choose to eliminate.
1952 if (used_rows[row]) continue;
1953 used_rows[row] = true;
1954
1955 // Note that we consider all rows here, not only tight one. This makes a
1956 // big difference on problem like blp-ic98.pb.gz. We can also use the
1957 // integrality of the slack when adding a non-tight row to derive good
1958 // cuts. Also, non-tight row will have a low weight, so they should
1959 // still be chosen after the tight-one in most situation.
1960 bool add_row = false;
1961 if (!integer_lp_[row].ub_is_trivial) {
1962 if (coeff > 0.0) {
1963 if (dense_cut[var_to_eliminate] < 0) add_row = true;
1964 } else {
1965 if (dense_cut[var_to_eliminate] > 0) add_row = true;
1966 }
1967 }
1968 if (!integer_lp_[row].lb_is_trivial) {
1969 if (coeff > 0.0) {
1970 if (dense_cut[var_to_eliminate] > 0) add_row = true;
1971 } else {
1972 if (dense_cut[var_to_eliminate] < 0) add_row = true;
1973 }
1974 }
1975 if (add_row) {
1976 possible_rows.push_back(row);
1977 weights.push_back(row_weights[row]);
1978 }
1979 }
1980 if (possible_rows.empty()) break;
1981
1982 const RowIndex row_to_combine =
1983 possible_rows[WeightedPick(weights, *random_)];
1984
1985 // Find the coefficient of the variable to eliminate.
1986 IntegerValue to_combine_coeff = 0;
1987 const LinearConstraintInternal& ct_to_combine =
1988 integer_lp_[row_to_combine];
1989 for (int i = 0; i < ct_to_combine.num_terms; ++i) {
1990 const int index = ct_to_combine.start_in_buffer + i;
1991 if (integer_lp_cols_[index] == var_to_eliminate) {
1992 to_combine_coeff = integer_lp_coeffs_[index];
1993 break;
1994 }
1995 }
1996 CHECK_NE(to_combine_coeff, 0);
1997
1998 IntegerValue mult1 = -to_combine_coeff;
1999 IntegerValue mult2 = dense_cut[var_to_eliminate];
2000 CHECK_NE(mult2, 0);
2001 if (mult1 < 0) {
2002 mult1 = -mult1;
2003 mult2 = -mult2;
2004 }
2005
2006 const IntegerValue gcd = IntegerValue(
2007 MathUtil::GCD64(std::abs(mult1.value()), std::abs(mult2.value())));
2008 CHECK_NE(gcd, 0);
2009 mult1 /= gcd;
2010 mult2 /= gcd;
2011
2012 // Overflow detection.
2013 //
2014 // TODO(user): do that in the possible_rows selection? only problem is
2015 // that we do not have the integer coefficient there...
2016 for (std::pair<RowIndex, IntegerValue>& entry : integer_multipliers) {
2017 max_magnitude = std::max(max_magnitude, IntTypeAbs(entry.second));
2018 }
2019 if (CapAdd(CapProd(max_magnitude.value(), std::abs(mult1.value())),
2020 CapProd(infinity_norms_[row_to_combine].value(),
2021 std::abs(mult2.value()))) ==
2022 std::numeric_limits<int64_t>::max()) {
2023 break;
2024 }
2025
2026 for (std::pair<RowIndex, IntegerValue>& entry : integer_multipliers) {
2027 dtime_num_entries += integer_lp_[entry.first].num_terms;
2028 entry.second *= mult1;
2029 }
2030 dtime_num_entries += integer_lp_[row_to_combine].num_terms;
2031 integer_multipliers.push_back({row_to_combine, mult2});
2032
2033 // TODO(user): Not supper efficient to recombine the rows.
2034 if (AddCutFromConstraints(absl::StrCat("MIR_", i + 2),
2035 integer_multipliers)) {
2036 break;
2037 }
2038
2039 // Minor optim: the computation below is only needed if we do one more
2040 // iteration.
2041 if (i + 1 == kMaxAggregation) break;
2042
2043 for (ColIndex col : non_zeros_.PositionsSetAtLeastOnce()) {
2044 dense_cut[col] *= mult1;
2045 }
2046 for (int i = 0; i < ct_to_combine.num_terms; ++i) {
2047 const int index = ct_to_combine.start_in_buffer + i;
2048 const ColIndex col = integer_lp_cols_[index];
2049 const IntegerValue coeff = integer_lp_coeffs_[index];
2050 non_zeros_.Set(col);
2051 dense_cut[col] += coeff * mult2;
2052 }
2053 }
2054 }
2055}
2056
2057void LinearProgrammingConstraint::AddZeroHalfCuts() {
2058 if (time_limit_->LimitReached()) return;
2059
2060 tmp_lp_values_.clear();
2061 tmp_var_lbs_.clear();
2062 tmp_var_ubs_.clear();
2063 for (const IntegerVariable var : integer_variables_) {
2064 tmp_lp_values_.push_back(expanded_lp_solution_[var]);
2065 tmp_var_lbs_.push_back(integer_trail_->LevelZeroLowerBound(var));
2066 tmp_var_ubs_.push_back(integer_trail_->LevelZeroUpperBound(var));
2067 }
2068
2069 // TODO(user): See if it make sense to try to use implied bounds there.
2070 zero_half_cut_helper_.ProcessVariables(tmp_lp_values_, tmp_var_lbs_,
2071 tmp_var_ubs_);
2072 for (glop::RowIndex row(0); row < integer_lp_.size(); ++row) {
2073 // Even though we could use non-tight row, for now we prefer to use tight
2074 // ones.
2075 const auto status = simplex_.GetConstraintStatus(row);
2076 if (status == glop::ConstraintStatus::BASIC) continue;
2077 if (status == glop::ConstraintStatus::FREE) continue;
2078
2079 zero_half_cut_helper_.AddOneConstraint(
2080 row, IntegerLpRowCols(row), IntegerLpRowCoeffs(row),
2081 integer_lp_[row].lb, integer_lp_[row].ub);
2082 }
2083 for (const std::vector<std::pair<RowIndex, IntegerValue>>& multipliers :
2084 zero_half_cut_helper_.InterestingCandidates(random_)) {
2085 if (time_limit_->LimitReached()) break;
2086
2087 // TODO(user): Make sure that if the resulting linear coefficients are not
2088 // too high, we do try a "divisor" of two and thus try a true zero-half cut
2089 // instead of just using our best MIR heuristic (which might still be better
2090 // though).
2091 AddCutFromConstraints("ZERO_HALF", multipliers);
2092 }
2093}
2094
2095void LinearProgrammingConstraint::UpdateSimplexIterationLimit(
2096 const int64_t min_iter, const int64_t max_iter) {
2097 if (parameters_.linearization_level() < 2) return;
2098 const int64_t num_degenerate_columns = CalculateDegeneracy();
2099 const int64_t num_cols = simplex_.GetProblemNumCols().value();
2100 if (num_cols <= 0) {
2101 return;
2102 }
2103 CHECK_GT(num_cols, 0);
2104 const int64_t decrease_factor = (10 * num_degenerate_columns) / num_cols;
2105 if (simplex_.GetProblemStatus() == glop::ProblemStatus::DUAL_FEASIBLE) {
2106 // We reached here probably because we predicted wrong. We use this as a
2107 // signal to increase the iterations or punish less for degeneracy compare
2108 // to the other part.
2109 if (is_degenerate_) {
2110 next_simplex_iter_ /= std::max(int64_t{1}, decrease_factor);
2111 } else {
2112 next_simplex_iter_ *= 2;
2113 }
2114 } else if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
2115 if (is_degenerate_) {
2116 next_simplex_iter_ /= std::max(int64_t{1}, 2 * decrease_factor);
2117 } else {
2118 // This is the most common case. We use the size of the problem to
2119 // determine the limit and ignore the previous limit.
2120 next_simplex_iter_ = num_cols / 40;
2121 }
2122 }
2123 next_simplex_iter_ =
2124 std::max(min_iter, std::min(max_iter, next_simplex_iter_));
2125}
2126
2128 const int old_num_force = num_force_lp_call_on_next_propagate_;
2129 num_force_lp_call_on_next_propagate_ = 0;
2130 if (!enabled_) return true;
2131 if (time_limit_->LimitReached()) return true;
2132 const int64_t timestamp_at_function_start = integer_trail_->num_enqueues();
2133
2134 UpdateBoundsOfLpVariables();
2135
2136 // TODO(user): It seems the time we loose by not stopping early might be worth
2137 // it because we end up with a better explanation at optimality.
2138 if (/* DISABLES CODE */ (false) && objective_is_defined_) {
2139 // We put a limit on the dual objective since there is no point increasing
2140 // it past our current objective upper-bound (we will already fail as soon
2141 // as we pass it). Note that this limit is properly transformed using the
2142 // objective scaling factor and offset stored in lp_data_.
2143 //
2144 // Note that we use a bigger epsilon here to be sure that if we abort
2145 // because of this, we will report a conflict.
2146 simplex_params_.set_objective_upper_limit(
2147 static_cast<double>(integer_trail_->UpperBound(objective_cp_).value() +
2148 100.0 * kCpEpsilon));
2149 }
2150
2151 // Put an iteration limit on the work we do in the simplex for this call. Note
2152 // that because we are "incremental", even if we don't solve it this time we
2153 // will make progress towards a solve in the lower node of the tree search.
2154 if (trail_->CurrentDecisionLevel() == 0) {
2155 simplex_params_.set_max_number_of_iterations(
2156 parameters_.root_lp_iterations());
2157 } else {
2158 simplex_params_.set_max_number_of_iterations(next_simplex_iter_);
2159 }
2160
2161 simplex_.SetParameters(simplex_params_);
2162 simplex_.SetRandom(*random_);
2163 if (!SolveLp()) return true;
2164 if (!AnalyzeLp()) return false;
2165
2166 // Add new constraints to the LP and resolve?
2167 const int max_cuts_rounds = trail_->CurrentDecisionLevel() == 0
2168 ? parameters_.max_cut_rounds_at_level_zero()
2169 : 1;
2170 int cuts_round = 0;
2171 while (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL &&
2172 cuts_round < max_cuts_rounds) {
2173 // We are about to spend some effort finding cuts or changing the LP.
2174 // If one of the LP solve done by this function propagated something, it
2175 // seems better to reach the propagation fix point first before doing that.
2176 //
2177 // Heuristic: if we seem to be in a propagation loop, we might need more
2178 // cut/lazy-constraints in order to just get out of it, so we loop only if
2179 // num_force_lp_call_on_next_propagate_ is small.
2180 if (integer_trail_->num_enqueues() > timestamp_at_function_start &&
2181 old_num_force < 3) {
2182 num_force_lp_call_on_next_propagate_ = old_num_force + 1;
2183 watcher_->CallAgainDuringThisPropagation();
2184 return true;
2185 }
2186
2187 // We wait for the first batch of problem constraints to be added before we
2188 // begin to generate cuts. Note that we rely on num_solves_ since on some
2189 // problems there is no other constraints than the cuts.
2190 cuts_round++;
2191 if (parameters_.cut_level() > 0 &&
2192 (num_solves_ > 1 || !parameters_.add_lp_constraints_lazily())) {
2193 // This must be called first.
2194 implied_bounds_processor_.RecomputeCacheAndSeparateSomeImpliedBoundCuts(
2195 expanded_lp_solution_);
2196 if (parameters_.add_rlt_cuts()) {
2197 rlt_cut_helper_.Initialize(extended_integer_variables_);
2198 }
2199
2200 // The "generic" cuts are currently part of this class as they are using
2201 // data from the current LP.
2202 //
2203 // TODO(user): Refactor so that they are just normal cut generators?
2204 const int level = trail_->CurrentDecisionLevel();
2205 if (trail_->CurrentDecisionLevel() == 0) {
2206 problem_proven_infeasible_by_cuts_ = false;
2207 if (parameters_.add_objective_cut()) AddObjectiveCut();
2208 if (parameters_.add_mir_cuts()) AddMirCuts();
2209 if (parameters_.add_cg_cuts()) AddCGCuts();
2210 if (parameters_.add_zero_half_cuts()) AddZeroHalfCuts();
2211 if (problem_proven_infeasible_by_cuts_) {
2212 return integer_trail_->ReportConflict({});
2213 }
2214 top_n_cuts_.TransferToManager(&constraint_manager_);
2215 }
2216
2217 // Try to add cuts.
2218 if (level == 0 || !parameters_.only_add_cuts_at_level_zero()) {
2219 for (CutGenerator& generator : cut_generators_) {
2220 if (level > 0 && generator.only_run_at_level_zero) continue;
2221 if (!generator.generate_cuts(&constraint_manager_)) {
2222 return false;
2223 }
2224 }
2225 }
2226
2227 implied_bounds_processor_.IbCutPool().TransferToManager(
2228 &constraint_manager_);
2229 }
2230
2231 int num_added = 0;
2232 if (constraint_manager_.ChangeLp(&state_, &num_added)) {
2233 ++num_lp_changes_;
2234 simplex_.LoadStateForNextSolve(state_);
2235 if (!CreateLpFromConstraintManager()) {
2236 return integer_trail_->ReportConflict({});
2237 }
2238
2239 // If we didn't add any new constraint, we delay the next Solve() since
2240 // likely the optimal didn't change.
2241 if (num_added == 0) {
2242 break;
2243 }
2244
2245 const double old_obj = simplex_.GetObjectiveValue();
2246 if (!SolveLp()) return true;
2247 if (!AnalyzeLp()) return false;
2248 if (simplex_.GetProblemStatus() == glop::ProblemStatus::OPTIMAL) {
2249 VLOG(3) << "Relaxation improvement " << old_obj << " -> "
2250 << simplex_.GetObjectiveValue()
2251 << " diff: " << simplex_.GetObjectiveValue() - old_obj
2252 << " level: " << trail_->CurrentDecisionLevel();
2253 }
2254 } else {
2255 if (trail_->CurrentDecisionLevel() == 0) {
2256 lp_at_level_zero_is_final_ = true;
2257 }
2258 break;
2259 }
2260 }
2261
2262 return true;
2263}
2264
2265absl::int128 LinearProgrammingConstraint::GetImpliedLowerBound(
2266 const LinearConstraint& terms) const {
2267 absl::int128 lower_bound(0);
2268 const int size = terms.num_terms;
2269 for (int i = 0; i < size; ++i) {
2270 const IntegerVariable var = terms.vars[i];
2271 const IntegerValue coeff = terms.coeffs[i];
2272 const IntegerValue bound = coeff > 0 ? integer_trail_->LowerBound(var)
2273 : integer_trail_->UpperBound(var);
2274 lower_bound += absl::int128(bound.value()) * absl::int128(coeff.value());
2275 }
2276 return lower_bound;
2277}
2278
2279bool LinearProgrammingConstraint::ScalingCanOverflow(
2280 int power, bool take_objective_into_account,
2281 absl::Span<const std::pair<glop::RowIndex, double>> multipliers,
2282 int64_t overflow_cap) const {
2283 int64_t bound = 0;
2284 const int64_t factor = int64_t{1} << power;
2285 const double factor_as_double = static_cast<double>(factor);
2286 if (take_objective_into_account) {
2287 bound = CapAdd(bound, CapProd(factor, objective_infinity_norm_.value()));
2288 if (bound >= overflow_cap) return true;
2289 }
2290 for (const auto [row, double_coeff] : multipliers) {
2291 const double magnitude =
2292 std::abs(std::round(double_coeff * factor_as_double));
2293 if (std::isnan(magnitude)) return true;
2294 if (magnitude >= static_cast<double>(std::numeric_limits<int64_t>::max())) {
2295 return true;
2296 }
2297 bound = CapAdd(bound, CapProd(static_cast<int64_t>(magnitude),
2298 infinity_norms_[row].value()));
2299 if (bound >= overflow_cap) return true;
2300 }
2301 return bound >= overflow_cap;
2302}
2303
2304void LinearProgrammingConstraint::IgnoreTrivialConstraintMultipliers(
2305 std::vector<std::pair<RowIndex, double>>* lp_multipliers) {
2306 int new_size = 0;
2307 for (const std::pair<RowIndex, double>& p : *lp_multipliers) {
2308 const RowIndex row = p.first;
2309 const Fractional lp_multi = p.second;
2310 if (lp_multi > 0.0 && integer_lp_[row].ub_is_trivial) continue;
2311 if (lp_multi < 0.0 && integer_lp_[row].lb_is_trivial) continue;
2312 (*lp_multipliers)[new_size++] = p;
2313 }
2314 lp_multipliers->resize(new_size);
2315}
2316
2317void LinearProgrammingConstraint::ScaleMultipliers(
2318 absl::Span<const std::pair<RowIndex, double>> lp_multipliers,
2319 bool take_objective_into_account, IntegerValue* scaling,
2320 std::vector<std::pair<RowIndex, IntegerValue>>* output) const {
2321 *scaling = 0;
2322
2323 output->clear();
2324 if (lp_multipliers.empty()) {
2325 // Empty linear combinaison.
2326 return;
2327 }
2328
2329 // TODO(user): we currently do not support scaling down, so we just abort
2330 // if with a scaling of 1, we reach the overflow_cap.
2331 const int64_t overflow_cap = std::numeric_limits<int64_t>::max();
2332 if (ScalingCanOverflow(/*power=*/0, take_objective_into_account,
2333 lp_multipliers, overflow_cap)) {
2334 ++num_scaling_issues_;
2335 return;
2336 }
2337
2338 // Note that we don't try to scale by more than 63 since in practice the
2339 // constraint multipliers should not be all super small.
2340 //
2341 // TODO(user): We could be faster here, but trying to compute the exact power
2342 // in one go with floating points seems tricky. So we just do around 6 passes
2343 // plus the one above for zero.
2344 const int power = BinarySearch<int>(0, 63, [&](int candidate) {
2345 // Because BinarySearch() wants f(upper_bound) to fail, we bypass the test
2346 // here as when the set of floating points are really small, we could pass
2347 // with a large power.
2348 if (candidate >= 63) return false;
2349
2350 return !ScalingCanOverflow(candidate, take_objective_into_account,
2351 lp_multipliers, overflow_cap);
2352 });
2353 *scaling = int64_t{1} << power;
2354
2355 // Scale the multipliers by *scaling.
2356 // Note that we use the exact same formula as in ScalingCanOverflow().
2357 int64_t gcd = scaling->value();
2358 const double scaling_as_double = static_cast<double>(scaling->value());
2359 for (const auto [row, double_coeff] : lp_multipliers) {
2360 const IntegerValue coeff(std::round(double_coeff * scaling_as_double));
2361 if (coeff != 0) {
2362 gcd = std::gcd(gcd, std::abs(coeff.value()));
2363 output->push_back({row, coeff});
2364 }
2365 }
2366 if (gcd > 1) {
2367 *scaling /= gcd;
2368 for (auto& entry : *output) {
2369 entry.second /= gcd;
2370 }
2371 }
2372}
2373
2374template <bool check_overflow>
2375bool LinearProgrammingConstraint::ComputeNewLinearConstraint(
2376 absl::Span<const std::pair<RowIndex, IntegerValue>> integer_multipliers,
2377 ScatteredIntegerVector* scattered_vector, IntegerValue* upper_bound) const {
2378 // Initialize the new constraint.
2379 *upper_bound = 0;
2380 scattered_vector->ClearAndResize(extended_integer_variables_.size());
2381
2382 // Compute the new constraint by taking the linear combination given by
2383 // integer_multipliers of the integer constraints in integer_lp_.
2384 for (const std::pair<RowIndex, IntegerValue>& term : integer_multipliers) {
2385 const RowIndex row = term.first;
2386 const IntegerValue multiplier = term.second;
2387 DCHECK_LT(row, integer_lp_.size());
2388
2389 // Update the constraint.
2390 if (!scattered_vector->AddLinearExpressionMultiple<check_overflow>(
2391 multiplier, IntegerLpRowCols(row), IntegerLpRowCoeffs(row),
2392 infinity_norms_[row])) {
2393 return false;
2394 }
2395
2396 // Update the upper bound.
2397 const IntegerValue bound =
2398 multiplier > 0 ? integer_lp_[row].ub : integer_lp_[row].lb;
2399 if (!AddProductTo(multiplier, bound, upper_bound)) return false;
2400 }
2401
2402 return true;
2403}
2404
2405// TODO(user): no need to update the multipliers.
2406void LinearProgrammingConstraint::AdjustNewLinearConstraint(
2407 std::vector<std::pair<glop::RowIndex, IntegerValue>>* integer_multipliers,
2408 ScatteredIntegerVector* scattered_vector, IntegerValue* upper_bound) const {
2409 const IntegerValue kMaxWantedCoeff(1e18);
2410 bool adjusted = false;
2411 for (std::pair<RowIndex, IntegerValue>& term : *integer_multipliers) {
2412 const RowIndex row = term.first;
2413 const IntegerValue multiplier = term.second;
2414 if (multiplier == 0) continue;
2415
2416 // We will only allow change of the form "multiplier += to_add" with to_add
2417 // in [-negative_limit, positive_limit].
2418 //
2419 // We do not want to_add * row to overflow.
2420 IntegerValue negative_limit =
2421 FloorRatio(kMaxWantedCoeff, infinity_norms_[row]);
2422 IntegerValue positive_limit = negative_limit;
2423
2424 // Make sure we never change the sign of the multiplier, except if the
2425 // row is an equality in which case we don't care.
2426 if (integer_lp_[row].ub != integer_lp_[row].lb) {
2427 if (multiplier > 0) {
2428 negative_limit = std::min(negative_limit, multiplier);
2429 } else {
2430 positive_limit = std::min(positive_limit, -multiplier);
2431 }
2432 }
2433
2434 // Make sure upper_bound + to_add * row_bound never overflow.
2435 const IntegerValue row_bound =
2436 multiplier > 0 ? integer_lp_[row].ub : integer_lp_[row].lb;
2437 if (row_bound != 0) {
2438 const IntegerValue limit1 = FloorRatio(
2439 std::max(IntegerValue(0), kMaxWantedCoeff - IntTypeAbs(*upper_bound)),
2440 IntTypeAbs(row_bound));
2441 const IntegerValue limit2 =
2442 FloorRatio(kMaxWantedCoeff, IntTypeAbs(row_bound));
2443 if ((*upper_bound > 0) == (row_bound > 0)) { // Same sign.
2444 positive_limit = std::min(positive_limit, limit1);
2445 negative_limit = std::min(negative_limit, limit2);
2446 } else {
2447 negative_limit = std::min(negative_limit, limit1);
2448 positive_limit = std::min(positive_limit, limit2);
2449 }
2450 }
2451
2452 // If we add the row to the scattered_vector, diff will indicate by how much
2453 // |upper_bound - ImpliedLB(scattered_vector)| will change. That correspond
2454 // to increasing the multiplier by 1.
2455 //
2456 // At this stage, we are not sure computing sum coeff * bound will not
2457 // overflow, so we use floating point numbers. It is fine to do so since
2458 // this is not directly involved in the actual exact constraint generation:
2459 // these variables are just used in an heuristic.
2460 double common_diff = ToDouble(row_bound);
2461 double positive_diff = 0.0;
2462 double negative_diff = 0.0;
2463
2464 // TODO(user): we could relax a bit some of the condition and allow a sign
2465 // change. It is just trickier to compute the diff when we allow such
2466 // changes.
2467 const LinearConstraintInternal& ct = integer_lp_[row];
2468 for (int i = 0; i < ct.num_terms; ++i) {
2469 const int index = ct.start_in_buffer + i;
2470 const ColIndex col = integer_lp_cols_[index];
2471 const IntegerValue coeff = integer_lp_coeffs_[index];
2472 DCHECK_NE(coeff, 0);
2473
2474 // Moving a variable away from zero seems to improve the bound even
2475 // if it reduces the number of non-zero. Note that this is because of
2476 // this that positive_diff and negative_diff are not the same.
2477 const IntegerValue current = (*scattered_vector)[col];
2478 if (current == 0) {
2479 const IntegerVariable var = integer_variables_[col.value()];
2480 const IntegerValue lb = integer_trail_->LowerBound(var);
2481 const IntegerValue ub = integer_trail_->UpperBound(var);
2482 if (coeff > 0) {
2483 positive_diff -= ToDouble(coeff) * ToDouble(lb);
2484 negative_diff -= ToDouble(coeff) * ToDouble(ub);
2485 } else {
2486 positive_diff -= ToDouble(coeff) * ToDouble(ub);
2487 negative_diff -= ToDouble(coeff) * ToDouble(lb);
2488 }
2489 continue;
2490 }
2491
2492 // We don't want to change the sign of current (except if the variable is
2493 // fixed, but in practice we should have removed any fixed variable, so we
2494 // don't care here) or to have an overflow.
2495 //
2496 // Corner case:
2497 // - IntTypeAbs(current) can be larger than kMaxWantedCoeff!
2498 // - The code assumes that 2 * kMaxWantedCoeff do not overflow.
2499 //
2500 // Note that because we now that limit * abs_coeff will never overflow
2501 // because we used infinity_norms_[row] above.
2502 const IntegerValue abs_coeff = IntTypeAbs(coeff);
2503 const IntegerValue current_magnitude = IntTypeAbs(current);
2504 const IntegerValue overflow_threshold =
2505 std::max(IntegerValue(0), kMaxWantedCoeff - current_magnitude);
2506 if ((current > 0) == (coeff > 0)) { // Same sign.
2507 if (negative_limit * abs_coeff > current_magnitude) {
2508 negative_limit = current_magnitude / abs_coeff;
2509 if (positive_limit == 0 && negative_limit == 0) break;
2510 }
2511 if (positive_limit * abs_coeff > overflow_threshold) {
2512 positive_limit = overflow_threshold / abs_coeff;
2513 if (positive_limit == 0 && negative_limit == 0) break;
2514 }
2515 } else {
2516 if (negative_limit * abs_coeff > overflow_threshold) {
2517 negative_limit = overflow_threshold / abs_coeff;
2518 if (positive_limit == 0 && negative_limit == 0) break;
2519 }
2520 if (positive_limit * abs_coeff > current_magnitude) {
2521 positive_limit = current_magnitude / abs_coeff;
2522 if (positive_limit == 0 && negative_limit == 0) break;
2523 }
2524 }
2525
2526 // This is how diff change.
2527 const IntegerVariable var = integer_variables_[col.value()];
2528 const IntegerValue implied = current > 0
2529 ? integer_trail_->LowerBound(var)
2530 : integer_trail_->UpperBound(var);
2531 if (implied != 0) {
2532 common_diff -= ToDouble(coeff) * ToDouble(implied);
2533 }
2534 }
2535
2536 positive_diff += common_diff;
2537 negative_diff += common_diff;
2538
2539 // Only add a multiple of this row if it tighten the final constraint.
2540 // The positive_diff/negative_diff are supposed to be integer modulo the
2541 // double precision, so we only add a multiple if they seems far away from
2542 // zero.
2543 IntegerValue to_add(0);
2544 if (positive_diff <= -1.0 && positive_limit > 0) {
2545 to_add = positive_limit;
2546 }
2547 if (negative_diff >= 1.0 && negative_limit > 0) {
2548 // Pick this if it is better than the positive sign.
2549 if (to_add == 0 ||
2550 std::abs(ToDouble(negative_limit) * negative_diff) >
2551 std::abs(ToDouble(positive_limit) * positive_diff)) {
2552 to_add = -negative_limit;
2553 }
2554 }
2555 if (to_add != 0) {
2556 term.second += to_add;
2557 *upper_bound += to_add * row_bound;
2558 adjusted = true;
2559 CHECK(scattered_vector
2560 ->AddLinearExpressionMultiple</*check_overflow=*/false>(
2561 to_add, IntegerLpRowCols(row), IntegerLpRowCoeffs(row),
2562 infinity_norms_[row]));
2563 }
2564 }
2565 if (adjusted) ++num_adjusts_;
2566}
2567
2568void LinearProgrammingConstraint::SetReducedCostsInConstraintManager(
2569 const LinearConstraint& ct) {
2570 constraint_manager_.ClearReducedCostsInfo();
2571 absl::int128 ub = ct.ub.value();
2572 absl::int128 level_zero_lb = 0;
2573 for (int i = 0; i < ct.num_terms; ++i) {
2574 IntegerVariable var = ct.vars[i];
2575 IntegerValue coeff = ct.coeffs[i];
2576 if (coeff < 0) {
2577 coeff = -coeff;
2578 var = NegationOf(var);
2579 }
2580
2581 const IntegerValue lb = integer_trail_->LevelZeroLowerBound(var);
2582 level_zero_lb += absl::int128(coeff.value()) * absl::int128(lb.value());
2583
2584 if (lb == integer_trail_->LevelZeroUpperBound(var)) continue;
2585 const LiteralIndex lit = integer_encoder_->GetAssociatedLiteral(
2586 IntegerLiteral::GreaterOrEqual(var, lb + 1));
2587 if (lit != kNoLiteralIndex) {
2588 constraint_manager_.SetLiteralReducedCost(Literal(lit), coeff);
2589 }
2590 }
2591 const absl::int128 gap = absl::int128(ct.ub.value()) - level_zero_lb;
2592 if (gap > 0) {
2593 constraint_manager_.SetReducedCostsGap(gap);
2594 } else {
2595 constraint_manager_.ClearReducedCostsInfo();
2596 }
2597}
2598
2599bool LinearProgrammingConstraint::PropagateLpConstraint(LinearConstraint ct) {
2600 DCHECK(constraint_manager_.DebugCheckConstraint(ct));
2601
2602 // We need to cache this before we std::move() the constraint!
2603 const int num_terms = ct.num_terms;
2604 if (num_terms == 0) {
2605 if (ct.ub >= 0) return true;
2606 return integer_trail_->ReportConflict({}); // Unsat.
2607 }
2608
2609 std::unique_ptr<IntegerSumLE128> cp_constraint(
2610 new IntegerSumLE128(std::move(ct), model_));
2611
2612 // We always propagate level zero bounds first.
2613 // If we are at level zero, there is nothing else to do.
2614 if (!cp_constraint->PropagateAtLevelZero()) return false;
2615 if (trail_->CurrentDecisionLevel() == 0) return true;
2616
2617 // To optimize the memory usage, if the constraint didn't propagate anything,
2618 // we don't need to keep it around.
2619 const int64_t stamp = integer_trail_->num_enqueues();
2620 const bool no_conflict = cp_constraint->Propagate();
2621 if (no_conflict && integer_trail_->num_enqueues() == stamp) return true;
2622
2623 const int64_t current_size =
2624 cumulative_optimal_constraint_sizes_.empty()
2625 ? 0
2626 : cumulative_optimal_constraint_sizes_.back();
2627 optimal_constraints_.push_back(std::move(cp_constraint));
2628 cumulative_optimal_constraint_sizes_.push_back(current_size + num_terms);
2629 rev_optimal_constraints_size_ = optimal_constraints_.size();
2630 return no_conflict;
2631}
2632
2633// The "exact" computation go as follows:
2634//
2635// Given any INTEGER linear combination of the LP constraints, we can create a
2636// new integer constraint that is valid (its computation must not overflow
2637// though). Lets call this "linear_combination <= ub". We can then always add to
2638// it the inequality "objective_terms <= objective_var", so we get:
2639// ImpliedLB(objective_terms + linear_combination) - ub <= objective_var.
2640// where ImpliedLB() is computed from the variable current bounds.
2641//
2642// Now, if we use for the linear combination and approximation of the optimal
2643// negated dual LP values (by scaling them and rounding them to integer), we
2644// will get an EXACT objective lower bound that is more or less the same as the
2645// inexact bound given by the LP relaxation. This allows to derive exact reasons
2646// for any propagation done by this constraint.
2647bool LinearProgrammingConstraint::PropagateExactLpReason() {
2648 // Clear old reason and deductions.
2649 integer_reason_.clear();
2650 deductions_.clear();
2651 deductions_reason_.clear();
2652
2653 // The row multipliers will be the negation of the LP duals.
2654 //
2655 // TODO(user): Provide and use a sparse API in Glop to get the duals.
2656 const RowIndex num_rows = simplex_.GetProblemNumRows();
2657 tmp_lp_multipliers_.clear();
2658 for (RowIndex row(0); row < num_rows; ++row) {
2659 const double value = -simplex_.GetDualValue(row);
2660 if (std::abs(value) < kZeroTolerance) continue;
2661 tmp_lp_multipliers_.push_back({row, scaler_.UnscaleDualValue(row, value)});
2662 }
2663
2664 // In this case, the LP lower bound match the basic objective "constraint"
2665 // propagation. That is there is an LP solution with all objective variable at
2666 // their current best bound. There is no need to do more work here.
2667 if (tmp_lp_multipliers_.empty()) return true;
2668
2669 // For the corner case of an objective of size 1, we do not want or need
2670 // to take it into account.
2671 bool take_objective_into_account = true;
2672 if (objective_cp_is_part_of_lp_) {
2673 // The objective is part of the lp.
2674 // This should only happen for objective with a single term.
2675 CHECK_EQ(integer_objective_.size(), 1);
2676 CHECK_EQ(integer_objective_[0].first, mirror_lp_variable_[objective_cp_]);
2677 CHECK_EQ(integer_objective_[0].second, IntegerValue(1));
2678
2679 take_objective_into_account = false;
2680 }
2681
2682 IntegerValue scaling = 0;
2683 IgnoreTrivialConstraintMultipliers(&tmp_lp_multipliers_);
2684 ScaleMultipliers(tmp_lp_multipliers_, take_objective_into_account, &scaling,
2685 &tmp_integer_multipliers_);
2686 if (scaling == 0) {
2687 VLOG(1) << simplex_.GetProblemStatus();
2688 VLOG(1) << "Issue while computing the exact LP reason. Aborting.";
2689 return true;
2690 }
2691
2692 IntegerValue rc_ub;
2693 CHECK(ComputeNewLinearConstraint</*check_overflow=*/false>(
2694 tmp_integer_multipliers_, &tmp_scattered_vector_, &rc_ub));
2695
2696 std::optional<std::pair<IntegerVariable, IntegerValue>> extra_term =
2697 std::nullopt;
2698 if (take_objective_into_account) {
2699 // The "objective constraint" behave like if the unscaled cp multiplier was
2700 // 1.0, so we will multiply it by this number and add it to reduced_costs.
2701 const IntegerValue obj_scale = scaling;
2702
2703 // TODO(user): Maybe avoid this conversion.
2704 tmp_cols_.clear();
2705 tmp_coeffs_.clear();
2706 for (const auto [col, coeff] : integer_objective_) {
2707 tmp_cols_.push_back(col);
2708 tmp_coeffs_.push_back(coeff);
2709 }
2710 CHECK(tmp_scattered_vector_
2711 .AddLinearExpressionMultiple</*check_overflow=*/false>(
2712 obj_scale, tmp_cols_, tmp_coeffs_, objective_infinity_norm_));
2713 CHECK(AddProductTo(-obj_scale, integer_objective_offset_, &rc_ub));
2714
2715 extra_term = {objective_cp_, -obj_scale};
2716 }
2717
2718 // TODO(user): It seems when the LP as a single variable and the equation is
2719 // obj >= lower_bound, this removes it ? For now we disable this if the
2720 // objective variable is part of the LP (i.e. single objective).
2721 if (take_objective_into_account) {
2722 AdjustNewLinearConstraint(&tmp_integer_multipliers_, &tmp_scattered_vector_,
2723 &rc_ub);
2724 }
2725
2726 // Create the IntegerSumLE that will allow to propagate the objective and more
2727 // generally do the reduced cost fixing.
2728 LinearConstraint explanation =
2729 tmp_scattered_vector_.ConvertToLinearConstraint(
2730 extended_integer_variables_, rc_ub, extra_term);
2731
2732 // Corner case where prevent overflow removed all terms.
2733 if (explanation.num_terms == 0) {
2734 trail_->MutableConflict()->clear();
2735 return explanation.ub >= 0;
2736 }
2737
2738 SetReducedCostsInConstraintManager(explanation);
2739 return PropagateLpConstraint(std::move(explanation));
2740}
2741
2742bool LinearProgrammingConstraint::PropagateExactDualRay() {
2743 IntegerValue scaling;
2744 const glop::DenseColumn ray = simplex_.GetDualRay();
2745 tmp_lp_multipliers_.clear();
2746 for (RowIndex row(0); row < ray.size(); ++row) {
2747 const double value = ray[row];
2748 if (std::abs(value) < kZeroTolerance) continue;
2749
2750 // This is the same as UnscaleLeftSolveValue(). Note that we don't need to
2751 // scale by the objective factor here like we do in UnscaleDualValue().
2752 tmp_lp_multipliers_.push_back({row, row_factors_[row.value()] * value});
2753 }
2754 IgnoreTrivialConstraintMultipliers(&tmp_lp_multipliers_);
2755 ScaleMultipliers(tmp_lp_multipliers_, /*take_objective_into_account=*/false,
2756 &scaling, &tmp_integer_multipliers_);
2757 if (scaling == 0) {
2758 VLOG(1) << "Isse while computing the exact dual ray reason. Aborting.";
2759 return true;
2760 }
2761
2762 IntegerValue new_constraint_ub;
2763 CHECK(ComputeNewLinearConstraint</*check_overflow=*/false>(
2764 tmp_integer_multipliers_, &tmp_scattered_vector_, &new_constraint_ub))
2765 << scaling;
2766
2767 AdjustNewLinearConstraint(&tmp_integer_multipliers_, &tmp_scattered_vector_,
2768 &new_constraint_ub);
2769
2770 LinearConstraint explanation =
2771 tmp_scattered_vector_.ConvertToLinearConstraint(
2772 extended_integer_variables_, new_constraint_ub);
2773
2774 std::string message;
2775 if (VLOG_IS_ON(1)) {
2776 // Unfortunately, we need to set this up before we std::move() it.
2777 message = absl::StrCat("LP exact dual ray not infeasible,",
2778 " implied_lb: ", GetImpliedLowerBound(explanation),
2779 " ub: ", explanation.ub.value());
2780 }
2781
2782 // This should result in a conflict if the precision is good enough.
2783 if (!PropagateLpConstraint(std::move(explanation))) return false;
2784
2785 VLOG(1) << message;
2786 return true;
2787}
2788
2789int64_t LinearProgrammingConstraint::CalculateDegeneracy() {
2790 int num_non_basic_with_zero_rc = 0;
2791 const auto reduced_costs = simplex_.GetReducedCosts().const_view();
2792 for (const glop::ColIndex i : simplex_.GetNotBasicBitRow()) {
2793 if (reduced_costs[i] == 0.0) {
2794 num_non_basic_with_zero_rc++;
2795 }
2796 }
2797 const int64_t num_cols = simplex_.GetProblemNumCols().value();
2798 is_degenerate_ = num_non_basic_with_zero_rc >= 0.3 * num_cols;
2799 return num_non_basic_with_zero_rc;
2800}
2801
2802void LinearProgrammingConstraint::ReducedCostStrengtheningDeductions(
2803 double cp_objective_delta) {
2804 deductions_.clear();
2805
2806 const double lp_objective_delta =
2807 cp_objective_delta / scaler_.ObjectiveScalingFactor();
2808 const int num_vars = integer_variables_.size();
2809 for (int i = 0; i < num_vars; i++) {
2810 const IntegerVariable cp_var = integer_variables_[i];
2811 const glop::ColIndex lp_var = glop::ColIndex(i);
2812 const double rc = simplex_.GetReducedCost(lp_var);
2813 const double value = simplex_.GetVariableValue(lp_var);
2814
2815 if (rc == 0.0) continue;
2816 const double lp_other_bound = value + lp_objective_delta / rc;
2817 const double cp_other_bound =
2818 scaler_.UnscaleVariableValue(lp_var, lp_other_bound);
2819
2820 if (rc > kLpEpsilon) {
2821 const double ub = ToDouble(integer_trail_->UpperBound(cp_var));
2822 const double new_ub = std::floor(cp_other_bound + kCpEpsilon);
2823 if (new_ub < ub) {
2824 // TODO(user): Because rc > kLpEpsilon, the lower_bound of cp_var
2825 // will be part of the reason returned by FillReducedCostsReason(), but
2826 // we actually do not need it here. Same below.
2827 const IntegerValue new_ub_int(static_cast<IntegerValue>(new_ub));
2828 deductions_.push_back(IntegerLiteral::LowerOrEqual(cp_var, new_ub_int));
2829 }
2830 } else if (rc < -kLpEpsilon) {
2831 const double lb = ToDouble(integer_trail_->LowerBound(cp_var));
2832 const double new_lb = std::ceil(cp_other_bound - kCpEpsilon);
2833 if (new_lb > lb) {
2834 const IntegerValue new_lb_int(static_cast<IntegerValue>(new_lb));
2835 deductions_.push_back(
2836 IntegerLiteral::GreaterOrEqual(cp_var, new_lb_int));
2837 }
2838 }
2839 }
2840}
2841
2842void LinearProgrammingConstraint::UpdateAverageReducedCosts() {
2843 const int num_vars = integer_variables_.size();
2844 if (sum_cost_down_.size() < num_vars) {
2845 sum_cost_down_.resize(num_vars, 0.0);
2846 num_cost_down_.resize(num_vars, 0);
2847 sum_cost_up_.resize(num_vars, 0.0);
2848 num_cost_up_.resize(num_vars, 0);
2849 rc_scores_.resize(num_vars, 0.0);
2850 }
2851
2852 // Decay averages.
2853 num_calls_since_reduced_cost_averages_reset_++;
2854 if (num_calls_since_reduced_cost_averages_reset_ == 10000) {
2855 for (int i = 0; i < num_vars; i++) {
2856 sum_cost_up_[i] /= 2;
2857 num_cost_up_[i] /= 2;
2858 sum_cost_down_[i] /= 2;
2859 num_cost_down_[i] /= 2;
2860 }
2861 num_calls_since_reduced_cost_averages_reset_ = 0;
2862 }
2863
2864 // Accumulate reduced costs of all unassigned variables.
2865 for (int i = 0; i < num_vars; i++) {
2866 const IntegerVariable var = integer_variables_[i];
2867
2868 // Skip fixed variables.
2869 if (integer_trail_->IsFixed(var)) continue;
2870
2871 // Skip reduced costs that are zero or close.
2872 const double rc = lp_reduced_cost_[i];
2873 if (std::abs(rc) < kCpEpsilon) continue;
2874
2875 if (rc < 0.0) {
2876 sum_cost_down_[i] -= rc;
2877 num_cost_down_[i]++;
2878 } else {
2879 sum_cost_up_[i] += rc;
2880 num_cost_up_[i]++;
2881 }
2882 }
2883
2884 // Tricky, we artificially reset the rc_rev_int_repository_ to level zero
2885 // so that the rev_rc_start_ is zero.
2886 rc_rev_int_repository_.SetLevel(0);
2887 rc_rev_int_repository_.SetLevel(trail_->CurrentDecisionLevel());
2888 rev_rc_start_ = 0;
2889
2890 // Cache the new score (higher is better) using the average reduced costs
2891 // as a signal.
2892 positions_by_decreasing_rc_score_.clear();
2893 for (int i = 0; i < num_vars; i++) {
2894 // If only one direction exist, we takes its value divided by 2, so that
2895 // such variable should have a smaller cost than the min of the two side
2896 // except if one direction have a really high reduced costs.
2897 const double a_up =
2898 num_cost_up_[i] > 0 ? sum_cost_up_[i] / num_cost_up_[i] : 0.0;
2899 const double a_down =
2900 num_cost_down_[i] > 0 ? sum_cost_down_[i] / num_cost_down_[i] : 0.0;
2901 if (num_cost_down_[i] > 0 && num_cost_up_[i] > 0) {
2902 rc_scores_[i] = std::min(a_up, a_down);
2903 } else {
2904 rc_scores_[i] = 0.5 * (a_down + a_up);
2905 }
2906
2907 // We ignore scores of zero (i.e. no data) and will follow the default
2908 // search heuristic if all variables are like this.
2909 if (rc_scores_[i] > 0.0) {
2910 positions_by_decreasing_rc_score_.push_back({-rc_scores_[i], i});
2911 }
2912 }
2913 std::sort(positions_by_decreasing_rc_score_.begin(),
2914 positions_by_decreasing_rc_score_.end());
2915}
2916
2917std::function<IntegerLiteral()>
2919 return [this]() { return this->LPReducedCostAverageDecision(); };
2920}
2921
2922IntegerLiteral LinearProgrammingConstraint::LPReducedCostAverageDecision() {
2923 // Select noninstantiated variable with highest positive average reduced cost.
2924 int selected_index = -1;
2925 const int size = positions_by_decreasing_rc_score_.size();
2926 rc_rev_int_repository_.SaveState(&rev_rc_start_);
2927 for (int i = rev_rc_start_; i < size; ++i) {
2928 const int index = positions_by_decreasing_rc_score_[i].second;
2929 const IntegerVariable var = integer_variables_[index];
2930 if (integer_trail_->IsFixed(var)) continue;
2931 selected_index = index;
2932 rev_rc_start_ = i;
2933 break;
2934 }
2935
2936 if (selected_index == -1) return IntegerLiteral();
2937 const IntegerVariable var = integer_variables_[selected_index];
2938
2939 // If ceil(value) is current upper bound, try var == upper bound first.
2940 // Guarding with >= prevents numerical problems.
2941 // With 0/1 variables, this will tend to try setting to 1 first,
2942 // which produces more shallow trees.
2943 const IntegerValue ub = integer_trail_->UpperBound(var);
2944 const IntegerValue value_ceil(
2945 std::ceil(this->GetSolutionValue(var) - kCpEpsilon));
2946 if (value_ceil >= ub) {
2947 return IntegerLiteral::GreaterOrEqual(var, ub);
2948 }
2949
2950 // If floor(value) is current lower bound, try var == lower bound first.
2951 // Guarding with <= prevents numerical problems.
2952 const IntegerValue lb = integer_trail_->LowerBound(var);
2953 const IntegerValue value_floor(
2954 std::floor(this->GetSolutionValue(var) + kCpEpsilon));
2955 if (value_floor <= lb) {
2956 return IntegerLiteral::LowerOrEqual(var, lb);
2957 }
2958
2959 // Here lb < value_floor <= value_ceil < ub.
2960 // Try the most promising split between var <= floor or var >= ceil.
2961 const double a_up =
2962 num_cost_up_[selected_index] > 0
2963 ? sum_cost_up_[selected_index] / num_cost_up_[selected_index]
2964 : 0.0;
2965 const double a_down =
2966 num_cost_down_[selected_index] > 0
2967 ? sum_cost_down_[selected_index] / num_cost_down_[selected_index]
2968 : 0.0;
2969 if (a_down < a_up) {
2970 return IntegerLiteral::LowerOrEqual(var, value_floor);
2971 } else {
2972 return IntegerLiteral::GreaterOrEqual(var, value_ceil);
2973 }
2974}
2975
2976absl::Span<const glop::ColIndex> LinearProgrammingConstraint::IntegerLpRowCols(
2977 glop::RowIndex row) const {
2978 const int start = integer_lp_[row].start_in_buffer;
2979 const size_t num_terms = static_cast<size_t>(integer_lp_[row].num_terms);
2980 return {integer_lp_cols_.data() + start, num_terms};
2981}
2982
2983absl::Span<const IntegerValue> LinearProgrammingConstraint::IntegerLpRowCoeffs(
2984 glop::RowIndex row) const {
2985 const int start = integer_lp_[row].start_in_buffer;
2986 const size_t num_terms = static_cast<size_t>(integer_lp_[row].num_terms);
2987 return {integer_lp_coeffs_.data() + start, num_terms};
2988}
2989
2991 return absl::StrFormat("%d rows, %d columns, %d entries", integer_lp_.size(),
2992 integer_variables_.size(), integer_lp_coeffs_.size());
2993}
2994
2995} // namespace sat
2996} // namespace operations_research
static int64_t GCD64(int64_t x, int64_t y)
Definition mathutil.h:107
void SaveState(T *object)
Definition rev.h:62
Fractional VariableScalingFactor(ColIndex col) const
Fractional UnscaleVariableValue(ColIndex col, Fractional value) const
Transforms corresponding value from the scaled domain to the original one.
Fractional GetVariableValue(ColIndex col) const
StrictITISpan< RowIndex, const Fractional > ConstView
Definition lp_types.h:291
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1317
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1321
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
Definition integer.h:1325
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1419
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1412
const std::vector< ConstraintIndex > & LpConstraints() const
const util_intops::StrongVector< ConstraintIndex, ConstraintInfo > & AllConstraints() const
All the constraints managed by this class.
A class that stores the collection of all LP constraints in a model.
void SetLevel(int level) override
ReversibleInterface API.
void AddCutGenerator(CutGenerator generator)
Register a new cut generator with this constraint.
void SetObjectiveCoefficient(IntegerVariable ivar, IntegerValue coeff)
bool IncrementalPropagate(const std::vector< int > &watch_indices) override
LinearProgrammingConstraint(Model *model, absl::Span< const IntegerVariable > vars)
static constexpr SearchBranching LP_SEARCH
bool Add(glop::ColIndex col, IntegerValue value)
Does vector[col] += value and return false in case of overflow.
LinearConstraint ConvertToLinearConstraint(absl::Span< const IntegerVariable > integer_variables, IntegerValue upper_bound, std::optional< std::pair< IntegerVariable, IntegerValue > > extra_term=std::nullopt)
void ConvertToCutData(absl::int128 rhs, absl::Span< const IntegerVariable > integer_variables, absl::Span< const double > lp_solution, IntegerTrail *integer_trail, CutData *result)
std::vector< std::pair< glop::ColIndex, IntegerValue > > GetTerms()
Similar to ConvertToLinearConstraint().
bool AddLinearExpressionMultiple(IntegerValue multiplier, absl::Span< const glop::ColIndex > cols, absl::Span< const IntegerValue > coeffs, IntegerValue max_coeff_magnitude)
Force instantations for tests.
Simple class to add statistics by name and print them at the end.
void assign(size_type n, const value_type &val)
value_type * data()
– Pass-through methods to STL vector ----------------------------------—
void push_back(const value_type &val)
constexpr ColIndex kInvalidCol(-1)
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
Definition lp_types.h:54
RowIndex ColToRowIndex(ColIndex col)
Get the RowIndex corresponding to the row # col.
Definition lp_types.h:57
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:380
StrictITIVector< ColIndex, Fractional > DenseRow
Row-vector types. Row-vector types are indexed by a column index.
Definition lp_types.h:351
@ ABNORMAL
An error occurred during the solving process.
Definition lp_types.h:158
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
bool AddProductTo(IntegerValue a, IntegerValue b, IntegerValue *result)
Computes result += a * b, and return false iff there is an overflow.
void DivideByGCD(LinearConstraint *constraint)
IntType IntTypeAbs(IntType t)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
Definition integer.cc:52
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
IntegerVariable PositiveVariable(IntegerVariable i)
int WeightedPick(absl::Span< const double > input, absl::BitGenRef random)
Definition util.cc:426
bool VariableIsPositive(IntegerVariable i)
LinearConstraintPropagator< true > IntegerSumLE128
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
Overflows and saturated arithmetic.
double ToDouble(IntegerValue value)
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
bool AddIntoOverflow(int64_t x, int64_t *y)
const bool DEBUG_MODE
Definition radix_sort.h:266
Point BinarySearch(Point x_true, Point x_false, std::function< bool(Point)> f)
int64_t CapProd(int64_t x, int64_t y)
Our cut are always of the form linear_expression <= rhs.
Definition cuts.h:116
std::vector< CutTerm > terms
Definition cuts.h:152
bool AppendOneTerm(IntegerVariable var, IntegerValue coeff, double lp_value, IntegerValue lb, IntegerValue ub)
Definition cuts.cc:137
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 IntegerValue > CoeffsAsSpan() const
absl::Span< const IntegerVariable > VarsAsSpan() const
Same as ModelLpValues for reduced costs.