Skip to content

Commit

Permalink
fix bugs, add limited memory interleaved mode
Browse files Browse the repository at this point in the history
  • Loading branch information
lperron committed Sep 3, 2019
1 parent 94878ac commit a6dd04c
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 62 deletions.
25 changes: 15 additions & 10 deletions ortools/sat/cp_model_lns.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -55,15 +56,16 @@ 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 << ", "
<< old_ub << "] new domain: [" << new_lb << ", " << new_ub
<< "]";
}
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()) {
Expand All @@ -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.
Expand Down Expand Up @@ -139,16 +142,18 @@ 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 {
Neighborhood neighborhood;
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;
}

Expand Down
13 changes: 9 additions & 4 deletions ortools/sat/cp_model_lns.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 10 additions & 5 deletions ortools/sat/cp_model_presolve.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -379,7 +383,8 @@ int PresolveContext::GetOrCreateVarValueEncoding(int ref, int64 value) {
}
std::pair<int, int64> 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);
Expand Down
114 changes: 76 additions & 38 deletions ortools/sat/cp_model_search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions ortools/sat/cp_model_solver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SharedBoundsManager> shared_bounds_manager;
if (global_model->GetOrCreate<SatParameters>()->share_level_zero_bounds()) {
Expand Down Expand Up @@ -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<NeighborhoodGeneratorHelper>(
/*id=*/subsolvers.size(), model_proto, &parameters,
/*id=*/subsolvers.size(), &model_proto, &parameters,
shared_response_manager, shared_time_limit,
shared_bounds_manager.get());
NeighborhoodGeneratorHelper* helper = unique_helper.get();
Expand Down
1 change: 1 addition & 0 deletions ortools/sat/cp_model_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ bool DomainInProtoContains(const ProtoWithDomain& proto, int64 value) {
template <typename ProtoWithDomain>
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);
Expand Down
5 changes: 4 additions & 1 deletion ortools/sat/sat_parameters.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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().
//
Expand Down

0 comments on commit a6dd04c

Please sign in to comment.