OR-Tools  9.2
bop_ls.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/bop/bop_ls.h"
15 
16 #include <cstdint>
17 #include <limits>
18 
19 #include "absl/memory/memory.h"
20 #include "absl/strings/str_format.h"
22 #include "ortools/bop/bop_util.h"
24 
25 namespace operations_research {
26 namespace bop {
27 
28 using ::operations_research::sat::LinearBooleanConstraint;
29 using ::operations_research::sat::LinearBooleanProblem;
30 using ::operations_research::sat::LinearObjective;
31 
32 //------------------------------------------------------------------------------
33 // LocalSearchOptimizer
34 //------------------------------------------------------------------------------
35 
37  int max_num_decisions,
38  absl::BitGenRef random,
39  sat::SatSolver* sat_propagator)
41  state_update_stamp_(ProblemState::kInitialStampValue),
42  max_num_decisions_(max_num_decisions),
43  sat_wrapper_(sat_propagator),
44  assignment_iterator_(),
45  random_(random) {}
46 
48 
49 bool LocalSearchOptimizer::ShouldBeRun(
50  const ProblemState& problem_state) const {
51  return problem_state.solution().IsFeasible();
52 }
53 
54 BopOptimizerBase::Status LocalSearchOptimizer::Optimize(
55  const BopParameters& parameters, const ProblemState& problem_state,
56  LearnedInfo* learned_info, TimeLimit* time_limit) {
57  CHECK(learned_info != nullptr);
58  CHECK(time_limit != nullptr);
59  learned_info->Clear();
60 
61  if (assignment_iterator_ == nullptr) {
62  assignment_iterator_ = absl::make_unique<LocalSearchAssignmentIterator>(
63  problem_state, max_num_decisions_,
64  parameters.max_num_broken_constraints_in_ls(), random_, &sat_wrapper_);
65  }
66 
67  if (state_update_stamp_ != problem_state.update_stamp()) {
68  // We have a new problem_state.
69  state_update_stamp_ = problem_state.update_stamp();
70  assignment_iterator_->Synchronize(problem_state);
71  }
72  assignment_iterator_->SynchronizeSatWrapper();
73 
74  double prev_deterministic_time = assignment_iterator_->deterministic_time();
75  assignment_iterator_->UseTranspositionTable(
76  parameters.use_transposition_table_in_ls());
77  assignment_iterator_->UsePotentialOneFlipRepairs(
78  parameters.use_potential_one_flip_repairs_in_ls());
79  int64_t num_assignments_to_explore =
80  parameters.max_number_of_explored_assignments_per_try_in_ls();
81 
82  while (!time_limit->LimitReached() && num_assignments_to_explore > 0 &&
83  assignment_iterator_->NextAssignment()) {
84  time_limit->AdvanceDeterministicTime(
85  assignment_iterator_->deterministic_time() - prev_deterministic_time);
86  prev_deterministic_time = assignment_iterator_->deterministic_time();
87  --num_assignments_to_explore;
88  }
89  if (sat_wrapper_.IsModelUnsat()) {
90  // TODO(user): we do that all the time, return an UNSAT satus instead and
91  // do this only once.
92  return problem_state.solution().IsFeasible()
95  }
96 
97  // TODO(user): properly abort when we found a new solution and then finished
98  // the ls? note that this is minor.
99  sat_wrapper_.ExtractLearnedInfo(learned_info);
100  if (assignment_iterator_->BetterSolutionHasBeenFound()) {
101  // TODO(user): simply use vector<bool> instead of a BopSolution internally.
102  learned_info->solution = assignment_iterator_->LastReferenceAssignment();
104  }
105 
106  if (time_limit->LimitReached()) {
107  // The time limit is reached without finding a solution.
109  }
110 
111  if (num_assignments_to_explore <= 0) {
112  // Explore the remaining assignments in a future call to Optimize().
114  }
115 
116  // All assignments reachable in max_num_decisions_ or less have been explored,
117  // don't call optimize() with the same initial solution again.
119 }
120 
121 //------------------------------------------------------------------------------
122 // BacktrackableIntegerSet
123 //------------------------------------------------------------------------------
124 
125 template <typename IntType>
127  size_ = 0;
128  saved_sizes_.clear();
129  saved_stack_sizes_.clear();
130  stack_.clear();
131  in_stack_.assign(n.value(), false);
132 }
133 
134 template <typename IntType>
136  bool should_be_inside) {
137  size_ += should_be_inside ? 1 : -1;
138  if (!in_stack_[i.value()]) {
139  in_stack_[i.value()] = true;
140  stack_.push_back(i);
141  }
142 }
143 
144 template <typename IntType>
146  saved_stack_sizes_.push_back(stack_.size());
147  saved_sizes_.push_back(size_);
148 }
149 
150 template <typename IntType>
152  if (saved_stack_sizes_.empty()) {
153  BacktrackAll();
154  } else {
155  for (int i = saved_stack_sizes_.back(); i < stack_.size(); ++i) {
156  in_stack_[stack_[i].value()] = false;
157  }
158  stack_.resize(saved_stack_sizes_.back());
159  saved_stack_sizes_.pop_back();
160  size_ = saved_sizes_.back();
161  saved_sizes_.pop_back();
162  }
163 }
164 
165 template <typename IntType>
167  for (int i = 0; i < stack_.size(); ++i) {
168  in_stack_[stack_[i].value()] = false;
169  }
170  stack_.clear();
171  saved_stack_sizes_.clear();
172  size_ = 0;
173  saved_sizes_.clear();
174 }
175 
176 // Explicit instantiation of BacktrackableIntegerSet.
177 // TODO(user): move the code in a separate .h and -inl.h to avoid this.
179 
180 //------------------------------------------------------------------------------
181 // AssignmentAndConstraintFeasibilityMaintainer
182 //------------------------------------------------------------------------------
183 
186  const LinearBooleanProblem& problem, absl::BitGenRef random)
187  : by_variable_matrix_(problem.num_variables()),
188  constraint_lower_bounds_(),
189  constraint_upper_bounds_(),
190  assignment_(problem, "Assignment"),
191  reference_(problem, "Assignment"),
192  constraint_values_(),
193  flipped_var_trail_backtrack_levels_(),
194  flipped_var_trail_(),
195  constraint_set_hasher_(random) {
196  // Add the objective constraint as the first constraint.
197  const LinearObjective& objective = problem.objective();
198  CHECK_EQ(objective.literals_size(), objective.coefficients_size());
199  for (int i = 0; i < objective.literals_size(); ++i) {
200  CHECK_GT(objective.literals(i), 0);
201  CHECK_NE(objective.coefficients(i), 0);
202 
203  const VariableIndex var(objective.literals(i) - 1);
204  const int64_t weight = objective.coefficients(i);
205  by_variable_matrix_[var].push_back(
206  ConstraintEntry(kObjectiveConstraint, weight));
207  }
208  constraint_lower_bounds_.push_back(std::numeric_limits<int64_t>::min());
209  constraint_values_.push_back(0);
210  constraint_upper_bounds_.push_back(std::numeric_limits<int64_t>::max());
211 
212  // Add each constraint.
213  ConstraintIndex num_constraints_with_objective(1);
214  for (const LinearBooleanConstraint& constraint : problem.constraints()) {
215  if (constraint.literals_size() <= 2) {
216  // Infeasible binary constraints are automatically repaired by propagation
217  // (when possible). Then there are no needs to consider the binary
218  // constraints here, the propagation is delegated to the SAT propagator.
219  continue;
220  }
221 
222  CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
223  for (int i = 0; i < constraint.literals_size(); ++i) {
224  const VariableIndex var(constraint.literals(i) - 1);
225  const int64_t weight = constraint.coefficients(i);
226  by_variable_matrix_[var].push_back(
227  ConstraintEntry(num_constraints_with_objective, weight));
228  }
229  constraint_lower_bounds_.push_back(
230  constraint.has_lower_bound() ? constraint.lower_bound()
232  constraint_values_.push_back(0);
233  constraint_upper_bounds_.push_back(
234  constraint.has_upper_bound() ? constraint.upper_bound()
236 
237  ++num_constraints_with_objective;
238  }
239 
240  // Initialize infeasible_constraint_set_;
241  infeasible_constraint_set_.ClearAndResize(
242  ConstraintIndex(constraint_values_.size()));
243 
244  CHECK_EQ(constraint_values_.size(), constraint_lower_bounds_.size());
245  CHECK_EQ(constraint_values_.size(), constraint_upper_bounds_.size());
246 }
247 
248 const ConstraintIndex
250 
252  const BopSolution& reference_solution) {
253  CHECK(reference_solution.IsFeasible());
254  infeasible_constraint_set_.BacktrackAll();
255 
256  assignment_ = reference_solution;
257  reference_ = assignment_;
258  flipped_var_trail_backtrack_levels_.clear();
259  flipped_var_trail_.clear();
260  AddBacktrackingLevel(); // To handle initial propagation.
261 
262  // Recompute the value of all constraints.
263  constraint_values_.assign(NumConstraints(), 0);
264  for (VariableIndex var(0); var < assignment_.Size(); ++var) {
265  if (assignment_.Value(var)) {
266  for (const ConstraintEntry& entry : by_variable_matrix_[var]) {
267  constraint_values_[entry.constraint] += entry.weight;
268  }
269  }
270  }
271 
272  MakeObjectiveConstraintInfeasible(1);
273 }
274 
277  for (const VariableIndex var : flipped_var_trail_) {
278  reference_.SetValue(var, assignment_.Value(var));
279  }
280  flipped_var_trail_.clear();
281  flipped_var_trail_backtrack_levels_.clear();
282  AddBacktrackingLevel(); // To handle initial propagation.
283  MakeObjectiveConstraintInfeasible(1);
284 }
285 
286 void AssignmentAndConstraintFeasibilityMaintainer::
287  MakeObjectiveConstraintInfeasible(int delta) {
288  CHECK(IsFeasible());
289  CHECK(flipped_var_trail_.empty());
290  constraint_upper_bounds_[kObjectiveConstraint] =
291  constraint_values_[kObjectiveConstraint] - delta;
292  infeasible_constraint_set_.BacktrackAll();
293  infeasible_constraint_set_.ChangeState(kObjectiveConstraint, true);
294  infeasible_constraint_set_.AddBacktrackingLevel();
296  CHECK(!IsFeasible());
297  if (DEBUG_MODE) {
298  for (ConstraintIndex ct(1); ct < NumConstraints(); ++ct) {
300  }
301  }
302 }
303 
305  const std::vector<sat::Literal>& literals) {
306  for (const sat::Literal& literal : literals) {
307  const VariableIndex var(literal.Variable().value());
308  const bool value = literal.IsPositive();
309  if (assignment_.Value(var) != value) {
310  flipped_var_trail_.push_back(var);
311  assignment_.SetValue(var, value);
312  for (const ConstraintEntry& entry : by_variable_matrix_[var]) {
313  const bool was_feasible = ConstraintIsFeasible(entry.constraint);
314  constraint_values_[entry.constraint] +=
315  value ? entry.weight : -entry.weight;
316  if (ConstraintIsFeasible(entry.constraint) != was_feasible) {
317  infeasible_constraint_set_.ChangeState(entry.constraint,
318  was_feasible);
319  }
320  }
321  }
322  }
323 }
324 
326  flipped_var_trail_backtrack_levels_.push_back(flipped_var_trail_.size());
327  infeasible_constraint_set_.AddBacktrackingLevel();
328 }
329 
331  // Backtrack each literal of the last level.
332  for (int i = flipped_var_trail_backtrack_levels_.back();
333  i < flipped_var_trail_.size(); ++i) {
334  const VariableIndex var(flipped_var_trail_[i]);
335  const bool new_value = !assignment_.Value(var);
336  DCHECK_EQ(new_value, reference_.Value(var));
337  assignment_.SetValue(var, new_value);
338  for (const ConstraintEntry& entry : by_variable_matrix_[var]) {
339  constraint_values_[entry.constraint] +=
340  new_value ? entry.weight : -entry.weight;
341  }
342  }
343  flipped_var_trail_.resize(flipped_var_trail_backtrack_levels_.back());
344  flipped_var_trail_backtrack_levels_.pop_back();
345  infeasible_constraint_set_.BacktrackOneLevel();
346 }
347 
349  while (!flipped_var_trail_backtrack_levels_.empty()) BacktrackOneLevel();
350 }
351 
352 const std::vector<sat::Literal>&
354  if (!constraint_set_hasher_.IsInitialized()) {
355  InitializeConstraintSetHasher();
356  }
357 
358  // First, we compute the hash that a Literal should have in order to repair
359  // all the infeasible constraint (ignoring the objective).
360  //
361  // TODO(user): If this starts to show-up in a performance profile, we can
362  // easily maintain this hash incrementally.
363  uint64_t hash = 0;
364  for (const ConstraintIndex ci : PossiblyInfeasibleConstraints()) {
365  const int64_t value = ConstraintValue(ci);
366  if (value > ConstraintUpperBound(ci)) {
367  hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci, false));
368  } else if (value < ConstraintLowerBound(ci)) {
369  hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci, true));
370  }
371  }
372 
373  tmp_potential_repairs_.clear();
374  const auto it = hash_to_potential_repairs_.find(hash);
375  if (it != hash_to_potential_repairs_.end()) {
376  for (const sat::Literal literal : it->second) {
377  // We only returns the flips.
378  if (assignment_.Value(VariableIndex(literal.Variable().value())) !=
379  literal.IsPositive()) {
380  tmp_potential_repairs_.push_back(literal);
381  }
382  }
383  }
384  return tmp_potential_repairs_;
385 }
386 
388  std::string str;
389  str += "curr: ";
390  for (bool value : assignment_) {
391  str += value ? " 1 " : " 0 ";
392  }
393  str += "\nFlipped variables: ";
394  // TODO(user): show the backtrack levels.
395  for (const VariableIndex var : flipped_var_trail_) {
396  str += absl::StrFormat(" %d", var.value());
397  }
398  str += "\nmin curr max\n";
399  for (ConstraintIndex ct(0); ct < constraint_values_.size(); ++ct) {
400  if (constraint_lower_bounds_[ct] == std::numeric_limits<int64_t>::min()) {
401  str += absl::StrFormat("- %d %d\n", constraint_values_[ct],
402  constraint_upper_bounds_[ct]);
403  } else {
404  str +=
405  absl::StrFormat("%d %d %d\n", constraint_lower_bounds_[ct],
406  constraint_values_[ct], constraint_upper_bounds_[ct]);
407  }
408  }
409  return str;
410 }
411 
412 void AssignmentAndConstraintFeasibilityMaintainer::
413  InitializeConstraintSetHasher() {
414  const int num_constraints_with_objective = constraint_upper_bounds_.size();
415 
416  // Initialize the potential one flip repair. Note that we ignore the
417  // objective constraint completely so that we consider a repair even if the
418  // objective constraint is not infeasible.
419  constraint_set_hasher_.Initialize(2 * num_constraints_with_objective);
420  constraint_set_hasher_.IgnoreElement(
421  FromConstraintIndex(kObjectiveConstraint, true));
422  constraint_set_hasher_.IgnoreElement(
423  FromConstraintIndex(kObjectiveConstraint, false));
424  for (VariableIndex var(0); var < by_variable_matrix_.size(); ++var) {
425  // We add two entries, one for a positive flip (from false to true) and one
426  // for a negative flip (from true to false).
427  for (const bool flip_is_positive : {true, false}) {
428  uint64_t hash = 0;
429  for (const ConstraintEntry& entry : by_variable_matrix_[var]) {
430  const bool coeff_is_positive = entry.weight > 0;
431  hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(
432  entry.constraint,
433  /*up=*/flip_is_positive ? coeff_is_positive : !coeff_is_positive));
434  }
435  hash_to_potential_repairs_[hash].push_back(
436  sat::Literal(sat::BooleanVariable(var.value()), flip_is_positive));
437  }
438  }
439 }
440 
441 //------------------------------------------------------------------------------
442 // OneFlipConstraintRepairer
443 //------------------------------------------------------------------------------
444 
446  const LinearBooleanProblem& problem,
448  const sat::VariablesAssignment& sat_assignment)
449  : by_constraint_matrix_(problem.constraints_size() + 1),
450  maintainer_(maintainer),
451  sat_assignment_(sat_assignment) {
452  // Fill the by_constraint_matrix_.
453  //
454  // IMPORTANT: The order of the constraint needs to exactly match the one of
455  // the constraint in the AssignmentAndConstraintFeasibilityMaintainer.
456 
457  // Add the objective constraint as the first constraint.
458  ConstraintIndex num_constraint(0);
459  const LinearObjective& objective = problem.objective();
460  CHECK_EQ(objective.literals_size(), objective.coefficients_size());
461  for (int i = 0; i < objective.literals_size(); ++i) {
462  CHECK_GT(objective.literals(i), 0);
463  CHECK_NE(objective.coefficients(i), 0);
464 
465  const VariableIndex var(objective.literals(i) - 1);
466  const int64_t weight = objective.coefficients(i);
467  by_constraint_matrix_[num_constraint].push_back(
469  }
470 
471  // Add the non-binary problem constraints.
472  for (const LinearBooleanConstraint& constraint : problem.constraints()) {
473  if (constraint.literals_size() <= 2) {
474  // Infeasible binary constraints are automatically repaired by propagation
475  // (when possible). Then there are no needs to consider the binary
476  // constraints here, the propagation is delegated to the SAT propagator.
477  continue;
478  }
479 
480  ++num_constraint;
481  CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
482  for (int i = 0; i < constraint.literals_size(); ++i) {
483  const VariableIndex var(constraint.literals(i) - 1);
484  const int64_t weight = constraint.coefficients(i);
485  by_constraint_matrix_[num_constraint].push_back(
487  }
488  }
489 
490  SortTermsOfEachConstraints(problem.num_variables());
491 }
492 
493 const ConstraintIndex OneFlipConstraintRepairer::kInvalidConstraint(-1);
494 const TermIndex OneFlipConstraintRepairer::kInitTerm(-1);
495 const TermIndex OneFlipConstraintRepairer::kInvalidTerm(-2);
496 
498  ConstraintIndex selected_ct = kInvalidConstraint;
499  int32_t selected_num_branches = std::numeric_limits<int32_t>::max();
500  int num_infeasible_constraints_left = maintainer_.NumInfeasibleConstraints();
501 
502  // Optimization: We inspect the constraints in reverse order because the
503  // objective one will always be first (in our current code) and with some
504  // luck, we will break early instead of fully exploring it.
505  const std::vector<ConstraintIndex>& infeasible_constraints =
506  maintainer_.PossiblyInfeasibleConstraints();
507  for (int index = infeasible_constraints.size() - 1; index >= 0; --index) {
508  const ConstraintIndex& i = infeasible_constraints[index];
509  if (maintainer_.ConstraintIsFeasible(i)) continue;
510  --num_infeasible_constraints_left;
511 
512  // Optimization: We return the only candidate without inspecting it.
513  // This is critical at the beginning of the search or later if the only
514  // candidate is the objective constraint which can be really long.
515  if (num_infeasible_constraints_left == 0 &&
516  selected_ct == kInvalidConstraint) {
517  return i;
518  }
519 
520  const int64_t constraint_value = maintainer_.ConstraintValue(i);
521  const int64_t lb = maintainer_.ConstraintLowerBound(i);
522  const int64_t ub = maintainer_.ConstraintUpperBound(i);
523 
524  int32_t num_branches = 0;
525  for (const ConstraintTerm& term : by_constraint_matrix_[i]) {
526  if (sat_assignment_.VariableIsAssigned(
527  sat::BooleanVariable(term.var.value()))) {
528  continue;
529  }
530  const int64_t new_value =
531  constraint_value +
532  (maintainer_.Assignment(term.var) ? -term.weight : term.weight);
533  if (new_value >= lb && new_value <= ub) {
534  ++num_branches;
535  if (num_branches >= selected_num_branches) break;
536  }
537  }
538 
539  // The constraint can't be repaired in one decision.
540  if (num_branches == 0) continue;
541  if (num_branches < selected_num_branches) {
542  selected_ct = i;
543  selected_num_branches = num_branches;
544  if (num_branches == 1) break;
545  }
546  }
547  return selected_ct;
548 }
549 
551  ConstraintIndex ct_index, TermIndex init_term_index,
552  TermIndex start_term_index) const {
554  by_constraint_matrix_[ct_index];
555  const int64_t constraint_value = maintainer_.ConstraintValue(ct_index);
556  const int64_t lb = maintainer_.ConstraintLowerBound(ct_index);
557  const int64_t ub = maintainer_.ConstraintUpperBound(ct_index);
558 
559  const TermIndex end_term_index(terms.size() + init_term_index + 1);
560  for (TermIndex loop_term_index(
561  start_term_index + 1 +
562  (start_term_index < init_term_index ? terms.size() : 0));
563  loop_term_index < end_term_index; ++loop_term_index) {
564  const TermIndex term_index(loop_term_index % terms.size());
565  const ConstraintTerm term = terms[term_index];
566  if (sat_assignment_.VariableIsAssigned(
567  sat::BooleanVariable(term.var.value()))) {
568  continue;
569  }
570  const int64_t new_value =
571  constraint_value +
572  (maintainer_.Assignment(term.var) ? -term.weight : term.weight);
573  if (new_value >= lb && new_value <= ub) {
574  return term_index;
575  }
576  }
577  return kInvalidTerm;
578 }
579 
580 bool OneFlipConstraintRepairer::RepairIsValid(ConstraintIndex ct_index,
581  TermIndex term_index) const {
582  if (maintainer_.ConstraintIsFeasible(ct_index)) return false;
583  const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
584  if (sat_assignment_.VariableIsAssigned(
585  sat::BooleanVariable(term.var.value()))) {
586  return false;
587  }
588  const int64_t new_value =
589  maintainer_.ConstraintValue(ct_index) +
590  (maintainer_.Assignment(term.var) ? -term.weight : term.weight);
591 
592  const int64_t lb = maintainer_.ConstraintLowerBound(ct_index);
593  const int64_t ub = maintainer_.ConstraintUpperBound(ct_index);
594  return (new_value >= lb && new_value <= ub);
595 }
596 
598  TermIndex term_index) const {
599  const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
600  const bool value = maintainer_.Assignment(term.var);
601  return sat::Literal(sat::BooleanVariable(term.var.value()), !value);
602 }
603 
604 void OneFlipConstraintRepairer::SortTermsOfEachConstraints(int num_variables) {
605  absl::StrongVector<VariableIndex, int64_t> objective(num_variables, 0);
606  for (const ConstraintTerm& term :
607  by_constraint_matrix_[AssignmentAndConstraintFeasibilityMaintainer::
609  objective[term.var] = std::abs(term.weight);
610  }
612  by_constraint_matrix_) {
613  std::sort(terms.begin(), terms.end(),
614  [&objective](const ConstraintTerm& a, const ConstraintTerm& b) {
615  return objective[a.var] > objective[b.var];
616  });
617  }
618 }
619 
620 //------------------------------------------------------------------------------
621 // SatWrapper
622 //------------------------------------------------------------------------------
623 
624 SatWrapper::SatWrapper(sat::SatSolver* sat_solver) : sat_solver_(sat_solver) {}
625 
626 void SatWrapper::BacktrackAll() { sat_solver_->Backtrack(0); }
627 
628 std::vector<sat::Literal> SatWrapper::FullSatTrail() const {
629  std::vector<sat::Literal> propagated_literals;
630  const sat::Trail& trail = sat_solver_->LiteralTrail();
631  for (int trail_index = 0; trail_index < trail.Index(); ++trail_index) {
632  propagated_literals.push_back(trail[trail_index]);
633  }
634  return propagated_literals;
635 }
636 
638  std::vector<sat::Literal>* propagated_literals) {
639  CHECK(!sat_solver_->Assignment().VariableIsAssigned(
640  decision_literal.Variable()));
641  CHECK(propagated_literals != nullptr);
642 
643  propagated_literals->clear();
644  const int old_decision_level = sat_solver_->CurrentDecisionLevel();
645  const int new_trail_index =
646  sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision_literal);
647  if (sat_solver_->IsModelUnsat()) {
648  return old_decision_level + 1;
649  }
650 
651  // Return the propagated literals, whenever there is a conflict or not.
652  // In case of conflict, these literals will have to be added to the last
653  // decision point after backtrack.
654  const sat::Trail& propagation_trail = sat_solver_->LiteralTrail();
655  for (int trail_index = new_trail_index;
656  trail_index < propagation_trail.Index(); ++trail_index) {
657  propagated_literals->push_back(propagation_trail[trail_index]);
658  }
659 
660  return old_decision_level + 1 - sat_solver_->CurrentDecisionLevel();
661 }
662 
664  const int old_decision_level = sat_solver_->CurrentDecisionLevel();
665  if (old_decision_level > 0) {
666  sat_solver_->Backtrack(old_decision_level - 1);
667  }
668 }
669 
671  bop::ExtractLearnedInfoFromSatSolver(sat_solver_, info);
672 }
673 
675  return sat_solver_->deterministic_time();
676 }
677 
678 //------------------------------------------------------------------------------
679 // LocalSearchAssignmentIterator
680 //------------------------------------------------------------------------------
681 
683  const ProblemState& problem_state, int max_num_decisions,
684  int max_num_broken_constraints, absl::BitGenRef random,
685  SatWrapper* sat_wrapper)
686  : max_num_decisions_(max_num_decisions),
687  max_num_broken_constraints_(max_num_broken_constraints),
688  maintainer_(problem_state.original_problem(), random),
689  sat_wrapper_(sat_wrapper),
690  repairer_(problem_state.original_problem(), maintainer_,
691  sat_wrapper->SatAssignment()),
692  search_nodes_(),
693  initial_term_index_(
694  problem_state.original_problem().constraints_size() + 1,
695  OneFlipConstraintRepairer::kInitTerm),
696  use_transposition_table_(false),
697  use_potential_one_flip_repairs_(false),
698  num_nodes_(0),
699  num_skipped_nodes_(0),
700  num_improvements_(0),
701  num_improvements_by_one_flip_repairs_(0),
702  num_inspected_one_flip_repairs_(0) {}
703 
705  VLOG(1) << "LS " << max_num_decisions_
706  << "\n num improvements: " << num_improvements_
707  << "\n num improvements with one flip repairs: "
708  << num_improvements_by_one_flip_repairs_
709  << "\n num inspected one flip repairs: "
710  << num_inspected_one_flip_repairs_;
711 }
712 
714  const ProblemState& problem_state) {
715  better_solution_has_been_found_ = false;
716  maintainer_.SetReferenceSolution(problem_state.solution());
717  for (const SearchNode& node : search_nodes_) {
718  initial_term_index_[node.constraint] = node.term_index;
719  }
720  search_nodes_.clear();
721  transposition_table_.clear();
722  num_nodes_ = 0;
723  num_skipped_nodes_ = 0;
724 }
725 
726 // In order to restore the synchronization from any state, we backtrack
727 // everything and retry to take the same decisions as before. We stop at the
728 // first one that can't be taken.
730  CHECK_EQ(better_solution_has_been_found_, false);
731  const std::vector<SearchNode> copy = search_nodes_;
732  sat_wrapper_->BacktrackAll();
733  maintainer_.BacktrackAll();
734 
735  // Note(user): at this stage, the sat trail contains the fixed variables.
736  // There will almost always be at the same value in the reference solution.
737  // However since the objective may be over-constrained in the sat_solver, it
738  // is possible that some variable where propagated to some other values.
739  maintainer_.Assign(sat_wrapper_->FullSatTrail());
740 
741  search_nodes_.clear();
742  for (const SearchNode& node : copy) {
743  if (!repairer_.RepairIsValid(node.constraint, node.term_index)) break;
744  search_nodes_.push_back(node);
745  ApplyDecision(repairer_.GetFlip(node.constraint, node.term_index));
746  }
747 }
748 
749 void LocalSearchAssignmentIterator::UseCurrentStateAsReference() {
750  better_solution_has_been_found_ = true;
751  maintainer_.UseCurrentStateAsReference();
752  sat_wrapper_->BacktrackAll();
753 
754  // Note(user): Here, there should be no discrepancies between the fixed
755  // variable and the new reference, so there is no need to do:
756  // maintainer_.Assign(sat_wrapper_->FullSatTrail());
757 
758  for (const SearchNode& node : search_nodes_) {
759  initial_term_index_[node.constraint] = node.term_index;
760  }
761  search_nodes_.clear();
762  transposition_table_.clear();
763  num_nodes_ = 0;
764  num_skipped_nodes_ = 0;
765  ++num_improvements_;
766 }
767 
769  if (sat_wrapper_->IsModelUnsat()) return false;
770  if (maintainer_.IsFeasible()) {
771  UseCurrentStateAsReference();
772  return true;
773  }
774 
775  // We only look for potential one flip repairs if we reached the end of the
776  // LS tree. I tried to do that at every level, but it didn't change the
777  // result much on the set-partitionning example I was using.
778  //
779  // TODO(user): Perform more experiments with this.
780  if (use_potential_one_flip_repairs_ &&
781  search_nodes_.size() == max_num_decisions_) {
782  for (const sat::Literal literal : maintainer_.PotentialOneFlipRepairs()) {
783  if (sat_wrapper_->SatAssignment().VariableIsAssigned(
784  literal.Variable())) {
785  continue;
786  }
787  ++num_inspected_one_flip_repairs_;
788 
789  // Temporarily apply the potential repair and see if it worked!
790  ApplyDecision(literal);
791  if (maintainer_.IsFeasible()) {
792  num_improvements_by_one_flip_repairs_++;
793  UseCurrentStateAsReference();
794  return true;
795  }
796  maintainer_.BacktrackOneLevel();
797  sat_wrapper_->BacktrackOneLevel();
798  }
799  }
800 
801  // If possible, go deeper, i.e. take one more decision.
802  if (!GoDeeper()) {
803  // If not, backtrack to the first node that still has untried way to fix
804  // its associated constraint. Update it to the next untried way.
805  Backtrack();
806  }
807 
808  // All nodes have been explored.
809  if (search_nodes_.empty()) {
810  VLOG(1) << std::string(27, ' ') + "LS " << max_num_decisions_
811  << " finished."
812  << " #explored:" << num_nodes_
813  << " #stored:" << transposition_table_.size()
814  << " #skipped:" << num_skipped_nodes_;
815  return false;
816  }
817 
818  // Apply the next decision, i.e. the literal of the flipped variable.
819  const SearchNode node = search_nodes_.back();
820  ApplyDecision(repairer_.GetFlip(node.constraint, node.term_index));
821  return true;
822 }
823 
824 // TODO(user): The 1.2 multiplier is an approximation only based on the time
825 // spent in the SAT wrapper. So far experiments show a good
826 // correlation with real time, but we might want to be more
827 // accurate.
829  return sat_wrapper_->deterministic_time() * 1.2;
830 }
831 
833  std::string str = "Search nodes:\n";
834  for (int i = 0; i < search_nodes_.size(); ++i) {
835  str += absl::StrFormat(" %d: %d %d\n", i,
836  search_nodes_[i].constraint.value(),
837  search_nodes_[i].term_index.value());
838  }
839  return str;
840 }
841 
842 void LocalSearchAssignmentIterator::ApplyDecision(sat::Literal literal) {
843  ++num_nodes_;
844  const int num_backtracks =
845  sat_wrapper_->ApplyDecision(literal, &tmp_propagated_literals_);
846 
847  // Sync the maintainer with SAT.
848  if (num_backtracks == 0) {
849  maintainer_.AddBacktrackingLevel();
850  maintainer_.Assign(tmp_propagated_literals_);
851  } else {
852  CHECK_GT(num_backtracks, 0);
853  CHECK_LE(num_backtracks, search_nodes_.size());
854 
855  // Only backtrack -1 decisions as the last one has not been pushed yet.
856  for (int i = 0; i < num_backtracks - 1; ++i) {
857  maintainer_.BacktrackOneLevel();
858  }
859  maintainer_.Assign(tmp_propagated_literals_);
860  search_nodes_.resize(search_nodes_.size() - num_backtracks);
861  }
862 }
863 
864 void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
865  std::array<int32_t, kStoredMaxDecisions>* a) {
866  int i = 0;
867  for (const SearchNode& n : search_nodes_) {
868  // Negated because we already fliped this variable, so GetFlip() will
869  // returns the old value.
870  (*a)[i] = -repairer_.GetFlip(n.constraint, n.term_index).SignedValue();
871  ++i;
872  }
873 
874  // 'a' is not zero-initialized, so we need to complete it with zeros.
875  while (i < kStoredMaxDecisions) {
876  (*a)[i] = 0;
877  ++i;
878  }
879 }
880 
881 bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
882  sat::Literal l) {
883  if (search_nodes_.size() + 1 > kStoredMaxDecisions) return false;
884 
885  // Fill the transposition table element, i.e the array 'a' of decisions.
886  std::array<int32_t, kStoredMaxDecisions> a;
887  InitializeTranspositionTableKey(&a);
888  a[search_nodes_.size()] = l.SignedValue();
889  std::sort(a.begin(), a.begin() + 1 + search_nodes_.size());
890 
891  if (transposition_table_.find(a) == transposition_table_.end()) {
892  return false;
893  } else {
894  ++num_skipped_nodes_;
895  return true;
896  }
897 }
898 
899 void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
900  // If there is more decision that kStoredMaxDecisions, do nothing.
901  if (search_nodes_.size() > kStoredMaxDecisions) return;
902 
903  // Fill the transposition table element, i.e the array 'a' of decisions.
904  std::array<int32_t, kStoredMaxDecisions> a;
905  InitializeTranspositionTableKey(&a);
906  std::sort(a.begin(), a.begin() + search_nodes_.size());
907 
908  transposition_table_.insert(a);
909 }
910 
911 bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
912  ConstraintIndex ct_to_repair, TermIndex term_index) {
913  if (term_index == initial_term_index_[ct_to_repair]) return false;
914  if (term_index == OneFlipConstraintRepairer::kInvalidTerm) {
915  term_index = initial_term_index_[ct_to_repair];
916  }
917  while (true) {
918  term_index = repairer_.NextRepairingTerm(
919  ct_to_repair, initial_term_index_[ct_to_repair], term_index);
920  if (term_index == OneFlipConstraintRepairer::kInvalidTerm) return false;
921  if (!use_transposition_table_ ||
922  !NewStateIsInTranspositionTable(
923  repairer_.GetFlip(ct_to_repair, term_index))) {
924  search_nodes_.push_back(SearchNode(ct_to_repair, term_index));
925  return true;
926  }
927  if (term_index == initial_term_index_[ct_to_repair]) return false;
928  }
929 }
930 
931 bool LocalSearchAssignmentIterator::GoDeeper() {
932  // Can we add one more decision?
933  if (search_nodes_.size() >= max_num_decisions_) {
934  return false;
935  }
936 
937  // Is the number of infeasible constraints reasonable?
938  //
939  // TODO(user): Make this parameters dynamic. We can either try lower value
940  // first and increase it later, or try to dynamically change it during the
941  // search. Another idea is to have instead a "max number of constraints that
942  // can be repaired in one decision" and to take into account the number of
943  // decisions left.
944  if (maintainer_.NumInfeasibleConstraints() > max_num_broken_constraints_) {
945  return false;
946  }
947 
948  // Can we find a constraint that can be repaired in one decision?
949  const ConstraintIndex ct_to_repair = repairer_.ConstraintToRepair();
950  if (ct_to_repair == OneFlipConstraintRepairer::kInvalidConstraint) {
951  return false;
952  }
953 
954  // Add the new decision.
955  //
956  // TODO(user): Store the last explored term index to not start from -1 each
957  // time. This will be very useful when a backtrack occurred due to the SAT
958  // propagator. Note however that this behavior is already enforced when we use
959  // the transposition table, since we will not explore again the branches
960  // already explored.
961  return EnqueueNextRepairingTermIfAny(ct_to_repair,
963 }
964 
965 void LocalSearchAssignmentIterator::Backtrack() {
966  while (!search_nodes_.empty()) {
967  // We finished exploring this node. Store it in the transposition table so
968  // that the same decisions will not be explored again. Note that the SAT
969  // solver may have learned more the second time the exact same decisions are
970  // seen, but we assume that it is not worth exploring again.
971  if (use_transposition_table_) InsertInTranspositionTable();
972 
973  const SearchNode last_node = search_nodes_.back();
974  search_nodes_.pop_back();
975  maintainer_.BacktrackOneLevel();
976  sat_wrapper_->BacktrackOneLevel();
977  if (EnqueueNextRepairingTermIfAny(last_node.constraint,
978  last_node.term_index)) {
979  return;
980  }
981  }
982 }
983 
984 } // namespace bop
985 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
const bool DEBUG_MODE
Definition: macros.h:24
int64_t min
Definition: alldiff_cst.cc:139
OneFlipConstraintRepairer(const sat::LinearBooleanProblem &problem, const AssignmentAndConstraintFeasibilityMaintainer &maintainer, const sat::VariablesAssignment &sat_assignment)
Definition: bop_ls.cc:445
ModelSharedTimeLimit * time_limit
bool Value(VariableIndex var) const
Definition: bop_solution.h:46
void SetReferenceSolution(const BopSolution &reference_solution)
Definition: bop_ls.cc:251
#define CHECK_GT(val1, val2)
Definition: base/logging.h:707
std::vector< sat::Literal > FullSatTrail() const
Definition: bop_ls.cc:628
#define VLOG(verboselevel)
Definition: base/logging.h:983
const std::string name
int ApplyDecision(sat::Literal decision_literal, std::vector< sat::Literal > *propagated_literals)
Definition: bop_ls.cc:637
TermIndex NextRepairingTerm(ConstraintIndex ct_index, TermIndex init_term_index, TermIndex start_term_index) const
Definition: bop_ls.cc:550
void ChangeState(IntType i, bool should_be_inside)
Definition: bop_ls.cc:135
int64_t b
void ExtractLearnedInfoFromSatSolver(sat::SatSolver *solver, LearnedInfo *info)
Definition: bop_util.cc:99
LocalSearchOptimizer(const std::string &name, int max_num_decisions, absl::BitGenRef random, sat::SatSolver *sat_propagator)
Definition: bop_ls.cc:36
const std::vector< sat::Literal > & PotentialOneFlipRepairs()
Definition: bop_ls.cc:353
const BopSolution & solution() const
Definition: bop_base.h:196
uint64_t Hash(const std::vector< IntType > &set) const
Definition: bop_ls.h:223
void Synchronize(const ProblemState &problem_state)
Definition: bop_ls.cc:713
int64_t max
Definition: alldiff_cst.cc:140
sat::Literal GetFlip(ConstraintIndex ct_index, TermIndex term_index) const
Definition: bop_ls.cc:597
int64_t weight
Definition: pack.cc:510
BooleanVariable Variable() const
Definition: sat_base.h:82
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
Definition: sat_solver.cc:500
#define CHECK_LE(val1, val2)
Definition: base/logging.h:704
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:160
int64_t ConstraintLowerBound(ConstraintIndex constraint) const
Definition: bop_ls.h:343
int64_t hash
Definition: matrix_utils.cc:61
void push_back(const value_type &x)
void SetValue(VariableIndex var, bool value)
Definition: bop_solution.h:39
int index
Definition: pack.cc:509
void Backtrack(int target_level)
Definition: sat_solver.cc:889
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
const std::vector< ConstraintIndex > & PossiblyInfeasibleConstraints() const
Definition: bop_ls.h:325
int64_t delta
Definition: resource.cc:1692
size_type size() const
void Assign(const std::vector< sat::Literal > &literals)
Definition: bop_ls.cc:304
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:363
int64_t ConstraintUpperBound(ConstraintIndex constraint) const
Definition: bop_ls.h:348
bool RepairIsValid(ConstraintIndex ct_index, TermIndex term_index) const
Definition: bop_ls.cc:580
LocalSearchAssignmentIterator(const ProblemState &problem_state, int max_num_decisions, int max_num_broken_constraints, absl::BitGenRef random, SatWrapper *sat_wrapper)
Definition: bop_ls.cc:682
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
const sat::VariablesAssignment & SatAssignment() const
Definition: bop_ls.h:66
SatWrapper(sat::SatSolver *sat_solver)
Definition: bop_ls.cc:624
Collection of objects used to extend the Constraint Solver library.
void ExtractLearnedInfo(LearnedInfo *info)
Definition: bop_ls.cc:670
void assign(size_type n, const value_type &val)
SatParameters parameters
static const ConstraintIndex kInvalidConstraint
Definition: bop_ls.h:447
constexpr int kObjectiveConstraint
IntVar * var
Definition: expr_array.cc:1874
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
int64_t ConstraintValue(ConstraintIndex constraint) const
Definition: bop_ls.h:355
int64_t value
Literal literal
Definition: optimization.cc:85
#define CHECK_NE(val1, val2)
Definition: base/logging.h:703
const Constraint * ct
AssignmentAndConstraintFeasibilityMaintainer(const sat::LinearBooleanProblem &problem, absl::BitGenRef random)
Definition: bop_ls.cc:185
bool ConstraintIsFeasible(ConstraintIndex constraint) const
Definition: bop_ls.h:360
int64_t a