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