OR-Tools  9.6
max_hs.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/sat/max_hs.h"
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <functional>
20 #include <limits>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/flags/flag.h"
27 #include "absl/meta/type_traits.h"
28 #include "absl/random/random.h"
29 #include "absl/strings/string_view.h"
30 #include "ortools/base/cleanup.h"
31 #include "ortools/base/logging.h"
33 #if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
35 #endif // __PORTABLE_PLATFORM__
36 #include "ortools/linear_solver/linear_solver.pb.h"
37 #include "ortools/sat/cp_model.pb.h"
39 #include "ortools/sat/integer.h"
43 #include "ortools/sat/model.h"
45 #include "ortools/sat/sat_base.h"
46 #include "ortools/sat/sat_parameters.pb.h"
47 #include "ortools/sat/sat_solver.h"
49 #include "ortools/sat/util.h"
52 
53 // TODO(user): Remove this flag when experiments are stable.
55  int, max_hs_strategy, 0,
56  "MaxHsStrategy: 0 extract only objective variable, 1 extract all variables "
57  "colocated with objective variables, 2 extract all variables in the "
58  "linearization");
59 
60 namespace operations_research {
61 namespace sat {
62 
64  const CpModelProto& model_proto,
65  const ObjectiveDefinition& objective_definition,
66  const std::function<void()>& feasible_solution_observer, Model* model)
67  : model_proto_(model_proto),
68  objective_definition_(objective_definition),
69  feasible_solution_observer_(feasible_solution_observer),
70  model_(model),
71  sat_solver_(model->GetOrCreate<SatSolver>()),
72  time_limit_(model->GetOrCreate<TimeLimit>()),
73  parameters_(*model->GetOrCreate<SatParameters>()),
74  random_(model->GetOrCreate<ModelRandomGenerator>()),
75  shared_response_(model->GetOrCreate<SharedResponseManager>()),
76  integer_trail_(model->GetOrCreate<IntegerTrail>()),
77  integer_encoder_(model_->GetOrCreate<IntegerEncoder>()) {
78  request_.set_solver_specific_parameters("limits/gap = 0");
79  request_.set_solver_type(MPModelRequest::SCIP_MIXED_INTEGER_PROGRAMMING);
80 }
81 
82 bool HittingSetOptimizer::ImportFromOtherWorkers() {
83  auto* level_zero_callbacks = model_->GetOrCreate<LevelZeroCallbackHelper>();
84  for (const auto& cb : level_zero_callbacks->callbacks) {
85  if (!cb()) {
86  sat_solver_->NotifyThatModelIsUnsat();
87  return false;
88  }
89  }
90  return true;
91 }
92 
93 // Slightly different algo than FindCores() which aim to extract more cores, but
94 // not necessarily non-overlaping ones.
95 SatSolver::Status HittingSetOptimizer::FindMultipleCoresForMaxHs(
96  std::vector<Literal> assumptions,
97  std::vector<std::vector<Literal>>* cores) {
98  cores->clear();
99  const double saved_dlimit = time_limit_->GetDeterministicLimit();
100  auto cleanup = ::absl::MakeCleanup([this, saved_dlimit]() {
101  time_limit_->ChangeDeterministicLimit(saved_dlimit);
102  });
103 
104  bool first_loop = true;
105  do {
106  if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
107 
108  // The order of assumptions do not matter.
109  // Randomizing it should improve diversity.
110  std::shuffle(assumptions.begin(), assumptions.end(), *random_);
111 
112  const SatSolver::Status result =
113  ResetAndSolveIntegerProblem(assumptions, model_);
114  if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
115  std::vector<Literal> core = sat_solver_->GetLastIncompatibleDecisions();
116  if (sat_solver_->parameters().minimize_core()) {
117  MinimizeCoreWithPropagation(time_limit_, sat_solver_, &core);
118  }
119  CHECK(!core.empty());
120  cores->push_back(core);
121  if (!parameters_.find_multiple_cores()) break;
122 
123  // Pick a random literal from the core and remove it from the set of
124  // assumptions.
125  CHECK(!core.empty());
126  const Literal random_literal =
127  core[absl::Uniform<int>(*random_, 0, core.size())];
128  for (int i = 0; i < assumptions.size(); ++i) {
129  if (assumptions[i] == random_literal) {
130  std::swap(assumptions[i], assumptions.back());
131  assumptions.pop_back();
132  break;
133  }
134  }
135 
136  // Once we found at least one core, we impose a time limit to not spend
137  // too much time finding more.
138  if (first_loop) {
139  time_limit_->ChangeDeterministicLimit(std::min(
140  saved_dlimit, time_limit_->GetElapsedDeterministicTime() + 1.0));
141  first_loop = false;
142  }
143  } while (!assumptions.empty());
144 
146 }
147 
148 int HittingSetOptimizer::GetExtractedIndex(IntegerVariable var) const {
149  if (var.value() >= sat_var_to_mp_var_.size()) return kUnextracted;
150  return sat_var_to_mp_var_[var];
151 }
152 
153 void HittingSetOptimizer::ExtractObjectiveVariables() {
154  const std::vector<IntegerVariable>& variables = objective_definition_.vars;
155  const std::vector<IntegerValue>& coefficients = objective_definition_.coeffs;
156  MPModelProto* hs_model = request_.mutable_model();
157 
158  // Create the initial objective constraint.
159  // It is used to constraint the objective during search.
160  if (obj_constraint_ == nullptr) {
161  obj_constraint_ = hs_model->add_constraint();
162  obj_constraint_->set_lower_bound(-std::numeric_limits<double>::infinity());
163  obj_constraint_->set_upper_bound(std::numeric_limits<double>::infinity());
164  }
165 
166  // Extract the objective variables.
167  for (int i = 0; i < variables.size(); ++i) {
168  IntegerVariable var = variables[i];
169  IntegerValue coeff = coefficients[i];
170 
171  // Link the extracted variable to the positive variable.
172  if (!VariableIsPositive(var)) {
173  var = NegationOf(var);
174  coeff = -coeff;
175  }
176 
177  // Normalized objective variables expects positive coefficients.
178  if (coeff > 0) {
179  normalized_objective_variables_.push_back(var);
180  normalized_objective_coefficients_.push_back(coeff);
181  } else {
182  normalized_objective_variables_.push_back(NegationOf(var));
183  normalized_objective_coefficients_.push_back(-coeff);
184  }
185 
186  // Extract.
187  const int index = hs_model->variable_size();
188  obj_constraint_->add_var_index(index);
189  obj_constraint_->add_coefficient(ToDouble(coeff));
190 
191  MPVariableProto* var_proto = hs_model->add_variable();
192  var_proto->set_lower_bound(ToDouble(integer_trail_->LowerBound(var)));
193  var_proto->set_upper_bound(ToDouble(integer_trail_->UpperBound(var)));
194  var_proto->set_objective_coefficient(ToDouble(coeff));
195  var_proto->set_is_integer(true);
196 
197  // Store extraction info.
198  const int max_index = std::max(var.value(), NegationOf(var).value());
199  if (max_index >= sat_var_to_mp_var_.size()) {
200  sat_var_to_mp_var_.resize(max_index + 1, -1);
201  }
202  sat_var_to_mp_var_[var] = index;
203  sat_var_to_mp_var_[NegationOf(var)] = index;
204  extracted_variables_info_.push_back({var, var_proto});
205  }
206 }
207 
208 void HittingSetOptimizer::ExtractAdditionalVariables(
209  const std::vector<IntegerVariable>& to_extract) {
210  MPModelProto* hs_model = request_.mutable_model();
211 
212  VLOG(2) << "Extract " << to_extract.size() << " additional variables";
213  for (IntegerVariable tmp_var : to_extract) {
214  if (GetExtractedIndex(tmp_var) != kUnextracted) continue;
215 
216  // Use the positive variable for the domain.
217  const IntegerVariable var = PositiveVariable(tmp_var);
218 
219  const int index = hs_model->variable_size();
220  MPVariableProto* var_proto = hs_model->add_variable();
221  var_proto->set_lower_bound(ToDouble(integer_trail_->LowerBound(var)));
222  var_proto->set_upper_bound(ToDouble(integer_trail_->UpperBound(var)));
223  var_proto->set_is_integer(true);
224 
225  // Store extraction info.
226  const int max_index = std::max(var.value(), NegationOf(var).value());
227  if (max_index >= sat_var_to_mp_var_.size()) {
228  sat_var_to_mp_var_.resize(max_index + 1, -1);
229  }
230  sat_var_to_mp_var_[var] = index;
231  sat_var_to_mp_var_[NegationOf(var)] = index;
232  extracted_variables_info_.push_back({var, var_proto});
233  }
234 }
235 
236 // This code will use heuristics to decide which non-objective variables to
237 // extract:
238 // 0: no additional variables.
239 // 1: any variable appearing in the same constraint as an objective variable.
240 // 2: all variables appearing in the linear relaxation.
241 //
242 // TODO(user): We could also decide to extract all if small enough.
243 std::vector<IntegerVariable>
244 HittingSetOptimizer::ComputeAdditionalVariablesToExtract() {
245  absl::flat_hash_set<IntegerVariable> result_set;
246  if (absl::GetFlag(FLAGS_max_hs_strategy) == 0) return {};
247  const bool extract_all = absl::GetFlag(FLAGS_max_hs_strategy) == 2;
248 
249  for (const std::vector<Literal>& literals : relaxation_.at_most_ones) {
250  bool found_at_least_one = extract_all;
251  for (const Literal literal : literals) {
252  if (GetExtractedIndex(integer_encoder_->GetLiteralView(literal)) !=
253  kUnextracted) {
254  found_at_least_one = true;
255  }
256  if (found_at_least_one) break;
257  }
258  if (!found_at_least_one) continue;
259  for (const Literal literal : literals) {
260  const IntegerVariable var = integer_encoder_->GetLiteralView(literal);
261  if (GetExtractedIndex(var) == kUnextracted) {
262  result_set.insert(PositiveVariable(var));
263  }
264  }
265  }
266 
267  for (const LinearConstraint& linear : relaxation_.linear_constraints) {
268  bool found_at_least_one = extract_all;
269  for (const IntegerVariable var : linear.vars) {
270  if (GetExtractedIndex(var) != kUnextracted) {
271  found_at_least_one = true;
272  }
273  if (found_at_least_one) break;
274  }
275  if (!found_at_least_one) continue;
276  for (const IntegerVariable var : linear.vars) {
277  if (GetExtractedIndex(var) == kUnextracted) {
278  result_set.insert(PositiveVariable(var));
279  }
280  }
281  }
282 
283  std::vector<IntegerVariable> result(result_set.begin(), result_set.end());
284  std::sort(result.begin(), result.end());
285 
286  return result;
287 }
288 
289 void HittingSetOptimizer::ProjectAndAddAtMostOne(
290  const std::vector<Literal>& literals) {
291  LinearConstraintBuilder builder(model_, 0, 1);
292  for (const Literal& literal : literals) {
293  if (!builder.AddLiteralTerm(literal, 1)) {
294  VLOG(3) << "Could not extract literal " << literal;
295  }
296  }
297 
298  if (ProjectAndAddLinear(builder.Build()) != nullptr) {
299  num_extracted_at_most_ones_++;
300  }
301 }
302 
303 MPConstraintProto* HittingSetOptimizer::ProjectAndAddLinear(
304  const LinearConstraint& linear) {
305  int num_extracted_variables = 0;
306  for (int i = 0; i < linear.vars.size(); ++i) {
307  if (GetExtractedIndex(PositiveVariable(linear.vars[i])) != kUnextracted) {
308  num_extracted_variables++;
309  }
310  }
311  if (num_extracted_variables <= 1) return nullptr;
312 
313  MPConstraintProto* ct = request_.mutable_model()->add_constraint();
314  ProjectLinear(linear, ct);
315  return ct;
316 }
317 
318 void HittingSetOptimizer::ProjectLinear(const LinearConstraint& linear,
319  MPConstraintProto* ct) {
320  IntegerValue lb = linear.lb;
321  IntegerValue ub = linear.ub;
322 
323  for (int i = 0; i < linear.vars.size(); ++i) {
324  const IntegerVariable var = linear.vars[i];
325  const IntegerValue coeff = linear.coeffs[i];
326  const int index = GetExtractedIndex(PositiveVariable(var));
327  const bool negated = !VariableIsPositive(var);
328  if (index != kUnextracted) {
329  ct->add_var_index(index);
330  ct->add_coefficient(negated ? -ToDouble(coeff) : ToDouble(coeff));
331  } else {
332  const IntegerValue var_lb = integer_trail_->LevelZeroLowerBound(var);
333  const IntegerValue var_ub = integer_trail_->LevelZeroUpperBound(var);
334 
335  if (coeff > 0) {
336  if (lb != kMinIntegerValue) lb -= coeff * var_ub;
337  if (ub != kMaxIntegerValue) ub -= coeff * var_lb;
338  } else {
339  if (lb != kMinIntegerValue) lb -= coeff * var_lb;
340  if (ub != kMaxIntegerValue) ub -= coeff * var_ub;
341  }
342  }
343  }
344 
345  ct->set_lower_bound(ToDouble(lb));
346  ct->set_upper_bound(ToDouble(ub));
347 }
348 
349 bool HittingSetOptimizer::ComputeInitialMpModel() {
350  if (!ImportFromOtherWorkers()) return false;
351 
352  ExtractObjectiveVariables();
353 
354  // Linearize the constraints from the model.
355  ActivityBoundHelper activity_bound_helper;
356  activity_bound_helper.AddAllAtMostOnes(model_proto_);
357  for (const auto& ct : model_proto_.constraints()) {
358  TryToLinearizeConstraint(model_proto_, ct, /*linearization_level=*/2,
359  model_, &relaxation_, &activity_bound_helper);
360  }
361 
362  ExtractAdditionalVariables(ComputeAdditionalVariablesToExtract());
363 
364  // Build the MPModel from the linear relaxation.
365  for (const auto& literals : relaxation_.at_most_ones) {
366  ProjectAndAddAtMostOne(literals);
367  }
368  if (num_extracted_at_most_ones_ > 0) {
369  VLOG(2) << "Projected " << num_extracted_at_most_ones_ << "/"
370  << relaxation_.at_most_ones.size() << " at_most_ones constraints";
371  }
372 
373  for (int i = 0; i < relaxation_.linear_constraints.size(); ++i) {
374  MPConstraintProto* ct =
375  ProjectAndAddLinear(relaxation_.linear_constraints[i]);
376  if (ct != nullptr) linear_extract_info_.push_back({i, ct});
377  }
378  if (!linear_extract_info_.empty()) {
379  VLOG(2) << "Projected " << linear_extract_info_.size() << "/"
380  << relaxation_.linear_constraints.size() << " linear constraints";
381  }
382  return true;
383 }
384 
385 void HittingSetOptimizer::TightenMpModel() {
386  // Update the MP variables bounds from the SAT level 0 bounds.
387  for (const auto& [var, var_proto] : extracted_variables_info_) {
388  var_proto->set_lower_bound(ToDouble(integer_trail_->LowerBound(var)));
389  var_proto->set_upper_bound(ToDouble(integer_trail_->UpperBound(var)));
390  }
391 
392  int tightened = 0;
393  for (const auto& [index, ct] : linear_extract_info_) {
394  const double original_lb = ct->lower_bound();
395  const double original_ub = ct->upper_bound();
396  ct->Clear();
397  ProjectLinear(relaxation_.linear_constraints[index], ct);
398  if (original_lb != ct->lower_bound() || original_ub != ct->upper_bound()) {
399  tightened++;
400  }
401  }
402  if (tightened > 0) {
403  VLOG(2) << "Tightened " << tightened << " linear constraints";
404  }
405 }
406 
407 bool HittingSetOptimizer::ProcessSolution() {
408  const std::vector<IntegerVariable>& variables = objective_definition_.vars;
409  const std::vector<IntegerValue>& coefficients = objective_definition_.coeffs;
410 
411  // We don't assume that objective_var is linked with its linear term, so
412  // we recompute the objective here.
413  IntegerValue objective(0);
414  for (int i = 0; i < variables.size(); ++i) {
415  objective +=
416  coefficients[i] * IntegerValue(model_->Get(Value(variables[i])));
417  }
418  if (objective >
419  integer_trail_->UpperBound(objective_definition_.objective_var)) {
420  return true;
421  }
422 
423  if (feasible_solution_observer_ != nullptr) {
424  feasible_solution_observer_();
425  }
426 
427  // Constrain objective_var. This has a better result when objective_var is
428  // used in an LP relaxation for instance.
429  sat_solver_->Backtrack(0);
430  sat_solver_->SetAssumptionLevel(0);
431  if (!integer_trail_->Enqueue(
432  IntegerLiteral::LowerOrEqual(objective_definition_.objective_var,
433  objective - 1),
434  {}, {})) {
435  return false;
436  }
437  return true;
438 }
439 
440 void HittingSetOptimizer::AddCoresToTheMpModel(
441  const std::vector<std::vector<Literal>>& cores) {
442  MPModelProto* hs_model = request_.mutable_model();
443 
444  for (const std::vector<Literal>& core : cores) {
445  // For cores of size 1, we can just constrain the bound of the variable.
446  if (core.size() == 1) {
447  for (const int index : assumption_to_indices_.at(core.front().Index())) {
448  const IntegerVariable var = normalized_objective_variables_[index];
449  const double new_bound = ToDouble(integer_trail_->LowerBound(var));
450  if (VariableIsPositive(var)) {
451  hs_model->mutable_variable(index)->set_lower_bound(new_bound);
452  } else {
453  hs_model->mutable_variable(index)->set_upper_bound(-new_bound);
454  }
455  }
456  continue;
457  }
458 
459  // Add the corresponding constraint to hs_model.
460  MPConstraintProto* at_least_one = hs_model->add_constraint();
461  at_least_one->set_lower_bound(1.0);
462  for (const Literal lit : core) {
463  for (const int index : assumption_to_indices_.at(lit.Index())) {
464  const IntegerVariable var = normalized_objective_variables_[index];
465  const double sat_lb = ToDouble(integer_trail_->LowerBound(var));
466  // normalized_objective_variables_[index] is mapped onto
467  // hs_model.variable[index] * sign.
468  const double sign = VariableIsPositive(var) ? 1.0 : -1.0;
469  // We round hs_value to the nearest integer. This should help in the
470  // hash_map part.
471  const double hs_value =
472  std::round(response_.variable_value(index)) * sign;
473 
474  if (hs_value == sat_lb) {
475  at_least_one->add_var_index(index);
476  at_least_one->add_coefficient(sign);
477  at_least_one->set_lower_bound(at_least_one->lower_bound() + hs_value);
478  } else {
479  // The operation type (< or >) is consistent for the same variable,
480  // so we do not need this information in the key.
481  const std::pair<int, int64_t> key = {index,
482  static_cast<int64_t>(hs_value)};
483  const int new_bool_var_index = hs_model->variable_size();
484  const auto [it, inserted] =
485  mp_integer_literals_.insert({key, new_bool_var_index});
486 
487  at_least_one->add_var_index(it->second);
488  at_least_one->add_coefficient(1.0);
489 
490  if (inserted) {
491  // Creates the implied bound constraint.
492  MPVariableProto* bool_var = hs_model->add_variable();
493  bool_var->set_lower_bound(0);
494  bool_var->set_upper_bound(1);
495  bool_var->set_is_integer(true);
496 
497  // (bool_var == 1) => x * sign > hs_value.
498  // (x * sign - sat_lb) - (hs_value - sat_lb + 1) * bool_var >= 0.
499  MPConstraintProto* implied_bound = hs_model->add_constraint();
500  implied_bound->set_lower_bound(sat_lb);
501  implied_bound->add_var_index(index);
502  implied_bound->add_coefficient(sign);
503  implied_bound->add_var_index(new_bool_var_index);
504  implied_bound->add_coefficient(sat_lb - hs_value - 1.0);
505  }
506  }
507  }
508  }
509  }
510 }
511 
512 std::vector<Literal> HittingSetOptimizer::BuildAssumptions(
513  IntegerValue stratified_threshold,
514  IntegerValue* next_stratified_threshold) {
515  std::vector<Literal> assumptions;
516  // This code assumes that the variables from the objective are extracted
517  // first, and in the order of the objective definition.
518  for (int i = 0; i < normalized_objective_variables_.size(); ++i) {
519  const IntegerVariable var = normalized_objective_variables_[i];
520  const IntegerValue coeff = normalized_objective_coefficients_[i];
521 
522  // Correct the sign of the value queried from the MP solution.
523  // Note that normalized_objective_variables_[i] is mapped onto
524  // hs_model.variable[i] * sign.
525  const IntegerValue hs_value(
526  static_cast<int64_t>(std::round(response_.variable_value(i))) *
527  (VariableIsPositive(var) ? 1 : -1));
528 
529  // Non binding, ignoring.
530  if (hs_value == integer_trail_->UpperBound(var)) continue;
531 
532  // Only consider the terms above the threshold.
533  if (coeff < stratified_threshold) {
534  *next_stratified_threshold = std::max(*next_stratified_threshold, coeff);
535  } else {
536  // It is possible that different variables have the same associated
537  // literal. So we do need to consider this case.
538  assumptions.push_back(integer_encoder_->GetOrCreateAssociatedLiteral(
539  IntegerLiteral::LowerOrEqual(var, hs_value)));
540  assumption_to_indices_[assumptions.back().Index()].push_back(i);
541  }
542  }
543  return assumptions;
544 }
545 
546 // This is the "generalized" hitting set problem we will solve. Each time
547 // we find a core, a new constraint will be added to this problem.
548 //
549 // TODO(user): remove code duplication with MinimizeWithCoreAndLazyEncoding();
551 #if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
552  if (!ComputeInitialMpModel()) return SatSolver::INFEASIBLE;
553 
554  // This is used by the "stratified" approach. We will only consider terms with
555  // a weight not lower than this threshold. The threshold will decrease as the
556  // algorithm progress.
557  IntegerValue stratified_threshold = kMaxIntegerValue;
558 
559  // Start the algorithm.
560  SatSolver::Status result;
561  for (int iter = 0;; ++iter) {
562  // TODO(user): Even though we keep the same solver, currently the solve is
563  // not really done incrementally. It might be hard to improve though.
564  //
565  // TODO(user): deal with time limit.
566 
567  // Get the best external bound and constraint the objective of the MPModel.
568  if (shared_response_ != nullptr) {
569  const IntegerValue best_lower_bound =
570  shared_response_->GetInnerObjectiveLowerBound();
571  obj_constraint_->set_lower_bound(ToDouble(best_lower_bound));
572  }
573 
574  if (!ImportFromOtherWorkers()) return SatSolver::INFEASIBLE;
575  TightenMpModel();
576 
577  // TODO(user): C^c is broken when using SCIP.
578  MPSolver::SolveWithProto(request_, &response_);
579  if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
580  // We currently abort if we have a non-optimal result.
581  // This is correct if we had a limit reached, but not in the other
582  // cases.
583  //
584  // TODO(user): It is actually easy to use a FEASIBLE result. If when
585  // passing it to SAT it is no feasbile, we can still create cores. If it
586  // is feasible, we have a solution, but we cannot increase the lower
587  // bound.
589  }
590  if (response_.status() != MPSolverResponseStatus::MPSOLVER_OPTIMAL) {
591  continue;
592  }
593 
594  const IntegerValue mip_objective(
595  static_cast<int64_t>(std::round(response_.objective_value())));
596  VLOG(2) << "--" << iter
597  << "-- constraints:" << request_.mutable_model()->constraint_size()
598  << " variables:" << request_.mutable_model()->variable_size()
599  << " hs_lower_bound:"
600  << objective_definition_.ScaleIntegerObjective(mip_objective)
601  << " strat:" << stratified_threshold;
602 
603  // Update the objective lower bound with our current bound.
604  //
605  // Note(user): This is not needed for correctness, but it might cause
606  // more propagation and is nice to have for reporting/logging purpose.
607  if (!integer_trail_->Enqueue(
608  IntegerLiteral::GreaterOrEqual(objective_definition_.objective_var,
609  mip_objective),
610  {}, {})) {
611  result = SatSolver::INFEASIBLE;
612  break;
613  }
614 
615  sat_solver_->Backtrack(0);
616  sat_solver_->SetAssumptionLevel(0);
617  assumption_to_indices_.clear();
618  IntegerValue next_stratified_threshold(0);
619  const std::vector<Literal> assumptions =
620  BuildAssumptions(stratified_threshold, &next_stratified_threshold);
621 
622  // No assumptions with the current stratified_threshold? use the new one.
623  if (assumptions.empty() && next_stratified_threshold > 0) {
624  CHECK_LT(next_stratified_threshold, stratified_threshold);
625  stratified_threshold = next_stratified_threshold;
626  --iter; // "false" iteration, the lower bound does not increase.
627  continue;
628  }
629 
630  // TODO(user): Use the real weights and exploit the extra cores.
631  // TODO(user): If we extract more than the objective variables, we could
632  // use the solution values from the MPModel as hints to the SAT model.
633  result = FindMultipleCoresForMaxHs(assumptions, &temp_cores_);
634  if (result == SatSolver::FEASIBLE) {
635  if (!ProcessSolution()) return SatSolver::INFEASIBLE;
636  if (parameters_.stop_after_first_solution()) {
638  }
639  if (temp_cores_.empty()) {
640  // If not all assumptions were taken, continue with a lower stratified
641  // bound. Otherwise we have an optimal solution.
642  stratified_threshold = next_stratified_threshold;
643  if (stratified_threshold == 0) break;
644  --iter; // "false" iteration, the lower bound does not increase.
645  continue;
646  }
647  } else if (result == SatSolver::LIMIT_REACHED) {
648  // Hack: we use a local limit internally that we restore at the end.
649  // However we still return LIMIT_REACHED in this case...
650  if (time_limit_->LimitReached()) break;
651  } else if (result != SatSolver::ASSUMPTIONS_UNSAT) {
652  break;
653  }
654 
655  sat_solver_->Backtrack(0);
656  sat_solver_->SetAssumptionLevel(0);
657  AddCoresToTheMpModel(temp_cores_);
658  }
659 
660  return result;
661 #else // !__PORTABLE_PLATFORM__ && USE_SCIP
662  LOG(FATAL) << "Not supported.";
663 #endif // !__PORTABLE_PLATFORM__ && USE_SCIP
664 }
665 
666 } // namespace sat
667 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void resize(size_type new_size)
size_type size() const
static void SolveWithProto(const MPModelRequest &model_request, MPSolutionResponse *response, std::atomic< bool > *interrupt=nullptr)
Solves the model encoded by a MPModelRequest protocol buffer and fills the solution encoded as a MPSo...
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
double GetDeterministicLimit() const
Queries the deterministic time limit.
Definition: time_limit.h:303
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:552
double GetElapsedDeterministicTime() const
Returns the elapsed deterministic time since the construction of this object.
Definition: time_limit.h:260
void ChangeDeterministicLimit(double new_limit)
Overwrites the deterministic time limit with the new value.
Definition: time_limit.h:296
HittingSetOptimizer(const CpModelProto &model_proto, const ObjectiveDefinition &objective_definition, const std::function< void()> &feasible_solution_observer, Model *model)
Definition: max_hs.cc:63
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:558
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
Definition: integer.cc:254
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1228
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1646
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1641
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition: sat/model.h:91
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
const SatParameters & parameters() const
Definition: sat_solver.cc:132
void SetAssumptionLevel(int assumption_level)
Definition: sat_solver.cc:1071
void Backtrack(int target_level)
Definition: sat_solver.cc:1004
std::vector< Literal > GetLastIncompatibleDecisions()
Definition: sat_solver.cc:1386
CpModelProto const * model_proto
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Span< const double > coefficients
GRBmodel * model
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
int index
ABSL_FLAG(int, max_hs_strategy, 0, "MaxHsStrategy: 0 extract only objective variable, 1 extract all variables " "colocated with objective variables, 2 extract all variables in the " "linearization")
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
std::function< int64_t(const Model &)> Value(IntegerVariable v)
Definition: integer.h:1795
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:149
void TryToLinearizeConstraint(const CpModelProto &model_proto, const ConstraintProto &ct, int linearization_level, Model *model, LinearRelaxation *relaxation, ActivityBoundHelper *activity_helper)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:145
double ToDouble(IntegerValue value)
Definition: integer.h:77
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1505
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
std::vector< std::vector< Literal > > at_most_ones
std::vector< LinearConstraint > linear_constraints
double ScaleIntegerObjective(IntegerValue value) const
#define VLOG(verboselevel)
Definition: vlog.h:39