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) {
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_) {
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());
331 flipped_var_trail_.push_back(
var);
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());
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);
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>&
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);
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,
468 const AssignmentAndConstraintFeasibilityMaintainer& maintainer,
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(
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(
511 SortTermsOfEachConstraints(problem.num_variables());
520 int32_t selected_num_branches = std::numeric_limits<int32_t>::max();
526 const std::vector<ConstraintIndex>& infeasible_constraints =
528 for (
int index = infeasible_constraints.size() - 1;
index >= 0; --
index) {
529 const ConstraintIndex& i = infeasible_constraints[
index];
531 --num_infeasible_constraints_left;
536 if (num_infeasible_constraints_left == 0 &&
545 int32_t num_branches = 0;
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 {
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());
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 {
604 const ConstraintTerm term = by_constraint_matrix_[
ct_index][term_index];
606 sat::BooleanVariable(term.var.value()))) {
609 const int64_t new_value =
611 (maintainer_.
Assignment(term.var) ? -term.weight : term.weight);
615 return (new_value >= lb && new_value <= ub);
619 TermIndex term_index)
const {
620 const ConstraintTerm term = by_constraint_matrix_[
ct_index][term_index];
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;
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) {
662 CHECK(propagated_literals !=
nullptr);
664 propagated_literals->clear();
666 const int new_trail_index =
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);
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_,
712 sat_wrapper->SatAssignment()),
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;
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_;
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;
792 UseCurrentStateAsReference();
801 if (use_potential_one_flip_repairs_ &&
802 search_nodes_.size() == max_num_decisions_) {
808 ++num_inspected_one_flip_repairs_;
813 num_improvements_by_one_flip_repairs_++;
814 UseCurrentStateAsReference();
830 if (search_nodes_.empty()) {
831 VLOG(1) << std::string(27,
' ') +
"LS " << max_num_decisions_
832 <<
" finished." <<
" #explored:" << num_nodes_
833 <<
" #stored:" << transposition_table_.size()
834 <<
" #skipped:" << num_skipped_nodes_;
839 const SearchNode node = search_nodes_.back();
840 ApplyDecision(repairer_.
GetFlip(node.constraint, node.term_index));
853 std::string str =
"Search nodes:\n";
854 for (
int i = 0;
i < search_nodes_.size(); ++
i) {
855 str += absl::StrFormat(
" %d: %d %d\n", i,
856 search_nodes_[i].constraint.value(),
857 search_nodes_[i].term_index.value());
864 const int num_backtracks =
868 if (num_backtracks == 0) {
870 maintainer_.
Assign(tmp_propagated_literals_);
872 CHECK_GT(num_backtracks, 0);
873 CHECK_LE(num_backtracks, search_nodes_.size());
876 for (
int i = 0; i < num_backtracks - 1; ++i) {
879 maintainer_.
Assign(tmp_propagated_literals_);
880 search_nodes_.resize(search_nodes_.size() - num_backtracks);
884void LocalSearchAssignmentIterator::InitializeTranspositionTableKey(
885 std::array<int32_t, kStoredMaxDecisions>*
a) {
887 for (
const SearchNode& n : search_nodes_) {
895 while (i < kStoredMaxDecisions) {
901bool LocalSearchAssignmentIterator::NewStateIsInTranspositionTable(
903 if (search_nodes_.size() + 1 > kStoredMaxDecisions)
return false;
906 std::array<int32_t, kStoredMaxDecisions>
a;
907 InitializeTranspositionTableKey(&
a);
908 a[search_nodes_.size()] = l.SignedValue();
909 std::sort(
a.begin(),
a.begin() + 1 + search_nodes_.size());
911 if (transposition_table_.find(
a) == transposition_table_.end()) {
914 ++num_skipped_nodes_;
919void LocalSearchAssignmentIterator::InsertInTranspositionTable() {
921 if (search_nodes_.size() > kStoredMaxDecisions)
return;
924 std::array<int32_t, kStoredMaxDecisions>
a;
925 InitializeTranspositionTableKey(&
a);
926 std::sort(
a.begin(),
a.begin() + search_nodes_.size());
928 transposition_table_.insert(
a);
931bool LocalSearchAssignmentIterator::EnqueueNextRepairingTermIfAny(
932 ConstraintIndex ct_to_repair, TermIndex term_index) {
933 if (term_index == initial_term_index_[ct_to_repair])
return false;
935 term_index = initial_term_index_[ct_to_repair];
939 ct_to_repair, initial_term_index_[ct_to_repair], term_index);
941 if (!use_transposition_table_ ||
942 !NewStateIsInTranspositionTable(
943 repairer_.
GetFlip(ct_to_repair, term_index))) {
944 search_nodes_.push_back(SearchNode(ct_to_repair, term_index));
947 if (term_index == initial_term_index_[ct_to_repair])
return false;
951bool LocalSearchAssignmentIterator::GoDeeper() {
953 if (search_nodes_.size() >= max_num_decisions_) {
981 return EnqueueNextRepairingTermIfAny(ct_to_repair,
985void LocalSearchAssignmentIterator::Backtrack() {
986 while (!search_nodes_.empty()) {
991 if (use_transposition_table_) InsertInTranspositionTable();
993 const SearchNode last_node = search_nodes_.back();
994 search_nodes_.pop_back();
997 if (EnqueueNextRepairingTermIfAny(last_node.constraint,
998 last_node.term_index)) {