From a6dd04cc18e95ce8fa91b315248e1d428ed4ceb7 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Tue, 3 Sep 2019 15:44:24 +0200 Subject: [PATCH] fix bugs, add limited memory interleaved mode --- ortools/sat/cp_model_lns.cc | 25 ++++--- ortools/sat/cp_model_lns.h | 13 ++-- ortools/sat/cp_model_presolve.cc | 15 ++-- ortools/sat/cp_model_search.cc | 114 ++++++++++++++++++++----------- ortools/sat/cp_model_solver.cc | 9 +-- ortools/sat/cp_model_utils.h | 1 + ortools/sat/sat_parameters.proto | 5 +- 7 files changed, 120 insertions(+), 62 deletions(-) diff --git a/ortools/sat/cp_model_lns.cc b/ortools/sat/cp_model_lns.cc index 364fe4441d7..217ae01da96 100644 --- a/ortools/sat/cp_model_lns.cc +++ b/ortools/sat/cp_model_lns.cc @@ -26,17 +26,18 @@ namespace operations_research { namespace sat { NeighborhoodGeneratorHelper::NeighborhoodGeneratorHelper( - int id, const CpModelProto& model_proto, SatParameters const* parameters, - SharedResponseManager* shared_response, - class SharedTimeLimit* shared_time_limit, + int id, CpModelProto const* model_proto, SatParameters const* parameters, + SharedResponseManager* shared_response, SharedTimeLimit* shared_time_limit, SharedBoundsManager* shared_bounds) : SubSolver(id, "helper"), - model_proto_(model_proto), parameters_(*parameters), + model_proto_(*model_proto), shared_time_limit_(shared_time_limit), shared_bounds_(shared_bounds), shared_response_(shared_response) { CHECK(shared_response_ != nullptr); + *model_proto_with_only_variables_.mutable_variables() = + model_proto_.variables(); RecomputeHelperData(); Synchronize(); } @@ -55,7 +56,8 @@ void NeighborhoodGeneratorHelper::Synchronize() { const int64 new_lb = new_lower_bounds[i]; const int64 new_ub = new_upper_bounds[i]; if (VLOG_IS_ON(3)) { - const auto& domain = model_proto_.variables(var).domain(); + const auto& domain = + model_proto_with_only_variables_.variables(var).domain(); const int64 old_lb = domain.Get(0); const int64 old_ub = domain.Get(domain.size() - 1); VLOG(3) << "Variable: " << var << " old domain: [" << old_lb << ", " @@ -63,7 +65,7 @@ void NeighborhoodGeneratorHelper::Synchronize() { << "]"; } const Domain old_domain = - ReadDomainFromProto(model_proto_.variables(var)); + ReadDomainFromProto(model_proto_with_only_variables_.variables(var)); const Domain new_domain = old_domain.IntersectionWith(Domain(new_lb, new_ub)); if (new_domain.IsEmpty()) { @@ -72,7 +74,8 @@ void NeighborhoodGeneratorHelper::Synchronize() { if (shared_time_limit_ != nullptr) shared_time_limit_->Stop(); return; } - FillDomainInProto(new_domain, model_proto_.mutable_variables(var)); + FillDomainInProto( + new_domain, model_proto_with_only_variables_.mutable_variables(var)); } // Only trigger the computation if needed. @@ -139,9 +142,9 @@ bool NeighborhoodGeneratorHelper::IsActive(int var) const { } bool NeighborhoodGeneratorHelper::IsConstant(int var) const { - return model_proto_.variables(var).domain_size() == 2 && - model_proto_.variables(var).domain(0) == - model_proto_.variables(var).domain(1); + return model_proto_with_only_variables_.variables(var).domain_size() == 2 && + model_proto_with_only_variables_.variables(var).domain(0) == + model_proto_with_only_variables_.variables(var).domain(1); } Neighborhood NeighborhoodGeneratorHelper::FullNeighborhood() const { @@ -149,6 +152,8 @@ Neighborhood NeighborhoodGeneratorHelper::FullNeighborhood() const { neighborhood.is_reduced = false; neighborhood.is_generated = true; neighborhood.cp_model = model_proto_; + *neighborhood.cp_model.mutable_variables() = + model_proto_with_only_variables_.variables(); return neighborhood; } diff --git a/ortools/sat/cp_model_lns.h b/ortools/sat/cp_model_lns.h index eecac31cf58..873a8e809a1 100644 --- a/ortools/sat/cp_model_lns.h +++ b/ortools/sat/cp_model_lns.h @@ -52,7 +52,7 @@ struct Neighborhood { // the bounds of the base problem with the external world. class NeighborhoodGeneratorHelper : public SubSolver { public: - NeighborhoodGeneratorHelper(int id, const CpModelProto& model_proto, + NeighborhoodGeneratorHelper(int id, CpModelProto const* model_proto, SatParameters const* parameters, SharedResponseManager* shared_response, SharedTimeLimit* shared_time_limit = nullptr, @@ -130,14 +130,19 @@ class NeighborhoodGeneratorHelper : public SubSolver { // Indicates if a variable is fixed in the model. bool IsConstant(int var) const; - // TODO(user): To reduce memory, take a const proto and keep the updated - // variable bounds separated. - CpModelProto model_proto_; const SatParameters& parameters_; + const CpModelProto& model_proto_; SharedTimeLimit* shared_time_limit_; SharedBoundsManager* shared_bounds_; SharedResponseManager* shared_response_; + // This proto will only contain the field variables() with an updated version + // of the domains compared to model_proto_.variables(). We do it like this to + // reduce the memory footprint of the helper when the model is large. + // + // TODO(user): Use custom domain repository rather than a proto? + CpModelProto model_proto_with_only_variables_; + mutable absl::Mutex mutex_; // Constraints by types. diff --git a/ortools/sat/cp_model_presolve.cc b/ortools/sat/cp_model_presolve.cc index 4ecff6f949e..2c01baaedef 100644 --- a/ortools/sat/cp_model_presolve.cc +++ b/ortools/sat/cp_model_presolve.cc @@ -77,10 +77,14 @@ void PresolveContext::AddImplication(int a, int b) { // b => x in [lb, ub]. void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) { ConstraintProto* const imply = working_model->add_constraints(); - imply->add_enforcement_literal(b); - imply->mutable_linear()->add_vars(x); - imply->mutable_linear()->add_coeffs(1); - FillDomainInProto(domain, imply->mutable_linear()); + + // Doing it like this seems to use slightly less memory. + // TODO(user): Find the best way to create such small proto. + imply->mutable_enforcement_literal()->Resize(1, b); + LinearConstraintProto* mutable_linear = imply->mutable_linear(); + mutable_linear->mutable_vars()->Resize(1, x); + mutable_linear->mutable_coeffs()->Resize(1, 1); + FillDomainInProto(domain, mutable_linear); } bool PresolveContext::DomainIsEmpty(int ref) const { @@ -379,7 +383,8 @@ int PresolveContext::GetOrCreateVarValueEncoding(int ref, int64 value) { } std::pair key{var, s_value}; - if (encoding.contains(key)) return encoding[key]; + auto it = encoding.find(key); + if (it != encoding.end()) return it->second; if (domains[var].Size() == 1) { const int true_literal = GetOrCreateConstantVar(1); diff --git a/ortools/sat/cp_model_search.cc b/ortools/sat/cp_model_search.cc index 56a11d8a063..2767f7dffb6 100644 --- a/ortools/sat/cp_model_search.cc +++ b/ortools/sat/cp_model_search.cc @@ -285,57 +285,95 @@ SatParameters DiversifySearchParameters(const SatParameters& params, new_params.set_use_lns_only(false); int index = worker_id; - if (cp_model.has_objective() && params.num_search_workers() == 1 && + if (params.reduce_memory_usage_in_interleave_mode() && params.interleave_search()) { // Low memory mode for interleaved search in single thread (4 workers). - - // First worker. - if (index == 0) { // Use default parameters and automatic search. - new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); - *name = "auto"; - return new_params; - } - - // Second worker (fixed or lp branching). - if (cp_model.search_strategy_size() > 0) { - if (--index == 0) { // Use default parameters and fixed search. - new_params.set_search_branching(SatParameters::FIXED_SEARCH); - *name = "fixed"; + CHECK_LE(index, 4); + if (cp_model.has_objective()) { + // First strategy (default). + if (index == 0) { // Use default parameters and automatic search. + new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); + *name = "auto"; return new_params; } - } else { - // TODO(user): Disable lp_br if linear part is small or empty. - if (--index == 0) { - new_params.set_search_branching(SatParameters::PSEUDO_COST_SEARCH); - new_params.set_exploit_best_solution(true); - *name = "pseudo_cost"; - return new_params; + + // Second strategy (fixed or lp branching). + if (cp_model.search_strategy_size() > 0) { + if (--index == 0) { // Use default parameters and fixed search. + new_params.set_search_branching(SatParameters::FIXED_SEARCH); + *name = "fixed"; + return new_params; + } + } else { + // TODO(user): Disable lp_br if linear part is small or empty. + if (--index == 0) { + new_params.set_search_branching(SatParameters::PSEUDO_COST_SEARCH); + new_params.set_exploit_best_solution(true); + *name = "pseudo_cost"; + return new_params; + } } - } - // Third worker (core or no_lp). - if (cp_model.objective().vars_size() > 1) { - if (--index == 0) { // Core based approach. + // Third strategy (core or lp branching). + if (cp_model.objective().vars_size() > 1) { + if (--index == 0) { // Core based approach. + new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); + new_params.set_optimize_with_core(true); + new_params.set_linearization_level(0); + *name = "core"; + return new_params; + } + } else { + if (--index == 0) { // Remove LP relaxation. + new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); + new_params.set_linearization_level(0); + *name = "no_lp"; + return new_params; + } + } + + // Fourth strategy using LNS. + new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); + new_params.set_use_lns_only(true); + *name = "lns"; + return new_params; + } else { // No objective + // First strategy (default). + if (index == 0) { // Use default parameters and automatic search. new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); - new_params.set_optimize_with_core(true); - new_params.set_linearization_level(0); - *name = "core"; + *name = "auto"; return new_params; } - } else { - if (--index == 0) { // Remove LP relaxation. + // Second strategy (fixed or no_lp). + if (cp_model.search_strategy_size() > 0) { + if (--index == 0) { // Use default parameters and fixed search. + new_params.set_search_branching(SatParameters::FIXED_SEARCH); + *name = "fixed"; + return new_params; + } + } else { + // TODO(user): Disable lp_br if linear part is small or empty. + if (--index == 0) { + new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); + new_params.set_linearization_level(0); + *name = "no_lp"; + return new_params; + } + } + + if (--index == 0) { // Third strategy: reduce boolean encoding. new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); - new_params.set_linearization_level(0); - *name = "no_lp"; + new_params.set_boolean_encoding_level(0); + *name = "less encoding"; return new_params; } - } - // Fourth worker using LNS. - new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); - new_params.set_use_lns_only(true); - *name = absl::StrFormat("lns_%i", index); - return new_params; + // Final strategy: quick restart. + new_params.set_search_branching( + SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH); + *name = "random"; + return new_params; + } } else if (cp_model.has_objective()) { if (index == 0) { // Use default parameters and automatic search. new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH); diff --git a/ortools/sat/cp_model_solver.cc b/ortools/sat/cp_model_solver.cc index 0cc5c614dc1..d8102467a7f 100644 --- a/ortools/sat/cp_model_solver.cc +++ b/ortools/sat/cp_model_solver.cc @@ -2065,9 +2065,10 @@ void SolveCpModelParallel(const CpModelProto& model_proto, // If "interleave_search" is true, then the number of strategies is // 4 if num_search_workers = 1, or 8 otherwise. - const int num_strategies = parameters.interleave_search() - ? (num_search_workers == 1 ? 4 : 8) - : num_search_workers; + const int num_strategies = + parameters.interleave_search() + ? (parameters.reduce_memory_usage_in_interleave_mode() ? 4 : 8) + : num_search_workers; std::unique_ptr shared_bounds_manager; if (global_model->GetOrCreate()->share_level_zero_bounds()) { @@ -2129,7 +2130,7 @@ void SolveCpModelParallel(const CpModelProto& model_proto, // Add the NeighborhoodGeneratorHelper as a special subsolver so that its // Synchronize() is called before any LNS neighborhood solvers. auto unique_helper = absl::make_unique( - /*id=*/subsolvers.size(), model_proto, ¶meters, + /*id=*/subsolvers.size(), &model_proto, ¶meters, shared_response_manager, shared_time_limit, shared_bounds_manager.get()); NeighborhoodGeneratorHelper* helper = unique_helper.get(); diff --git a/ortools/sat/cp_model_utils.h b/ortools/sat/cp_model_utils.h index 5c343f2d80b..5ec2f9a851f 100644 --- a/ortools/sat/cp_model_utils.h +++ b/ortools/sat/cp_model_utils.h @@ -86,6 +86,7 @@ bool DomainInProtoContains(const ProtoWithDomain& proto, int64 value) { template void FillDomainInProto(const Domain& domain, ProtoWithDomain* proto) { proto->clear_domain(); + proto->mutable_domain()->Reserve(domain.NumIntervals()); for (const ClosedInterval& interval : domain) { proto->add_domain(interval.start); proto->add_domain(interval.end); diff --git a/ortools/sat/sat_parameters.proto b/ortools/sat/sat_parameters.proto index a072898e03d..f2e6237dcc8 100644 --- a/ortools/sat/sat_parameters.proto +++ b/ortools/sat/sat_parameters.proto @@ -21,7 +21,7 @@ option java_multiple_files = true; // Contains the definitions for all the sat algorithm parameters and their // default values. // -// NEXT TAG: 141 +// NEXT TAG: 142 message SatParameters { // ========================================================================== // Branching and polarity @@ -702,6 +702,9 @@ message SatParameters { // if deterministic_parallel_search is false. optional bool interleave_search = 136 [default = false]; + // Temporary parameter until the memory usage is more optimized. + optional bool reduce_memory_usage_in_interleave_mode = 141 [default = true]; + // Make the parallelization deterministic. Currently, this only work with // use_lns_only(). //