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