diff --git a/src/constraint_solver/constraint_solver.h b/src/constraint_solver/constraint_solver.h index aa53681b39..8098d66d51 100644 --- a/src/constraint_solver/constraint_solver.h +++ b/src/constraint_solver/constraint_solver.h @@ -232,27 +232,6 @@ struct DefaultPhaseParameters { enum DisplayLevel { NONE = 0, NORMAL = 1, VERBOSE = 2 }; - static const int kDefaultNumberOfSplits; - static const int kDefaultHeuristicPeriod; - static const int kDefaultHeuristicNumFailuresLimit; - static const int kDefaultSeed; - static const double kDefaultRestartLogSize; - static const bool kDefaultUseNoGoods; - - DefaultPhaseParameters() - : var_selection_schema(CHOOSE_MAX_SUM_IMPACT), - value_selection_schema(SELECT_MIN_IMPACT), - initialization_splits(kDefaultNumberOfSplits), - run_all_heuristics(true), - heuristic_period(kDefaultHeuristicPeriod), - heuristic_num_failures_limit(kDefaultHeuristicNumFailuresLimit), - persistent_impact(true), - random_seed(kDefaultSeed), - restart_log_size(kDefaultRestartLogSize), - display_level(NORMAL), - use_no_goods(kDefaultUseNoGoods), - decision_builder(nullptr) {} - // This parameter describes how the next variable to instantiate // will be chosen. VariableSelection var_selection_schema; @@ -302,8 +281,13 @@ struct DefaultPhaseParameters { // Should we use Nogoods when restarting. The default is false. bool use_no_goods; + // Should we use last conflict method. The default is false. + bool use_last_conflict; + // When defined, this override the default impact based decision builder. DecisionBuilder* decision_builder; + + DefaultPhaseParameters(); }; ///////////////////////////////////////////////////////////////////// diff --git a/src/constraint_solver/default_search.cc b/src/constraint_solver/default_search.cc index a5b594c85f..568ead31b1 100644 --- a/src/constraint_solver/default_search.cc +++ b/src/constraint_solver/default_search.cc @@ -36,16 +36,34 @@ DEFINE_int32(cp_impact_divider, 10, "Divider for continuous update."); namespace operations_research { -// Default constants for search phase parameters. -const int DefaultPhaseParameters::kDefaultNumberOfSplits = 100; -const int DefaultPhaseParameters::kDefaultHeuristicPeriod = 100; -const int DefaultPhaseParameters::kDefaultHeuristicNumFailuresLimit = 30; -const int DefaultPhaseParameters::kDefaultSeed = 0; -const double DefaultPhaseParameters::kDefaultRestartLogSize = -1.0; -const bool DefaultPhaseParameters::kDefaultUseNoGoods = true; - class NoGoodManager; +namespace { +// Default constants for search phase parameters. +const int kDefaultNumberOfSplits = 100; +const int kDefaultHeuristicPeriod = 100; +const int kDefaultHeuristicNumFailuresLimit = 30; +const int kDefaultSeed = 0; +const double kDefaultRestartLogSize = -1.0; +const bool kDefaultUseNoGoods = true; +const bool kDefaultUseLastConflict = true; +} // namespace + +DefaultPhaseParameters::DefaultPhaseParameters() + : var_selection_schema(DefaultPhaseParameters::CHOOSE_MAX_SUM_IMPACT), + value_selection_schema(DefaultPhaseParameters::SELECT_MIN_IMPACT), + initialization_splits(kDefaultNumberOfSplits), + run_all_heuristics(true), + heuristic_period(kDefaultHeuristicPeriod), + heuristic_num_failures_limit(kDefaultHeuristicNumFailuresLimit), + persistent_impact(true), + random_seed(kDefaultSeed), + restart_log_size(kDefaultRestartLogSize), + display_level(DefaultPhaseParameters::NORMAL), + use_no_goods(kDefaultUseNoGoods), + use_last_conflict(kDefaultUseLastConflict), + decision_builder(nullptr) {} + namespace { // ----- DomainWatcher ----- @@ -1072,6 +1090,9 @@ class DefaultIntegerSearch : public DecisionBuilder { parameters_.heuristic_period, parameters_.heuristic_num_failures_limit), restart_monitor_(solver, parameters_, &domain_watcher_), + find_var_(), + last_int_var_(nullptr), + last_int_value_(0), init_done_(false) {} ~DefaultIntegerSearch() override {} @@ -1083,9 +1104,31 @@ class DefaultIntegerSearch : public DecisionBuilder { return &heuristics_; } - return parameters_.decision_builder != nullptr - ? parameters_.decision_builder->Next(solver) - : ImpactNext(solver); + if (parameters_.use_last_conflict && + last_int_var_ != nullptr && + !last_int_var_->Bound() && + last_int_var_->Contains(last_int_value_)) { + Decision* const assign_last = + solver->MakeAssignVariableValue(last_int_var_, last_int_value_); + last_int_var_ = nullptr; + last_int_value_ = 0; + return assign_last; + } + + Decision* const decision = parameters_.decision_builder != nullptr + ? parameters_.decision_builder->Next(solver) + : ImpactNext(solver); + + if (parameters_.use_last_conflict && decision != nullptr) { + // Store the last decision to replay it upon failure. + decision->Accept(&find_var_); + if (find_var_.valid()) { + last_int_var_ = find_var_.var(); + last_int_value_ = find_var_.value(); + } + } + + return decision; } void AppendMonitors(Solver* const solver, @@ -1212,6 +1255,9 @@ class DefaultIntegerSearch : public DecisionBuilder { ImpactRecorder impact_recorder_; RunHeuristicsAsDives heuristics_; RestartMonitor restart_monitor_; + FindVar find_var_; + IntVar* last_int_var_; + int64 last_int_value_; bool init_done_; }; diff --git a/src/flatzinc/fz.cc b/src/flatzinc/fz.cc index 54e2c7b1f4..3a9a9eb0f6 100644 --- a/src/flatzinc/fz.cc +++ b/src/flatzinc/fz.cc @@ -37,6 +37,7 @@ DEFINE_int32(log_period, 10000000, "Search log period"); DEFINE_bool(all, false, "Search for all solutions"); DEFINE_bool(free, false, "Ignore search annotations"); +DEFINE_bool(last_conflict, false, "Use last conflict search hints"); DEFINE_int32(num_solutions, 0, "Number of solution to search for"); DEFINE_int32(time_limit, 0, "time limit in ms"); DEFINE_int32(workers, 0, "Number of workers"); @@ -67,6 +68,7 @@ void SequentialRun(const FzModel* model) { FzSolverParameters parameters; parameters.all_solutions = FLAGS_all; parameters.free_search = FLAGS_free; + parameters.last_conflict = FLAGS_last_conflict; parameters.heuristic_period = FLAGS_heuristic_period; parameters.ignore_unknown = false; parameters.log_period = FLAGS_log_period; @@ -104,12 +106,14 @@ void ParallelRun(const FzModel* const model, int worker_id, switch (worker_id) { case 0: { parameters.free_search = false; + parameters.last_conflict = false; parameters.search_type = operations_research::FzSolverParameters::DEFAULT; parameters.restart_log_size = -1.0; break; } case 1: { parameters.free_search = true; + parameters.last_conflict = false; parameters.search_type = operations_research::FzSolverParameters::MIN_SIZE; parameters.restart_log_size = -1.0; @@ -117,12 +121,14 @@ void ParallelRun(const FzModel* const model, int worker_id, } case 2: { parameters.free_search = true; + parameters.last_conflict = false; parameters.search_type = operations_research::FzSolverParameters::IBS; parameters.restart_log_size = FLAGS_restart_log_size; break; } case 3: { parameters.free_search = true; + parameters.last_conflict = false; parameters.search_type = operations_research::FzSolverParameters::FIRST_UNBOUND; parameters.restart_log_size = -1.0; @@ -131,6 +137,7 @@ void ParallelRun(const FzModel* const model, int worker_id, } case 4: { parameters.free_search = true; + parameters.last_conflict = false; parameters.search_type = operations_research::FzSolverParameters::DEFAULT; parameters.restart_log_size = -1.0; parameters.heuristic_period = 30; @@ -139,6 +146,7 @@ void ParallelRun(const FzModel* const model, int worker_id, } default: { parameters.free_search = true; + parameters.last_conflict = false; parameters.search_type = worker_id % 2 == 0 ? operations_research::FzSolverParameters::RANDOM_MIN diff --git a/src/flatzinc/search.cc b/src/flatzinc/search.cc index 95652edc19..31a2a001b7 100644 --- a/src/flatzinc/search.cc +++ b/src/flatzinc/search.cc @@ -145,6 +145,7 @@ std::string FzMemoryUsage() { FzSolverParameters::FzSolverParameters() : all_solutions(false), free_search(false), + last_conflict(false), ignore_annotations(false), ignore_unknown(true), use_log(false), @@ -269,7 +270,7 @@ void FzSolver::ParseSearchAnnotations(bool ignore_unknown, FlattenAnnotations(ann, &flat_annotations); } - FZLOG << " - using search annotations" << std::endl; + FZLOG << " - parsing search annotations" << std::endl; hash_set added; for (const FzAnnotation& ann : flat_annotations) { FZLOG << " - parse " << ann.DebugString() << FZENDL; @@ -445,7 +446,8 @@ void FzSolver::AddCompletionDecisionBuilders( DecisionBuilder* FzSolver::CreateDecisionBuilders(const FzSolverParameters& p, SearchLimit* limit) { - FZLOG << "Defining search" << std::endl; + FZLOG << "Defining search" << (p.free_search ? " (free)" : " (fixed)") + << std::endl; // Fill builders_ with predefined search. std::vector defined; std::vector defined_variables; @@ -507,6 +509,7 @@ DecisionBuilder* FzSolver::CreateDecisionBuilders(const FzSolverParameters& p, defined_variables, Solver::CHOOSE_RANDOM, Solver::ASSIGN_MAX_VALUE); } } + parameters.use_last_conflict = p.last_conflict; parameters.run_all_heuristics = p.run_all_heuristics; parameters.heuristic_period = model_.objective() != nullptr || @@ -627,8 +630,13 @@ void FzSolver::Solve(FzSolverParameters p, } if (p.luby_restart > 0) { + FZLOG << " - using luby restart with a factor of " << p.luby_restart + << std::endl; monitors.push_back(solver()->MakeLubyRestart(p.luby_restart)); } + if (p.last_conflict && p.free_search) { + FZLOG << " - using last conflict search hints" << std::endl; + } bool breaked = false; std::string solution_string; diff --git a/src/flatzinc/search.h b/src/flatzinc/search.h index f28afe87fd..cac6c601a2 100644 --- a/src/flatzinc/search.h +++ b/src/flatzinc/search.h @@ -31,6 +31,7 @@ struct FzSolverParameters { bool all_solutions; bool free_search; + bool last_conflict; bool ignore_annotations; bool ignore_unknown; bool use_log;