16 #include "absl/memory/memory.h"
17 #include "absl/strings/str_format.h"
25 using ::operations_research::sat::LinearBooleanConstraint;
26 using ::operations_research::sat::LinearBooleanProblem;
27 using ::operations_research::sat::LinearObjective;
34 int max_num_decisions,
38 max_num_decisions_(max_num_decisions),
39 sat_wrapper_(sat_propagator),
40 assignment_iterator_() {}
44 bool LocalSearchOptimizer::ShouldBeRun(
50 const BopParameters&
parameters,
const ProblemState& problem_state,
52 CHECK(learned_info !=
nullptr);
54 learned_info->Clear();
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_);
62 if (state_update_stamp_ != problem_state.update_stamp()) {
64 state_update_stamp_ = problem_state.update_stamp();
65 assignment_iterator_->Synchronize(problem_state);
67 assignment_iterator_->SynchronizeSatWrapper();
69 double prev_deterministic_time = assignment_iterator_->deterministic_time();
70 assignment_iterator_->UseTranspositionTable(
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();
77 while (!
time_limit->LimitReached() && num_assignments_to_explore > 0 &&
78 assignment_iterator_->NextAssignment()) {
80 assignment_iterator_->deterministic_time() - prev_deterministic_time);
81 prev_deterministic_time = assignment_iterator_->deterministic_time();
82 --num_assignments_to_explore;
87 return problem_state.solution().IsFeasible()
95 if (assignment_iterator_->BetterSolutionHasBeenFound()) {
97 learned_info->solution = assignment_iterator_->LastReferenceAssignment();
106 if (num_assignments_to_explore <= 0) {
120 template <
typename IntType>
123 saved_sizes_.clear();
124 saved_stack_sizes_.clear();
126 in_stack_.assign(n.value(),
false);
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;
139 template <
typename IntType>
141 saved_stack_sizes_.push_back(stack_.size());
142 saved_sizes_.push_back(size_);
145 template <
typename IntType>
147 if (saved_stack_sizes_.empty()) {
150 for (
int i = saved_stack_sizes_.back(); i < stack_.size(); ++i) {
151 in_stack_[stack_[i].value()] =
false;
153 stack_.resize(saved_stack_sizes_.back());
154 saved_stack_sizes_.pop_back();
155 size_ = saved_sizes_.back();
156 saved_sizes_.pop_back();
160 template <
typename IntType>
162 for (
int i = 0; i < stack_.size(); ++i) {
163 in_stack_[stack_[i].value()] =
false;
166 saved_stack_sizes_.clear();
168 saved_sizes_.clear();
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_() {
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) {
195 CHECK_NE(objective.coefficients(i), 0);
197 const VariableIndex
var(objective.literals(i) - 1);
199 by_variable_matrix_[
var].push_back(
202 constraint_lower_bounds_.push_back(
kint64min);
203 constraint_values_.push_back(0);
204 constraint_upper_bounds_.push_back(
kint64max);
207 ConstraintIndex num_constraints_with_objective(1);
208 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
209 if (constraint.literals_size() <= 2) {
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);
220 by_variable_matrix_[
var].push_back(
221 ConstraintEntry(num_constraints_with_objective,
weight));
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);
229 ++num_constraints_with_objective;
233 infeasible_constraint_set_.ClearAndResize(
234 ConstraintIndex(constraint_values_.size()));
236 CHECK_EQ(constraint_values_.size(), constraint_lower_bounds_.size());
237 CHECK_EQ(constraint_values_.size(), constraint_upper_bounds_.size());
240 const ConstraintIndex
248 assignment_ = reference_solution;
249 reference_ = assignment_;
250 flipped_var_trail_backtrack_levels_.clear();
251 flipped_var_trail_.clear();
256 for (VariableIndex
var(0);
var < assignment_.
Size(); ++
var) {
258 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
259 constraint_values_[entry.constraint] += entry.weight;
264 MakeObjectiveConstraintInfeasible(1);
269 for (
const VariableIndex
var : flipped_var_trail_) {
272 flipped_var_trail_.clear();
273 flipped_var_trail_backtrack_levels_.clear();
275 MakeObjectiveConstraintInfeasible(1);
278 void AssignmentAndConstraintFeasibilityMaintainer::
279 MakeObjectiveConstraintInfeasible(
int delta) {
281 CHECK(flipped_var_trail_.empty());
297 const std::vector<sat::Literal>& literals) {
299 const VariableIndex
var(
literal.Variable().value());
302 flipped_var_trail_.push_back(
var);
304 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
306 constraint_values_[entry.constraint] +=
307 value ? entry.weight : -entry.weight;
309 infeasible_constraint_set_.
ChangeState(entry.constraint,
318 flipped_var_trail_backtrack_levels_.push_back(flipped_var_trail_.size());
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);
330 for (
const ConstraintEntry& entry : by_variable_matrix_[
var]) {
331 constraint_values_[entry.constraint] +=
332 new_value ? entry.weight : -entry.weight;
335 flipped_var_trail_.resize(flipped_var_trail_backtrack_levels_.back());
336 flipped_var_trail_backtrack_levels_.pop_back();
344 const std::vector<sat::Literal>&
347 InitializeConstraintSetHasher();
359 hash ^= constraint_set_hasher_.
Hash(FromConstraintIndex(ci,
false));
361 hash ^= constraint_set_hasher_.
Hash(FromConstraintIndex(ci,
true));
365 tmp_potential_repairs_.clear();
366 const auto it = hash_to_potential_repairs_.find(
hash);
367 if (it != hash_to_potential_repairs_.end()) {
370 if (assignment_.
Value(VariableIndex(
literal.Variable().value())) !=
372 tmp_potential_repairs_.push_back(
literal);
376 return tmp_potential_repairs_;
382 for (
bool value : assignment_) {
383 str +=
value ?
" 1 " :
" 0 ";
385 str +=
"\nFlipped variables: ";
387 for (
const VariableIndex
var : flipped_var_trail_) {
388 str += absl::StrFormat(
" %d",
var.value());
390 str +=
"\nmin curr max\n";
391 for (ConstraintIndex
ct(0);
ct < constraint_values_.
size(); ++
ct) {
393 str += absl::StrFormat(
"- %d %d\n", constraint_values_[
ct],
394 constraint_upper_bounds_[
ct]);
397 absl::StrFormat(
"%d %d %d\n", constraint_lower_bounds_[
ct],
398 constraint_values_[
ct], constraint_upper_bounds_[
ct]);
404 void AssignmentAndConstraintFeasibilityMaintainer::
405 InitializeConstraintSetHasher() {
406 const int num_constraints_with_objective = constraint_upper_bounds_.
size();
411 constraint_set_hasher_.
Initialize(2 * num_constraints_with_objective);
416 for (VariableIndex
var(0);
var < by_variable_matrix_.
size(); ++
var) {
419 for (
const bool flip_is_positive : {
true,
false}) {
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(
425 flip_is_positive ? coeff_is_positive : !coeff_is_positive));
427 hash_to_potential_repairs_[
hash].push_back(
428 sat::Literal(sat::BooleanVariable(
var.value()), flip_is_positive));
438 const LinearBooleanProblem& problem,
441 : by_constraint_matrix_(problem.constraints_size() + 1),
442 maintainer_(maintainer),
443 sat_assignment_(sat_assignment) {
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) {
455 CHECK_NE(objective.coefficients(i), 0);
457 const VariableIndex
var(objective.literals(i) - 1);
459 by_constraint_matrix_[num_constraint].push_back(
464 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
465 if (constraint.literals_size() <= 2) {
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);
477 by_constraint_matrix_[num_constraint].push_back(
482 SortTermsOfEachConstraints(problem.num_variables());
497 const std::vector<ConstraintIndex>& infeasible_constraints =
499 for (
int index = infeasible_constraints.size() - 1;
index >= 0; --
index) {
500 const ConstraintIndex& i = infeasible_constraints[
index];
502 --num_infeasible_constraints_left;
507 if (num_infeasible_constraints_left == 0 &&
516 int32 num_branches = 0;
519 sat::BooleanVariable(term.var.value()))) {
522 const int64 new_value =
524 (maintainer_.
Assignment(term.var) ? -term.weight : term.weight);
525 if (new_value >= lb && new_value <= ub) {
527 if (num_branches >= selected_num_branches)
break;
532 if (num_branches == 0)
continue;
533 if (num_branches < selected_num_branches) {
535 selected_num_branches = num_branches;
536 if (num_branches == 1)
break;
543 ConstraintIndex ct_index, TermIndex init_term_index,
544 TermIndex start_term_index)
const {
546 by_constraint_matrix_[ct_index];
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());
559 sat::BooleanVariable(term.
var.value()))) {
562 const int64 new_value =
565 if (new_value >= lb && new_value <= ub) {
573 TermIndex term_index)
const {
575 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
577 sat::BooleanVariable(term.
var.value()))) {
580 const int64 new_value =
586 return (new_value >= lb && new_value <= ub);
590 TermIndex term_index)
const {
591 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
596 void OneFlipConstraintRepairer::SortTermsOfEachConstraints(
int num_variables) {
598 for (
const ConstraintTerm& term :
601 objective[term.var] = std::abs(term.weight);
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];
621 std::vector<sat::Literal> propagated_literals;
623 for (
int trail_index = 0; trail_index < trail.
Index(); ++trail_index) {
624 propagated_literals.push_back(trail[trail_index]);
626 return propagated_literals;
630 std::vector<sat::Literal>* propagated_literals) {
633 CHECK(propagated_literals !=
nullptr);
635 propagated_literals->clear();
637 const int new_trail_index =
640 return old_decision_level + 1;
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]);
657 if (old_decision_level > 0) {
658 sat_solver_->
Backtrack(old_decision_level - 1);
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()),
685 problem_state.original_problem().constraints_size() + 1,
687 use_transposition_table_(false),
688 use_potential_one_flip_repairs_(false),
690 num_skipped_nodes_(0),
691 num_improvements_(0),
692 num_improvements_by_one_flip_repairs_(0),
693 num_inspected_one_flip_repairs_(0) {}
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_;
706 better_solution_has_been_found_ =
false;
708 for (
const SearchNode& node : search_nodes_) {
709 initial_term_index_[node.constraint] = node.term_index;
711 search_nodes_.
clear();
712 transposition_table_.clear();
714 num_skipped_nodes_ = 0;
721 CHECK_EQ(better_solution_has_been_found_,
false);
722 const std::vector<SearchNode> copy = search_nodes_;
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));
740 void LocalSearchAssignmentIterator::UseCurrentStateAsReference() {
741 better_solution_has_been_found_ =
true;
749 for (
const SearchNode& node : search_nodes_) {
750 initial_term_index_[node.constraint] = node.term_index;
752 search_nodes_.
clear();
753 transposition_table_.clear();
755 num_skipped_nodes_ = 0;
762 UseCurrentStateAsReference();
771 if (use_potential_one_flip_repairs_ &&
772 search_nodes_.size() == max_num_decisions_) {
778 ++num_inspected_one_flip_repairs_;
783 num_improvements_by_one_flip_repairs_++;
784 UseCurrentStateAsReference();
800 if (search_nodes_.empty()) {
801 VLOG(1) << std::string(27,
' ') +
"LS " << max_num_decisions_
803 <<
" #explored:" << num_nodes_
804 <<
" #stored:" << transposition_table_.size()
805 <<
" #skipped:" << num_skipped_nodes_;
810 const SearchNode node = search_nodes_.back();
811 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
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());
835 const int num_backtracks =
839 if (num_backtracks == 0) {
841 maintainer_.
Assign(tmp_propagated_literals_);
844 CHECK_LE(num_backtracks, search_nodes_.size());
847 for (
int i = 0; i < num_backtracks - 1; ++i) {
850 maintainer_.
Assign(tmp_propagated_literals_);
851 search_nodes_.resize(search_nodes_.size() - num_backtracks);
855 void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
856 std::array<int32, kStoredMaxDecisions>*
a) {
858 for (
const SearchNode& n : search_nodes_) {
866 while (i < kStoredMaxDecisions) {
872 bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
874 if (search_nodes_.size() + 1 > kStoredMaxDecisions)
return false;
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());
882 if (transposition_table_.find(
a) == transposition_table_.end()) {
885 ++num_skipped_nodes_;
890 void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
892 if (search_nodes_.size() > kStoredMaxDecisions)
return;
895 std::array<int32, kStoredMaxDecisions>
a;
896 InitializeTranspositionTableKey(&
a);
897 std::sort(
a.begin(),
a.begin() + search_nodes_.size());
899 transposition_table_.insert(
a);
902 bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
903 ConstraintIndex ct_to_repair, TermIndex term_index) {
904 if (term_index == initial_term_index_[ct_to_repair])
return false;
906 term_index = initial_term_index_[ct_to_repair];
910 ct_to_repair, initial_term_index_[ct_to_repair], term_index);
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));
918 if (term_index == initial_term_index_[ct_to_repair])
return false;
922 bool LocalSearchAssignmentIterator::GoDeeper() {
924 if (search_nodes_.size() >= max_num_decisions_) {
952 return EnqueueNextRepairingTermIfAny(ct_to_repair,
956 void LocalSearchAssignmentIterator::Backtrack() {
957 while (!search_nodes_.empty()) {
962 if (use_transposition_table_) InsertInTranspositionTable();
964 const SearchNode last_node = search_nodes_.back();
965 search_nodes_.pop_back();
968 if (EnqueueNextRepairingTermIfAny(last_node.constraint,
969 last_node.term_index)) {
#define CHECK_EQ(val1, val2)
#define CHECK_GT(val1, val2)
#define CHECK_NE(val1, val2)
#define CHECK_LE(val1, val2)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
void assign(size_type n, const value_type &val)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
AssignmentAndConstraintFeasibilityMaintainer(const sat::LinearBooleanProblem &problem)
int64 ConstraintLowerBound(ConstraintIndex constraint) const
const std::vector< ConstraintIndex > & PossiblyInfeasibleConstraints() const
void AddBacktrackingLevel()
void UseCurrentStateAsReference()
static const ConstraintIndex kObjectiveConstraint
int NumInfeasibleConstraints() const
std::string DebugString() const
void SetReferenceSolution(const BopSolution &reference_solution)
int64 ConstraintUpperBound(ConstraintIndex constraint) const
size_t NumConstraints() const
bool ConstraintIsFeasible(ConstraintIndex constraint) const
bool Assignment(VariableIndex var) const
int64 ConstraintValue(ConstraintIndex constraint) const
void Assign(const std::vector< sat::Literal > &literals)
const std::vector< sat::Literal > & PotentialOneFlipRepairs()
void ClearAndResize(IntType n)
void AddBacktrackingLevel()
void ChangeState(IntType i, bool should_be_inside)
void SetValue(VariableIndex var, bool value)
bool Value(VariableIndex var) const
~LocalSearchAssignmentIterator()
void SynchronizeSatWrapper()
LocalSearchAssignmentIterator(const ProblemState &problem_state, int max_num_decisions, int max_num_broken_constraints, SatWrapper *sat_wrapper)
std::string DebugString() const
void Synchronize(const ProblemState &problem_state)
double deterministic_time() const
LocalSearchOptimizer(const std::string &name, int max_num_decisions, sat::SatSolver *sat_propagator)
~LocalSearchOptimizer() override
bool IsInitialized() const
void Initialize(int size)
void IgnoreElement(IntType e)
uint64 Hash(const std::vector< IntType > &set) const
sat::Literal GetFlip(ConstraintIndex ct_index, TermIndex term_index) const
static const TermIndex kInvalidTerm
ConstraintIndex ConstraintToRepair() const
bool RepairIsValid(ConstraintIndex ct_index, TermIndex term_index) const
TermIndex NextRepairingTerm(ConstraintIndex ct_index, TermIndex init_term_index, TermIndex start_term_index) const
static const TermIndex kInitTerm
OneFlipConstraintRepairer(const sat::LinearBooleanProblem &problem, const AssignmentAndConstraintFeasibilityMaintainer &maintainer, const sat::VariablesAssignment &sat_assignment)
static const ConstraintIndex kInvalidConstraint
const BopSolution & solution() const
const sat::VariablesAssignment & SatAssignment() const
SatWrapper(sat::SatSolver *sat_solver)
std::vector< sat::Literal > FullSatTrail() const
int ApplyDecision(sat::Literal decision_literal, std::vector< sat::Literal > *propagated_literals)
void ExtractLearnedInfo(LearnedInfo *info)
bool IsModelUnsat() const
double deterministic_time() const
BooleanVariable Variable() const
const Trail & LiteralTrail() const
const VariablesAssignment & Assignment() const
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
void Backtrack(int target_level)
bool IsModelUnsat() const
int CurrentDecisionLevel() const
double deterministic_time() const
bool VariableIsAssigned(BooleanVariable var) const
SharedTimeLimit * time_limit
static const int64 kint64max
static const int32 kint32max
static const int64 kint64min
void ExtractLearnedInfoFromSatSolver(sat::SatSolver *solver, LearnedInfo *info)
constexpr int kObjectiveConstraint
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...