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