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