207 const LinearBooleanProblem& problem, absl::BitGenRef random)
208 : by_variable_matrix_(problem.num_variables()),
209 constraint_lower_bounds_(),
210 constraint_upper_bounds_(),
211 assignment_(problem,
"Assignment"),
212 reference_(problem,
"Assignment"),
213 constraint_values_(),
214 flipped_var_trail_backtrack_levels_(),
215 flipped_var_trail_(),
216 constraint_set_hasher_(random) {
218 const LinearObjective& objective = problem.objective();
219 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
220 for (
int i = 0; i < objective.literals_size(); ++i) {
221 CHECK_GT(objective.literals(i), 0);
222 CHECK_NE(objective.coefficients(i), 0);
224 const VariableIndex var(objective.literals(i) - 1);
225 const int64_t weight = objective.coefficients(i);
226 by_variable_matrix_[var].push_back(
227 ConstraintEntry(kObjectiveConstraint, weight));
229 constraint_lower_bounds_.push_back(std::numeric_limits<int64_t>::min());
230 constraint_values_.push_back(0);
231 constraint_upper_bounds_.push_back(std::numeric_limits<int64_t>::max());
234 ConstraintIndex num_constraints_with_objective(1);
235 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
236 if (constraint.literals_size() <= 2) {
243 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
244 for (
int i = 0; i < constraint.literals_size(); ++i) {
245 const VariableIndex var(constraint.literals(i) - 1);
246 const int64_t weight = constraint.coefficients(i);
247 by_variable_matrix_[var].push_back(
248 ConstraintEntry(num_constraints_with_objective, weight));
250 constraint_lower_bounds_.push_back(
251 constraint.has_lower_bound() ? constraint.lower_bound()
252 : std::numeric_limits<int64_t>::min());
253 constraint_values_.push_back(0);
254 constraint_upper_bounds_.push_back(
255 constraint.has_upper_bound() ? constraint.upper_bound()
256 : std::numeric_limits<int64_t>::max());
258 ++num_constraints_with_objective;
262 infeasible_constraint_set_.ClearAndResize(
263 ConstraintIndex(constraint_values_.size()));
265 CHECK_EQ(constraint_values_.size(), constraint_lower_bounds_.size());
266 CHECK_EQ(constraint_values_.size(), constraint_upper_bounds_.size());
277 assignment_ = reference_solution;
278 reference_ = assignment_;
279 flipped_var_trail_backtrack_levels_.clear();
280 flipped_var_trail_.clear();
285 for (VariableIndex var(0); var < assignment_.Size(); ++var) {
286 if (assignment_.Value(var)) {
287 for (
const ConstraintEntry& entry : by_variable_matrix_[var]) {
288 constraint_values_[entry.constraint] += entry.weight;
293 MakeObjectiveConstraintInfeasible(1);
298 for (
const VariableIndex var : flipped_var_trail_) {
299 reference_.SetValue(var, assignment_.Value(var));
301 flipped_var_trail_.clear();
302 flipped_var_trail_backtrack_levels_.clear();
304 MakeObjectiveConstraintInfeasible(1);
307void AssignmentAndConstraintFeasibilityMaintainer::
308 MakeObjectiveConstraintInfeasible(
int delta) {
310 CHECK(flipped_var_trail_.empty());
326 absl::Span<const sat::Literal> literals) {
327 for (
const sat::Literal& literal : literals) {
328 const VariableIndex var(literal.Variable().value());
329 const bool value = literal.IsPositive();
330 if (assignment_.Value(var) != value) {
331 flipped_var_trail_.push_back(var);
332 assignment_.SetValue(var, value);
333 for (
const ConstraintEntry& entry : by_variable_matrix_[var]) {
335 constraint_values_[entry.constraint] +=
336 value ? entry.weight : -entry.weight;
338 infeasible_constraint_set_.ChangeState(entry.constraint,
347 flipped_var_trail_backtrack_levels_.push_back(flipped_var_trail_.size());
348 infeasible_constraint_set_.AddBacktrackingLevel();
353 for (
int i = flipped_var_trail_backtrack_levels_.back();
354 i < flipped_var_trail_.size(); ++i) {
355 const VariableIndex var(flipped_var_trail_[i]);
356 const bool new_value = !assignment_.Value(var);
357 DCHECK_EQ(new_value, reference_.Value(var));
358 assignment_.SetValue(var, new_value);
359 for (
const ConstraintEntry& entry : by_variable_matrix_[var]) {
360 constraint_values_[entry.constraint] +=
361 new_value ? entry.weight : -entry.weight;
364 flipped_var_trail_.resize(flipped_var_trail_backtrack_levels_.back());
365 flipped_var_trail_backtrack_levels_.pop_back();
373const std::vector<sat::Literal>&
375 if (!constraint_set_hasher_.IsInitialized()) {
376 InitializeConstraintSetHasher();
388 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci,
false));
390 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(ci,
true));
394 tmp_potential_repairs_.clear();
395 const auto it = hash_to_potential_repairs_.find(hash);
396 if (it != hash_to_potential_repairs_.end()) {
399 if (assignment_.
Value(VariableIndex(literal.
Variable().value())) !=
401 tmp_potential_repairs_.push_back(literal);
405 return tmp_potential_repairs_;
411 for (
bool value : assignment_) {
412 str += value ?
" 1 " :
" 0 ";
414 str +=
"\nFlipped variables: ";
416 for (
const VariableIndex var : flipped_var_trail_) {
417 str += absl::StrFormat(
" %d", var.value());
419 str +=
"\nmin curr max\n";
420 for (ConstraintIndex ct(0); ct < constraint_values_.size(); ++ct) {
421 if (constraint_lower_bounds_[ct] == std::numeric_limits<int64_t>::min()) {
422 str += absl::StrFormat(
"- %d %d\n", constraint_values_[ct],
423 constraint_upper_bounds_[ct]);
426 absl::StrFormat(
"%d %d %d\n", constraint_lower_bounds_[ct],
427 constraint_values_[ct], constraint_upper_bounds_[ct]);
433void AssignmentAndConstraintFeasibilityMaintainer::
434 InitializeConstraintSetHasher() {
435 const int num_constraints_with_objective = constraint_upper_bounds_.size();
440 constraint_set_hasher_.Initialize(2 * num_constraints_with_objective);
441 constraint_set_hasher_.IgnoreElement(
443 constraint_set_hasher_.IgnoreElement(
445 for (VariableIndex var(0); var < by_variable_matrix_.size(); ++var) {
448 for (
const bool flip_is_positive : {
true,
false}) {
450 for (
const ConstraintEntry& entry : by_variable_matrix_[var]) {
451 const bool coeff_is_positive = entry.weight > 0;
452 hash ^= constraint_set_hasher_.Hash(FromConstraintIndex(
454 flip_is_positive ? coeff_is_positive : !coeff_is_positive));
456 hash_to_potential_repairs_[hash].push_back(
457 sat::Literal(sat::BooleanVariable(var.value()), flip_is_positive));
467 const LinearBooleanProblem& problem,
469 const sat::VariablesAssignment& sat_assignment)
470 : by_constraint_matrix_(problem.constraints_size() + 1),
471 maintainer_(maintainer),
472 sat_assignment_(sat_assignment) {
479 ConstraintIndex num_constraint(0);
480 const LinearObjective& objective = problem.objective();
481 CHECK_EQ(objective.literals_size(), objective.coefficients_size());
482 for (
int i = 0;
i < objective.literals_size(); ++
i) {
483 CHECK_GT(objective.literals(i), 0);
484 CHECK_NE(objective.coefficients(i), 0);
486 const VariableIndex var(objective.literals(i) - 1);
487 const int64_t weight = objective.coefficients(i);
488 by_constraint_matrix_[num_constraint].push_back(
489 ConstraintTerm(var, weight));
493 for (
const LinearBooleanConstraint& constraint : problem.constraints()) {
494 if (constraint.literals_size() <= 2) {
502 CHECK_EQ(constraint.literals_size(), constraint.coefficients_size());
503 for (
int i = 0;
i < constraint.literals_size(); ++
i) {
504 const VariableIndex var(constraint.literals(i) - 1);
505 const int64_t weight = constraint.coefficients(i);
506 by_constraint_matrix_[num_constraint].push_back(
507 ConstraintTerm(var, weight));
511 SortTermsOfEachConstraints(problem.num_variables());
520 int32_t selected_num_branches = std::numeric_limits<int32_t>::max();
521 int num_infeasible_constraints_left = maintainer_.NumInfeasibleConstraints();
526 const std::vector<ConstraintIndex>& infeasible_constraints =
527 maintainer_.PossiblyInfeasibleConstraints();
528 for (
int index = infeasible_constraints.size() - 1; index >= 0; --index) {
529 const ConstraintIndex& i = infeasible_constraints[index];
530 if (maintainer_.ConstraintIsFeasible(i))
continue;
531 --num_infeasible_constraints_left;
536 if (num_infeasible_constraints_left == 0 &&
541 const int64_t constraint_value = maintainer_.ConstraintValue(i);
542 const int64_t lb = maintainer_.ConstraintLowerBound(i);
543 const int64_t ub = maintainer_.ConstraintUpperBound(i);
545 int32_t num_branches = 0;
547 if (sat_assignment_.VariableIsAssigned(
548 sat::BooleanVariable(term.var.value()))) {
551 const int64_t new_value =
553 (maintainer_.Assignment(term.var) ? -term.weight : term.weight);
554 if (new_value >= lb && new_value <= ub) {
556 if (num_branches >= selected_num_branches)
break;
561 if (num_branches == 0)
continue;
562 if (num_branches < selected_num_branches) {
564 selected_num_branches = num_branches;
565 if (num_branches == 1)
break;
572 ConstraintIndex ct_index, TermIndex init_term_index,
573 TermIndex start_term_index)
const {
575 by_constraint_matrix_[ct_index];
576 const int64_t constraint_value = maintainer_.
ConstraintValue(ct_index);
580 const TermIndex end_term_index(terms.size() + init_term_index + 1);
581 for (TermIndex loop_term_index(
582 start_term_index + 1 +
583 (start_term_index < init_term_index ? terms.size() : 0));
584 loop_term_index < end_term_index; ++loop_term_index) {
585 const TermIndex term_index(loop_term_index % terms.size());
587 if (sat_assignment_.VariableIsAssigned(
588 sat::BooleanVariable(term.
var.value()))) {
591 const int64_t new_value =
594 if (new_value >= lb && new_value <= ub) {
602 TermIndex term_index)
const {
603 if (maintainer_.ConstraintIsFeasible(ct_index))
return false;
604 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
605 if (sat_assignment_.VariableIsAssigned(
606 sat::BooleanVariable(term.var.value()))) {
609 const int64_t new_value =
610 maintainer_.ConstraintValue(ct_index) +
611 (maintainer_.Assignment(term.var) ? -term.weight : term.weight);
613 const int64_t lb = maintainer_.ConstraintLowerBound(ct_index);
614 const int64_t ub = maintainer_.ConstraintUpperBound(ct_index);
615 return (new_value >= lb && new_value <= ub);
619 TermIndex term_index)
const {
620 const ConstraintTerm term = by_constraint_matrix_[ct_index][term_index];
621 const bool value = maintainer_.
Assignment(term.var);
622 return sat::Literal(sat::BooleanVariable(term.var.value()), !value);
625void OneFlipConstraintRepairer::SortTermsOfEachConstraints(
int num_variables) {
629 kObjectiveConstraint]) {
630 objective[term.var] = std::abs(term.weight);
633 by_constraint_matrix_) {
634 std::sort(terms.begin(), terms.end(),
635 [&objective](
const ConstraintTerm& a,
const ConstraintTerm& b) {
636 return objective[a.var] > objective[b.var];
650 std::vector<sat::Literal> propagated_literals;
651 const sat::Trail& trail = sat_solver_->LiteralTrail();
652 for (
int trail_index = 0; trail_index < trail.Index(); ++trail_index) {
653 propagated_literals.push_back(trail[trail_index]);
655 return propagated_literals;
659 std::vector<sat::Literal>* propagated_literals) {
660 CHECK(!sat_solver_->Assignment().VariableIsAssigned(
662 CHECK(propagated_literals !=
nullptr);
664 propagated_literals->clear();
665 const int old_decision_level = sat_solver_->CurrentDecisionLevel();
666 const int new_trail_index =
667 sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision_literal);
668 if (sat_solver_->IsModelUnsat()) {
669 return old_decision_level + 1;
676 for (
int trail_index = new_trail_index;
677 trail_index < propagation_trail.
Index(); ++trail_index) {
678 propagated_literals->push_back(propagation_trail[trail_index]);
686 if (old_decision_level > 0) {
687 sat_solver_->
Backtrack(old_decision_level - 1);
696 return sat_solver_->deterministic_time();
704 const ProblemState& problem_state,
int max_num_decisions,
705 int max_num_broken_constraints, absl::BitGenRef random,
707 : max_num_decisions_(max_num_decisions),
708 max_num_broken_constraints_(max_num_broken_constraints),
709 maintainer_(problem_state.original_problem(), random),
710 sat_wrapper_(sat_wrapper),
711 repairer_(problem_state.original_problem(), maintainer_,
715 problem_state.original_problem().constraints_size() + 1,
717 use_transposition_table_(false),
718 use_potential_one_flip_repairs_(false),
720 num_skipped_nodes_(0),
721 num_improvements_(0),
722 num_improvements_by_one_flip_repairs_(0),
723 num_inspected_one_flip_repairs_(0) {}
726 VLOG(1) <<
"LS " << max_num_decisions_
727 <<
"\n num improvements: " << num_improvements_
728 <<
"\n num improvements with one flip repairs: "
729 << num_improvements_by_one_flip_repairs_
730 <<
"\n num inspected one flip repairs: "
731 << num_inspected_one_flip_repairs_;
735 const ProblemState& problem_state) {
736 better_solution_has_been_found_ =
false;
737 maintainer_.SetReferenceSolution(problem_state.solution());
738 for (
const SearchNode& node : search_nodes_) {
739 initial_term_index_[node.constraint] = node.term_index;
741 search_nodes_.clear();
742 transposition_table_.clear();
744 num_skipped_nodes_ = 0;
751 CHECK_EQ(better_solution_has_been_found_,
false);
752 const std::vector<SearchNode> copy = search_nodes_;
753 sat_wrapper_->BacktrackAll();
754 maintainer_.BacktrackAll();
760 maintainer_.Assign(sat_wrapper_->FullSatTrail());
762 search_nodes_.clear();
763 for (
const SearchNode& node : copy) {
764 if (!repairer_.RepairIsValid(node.constraint, node.term_index))
break;
765 search_nodes_.push_back(node);
766 ApplyDecision(repairer_.GetFlip(node.constraint, node.term_index));
770void LocalSearchAssignmentIterator::UseCurrentStateAsReference() {
771 better_solution_has_been_found_ =
true;
779 for (
const SearchNode& node : search_nodes_) {
780 initial_term_index_[node.constraint] = node.term_index;
782 search_nodes_.clear();
783 transposition_table_.clear();
785 num_skipped_nodes_ = 0;
790 if (sat_wrapper_->IsModelUnsat())
return false;
791 if (maintainer_.IsFeasible()) {
792 UseCurrentStateAsReference();
801 if (use_potential_one_flip_repairs_ &&
802 search_nodes_.size() == max_num_decisions_) {
803 for (
const sat::Literal literal : maintainer_.PotentialOneFlipRepairs()) {
804 if (sat_wrapper_->SatAssignment().VariableIsAssigned(
808 ++num_inspected_one_flip_repairs_;
811 ApplyDecision(literal);
812 if (maintainer_.IsFeasible()) {
813 num_improvements_by_one_flip_repairs_++;
814 UseCurrentStateAsReference();
817 maintainer_.BacktrackOneLevel();
818 sat_wrapper_->BacktrackOneLevel();
830 if (search_nodes_.empty()) {
831 VLOG(1) << std::string(27,
' ') +
"LS " << max_num_decisions_
833 <<
" #explored:" << num_nodes_
834 <<
" #stored:" << transposition_table_.size()
835 <<
" #skipped:" << num_skipped_nodes_;
840 const SearchNode node = search_nodes_.back();
841 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
850 return sat_wrapper_->deterministic_time() * 1.2;
854 std::string str =
"Search nodes:\n";
855 for (
int i = 0;
i < search_nodes_.size(); ++
i) {
856 str += absl::StrFormat(
" %d: %d %d\n", i,
857 search_nodes_[i].constraint.value(),
858 search_nodes_[i].term_index.value());
863void LocalSearchAssignmentIterator::ApplyDecision(
sat::Literal literal) {
865 const int num_backtracks =
866 sat_wrapper_->ApplyDecision(literal, &tmp_propagated_literals_);
869 if (num_backtracks == 0) {
870 maintainer_.AddBacktrackingLevel();
871 maintainer_.Assign(tmp_propagated_literals_);
873 CHECK_GT(num_backtracks, 0);
874 CHECK_LE(num_backtracks, search_nodes_.size());
877 for (
int i = 0; i < num_backtracks - 1; ++i) {
880 maintainer_.
Assign(tmp_propagated_literals_);
881 search_nodes_.resize(search_nodes_.size() - num_backtracks);
885void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
886 std::array<int32_t, kStoredMaxDecisions>* a) {
888 for (
const SearchNode& n : search_nodes_) {
891 (*a)[
i] = -repairer_.GetFlip(n.constraint, n.term_index).SignedValue();
896 while (i < kStoredMaxDecisions) {
902bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
904 if (search_nodes_.size() + 1 > kStoredMaxDecisions)
return false;
907 std::array<int32_t, kStoredMaxDecisions> a;
908 InitializeTranspositionTableKey(&a);
909 a[search_nodes_.size()] = l.SignedValue();
910 std::sort(a.begin(), a.begin() + 1 + search_nodes_.size());
912 if (transposition_table_.find(a) == transposition_table_.end()) {
915 ++num_skipped_nodes_;
920void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
922 if (search_nodes_.size() > kStoredMaxDecisions)
return;
925 std::array<int32_t, kStoredMaxDecisions> a;
926 InitializeTranspositionTableKey(&a);
927 std::sort(a.begin(), a.begin() + search_nodes_.size());
929 transposition_table_.insert(a);
932bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
933 ConstraintIndex ct_to_repair, TermIndex term_index) {
934 if (term_index == initial_term_index_[ct_to_repair])
return false;
936 term_index = initial_term_index_[ct_to_repair];
939 term_index = repairer_.NextRepairingTerm(
940 ct_to_repair, initial_term_index_[ct_to_repair], term_index);
942 if (!use_transposition_table_ ||
943 !NewStateIsInTranspositionTable(
944 repairer_.GetFlip(ct_to_repair, term_index))) {
945 search_nodes_.push_back(SearchNode(ct_to_repair, term_index));
948 if (term_index == initial_term_index_[ct_to_repair])
return false;
952bool LocalSearchAssignmentIterator::GoDeeper() {
954 if (search_nodes_.size() >= max_num_decisions_) {
965 if (maintainer_.NumInfeasibleConstraints() > max_num_broken_constraints_) {
970 const ConstraintIndex ct_to_repair = repairer_.ConstraintToRepair();
982 return EnqueueNextRepairingTermIfAny(ct_to_repair,
986void LocalSearchAssignmentIterator::Backtrack() {
987 while (!search_nodes_.empty()) {
992 if (use_transposition_table_) InsertInTranspositionTable();
994 const SearchNode last_node = search_nodes_.back();
995 search_nodes_.pop_back();
996 maintainer_.BacktrackOneLevel();
997 sat_wrapper_->BacktrackOneLevel();
998 if (EnqueueNextRepairingTermIfAny(last_node.constraint,
999 last_node.term_index)) {