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