24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/flags/flag.h"
27 #include "absl/random/distributions.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/string_view.h"
31 #include "ortools/sat/cp_model.pb.h"
38 #include "ortools/sat/sat_parameters.pb.h"
44 ABSL_FLAG(
bool, cp_model_use_max_hs,
false,
"Use max_hs in search portfolio.");
93 int64_t
value)
const {
109 int64_t
value)
const {
119 IntegerValue(
value));
130 const IntegerVariable variable = mapping_.
Integer(
var);
131 const std::vector<ValueLiteralPair> encoding =
137 const int target = (encoding.size() + 1) / 2 - 1;
152 bool ModelHasSchedulingConstraints(
const CpModelProto& cp_model_proto) {
153 for (
const ConstraintProto&
ct : cp_model_proto.constraints()) {
154 if (
ct.constraint_case() == ConstraintProto::kNoOverlap)
return true;
155 if (
ct.constraint_case() == ConstraintProto::kCumulative)
return true;
160 void AddDualSchedulingHeuristics(SatParameters& new_params) {
161 new_params.set_exploit_all_precedences(
true);
162 new_params.set_use_hard_precedences_in_cumulative(
true);
163 new_params.set_use_overload_checker_in_cumulative(
true);
164 new_params.set_use_strong_propagation_in_disjunctive(
true);
165 new_params.set_use_timetable_edge_finding_in_cumulative(
true);
171 const std::vector<DecisionStrategyProto>& strategies,
Model*
model) {
178 return [&view, &
parameters, random, strategies]() {
179 for (
const DecisionStrategyProto& strategy : strategies) {
187 std::vector<VarValue> active_refs;
190 for (
int i = 0; i < strategy.variables().size(); ++i) {
191 const int ref = strategy.variables(i);
193 if (view.IsFixed(
var) || view.IsCurrentlyFree(
var))
continue;
197 while (t_index < strategy.transformations().size() &&
198 strategy.transformations(t_index).index() < i) {
201 if (t_index < strategy.transformations_size() &&
202 strategy.transformations(t_index).index() == i) {
203 coeff = strategy.transformations(t_index).positive_coeff();
204 offset = strategy.transformations(t_index).offset();
212 int64_t lb = view.Min(
var);
213 int64_t ub = view.Max(
var);
218 switch (strategy.variable_selection_strategy()) {
219 case DecisionStrategyProto::CHOOSE_FIRST:
221 case DecisionStrategyProto::CHOOSE_LOWEST_MIN:
222 value = coeff * lb + offset;
224 case DecisionStrategyProto::CHOOSE_HIGHEST_MAX:
225 value = -(coeff * ub + offset);
227 case DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE:
228 value = coeff * (ub - lb + 1);
230 case DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE:
231 value = -coeff * (ub - lb + 1);
234 LOG(FATAL) <<
"Unknown VariableSelectionStrategy "
235 << strategy.variable_selection_strategy();
237 if (
value < candidate_value) {
239 candidate_value =
value;
241 if (strategy.variable_selection_strategy() ==
242 DecisionStrategyProto::CHOOSE_FIRST &&
247 candidate_value +
parameters.search_randomization_tolerance()) {
248 active_refs.push_back({ref,
value});
255 CHECK(!active_refs.empty());
256 const IntegerValue threshold(
257 candidate_value +
parameters.search_randomization_tolerance());
258 auto is_above_tolerance = [threshold](
const VarValue& entry) {
259 return entry.value > threshold;
262 active_refs.erase(std::remove_if(active_refs.begin(), active_refs.end(),
265 const int winner = absl::Uniform<int>(*random, 0, active_refs.size());
266 candidate = active_refs[winner].ref;
269 DecisionStrategyProto::DomainReductionStrategy selection =
270 strategy.domain_reduction_strategy();
273 case DecisionStrategyProto::SELECT_MIN_VALUE:
274 selection = DecisionStrategyProto::SELECT_MAX_VALUE;
276 case DecisionStrategyProto::SELECT_MAX_VALUE:
277 selection = DecisionStrategyProto::SELECT_MIN_VALUE;
279 case DecisionStrategyProto::SELECT_LOWER_HALF:
280 selection = DecisionStrategyProto::SELECT_UPPER_HALF;
282 case DecisionStrategyProto::SELECT_UPPER_HALF:
283 selection = DecisionStrategyProto::SELECT_LOWER_HALF;
291 const int64_t lb = view.Min(
var);
292 const int64_t ub = view.Max(
var);
294 case DecisionStrategyProto::SELECT_MIN_VALUE:
295 return view.LowerOrEqual(
var, lb);
296 case DecisionStrategyProto::SELECT_MAX_VALUE:
297 return view.GreaterOrEqual(
var, ub);
298 case DecisionStrategyProto::SELECT_LOWER_HALF:
299 return view.LowerOrEqual(
var, lb + (ub - lb) / 2);
300 case DecisionStrategyProto::SELECT_UPPER_HALF:
301 return view.GreaterOrEqual(
var, ub - (ub - lb) / 2);
302 case DecisionStrategyProto::SELECT_MEDIAN_VALUE:
303 return view.MedianValue(
var);
305 LOG(FATAL) <<
"Unknown DomainReductionStrategy "
306 << strategy.domain_reduction_strategy();
314 const CpModelProto& cp_model_proto,
Model*
model) {
315 std::vector<DecisionStrategyProto> strategies;
316 for (
const DecisionStrategyProto&
proto : cp_model_proto.search_strategy()) {
317 strategies.push_back(
proto);
323 const CpModelProto& cp_model_proto,
324 const std::vector<IntegerVariable>& variable_mapping,
325 IntegerVariable objective_var,
Model*
model) {
329 const auto& params = *
model->GetOrCreate<SatParameters>();
330 if (params.search_branching() != SatParameters::PARTIAL_FIXED_SEARCH) {
336 if (ModelHasSchedulingConstraints(cp_model_proto)) {
341 if (params.instantiate_all_variables()) {
342 std::vector<IntegerVariable> decisions;
343 for (
const IntegerVariable
var : variable_mapping) {
348 decisions.push_back(objective_var);
350 decisions.push_back(
var);
360 const CpModelProto& cp_model_proto,
361 const std::vector<IntegerVariable>& variable_mapping,
364 std::vector<int> ref_to_display;
365 for (
int i = 0; i < cp_model_proto.variables_size(); ++i) {
367 if (cp_model_proto.variables(i).name().empty())
continue;
368 ref_to_display.push_back(i);
370 std::sort(ref_to_display.begin(), ref_to_display.end(), [&](
int i,
int j) {
371 return cp_model_proto.variables(i).name() <
372 cp_model_proto.variables(j).name();
375 std::vector<std::pair<int64_t, int64_t>> old_domains(variable_mapping.size());
376 return [instrumented_strategy,
model, variable_mapping, cp_model_proto,
377 old_domains, ref_to_display]()
mutable {
379 if (!decision.
HasValue())
return decision;
383 LOG(INFO) <<
"Boolean decision " << l;
385 for (
const IntegerLiteral i_lit : encoder->GetIntegerLiterals(l)) {
386 LOG(INFO) <<
" - associated with " << i_lit;
388 for (
const auto [
var,
value] : encoder->GetEqualityLiterals(l)) {
389 LOG(INFO) <<
" - associated with " <<
var <<
" == " <<
value;
394 const int level =
model->Get<
Trail>()->CurrentDecisionLevel();
395 std::string to_display =
396 absl::StrCat(
"Diff since last call, level=", level,
"\n");
398 for (
const int ref : ref_to_display) {
399 const IntegerVariable
var = variable_mapping[ref];
400 const std::pair<int64_t, int64_t> new_domain(
403 if (new_domain != old_domains[ref]) {
404 absl::StrAppend(&to_display, cp_model_proto.variables(ref).name(),
" [",
405 old_domains[ref].first,
",", old_domains[ref].second,
406 "] -> [", new_domain.first,
",", new_domain.second,
408 old_domains[ref] = new_domain;
411 LOG(INFO) << to_display;
420 int ValidSumSeed(
int base_seed,
int delta) {
422 int64_t result = int64_t{base_seed} + int64_t{
delta};
424 while (result > int32max) {
427 return static_cast<int>(result);
440 const SatParameters& base_params,
const CpModelProto& cp_model) {
443 absl::flat_hash_map<std::string, SatParameters> strategies;
446 strategies[
"default"] = base_params;
450 SatParameters new_params = base_params;
451 new_params.set_linearization_level(0);
452 strategies[
"no_lp"] = new_params;
453 new_params.set_linearization_level(1);
454 strategies[
"default_lp"] = new_params;
455 new_params.set_linearization_level(2);
456 new_params.set_add_lp_constraints_lazily(
false);
457 strategies[
"max_lp"] = new_params;
467 SatParameters new_params = base_params;
468 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
469 new_params.set_optimize_with_core(
true);
470 new_params.set_linearization_level(0);
471 strategies[
"core"] = new_params;
476 SatParameters new_params = base_params;
477 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
478 new_params.set_optimize_with_core(
true);
479 new_params.set_linearization_level(1);
480 strategies[
"core_default_lp"] = new_params;
484 SatParameters new_params = base_params;
485 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
486 new_params.set_optimize_with_core(
true);
487 new_params.set_linearization_level(2);
488 strategies[
"core_max_lp"] = new_params;
492 SatParameters new_params = base_params;
493 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
494 new_params.set_optimize_with_core(
true);
495 new_params.set_optimize_with_max_hs(
true);
496 strategies[
"max_hs"] = new_params;
500 SatParameters new_params = base_params;
501 new_params.set_optimize_with_lb_tree_search(
true);
502 new_params.set_linearization_level(2);
503 if (base_params.use_dual_scheduling_heuristics()) {
504 AddDualSchedulingHeuristics(new_params);
508 new_params.set_add_lp_constraints_lazily(
false);
509 new_params.set_root_lp_iterations(100'000);
513 new_params.set_share_objective_bounds(
false);
514 strategies[
"lb_tree_search"] = new_params;
518 SatParameters new_params = base_params;
519 new_params.set_linearization_level(1);
520 new_params.set_use_objective_lb_search(
true);
521 if (base_params.use_dual_scheduling_heuristics()) {
522 AddDualSchedulingHeuristics(new_params);
524 strategies[
"objective_lb_search"] = new_params;
526 new_params.set_linearization_level(0);
527 strategies[
"objective_lb_search_no_lp"] = new_params;
529 new_params.set_linearization_level(2);
530 strategies[
"objective_lb_search_max_lp"] = new_params;
534 SatParameters new_params = base_params;
535 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
536 new_params.set_use_probing_search(
true);
537 if (base_params.use_dual_scheduling_heuristics()) {
538 AddDualSchedulingHeuristics(new_params);
540 strategies[
"probing"] = new_params;
542 new_params.set_linearization_level(0);
543 strategies[
"probing_no_lp"] = new_params;
545 new_params.set_linearization_level(2);
546 strategies[
"probing_max_lp"] = new_params;
551 SatParameters new_params = base_params;
552 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
553 strategies[
"auto"] = new_params;
555 new_params.set_search_branching(SatParameters::FIXED_SEARCH);
556 strategies[
"fixed"] = new_params;
558 new_params.set_search_branching(
559 SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
560 strategies[
"quick_restart"] = new_params;
562 new_params.set_search_branching(
563 SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
564 new_params.set_linearization_level(0);
565 strategies[
"quick_restart_no_lp"] = new_params;
567 new_params.set_search_branching(
568 SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
569 new_params.set_linearization_level(2);
570 strategies[
"quick_restart_max_lp"] = new_params;
574 SatParameters new_params = base_params;
575 new_params.set_linearization_level(2);
576 new_params.set_search_branching(SatParameters::LP_SEARCH);
577 if (base_params.use_dual_scheduling_heuristics()) {
578 AddDualSchedulingHeuristics(new_params);
580 strategies[
"reduced_costs"] = new_params;
584 SatParameters new_params = base_params;
585 new_params.set_linearization_level(2);
586 new_params.set_search_branching(SatParameters::PSEUDO_COST_SEARCH);
587 new_params.set_exploit_best_solution(
true);
588 strategies[
"pseudo_costs"] = new_params;
593 SatParameters new_params = base_params;
594 new_params.set_boolean_encoding_level(0);
595 strategies[
"less_encoding"] = new_params;
599 for (
const SatParameters& params : base_params.subsolver_params()) {
600 strategies[params.name()] = params;
609 const bool use_fixed_strategy = !cp_model.search_strategy().empty() ||
610 ModelHasSchedulingConstraints(cp_model);
616 std::vector<std::string> names;
619 if (base_params.subsolvers().empty()) {
620 names.push_back(
"default_lp");
621 names.push_back(
"fixed");
622 names.push_back(
"less_encoding");
624 names.push_back(
"no_lp");
625 names.push_back(
"max_lp");
626 names.push_back(
"core");
628 names.push_back(
"reduced_costs");
629 names.push_back(
"pseudo_costs");
631 names.push_back(
"quick_restart");
632 names.push_back(
"quick_restart_no_lp");
633 names.push_back(
"lb_tree_search");
635 if (cp_model.has_objective() &&
636 (cp_model.objective().vars().size() == 1 ||
637 base_params.num_workers() > 16)) {
638 names.push_back(
"objective_lb_search");
640 names.push_back(
"probing");
641 if (base_params.num_workers() >= 20) {
642 names.push_back(
"probing_max_lp");
644 if (base_params.num_workers() >= 24) {
645 names.push_back(
"objective_lb_search_max_lp");
647 #if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
648 if (absl::GetFlag(FLAGS_cp_model_use_max_hs)) names.push_back(
"max_hs");
651 for (
const std::string&
name : base_params.subsolvers()) {
654 if (
name ==
"core_or_no_lp") {
655 if (!cp_model.has_objective() ||
656 cp_model.objective().vars_size() <= 1) {
657 names.push_back(
"no_lp");
659 names.push_back(
"core");
662 names.push_back(
name);
668 for (
const std::string&
name : base_params.extra_subsolvers()) {
669 names.push_back(
name);
673 absl::flat_hash_set<std::string> to_ignore;
674 for (
const std::string&
name : base_params.ignore_subsolvers()) {
675 to_ignore.insert(
name);
678 for (
const std::string&
name : names) {
679 if (to_ignore.contains(
name))
continue;
680 names[new_size++] =
name;
682 names.resize(new_size);
685 std::vector<SatParameters> result;
686 for (
const std::string&
name : names) {
687 if (!strategies.contains(
name)) {
690 LOG(WARNING) <<
"Unknown parameter name '" <<
name <<
"'";
693 SatParameters params = strategies.at(
name);
696 if (!use_fixed_strategy &&
697 params.search_branching() == SatParameters::FIXED_SEARCH) {
703 if (params.use_probing_search() && params.interleave_search())
continue;
707 if (params.use_probing_search() && cp_model.variables().empty())
continue;
709 if (cp_model.has_objective() && !cp_model.objective().vars().empty()) {
711 if (cp_model.objective().vars().size() == 1 &&
712 params.optimize_with_core()) {
716 if (
name ==
"less_encoding")
continue;
721 if (params.interleave_search() &&
722 (params.optimize_with_lb_tree_search() ||
723 params.use_objective_lb_search())) {
728 if (params.optimize_with_lb_tree_search())
continue;
729 if (params.optimize_with_core())
continue;
730 if (params.use_objective_lb_search())
continue;
731 if (params.search_branching() == SatParameters::LP_SEARCH)
continue;
732 if (params.search_branching() == SatParameters::PSEUDO_COST_SEARCH) {
741 params.set_name(
name);
742 params.set_random_seed(
743 ValidSumSeed(base_params.random_seed(), result.size() + 1));
744 result.push_back(params);
747 if (cp_model.has_objective() && !cp_model.objective().vars().empty()) {
751 1, base_params.num_workers() - base_params.min_num_lns_workers());
752 if (!base_params.interleave_search() && result.size() > target) {
753 result.resize(target);
758 const bool need_extra_workers =
759 !base_params.interleave_search() &&
760 (base_params.use_rins_lns() || base_params.use_feasibility_pump());
761 int target = base_params.num_workers();
762 if (need_extra_workers && target > 4) {
765 }
else if (target == 9) {
771 if (!base_params.interleave_search() && result.size() > target) {
772 result.resize(target);
779 const SatParameters& base_params,
const CpModelProto& cp_model,
780 int num_params_to_generate) {
781 std::vector<SatParameters> result;
782 if (num_params_to_generate <= 0)
return result;
784 int num_random_qr = 0;
785 while (result.size() < num_params_to_generate) {
786 SatParameters new_params = base_params;
787 const int base_seed = base_params.random_seed();
788 if (num_random <= num_random_qr) {
793 if (cp_model.search_strategy().empty() && num_random % 2 == 0) {
794 new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
796 new_params.set_search_branching(SatParameters::FIXED_SEARCH);
798 new_params.set_randomize_search(
true);
799 new_params.set_search_randomization_tolerance(num_random + 1);
800 new_params.set_random_seed(ValidSumSeed(base_seed, 2 * num_random + 1));
801 new_params.set_name(absl::StrCat(
"random_", num_random));
804 new_params.set_search_branching(
805 SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
806 new_params.set_randomize_search(
true);
807 new_params.set_search_randomization_tolerance(num_random_qr + 1);
808 new_params.set_random_seed(ValidSumSeed(base_seed, 2 * num_random_qr));
809 new_params.set_name(absl::StrCat(
"random_quick_restart_", num_random_qr));
812 result.push_back(new_params);
An Assignment is a variable -> domains mapping, used to report solutions to the user.
bool IsBoolean(int ref) const
bool IsInteger(int ref) const
IntegerVariable Integer(int ref) const
sat::Literal Literal(int ref) const
int NumProtoVariables() const
bool IsFixed(int var) const
BooleanOrIntegerLiteral GreaterOrEqual(int var, int64_t value) const
bool IsCurrentlyFree(int var) const
BooleanOrIntegerLiteral MedianValue(int var) const
CpModelView(Model *model)
int64_t Max(int var) const
BooleanOrIntegerLiteral LowerOrEqual(int var, int64_t value) const
int64_t Min(int var) const
std::vector< ValueLiteralPair > FullDomainEncoding(IntegerVariable var) const
bool IsCurrentlyIgnored(IntegerVariable i) const
bool IsFixed(IntegerVariable i) const
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue LowerBound(IntegerVariable i) const
LiteralIndex NegatedIndex() const
LiteralIndex Index() const
BooleanVariable Variable() const
Class that owns everything related to a particular optimization model.
bool VariableIsAssigned(BooleanVariable var) const
bool LiteralIsTrue(Literal literal) const
bool LiteralIsFalse(Literal literal) const
ABSL_FLAG(bool, cp_model_use_max_hs, false, "Use max_hs in search portfolio.")
const std::function< BooleanOrIntegerLiteral()> ConstructSearchStrategyInternal(const std::vector< DecisionStrategyProto > &strategies, Model *model)
std::function< BooleanOrIntegerLiteral()> FirstUnassignedVarAtItsMinHeuristic(const std::vector< IntegerVariable > &vars, Model *model)
bool RefIsPositive(int ref)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> ConstructUserSearchStrategy(const CpModelProto &cp_model_proto, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
std::function< BooleanOrIntegerLiteral()> SchedulingSearchHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> ConstructFixedSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, IntegerVariable objective_var, Model *model)
std::vector< SatParameters > GetDiverseSetOfParameters(const SatParameters &base_params, const CpModelProto &cp_model)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
std::vector< SatParameters > GetFirstSolutionParams(const SatParameters &base_params, const CpModelProto &cp_model, int num_params_to_generate)
std::function< BooleanOrIntegerLiteral()> InstrumentSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, const std::function< BooleanOrIntegerLiteral()> &instrumented_strategy, Model *model)
Collection of objects used to extend the Constraint Solver library.
LiteralIndex boolean_literal_index
IntegerLiteral integer_literal
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)