Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
optimization.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <functional>
20#include <string>
21#include <utility>
22#include <vector>
23
24#include "absl/cleanup/cleanup.h"
25#include "absl/container/btree_map.h"
26#include "absl/container/btree_set.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/log/log.h"
30#include "absl/log/vlog_is_on.h"
31#include "absl/strings/str_cat.h"
32#include "absl/strings/str_format.h"
33#include "absl/types/span.h"
38#include "ortools/sat/clause.h"
40#include "ortools/sat/integer.h"
44#include "ortools/sat/model.h"
50#include "ortools/sat/util.h"
54
55namespace operations_research {
56namespace sat {
57
59 std::vector<Literal>* core) {
60 if (solver->ModelIsUnsat()) return;
61 absl::btree_set<LiteralIndex> moved_last;
62 std::vector<Literal> candidate(core->begin(), core->end());
63
64 if (!solver->ResetToLevelZero()) return;
65 while (!limit->LimitReached()) {
66 // We want each literal in candidate to appear last once in our propagation
67 // order. We want to do that while maximizing the reutilization of the
68 // current assignment prefix, that is minimizing the number of
69 // decision/progagation we need to perform.
70 const int target_level = MoveOneUnprocessedLiteralLast(
71 moved_last, solver->CurrentDecisionLevel(), &candidate);
72 if (target_level == -1) break;
73 if (!solver->BacktrackAndPropagateReimplications(target_level)) return;
74 while (!solver->ModelIsUnsat() && !limit->LimitReached() &&
75 solver->CurrentDecisionLevel() < candidate.size()) {
76 const Literal decision = candidate[solver->CurrentDecisionLevel()];
77 if (solver->Assignment().LiteralIsTrue(decision)) {
78 candidate.erase(candidate.begin() + solver->CurrentDecisionLevel());
79 continue;
80 } else if (solver->Assignment().LiteralIsFalse(decision)) {
81 // This is a "weird" API to get the subset of decisions that caused
82 // this literal to be false with reason analysis.
84 candidate = solver->GetLastIncompatibleDecisions();
85 break;
86 } else {
88 }
89 }
90 if (candidate.empty() || solver->ModelIsUnsat()) return;
91 moved_last.insert(candidate.back().Index());
92 }
93
94 if (!solver->ResetToLevelZero()) return;
95 if (candidate.size() < core->size()) {
96 VLOG(1) << "minimization with propag " << core->size() << " -> "
97 << candidate.size();
98
99 // We want to preserve the order of literal in the response.
100 absl::flat_hash_set<LiteralIndex> set;
101 for (const Literal l : candidate) set.insert(l.Index());
102 int new_size = 0;
103 for (const Literal l : *core) {
104 if (set.contains(l.Index())) {
105 (*core)[new_size++] = l;
106 }
107 }
108 core->resize(new_size);
109 }
110}
111
113 std::vector<Literal>* core) {
114 if (solver->ModelIsUnsat()) return;
115
116 // TODO(user): tune.
117 if (core->size() > 100 || core->size() == 1) return;
118
119 const bool old_log_state = solver->mutable_logger()->LoggingIsEnabled();
120 solver->mutable_logger()->EnableLogging(false);
121
122 const int old_size = core->size();
123 std::vector<Literal> assumptions;
124 absl::flat_hash_set<LiteralIndex> removed_once;
125
126 // We stop as soon as the core size is one since there is nothing more
127 // to minimize then.
128 while (core->size() > 1) {
129 if (limit->LimitReached()) break;
130
131 // Find a not yet removed literal to remove.
132 // We prefer to remove high indices since these are more likely to be of
133 // high depth.
134 //
135 // TODO(user): Properly use the node depth instead.
136 int to_remove = -1;
137 for (int i = core->size(); --i >= 0;) {
138 const Literal l = (*core)[i];
139 const auto [_, inserted] = removed_once.insert(l.Index());
140 if (inserted) {
141 to_remove = i;
142 break;
143 }
144 }
145 if (to_remove == -1) break;
146
147 assumptions.clear();
148 for (int i = 0; i < core->size(); ++i) {
149 if (i == to_remove) continue;
150 assumptions.push_back((*core)[i]);
151 }
152
153 const auto status = solver->ResetAndSolveWithGivenAssumptions(
154 assumptions, /*max_number_of_conflicts=*/1000);
155 if (status == SatSolver::ASSUMPTIONS_UNSAT) {
156 *core = solver->GetLastIncompatibleDecisions();
157 }
158 }
159
160 if (core->size() < old_size) {
161 VLOG(1) << "minimization with search " << old_size << " -> "
162 << core->size();
163 }
164
165 (void)solver->ResetToLevelZero();
166 solver->mutable_logger()->EnableLogging(old_log_state);
167}
168
169bool ProbeLiteral(Literal assumption, SatSolver* solver) {
170 if (solver->ModelIsUnsat()) return false;
171
172 const bool old_log_state = solver->mutable_logger()->LoggingIsEnabled();
173 solver->mutable_logger()->EnableLogging(false);
174
175 // Note that since we only care about Booleans here, even if we have a
176 // feasible solution, it might not be feasible for the full cp_model.
177 //
178 // TODO(user): Still use it if the problem is Boolean only.
179 const auto status = solver->ResetAndSolveWithGivenAssumptions(
180 {assumption}, /*max_number_of_conflicts=*/1'000);
181 if (!solver->ResetToLevelZero()) return false;
182 if (status == SatSolver::ASSUMPTIONS_UNSAT) {
183 if (!solver->AddUnitClause(assumption.Negated())) {
184 return false;
185 }
186 if (!solver->Propagate()) {
187 solver->NotifyThatModelIsUnsat();
188 return false;
189 }
190 }
191
192 solver->mutable_logger()->EnableLogging(old_log_state);
193 return true;
194}
195
196// A core cannot be all true.
198 std::vector<Literal>* core) {
199 int new_size = 0;
200 for (const Literal lit : *core) {
201 if (assignment.LiteralIsTrue(lit)) continue;
202 if (assignment.LiteralIsFalse(lit)) {
203 (*core)[0] = lit;
204 core->resize(1);
205 return;
206 }
207 (*core)[new_size++] = lit;
208 }
209 core->resize(new_size);
210}
211
213 IntegerVariable objective_var,
214 const std::function<void()>& feasible_solution_observer, Model* model) {
215 auto* sat_solver = model->GetOrCreate<SatSolver>();
216 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
217 auto* search = model->GetOrCreate<IntegerSearchHelper>();
218 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
219
220 // Simple linear scan algorithm to find the optimal.
221 if (!sat_solver->ResetToLevelZero()) return SatSolver::INFEASIBLE;
222 while (true) {
223 if (DEBUG_MODE) {
224 // The solver usually always solve a "restricted decision problem"
225 // obj < current_best. So when we have an optimal solution, then the
226 // problem is UNSAT, and any clauses we learn can break the debug
227 // solution. So we disable this checks once we found an optimal solution.
228 const DebugSolution* debug_sol = model->Get<DebugSolution>();
229 if (debug_sol && integer_trail->LowerBound(objective_var) <=
230 debug_sol->inner_objective_value) {
231 model->GetOrCreate<DebugSolution>()->Clear();
232 }
233 }
234
235 const SatSolver::Status result = search->SolveIntegerProblem();
236 if (result != SatSolver::FEASIBLE) return result;
237
238 // The objective is the current lower bound of the objective_var.
239 const IntegerValue objective = integer_trail->LowerBound(objective_var);
240
241 // We have a solution!
242 if (feasible_solution_observer != nullptr) {
243 feasible_solution_observer();
244 }
245 if (parameters.stop_after_first_solution()) {
247 }
248
249 // Restrict the objective.
250 sat_solver->Backtrack(0);
251 if (!integer_trail->Enqueue(
252 IntegerLiteral::LowerOrEqual(objective_var, objective - 1), {},
253 {})) {
255 }
256 }
257}
258
260 IntegerVariable objective_var,
261 const std::function<void()>& feasible_solution_observer, Model* model) {
262 const SatParameters old_params = *model->GetOrCreate<SatParameters>();
263 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
264 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
265 IntegerEncoder* integer_encoder = model->GetOrCreate<IntegerEncoder>();
266
267 // Set the requested conflict limit.
268 {
269 SatParameters new_params = old_params;
271 old_params.binary_search_num_conflicts());
272 *model->GetOrCreate<SatParameters>() = new_params;
273 }
274
275 // The assumption (objective <= value) for values in
276 // [unknown_min, unknown_max] reached the conflict limit.
277 bool loop = true;
278 IntegerValue unknown_min = integer_trail->UpperBound(objective_var);
279 IntegerValue unknown_max = integer_trail->LowerBound(objective_var);
280 while (loop) {
281 sat_solver->Backtrack(0);
282 const IntegerValue lb = integer_trail->LowerBound(objective_var);
283 const IntegerValue ub = integer_trail->UpperBound(objective_var);
284 unknown_min = std::min(unknown_min, ub);
285 unknown_max = std::max(unknown_max, lb);
286
287 // We first refine the lower bound and then the upper bound.
288 IntegerValue target;
289 if (lb < unknown_min) {
290 target = lb + (unknown_min - lb) / 2;
291 } else if (unknown_max < ub) {
292 target = ub - (ub - unknown_max) / 2;
293 } else {
294 VLOG(1) << "Binary-search, done.";
295 break;
296 }
297 VLOG(1) << "Binary-search, objective: [" << lb << "," << ub << "]"
298 << " tried: [" << unknown_min << "," << unknown_max << "]"
299 << " target: obj<=" << target;
300 SatSolver::Status result;
301 if (target < ub) {
302 const Literal assumption = integer_encoder->GetOrCreateAssociatedLiteral(
303 IntegerLiteral::LowerOrEqual(objective_var, target));
304 result = ResetAndSolveIntegerProblem({assumption}, model);
305 } else {
306 result = ResetAndSolveIntegerProblem({}, model);
307 }
308
309 switch (result) {
311 loop = false;
312 break;
313 }
315 // Update the objective lower bound.
316 sat_solver->Backtrack(0);
317 if (!integer_trail->Enqueue(
318 IntegerLiteral::GreaterOrEqual(objective_var, target + 1), {},
319 {})) {
320 loop = false;
321 }
322 break;
323 }
324 case SatSolver::FEASIBLE: {
325 // The objective is the current lower bound of the objective_var.
326 const IntegerValue objective = integer_trail->LowerBound(objective_var);
327 if (feasible_solution_observer != nullptr) {
328 feasible_solution_observer();
329 }
330
331 // We have a solution, restrict the objective upper bound to only look
332 // for better ones now.
333 sat_solver->Backtrack(0);
334 if (!integer_trail->Enqueue(
335 IntegerLiteral::LowerOrEqual(objective_var, objective - 1), {},
336 {})) {
337 loop = false;
338 }
339 break;
340 }
342 unknown_min = std::min(target, unknown_min);
343 unknown_max = std::max(target, unknown_max);
344 break;
345 }
346 }
347 }
348
349 sat_solver->Backtrack(0);
350 *model->GetOrCreate<SatParameters>() = old_params;
351}
352
353namespace {
354
355// If the given model is unsat under the given assumptions, returns one or more
356// non-overlapping set of assumptions, each set making the problem infeasible on
357// its own (the cores).
358//
359// In presence of weights, we "generalize" the notions of disjoints core using
360// the WCE idea describe in "Weight-Aware Core Extraction in SAT-Based MaxSAT
361// solving" Jeremias Berg And Matti Jarvisalo.
362//
363// The returned status can be either:
364// - ASSUMPTIONS_UNSAT if the set of returned core perfectly cover the given
365// assumptions, in this case, we don't bother trying to find a SAT solution
366// with no assumptions.
367// - FEASIBLE if after finding zero or more core we have a solution.
368// - LIMIT_REACHED if we reached the time-limit before one of the two status
369// above could be decided.
370//
371// TODO(user): There is many way to combine the WCE and stratification
372// heuristics. I didn't had time to properly compare the different approach. See
373// the WCE papers for some ideas, but there is many more ways to try to find a
374// lot of core at once and try to minimize the minimum weight of each of the
375// cores.
376SatSolver::Status FindCores(std::vector<Literal> assumptions,
377 std::vector<IntegerValue> assumption_weights,
378 IntegerValue stratified_threshold, Model* model,
379 std::vector<std::vector<Literal>>* cores) {
380 cores->clear();
381 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
382 TimeLimit* limit = model->GetOrCreate<TimeLimit>();
383 do {
384 if (limit->LimitReached()) return SatSolver::LIMIT_REACHED;
385
386 const SatSolver::Status result =
387 ResetAndSolveIntegerProblem(assumptions, model);
388 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
389 std::vector<Literal> core = sat_solver->GetLastIncompatibleDecisions();
390 if (sat_solver->parameters().core_minimization_level() > 0) {
391 MinimizeCoreWithPropagation(limit, sat_solver, &core);
392 }
393 if (core.size() == 1) {
394 if (!sat_solver->AddUnitClause(core[0].Negated())) {
396 }
397 }
398 if (core.empty()) return sat_solver->UnsatStatus();
399 cores->push_back(core);
400 if (!sat_solver->parameters().find_multiple_cores()) break;
401
402 // Recover the original indices of the assumptions that are part of the
403 // core.
404 std::vector<int> indices;
405 {
406 absl::btree_set<Literal> temp(core.begin(), core.end());
407 for (int i = 0; i < assumptions.size(); ++i) {
408 if (temp.contains(assumptions[i])) {
409 indices.push_back(i);
410 }
411 }
412 }
413
414 // Remove min_weight from the weights of all the assumptions in the core.
415 //
416 // TODO(user): push right away the objective bound by that much? This should
417 // be better in a multi-threading context as we can share more quickly the
418 // better bound.
419 IntegerValue min_weight = assumption_weights[indices.front()];
420 for (const int i : indices) {
421 min_weight = std::min(min_weight, assumption_weights[i]);
422 }
423 for (const int i : indices) {
424 assumption_weights[i] -= min_weight;
425 }
426
427 // Remove from assumptions all the one with a new weight smaller than the
428 // current stratification threshold and see if we can find another core.
429 int new_size = 0;
430 for (int i = 0; i < assumptions.size(); ++i) {
431 if (assumption_weights[i] < stratified_threshold) continue;
432 assumptions[new_size] = assumptions[i];
433 assumption_weights[new_size] = assumption_weights[i];
434 ++new_size;
435 }
436 assumptions.resize(new_size);
437 assumption_weights.resize(new_size);
438 } while (!assumptions.empty());
440}
441
442} // namespace
443
445 IntegerVariable objective_var, absl::Span<const IntegerVariable> variables,
446 absl::Span<const IntegerValue> coefficients,
447 std::function<void()> feasible_solution_observer, Model* model)
448 : parameters_(model->GetOrCreate<SatParameters>()),
449 sat_solver_(model->GetOrCreate<SatSolver>()),
450 clauses_(model->GetOrCreate<ClauseManager>()),
451 time_limit_(model->GetOrCreate<TimeLimit>()),
452 implications_(model->GetOrCreate<BinaryImplicationGraph>()),
453 integer_trail_(model->GetOrCreate<IntegerTrail>()),
454 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
455 model_(model),
456 objective_var_(objective_var),
457 feasible_solution_observer_(std::move(feasible_solution_observer)) {
458 CHECK_EQ(variables.size(), coefficients.size());
459 for (int i = 0; i < variables.size(); ++i) {
460 if (coefficients[i] > 0) {
461 terms_.push_back({variables[i], coefficients[i]});
462 } else if (coefficients[i] < 0) {
463 terms_.push_back({NegationOf(variables[i]), -coefficients[i]});
464 } else {
465 continue; // coefficients[i] == 0
466 }
467 terms_.back().depth = 0;
468 }
469
470 // This is used by the "stratified" approach. We will only consider terms with
471 // a weight not lower than this threshold. The threshold will decrease as the
472 // algorithm progress.
473 stratification_threshold_ = parameters_->max_sat_stratification() ==
475 ? IntegerValue(1)
477}
478
479bool CoreBasedOptimizer::ProcessSolution() {
480 // We don't assume that objective_var is linked with its linear term, so
481 // we recompute the objective here.
482 IntegerValue objective(0);
483 for (ObjectiveTerm& term : terms_) {
484 const IntegerValue value = integer_trail_->LowerBound(term.var);
485 objective += term.weight * value;
486
487 // Also keep in term.cover_ub the minimum value for term.var that we have
488 // seens amongst all the feasible solutions found so far.
489 term.cover_ub = std::min(term.cover_ub, value);
490 }
491
492 // Test that the current objective value fall in the requested objective
493 // domain, which could potentially have holes.
494 if (!integer_trail_->InitialVariableDomain(objective_var_)
495 .Contains(objective.value())) {
496 return true;
497 }
498
499 if (feasible_solution_observer_ != nullptr) {
500 feasible_solution_observer_();
501 }
502 if (parameters_->stop_after_first_solution()) {
503 stop_ = true;
504 }
505
506 // Constrain objective_var. This has a better result when objective_var is
507 // used in an LP relaxation for instance.
508 sat_solver_->Backtrack(0);
509 sat_solver_->SetAssumptionLevel(0);
510 return integer_trail_->Enqueue(
511 IntegerLiteral::LowerOrEqual(objective_var_, objective - 1), {}, {});
512}
513
514bool CoreBasedOptimizer::PropagateObjectiveBounds() {
515 // We assumes all terms (modulo stratification) at their lower-bound.
516 bool some_bound_were_tightened = true;
517 while (some_bound_were_tightened) {
518 some_bound_were_tightened = false;
519 if (!sat_solver_->ResetToLevelZero()) return false;
520 if (time_limit_->LimitReached()) return true;
521
522 // Compute implied lb.
523 IntegerValue implied_objective_lb(0);
524 for (ObjectiveTerm& term : terms_) {
525 const IntegerValue var_lb = integer_trail_->LowerBound(term.var);
526 term.old_var_lb = var_lb;
527 implied_objective_lb += term.weight * var_lb.value();
528 }
529
530 // Update the objective lower bound with our current bound.
531 if (implied_objective_lb > integer_trail_->LowerBound(objective_var_)) {
532 if (!integer_trail_->Enqueue(IntegerLiteral::GreaterOrEqual(
533 objective_var_, implied_objective_lb),
534 {}, {})) {
535 return false;
536 }
537
538 some_bound_were_tightened = true;
539 }
540
541 // The gap is used to propagate the upper-bound of all variable that are
542 // in the current objective (Exactly like done in the propagation of a
543 // linear constraint with the slack). When this fix a variable to its
544 // lower bound, it is called "hardening" in the max-sat literature. This
545 // has a really beneficial effect on some weighted max-sat problems like
546 // the haplotyping-pedigrees ones.
547 const IntegerValue gap =
548 integer_trail_->UpperBound(objective_var_) - implied_objective_lb;
549
550 for (const ObjectiveTerm& term : terms_) {
551 if (term.weight == 0) continue;
552 const IntegerValue var_lb = integer_trail_->LowerBound(term.var);
553 const IntegerValue var_ub = integer_trail_->UpperBound(term.var);
554 if (var_lb == var_ub) continue;
555
556 // Hardening. This basically just propagate the implied upper bound on
557 // term.var from the current best solution. Note that the gap is
558 // non-negative and the weight positive here. The test is done in order
559 // to avoid any integer overflow provided (ub - lb) do not overflow, but
560 // this is a precondition in our cp-model.
561 if (gap / term.weight < var_ub - var_lb) {
562 some_bound_were_tightened = true;
563 const IntegerValue new_ub = var_lb + gap / term.weight;
564 DCHECK_LT(new_ub, var_ub);
565 if (!integer_trail_->Enqueue(
566 IntegerLiteral::LowerOrEqual(term.var, new_ub), {}, {})) {
567 return false;
568 }
569 }
570 }
571 }
572 return true;
573}
574
575// A basic algorithm is to take the next one, or at least the next one
576// that invalidate the current solution. But to avoid corner cases for
577// problem with a lot of terms all with different objective weights (in
578// which case we will kind of introduce only one assumption per loop
579// which is little), we use an heuristic and take the 90% percentile of
580// the unique weights not yet included.
581//
582// TODO(user): There is many other possible heuristics here, and I
583// didn't have the time to properly compare them.
584void CoreBasedOptimizer::ComputeNextStratificationThreshold() {
585 std::vector<IntegerValue> weights;
586 for (ObjectiveTerm& term : terms_) {
587 if (term.weight >= stratification_threshold_) continue;
588 if (term.weight == 0) continue;
589
590 const IntegerValue var_lb = integer_trail_->LevelZeroLowerBound(term.var);
591 const IntegerValue var_ub = integer_trail_->LevelZeroUpperBound(term.var);
592 if (var_lb == var_ub) continue;
593
594 weights.push_back(term.weight);
595 }
596 if (weights.empty()) {
597 stratification_threshold_ = IntegerValue(0);
598 return;
599 }
600
602 stratification_threshold_ =
603 weights[static_cast<int>(std::floor(0.9 * weights.size()))];
604}
605
606bool CoreBasedOptimizer::CoverOptimization() {
607 if (!sat_solver_->ResetToLevelZero()) return false;
608
609 // We set a fix deterministic time limit per all sub-solves and skip to the
610 // next core if the sum of the sub-solves is also over this limit.
611 constexpr double max_dtime_per_core = 0.5;
612 const double old_time_limit = parameters_->max_deterministic_time();
613 parameters_->set_max_deterministic_time(max_dtime_per_core);
614 auto cleanup = ::absl::MakeCleanup([old_time_limit, this]() {
615 parameters_->set_max_deterministic_time(old_time_limit);
616 });
617
618 for (const ObjectiveTerm& term : terms_) {
619 // We currently skip the initial objective terms as there could be many
620 // of them. TODO(user): provide an option to cover-optimize them? I
621 // fear that this will slow down the solver too much though.
622 if (term.depth == 0) continue;
623
624 // Find out the true lower bound of var. This is called "cover
625 // optimization" in some of the max-SAT literature. It can helps on some
626 // problem families and hurt on others, but the overall impact is
627 // positive.
628 const IntegerVariable var = term.var;
629 IntegerValue best =
630 std::min(term.cover_ub, integer_trail_->UpperBound(var));
631
632 // Note(user): this can happen in some corner case because each time we
633 // find a solution, we constrain the objective to be smaller than it, so
634 // it is possible that a previous best is now infeasible.
635 if (best <= integer_trail_->LowerBound(var)) continue;
636
637 // Compute the global deterministic time for this core cover
638 // optimization.
639 const double deterministic_limit =
640 time_limit_->GetElapsedDeterministicTime() + max_dtime_per_core;
641
642 // Simple linear scan algorithm to find the optimal of var.
643 SatSolver::Status result;
644 while (best > integer_trail_->LowerBound(var)) {
645 const Literal assumption = integer_encoder_->GetOrCreateAssociatedLiteral(
646 IntegerLiteral::LowerOrEqual(var, best - 1));
647 result = ResetAndSolveIntegerProblem({assumption}, model_);
648 if (result != SatSolver::FEASIBLE) break;
649
650 best = integer_trail_->LowerBound(var);
651 VLOG(1) << "cover_opt var:" << var << " domain:["
652 << integer_trail_->LevelZeroLowerBound(var) << "," << best << "]";
653 if (!ProcessSolution()) return false;
654 if (!sat_solver_->ResetToLevelZero()) return false;
655 if (stop_ ||
656 time_limit_->GetElapsedDeterministicTime() > deterministic_limit) {
657 break;
658 }
659 }
660 if (result == SatSolver::INFEASIBLE) return false;
661 if (result == SatSolver::ASSUMPTIONS_UNSAT) {
662 if (!sat_solver_->ResetToLevelZero()) return false;
663
664 // TODO(user): If we improve the lower bound of var, we should check
665 // if our global lower bound reached our current best solution in
666 // order to abort early if the optimal is proved.
667 if (!integer_trail_->Enqueue(IntegerLiteral::GreaterOrEqual(var, best),
668 {}, {})) {
669 return false;
670 }
671 }
672 }
673
674 return PropagateObjectiveBounds();
675}
676
678 absl::Span<const Literal> literals, absl::Span<const IntegerVariable> vars,
679 absl::Span<const Coefficient> coefficients, Coefficient offset) {
680 // Create one initial nodes per variables with cost.
681 // TODO(user): We could create EncodingNode out of IntegerVariable.
682 //
683 // Note that the nodes order and assumptions extracted from it will be stable.
684 // In particular, new nodes will be appended at the end, which make the solver
685 // more likely to find core involving only the first assumptions. This is
686 // important at the beginning so the solver as a chance to find a lot of
687 // non-overlapping small cores without the need to have dedicated
688 // non-overlapping core finder.
689 // TODO(user): It could still be beneficial to add one. Experiments.
690 ObjectiveEncoder encoder(model_);
691 if (vars.empty()) {
692 // All Booleans.
693 for (int i = 0; i < literals.size(); ++i) {
694 CHECK_GT(coefficients[i], 0);
695 encoder.AddBaseNode(
696 EncodingNode::LiteralNode(literals[i], coefficients[i]));
697 }
698 } else {
699 // Use integer encoding.
700 CHECK_EQ(vars.size(), coefficients.size());
701 for (int i = 0; i < vars.size(); ++i) {
702 CHECK_GT(coefficients[i], 0);
703 const IntegerVariable var = vars[i];
704 const IntegerValue var_lb = integer_trail_->LowerBound(var);
705 const IntegerValue var_ub = integer_trail_->UpperBound(var);
706 if (var_ub - var_lb == 1) {
707 const Literal lit = integer_encoder_->GetOrCreateAssociatedLiteral(
708 IntegerLiteral::GreaterOrEqual(var, var_ub));
709 encoder.AddBaseNode(EncodingNode::LiteralNode(lit, coefficients[i]));
710 } else {
711 // TODO(user): This might not be ideal if there are holes in the domain.
712 // It should work by adding duplicates literal, but we should be able to
713 // be more efficient.
714 const int lb = 0;
715 const int ub = static_cast<int>(var_ub.value() - var_lb.value());
717 lb, ub,
718 [var, var_lb, this](int x) {
719 return integer_encoder_->GetOrCreateAssociatedLiteral(
721 var_lb + IntegerValue(x + 1)));
722 },
723 coefficients[i]));
724 }
725 }
726 }
727
728 // Initialize the bounds.
729 // This is in term of number of variables not at their minimal value.
730 Coefficient lower_bound(0);
731
732 // This is used by the "stratified" approach.
733 Coefficient stratified_lower_bound(0);
734 if (parameters_->max_sat_stratification() !=
736 for (EncodingNode* n : encoder.nodes()) {
737 stratified_lower_bound = std::max(stratified_lower_bound, n->weight());
738 }
739 }
740
741 // Start the algorithm.
742 int max_depth = 0;
743 std::string previous_core_info = "";
744 for (int iter = 0;;) {
745 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
746 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
747
748 // Note that the objective_var_ upper bound is the one from the "improving"
749 // problem, so if we have a feasible solution, it will be the best solution
750 // objective value - 1.
751 const Coefficient upper_bound(
752 integer_trail_->UpperBound(objective_var_).value() - offset.value());
753 ReduceNodes(upper_bound, &lower_bound, encoder.mutable_nodes(),
754 sat_solver_);
755 const IntegerValue new_obj_lb(lower_bound.value() + offset.value());
756 if (new_obj_lb > integer_trail_->LowerBound(objective_var_)) {
757 if (!integer_trail_->Enqueue(
758 IntegerLiteral::GreaterOrEqual(objective_var_, new_obj_lb), {},
759 {})) {
761 }
762
763 // Report the improvement.
764 // Note that we have a callback that will do the same, but doing it
765 // earlier allow us to add more information.
766 const int num_bools = sat_solver_->NumVariables();
767 const int num_fixed = sat_solver_->NumFixedVariables();
768 model_->GetOrCreate<SharedResponseManager>()->UpdateInnerObjectiveBounds(
769 absl::StrFormat("bool_%s (num_cores=%d [%s] a=%u d=%d "
770 "fixed=%d/%d clauses=%s)",
771 model_->Name(), iter, previous_core_info,
772 encoder.nodes().size(), max_depth, num_fixed,
773 num_bools, FormatCounter(clauses_->num_clauses())),
774 new_obj_lb, integer_trail_->LevelZeroUpperBound(objective_var_));
775 }
776
777 if (parameters_->cover_optimization() && encoder.nodes().size() > 1) {
778 const Literal last_assumption =
779 encoder.mutable_nodes()->back()->GetAssumption(sat_solver_);
780 if (!ProbeLiteral(last_assumption, sat_solver_)) {
782 }
783 if (sat_solver_->Assignment().LiteralIsAssigned(last_assumption)) {
784 previous_core_info = "cover";
785 continue;
786 }
787 }
788
789 // We adapt the stratified lower bound when the gap is small. All literals
790 // with such weight will be in an at_most_one relationship, which will lead
791 // to a nice encoding if we find a core.
792 const Coefficient gap = upper_bound - lower_bound;
793 if (stratified_lower_bound > (gap + 2) / 2) {
794 stratified_lower_bound = (gap + 2) / 2;
795 }
796 std::vector<Literal> assumptions;
797 while (true) {
798 assumptions = ExtractAssumptions(stratified_lower_bound, encoder.nodes(),
799 sat_solver_);
800 if (!assumptions.empty()) break;
801
802 stratified_lower_bound =
803 MaxNodeWeightSmallerThan(encoder.nodes(), stratified_lower_bound);
804 if (stratified_lower_bound > 0) continue;
805
806 // We do not have any assumptions anymore, but we still need to see
807 // if the problem is feasible or not!
808 break;
809 }
810 VLOG(2) << "[Core] #nodes " << encoder.nodes().size()
811 << " #assumptions:" << assumptions.size()
812 << " stratification:" << stratified_lower_bound << " gap:" << gap;
813
814 // Solve under the assumptions.
815 //
816 // TODO(user): Find multiple core like in the "main" algorithm. This is just
817 // trying to solve with assumptions not involving the newly found core.
818 //
819 // TODO(user): With stratification, sometime we just spend too much time
820 // trying to find a feasible solution/prove infeasibility and we could
821 // instead just use stratification=0 to find easty core and improve lower
822 // bound.
823 const SatSolver::Status result =
824 ResetAndSolveIntegerProblem(assumptions, model_);
825 if (result == SatSolver::FEASIBLE) {
826 if (!ProcessSolution()) return SatSolver::INFEASIBLE;
827 if (stop_) return SatSolver::LIMIT_REACHED;
828
829 // If not all assumptions were taken, continue with a lower stratified
830 // bound. Otherwise we have an optimal solution.
831 stratified_lower_bound =
832 MaxNodeWeightSmallerThan(encoder.nodes(), stratified_lower_bound);
833 if (stratified_lower_bound > 0) continue;
835 }
836 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
837
838 // We have a new core.
839 std::vector<Literal> core = sat_solver_->GetLastIncompatibleDecisions();
840 if (parameters_->core_minimization_level() > 0) {
841 MinimizeCoreWithPropagation(time_limit_, sat_solver_, &core);
842 }
843 if (parameters_->core_minimization_level() > 1) {
844 MinimizeCoreWithSearch(time_limit_, sat_solver_, &core);
845 }
846 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
847 FilterAssignedLiteral(sat_solver_->Assignment(), &core);
848 if (core.empty()) return SatSolver::INFEASIBLE;
849
850 // Compute the min weight of all the nodes in the core.
851 // The lower bound will be increased by that much.
852 const Coefficient min_weight = ComputeCoreMinWeight(encoder.nodes(), core);
853 previous_core_info =
854 absl::StrFormat("size:%u mw:%d", core.size(), min_weight.value());
855
856 // We only count an iter when we found a core.
857 ++iter;
858 if (!encoder.ProcessCore(core, min_weight, gap, &previous_core_info)) {
860 }
861 max_depth = std::max(max_depth, encoder.nodes().back()->depth());
862 }
863
864 return SatSolver::FEASIBLE; // shouldn't reach here.
865}
866
867void PresolveBooleanLinearExpression(std::vector<Literal>* literals,
868 std::vector<Coefficient>* coefficients,
869 Coefficient* offset) {
870 // Sorting by literal index regroup duplicate or negated literal together.
871 std::vector<std::pair<LiteralIndex, Coefficient>> pairs;
872 const int size = literals->size();
873 for (int i = 0; i < size; ++i) {
874 pairs.push_back({(*literals)[i].Index(), (*coefficients)[i]});
875 }
876 std::sort(pairs.begin(), pairs.end());
877
878 // Merge terms if needed.
879 int new_size = 0;
880 for (const auto& [index, coeff] : pairs) {
881 if (new_size > 0) {
882 if (pairs[new_size - 1].first == index) {
883 pairs[new_size - 1].second += coeff;
884 continue;
885 } else if (pairs[new_size - 1].first == Literal(index).NegatedIndex()) {
886 // The term is coeff *( 1 - X).
887 pairs[new_size - 1].second -= coeff;
888 *offset += coeff;
889 continue;
890 }
891 }
892 pairs[new_size++] = {index, coeff};
893 }
894 pairs.resize(new_size);
895
896 // Rebuild with positive coeff.
897 literals->clear();
898 coefficients->clear();
899 for (const auto& [index, coeff] : pairs) {
900 if (coeff > 0) {
901 literals->push_back(Literal(index));
902 coefficients->push_back(coeff);
903 } else if (coeff < 0) {
904 // coeff * X = coeff - coeff * (1 - X).
905 *offset += coeff;
906 literals->push_back(Literal(index).Negated());
907 coefficients->push_back(-coeff);
908 }
909 }
910}
911
912void CoreBasedOptimizer::PresolveObjectiveWithAtMostOne(
913 std::vector<Literal>* literals, std::vector<Coefficient>* coefficients,
914 Coefficient* offset) {
915 // This contains non-negative value. If a literal has negative weight, then
916 // we just put a positive weight on its negation and update the offset.
917 const int num_literals = implications_->literal_size();
919 util_intops::StrongVector<LiteralIndex, bool> is_candidate(num_literals);
920
921 // For now, we do not use weight. Note that finding the at most on in the
922 // creation order of the variable make a HUGE difference on the max-sat frb
923 // family.
924 //
925 // TODO(user): We can assign preferences to literals to favor certain at most
926 // one instead of other. For now we don't, so ExpandAtMostOneWithWeight() will
927 // kind of randomize the expansion amongst possible choices.
929
930 // Collect all literals with "negative weights", we will try to find at most
931 // one between them.
932 std::vector<Literal> candidates;
933 const int num_terms = literals->size();
934 for (int i = 0; i < num_terms; ++i) {
935 const Literal lit = (*literals)[i];
936 const Coefficient coeff = (*coefficients)[i];
937
938 // For now we know the input only has positive weight, but it is easy to
939 // adapt if needed.
940 CHECK_GT(coeff, 0);
941 weights[lit] = coeff;
942
943 candidates.push_back(lit.Negated());
944 is_candidate[lit.NegatedIndex()] = true;
945 }
946
947 int num_at_most_ones = 0;
948 Coefficient overall_lb_increase(0);
949
950 std::vector<Literal> at_most_one;
951 std::vector<std::pair<Literal, Coefficient>> new_obj_terms;
952 implications_->ResetWorkDone();
953 for (const Literal root : candidates) {
954 if (weights[root.NegatedIndex()] == 0) continue;
955 if (implications_->WorkDone() > 1e8) continue;
956
957 // We never put weight on both a literal and its negation.
958 CHECK_EQ(weights[root], 0);
959
960 // Note that for this to be as exhaustive as possible, the probing needs
961 // to have added binary clauses corresponding to lvl0 propagation.
962 at_most_one =
963 implications_->ExpandAtMostOneWithWeight</*use_weight=*/false>(
964 {root}, is_candidate, preferences);
965 if (at_most_one.size() <= 1) continue;
966 ++num_at_most_ones;
967
968 // Change the objective weights. Note that all the literal in the at most
969 // one will not be processed again since the weight of their negation will
970 // be zero after this step.
971 Coefficient max_coeff(0);
972 Coefficient lb_increase(0);
973 for (const Literal lit : at_most_one) {
974 const Coefficient coeff = weights[lit.NegatedIndex()];
975 lb_increase += coeff;
976 max_coeff = std::max(max_coeff, coeff);
977 }
978 lb_increase -= max_coeff;
979
980 *offset += lb_increase;
981 overall_lb_increase += lb_increase;
982
983 for (const Literal lit : at_most_one) {
984 is_candidate[lit] = false;
985 const Coefficient new_weight = max_coeff - weights[lit.NegatedIndex()];
986 CHECK_EQ(weights[lit], 0);
987 weights[lit] = new_weight;
988 weights[lit.NegatedIndex()] = 0;
989 if (new_weight > 0) {
990 // TODO(user): While we autorize this to be in future at most one, it
991 // will not appear in the "literal" list. We might also want to continue
992 // until we reached the fix point.
993 is_candidate[lit.NegatedIndex()] = true;
994 }
995 }
996
997 // Create a new Boolean with weight max_coeff.
998 const Literal new_lit(sat_solver_->NewBooleanVariable(), true);
999 new_obj_terms.push_back({new_lit, max_coeff});
1000
1001 // The new boolean is true only if all the one in the at most one are false.
1002 at_most_one.push_back(new_lit);
1003 sat_solver_->AddProblemClause(at_most_one);
1004 is_candidate.resize(implications_->literal_size(), false);
1005 preferences.resize(implications_->literal_size(), 1.0);
1006 }
1007
1008 if (overall_lb_increase > 0) {
1009 // Report new bounds right away with extra information.
1010 model_->GetOrCreate<SharedResponseManager>()->UpdateInnerObjectiveBounds(
1011 absl::StrFormat("am1_presolve (num_literals=%d num_am1=%d "
1012 "increase=%lld work_done=%lld)",
1013 (int)candidates.size(), num_at_most_ones,
1014 overall_lb_increase.value(), implications_->WorkDone()),
1015 IntegerValue(offset->value()),
1016 integer_trail_->LevelZeroUpperBound(objective_var_));
1017 }
1018
1019 // Reconstruct the objective.
1020 literals->clear();
1021 coefficients->clear();
1022 for (const Literal root : candidates) {
1023 if (weights[root] > 0) {
1024 CHECK_EQ(weights[root.NegatedIndex()], 0);
1025 literals->push_back(root);
1026 coefficients->push_back(weights[root]);
1027 }
1028 if (weights[root.NegatedIndex()] > 0) {
1029 CHECK_EQ(weights[root], 0);
1030 literals->push_back(root.Negated());
1031 coefficients->push_back(weights[root.NegatedIndex()]);
1032 }
1033 }
1034 for (const auto& [lit, coeff] : new_obj_terms) {
1035 literals->push_back(lit);
1036 coefficients->push_back(coeff);
1037 }
1038}
1039
1041 // Hack: If the objective is fully Boolean, we use the
1042 // OptimizeWithSatEncoding() version as it seems to be better.
1043 //
1044 // TODO(user): Try to understand exactly why and merge both code path.
1045 if (!parameters_->interleave_search()) {
1046 Coefficient offset(0);
1047 std::vector<Literal> literals;
1048 std::vector<IntegerVariable> vars;
1049 std::vector<Coefficient> coefficients;
1050 bool all_booleans = true;
1051 IntegerValue range(0);
1052 for (const ObjectiveTerm& term : terms_) {
1053 const IntegerVariable var = term.var;
1054 const IntegerValue coeff = term.weight;
1055 const IntegerValue lb = integer_trail_->LowerBound(var);
1056 const IntegerValue ub = integer_trail_->UpperBound(var);
1057 offset += Coefficient((lb * coeff).value());
1058 if (lb == ub) continue;
1059
1060 vars.push_back(var);
1061 coefficients.push_back(Coefficient(coeff.value()));
1062 if (ub - lb == 1) {
1063 literals.push_back(integer_encoder_->GetOrCreateAssociatedLiteral(
1065 } else {
1066 all_booleans = false;
1067 range += ub - lb;
1068 }
1069 }
1070 if (all_booleans) {
1071 // In some corner case, it is possible the GetOrCreateAssociatedLiteral()
1072 // returns identical or negated literal of another term. We don't support
1073 // this below, so we need to make sure this is not the case.
1074 PresolveBooleanLinearExpression(&literals, &coefficients, &offset);
1075
1076 // TODO(user): It might be interesting to redo this kind of presolving
1077 // once high cost booleans have been fixed as we might have more at most
1078 // one between literal in the objective by then.
1079 //
1080 // Or alternatively, we could try this or something like it on the
1081 // literals from the cores as they are found. We should probably make
1082 // sure that if it exist, a core of size two is always added. And for
1083 // such core, we can always try to see if the "at most one" can be
1084 // extended.
1085 PresolveObjectiveWithAtMostOne(&literals, &coefficients, &offset);
1086 return OptimizeWithSatEncoding(literals, {}, coefficients, offset);
1087 }
1088 if (range < 1e8) {
1089 return OptimizeWithSatEncoding({}, vars, coefficients, offset);
1090 }
1091 }
1092
1093 // TODO(user): The core is returned in the same order as the assumptions,
1094 // so we don't really need this map, we could just do a linear scan to
1095 // recover which node are part of the core. This however needs to be properly
1096 // unit tested before usage.
1097 absl::btree_map<LiteralIndex, int> literal_to_term_index;
1098
1099 // Start the algorithm.
1100 stop_ = false;
1101 while (true) {
1102 // TODO(user): This always resets the solver to level zero.
1103 // Because of that we don't resume a solve in "chunk" perfectly. Fix.
1104 if (!PropagateObjectiveBounds()) return SatSolver::INFEASIBLE;
1105 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
1106
1107 // Bulk cover optimization.
1108 //
1109 // TODO(user): If the search is aborted during this phase and we solve in
1110 // "chunk", we don't resume perfectly from where it was. Fix.
1111 if (parameters_->cover_optimization()) {
1112 if (!CoverOptimization()) return SatSolver::INFEASIBLE;
1113 if (stop_) return SatSolver::LIMIT_REACHED;
1114 }
1115
1116 // We assumes all terms (modulo stratification) at their lower-bound.
1117 std::vector<int> term_indices;
1118 std::vector<IntegerLiteral> integer_assumptions;
1119 std::vector<IntegerValue> assumption_weights;
1120 IntegerValue objective_offset(0);
1121 bool some_assumptions_were_skipped = false;
1122 for (int i = 0; i < terms_.size(); ++i) {
1123 const ObjectiveTerm term = terms_[i];
1124
1125 // TODO(user): These can be simply removed from the list.
1126 if (term.weight == 0) continue;
1127
1128 // Skip fixed terms.
1129 // We still keep them around for a proper lower-bound computation.
1130 //
1131 // TODO(user): we could keep an objective offset instead.
1132 const IntegerValue var_lb = integer_trail_->LowerBound(term.var);
1133 const IntegerValue var_ub = integer_trail_->UpperBound(term.var);
1134 if (var_lb == var_ub) {
1135 objective_offset += term.weight * var_lb.value();
1136 continue;
1137 }
1138
1139 // Only consider the terms above the threshold.
1140 if (term.weight >= stratification_threshold_) {
1141 integer_assumptions.push_back(
1142 IntegerLiteral::LowerOrEqual(term.var, var_lb));
1143 assumption_weights.push_back(term.weight);
1144 term_indices.push_back(i);
1145 } else {
1146 some_assumptions_were_skipped = true;
1147 }
1148 }
1149
1150 // No assumptions with the current stratification? use the next one.
1151 if (term_indices.empty() && some_assumptions_were_skipped) {
1152 ComputeNextStratificationThreshold();
1153 continue;
1154 }
1155
1156 // If there is only one or two assumptions left, we switch the algorithm.
1157 if (term_indices.size() <= 2 && !some_assumptions_were_skipped) {
1158 VLOG(1) << "Switching to linear scan...";
1159 if (!already_switched_to_linear_scan_) {
1160 already_switched_to_linear_scan_ = true;
1161 std::vector<IntegerVariable> constraint_vars;
1162 std::vector<int64_t> constraint_coeffs;
1163 for (const int index : term_indices) {
1164 constraint_vars.push_back(terms_[index].var);
1165 constraint_coeffs.push_back(terms_[index].weight.value());
1166 }
1167 constraint_vars.push_back(objective_var_);
1168 constraint_coeffs.push_back(-1);
1169 model_->Add(WeightedSumLowerOrEqual(constraint_vars, constraint_coeffs,
1170 -objective_offset.value()));
1171 }
1172
1174 objective_var_, feasible_solution_observer_, model_);
1175 }
1176
1177 // Display the progress.
1178 if (VLOG_IS_ON(1)) {
1179 int max_depth = 0;
1180 for (const ObjectiveTerm& term : terms_) {
1181 max_depth = std::max(max_depth, term.depth);
1182 }
1183 const int64_t lb = integer_trail_->LowerBound(objective_var_).value();
1184 const int64_t ub = integer_trail_->UpperBound(objective_var_).value();
1185 const int gap =
1186 lb == ub
1187 ? 0
1188 : static_cast<int>(std::ceil(
1189 100.0 * (ub - lb) / std::max(std::abs(ub), std::abs(lb))));
1190 VLOG(1) << absl::StrCat("unscaled_next_obj_range:[", lb, ",", ub,
1191 "]"
1192 " gap:",
1193 gap, "%", " assumptions:", term_indices.size(),
1194 " strat:", stratification_threshold_.value(),
1195 " depth:", max_depth,
1196 " bool: ", sat_solver_->NumVariables());
1197 }
1198
1199 // Convert integer_assumptions to Literals.
1200 std::vector<Literal> assumptions;
1201 literal_to_term_index.clear();
1202 for (int i = 0; i < integer_assumptions.size(); ++i) {
1203 assumptions.push_back(integer_encoder_->GetOrCreateAssociatedLiteral(
1204 integer_assumptions[i]));
1205
1206 // Tricky: In some rare case, it is possible that the same literal
1207 // correspond to more that one assumptions. In this case, we can just
1208 // pick one of them when converting back a core to term indices.
1209 //
1210 // TODO(user): We can probably be smarter about the cost of the
1211 // assumptions though.
1212 literal_to_term_index[assumptions.back()] = term_indices[i];
1213 }
1214
1215 // Solve under the assumptions.
1216 //
1217 // TODO(user): If the "search" is interrupted while computing cores, we
1218 // currently do not resume it flawlessly. We however add any cores we found
1219 // before aborting.
1220 std::vector<std::vector<Literal>> cores;
1221 const SatSolver::Status result =
1222 FindCores(assumptions, assumption_weights, stratification_threshold_,
1223 model_, &cores);
1224 if (result == SatSolver::INFEASIBLE) return SatSolver::INFEASIBLE;
1225 if (result == SatSolver::FEASIBLE) {
1226 if (!ProcessSolution()) return SatSolver::INFEASIBLE;
1227 if (stop_) return SatSolver::LIMIT_REACHED;
1228 if (cores.empty()) {
1229 ComputeNextStratificationThreshold();
1230 if (stratification_threshold_ == 0) return SatSolver::INFEASIBLE;
1231 continue;
1232 }
1233 }
1234
1235 // Process the cores by creating new variables and transferring the minimum
1236 // weight of each core to it.
1237 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1238 for (const std::vector<Literal>& core : cores) {
1239 // This just increase the lower-bound of the corresponding node.
1240 // TODO(user): Maybe the solver should do it right away.
1241 if (core.size() == 1) {
1242 if (!sat_solver_->AddUnitClause(core[0].Negated())) {
1243 return SatSolver::INFEASIBLE;
1244 }
1245 continue;
1246 }
1247
1248 // Compute the min weight of all the terms in the core. The lower bound
1249 // will be increased by that much because at least one assumption in the
1250 // core must be true. This is also why we can start at 1 for new_var_lb.
1251 bool ignore_this_core = false;
1252 IntegerValue min_weight = kMaxIntegerValue;
1253 IntegerValue max_weight(0);
1254 IntegerValue new_var_lb(1);
1255 IntegerValue new_var_ub(0);
1256 int new_depth = 0;
1257 for (const Literal lit : core) {
1258 const int index = literal_to_term_index.at(lit.Index());
1259
1260 // When this happen, the core is now trivially "minimized" by the new
1261 // bound on this variable, so there is no point in adding it.
1262 if (terms_[index].old_var_lb <
1263 integer_trail_->LowerBound(terms_[index].var)) {
1264 ignore_this_core = true;
1265 break;
1266 }
1267
1268 const IntegerValue weight = terms_[index].weight;
1269 min_weight = std::min(min_weight, weight);
1270 max_weight = std::max(max_weight, weight);
1271 new_depth = std::max(new_depth, terms_[index].depth + 1);
1272 new_var_lb += integer_trail_->LowerBound(terms_[index].var);
1273 new_var_ub += integer_trail_->UpperBound(terms_[index].var);
1274 }
1275 if (ignore_this_core) continue;
1276
1277 VLOG(1) << absl::StrFormat(
1278 "core:%u weight:[%d,%d] domain:[%d,%d] depth:%d", core.size(),
1279 min_weight.value(), max_weight.value(), new_var_lb.value(),
1280 new_var_ub.value(), new_depth);
1281
1282 // We will "transfer" min_weight from all the variables of the core
1283 // to a new variable.
1284 const IntegerVariable new_var =
1285 integer_trail_->AddIntegerVariable(new_var_lb, new_var_ub);
1286 terms_.push_back({new_var, min_weight, new_depth});
1287 terms_.back().cover_ub = new_var_ub;
1288
1289 // Sum variables in the core <= new_var.
1290 {
1291 std::vector<IntegerVariable> constraint_vars;
1292 std::vector<int64_t> constraint_coeffs;
1293 for (const Literal lit : core) {
1294 const int index = literal_to_term_index.at(lit.Index());
1295 terms_[index].weight -= min_weight;
1296 constraint_vars.push_back(terms_[index].var);
1297 constraint_coeffs.push_back(1);
1298 }
1299 constraint_vars.push_back(new_var);
1300 constraint_coeffs.push_back(-1);
1301 model_->Add(
1302 WeightedSumLowerOrEqual(constraint_vars, constraint_coeffs, 0));
1303 }
1304 }
1305
1306 // Abort if we reached the time limit. Note that we still add any cores we
1307 // found in case the solve is split in "chunk".
1308 if (result == SatSolver::LIMIT_REACHED) return result;
1309 }
1310}
1311
1312} // namespace sat
1313} // namespace operations_research
Definition model.h:345
bool Contains(int64_t value) const
void EnableLogging(bool enable)
Definition logging.h:51
std::vector< Literal > ExpandAtMostOneWithWeight(absl::Span< const Literal > at_most_one, const util_intops::StrongVector< LiteralIndex, bool > &can_be_included, const util_intops::StrongVector< LiteralIndex, double > &expanded_lp_values)
Definition clause.cc:2754
SatSolver::Status OptimizeWithSatEncoding(absl::Span< const Literal > literals, absl::Span< const IntegerVariable > vars, absl::Span< const Coefficient > coefficients, Coefficient offset)
CoreBasedOptimizer(IntegerVariable objective_var, absl::Span< const IntegerVariable > variables, absl::Span< const IntegerValue > coefficients, std::function< void()> feasible_solution_observer, Model *model)
static EncodingNode GenericNode(int lb, int ub, std::function< Literal(int x)> create_lit, Coefficient weight)
Definition encoding.cc:63
static EncodingNode LiteralNode(Literal l, Coefficient weight)
Definition encoding.cc:52
IntegerValue LowerBound(IntegerVariable i) const
Definition integer.h:1537
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition integer.cc:895
LiteralIndex Index() const
Definition sat_base.h:92
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition model.h:110
std::vector< EncodingNode * > * mutable_nodes()
Definition encoding.h:252
void AddBaseNode(EncodingNode node)
Definition encoding.h:245
const std::vector< EncodingNode * > & nodes() const
Definition encoding.h:251
bool ProcessCore(absl::Span< const Literal > core, Coefficient min_weight, Coefficient gap, std::string *info)
Definition encoding.cc:588
static constexpr MaxSatStratificationAlgorithm STRATIFICATION_NONE
Status EnqueueDecisionAndBacktrackOnConflict(Literal true_literal, int *first_propagation_index=nullptr)
std::vector< Literal > GetLastIncompatibleDecisions()
void Backtrack(int target_level)
ABSL_MUST_USE_RESULT bool Propagate()
ABSL_MUST_USE_RESULT bool AddUnitClause(Literal true_literal)
Status ResetAndSolveWithGivenAssumptions(const std::vector< Literal > &assumptions, int64_t max_number_of_conflicts=-1)
const VariablesAssignment & Assignment() const
Definition sat_solver.h:420
ABSL_MUST_USE_RESULT bool ResetToLevelZero()
bool BacktrackAndPropagateReimplications(int target_level)
Definition sat_solver.h:327
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal, std::optional< ConflictCallback > callback=std::nullopt)
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:203
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:206
void resize(size_type new_size)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
std::tuple< int64_t, int64_t, const double > Coefficient
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::vector< Literal > ExtractAssumptions(Coefficient stratified_lower_bound, const std::vector< EncodingNode * > &nodes, SatSolver *solver)
Definition encoding.cc:549
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition integer.h:1819
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
void ReduceNodes(Coefficient upper_bound, Coefficient *lower_bound, std::vector< EncodingNode * > *nodes, SatSolver *solver)
Definition encoding.cc:503
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
void FilterAssignedLiteral(const VariablesAssignment &assignment, std::vector< Literal > *core)
Coefficient ComputeCoreMinWeight(const std::vector< EncodingNode * > &nodes, absl::Span< const Literal > core)
Definition encoding.cc:562
void MinimizeCoreWithSearch(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
void PresolveBooleanLinearExpression(std::vector< Literal > *literals, std::vector< Coefficient > *coefficients, Coefficient *offset)
bool ProbeLiteral(Literal assumption, SatSolver *solver)
SatSolver::Status MinimizeIntegerVariableWithLinearScanAndLazyEncoding(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
int MoveOneUnprocessedLiteralLast(const absl::btree_set< LiteralIndex > &processed, int relevant_prefix_size, std::vector< Literal > *literals)
Definition util.cc:377
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
void RestrictObjectiveDomainWithBinarySearch(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
Coefficient MaxNodeWeightSmallerThan(const std::vector< EncodingNode * > &nodes, Coefficient upper_bound)
Definition encoding.cc:576
std::function< void(Model *)> WeightedSumLowerOrEqual(absl::Span< const IntegerVariable > vars, const VectorInt &coefficients, int64_t upper_bound)
OR-Tools root namespace.
const bool DEBUG_MODE
Definition radix_sort.h:266
std::string FormatCounter(int64_t num)
Definition logging.cc:30
STL namespace.
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)