OR-Tools  9.3
optimization.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <cstdlib>
20#include <deque>
21#include <functional>
22#include <limits>
23#include <string>
24#include <utility>
25#include <vector>
26
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/random/bit_gen_ref.h"
31#include "absl/random/random.h"
32#include "absl/strings/str_cat.h"
33#include "absl/strings/str_format.h"
36#include "ortools/base/macros.h"
41#include "ortools/sat/boolean_problem.pb.h"
43#include "ortools/sat/integer.h"
46#include "ortools/sat/model.h"
49#include "ortools/sat/sat_parameters.pb.h"
52#include "ortools/sat/util.h"
55
56namespace operations_research {
57namespace sat {
58
59namespace {
60
61// Used to log messages to stdout or to the normal logging framework according
62// to the given LogBehavior value.
63class Logger {
64 public:
65 explicit Logger(LogBehavior v) : use_stdout_(v == STDOUT_LOG) {}
66 void Log(const std::string& message) {
67 if (use_stdout_) {
68 absl::PrintF("%s\n", message);
69 } else {
70 LOG(INFO) << message;
71 }
72 }
73
74 private:
75 bool use_stdout_;
76};
77
78// Outputs the current objective value in the cnf output format.
79// Note that this function scale the given objective.
80std::string CnfObjectiveLine(const LinearBooleanProblem& problem,
81 Coefficient objective) {
82 const double scaled_objective =
83 AddOffsetAndScaleObjectiveValue(problem, objective);
84 return absl::StrFormat("o %d", static_cast<int64_t>(scaled_objective));
85}
86
87struct LiteralWithCoreIndex {
88 LiteralWithCoreIndex(Literal l, int i) : literal(l), core_index(i) {}
89 Literal literal;
91};
92
93// Deletes the given indices from a vector. The given indices must be sorted in
94// increasing order. The order of the non-deleted entries in the vector is
95// preserved.
96template <typename Vector>
97void DeleteVectorIndices(const std::vector<int>& indices, Vector* v) {
98 int new_size = 0;
99 int indices_index = 0;
100 for (int i = 0; i < v->size(); ++i) {
101 if (indices_index < indices.size() && i == indices[indices_index]) {
102 ++indices_index;
103 } else {
104 (*v)[new_size] = (*v)[i];
105 ++new_size;
106 }
107 }
108 v->resize(new_size);
109}
110
111// In the Fu & Malik algorithm (or in WPM1), when two cores overlap, we
112// artificially introduce symmetries. More precisely:
113//
114// The picture below shows two cores with index 0 and 1, with one blocking
115// variable per '-' and with the variables ordered from left to right (by their
116// assumptions index). The blocking variables will be the one added to "relax"
117// the core for the next iteration.
118//
119// 1: -------------------------------
120// 0: ------------------------------------
121//
122// The 2 following assignment of the blocking variables are equivalent.
123// Remember that exactly one blocking variable per core must be assigned to 1.
124//
125// 1: ----------------------1--------
126// 0: --------1---------------------------
127//
128// and
129//
130// 1: ---------------------------1---
131// 0: ---1--------------------------------
132//
133// This class allows to add binary constraints excluding the second possibility.
134// Basically, each time a new core is added, if two of its blocking variables
135// (b1, b2) have the same assumption index of two blocking variables from
136// another core (c1, c2), then we forbid the assignment c1 true and b2 true.
137//
138// Reference: C Ansótegui, ML Bonet, J Levy, "Sat-based maxsat algorithms",
139// Artificial Intelligence, 2013 - Elsevier.
140class FuMalikSymmetryBreaker {
141 public:
142 FuMalikSymmetryBreaker() {}
143
144 // Must be called before a new core is processed.
145 void StartResolvingNewCore(int new_core_index) {
146 literal_by_core_.resize(new_core_index);
147 for (int i = 0; i < new_core_index; ++i) {
148 literal_by_core_[i].clear();
149 }
150 }
151
152 // This should be called for each blocking literal b of the new core. The
153 // assumption_index identify the soft clause associated to the given blocking
154 // literal. Note that between two StartResolvingNewCore() calls,
155 // ProcessLiteral() is assumed to be called with different assumption_index.
156 //
157 // Changing the order of the calls will not change the correctness, but will
158 // change the symmetry-breaking clause produced.
159 //
160 // Returns a set of literals which can't be true at the same time as b (under
161 // symmetry breaking).
162 std::vector<Literal> ProcessLiteral(int assumption_index, Literal b) {
163 if (assumption_index >= info_by_assumption_index_.size()) {
164 info_by_assumption_index_.resize(assumption_index + 1);
165 }
166
167 // Compute the function result.
168 // info_by_assumption_index_[assumption_index] will contain all the pairs
169 // (blocking_literal, core) of the previous resolved cores at the same
170 // assumption index as b.
171 std::vector<Literal> result;
172 for (LiteralWithCoreIndex data :
173 info_by_assumption_index_[assumption_index]) {
174 // literal_by_core_ will contain all the blocking literal of a given core
175 // with an assumption_index that was used in one of the ProcessLiteral()
176 // calls since the last StartResolvingNewCore().
177 //
178 // Note that there can be only one such literal by core, so we will not
179 // add duplicates.
180 result.insert(result.end(), literal_by_core_[data.core_index].begin(),
181 literal_by_core_[data.core_index].end());
182 }
183
184 // Update the internal data structure.
185 for (LiteralWithCoreIndex data :
186 info_by_assumption_index_[assumption_index]) {
187 literal_by_core_[data.core_index].push_back(data.literal);
188 }
189 info_by_assumption_index_[assumption_index].push_back(
190 LiteralWithCoreIndex(b, literal_by_core_.size()));
191 return result;
192 }
193
194 // Deletes the given assumption indices.
195 void DeleteIndices(const std::vector<int>& indices) {
196 DeleteVectorIndices(indices, &info_by_assumption_index_);
197 }
198
199 // This is only used in WPM1 to forget all the information related to a given
200 // assumption_index.
201 void ClearInfo(int assumption_index) {
202 CHECK_LE(assumption_index, info_by_assumption_index_.size());
203 info_by_assumption_index_[assumption_index].clear();
204 }
205
206 // This is only used in WPM1 when a new assumption_index is created.
207 void AddInfo(int assumption_index, Literal b) {
208 CHECK_GE(assumption_index, info_by_assumption_index_.size());
209 info_by_assumption_index_.resize(assumption_index + 1);
210 info_by_assumption_index_[assumption_index].push_back(
211 LiteralWithCoreIndex(b, literal_by_core_.size()));
212 }
213
214 private:
215 std::vector<std::vector<LiteralWithCoreIndex>> info_by_assumption_index_;
216 std::vector<std::vector<Literal>> literal_by_core_;
217
218 DISALLOW_COPY_AND_ASSIGN(FuMalikSymmetryBreaker);
219};
220
221} // namespace
222
224 std::vector<Literal>* core) {
225 if (solver->IsModelUnsat()) return;
226 absl::btree_set<LiteralIndex> moved_last;
227 std::vector<Literal> candidate(core->begin(), core->end());
228
229 solver->Backtrack(0);
230 solver->SetAssumptionLevel(0);
231 if (!solver->FinishPropagation()) return;
232 while (!limit->LimitReached()) {
233 // We want each literal in candidate to appear last once in our propagation
234 // order. We want to do that while maximizing the reutilization of the
235 // current assignment prefix, that is minimizing the number of
236 // decision/progagation we need to perform.
237 const int target_level = MoveOneUnprocessedLiteralLast(
238 moved_last, solver->CurrentDecisionLevel(), &candidate);
239 if (target_level == -1) break;
240 solver->Backtrack(target_level);
241 while (!solver->IsModelUnsat() && !limit->LimitReached() &&
242 solver->CurrentDecisionLevel() < candidate.size()) {
243 const Literal decision = candidate[solver->CurrentDecisionLevel()];
244 if (solver->Assignment().LiteralIsTrue(decision)) {
245 candidate.erase(candidate.begin() + solver->CurrentDecisionLevel());
246 continue;
247 } else if (solver->Assignment().LiteralIsFalse(decision)) {
248 // This is a "weird" API to get the subset of decisions that caused
249 // this literal to be false with reason analysis.
251 candidate = solver->GetLastIncompatibleDecisions();
252 break;
253 } else {
254 solver->EnqueueDecisionAndBackjumpOnConflict(decision);
255 }
256 }
257 if (candidate.empty() || solver->IsModelUnsat()) return;
258 moved_last.insert(candidate.back().Index());
259 }
260
261 solver->Backtrack(0);
262 solver->SetAssumptionLevel(0);
263 if (candidate.size() < core->size()) {
264 VLOG(1) << "minimization " << core->size() << " -> " << candidate.size();
265
266 // We want to preserve the order of literal in the response.
267 absl::flat_hash_set<LiteralIndex> set;
268 for (const Literal l : candidate) set.insert(l.Index());
269 int new_size = 0;
270 for (const Literal l : *core) {
271 if (set.contains(l.Index())) {
272 (*core)[new_size++] = l;
273 }
274 }
275 core->resize(new_size);
276 }
277}
278
279// This algorithm works by exploiting the unsat core returned by the SAT solver
280// when the problem is UNSAT. It starts by trying to solve the decision problem
281// where all the objective variables are set to their value with minimal cost,
282// and relax in each step some of these fixed variables until the problem
283// becomes satisfiable.
285 const LinearBooleanProblem& problem,
286 SatSolver* solver,
287 std::vector<bool>* solution) {
288 Logger logger(log);
289 FuMalikSymmetryBreaker symmetry;
290
291 // blocking_clauses will contains a set of clauses that are currently added to
292 // the initial problem.
293 //
294 // Initially, each clause just contains a literal associated to an objective
295 // variable with non-zero cost. Setting all these literals to true will lead
296 // to the lowest possible objective.
297 //
298 // During the algorithm, "blocking" literals will be added to each clause.
299 // Moreover each clause will contain an extra "assumption" literal stored in
300 // the separate assumptions vector (in its negated form).
301 //
302 // The meaning of a given clause will always be:
303 // If the assumption literal and all blocking literals are false, then the
304 // "objective" literal (which is the first one in the clause) must be true.
305 // When the "objective" literal is true, its variable (which have a non-zero
306 // cost) is set to the value that minimize the objective cost.
307 //
308 // ex: If a variable "x" as a cost of 3, its cost contribution is smaller when
309 // it is set to false (since it will contribute to zero instead of 3).
310 std::vector<std::vector<Literal>> blocking_clauses;
311 std::vector<Literal> assumptions;
312
313 // Initialize blocking_clauses and assumptions.
314 const LinearObjective& objective = problem.objective();
315 CHECK_GT(objective.coefficients_size(), 0);
316 const Coefficient unique_objective_coeff(std::abs(objective.coefficients(0)));
317 for (int i = 0; i < objective.literals_size(); ++i) {
318 CHECK_EQ(std::abs(objective.coefficients(i)), unique_objective_coeff)
319 << "The basic Fu & Malik algorithm needs constant objective coeffs.";
320 const Literal literal(objective.literals(i));
321
322 // We want to minimize the cost when this literal is true.
323 const Literal min_literal =
324 objective.coefficients(i) > 0 ? literal.Negated() : literal;
325 blocking_clauses.push_back(std::vector<Literal>(1, min_literal));
326
327 // Note that initialy, we do not create any extra variables.
328 assumptions.push_back(min_literal);
329 }
330
331 // Print the number of variable with a non-zero cost.
332 logger.Log(absl::StrFormat("c #weights:%u #vars:%d #constraints:%d",
333 assumptions.size(), problem.num_variables(),
334 problem.constraints_size()));
335
336 // Starts the algorithm. Each loop will solve the problem under the given
337 // assumptions, and if unsat, will relax exactly one of the objective
338 // variables (from the unsat core) to be in its "costly" state. When the
339 // algorithm terminates, the number of iterations is exactly the minimal
340 // objective value.
341 for (int iter = 0;; ++iter) {
342 const SatSolver::Status result =
343 solver->ResetAndSolveWithGivenAssumptions(assumptions);
344 if (result == SatSolver::FEASIBLE) {
345 ExtractAssignment(problem, *solver, solution);
346 Coefficient objective = ComputeObjectiveValue(problem, *solution);
347 logger.Log(CnfObjectiveLine(problem, objective));
348 return SatSolver::FEASIBLE;
349 }
350 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
351
352 // The interesting case: we have an unsat core.
353 //
354 // We need to add new "blocking" variables b_i for all the objective
355 // variable appearing in the core. Moreover, we will only relax as little
356 // as possible (to not miss the optimal), so we will enforce that the sum
357 // of the b_i is exactly one.
358 std::vector<Literal> core = solver->GetLastIncompatibleDecisions();
359 MinimizeCore(solver, &core);
360 solver->Backtrack(0);
361
362 // Print the search progress.
363 logger.Log(absl::StrFormat("c iter:%d core:%u", iter, core.size()));
364
365 // Special case for a singleton core.
366 if (core.size() == 1) {
367 // Find the index of the "objective" variable that need to be fixed in
368 // its "costly" state.
369 const int index =
370 std::find(assumptions.begin(), assumptions.end(), core[0]) -
371 assumptions.begin();
372 CHECK_LT(index, assumptions.size());
373
374 // Fix it. We also fix all the associated blocking variables if any.
375 if (!solver->AddUnitClause(core[0].Negated())) {
377 }
378 for (Literal b : blocking_clauses[index]) {
379 if (!solver->AddUnitClause(b.Negated())) return SatSolver::INFEASIBLE;
380 }
381
382 // Erase this entry from the current "objective"
383 std::vector<int> to_delete(1, index);
384 DeleteVectorIndices(to_delete, &assumptions);
385 DeleteVectorIndices(to_delete, &blocking_clauses);
386 symmetry.DeleteIndices(to_delete);
387 } else {
388 symmetry.StartResolvingNewCore(iter);
389
390 // We will add 2 * |core.size()| variables.
391 const int old_num_variables = solver->NumVariables();
392 if (core.size() == 2) {
393 // Special case. If core.size() == 2, we can use only one blocking
394 // variable (the other one beeing its negation). This actually do happen
395 // quite often in practice, so it is worth it.
396 solver->SetNumVariables(old_num_variables + 3);
397 } else {
398 solver->SetNumVariables(old_num_variables + 2 * core.size());
399 }
400
401 // Temporary vectors for the constraint (sum new blocking variable == 1).
402 std::vector<LiteralWithCoeff> at_most_one_constraint;
403 std::vector<Literal> at_least_one_constraint;
404
405 // This will be set to false if the problem becomes unsat while adding a
406 // new clause. This is unlikely, but may be possible.
407 bool ok = true;
408
409 // Loop over the core.
410 int index = 0;
411 for (int i = 0; i < core.size(); ++i) {
412 // Since the assumptions appear in order in the core, we can find the
413 // relevant "objective" variable efficiently with a simple linear scan
414 // in the assumptions vector (done with index).
415 index =
416 std::find(assumptions.begin() + index, assumptions.end(), core[i]) -
417 assumptions.begin();
418 CHECK_LT(index, assumptions.size());
419
420 // The new blocking and assumption variables for this core entry.
421 const Literal a(BooleanVariable(old_num_variables + i), true);
422 Literal b(BooleanVariable(old_num_variables + core.size() + i), true);
423 if (core.size() == 2) {
424 b = Literal(BooleanVariable(old_num_variables + 2), true);
425 if (i == 1) b = b.Negated();
426 }
427
428 // Symmetry breaking clauses.
429 for (Literal l : symmetry.ProcessLiteral(index, b)) {
430 ok &= solver->AddBinaryClause(l.Negated(), b.Negated());
431 }
432
433 // Note(user): There is more than one way to encode the algorithm in
434 // SAT. Here we "delete" the old blocking clause and add a new one. In
435 // the WPM1 algorithm below, the blocking clause is decomposed into
436 // 3-SAT and we don't need to delete anything.
437
438 // First, fix the old "assumption" variable to false, which has the
439 // effect of deleting the old clause from the solver.
440 if (assumptions[index].Variable() >= problem.num_variables()) {
441 CHECK(solver->AddUnitClause(assumptions[index].Negated()));
442 }
443
444 // Add the new blocking variable.
445 blocking_clauses[index].push_back(b);
446
447 // Add the new clause to the solver. Temporary including the
448 // assumption, but removing it right afterwards.
449 blocking_clauses[index].push_back(a);
450 ok &= solver->AddProblemClause(blocking_clauses[index]);
451 blocking_clauses[index].pop_back();
452
453 // For the "== 1" constraint on the blocking literals.
454 at_most_one_constraint.push_back(LiteralWithCoeff(b, 1.0));
455 at_least_one_constraint.push_back(b);
456
457 // The new assumption variable replace the old one.
458 assumptions[index] = a.Negated();
459 }
460
461 // Add the "<= 1" side of the "== 1" constraint.
462 ok &= solver->AddLinearConstraint(false, Coefficient(0), true,
463 Coefficient(1.0),
464 &at_most_one_constraint);
465
466 // TODO(user): The algorithm does not really need the >= 1 side of this
467 // constraint. Initial investigation shows that it doesn't really help,
468 // but investigate more.
469 if (/* DISABLES CODE */ (false)) {
470 ok &= solver->AddProblemClause(at_least_one_constraint);
471 }
472
473 if (!ok) {
474 LOG(INFO) << "Infeasible while adding a clause.";
476 }
477 }
478 }
479}
480
482 const LinearBooleanProblem& problem,
483 SatSolver* solver,
484 std::vector<bool>* solution) {
485 Logger logger(log);
486 FuMalikSymmetryBreaker symmetry;
487
488 // The current lower_bound on the cost.
489 // It will be correct after the initialization.
490 Coefficient lower_bound(static_cast<int64_t>(problem.objective().offset()));
492
493 // The assumption literals and their associated cost.
494 std::vector<Literal> assumptions;
495 std::vector<Coefficient> costs;
496 std::vector<Literal> reference;
497
498 // Initialization.
499 const LinearObjective& objective = problem.objective();
500 CHECK_GT(objective.coefficients_size(), 0);
501 for (int i = 0; i < objective.literals_size(); ++i) {
502 const Literal literal(objective.literals(i));
503 const Coefficient coeff(objective.coefficients(i));
504
505 // We want to minimize the cost when the assumption is true.
506 // Note that initially, we do not create any extra variables.
507 if (coeff > 0) {
508 assumptions.push_back(literal.Negated());
509 costs.push_back(coeff);
510 } else {
511 assumptions.push_back(literal);
512 costs.push_back(-coeff);
514 }
515 }
516 reference = assumptions;
517
518 // This is used by the "stratified" approach.
519 Coefficient stratified_lower_bound =
520 *std::max_element(costs.begin(), costs.end());
521
522 // Print the number of variables with a non-zero cost.
523 logger.Log(absl::StrFormat("c #weights:%u #vars:%d #constraints:%d",
524 assumptions.size(), problem.num_variables(),
525 problem.constraints_size()));
526
527 for (int iter = 0;; ++iter) {
528 // This is called "hardening" in the literature.
529 // Basically, we know that there is only hardening_threshold weight left
530 // to distribute, so any assumption with a greater cost than this can never
531 // be false. We fix it instead of treating it as an assumption.
532 solver->Backtrack(0);
533 const Coefficient hardening_threshold = upper_bound - lower_bound;
534 CHECK_GE(hardening_threshold, 0);
535 std::vector<int> to_delete;
536 int num_above_threshold = 0;
537 for (int i = 0; i < assumptions.size(); ++i) {
538 if (costs[i] > hardening_threshold) {
539 if (!solver->AddUnitClause(assumptions[i])) {
541 }
542 to_delete.push_back(i);
543 ++num_above_threshold;
544 } else {
545 // This impact the stratification heuristic.
546 if (solver->Assignment().LiteralIsTrue(assumptions[i])) {
547 to_delete.push_back(i);
548 }
549 }
550 }
551 if (!to_delete.empty()) {
552 logger.Log(absl::StrFormat("c fixed %u assumptions, %d with cost > %d",
553 to_delete.size(), num_above_threshold,
554 hardening_threshold.value()));
555 DeleteVectorIndices(to_delete, &assumptions);
556 DeleteVectorIndices(to_delete, &costs);
557 DeleteVectorIndices(to_delete, &reference);
558 symmetry.DeleteIndices(to_delete);
559 }
560
561 // This is the "stratification" part.
562 // Extract the assumptions with a cost >= stratified_lower_bound.
563 std::vector<Literal> assumptions_subset;
564 for (int i = 0; i < assumptions.size(); ++i) {
565 if (costs[i] >= stratified_lower_bound) {
566 assumptions_subset.push_back(assumptions[i]);
567 }
568 }
569
570 const SatSolver::Status result =
571 solver->ResetAndSolveWithGivenAssumptions(assumptions_subset);
572 if (result == SatSolver::FEASIBLE) {
573 // If not all assumptions were taken, continue with a lower stratified
574 // bound. Otherwise we have an optimal solution!
575 //
576 // TODO(user): Try more advanced variant where the bound is lowered by
577 // more than this minimal amount.
578 const Coefficient old_lower_bound = stratified_lower_bound;
579 for (Coefficient cost : costs) {
580 if (cost < old_lower_bound) {
581 if (stratified_lower_bound == old_lower_bound ||
582 cost > stratified_lower_bound) {
583 stratified_lower_bound = cost;
584 }
585 }
586 }
587
588 ExtractAssignment(problem, *solver, solution);
589 DCHECK(IsAssignmentValid(problem, *solution));
590 const Coefficient objective_offset(
591 static_cast<int64_t>(problem.objective().offset()));
592 const Coefficient objective = ComputeObjectiveValue(problem, *solution);
593 if (objective + objective_offset < upper_bound) {
594 logger.Log(CnfObjectiveLine(problem, objective));
595 upper_bound = objective + objective_offset;
596 }
597
598 if (stratified_lower_bound < old_lower_bound) continue;
599 return SatSolver::FEASIBLE;
600 }
601 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
602
603 // The interesting case: we have an unsat core.
604 //
605 // We need to add new "blocking" variables b_i for all the objective
606 // variables appearing in the core. Moreover, we will only relax as little
607 // as possible (to not miss the optimal), so we will enforce that the sum
608 // of the b_i is exactly one.
609 std::vector<Literal> core = solver->GetLastIncompatibleDecisions();
610 MinimizeCore(solver, &core);
611 solver->Backtrack(0);
612
613 // Compute the min cost of all the assertions in the core.
614 // The lower bound will be updated by that much.
615 Coefficient min_cost = kCoefficientMax;
616 {
617 int index = 0;
618 for (int i = 0; i < core.size(); ++i) {
619 index =
620 std::find(assumptions.begin() + index, assumptions.end(), core[i]) -
621 assumptions.begin();
622 CHECK_LT(index, assumptions.size());
623 min_cost = std::min(min_cost, costs[index]);
624 }
625 }
626 lower_bound += min_cost;
627
628 // Print the search progress.
629 logger.Log(absl::StrFormat(
630 "c iter:%d core:%u lb:%d min_cost:%d strat:%d", iter, core.size(),
631 lower_bound.value(), min_cost.value(), stratified_lower_bound.value()));
632
633 // This simple line helps a lot on the packup-wpms instances!
634 //
635 // TODO(user): That was because of a bug before in the way
636 // stratified_lower_bound was decremented, not sure it helps that much now.
637 if (min_cost > stratified_lower_bound) {
638 stratified_lower_bound = min_cost;
639 }
640
641 // Special case for a singleton core.
642 if (core.size() == 1) {
643 // Find the index of the "objective" variable that need to be fixed in
644 // its "costly" state.
645 const int index =
646 std::find(assumptions.begin(), assumptions.end(), core[0]) -
647 assumptions.begin();
648 CHECK_LT(index, assumptions.size());
649
650 // Fix it.
651 if (!solver->AddUnitClause(core[0].Negated())) {
653 }
654
655 // Erase this entry from the current "objective".
656 std::vector<int> to_delete(1, index);
657 DeleteVectorIndices(to_delete, &assumptions);
658 DeleteVectorIndices(to_delete, &costs);
659 DeleteVectorIndices(to_delete, &reference);
660 symmetry.DeleteIndices(to_delete);
661 } else {
662 symmetry.StartResolvingNewCore(iter);
663
664 // We will add 2 * |core.size()| variables.
665 const int old_num_variables = solver->NumVariables();
666 if (core.size() == 2) {
667 // Special case. If core.size() == 2, we can use only one blocking
668 // variable (the other one beeing its negation). This actually do happen
669 // quite often in practice, so it is worth it.
670 solver->SetNumVariables(old_num_variables + 3);
671 } else {
672 solver->SetNumVariables(old_num_variables + 2 * core.size());
673 }
674
675 // Temporary vectors for the constraint (sum new blocking variable == 1).
676 std::vector<LiteralWithCoeff> at_most_one_constraint;
677 std::vector<Literal> at_least_one_constraint;
678
679 // This will be set to false if the problem becomes unsat while adding a
680 // new clause. This is unlikely, but may be possible.
681 bool ok = true;
682
683 // Loop over the core.
684 int index = 0;
685 for (int i = 0; i < core.size(); ++i) {
686 // Since the assumptions appear in order in the core, we can find the
687 // relevant "objective" variable efficiently with a simple linear scan
688 // in the assumptions vector (done with index).
689 index =
690 std::find(assumptions.begin() + index, assumptions.end(), core[i]) -
691 assumptions.begin();
692 CHECK_LT(index, assumptions.size());
693
694 // The new blocking and assumption variables for this core entry.
695 const Literal a(BooleanVariable(old_num_variables + i), true);
696 Literal b(BooleanVariable(old_num_variables + core.size() + i), true);
697 if (core.size() == 2) {
698 b = Literal(BooleanVariable(old_num_variables + 2), true);
699 if (i == 1) b = b.Negated();
700 }
701
702 // a false & b false => previous assumptions (which was false).
703 const Literal old_a = assumptions[index];
704 ok &= solver->AddTernaryClause(a, b, old_a);
705
706 // Optional. Also add the two implications a => x and b => x where x is
707 // the negation of the previous assumption variable.
708 ok &= solver->AddBinaryClause(a.Negated(), old_a.Negated());
709 ok &= solver->AddBinaryClause(b.Negated(), old_a.Negated());
710
711 // Optional. Also add the implication a => not(b).
712 ok &= solver->AddBinaryClause(a.Negated(), b.Negated());
713
714 // This is the difference with the Fu & Malik algorithm.
715 // If the soft clause protected by old_a has a cost greater than
716 // min_cost then:
717 // - its cost is disminished by min_cost.
718 // - an identical clause with cost min_cost is artificially added to
719 // the problem.
720 CHECK_GE(costs[index], min_cost);
721 if (costs[index] == min_cost) {
722 // The new assumption variable replaces the old one.
723 assumptions[index] = a.Negated();
724
725 // Symmetry breaking clauses.
726 for (Literal l : symmetry.ProcessLiteral(index, b)) {
727 ok &= solver->AddBinaryClause(l.Negated(), b.Negated());
728 }
729 } else {
730 // Since the cost of the given index changes, we need to start a new
731 // "equivalence" class for the symmetry breaking algo and clear the
732 // old one.
733 symmetry.AddInfo(assumptions.size(), b);
734 symmetry.ClearInfo(index);
735
736 // Reduce the cost of the old assumption.
737 costs[index] -= min_cost;
738
739 // We add the new assumption with a cost of min_cost.
740 //
741 // Note(user): I think it is nice that these are added after old_a
742 // because assuming old_a will implies all the derived assumptions to
743 // true, and thus they will never appear in a core until old_a is not
744 // an assumption anymore.
745 assumptions.push_back(a.Negated());
746 costs.push_back(min_cost);
747 reference.push_back(reference[index]);
748 }
749
750 // For the "<= 1" constraint on the blocking literals.
751 // Note(user): we don't add the ">= 1" side because it is not needed for
752 // the correctness and it doesn't seems to help.
753 at_most_one_constraint.push_back(LiteralWithCoeff(b, 1.0));
754
755 // Because we have a core, we know that at least one of the initial
756 // problem variables must be true. This seems to help a bit.
757 //
758 // TODO(user): Experiment more.
759 at_least_one_constraint.push_back(reference[index].Negated());
760 }
761
762 // Add the "<= 1" side of the "== 1" constraint.
763 ok &= solver->AddLinearConstraint(false, Coefficient(0), true,
764 Coefficient(1.0),
765 &at_most_one_constraint);
766
767 // Optional. Add the ">= 1" constraint on the initial problem variables.
768 ok &= solver->AddProblemClause(at_least_one_constraint);
769
770 if (!ok) {
771 LOG(INFO) << "Unsat while adding a clause.";
773 }
774 }
775 }
776}
777
779 LogBehavior log, const LinearBooleanProblem& problem, int num_times,
780 absl::BitGenRef random, SatSolver* solver, std::vector<bool>* solution) {
781 Logger logger(log);
782 const SatParameters initial_parameters = solver->parameters();
783
784 SatParameters parameters = initial_parameters;
785 TimeLimit time_limit(parameters.max_time_in_seconds());
786
787 // We start with a low conflict limit and increase it until we are able to
788 // solve the problem at least once. After this, the limit stays the same.
789 int max_number_of_conflicts = 5;
790 parameters.set_log_search_progress(false);
791
794 Coefficient best(min_seen);
795 for (int i = 0; i < num_times; ++i) {
796 solver->Backtrack(0);
798
799 parameters.set_max_number_of_conflicts(max_number_of_conflicts);
800 parameters.set_max_time_in_seconds(time_limit.GetTimeLeft());
801 parameters.set_random_seed(i);
802 solver->SetParameters(parameters);
803 solver->ResetDecisionHeuristic();
804
805 const bool use_obj = absl::Bernoulli(random, 1.0 / 4);
806 if (use_obj) UseObjectiveForSatAssignmentPreference(problem, solver);
807
808 const SatSolver::Status result = solver->Solve();
809 if (result == SatSolver::INFEASIBLE) {
810 // If the problem is INFEASIBLE after we over-constrained the objective,
811 // then we found an optimal solution, otherwise, even the decision problem
812 // is INFEASIBLE.
813 if (best == kCoefficientMax) return SatSolver::INFEASIBLE;
814 return SatSolver::FEASIBLE;
815 }
816 if (result == SatSolver::LIMIT_REACHED) {
817 // We augment the number of conflict until we have one feasible solution.
818 if (best == kCoefficientMax) ++max_number_of_conflicts;
820 continue;
821 }
822
824 std::vector<bool> candidate;
825 ExtractAssignment(problem, *solver, &candidate);
826 CHECK(IsAssignmentValid(problem, candidate));
827 const Coefficient objective = ComputeObjectiveValue(problem, candidate);
828 if (objective < best) {
829 *solution = candidate;
830 best = objective;
831 logger.Log(CnfObjectiveLine(problem, objective));
832
833 // Overconstrain the objective.
834 solver->Backtrack(0);
835 if (!AddObjectiveConstraint(problem, false, Coefficient(0), true,
836 objective - 1, solver)) {
837 return SatSolver::FEASIBLE;
838 }
839 }
840 min_seen = std::min(min_seen, objective);
841 max_seen = std::max(max_seen, objective);
842
843 logger.Log(absl::StrCat(
844 "c ", objective.value(), " [", min_seen.value(), ", ", max_seen.value(),
845 "] objective_preference: ", use_obj ? "true" : "false", " ",
847 }
848
849 // Retore the initial parameter (with an updated time limit).
850 parameters = initial_parameters;
851 parameters.set_max_time_in_seconds(time_limit.GetTimeLeft());
852 solver->SetParameters(parameters);
854}
855
857 const LinearBooleanProblem& problem,
858 SatSolver* solver,
859 std::vector<bool>* solution) {
860 Logger logger(log);
861
862 // This has a big positive impact on most problems.
864
865 Coefficient objective = kCoefficientMax;
866 if (!solution->empty()) {
867 CHECK(IsAssignmentValid(problem, *solution));
868 objective = ComputeObjectiveValue(problem, *solution);
869 }
870 while (true) {
871 if (objective != kCoefficientMax) {
872 // Over constrain the objective.
873 solver->Backtrack(0);
874 if (!AddObjectiveConstraint(problem, false, Coefficient(0), true,
875 objective - 1, solver)) {
876 return SatSolver::FEASIBLE;
877 }
878 }
879
880 // Solve the problem.
881 const SatSolver::Status result = solver->Solve();
883 if (result == SatSolver::INFEASIBLE) {
884 if (objective == kCoefficientMax) return SatSolver::INFEASIBLE;
885 return SatSolver::FEASIBLE;
886 }
887 if (result == SatSolver::LIMIT_REACHED) {
889 }
890
891 // Extract the new best solution.
893 ExtractAssignment(problem, *solver, solution);
894 CHECK(IsAssignmentValid(problem, *solution));
895 const Coefficient old_objective = objective;
896 objective = ComputeObjectiveValue(problem, *solution);
897 CHECK_LT(objective, old_objective);
898 logger.Log(CnfObjectiveLine(problem, objective));
899 }
900}
901
903 LogBehavior log, const LinearBooleanProblem& problem, SatSolver* solver,
904 std::vector<bool>* solution) {
905 Logger logger(log);
906 std::deque<EncodingNode> repository;
907
908 // Create one initial node per variables with cost.
909 Coefficient offset(0);
910 std::vector<EncodingNode*> nodes =
911 CreateInitialEncodingNodes(problem.objective(), &offset, &repository);
912
913 // This algorithm only work with weights of the same magnitude.
914 CHECK(!nodes.empty());
915 const Coefficient reference = nodes.front()->weight();
916 for (const EncodingNode* n : nodes) CHECK_EQ(n->weight(), reference);
917
918 // Initialize the current objective.
919 Coefficient objective = kCoefficientMax;
921 if (!solution->empty()) {
922 CHECK(IsAssignmentValid(problem, *solution));
923 objective = ComputeObjectiveValue(problem, *solution);
924 upper_bound = objective + offset;
925 }
926
927 // Print the number of variables with a non-zero cost.
928 logger.Log(absl::StrFormat("c #weights:%u #vars:%d #constraints:%d",
929 nodes.size(), problem.num_variables(),
930 problem.constraints_size()));
931
932 // Create the sorter network.
933 solver->Backtrack(0);
934 EncodingNode* root =
935 MergeAllNodesWithDeque(upper_bound, nodes, solver, &repository);
936 logger.Log(absl::StrFormat("c encoding depth:%d", root->depth()));
937
938 while (true) {
939 if (objective != kCoefficientMax) {
940 // Over constrain the objective by fixing the variable index - 1 of the
941 // root node to 0.
942 const int index = offset.value() + objective.value();
943 if (index == 0) return SatSolver::FEASIBLE;
944 solver->Backtrack(0);
945 if (!solver->AddUnitClause(root->literal(index - 1).Negated())) {
946 return SatSolver::FEASIBLE;
947 }
948 }
949
950 // Solve the problem.
951 const SatSolver::Status result = solver->Solve();
953 if (result == SatSolver::INFEASIBLE) {
954 if (objective == kCoefficientMax) return SatSolver::INFEASIBLE;
955 return SatSolver::FEASIBLE;
956 }
958
959 // Extract the new best solution.
961 ExtractAssignment(problem, *solver, solution);
962 CHECK(IsAssignmentValid(problem, *solution));
963 const Coefficient old_objective = objective;
964 objective = ComputeObjectiveValue(problem, *solution);
965 CHECK_LT(objective, old_objective);
966 logger.Log(CnfObjectiveLine(problem, objective));
967 }
968}
969
971 LogBehavior log, const LinearBooleanProblem& problem, SatSolver* solver,
972 std::vector<bool>* solution) {
973 Logger logger(log);
974 SatParameters parameters = solver->parameters();
975
976 // Create one initial nodes per variables with cost.
977 Coefficient offset(0);
978 std::deque<EncodingNode> repository;
979 std::vector<EncodingNode*> nodes =
980 CreateInitialEncodingNodes(problem.objective(), &offset, &repository);
981
982 // Initialize the bounds.
983 // This is in term of number of variables not at their minimal value.
986 if (!solution->empty()) {
987 CHECK(IsAssignmentValid(problem, *solution));
988 upper_bound = ComputeObjectiveValue(problem, *solution) + offset;
989 }
990
991 // Print the number of variables with a non-zero cost.
992 logger.Log(absl::StrFormat("c #weights:%u #vars:%d #constraints:%d",
993 nodes.size(), problem.num_variables(),
994 problem.constraints_size()));
995
996 // This is used by the "stratified" approach.
997 Coefficient stratified_lower_bound(0);
998 if (parameters.max_sat_stratification() ==
999 SatParameters::STRATIFICATION_DESCENT) {
1000 // In this case, we initialize it to the maximum assumption weights.
1001 for (EncodingNode* n : nodes) {
1002 stratified_lower_bound = std::max(stratified_lower_bound, n->weight());
1003 }
1004 }
1005
1006 // Start the algorithm.
1007 int max_depth = 0;
1008 std::string previous_core_info = "";
1009 for (int iter = 0;; ++iter) {
1010 // TODO(user): We are suboptimal here because we use for upper bound the
1011 // current best objective, not best_obj - 1. This code is not really used
1012 // but we should still fix it.
1013 const std::vector<Literal> assumptions = ReduceNodesAndExtractAssumptions(
1014 upper_bound, stratified_lower_bound, &lower_bound, &nodes, solver);
1015 if (assumptions.empty()) return SatSolver::FEASIBLE;
1016
1017 // Display the progress.
1018 const std::string gap_string =
1020 ? ""
1021 : absl::StrFormat(" gap:%d", (upper_bound - lower_bound).value());
1022 logger.Log(
1023 absl::StrFormat("c iter:%d [%s] lb:%d%s assumptions:%u depth:%d", iter,
1024 previous_core_info,
1025 lower_bound.value() - offset.value() +
1026 static_cast<int64_t>(problem.objective().offset()),
1027 gap_string, nodes.size(), max_depth));
1028
1029 // Solve under the assumptions.
1030 const SatSolver::Status result =
1031 solver->ResetAndSolveWithGivenAssumptions(assumptions);
1032 if (result == SatSolver::FEASIBLE) {
1033 // Extract the new solution and save it if it is the best found so far.
1034 std::vector<bool> temp_solution;
1035 ExtractAssignment(problem, *solver, &temp_solution);
1036 CHECK(IsAssignmentValid(problem, temp_solution));
1037 const Coefficient obj = ComputeObjectiveValue(problem, temp_solution);
1038 if (obj + offset < upper_bound) {
1039 *solution = temp_solution;
1040 logger.Log(CnfObjectiveLine(problem, obj));
1041 upper_bound = obj + offset;
1042 }
1043
1044 // If not all assumptions were taken, continue with a lower stratified
1045 // bound. Otherwise we have an optimal solution.
1046 stratified_lower_bound =
1047 MaxNodeWeightSmallerThan(nodes, stratified_lower_bound);
1048 if (stratified_lower_bound > 0) continue;
1049 return SatSolver::FEASIBLE;
1050 }
1051 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
1052
1053 // We have a new core.
1054 std::vector<Literal> core = solver->GetLastIncompatibleDecisions();
1055 if (parameters.minimize_core()) MinimizeCore(solver, &core);
1056
1057 // Compute the min weight of all the nodes in the core.
1058 // The lower bound will be increased by that much.
1059 const Coefficient min_weight = ComputeCoreMinWeight(nodes, core);
1060 previous_core_info =
1061 absl::StrFormat("core:%u mw:%d", core.size(), min_weight.value());
1062
1063 // Increase stratified_lower_bound according to the parameters.
1064 if (stratified_lower_bound < min_weight &&
1065 parameters.max_sat_stratification() ==
1066 SatParameters::STRATIFICATION_ASCENT) {
1067 stratified_lower_bound = min_weight;
1068 }
1069
1070 ProcessCore(core, min_weight, &repository, &nodes, solver);
1071 max_depth = std::max(max_depth, nodes.back()->depth());
1072 }
1073}
1074
1076 IntegerVariable objective_var,
1077 const std::function<void()>& feasible_solution_observer, Model* model) {
1078 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
1079 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1080 const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
1081
1082 // Simple linear scan algorithm to find the optimal.
1083 if (!sat_solver->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1084 while (true) {
1086 if (result != SatSolver::FEASIBLE) return result;
1087
1088 // The objective is the current lower bound of the objective_var.
1089 const IntegerValue objective = integer_trail->LowerBound(objective_var);
1090
1091 // We have a solution!
1092 if (feasible_solution_observer != nullptr) {
1093 feasible_solution_observer();
1094 }
1095 if (parameters.stop_after_first_solution()) {
1097 }
1098
1099 // Restrict the objective.
1100 sat_solver->Backtrack(0);
1101 if (!integer_trail->Enqueue(
1102 IntegerLiteral::LowerOrEqual(objective_var, objective - 1), {},
1103 {})) {
1104 return SatSolver::INFEASIBLE;
1105 }
1106 }
1107}
1108
1110 IntegerVariable objective_var,
1111 const std::function<void()>& feasible_solution_observer, Model* model) {
1112 const SatParameters old_params = *model->GetOrCreate<SatParameters>();
1113 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
1114 IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
1115 IntegerEncoder* integer_encoder = model->GetOrCreate<IntegerEncoder>();
1116
1117 // Set the requested conflict limit.
1118 {
1119 SatParameters new_params = old_params;
1120 new_params.set_max_number_of_conflicts(
1121 old_params.binary_search_num_conflicts());
1122 *model->GetOrCreate<SatParameters>() = new_params;
1123 }
1124
1125 // The assumption (objective <= value) for values in
1126 // [unknown_min, unknown_max] reached the conflict limit.
1127 bool loop = true;
1128 IntegerValue unknown_min = integer_trail->UpperBound(objective_var);
1129 IntegerValue unknown_max = integer_trail->LowerBound(objective_var);
1130 while (loop) {
1131 sat_solver->Backtrack(0);
1132 const IntegerValue lb = integer_trail->LowerBound(objective_var);
1133 const IntegerValue ub = integer_trail->UpperBound(objective_var);
1134 unknown_min = std::min(unknown_min, ub);
1135 unknown_max = std::max(unknown_max, lb);
1136
1137 // We first refine the lower bound and then the upper bound.
1138 IntegerValue target;
1139 if (lb < unknown_min) {
1140 target = lb + (unknown_min - lb) / 2;
1141 } else if (unknown_max < ub) {
1142 target = ub - (ub - unknown_max) / 2;
1143 } else {
1144 VLOG(1) << "Binary-search, done.";
1145 break;
1146 }
1147 VLOG(1) << "Binary-search, objective: [" << lb << "," << ub << "]"
1148 << " tried: [" << unknown_min << "," << unknown_max << "]"
1149 << " target: obj<=" << target;
1150 SatSolver::Status result;
1151 if (target < ub) {
1152 const Literal assumption = integer_encoder->GetOrCreateAssociatedLiteral(
1153 IntegerLiteral::LowerOrEqual(objective_var, target));
1154 result = ResetAndSolveIntegerProblem({assumption}, model);
1155 } else {
1156 result = ResetAndSolveIntegerProblem({}, model);
1157 }
1158
1159 switch (result) {
1160 case SatSolver::INFEASIBLE: {
1161 loop = false;
1162 break;
1163 }
1165 // Update the objective lower bound.
1166 sat_solver->Backtrack(0);
1167 if (!integer_trail->Enqueue(
1168 IntegerLiteral::GreaterOrEqual(objective_var, target + 1), {},
1169 {})) {
1170 loop = false;
1171 }
1172 break;
1173 }
1174 case SatSolver::FEASIBLE: {
1175 // The objective is the current lower bound of the objective_var.
1176 const IntegerValue objective = integer_trail->LowerBound(objective_var);
1177 if (feasible_solution_observer != nullptr) {
1178 feasible_solution_observer();
1179 }
1180
1181 // We have a solution, restrict the objective upper bound to only look
1182 // for better ones now.
1183 sat_solver->Backtrack(0);
1184 if (!integer_trail->Enqueue(
1185 IntegerLiteral::LowerOrEqual(objective_var, objective - 1), {},
1186 {})) {
1187 loop = false;
1188 }
1189 break;
1190 }
1192 unknown_min = std::min(target, unknown_min);
1193 unknown_max = std::max(target, unknown_max);
1194 break;
1195 }
1196 }
1197 }
1198
1199 sat_solver->Backtrack(0);
1200 *model->GetOrCreate<SatParameters>() = old_params;
1201}
1202
1203namespace {
1204
1205// If the given model is unsat under the given assumptions, returns one or more
1206// non-overlapping set of assumptions, each set making the problem infeasible on
1207// its own (the cores).
1208//
1209// In presence of weights, we "generalize" the notions of disjoints core using
1210// the WCE idea describe in "Weight-Aware Core Extraction in SAT-Based MaxSAT
1211// solving" Jeremias Berg And Matti Jarvisalo.
1212//
1213// The returned status can be either:
1214// - ASSUMPTIONS_UNSAT if the set of returned core perfectly cover the given
1215// assumptions, in this case, we don't bother trying to find a SAT solution
1216// with no assumptions.
1217// - FEASIBLE if after finding zero or more core we have a solution.
1218// - LIMIT_REACHED if we reached the time-limit before one of the two status
1219// above could be decided.
1220//
1221// TODO(user): There is many way to combine the WCE and stratification
1222// heuristics. I didn't had time to properly compare the different approach. See
1223// the WCE papers for some ideas, but there is many more ways to try to find a
1224// lot of core at once and try to minimize the minimum weight of each of the
1225// cores.
1226SatSolver::Status FindCores(std::vector<Literal> assumptions,
1227 std::vector<IntegerValue> assumption_weights,
1228 IntegerValue stratified_threshold, Model* model,
1229 std::vector<std::vector<Literal>>* cores) {
1230 cores->clear();
1231 SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
1232 TimeLimit* limit = model->GetOrCreate<TimeLimit>();
1233 do {
1234 if (limit->LimitReached()) return SatSolver::LIMIT_REACHED;
1235
1236 const SatSolver::Status result =
1237 ResetAndSolveIntegerProblem(assumptions, model);
1238 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
1239 std::vector<Literal> core = sat_solver->GetLastIncompatibleDecisions();
1240 if (sat_solver->parameters().minimize_core()) {
1241 MinimizeCoreWithPropagation(limit, sat_solver, &core);
1242 }
1243 if (core.size() == 1) {
1244 if (!sat_solver->AddUnitClause(core[0].Negated())) {
1245 return SatSolver::INFEASIBLE;
1246 }
1247 }
1248 if (core.empty()) return sat_solver->UnsatStatus();
1249 cores->push_back(core);
1250 if (!sat_solver->parameters().find_multiple_cores()) break;
1251
1252 // Recover the original indices of the assumptions that are part of the
1253 // core.
1254 std::vector<int> indices;
1255 {
1256 absl::btree_set<Literal> temp(core.begin(), core.end());
1257 for (int i = 0; i < assumptions.size(); ++i) {
1258 if (gtl::ContainsKey(temp, assumptions[i])) {
1259 indices.push_back(i);
1260 }
1261 }
1262 }
1263
1264 // Remove min_weight from the weights of all the assumptions in the core.
1265 //
1266 // TODO(user): push right away the objective bound by that much? This should
1267 // be better in a multi-threading context as we can share more quickly the
1268 // better bound.
1269 IntegerValue min_weight = assumption_weights[indices.front()];
1270 for (const int i : indices) {
1271 min_weight = std::min(min_weight, assumption_weights[i]);
1272 }
1273 for (const int i : indices) {
1274 assumption_weights[i] -= min_weight;
1275 }
1276
1277 // Remove from assumptions all the one with a new weight smaller than the
1278 // current stratification threshold and see if we can find another core.
1279 int new_size = 0;
1280 for (int i = 0; i < assumptions.size(); ++i) {
1281 if (assumption_weights[i] < stratified_threshold) continue;
1282 assumptions[new_size] = assumptions[i];
1283 assumption_weights[new_size] = assumption_weights[i];
1284 ++new_size;
1285 }
1286 assumptions.resize(new_size);
1287 assumption_weights.resize(new_size);
1288 } while (!assumptions.empty());
1290}
1291
1292} // namespace
1293
1295 IntegerVariable objective_var,
1296 const std::vector<IntegerVariable>& variables,
1297 const std::vector<IntegerValue>& coefficients,
1298 std::function<void()> feasible_solution_observer, Model* model)
1299 : parameters_(model->GetOrCreate<SatParameters>()),
1300 sat_solver_(model->GetOrCreate<SatSolver>()),
1301 time_limit_(model->GetOrCreate<TimeLimit>()),
1302 implications_(model->GetOrCreate<BinaryImplicationGraph>()),
1303 integer_trail_(model->GetOrCreate<IntegerTrail>()),
1304 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
1305 model_(model),
1306 objective_var_(objective_var),
1307 feasible_solution_observer_(std::move(feasible_solution_observer)) {
1308 CHECK_EQ(variables.size(), coefficients.size());
1309 for (int i = 0; i < variables.size(); ++i) {
1310 if (coefficients[i] > 0) {
1311 terms_.push_back({variables[i], coefficients[i]});
1312 } else if (coefficients[i] < 0) {
1313 terms_.push_back({NegationOf(variables[i]), -coefficients[i]});
1314 } else {
1315 continue; // coefficients[i] == 0
1316 }
1317 terms_.back().depth = 0;
1318 }
1319
1320 // This is used by the "stratified" approach. We will only consider terms with
1321 // a weight not lower than this threshold. The threshold will decrease as the
1322 // algorithm progress.
1323 stratification_threshold_ = parameters_->max_sat_stratification() ==
1324 SatParameters::STRATIFICATION_NONE
1325 ? IntegerValue(1)
1327}
1328
1329bool CoreBasedOptimizer::ProcessSolution() {
1330 // We don't assume that objective_var is linked with its linear term, so
1331 // we recompute the objective here.
1332 IntegerValue objective(0);
1333 for (ObjectiveTerm& term : terms_) {
1334 const IntegerValue value = integer_trail_->LowerBound(term.var);
1335 objective += term.weight * value;
1336
1337 // Also keep in term.cover_ub the minimum value for term.var that we have
1338 // seens amongst all the feasible solutions found so far.
1339 term.cover_ub = std::min(term.cover_ub, value);
1340 }
1341
1342 // We use the level zero upper bound of the objective to indicate an upper
1343 // limit for the solution objective we are looking for. Again, because the
1344 // objective_var is not assumed to be linked, it could take any value in the
1345 // current solution.
1346 if (objective > integer_trail_->LevelZeroUpperBound(objective_var_)) {
1347 return true;
1348 }
1349
1350 if (feasible_solution_observer_ != nullptr) {
1351 feasible_solution_observer_();
1352 }
1353 if (parameters_->stop_after_first_solution()) {
1354 stop_ = true;
1355 }
1356
1357 // Constrain objective_var. This has a better result when objective_var is
1358 // used in an LP relaxation for instance.
1359 sat_solver_->Backtrack(0);
1360 sat_solver_->SetAssumptionLevel(0);
1361 return integer_trail_->Enqueue(
1362 IntegerLiteral::LowerOrEqual(objective_var_, objective - 1), {}, {});
1363}
1364
1365bool CoreBasedOptimizer::PropagateObjectiveBounds() {
1366 // We assumes all terms (modulo stratification) at their lower-bound.
1367 bool some_bound_were_tightened = true;
1368 while (some_bound_were_tightened) {
1369 some_bound_were_tightened = false;
1370 if (!sat_solver_->ResetToLevelZero()) return false;
1371 if (time_limit_->LimitReached()) return true;
1372
1373 // Compute implied lb.
1374 IntegerValue implied_objective_lb(0);
1375 for (ObjectiveTerm& term : terms_) {
1376 const IntegerValue var_lb = integer_trail_->LowerBound(term.var);
1377 term.old_var_lb = var_lb;
1378 implied_objective_lb += term.weight * var_lb.value();
1379 }
1380
1381 // Update the objective lower bound with our current bound.
1382 if (implied_objective_lb > integer_trail_->LowerBound(objective_var_)) {
1383 if (!integer_trail_->Enqueue(IntegerLiteral::GreaterOrEqual(
1384 objective_var_, implied_objective_lb),
1385 {}, {})) {
1386 return false;
1387 }
1388
1389 some_bound_were_tightened = true;
1390 }
1391
1392 // The gap is used to propagate the upper-bound of all variable that are
1393 // in the current objective (Exactly like done in the propagation of a
1394 // linear constraint with the slack). When this fix a variable to its
1395 // lower bound, it is called "hardening" in the max-sat literature. This
1396 // has a really beneficial effect on some weighted max-sat problems like
1397 // the haplotyping-pedigrees ones.
1398 const IntegerValue gap =
1399 integer_trail_->UpperBound(objective_var_) - implied_objective_lb;
1400
1401 for (const ObjectiveTerm& term : terms_) {
1402 if (term.weight == 0) continue;
1403 const IntegerValue var_lb = integer_trail_->LowerBound(term.var);
1404 const IntegerValue var_ub = integer_trail_->UpperBound(term.var);
1405 if (var_lb == var_ub) continue;
1406
1407 // Hardening. This basically just propagate the implied upper bound on
1408 // term.var from the current best solution. Note that the gap is
1409 // non-negative and the weight positive here. The test is done in order
1410 // to avoid any integer overflow provided (ub - lb) do not overflow, but
1411 // this is a precondition in our cp-model.
1412 if (gap / term.weight < var_ub - var_lb) {
1413 some_bound_were_tightened = true;
1414 const IntegerValue new_ub = var_lb + gap / term.weight;
1415 DCHECK_LT(new_ub, var_ub);
1416 DCHECK(!integer_trail_->IsCurrentlyIgnored(term.var));
1417 if (!integer_trail_->Enqueue(
1418 IntegerLiteral::LowerOrEqual(term.var, new_ub), {}, {})) {
1419 return false;
1420 }
1421 }
1422 }
1423 }
1424 return true;
1425}
1426
1427// A basic algorithm is to take the next one, or at least the next one
1428// that invalidate the current solution. But to avoid corner cases for
1429// problem with a lot of terms all with different objective weights (in
1430// which case we will kind of introduce only one assumption per loop
1431// which is little), we use an heuristic and take the 90% percentile of
1432// the unique weights not yet included.
1433//
1434// TODO(user): There is many other possible heuristics here, and I
1435// didn't have the time to properly compare them.
1436void CoreBasedOptimizer::ComputeNextStratificationThreshold() {
1437 std::vector<IntegerValue> weights;
1438 for (ObjectiveTerm& term : terms_) {
1439 if (term.weight >= stratification_threshold_) continue;
1440 if (term.weight == 0) continue;
1441
1442 const IntegerValue var_lb = integer_trail_->LevelZeroLowerBound(term.var);
1443 const IntegerValue var_ub = integer_trail_->LevelZeroUpperBound(term.var);
1444 if (var_lb == var_ub) continue;
1445
1446 weights.push_back(term.weight);
1447 }
1448 if (weights.empty()) {
1449 stratification_threshold_ = IntegerValue(0);
1450 return;
1451 }
1452
1454 stratification_threshold_ =
1455 weights[static_cast<int>(std::floor(0.9 * weights.size()))];
1456}
1457
1458bool CoreBasedOptimizer::CoverOptimization() {
1459 if (!sat_solver_->ResetToLevelZero()) return false;
1460
1461 // We set a fix deterministic time limit per all sub-solve and skip to the
1462 // next core if the sum of the subsolve is also over this limit.
1463 constexpr double max_dtime_per_core = 0.5;
1464 const double old_time_limit = parameters_->max_deterministic_time();
1465 parameters_->set_max_deterministic_time(max_dtime_per_core);
1466 auto cleanup = ::absl::MakeCleanup([old_time_limit, this]() {
1467 parameters_->set_max_deterministic_time(old_time_limit);
1468 });
1469
1470 for (const ObjectiveTerm& term : terms_) {
1471 // We currently skip the initial objective terms as there could be many
1472 // of them. TODO(user): provide an option to cover-optimize them? I
1473 // fear that this will slow down the solver too much though.
1474 if (term.depth == 0) continue;
1475
1476 // Find out the true lower bound of var. This is called "cover
1477 // optimization" in some of the max-SAT literature. It can helps on some
1478 // problem families and hurt on others, but the overall impact is
1479 // positive.
1480 const IntegerVariable var = term.var;
1481 IntegerValue best =
1482 std::min(term.cover_ub, integer_trail_->UpperBound(var));
1483
1484 // Note(user): this can happen in some corner case because each time we
1485 // find a solution, we constrain the objective to be smaller than it, so
1486 // it is possible that a previous best is now infeasible.
1487 if (best <= integer_trail_->LowerBound(var)) continue;
1488
1489 // Compute the global deterministic time for this core cover
1490 // optimization.
1491 const double deterministic_limit =
1492 time_limit_->GetElapsedDeterministicTime() + max_dtime_per_core;
1493
1494 // Simple linear scan algorithm to find the optimal of var.
1495 SatSolver::Status result;
1496 while (best > integer_trail_->LowerBound(var)) {
1497 const Literal assumption = integer_encoder_->GetOrCreateAssociatedLiteral(
1499 result = ResetAndSolveIntegerProblem({assumption}, model_);
1500 if (result != SatSolver::FEASIBLE) break;
1501
1502 best = integer_trail_->LowerBound(var);
1503 VLOG(1) << "cover_opt var:" << var << " domain:["
1504 << integer_trail_->LevelZeroLowerBound(var) << "," << best << "]";
1505 if (!ProcessSolution()) return false;
1506 if (!sat_solver_->ResetToLevelZero()) return false;
1507 if (stop_ ||
1508 time_limit_->GetElapsedDeterministicTime() > deterministic_limit) {
1509 break;
1510 }
1511 }
1512 if (result == SatSolver::INFEASIBLE) return false;
1513 if (result == SatSolver::ASSUMPTIONS_UNSAT) {
1514 if (!sat_solver_->ResetToLevelZero()) return false;
1515
1516 // TODO(user): If we improve the lower bound of var, we should check
1517 // if our global lower bound reached our current best solution in
1518 // order to abort early if the optimal is proved.
1519 if (!integer_trail_->Enqueue(IntegerLiteral::GreaterOrEqual(var, best),
1520 {}, {})) {
1521 return false;
1522 }
1523 }
1524 }
1525
1526 return PropagateObjectiveBounds();
1527}
1528
1530 const std::vector<Literal>& literals,
1531 const std::vector<Coefficient>& coefficients, Coefficient offset) {
1532 // Create one initial nodes per variables with cost.
1533 // TODO(user): We could create EncodingNode out of IntegerVariable.
1534 //
1535 // Note that the nodes order and assumptions extracted from it will be stable.
1536 // In particular, new nodes will be appended at the end, which make the solver
1537 // more likely to find core involving only the first assumptions. This is
1538 // important at the beginning so the solver as a chance to find a lot of
1539 // non-overlapping small cores without the need to have dedicated
1540 // non-overlapping core finder.
1541 // TODO(user): It could still be beneficial to add one. Experiments.
1542 std::deque<EncodingNode> repository;
1543 Coefficient extra_offset = 0;
1544 std::vector<EncodingNode*> nodes = CreateInitialEncodingNodes(
1545 literals, coefficients, &extra_offset, &repository);
1546 CHECK_EQ(extra_offset, 0);
1547
1548 // Initialize the bounds.
1549 // This is in term of number of variables not at their minimal value.
1551
1552 // This is used by the "stratified" approach.
1553 // TODO(user): Take into account parameters.
1554 Coefficient stratified_lower_bound(0);
1555 for (EncodingNode* n : nodes) {
1556 stratified_lower_bound = std::max(stratified_lower_bound, n->weight());
1557 }
1558
1559 // Start the algorithm.
1560 int max_depth = 0;
1561 std::string previous_core_info = "";
1562 for (int iter = 0;;) {
1563 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
1564 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1565
1567 integer_trail_->UpperBound(objective_var_).value() - offset.value());
1568 const std::vector<Literal> assumptions = ReduceNodesAndExtractAssumptions(
1569 upper_bound, stratified_lower_bound, &lower_bound, &nodes, sat_solver_);
1570 if (assumptions.empty()) {
1571 stratified_lower_bound =
1572 MaxNodeWeightSmallerThan(nodes, stratified_lower_bound);
1573 if (stratified_lower_bound > 0) continue;
1574
1575 // We do not have any assumptions anymore, but we still need to see
1576 // if the problem is feasible or not!
1577 }
1578 const IntegerValue new_obj_lb(lower_bound.value() + offset.value());
1579 if (new_obj_lb > integer_trail_->LowerBound(objective_var_)) {
1580 if (!integer_trail_->Enqueue(
1581 IntegerLiteral::GreaterOrEqual(objective_var_, new_obj_lb), {},
1582 {})) {
1583 return SatSolver::INFEASIBLE;
1584 }
1585
1586 // Report the improvement.
1587 // Note that we have a callback that will do the same, but doing it
1588 // earlier allow us to add more information.
1589 const int num_bools = sat_solver_->NumVariables();
1590 const int num_fixed = sat_solver_->NumFixedVariables();
1591 model_->GetOrCreate<SharedResponseManager>()->UpdateInnerObjectiveBounds(
1592 absl::StrFormat("bool_core num_cores:%d [%s] assumptions:%u "
1593 "depth:%d fixed_bools:%d/%d",
1594 iter, previous_core_info, nodes.size(), max_depth,
1595 num_fixed, num_bools),
1596 new_obj_lb, integer_trail_->LevelZeroUpperBound(objective_var_));
1597 }
1598
1599 // Solve under the assumptions.
1600 //
1601 // TODO(user): Find multiple core like in the "main" algorithm.
1602 // this is just trying to solve with assumptions not involving the newly
1603 // found core.
1604 const SatSolver::Status result =
1605 ResetAndSolveIntegerProblem(assumptions, model_);
1606 if (result == SatSolver::FEASIBLE) {
1607 if (!ProcessSolution()) return SatSolver::INFEASIBLE;
1608 if (stop_) return SatSolver::LIMIT_REACHED;
1609
1610 // If not all assumptions were taken, continue with a lower stratified
1611 // bound. Otherwise we have an optimal solution.
1612 stratified_lower_bound =
1613 MaxNodeWeightSmallerThan(nodes, stratified_lower_bound);
1614 if (stratified_lower_bound > 0) continue;
1615 return SatSolver::INFEASIBLE;
1616 }
1617 if (result != SatSolver::ASSUMPTIONS_UNSAT) return result;
1618
1619 // We have a new core.
1620 std::vector<Literal> core = sat_solver_->GetLastIncompatibleDecisions();
1621 if (parameters_->minimize_core()) {
1622 MinimizeCoreWithPropagation(time_limit_, sat_solver_, &core);
1623 }
1624
1625 // Compute the min weight of all the nodes in the core.
1626 // The lower bound will be increased by that much.
1627 const Coefficient min_weight = ComputeCoreMinWeight(nodes, core);
1628 previous_core_info =
1629 absl::StrFormat("core:%u mw:%d d:%d", core.size(), min_weight.value(),
1630 nodes.back()->depth());
1631
1632 // We only count an iter when we found a core.
1633 ++iter;
1634 if (!ProcessCore(core, min_weight, &repository, &nodes, sat_solver_)) {
1635 return SatSolver::INFEASIBLE;
1636 }
1637 max_depth = std::max(max_depth, nodes.back()->depth());
1638 }
1639
1640 return SatSolver::FEASIBLE; // shouldn't reach here.
1641}
1642
1643void CoreBasedOptimizer::PresolveObjectiveWithAtMostOne(
1644 std::vector<Literal>* literals, std::vector<Coefficient>* coefficients,
1645 Coefficient* offset) {
1646 // This contains non-negative value. If a literal has negative weight, then
1647 // we just put a positive weight on its negation and update the offset.
1648 const int num_literals = implications_->literal_size();
1650 absl::StrongVector<LiteralIndex, bool> is_candidate(num_literals);
1651
1652 // For now, we do not use weight. Note that finding the at most on in the
1653 // creation order of the variable make a HUGE difference on the max-sat frb
1654 // family.
1655 //
1656 // TODO(user): We can assign preferences to literals to favor certain at most
1657 // one instead of other. For now we don't, so ExpandAtMostOneWithWeight() will
1658 // kind of randomize the expansion amongst possible choices.
1660
1661 // Collect all literals with "negative weights", we will try to find at most
1662 // one between them.
1663 std::vector<Literal> candidates;
1664 const int num_terms = literals->size();
1665 for (int i = 0; i < num_terms; ++i) {
1666 const Literal lit = (*literals)[i];
1667 const Coefficient coeff = (*coefficients)[i];
1668
1669 // For now we know the input only has positive weight, but it is easy to
1670 // adapt if needed.
1671 CHECK_GT(coeff, 0);
1672 weights[lit.Index()] = coeff;
1673
1674 candidates.push_back(lit.Negated());
1675 is_candidate[lit.NegatedIndex()] = true;
1676 }
1677
1678 int num_at_most_ones = 0;
1679 Coefficient overall_lb_increase(0);
1680
1681 std::vector<Literal> at_most_one;
1682 std::vector<std::pair<Literal, Coefficient>> new_obj_terms;
1683 implications_->ResetWorkDone();
1684 for (const Literal root : candidates) {
1685 if (weights[root.NegatedIndex()] == 0) continue;
1686 if (implications_->WorkDone() > 1e8) continue;
1687
1688 // We never put weight on both a literal and its negation.
1689 CHECK_EQ(weights[root.Index()], 0);
1690
1691 // Note that for this to be as exhaustive as possible, the probing needs
1692 // to have added binary clauses corresponding to lvl0 propagation.
1693 at_most_one =
1694 implications_->ExpandAtMostOneWithWeight</*use_weight=*/false>(
1695 {root}, is_candidate, preferences);
1696 if (at_most_one.size() <= 1) continue;
1697 ++num_at_most_ones;
1698
1699 // Change the objective weights. Note that all the literal in the at most
1700 // one will not be processed again since the weight of their negation will
1701 // be zero after this step.
1702 Coefficient max_coeff(0);
1703 Coefficient lb_increase(0);
1704 for (const Literal lit : at_most_one) {
1705 const Coefficient coeff = weights[lit.NegatedIndex()];
1706 lb_increase += coeff;
1707 max_coeff = std::max(max_coeff, coeff);
1708 }
1709 lb_increase -= max_coeff;
1710
1711 *offset += lb_increase;
1712 overall_lb_increase += lb_increase;
1713
1714 for (const Literal lit : at_most_one) {
1715 is_candidate[lit.Index()] = false;
1716 const Coefficient new_weight = max_coeff - weights[lit.NegatedIndex()];
1717 CHECK_EQ(weights[lit.Index()], 0);
1718 weights[lit.Index()] = new_weight;
1719 weights[lit.NegatedIndex()] = 0;
1720 if (new_weight > 0) {
1721 // TODO(user): While we autorize this to be in future at most one, it
1722 // will not appear in the "literal" list. We might also want to continue
1723 // until we reached the fix point.
1724 is_candidate[lit.NegatedIndex()] = true;
1725 }
1726 }
1727
1728 // Create a new Boolean with weight max_coeff.
1729 const Literal new_lit(sat_solver_->NewBooleanVariable(), true);
1730 new_obj_terms.push_back({new_lit, max_coeff});
1731
1732 // The new boolean is true only if all the one in the at most one are false.
1733 at_most_one.push_back(new_lit);
1734 sat_solver_->AddProblemClause(at_most_one);
1735 is_candidate.resize(implications_->literal_size(), false);
1736 preferences.resize(implications_->literal_size(), 1.0);
1737 }
1738
1739 if (overall_lb_increase > 0) {
1740 // Report new bounds right away with extra information.
1741 model_->GetOrCreate<SharedResponseManager>()->UpdateInnerObjectiveBounds(
1742 absl::StrFormat("am1_presolve num_literals:%d num_am1:%d "
1743 "increase:%lld work_done:%lld",
1744 (int)candidates.size(), num_at_most_ones,
1745 overall_lb_increase.value(), implications_->WorkDone()),
1746 IntegerValue(offset->value()),
1747 integer_trail_->LevelZeroUpperBound(objective_var_));
1748 }
1749
1750 // Reconstruct the objective.
1751 literals->clear();
1752 coefficients->clear();
1753 for (const Literal root : candidates) {
1754 if (weights[root.Index()] > 0) {
1755 CHECK_EQ(weights[root.NegatedIndex()], 0);
1756 literals->push_back(root);
1757 coefficients->push_back(weights[root.Index()]);
1758 }
1759 if (weights[root.NegatedIndex()] > 0) {
1760 CHECK_EQ(weights[root.Index()], 0);
1761 literals->push_back(root.Negated());
1762 coefficients->push_back(weights[root.NegatedIndex()]);
1763 }
1764 }
1765 for (const auto& [lit, coeff] : new_obj_terms) {
1766 literals->push_back(lit);
1767 coefficients->push_back(coeff);
1768 }
1769}
1770
1772 // Hack: If the objective is fully Boolean, we use the
1773 // OptimizeWithSatEncoding() version as it seems to be better.
1774 //
1775 // TODO(user): Try to understand exactly why and merge both code path.
1776 if (!parameters_->interleave_search()) {
1777 Coefficient offset(0);
1778 std::vector<Literal> literals;
1779 std::vector<Coefficient> coefficients;
1780 bool all_booleans = true;
1781 for (const ObjectiveTerm& term : terms_) {
1782 const IntegerVariable var = term.var;
1783 const IntegerValue coeff = term.weight;
1784 const IntegerValue lb = integer_trail_->LowerBound(var);
1785 const IntegerValue ub = integer_trail_->UpperBound(var);
1786 if (lb == ub) {
1787 offset += Coefficient((lb * coeff).value());
1788 } else if (ub - lb == 1) {
1789 offset += Coefficient((lb * coeff).value());
1790 literals.push_back(integer_encoder_->GetOrCreateAssociatedLiteral(
1792 coefficients.push_back(Coefficient(coeff.value()));
1793 } else {
1794 all_booleans = false;
1795 break;
1796 }
1797 }
1798 if (all_booleans) {
1799 // TODO(user): It might be interesting to redo this kind of presolving
1800 // once high cost booleans have been fixed as we might have more at most
1801 // one between literal in the objective by then.
1802 //
1803 // Or alternatively, we could try this or something like it on the
1804 // literals from the cores as they are found. We should probably make
1805 // sure that if it exist, a core of size two is always added. And for
1806 // such core, we can always try to see if the "at most one" can be
1807 // extended.
1808 PresolveObjectiveWithAtMostOne(&literals, &coefficients, &offset);
1809 return OptimizeWithSatEncoding(literals, coefficients, offset);
1810 }
1811 }
1812
1813 // TODO(user): The core is returned in the same order as the assumptions,
1814 // so we don't really need this map, we could just do a linear scan to
1815 // recover which node are part of the core. This however needs to be properly
1816 // unit tested before usage.
1817 absl::btree_map<LiteralIndex, int> literal_to_term_index;
1818
1819 // Start the algorithm.
1820 stop_ = false;
1821 while (true) {
1822 // TODO(user): This always resets the solver to level zero.
1823 // Because of that we don't resume a solve in "chunk" perfectly. Fix.
1824 if (!PropagateObjectiveBounds()) return SatSolver::INFEASIBLE;
1825 if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
1826
1827 // Bulk cover optimization.
1828 //
1829 // TODO(user): If the search is aborted during this phase and we solve in
1830 // "chunk", we don't resume perfectly from where it was. Fix.
1831 if (parameters_->cover_optimization()) {
1832 if (!CoverOptimization()) return SatSolver::INFEASIBLE;
1833 if (stop_) return SatSolver::LIMIT_REACHED;
1834 }
1835
1836 // We assumes all terms (modulo stratification) at their lower-bound.
1837 std::vector<int> term_indices;
1838 std::vector<IntegerLiteral> integer_assumptions;
1839 std::vector<IntegerValue> assumption_weights;
1840 IntegerValue objective_offset(0);
1841 bool some_assumptions_were_skipped = false;
1842 for (int i = 0; i < terms_.size(); ++i) {
1843 const ObjectiveTerm term = terms_[i];
1844
1845 // TODO(user): These can be simply removed from the list.
1846 if (term.weight == 0) continue;
1847
1848 // Skip fixed terms.
1849 // We still keep them around for a proper lower-bound computation.
1850 //
1851 // TODO(user): we could keep an objective offset instead.
1852 const IntegerValue var_lb = integer_trail_->LowerBound(term.var);
1853 const IntegerValue var_ub = integer_trail_->UpperBound(term.var);
1854 if (var_lb == var_ub) {
1855 objective_offset += term.weight * var_lb.value();
1856 continue;
1857 }
1858
1859 // Only consider the terms above the threshold.
1860 if (term.weight >= stratification_threshold_) {
1861 integer_assumptions.push_back(
1862 IntegerLiteral::LowerOrEqual(term.var, var_lb));
1863 assumption_weights.push_back(term.weight);
1864 term_indices.push_back(i);
1865 } else {
1866 some_assumptions_were_skipped = true;
1867 }
1868 }
1869
1870 // No assumptions with the current stratification? use the next one.
1871 if (term_indices.empty() && some_assumptions_were_skipped) {
1872 ComputeNextStratificationThreshold();
1873 continue;
1874 }
1875
1876 // If there is only one or two assumptions left, we switch the algorithm.
1877 if (term_indices.size() <= 2 && !some_assumptions_were_skipped) {
1878 VLOG(1) << "Switching to linear scan...";
1879 if (!already_switched_to_linear_scan_) {
1880 already_switched_to_linear_scan_ = true;
1881 std::vector<IntegerVariable> constraint_vars;
1882 std::vector<int64_t> constraint_coeffs;
1883 for (const int index : term_indices) {
1884 constraint_vars.push_back(terms_[index].var);
1885 constraint_coeffs.push_back(terms_[index].weight.value());
1886 }
1887 constraint_vars.push_back(objective_var_);
1888 constraint_coeffs.push_back(-1);
1889 model_->Add(WeightedSumLowerOrEqual(constraint_vars, constraint_coeffs,
1890 -objective_offset.value()));
1891 }
1892
1894 objective_var_, feasible_solution_observer_, model_);
1895 }
1896
1897 // Display the progress.
1898 if (VLOG_IS_ON(1)) {
1899 int max_depth = 0;
1900 for (const ObjectiveTerm& term : terms_) {
1901 max_depth = std::max(max_depth, term.depth);
1902 }
1903 const int64_t lb = integer_trail_->LowerBound(objective_var_).value();
1904 const int64_t ub = integer_trail_->UpperBound(objective_var_).value();
1905 const int gap =
1906 lb == ub
1907 ? 0
1908 : static_cast<int>(std::ceil(
1909 100.0 * (ub - lb) / std::max(std::abs(ub), std::abs(lb))));
1910 VLOG(1) << absl::StrCat("unscaled_next_obj_range:[", lb, ",", ub,
1911 "]"
1912 " gap:",
1913 gap, "%", " assumptions:", term_indices.size(),
1914 " strat:", stratification_threshold_.value(),
1915 " depth:", max_depth,
1916 " bool: ", sat_solver_->NumVariables());
1917 }
1918
1919 // Convert integer_assumptions to Literals.
1920 std::vector<Literal> assumptions;
1921 literal_to_term_index.clear();
1922 for (int i = 0; i < integer_assumptions.size(); ++i) {
1923 assumptions.push_back(integer_encoder_->GetOrCreateAssociatedLiteral(
1924 integer_assumptions[i]));
1925
1926 // Tricky: In some rare case, it is possible that the same literal
1927 // correspond to more that one assumptions. In this case, we can just
1928 // pick one of them when converting back a core to term indices.
1929 //
1930 // TODO(user): We can probably be smarter about the cost of the
1931 // assumptions though.
1932 literal_to_term_index[assumptions.back().Index()] = term_indices[i];
1933 }
1934
1935 // Solve under the assumptions.
1936 //
1937 // TODO(user): If the "search" is interrupted while computing cores, we
1938 // currently do not resume it flawlessly. We however add any cores we found
1939 // before aborting.
1940 std::vector<std::vector<Literal>> cores;
1941 const SatSolver::Status result =
1942 FindCores(assumptions, assumption_weights, stratification_threshold_,
1943 model_, &cores);
1944 if (result == SatSolver::INFEASIBLE) return SatSolver::INFEASIBLE;
1945 if (result == SatSolver::FEASIBLE) {
1946 if (!ProcessSolution()) return SatSolver::INFEASIBLE;
1947 if (stop_) return SatSolver::LIMIT_REACHED;
1948 if (cores.empty()) {
1949 ComputeNextStratificationThreshold();
1950 if (stratification_threshold_ == 0) return SatSolver::INFEASIBLE;
1951 continue;
1952 }
1953 }
1954
1955 // Process the cores by creating new variables and transferring the minimum
1956 // weight of each core to it.
1957 if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1958 for (const std::vector<Literal>& core : cores) {
1959 // This just increase the lower-bound of the corresponding node.
1960 // TODO(user): Maybe the solver should do it right away.
1961 if (core.size() == 1) {
1962 if (!sat_solver_->AddUnitClause(core[0].Negated())) {
1963 return SatSolver::INFEASIBLE;
1964 }
1965 continue;
1966 }
1967
1968 // Compute the min weight of all the terms in the core. The lower bound
1969 // will be increased by that much because at least one assumption in the
1970 // core must be true. This is also why we can start at 1 for new_var_lb.
1971 bool ignore_this_core = false;
1972 IntegerValue min_weight = kMaxIntegerValue;
1973 IntegerValue max_weight(0);
1974 IntegerValue new_var_lb(1);
1975 IntegerValue new_var_ub(0);
1976 int new_depth = 0;
1977 for (const Literal lit : core) {
1978 const int index = gtl::FindOrDie(literal_to_term_index, lit.Index());
1979
1980 // When this happen, the core is now trivially "minimized" by the new
1981 // bound on this variable, so there is no point in adding it.
1982 if (terms_[index].old_var_lb <
1983 integer_trail_->LowerBound(terms_[index].var)) {
1984 ignore_this_core = true;
1985 break;
1986 }
1987
1988 const IntegerValue weight = terms_[index].weight;
1989 min_weight = std::min(min_weight, weight);
1990 max_weight = std::max(max_weight, weight);
1991 new_depth = std::max(new_depth, terms_[index].depth + 1);
1992 new_var_lb += integer_trail_->LowerBound(terms_[index].var);
1993 new_var_ub += integer_trail_->UpperBound(terms_[index].var);
1994 }
1995 if (ignore_this_core) continue;
1996
1997 VLOG(1) << absl::StrFormat(
1998 "core:%u weight:[%d,%d] domain:[%d,%d] depth:%d", core.size(),
1999 min_weight.value(), max_weight.value(), new_var_lb.value(),
2000 new_var_ub.value(), new_depth);
2001
2002 // We will "transfer" min_weight from all the variables of the core
2003 // to a new variable.
2004 const IntegerVariable new_var =
2005 integer_trail_->AddIntegerVariable(new_var_lb, new_var_ub);
2006 terms_.push_back({new_var, min_weight, new_depth});
2007 terms_.back().cover_ub = new_var_ub;
2008
2009 // Sum variables in the core <= new_var.
2010 {
2011 std::vector<IntegerVariable> constraint_vars;
2012 std::vector<int64_t> constraint_coeffs;
2013 for (const Literal lit : core) {
2014 const int index = gtl::FindOrDie(literal_to_term_index, lit.Index());
2015 terms_[index].weight -= min_weight;
2016 constraint_vars.push_back(terms_[index].var);
2017 constraint_coeffs.push_back(1);
2018 }
2019 constraint_vars.push_back(new_var);
2020 constraint_coeffs.push_back(-1);
2021 model_->Add(
2022 WeightedSumLowerOrEqual(constraint_vars, constraint_coeffs, 0));
2023 }
2024 }
2025
2026 // Abort if we reached the time limit. Note that we still add any cores we
2027 // found in case the solve is split in "chunk".
2028 if (result == SatSolver::LIMIT_REACHED) return result;
2029 }
2030}
2031
2032} // namespace sat
2033} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
#define CHECK_NE(val1, val2)
Definition: base/logging.h:704
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
#define VLOG(verboselevel)
Definition: base/logging.h:984
void resize(size_type new_size)
size_type size() const
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:546
double GetElapsedDeterministicTime() const
Returns the elapsed deterministic time since the construction of this object.
Definition: time_limit.h:261
std::vector< Literal > ExpandAtMostOneWithWeight(const absl::Span< const Literal > at_most_one, const absl::StrongVector< LiteralIndex, bool > &can_be_included, const absl::StrongVector< LiteralIndex, double > &expanded_lp_values)
Definition: clause.cc:1639
CoreBasedOptimizer(IntegerVariable objective_var, const std::vector< IntegerVariable > &variables, const std::vector< IntegerValue > &coefficients, std::function< void()> feasible_solution_observer, Model *model)
SatSolver::Status OptimizeWithSatEncoding(const std::vector< Literal > &literals, const std::vector< Coefficient > &coefficients, Coefficient offset)
Literal literal(int i) const
Definition: encoding.h:84
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
Definition: integer.cc:238
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1048
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:705
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1449
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1534
IntegerVariable AddIntegerVariable(IntegerValue lower_bound, IntegerValue upper_bound)
Definition: integer.cc:643
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1529
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1445
LiteralIndex NegatedIndex() const
Definition: sat_base.h:88
LiteralIndex Index() const
Definition: sat_base.h:87
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
T Add(std::function< T(Model *)> f)
This makes it possible to have a nicer API on the client side, and it allows both of these forms:
Definition: sat/model.h:85
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
Definition: sat_solver.cc:343
void SetNumVariables(int num_variables)
Definition: sat_solver.cc:85
bool AddTernaryClause(Literal a, Literal b, Literal c)
Definition: sat_solver.cc:193
const SatParameters & parameters() const
Definition: sat_solver.cc:131
Status ResetAndSolveWithGivenAssumptions(const std::vector< Literal > &assumptions)
Definition: sat_solver.cc:1047
BooleanVariable NewBooleanVariable()
Definition: sat_solver.h:87
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:378
void SetAssumptionLevel(int assumption_level)
Definition: sat_solver.cc:1060
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
Definition: sat_solver.cc:536
void SetParameters(const SatParameters &parameters)
Definition: sat_solver.cc:136
bool AddBinaryClause(Literal a, Literal b)
Definition: sat_solver.cc:189
int EnqueueDecisionAndBacktrackOnConflict(Literal true_literal)
Definition: sat_solver.cc:963
void Backtrack(int target_level)
Definition: sat_solver.cc:991
std::vector< Literal > GetLastIncompatibleDecisions()
Definition: sat_solver.cc:1375
bool AddProblemClause(absl::Span< const Literal > literals)
Definition: sat_solver.cc:202
bool AddUnitClause(Literal true_literal)
Definition: sat_solver.cc:185
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:153
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:150
int64_t b
int64_t a
SatParameters parameters
ModelSharedTimeLimit * time_limit
int64_t value
IntVar * var
Definition: expr_array.cc:1874
double upper_bound
double lower_bound
absl::Span< const double > coefficients
GRBmodel * model
int index
const int INFO
Definition: log_severity.h:31
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:29
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
std::tuple< int64_t, int64_t, const double > Coefficient
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Definition: sat/util.cc:59
bool AddObjectiveConstraint(const LinearBooleanProblem &problem, bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, SatSolver *solver)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void RestrictObjectiveDomainWithBinarySearch(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
double AddOffsetAndScaleObjectiveValue(const LinearBooleanProblem &problem, Coefficient v)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
SatSolver::Status SolveWithCardinalityEncodingAndCore(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
Coefficient ComputeCoreMinWeight(const std::vector< EncodingNode * > &nodes, const std::vector< Literal > &core)
Definition: encoding.cc:501
EncodingNode * MergeAllNodesWithDeque(Coefficient upper_bound, const std::vector< EncodingNode * > &nodes, SatSolver *solver, std::deque< EncodingNode > *repository)
Definition: encoding.cc:335
std::function< void(Model *)> WeightedSumLowerOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t upper_bound)
Definition: integer_expr.h:367
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition: integer.h:1663
std::vector< Literal > ReduceNodesAndExtractAssumptions(Coefficient upper_bound, Coefficient stratified_lower_bound, Coefficient *lower_bound, std::vector< EncodingNode * > *nodes, SatSolver *solver)
Definition: encoding.cc:447
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
SatSolver::Status SolveWithLinearScan(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
SatSolver::Status SolveWithRandomParameters(LogBehavior log, const LinearBooleanProblem &problem, int num_times, absl::BitGenRef random, SatSolver *solver, std::vector< bool > *solution)
void MinimizeCore(SatSolver *solver, std::vector< Literal > *core)
Definition: sat_solver.cc:2655
SatSolver::Status SolveIntegerProblem(Model *model)
SatSolver::Status SolveWithWPM1(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
bool IsAssignmentValid(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:47
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
Coefficient ComputeObjectiveValue(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
SatSolver::Status SolveWithFuMalik(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
bool ProcessCore(const std::vector< Literal > &core, Coefficient min_weight, std::deque< EncodingNode > *repository, std::vector< EncodingNode * > *nodes, SatSolver *solver)
Definition: encoding.cc:528
Coefficient MaxNodeWeightSmallerThan(const std::vector< EncodingNode * > &nodes, Coefficient upper_bound)
Definition: encoding.cc:516
int MoveOneUnprocessedLiteralLast(const absl::btree_set< LiteralIndex > &processed, int relevant_prefix_size, std::vector< Literal > *literals)
Definition: sat/util.cc:282
SatSolver::Status SolveWithCardinalityEncoding(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
void ExtractAssignment(const LinearBooleanProblem &problem, const SatSolver &solver, std::vector< bool > *assignment)
std::vector< EncodingNode * > CreateInitialEncodingNodes(const std::vector< Literal > &literals, const std::vector< Coefficient > &coeffs, Coefficient *offset, std::deque< EncodingNode > *repository)
Definition: encoding.cc:385
const Coefficient kCoefficientMax(std::numeric_limits< Coefficient::ValueType >::max())
SatSolver::Status MinimizeIntegerVariableWithLinearScanAndLazyEncoding(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
Collection of objects used to extend the Constraint Solver library.
std::string ProtobufShortDebugString(const P &message)
STL namespace.
int core_index
Definition: optimization.cc:90
Literal literal
Definition: optimization.cc:89
int64_t weight
Definition: pack.cc:510
int64_t cost
int nodes
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1393
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1387
std::string message
Definition: trace.cc:398
const double coeff
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:44