24#include "absl/container/flat_hash_set.h"
25#include "absl/log/check.h"
26#include "absl/meta/type_traits.h"
27#include "absl/random/distributions.h"
28#include "absl/strings/str_cat.h"
29#include "absl/types/span.h"
32#include "ortools/sat/cp_model.pb.h"
48#include "ortools/sat/sat_parameters.pb.h"
60 const IntegerValue lb = integer_trail->
LowerBound(var);
70 if (variables.contains(var)) {
72 }
else if (variables.contains(
NegationOf(var))) {
80 const IntegerValue var_lb = integer_trail->
LowerBound(var);
81 const IntegerValue var_ub = integer_trail->
UpperBound(var);
82 CHECK_LT(var_lb, var_ub);
84 const IntegerValue chosen_value =
85 var_lb + std::max(IntegerValue(1), (var_ub - var_lb) / IntegerValue(2));
92 const IntegerValue lb = integer_trail->
LowerBound(var);
93 const IntegerValue ub = integer_trail->UpperBound(var);
95 const absl::flat_hash_set<IntegerVariable>& variables =
103 const bool branch_down_feasible = value >= lb && value < ub;
104 const bool branch_up_feasible = value > lb && value <= ub;
105 if (variables.contains(var) && branch_down_feasible) {
107 }
else if (variables.contains(
NegationOf(var)) && branch_up_feasible) {
109 }
else if (branch_down_feasible) {
111 }
else if (branch_up_feasible) {
118 auto* parameters = model->
GetOrCreate<SatParameters>();
122 const auto& it = lp_dispatcher->find(positive_var);
124 it == lp_dispatcher->end() ? nullptr : it->second;
137 const IntegerValue value = IntegerValue(
154 const int proto_var =
172 absl::Span<const IntegerVariable> vars,
Model* model) {
175 [ vars = std::vector<IntegerVariable>(vars.begin(), vars.end()),
177 for (
const IntegerVariable var : vars) {
188 return [lp_values, integer_trail, model]() {
189 double best_fractionality = 0.0;
191 for (IntegerVariable var(0); var < lp_values->size(); var += 2) {
192 if (integer_trail->IsFixed(var))
continue;
193 const double lp_value = (*lp_values)[var];
194 const double fractionality = std::abs(lp_value - std::round(lp_value));
195 if (fractionality > best_fractionality) {
196 best_fractionality = fractionality;
200 var, IntegerValue(std::floor(lp_value)), model));
212 return [lp_values, encoder, pseudo_costs, integer_trail]() {
213 double best_score = 0.0;
215 for (IntegerVariable var(0); var < lp_values->size(); var += 2) {
217 const IntegerValue lb = integer_trail->LowerBound(var);
218 const IntegerValue ub = integer_trail->UpperBound(var);
219 if (lb != 0 || ub != 1)
continue;
222 const LiteralIndex index =
226 const double lp_value = (*lp_values)[var];
228 pseudo_costs->BoolPseudoCost(
Literal(index), lp_value);
229 if (score > best_score) {
241 return [lp_values, pseudo_costs]() {
242 double best_score = 0.0;
244 for (IntegerVariable var(0); var < lp_values->size(); var += 2) {
246 pseudo_costs->EvaluateVar(var, *lp_values);
258 if (info.
score > best_score) {
259 best_score = info.
score;
274std::function<BooleanOrIntegerLiteral()>
276 absl::Span<const IntegerVariable> vars,
Model* model) {
279 [ vars = std::vector<IntegerVariable>(vars.begin(), vars.end()),
282 IntegerValue candidate_lb;
283 for (
const IntegerVariable var : vars) {
284 const IntegerValue lb = integer_trail->LowerBound(var);
298 return [heuristics]() {
299 for (
const auto& h : heuristics) {
301 if (decision.
HasValue())
return decision;
309 value_selection_heuristics,
317 if (!current_decision.
HasValue())
return current_decision;
322 sat_policy->InStablePhase()) {
323 return current_decision;
328 for (
const auto& value_heuristic : value_selection_heuristics) {
333 return current_decision;
340 for (
const IntegerVariable var : encoder->GetAllAssociatedVariables(
343 for (
const auto& value_heuristic : value_selection_heuristics) {
349 return current_decision;
354 auto* lp_constraints =
356 int num_lp_variables = 0;
358 num_lp_variables += lp->NumVariables();
360 const int num_integer_variables =
362 return (num_integer_variables <= 2 * num_lp_variables);
372 const SatParameters& parameters = *(model->
GetOrCreate<SatParameters>());
374 value_selection_heuristics;
381 (parameters.exploit_integer_lp_solution() ||
382 parameters.exploit_all_lp_solution())) {
383 value_selection_heuristics.push_back([model](IntegerVariable var) {
389 if (parameters.exploit_best_solution()) {
391 if (response_manager !=
nullptr) {
392 VLOG(3) <<
"Using best solution value selection heuristic.";
393 value_selection_heuristics.push_back(
394 [model, response_manager](IntegerVariable var) {
396 var, response_manager->SolutionsRepository(), model);
402 if (parameters.exploit_objective()) {
405 value_selection_heuristics.push_back([integer_trail, objective_definition](
406 IntegerVariable var) {
412 var_selection_heuristic, model);
419 return [sat_solver, trail, decision_policy] {
420 const bool all_assigned = trail->Index() == sat_solver->
NumVariables();
422 const Literal result = decision_policy->NextBranch();
432 const IntegerVariable obj_var = objective_definition->
objective_var;
437 return [obj_var, integer_trail, sat_solver, random]() {
439 const int level = sat_solver->CurrentDecisionLevel();
442 const IntegerValue obj_lb = integer_trail->LowerBound(obj_var);
443 const IntegerValue obj_ub = integer_trail->UpperBound(obj_var);
444 if (obj_lb == obj_ub)
return result;
445 const IntegerValue mid = (obj_ub - obj_lb) / 2;
446 const IntegerValue new_ub =
447 obj_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
456 const bool has_objective =
458 if (!has_objective) {
464 return [pseudo_costs, integer_trail]() {
465 const IntegerVariable chosen_var = pseudo_costs->GetBestDecisionVar();
483 const int64_t randomization_size = std::max<int64_t>(
485 model->
GetOrCreate<SatParameters>()->search_random_variable_pool_size());
490 const int num_intervals = repo->NumIntervals();
492 bool rev_is_in_dive =
false;
493 std::vector<IntervalVariable> intervals(num_intervals);
494 std::vector<IntegerValue> cached_start_mins(num_intervals);
495 for (IntervalVariable
i(0);
i < num_intervals; ++
i) {
496 intervals[
i.value()] =
i;
500 return [=]()
mutable {
516 bool operator<(
const ToSchedule& other)
const {
517 return std::tie(start_min, start_max, size_min, noise) <
518 std::tie(other.start_min, other.start_max, other.size_min,
524 bool MightBeBetter(
const ToSchedule& other)
const {
525 return std::tie(start_min, start_max) <=
526 std::tie(other.start_min, other.start_max);
529 std::vector<ToSchedule> top_decisions;
530 top_decisions.reserve(randomization_size);
531 top_decisions.resize(1);
534 rev_int_repo->SaveState(&rev_fixed);
538 for (
int i = rev_fixed;
i < num_intervals; ++
i) {
539 const ToSchedule& worst = top_decisions.back();
540 if (rev_is_in_dive && cached_start_mins[
i] > worst.start_min) {
544 const IntervalVariable interval = intervals[
i];
545 if (repo->IsAbsent(interval)) {
546 std::swap(intervals[
i], intervals[rev_fixed]);
547 std::swap(cached_start_mins[
i], cached_start_mins[rev_fixed]);
554 if (repo->IsPresent(interval) && integer_trail->IsFixed(start) &&
555 integer_trail->IsFixed(end)) {
556 std::swap(intervals[
i], intervals[rev_fixed]);
557 std::swap(cached_start_mins[
i], cached_start_mins[rev_fixed]);
562 ToSchedule candidate;
563 if (repo->IsOptional(interval)) {
568 const Literal lit = repo->PresenceLiteral(interval);
569 candidate.start_min = integer_trail->ConditionalLowerBound(lit, start);
570 candidate.start_max = integer_trail->ConditionalUpperBound(lit, start);
572 candidate.start_min = integer_trail->LowerBound(start);
573 candidate.start_max = integer_trail->UpperBound(start);
575 cached_start_mins[
i] = candidate.start_min;
576 if (top_decisions.size() < randomization_size ||
577 candidate.MightBeBetter(worst)) {
584 candidate.start = start;
586 candidate.presence = repo->IsOptional(interval)
587 ? repo->PresenceLiteral(interval).Index()
590 std::max(integer_trail->LowerBound(repo->Size(interval)),
591 integer_trail->LowerBound(end) - candidate.start_min);
592 candidate.noise = absl::Uniform(*random, 0.0, 1.0);
594 if (top_decisions.size() == randomization_size) {
596 if (worst < candidate)
continue;
597 top_decisions.pop_back();
599 top_decisions.push_back(candidate);
600 if (top_decisions.size() > 1) {
601 std::sort(top_decisions.begin(), top_decisions.end());
608 watcher->SetUntilNextBacktrack(&rev_is_in_dive);
610 const ToSchedule best =
611 top_decisions.size() == 1
612 ? top_decisions.front()
613 : top_decisions[absl::Uniform(
614 *random, 0,
static_cast<int>(top_decisions.size()))];
615 if (top_decisions.size() > 1) {
616 VLOG(2) <<
"Choose among " << top_decisions.size() <<
" "
617 << best.start_min <<
" " << best.size_min
618 <<
"[t=" << top_decisions.front().start_min
619 <<
", s=" << top_decisions.front().size_min
620 <<
", t=" << top_decisions.back().start_min
621 <<
", s=" << top_decisions.back().size_min <<
"]";
628 heuristic->next_decision_override = [trail, integer_trail, best,
629 num_times]()
mutable {
630 if (++num_times > 5) {
634 VLOG(3) <<
"Skipping ... ";
640 if (!trail->Assignment().LiteralIsAssigned(
Literal(best.presence))) {
641 VLOG(3) <<
"assign " << best.presence;
644 if (trail->Assignment().LiteralIsFalse(
Literal(best.presence))) {
645 VLOG(2) <<
"unperformed.";
651 if (!integer_trail->IsFixed(best.start)) {
652 const IntegerValue start_min = integer_trail->LowerBound(best.start);
653 VLOG(3) <<
"start == " << start_min;
658 if (!integer_trail->IsFixed(best.end)) {
659 const IntegerValue end_min = integer_trail->LowerBound(best.end);
660 VLOG(3) <<
"end == " << end_min;
665 const IntegerValue start = integer_trail->LowerBound(best.start);
666 VLOG(2) <<
"Fixed @[" << start <<
","
667 << integer_trail->LowerBound(best.end) <<
"]"
669 ? absl::StrCat(
" presence=",
672 << (best.start_min < start ? absl::StrCat(
" start_at_selection=",
673 best.start_min.value())
678 return heuristic->next_decision_override();
684bool PrecedenceIsBetter(SchedulingConstraintHelper* helper,
int a,
685 SchedulingConstraintHelper* other_helper,
int other_a) {
686 return std::make_tuple(helper->StartMin(a), helper->StartMax(a),
687 helper->SizeMin(a)) <
688 std::make_tuple(other_helper->StartMin(other_a),
689 other_helper->StartMax(other_a),
690 other_helper->SizeMin(other_a));
707 if (!helper->SynchronizeAndSetTimeDirection(
true)) {
716 for (
auto [
b, time] : helper->TaskByIncreasingStartMin()) {
717 if (helper->IsAbsent(
b))
continue;
718 if (a == -1 || helper->EndMin(a) <= helper->StartMin(
b)) {
724 if (PrecedenceIsBetter(helper,
b, helper, a)) {
728 if (helper->EndMin(a) <= helper->StartMin(
b)) {
736 if (best_helper ==
nullptr ||
737 PrecedenceIsBetter(helper, a, best_helper, best_before)) {
738 best_helper = helper;
746 if (best_helper !=
nullptr) {
748 for (
const int t : {best_before, best_after}) {
755 VLOG(2) <<
"New disjunctive precedence: "
761 repo->GetOrCreateDisjunctivePrecedenceLiteral(a,
b));
782 return [repo, integer_trail, trail, search_helper]() {
786 for (
const auto h : repo->AllCumulativeHelpers()) {
787 auto* helper = h.task_helper;
788 if (!helper->SynchronizeAndSetTimeDirection(
true)) {
792 const int num_tasks = helper->NumTasks();
793 std::vector<IntegerValue> added_demand(num_tasks, 0);
796 const auto& by_smin = helper->TaskByIncreasingStartMin();
797 const auto& by_emin = helper->TaskByIncreasingEndMin();
798 const IntegerValue capacity_max = integer_trail->UpperBound(h.capacity);
801 IntegerValue current_height = 0;
802 int first_skipped_task = -1;
808 while (!found && next_end < num_tasks) {
809 IntegerValue time = by_emin[next_end].time;
810 if (next_start < num_tasks) {
811 time = std::min(time, by_smin[next_start].time);
816 while (next_end < num_tasks && by_emin[next_end].time == time) {
817 const int t = by_emin[next_end].task_index;
818 if (!helper->IsPresent(t))
continue;
819 if (added_demand[t] > 0) {
820 current_height -= added_demand[t];
824 added_demand[t] = -1;
832 while (next_start < num_tasks && by_smin[next_start].time == time) {
833 const int t = by_smin[next_start].task_index;
834 if (!helper->IsPresent(t))
continue;
835 if (added_demand[t] == -1)
continue;
836 const IntegerValue demand_min = h.demand_helper->DemandMin(t);
837 if (current_height + demand_min <= capacity_max) {
839 added_demand[t] = demand_min;
840 current_height += demand_min;
841 }
else if (first_skipped_task == -1) {
843 first_skipped_task = t;
852 if (first_skipped_task == -1) {
853 CHECK_EQ(num_added, num_tasks);
861 std::vector<int> open_tasks;
862 for (
int t = 0; t < num_tasks; ++t) {
863 if (added_demand[t] <= 0)
continue;
864 open_tasks.push_back(t);
866 open_tasks.push_back(first_skipped_task);
873 bool found_precedence_to_add =
false;
874 std::vector<Literal> conflict;
875 helper->ClearReason();
876 for (
const int s : open_tasks) {
877 for (
const int t : open_tasks) {
878 if (s == t)
continue;
882 CHECK_LT(helper->StartMin(s), helper->EndMin(t));
883 CHECK_LT(helper->StartMin(t), helper->EndMin(s));
886 const LiteralIndex existing = repo->GetPrecedenceLiteral(
887 helper->Ends()[s], helper->Starts()[t]);
891 CHECK(!trail->Assignment().LiteralIsTrue(
Literal(existing)))
892 << helper->TaskDebugString(s) <<
" ( <= ?) "
893 << helper->TaskDebugString(t);
898 if (trail->Assignment().LiteralIsFalse(
Literal(existing))) {
899 conflict.push_back(
Literal(existing));
904 if (helper->EndMin(s) > helper->StartMax(t)) {
905 helper->AddReasonForBeingBefore(t, s);
910 CHECK(repo->CreatePrecedenceLiteral(helper->Ends()[s],
911 helper->Starts()[t]));
915 best_helper = helper;
918 found_precedence_to_add =
true;
921 if (found_precedence_to_add)
break;
923 if (found_precedence_to_add)
break;
931 std::vector<IntegerLiteral> integer_reason =
932 *helper->MutableIntegerReason();
933 if (!h.capacity.IsConstant()) {
934 integer_reason.push_back(
935 integer_trail->UpperBoundAsLiteral(h.capacity));
937 const auto& demands = h.demand_helper->Demands();
938 for (
const int t : open_tasks) {
939 if (helper->IsOptional(t)) {
940 CHECK(trail->Assignment().LiteralIsTrue(helper->PresenceLiteral(t)));
941 conflict.push_back(helper->PresenceLiteral(t).Negated());
945 integer_reason.push_back(integer_trail->LowerBoundAsLiteral(d));
948 integer_trail->ReportConflict(conflict, integer_reason);
949 search_helper->NotifyThatConflictWasFoundDuringGetDecision();
951 LOG(INFO) <<
"Conflict between precedences !";
952 for (
const int t : open_tasks) LOG(INFO) << helper->TaskDebugString(t);
959 if (best_helper !=
nullptr) {
960 VLOG(2) <<
"New precedence: " << best_helper->
TaskDebugString(best_before)
964 repo->CreatePrecedenceLiteral(end_a, start_b);
966 repo->GetPrecedenceLiteral(end_a, start_b));
974 bool lns_mode,
Model* model) {
983 std::vector<double> weights;
986 policies.push_back(
SequentialSearch({sat_policy, heuristics.fixed_search}));
987 weights.push_back(5);
990 if (heuristics.user_search !=
nullptr) {
992 {heuristics.user_search, sat_policy, heuristics.fixed_search}));
993 weights.push_back(1);
997 policies.push_back(
SequentialSearch({heuristics.heuristic_search, sat_policy,
998 heuristics.integer_completion_search}));
999 weights.push_back(1);
1004 std::discrete_distribution<int> var_dist(weights.begin(), weights.end());
1008 value_selection_heuristics;
1009 std::vector<int> value_selection_weight;
1012 const int linearization_level =
1013 model->
GetOrCreate<SatParameters>()->linearization_level();
1015 value_selection_heuristics.push_back([model](IntegerVariable var) {
1018 value_selection_weight.push_back(linearization_level == 2 ? 4 : 2);
1024 CHECK(response_manager !=
nullptr);
1025 value_selection_heuristics.push_back(
1026 [model, response_manager](IntegerVariable var) {
1028 var, response_manager->SolutionsRepository(), model);
1030 value_selection_weight.push_back(5);
1035 value_selection_heuristics.push_back([integer_trail](IntegerVariable var) {
1038 value_selection_weight.push_back(1);
1041 value_selection_weight.push_back(10);
1045 std::discrete_distribution<int> val_dist(value_selection_weight.begin(),
1046 value_selection_weight.end());
1048 int policy_index = 0;
1049 int val_policy_index = 0;
1052 return [=]()
mutable {
1056 decision_policy->ResetDecisionHeuristic();
1060 if (objective !=
nullptr && absl::Bernoulli(*random, 0.2)) {
1062 IntegerValue max_abs_weight = 0;
1063 for (
const IntegerValue coeff : objective->coeffs) {
1064 max_abs_weight = std::max(max_abs_weight,
IntTypeAbs(coeff));
1066 const double max_abs_weight_double =
ToDouble(max_abs_weight);
1068 const int objective_size = objective->vars.size();
1069 for (
int i = 0;
i < objective_size; ++
i) {
1070 const IntegerVariable var = objective->vars[
i];
1071 if (integer_trail->LowerBound(var) != 0)
continue;
1072 if (integer_trail->UpperBound(var) != 1)
continue;
1078 const IntegerValue coeff = objective->coeffs[
i];
1079 const double abs_weight =
1080 std::abs(
ToDouble(objective->coeffs[
i])) / max_abs_weight_double;
1085 decision_policy->SetAssignmentPreference(
1086 coeff > 0 ? literal.
Negated() : literal, abs_weight);
1091 policy_index = var_dist(*(random));
1094 val_policy_index = val_dist(*(random));
1099 if (!current_decision.
HasValue())
return current_decision;
1102 if (val_policy_index >= value_selection_heuristics.size()) {
1103 return current_decision;
1108 value_selection_heuristics[val_policy_index](
1111 return current_decision;
1115 for (
const IntegerVariable var : encoder->GetAllAssociatedVariables(
1119 value_selection_heuristics[val_policy_index](var);
1124 return current_decision;
1129 absl::Span<const BooleanOrIntegerVariable> vars,
1130 absl::Span<const IntegerValue> values,
Model* model) {
1141 *rev_start_index = 0;
1145 std::vector<BooleanOrIntegerVariable>(vars.begin(), vars.end()),
1146 values = std::vector<IntegerValue>(values.begin(), values.end())]() {
1147 rev_int_repo->SaveState(rev_start_index);
1148 for (
int i = *rev_start_index;
i < vars.size(); ++
i) {
1149 const IntegerValue value = values[
i];
1151 if (trail->Assignment().VariableIsAssigned(vars[
i].bool_var))
continue;
1154 *rev_start_index =
i;
1156 Literal(vars[
i].bool_var, value == 1).Index());
1158 const IntegerVariable integer_var = vars[
i].int_var;
1159 if (integer_trail->IsFixed(integer_var))
continue;
1163 positive_var, positive_var != integer_var ? -value : value, model);
1166 *rev_start_index =
i;
1179 bool reset_at_next_call =
true;
1180 int next_num_failures = 0;
1181 return [=]()
mutable {
1182 if (reset_at_next_call) {
1184 reset_at_next_call =
false;
1185 }
else if (solver->
num_failures() >= next_num_failures) {
1186 reset_at_next_call =
true;
1188 return reset_at_next_call;
1194 return [policy]() {
return policy->ShouldRestart(); };
1199std::function<BooleanOrIntegerLiteral()> WrapIntegerLiteralHeuristic(
1200 std::function<IntegerLiteral()> f) {
1201 return [f]() {
return BooleanOrIntegerLiteral(f()); };
1213 const SatParameters& parameters = *(model->
GetOrCreate<SatParameters>());
1214 switch (parameters.search_branching()) {
1215 case SatParameters::AUTOMATIC_SEARCH: {
1221 if (parameters.use_objective_lb_search()) {
1230 case SatParameters::FIXED_SEARCH: {
1236 if (parameters.randomize_search()) {
1243 auto no_restart = []() {
return false; };
1247 case SatParameters::PARTIAL_FIXED_SEARCH: {
1264 case SatParameters::HINT_SEARCH: {
1269 auto no_restart = []() {
return false; };
1273 case SatParameters::PORTFOLIO_SEARCH: {
1279 case SatParameters::LP_SEARCH: {
1281 for (
const auto& ct :
1283 lp_heuristics.push_back(WrapIntegerLiteralHeuristic(
1284 ct->HeuristicLpReducedCostAverageBranching()));
1286 if (lp_heuristics.empty()) {
1301 case SatParameters::PSEUDO_COST_SEARCH: {
1310 case SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH: {
1319 case SatParameters::RANDOMIZED_SEARCH: {
1330 incomplete_heuristics,
1333 complete_heuristics.reserve(incomplete_heuristics.size());
1334 for (
const auto& incomplete : incomplete_heuristics) {
1335 complete_heuristics.push_back(
1338 return complete_heuristics;
1342 : parameters_(*model->GetOrCreate<SatParameters>()),
1344 sat_solver_(model->GetOrCreate<
SatSolver>()),
1348 prober_(model->GetOrCreate<
Prober>()),
1350 time_limit_(model->GetOrCreate<
TimeLimit>()),
1359 if (integer_trail_->HasPendingRootLevelDeduction()) {
1360 sat_solver_->Backtrack(0);
1364 if (sat_solver_->CurrentDecisionLevel() != 0)
return true;
1368 const int saved_bool_index = sat_solver_->LiteralTrail().Index();
1369 const int saved_integer_index = integer_trail_->num_enqueues();
1372 for (
const auto& cb : level_zero_callbacks->callbacks) {
1374 sat_solver_->NotifyThatModelIsUnsat();
1380 if (sat_solver_->LiteralTrail().Index() > saved_bool_index ||
1381 integer_trail_->num_enqueues() > saved_integer_index ||
1382 integer_trail_->HasPendingRootLevelDeduction()) {
1383 if (!sat_solver_->ResetToLevelZero())
return false;
1386 if (parameters_.use_sat_inprocessing() &&
1387 !inprocessing_->InprocessingRound()) {
1388 sat_solver_->NotifyThatModelIsUnsat();
1408 if (sat_solver_->Assignment().LiteralIsAssigned(literal)) {
1413 VLOG(1) <<
"Trying to take a decision that is already assigned!"
1414 <<
" Fix this. Continuing for now...";
1417 return literal.
Index();
1423 while (!time_limit_->LimitReached()) {
1425 if (integer_trail_->InPropagationLoop()) {
1426 const IntegerVariable var =
1427 integer_trail_->NextVariableToBranchOnInPropagationLoop();
1435 if (must_process_conflict_) {
1436 must_process_conflict_ =
false;
1437 sat_solver_->ProcessCurrentConflict();
1438 (void)sat_solver_->FinishPropagation();
1443 integer_trail_->CurrentBranchHadAnIncompletePropagation()) {
1444 const IntegerVariable var = integer_trail_->FirstUnassignedVariable();
1449 if (!new_decision.
HasValue())
break;
1458 pseudo_costs_->BeforeTakingDecision(decision);
1464 const int old_level = sat_solver_->CurrentDecisionLevel();
1465 const int index = sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision);
1470 if (old_level == 0 && sat_solver_->CurrentDecisionLevel() == 1) {
1471 if (!implied_bounds_->ProcessIntegerTrail(decision))
return false;
1472 product_detector_->ProcessTrailAtLevelOne();
1476 pseudo_costs_->AfterTakingDecision(
1477 sat_solver_->CurrentDecisionLevel() <= old_level);
1479 sat_solver_->AdvanceDeterministicTime(time_limit_);
1480 return sat_solver_->ReapplyAssumptionsIfNeeded();
1488 CHECK_NE(num_policies, 0);
1501 if (!sat_solver_->FinishPropagation())
return sat_solver_->UnsatStatus();
1504 const int64_t old_num_conflicts = sat_solver_->num_failures();
1505 const int64_t conflict_limit = parameters_.max_number_of_conflicts();
1506 int64_t num_decisions_since_last_lp_record_ = 0;
1507 while (!time_limit_->LimitReached() &&
1508 (sat_solver_->num_failures() - old_num_conflicts < conflict_limit)) {
1511 if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
1512 return sat_solver_->UnsatStatus();
1521 if (sat_solver_->ModelIsUnsat())
return sat_solver_->UnsatStatus();
1558 if (parameters_.use_optimization_hints()) {
1560 const auto& trail = *model_->GetOrCreate<
Trail>();
1561 for (
int i = 0;
i < trail.Index(); ++
i) {
1562 sat_decision->SetAssignmentPreference(trail[
i], 0.0);
1569 return sat_solver_->UnsatStatus();
1585 parameters_.linearization_level() >= 2) {
1586 num_decisions_since_last_lp_record_++;
1587 if (num_decisions_since_last_lp_record_ >= 100) {
1592 num_decisions_since_last_lp_record_ = 0;
1600 const std::vector<Literal>& assumptions,
Model* model) {
1603 if (!sat_solver->ResetToLevelZero())
return sat_solver->
UnsatStatus();
1608 if (!helper->BeforeTakingDecision())
return sat_solver->UnsatStatus();
1611 if (!sat_solver->ResetWithGivenAssumptions(assumptions)) {
1612 return sat_solver->UnsatStatus();
1618 const IntegerVariable num_vars =
1620 std::vector<IntegerVariable> all_variables;
1621 for (IntegerVariable var(0); var < num_vars; ++var) {
1622 all_variables.push_back(var);
1634#define RETURN_IF_NOT_FEASIBLE(test) \
1635 const SatSolver::Status status = (test); \
1636 if (status != SatSolver::FEASIBLE) return status;
1641 sat_solver_(model->GetOrCreate<
SatSolver>()),
1642 time_limit_(model->GetOrCreate<
TimeLimit>()),
1645 trail_(model->GetOrCreate<
Trail>()),
1649 parameters_(*(model->GetOrCreate<SatParameters>())),
1651 prober_(model->GetOrCreate<
Prober>()),
1655 active_limit_(parameters_.shaving_search_deterministic_time()) {
1657 absl::flat_hash_set<BooleanVariable> visited;
1659 trail_index_at_start_of_iteration_ = trail_->Index();
1660 integer_trail_index_at_start_of_iteration_ = integer_trail_->Index();
1667 for (
int v = 0; v < model_proto.variables_size(); ++v) {
1668 if (mapping->IsBoolean(v)) {
1669 const BooleanVariable bool_var = mapping->Literal(v).Variable();
1670 const auto [_, inserted] = visited.insert(bool_var);
1672 bool_vars_.push_back(bool_var);
1675 IntegerVariable var = mapping->Integer(v);
1676 if (integer_trail_->IsFixed(var))
continue;
1677 int_vars_.push_back(var);
1681 VLOG(2) <<
"Start continuous probing with " << bool_vars_.size()
1682 <<
" Boolean variables, " << int_vars_.size()
1683 <<
" integer variables, deterministic time limit = "
1684 << time_limit_->GetDeterministicLimit() <<
" on " << model_->Name();
1698 while (!time_limit_->LimitReached()) {
1699 if (parameters_.use_sat_inprocessing() &&
1700 !inprocessing_->InprocessingRound()) {
1701 sat_solver_->NotifyThatModelIsUnsat();
1702 return sat_solver_->UnsatStatus();
1706 const int64_t initial_num_literals_fixed =
1707 prober_->num_new_literals_fixed();
1708 const int64_t initial_num_bounds_shaved = num_bounds_shaved_;
1709 const auto& assignment = sat_solver_->Assignment();
1713 for (; current_int_var_ < int_vars_.size(); ++current_int_var_) {
1714 const IntegerVariable int_var = int_vars_[current_int_var_];
1715 if (integer_trail_->IsFixed(int_var))
continue;
1717 const Literal shave_lb_literal =
1719 int_var, integer_trail_->LowerBound(int_var)));
1720 const BooleanVariable shave_lb_var = shave_lb_literal.
Variable();
1721 const auto [_lb, lb_inserted] = probed_bool_vars_.insert(shave_lb_var);
1723 if (!prober_->ProbeOneVariable(shave_lb_var)) {
1726 num_literals_probed_++;
1729 const Literal shave_ub_literal =
1731 int_var, integer_trail_->UpperBound(int_var)));
1732 const BooleanVariable shave_ub_var = shave_ub_literal.
Variable();
1733 const auto [_ub, ub_inserted] = probed_bool_vars_.insert(shave_ub_var);
1735 if (!prober_->ProbeOneVariable(shave_ub_var)) {
1738 num_literals_probed_++;
1743 if (ReportStatus(lb_status))
return lb_status;
1746 if (ReportStatus(ub_status))
return ub_status;
1753 for (; current_bool_var_ < bool_vars_.size(); ++current_bool_var_) {
1754 const BooleanVariable& bool_var = bool_vars_[current_bool_var_];
1756 if (assignment.VariableIsAssigned(bool_var))
continue;
1758 const auto [_, inserted] = probed_bool_vars_.insert(bool_var);
1759 if (!inserted)
continue;
1761 if (!prober_->ProbeOneVariable(bool_var)) {
1764 num_literals_probed_++;
1766 const Literal literal(bool_var,
true);
1767 if (use_shaving_ && !assignment.LiteralIsAssigned(literal)) {
1769 if (ReportStatus(true_status))
return true_status;
1773 if (ReportStatus(false_status))
return false_status;
1779 if (parameters_.use_extended_probing()) {
1780 const auto at_least_one_literal_is_true =
1781 [&assignment](absl::Span<const Literal> literals) {
1782 for (
const Literal literal : literals) {
1783 if (assignment.LiteralIsTrue(literal)) {
1792 const SatClause* clause = clause_manager_->NextClauseToProbe();
1793 if (clause ==
nullptr)
break;
1794 if (at_least_one_literal_is_true(clause->
AsSpan()))
continue;
1798 if (assignment.LiteralIsAssigned(literal))
continue;
1799 tmp_dnf_.push_back({literal});
1801 ++num_at_least_one_probed_;
1802 if (!prober_->ProbeDnf(
"at_least_one", tmp_dnf_)) {
1811 const absl::Span<const Literal> at_most_one =
1812 binary_implication_graph_->NextAtMostOne();
1813 if (at_most_one.empty())
break;
1814 if (at_least_one_literal_is_true(at_most_one))
continue;
1817 tmp_literals_.clear();
1818 for (
const Literal literal : at_most_one) {
1819 if (assignment.LiteralIsAssigned(literal))
continue;
1820 tmp_dnf_.push_back({literal});
1821 tmp_literals_.push_back(literal.Negated());
1823 tmp_dnf_.push_back(tmp_literals_);
1824 ++num_at_most_one_probed_;
1825 if (!prober_->ProbeDnf(
"at_most_one", tmp_dnf_)) {
1833 const int limit = parameters_.probing_num_combinations_limit();
1834 const bool max_num_bool_vars_for_pairs_probing =
1835 static_cast<int>(std::sqrt(2 * limit));
1836 const int num_bool_vars = bool_vars_.size();
1838 if (num_bool_vars < max_num_bool_vars_for_pairs_probing) {
1839 for (; current_bv1_ + 1 < bool_vars_.size(); ++current_bv1_) {
1840 const BooleanVariable bv1 = bool_vars_[current_bv1_];
1841 if (assignment.VariableIsAssigned(bv1))
continue;
1842 current_bv2_ = std::max(current_bv1_ + 1, current_bv2_);
1843 for (; current_bv2_ < bool_vars_.size(); ++current_bv2_) {
1844 const BooleanVariable& bv2 = bool_vars_[current_bv2_];
1845 if (assignment.VariableIsAssigned(bv2))
continue;
1846 if (!prober_->ProbeDnf(
1847 "pair_of_bool_vars",
1848 {{Literal(bv1, true), Literal(bv2, true)},
1849 {Literal(bv1, true), Literal(bv2, false)},
1850 {Literal(bv1, false), Literal(bv2, true)},
1851 {Literal(bv1, false), Literal(bv2, false)}})) {
1859 for (; random_pair_of_bool_vars_probed_ < 10000;
1860 ++random_pair_of_bool_vars_probed_) {
1861 const BooleanVariable bv1 =
1862 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1863 if (assignment.VariableIsAssigned(bv1))
continue;
1864 const BooleanVariable bv2 =
1865 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1866 if (assignment.VariableIsAssigned(bv2) || bv1 == bv2) {
1869 if (!prober_->ProbeDnf(
1870 "rnd_pair_of_bool_vars",
1871 {{Literal(bv1, true), Literal(bv2, true)},
1872 {Literal(bv1, true), Literal(bv2, false)},
1873 {Literal(bv1, false), Literal(bv2, true)},
1874 {Literal(bv1, false), Literal(bv2, false)}})) {
1883 const bool max_num_bool_vars_for_triplet_probing =
1884 static_cast<int>(std::cbrt(2 * limit));
1886 const int loop_limit =
1887 num_bool_vars < max_num_bool_vars_for_triplet_probing
1888 ? num_bool_vars * (num_bool_vars - 1) * (num_bool_vars - 2) / 2
1890 for (; random_triplet_of_bool_vars_probed_ < loop_limit;
1891 ++random_triplet_of_bool_vars_probed_) {
1892 const BooleanVariable bv1 =
1893 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1894 if (assignment.VariableIsAssigned(bv1))
continue;
1895 const BooleanVariable bv2 =
1896 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1897 if (assignment.VariableIsAssigned(bv2) || bv1 == bv2) {
1900 const BooleanVariable bv3 =
1901 bool_vars_[absl::Uniform<int>(*random_, 0, bool_vars_.size())];
1902 if (assignment.VariableIsAssigned(bv3) || bv1 == bv3 || bv2 == bv3) {
1906 for (
int i = 0;
i < 8; ++
i) {
1907 tmp_dnf_.push_back({
Literal(bv1, (
i & 1) > 0),
1912 if (!prober_->ProbeDnf(
"rnd_triplet_of_bool_vars", tmp_dnf_)) {
1922 const double deterministic_time =
1923 parameters_.shaving_search_deterministic_time();
1924 const bool something_has_been_detected =
1925 num_bounds_shaved_ != initial_num_bounds_shaved ||
1926 prober_->num_new_literals_fixed() != initial_num_literals_fixed;
1927 if (something_has_been_detected) {
1928 active_limit_ = deterministic_time;
1929 }
else if (active_limit_ < 25 * deterministic_time) {
1930 active_limit_ += deterministic_time;
1936 current_bool_var_ = 0;
1937 current_int_var_ = 0;
1940 random_pair_of_bool_vars_probed_ = 0;
1941 random_triplet_of_bool_vars_probed_ = 0;
1942 binary_implication_graph_->ResetAtMostOneIterator();
1943 clause_manager_->ResetToProbeIndex();
1944 probed_bool_vars_.clear();
1945 shaved_literals_.clear();
1947 const int new_trail_index = trail_->Index();
1948 const int new_integer_trail_index = integer_trail_->Index();
1954 parameters_.use_shaving_in_probing_search() ? !use_shaving_ :
false;
1955 trail_index_at_start_of_iteration_ = new_trail_index;
1956 integer_trail_index_at_start_of_iteration_ = new_integer_trail_index;
1960 for (
int i = 0;
i < bool_vars_.size(); ++
i) {
1961 if (!sat_solver_->Assignment().VariableIsAssigned(bool_vars_[
i])) {
1962 bool_vars_[new_size++] = bool_vars_[
i];
1965 bool_vars_.resize(new_size);
1969 for (
int i = 0;
i < int_vars_.size(); ++
i) {
1970 if (!integer_trail_->IsFixed(int_vars_[
i])) {
1971 int_vars_[new_size++] = int_vars_[
i];
1974 int_vars_.resize(new_size);
1976 return SatSolver::LIMIT_REACHED;
1979#undef RETURN_IF_NOT_FEASIBLE
1981SatSolver::Status ContinuousProber::PeriodicSyncAndCheck() {
1983 if (--num_test_limit_remaining_ <= 0) {
1984 num_test_limit_remaining_ = kTestLimitPeriod;
1985 if (time_limit_->LimitReached())
return SatSolver::LIMIT_REACHED;
1989 if (--num_logs_remaining_ <= 0) {
1990 num_logs_remaining_ = kLogPeriod;
1995 if (--num_syncs_remaining_ <= 0) {
1996 num_syncs_remaining_ = kSyncPeriod;
1997 if (!sat_solver_->ResetToLevelZero())
return SatSolver::INFEASIBLE;
1998 for (
const auto& cb : level_zero_callbacks_->callbacks) {
2000 sat_solver_->NotifyThatModelIsUnsat();
2001 return SatSolver::INFEASIBLE;
2006 return SatSolver::FEASIBLE;
2009SatSolver::Status ContinuousProber::ShaveLiteral(Literal literal) {
2010 const auto [_, inserted] = shaved_literals_.insert(literal.Index());
2011 if (trail_->Assignment().LiteralIsAssigned(literal) || !inserted) {
2012 return SatSolver::LIMIT_REACHED;
2014 num_bounds_tried_++;
2016 const double original_dtime_limit = time_limit_->GetDeterministicLimit();
2017 time_limit_->ChangeDeterministicLimit(
2018 std::min(original_dtime_limit,
2019 time_limit_->GetElapsedDeterministicTime() + active_limit_));
2020 const SatSolver::Status status =
2022 time_limit_->ChangeDeterministicLimit(original_dtime_limit);
2023 if (ReportStatus(status))
return status;
2025 if (status == SatSolver::ASSUMPTIONS_UNSAT) {
2026 num_bounds_shaved_++;
2031 if (!sat_solver_->ResetToLevelZero())
return SatSolver::INFEASIBLE;
2035bool ContinuousProber::ReportStatus(
const SatSolver::Status status) {
2036 return status == SatSolver::INFEASIBLE || status == SatSolver::FEASIBLE;
2039void ContinuousProber::LogStatistics() {
2040 if (shared_response_manager_ ==
nullptr ||
2041 shared_bounds_manager_ ==
nullptr) {
2044 if (VLOG_IS_ON(1)) {
2045 shared_response_manager_->LogMessageWithThrottling(
2048 " (iterations=", iteration_,
2049 " linearization_level=", parameters_.linearization_level(),
2050 " shaving=", use_shaving_,
" active_bool_vars=", bool_vars_.size(),
2051 " active_int_vars=", integer_trail_->NumIntegerVariables(),
2052 " literals fixed/probed=", prober_->num_new_literals_fixed(),
"/",
2053 num_literals_probed_,
" bounds shaved/tried=", num_bounds_shaved_,
2054 "/", num_bounds_tried_,
" new_integer_bounds=",
2055 shared_bounds_manager_->NumBoundsExported(
"probing"),
2056 " new_binary_clause=", prober_->num_new_binary_clauses(),
2057 " num_at_least_one_probed=", num_at_least_one_probed_,
2058 " num_at_most_one_probed=", num_at_most_one_probed_,
")"));
SatSolver::Status Probe()
ContinuousProber(const CpModelProto &model_proto, Model *model)
The model_proto is just used to construct the lists of variable to probe.
LiteralIndex GetAssociatedLiteral(IntegerLiteral i_lit) const
An helper class to share the code used by the different kind of search.
IntegerSearchHelper(Model *model)
bool TakeDecision(Literal decision)
LiteralIndex GetDecisionLiteral(const BooleanOrIntegerLiteral &decision)
bool GetDecision(const std::function< BooleanOrIntegerLiteral()> &f, LiteralIndex *decision)
bool BeforeTakingDecision()
SatSolver::Status SolveIntegerProblem()
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
IntegerValue UpperBound(IntegerVariable i) const
A class that stores the collection of all LP constraints in a model.
bool SolutionIsInteger() const
double GetSolutionValue(IntegerVariable variable) const
std::string DebugString() const
LiteralIndex Index() const
BooleanVariable Variable() const
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Contain the logic to decide when to restart a SAT tree search.
absl::Span< const Literal > AsSpan() const
Returns a Span<> representation of the clause.
Status UnsatStatus() const
int64_t num_failures() const
const VariablesAssignment & Assignment() const
int CurrentDecisionLevel() const
IntervalDefinition GetIntervalDefinition(int index) const
bool IsPresent(int t) const
Literal PresenceLiteral(int index) const
absl::Span< const AffineExpression > Starts() const
absl::Span< const AffineExpression > Ends() const
std::string TaskDebugString(int t) const
Returns a string with the current task bounds.
Solutions coming from the LP.
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
bool LiteralIsAssigned(Literal literal) const
#define RETURN_IF_NOT_FEASIBLE(test)
void ConfigureSearchHeuristics(Model *model)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Randomizes the decision heuristic of the given SatParameters.
std::function< BooleanOrIntegerLiteral()> FirstUnassignedVarAtItsMinHeuristic(absl::Span< const IntegerVariable > vars, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> DisjunctivePrecedenceSearchHeuristic(Model *model)
std::vector< std::function< BooleanOrIntegerLiteral()> > CompleteHeuristics(absl::Span< const std::function< BooleanOrIntegerLiteral()> > incomplete_heuristics, const std::function< BooleanOrIntegerLiteral()> &completion_heuristic)
std::function< BooleanOrIntegerLiteral()> BoolPseudoCostHeuristic(Model *model)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
std::function< BooleanOrIntegerLiteral()> SequentialValueSelection(std::vector< std::function< IntegerLiteral(IntegerVariable)> > value_selection_heuristics, std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
std::function< BooleanOrIntegerLiteral()> ShaveObjectiveLb(Model *model)
std::function< BooleanOrIntegerLiteral()> IntegerValueSelectionHeuristic(std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
IntegerLiteral SplitAroundLpValue(IntegerVariable var, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
std::function< BooleanOrIntegerLiteral()> UnassignedVarWithLowestMinAtItsMinHeuristic(absl::Span< const IntegerVariable > vars, Model *model)
IntegerLiteral ChooseBestObjectiveValue(IntegerVariable var, IntegerTrail *integer_trail, ObjectiveDefinition *objective_definition)
If a variable appear in the objective, branch on its best objective value.
IntegerLiteral AtMinValue(IntegerVariable var, IntegerTrail *integer_trail)
void RecordLPRelaxationValues(Model *model)
Adds the current LP solution to the pool.
SatSolver::Status SolveIntegerProblemWithLazyEncoding(Model *model)
std::function< BooleanOrIntegerLiteral()> PseudoCost(Model *model)
IntegerVariable PositiveVariable(IntegerVariable i)
std::function< BooleanOrIntegerLiteral()> SchedulingSearchHeuristic(Model *model)
A simple heuristic for scheduling models.
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
Returns the BooleanOrIntegerLiteral advised by the underlying SAT solver.
std::function< bool()> RestartEveryKFailures(int k, SatSolver *solver)
A restart policy that restarts every k failures.
std::function< BooleanOrIntegerLiteral()> RandomizeOnRestartHeuristic(bool lns_mode, Model *model)
IntegerLiteral SplitUsingBestSolutionValueInRepository(IntegerVariable var, const SharedSolutionRepository< int64_t > &solution_repo, Model *model)
std::function< BooleanOrIntegerLiteral()> FollowHint(absl::Span< const BooleanOrIntegerVariable > vars, absl::Span< const IntegerValue > values, Model *model)
bool LinearizedPartIsLarge(Model *model)
const int kUnsatTrailIndex
A constant used by the EnqueueDecision*() API.
IntegerLiteral SplitAroundGivenValue(IntegerVariable var, IntegerValue value, Model *model)
const BooleanVariable kNoBooleanVariable(-1)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
std::function< bool()> SatSolverRestartPolicy(Model *model)
A restart policy that uses the underlying sat solver's policy.
std::function< BooleanOrIntegerLiteral()> LpPseudoCostHeuristic(Model *model)
IntegerLiteral GreaterOrEqualToMiddleValue(IntegerVariable var, IntegerTrail *integer_trail)
double ToDouble(IntegerValue value)
std::function< BooleanOrIntegerLiteral()> MostFractionalHeuristic(Model *model)
Choose the variable with most fractional LP value.
std::function< BooleanOrIntegerLiteral()> CumulativePrecedenceSearchHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()> > heuristics)
In SWIG mode, we don't want anything besides these top-level includes.
LiteralIndex boolean_literal_index
IntegerLiteral integer_literal
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
IntegerLiteral Negated() const
The negation of x >= bound is x <= bound - 1.
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
IntegerVariable objective_var
absl::flat_hash_set< IntegerVariable > objective_impacting_variables
IntegerLiteral down_branch
std::function< BooleanOrIntegerLiteral()> fixed_search
Fixed search, built from above building blocks.
std::vector< std::function< bool()> > restart_policies
std::function< BooleanOrIntegerLiteral()> user_search
Contains the search specified by the user in CpModelProto.
std::vector< std::function< BooleanOrIntegerLiteral()> > decision_policies
std::function< BooleanOrIntegerLiteral()> hint_search
std::function< BooleanOrIntegerLiteral()> next_decision_override
int policy_index
Index in the vectors above that indicate the current configuration.