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