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