16 #include "absl/memory/memory.h"
17 #include "absl/strings/str_format.h"
24 using ::operations_research::sat::LinearBooleanConstraint;
25 using ::operations_research::sat::LinearBooleanProblem;
26 using ::operations_research::sat::LinearObjective;
33 int max_num_decisions,
37 max_num_decisions_(max_num_decisions),
38 sat_wrapper_(sat_propagator),
39 assignment_iterator_() {}
43 bool LocalSearchOptimizer::ShouldBeRun(
49 const BopParameters&
parameters,
const ProblemState& problem_state,
51 CHECK(learned_info !=
nullptr);
53 learned_info->Clear();
55 if (assignment_iterator_ ==
nullptr) {
56 assignment_iterator_ = absl::make_unique<LocalSearchAssignmentIterator>(
57 problem_state, max_num_decisions_,
58 parameters.max_num_broken_constraints_in_ls(), &sat_wrapper_);
61 if (state_update_stamp_ != problem_state.update_stamp()) {
63 state_update_stamp_ = problem_state.update_stamp();
64 assignment_iterator_->Synchronize(problem_state);
66 assignment_iterator_->SynchronizeSatWrapper();
68 double prev_deterministic_time = assignment_iterator_->deterministic_time();
69 assignment_iterator_->UseTranspositionTable(
71 assignment_iterator_->UsePotentialOneFlipRepairs(
72 parameters.use_potential_one_flip_repairs_in_ls());
73 int64 num_assignments_to_explore =
74 parameters.max_number_of_explored_assignments_per_try_in_ls();
76 while (!
time_limit->LimitReached() && num_assignments_to_explore > 0 &&
77 assignment_iterator_->NextAssignment()) {
79 assignment_iterator_->deterministic_time() - prev_deterministic_time);
80 prev_deterministic_time = assignment_iterator_->deterministic_time();
81 --num_assignments_to_explore;
86 return problem_state.solution().IsFeasible()
94 if (assignment_iterator_->BetterSolutionHasBeenFound()) {
96 learned_info->solution = assignment_iterator_->LastReferenceAssignment();
105 if (num_assignments_to_explore <= 0) {
119 template <
typename IntType>
122 saved_sizes_.clear();
123 saved_stack_sizes_.clear();
125 in_stack_.assign(n.value(),
false);
128 template <
typename IntType>
130 bool should_be_inside) {
131 size_ += should_be_inside ? 1 : -1;
132 if (!in_stack_[i.value()]) {
133 in_stack_[i.value()] =
true;
138 template <
typename IntType>
140 saved_stack_sizes_.push_back(stack_.size());
141 saved_sizes_.push_back(size_);
144 template <
typename IntType>
146 if (saved_stack_sizes_.empty()) {
149 for (
int i = saved_stack_sizes_.back(); i < stack_.size(); ++i) {
150 in_stack_[stack_[i].value()] =
false;
152 stack_.resize(saved_stack_sizes_.back());
153 saved_stack_sizes_.pop_back();
154 size_ = saved_sizes_.back();
155 saved_sizes_.pop_back();
159 template <
typename IntType>
161 for (
int i = 0; i < stack_.size(); ++i) {
162 in_stack_[stack_[i].value()] =
false;
165 saved_stack_sizes_.clear();
167 saved_sizes_.clear();
180 const LinearBooleanProblem& problem)
181 : by_variable_matrix_(problem.num_variables()),
182 constraint_lower_bounds_(),
183 constraint_upper_bounds_(),
184 assignment_(problem,
"Assignment"),
185 reference_(problem,
"Assignment"),
186 constraint_values_(),
187 flipped_var_trail_backtrack_levels_(),
188 flipped_var_trail_() {
190 const LinearObjective& objective = problem.objective();
191 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
192 for (
int i = 0; i < objective.literals_size(); ++i) {
194 CHECK_NE(objective.coefficients(i), 0);
196 const VariableIndex
var(objective.literals(i) - 1);
198 by_variable_matrix_[
var].push_back(
201 constraint_lower_bounds_.push_back(
kint64min);
202 constraint_values_.push_back(0);
203 constraint_upper_bounds_.push_back(
kint64max);
206 ConstraintIndex num_constraints_with_objective(1);
207 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
208 if (constraint.literals_size() <= 2) {
215 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
216 for (
int i = 0; i < constraint.literals_size(); ++i) {
217 const VariableIndex
var(constraint.literals(i) - 1);
219 by_variable_matrix_[
var].push_back(
220 ConstraintEntry(num_constraints_with_objective,
weight));
222 constraint_lower_bounds_.push_back(
223 constraint.has_lower_bound() ? constraint.lower_bound() :
kint64min);
224 constraint_values_.push_back(0);
225 constraint_upper_bounds_.push_back(
226 constraint.has_upper_bound() ? constraint.upper_bound() :
kint64max);
228 ++num_constraints_with_objective;
232 infeasible_constraint_set_.ClearAndResize(
233 ConstraintIndex(constraint_values_.size()));
235 CHECK_EQ(constraint_values_.size(), constraint_lower_bounds_.size());
236 CHECK_EQ(constraint_values_.size(), constraint_upper_bounds_.size());
239 const ConstraintIndex
247 assignment_ = reference_solution;
248 reference_ = assignment_;
249 flipped_var_trail_backtrack_levels_.clear();
250 flipped_var_trail_.clear();
255 for (VariableIndex
var(0);
var < assignment_.
Size(); ++
var) {
257 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
258 constraint_values_[entry.constraint] += entry.weight;
263 MakeObjectiveConstraintInfeasible(1);
268 for (
const VariableIndex
var : flipped_var_trail_) {
271 flipped_var_trail_.clear();
272 flipped_var_trail_backtrack_levels_.clear();
274 MakeObjectiveConstraintInfeasible(1);
277 void AssignmentAndConstraintFeasibilityMaintainer::
278 MakeObjectiveConstraintInfeasible(
int delta) {
280 CHECK(flipped_var_trail_.empty());
296 const std::vector<sat::Literal>& literals) {
298 const VariableIndex
var(
literal.Variable().value());
301 flipped_var_trail_.push_back(
var);
303 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
305 constraint_values_[entry.constraint] +=
306 value ? entry.weight : -entry.weight;
308 infeasible_constraint_set_.
ChangeState(entry.constraint,
317 flipped_var_trail_backtrack_levels_.push_back(flipped_var_trail_.size());
323 for (
int i = flipped_var_trail_backtrack_levels_.back();
324 i < flipped_var_trail_.size(); ++i) {
325 const VariableIndex
var(flipped_var_trail_[i]);
326 const bool new_value = !assignment_.
Value(
var);
329 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
330 constraint_values_[entry.constraint] +=
331 new_value ? entry.weight : -entry.weight;
334 flipped_var_trail_.resize(flipped_var_trail_backtrack_levels_.back());
335 flipped_var_trail_backtrack_levels_.pop_back();
343 const std::vector<sat::Literal>&
346 InitializeConstraintSetHasher();
358 hash ^= constraint_set_hasher_.
Hash(FromConstraintIndex(ci,
false));
360 hash ^= constraint_set_hasher_.
Hash(FromConstraintIndex(ci,
true));
364 tmp_potential_repairs_.clear();
365 const auto it = hash_to_potential_repairs_.find(
hash);
366 if (it != hash_to_potential_repairs_.end()) {
369 if (assignment_.
Value(VariableIndex(
literal.Variable().value())) !=
371 tmp_potential_repairs_.push_back(
literal);
375 return tmp_potential_repairs_;
381 for (
bool value : assignment_) {
382 str +=
value ?
" 1 " :
" 0 ";
384 str +=
"\nFlipped variables: ";
386 for (
const VariableIndex
var : flipped_var_trail_) {
387 str += absl::StrFormat(
" %d",
var.value());
389 str +=
"\nmin curr max\n";
390 for (ConstraintIndex
ct(0);
ct < constraint_values_.
size(); ++
ct) {
392 str += absl::StrFormat(
"- %d %d\n", constraint_values_[
ct],
393 constraint_upper_bounds_[
ct]);
396 absl::StrFormat(
"%d %d %d\n", constraint_lower_bounds_[
ct],
397 constraint_values_[
ct], constraint_upper_bounds_[
ct]);
403 void AssignmentAndConstraintFeasibilityMaintainer::
404 InitializeConstraintSetHasher() {
405 const int num_constraints_with_objective = constraint_upper_bounds_.
size();
410 constraint_set_hasher_.
Initialize(2 * num_constraints_with_objective);
415 for (VariableIndex
var(0);
var < by_variable_matrix_.
size(); ++
var) {
418 for (
const bool flip_is_positive : {
true,
false}) {
420 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
421 const bool coeff_is_positive = entry.weight > 0;
422 hash ^= constraint_set_hasher_.
Hash(FromConstraintIndex(
424 flip_is_positive ? coeff_is_positive : !coeff_is_positive));
426 hash_to_potential_repairs_[
hash].push_back(
427 sat::Literal(sat::BooleanVariable(
var.value()), flip_is_positive));
437 const LinearBooleanProblem& problem,
440 : by_constraint_matrix_(problem.constraints_size() + 1),
441 maintainer_(maintainer),
442 sat_assignment_(sat_assignment) {
449 ConstraintIndex num_constraint(0);
450 const LinearObjective& objective = problem.objective();
451 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
452 for (
int i = 0; i < objective.literals_size(); ++i) {
454 CHECK_NE(objective.coefficients(i), 0);
456 const VariableIndex
var(objective.literals(i) - 1);
458 by_constraint_matrix_[num_constraint].push_back(
463 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
464 if (constraint.literals_size() <= 2) {
472 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
473 for (
int i = 0; i < constraint.literals_size(); ++i) {
474 const VariableIndex
var(constraint.literals(i) - 1);
476 by_constraint_matrix_[num_constraint].push_back(
481 SortTermsOfEachConstraints(problem.num_variables());
496 const std::vector<ConstraintIndex>& infeasible_constraints =
498 for (
int index = infeasible_constraints.size() - 1;
index >= 0; --
index) {
499 const ConstraintIndex& i = infeasible_constraints[
index];
501 --num_infeasible_constraints_left;
506 if (num_infeasible_constraints_left == 0 &&
515 int32 num_branches = 0;
518 sat::BooleanVariable(term.var.value()))) {
521 const int64 new_value =
523 (maintainer_.
Assignment(term.var) ? -term.weight : term.weight);
524 if (new_value >= lb && new_value <= ub) {
526 if (num_branches >= selected_num_branches)
break;
531 if (num_branches == 0)
continue;
532 if (num_branches < selected_num_branches) {
534 selected_num_branches = num_branches;
535 if (num_branches == 1)
break;
542 ConstraintIndex ct_index, TermIndex init_term_index,
543 TermIndex start_term_index)
const {
545 by_constraint_matrix_[ct_index];
550 const TermIndex end_term_index(terms.
size() + init_term_index + 1);
551 for (TermIndex loop_term_index(
552 start_term_index + 1 +
553 (start_term_index < init_term_index ? terms.
size() : 0));
554 loop_term_index < end_term_index; ++loop_term_index) {
555 const TermIndex term_index(loop_term_index % terms.
size());
558 sat::BooleanVariable(term.
var.value()))) {
561 const int64 new_value =
564 if (new_value >= lb && new_value <= ub) {
572 TermIndex term_index)
const {
574 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
576 sat::BooleanVariable(term.
var.value()))) {
579 const int64 new_value =
585 return (new_value >= lb && new_value <= ub);
589 TermIndex term_index)
const {
590 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
595 void OneFlipConstraintRepairer::SortTermsOfEachConstraints(
int num_variables) {
597 for (
const ConstraintTerm& term :
600 objective[term.var] = std::abs(term.weight);
603 by_constraint_matrix_) {
604 std::sort(terms.begin(), terms.end(),
605 [&objective](
const ConstraintTerm&
a,
const ConstraintTerm&
b) {
606 return objective[a.var] > objective[b.var];
620 std::vector<sat::Literal> propagated_literals;
622 for (
int trail_index = 0; trail_index < trail.
Index(); ++trail_index) {
623 propagated_literals.push_back(trail[trail_index]);
625 return propagated_literals;
629 std::vector<sat::Literal>* propagated_literals) {
632 CHECK(propagated_literals !=
nullptr);
634 propagated_literals->clear();
636 const int new_trail_index =
639 return old_decision_level + 1;
646 for (
int trail_index = new_trail_index;
647 trail_index < propagation_trail.
Index(); ++trail_index) {
648 propagated_literals->push_back(propagation_trail[trail_index]);
656 if (old_decision_level > 0) {
657 sat_solver_->
Backtrack(old_decision_level - 1);
674 const ProblemState& problem_state,
int max_num_decisions,
675 int max_num_broken_constraints,
SatWrapper* sat_wrapper)
676 : max_num_decisions_(max_num_decisions),
677 max_num_broken_constraints_(max_num_broken_constraints),
678 maintainer_(problem_state.original_problem()),
679 sat_wrapper_(sat_wrapper),
680 repairer_(problem_state.original_problem(), maintainer_,
681 sat_wrapper->SatAssignment()),
684 problem_state.original_problem().constraints_size() + 1,
686 use_transposition_table_(false),
687 use_potential_one_flip_repairs_(false),
689 num_skipped_nodes_(0),
690 num_improvements_(0),
691 num_improvements_by_one_flip_repairs_(0),
692 num_inspected_one_flip_repairs_(0) {}
695 VLOG(1) <<
"LS " << max_num_decisions_
696 <<
"\n num improvements: " << num_improvements_
697 <<
"\n num improvements with one flip repairs: "
698 << num_improvements_by_one_flip_repairs_
699 <<
"\n num inspected one flip repairs: "
700 << num_inspected_one_flip_repairs_;
705 better_solution_has_been_found_ =
false;
707 for (
const SearchNode& node : search_nodes_) {
708 initial_term_index_[node.constraint] = node.term_index;
710 search_nodes_.
clear();
711 transposition_table_.clear();
713 num_skipped_nodes_ = 0;
720 CHECK_EQ(better_solution_has_been_found_,
false);
721 const std::vector<SearchNode> copy = search_nodes_;
731 search_nodes_.clear();
732 for (
const SearchNode& node : copy) {
733 if (!repairer_.
RepairIsValid(node.constraint, node.term_index))
break;
734 search_nodes_.push_back(node);
735 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
739 void LocalSearchAssignmentIterator::UseCurrentStateAsReference() {
740 better_solution_has_been_found_ =
true;
748 for (
const SearchNode& node : search_nodes_) {
749 initial_term_index_[node.constraint] = node.term_index;
751 search_nodes_.
clear();
752 transposition_table_.clear();
754 num_skipped_nodes_ = 0;
761 UseCurrentStateAsReference();
770 if (use_potential_one_flip_repairs_ &&
771 search_nodes_.size() == max_num_decisions_) {
777 ++num_inspected_one_flip_repairs_;
782 num_improvements_by_one_flip_repairs_++;
783 UseCurrentStateAsReference();
799 if (search_nodes_.empty()) {
800 VLOG(1) << std::string(27,
' ') +
"LS " << max_num_decisions_
802 <<
" #explored:" << num_nodes_
803 <<
" #stored:" << transposition_table_.size()
804 <<
" #skipped:" << num_skipped_nodes_;
809 const SearchNode node = search_nodes_.back();
810 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
823 std::string str =
"Search nodes:\n";
824 for (
int i = 0; i < search_nodes_.size(); ++i) {
825 str += absl::StrFormat(
" %d: %d %d\n", i,
826 search_nodes_[i].constraint.value(),
827 search_nodes_[i].term_index.value());
834 const int num_backtracks =
838 if (num_backtracks == 0) {
840 maintainer_.
Assign(tmp_propagated_literals_);
843 CHECK_LE(num_backtracks, search_nodes_.size());
846 for (
int i = 0; i < num_backtracks - 1; ++i) {
849 maintainer_.
Assign(tmp_propagated_literals_);
850 search_nodes_.resize(search_nodes_.size() - num_backtracks);
854 void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
855 std::array<int32, kStoredMaxDecisions>*
a) {
857 for (
const SearchNode& n : search_nodes_) {
865 while (i < kStoredMaxDecisions) {
871 bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
873 if (search_nodes_.size() + 1 > kStoredMaxDecisions)
return false;
876 std::array<int32, kStoredMaxDecisions>
a;
877 InitializeTranspositionTableKey(&
a);
878 a[search_nodes_.size()] = l.SignedValue();
879 std::sort(
a.begin(),
a.begin() + 1 + search_nodes_.size());
881 if (transposition_table_.find(
a) == transposition_table_.end()) {
884 ++num_skipped_nodes_;
889 void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
891 if (search_nodes_.size() > kStoredMaxDecisions)
return;
894 std::array<int32, kStoredMaxDecisions>
a;
895 InitializeTranspositionTableKey(&
a);
896 std::sort(
a.begin(),
a.begin() + search_nodes_.size());
898 transposition_table_.insert(
a);
901 bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
902 ConstraintIndex ct_to_repair, TermIndex term_index) {
903 if (term_index == initial_term_index_[ct_to_repair])
return false;
905 term_index = initial_term_index_[ct_to_repair];
909 ct_to_repair, initial_term_index_[ct_to_repair], term_index);
911 if (!use_transposition_table_ ||
912 !NewStateIsInTranspositionTable(
913 repairer_.
GetFlip(ct_to_repair, term_index))) {
914 search_nodes_.push_back(SearchNode(ct_to_repair, term_index));
917 if (term_index == initial_term_index_[ct_to_repair])
return false;
921 bool LocalSearchAssignmentIterator::GoDeeper() {
923 if (search_nodes_.size() >= max_num_decisions_) {
951 return EnqueueNextRepairingTermIfAny(ct_to_repair,
955 void LocalSearchAssignmentIterator::Backtrack() {
956 while (!search_nodes_.empty()) {
961 if (use_transposition_table_) InsertInTranspositionTable();
963 const SearchNode last_node = search_nodes_.back();
964 search_nodes_.pop_back();
967 if (EnqueueNextRepairingTermIfAny(last_node.constraint,
968 last_node.term_index)) {