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