more work on integer search; add RINS heuristis

This commit is contained in:
Laurent Perron
2019-04-17 22:05:32 +02:00
parent e6cce4540a
commit 85bb98a616
10 changed files with 292 additions and 44 deletions

View File

@@ -1105,6 +1105,7 @@ SAT_DEPS = \
$(SRC_DIR)/ortools/sat/probing.h \
$(SRC_DIR)/ortools/sat/pseudo_costs.h \
$(SRC_DIR)/ortools/sat/restart.h \
$(SRC_DIR)/ortools/sat/rins.h \
$(SRC_DIR)/ortools/sat/sat_base.h \
$(SRC_DIR)/ortools/sat/sat_decision.h \
$(SRC_DIR)/ortools/sat/sat_solver.h \
@@ -1162,6 +1163,7 @@ SAT_LIB_OBJS = \
$(OBJ_DIR)/sat/probing.$O \
$(OBJ_DIR)/sat/pseudo_costs.$O \
$(OBJ_DIR)/sat/restart.$O \
$(OBJ_DIR)/sat/rins.$O \
$(OBJ_DIR)/sat/sat_decision.$O \
$(OBJ_DIR)/sat/sat_solver.$O \
$(OBJ_DIR)/sat/simplification.$O \
@@ -2000,6 +2002,14 @@ objs/sat/restart.$O: ortools/sat/restart.cc ortools/sat/restart.h \
ortools/port/proto_utils.h | $(OBJ_DIR)/sat
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Srestart.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Srestart.$O
objs/sat/rins.$O: ortools/sat/rins.cc ortools/sat/rins.h \
ortools/sat/model.h ortools/base/logging.h ortools/base/integral_types.h \
ortools/base/macros.h ortools/base/map_util.h ortools/base/typeid.h \
ortools/gen/ortools/sat/sat_parameters.pb.h ortools/util/bitset.h \
ortools/base/basictypes.h ortools/util/running_stat.h \
ortools/port/proto_utils.h | $(OBJ_DIR)/sat
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Srins.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Srins.$O
objs/sat/sat_decision.$O: ortools/sat/sat_decision.cc \
ortools/sat/sat_decision.h ortools/base/integral_types.h \
ortools/sat/model.h ortools/base/logging.h ortools/base/macros.h \

View File

@@ -177,6 +177,7 @@ cc_library(
":optimization",
":precedences",
":probing",
":rins",
":sat_base",
":sat_parameters_cc_proto",
":sat_solver",
@@ -511,6 +512,7 @@ cc_library(
":integer",
":linear_programming_constraint",
":pseudo_costs",
":rins",
":sat_base",
":sat_decision",
":sat_solver",
@@ -1007,6 +1009,7 @@ cc_library(
":cp_model_utils",
":linear_programming_constraint",
":model",
":rins",
"//ortools/base",
"//ortools/util:random_engine",
"//ortools/util:time_limit",

View File

@@ -14,11 +14,13 @@
#include "ortools/sat/cp_model_lns.h"
#include <numeric>
#include <vector>
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_loader.h"
#include "ortools/sat/cp_model_utils.h"
#include "ortools/sat/linear_programming_constraint.h"
#include "ortools/sat/rins.h"
#include "ortools/util/random_engine.h"
namespace operations_research {
@@ -139,6 +141,15 @@ Neighborhood NeighborhoodGeneratorHelper::RelaxGivenVariables(
return FixGivenVariables(initial_solution, fixed_variables);
}
Neighborhood NeighborhoodGeneratorHelper::FixAllVariables(
const CpSolverResponse& initial_solution) const {
std::vector<int> fixed_variables;
for (const int i : active_variables_) {
fixed_variables.push_back(i);
}
return FixGivenVariables(initial_solution, fixed_variables);
}
double NeighborhoodGenerator::GetUCBScore(int64 total_num_calls) const {
DCHECK_GE(total_num_calls, num_calls_);
if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
@@ -443,43 +454,37 @@ Neighborhood RelaxationInducedNeighborhoodGenerator::Generate(
Neighborhood neighborhood;
neighborhood.cp_model = helper_.ModelProto();
const int num_active_vars = helper_.ActiveVariables().size();
const int num_model_vars = helper_.ModelProto().variables_size();
const int target_size =
num_model_vars - std::ceil(difficulty * num_active_vars);
if (target_size == num_active_vars) {
neighborhood.is_reduced = false;
return neighborhood;
}
auto* mapping = model_.Get<CpModelMapping>();
auto* lp_dispatcher = model_.Get<LinearProgrammingDispatcher>();
if (lp_dispatcher == nullptr) {
neighborhood.is_reduced = false;
return neighborhood;
}
std::vector<int> fixed_variables;
SharedRINSNeighborhoodManager* rins_manager =
model_->Mutable<SharedRINSNeighborhoodManager>();
std::vector<int> all_vars;
for (int i = 0; i < num_model_vars; ++i) {
if (!helper_.IsActive(i)) continue;
if (mapping->IsInteger(i)) {
const IntegerVariable var = mapping->Integer(i);
const IntegerVariable positive_var = PositiveVariable(var);
LinearProgrammingConstraint* lp =
gtl::FindWithDefault(*lp_dispatcher, positive_var, nullptr);
if (lp == nullptr) continue;
const double lp_value = (lp->GetSolutionValue(positive_var));
all_vars.push_back(i);
}
if (rins_manager == nullptr) {
// TODO(user): Support skipping this neighborhood instead of going
// through solving process for the trivial model.
return helper_.FixAllVariables(initial_solution);
}
absl::optional<RINSNeighborhood> rins_neighborhood_opt =
rins_manager->GetUnexploredNeighborhood();
if (std::abs(lp_value - initial_solution.solution(i)) < 1e-4) {
// Fix this var.
fixed_variables.push_back(i);
if (fixed_variables.size() >= target_size) break;
}
}
if (!rins_neighborhood_opt.has_value()) {
return helper_.FixAllVariables(initial_solution);
}
return helper_.FixGivenVariables(initial_solution, fixed_variables);
// Fix the variables in the local model.
for (const std::pair<int, int64> fixed_var :
rins_neighborhood_opt.value().fixed_vars) {
int var = fixed_var.first;
int64 value = fixed_var.second;
if (!helper_.IsActive(var)) continue;
neighborhood.cp_model.mutable_variables(var)->clear_domain();
neighborhood.cp_model.mutable_variables(var)->add_domain(value);
neighborhood.cp_model.mutable_variables(var)->add_domain(value);
neighborhood.is_reduced = true;
}
return neighborhood;
}
} // namespace sat

View File

@@ -61,6 +61,10 @@ class NeighborhoodGeneratorHelper {
const CpSolverResponse& initial_solution,
const std::vector<int>& relaxed_variables) const;
// Returns a trivial model by fixing all active variables to the initial
// solution values.
Neighborhood FixAllVariables(const CpSolverResponse& initial_solution) const;
// Indicates if the variable can be frozen. It happens if the variable is non
// constant, and if it is a decision variable, or if
// focus_on_decision_variables is false.
@@ -239,14 +243,14 @@ class SchedulingTimeWindowNeighborhoodGenerator : public NeighborhoodGenerator {
class RelaxationInducedNeighborhoodGenerator : public NeighborhoodGenerator {
public:
explicit RelaxationInducedNeighborhoodGenerator(
NeighborhoodGeneratorHelper const* helper, const Model& model,
NeighborhoodGeneratorHelper const* helper, Model* model,
const std::string& name)
: NeighborhoodGenerator(name, helper), model_(model) {}
Neighborhood Generate(const CpSolverResponse& initial_solution, int64 seed,
double difficulty) const final;
const Model& model_;
const Model* model_;
};
} // namespace sat

View File

@@ -71,6 +71,7 @@
#include "ortools/sat/optimization.h"
#include "ortools/sat/precedences.h"
#include "ortools/sat/probing.h"
#include "ortools/sat/rins.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/sat_solver.h"
#include "ortools/sat/simplification.h"
@@ -1873,7 +1874,7 @@ void SolveCpModelWithLNS(const CpModelProto& model_proto, int num_workers,
// TODO(user): Only do it if we have a linear relaxation.
generators.push_back(
absl::make_unique<RelaxationInducedNeighborhoodGenerator>(
&helper, *model, "rins"));
&helper, model, "rins"));
}
// The "optimal" difficulties do not have to be the same for different
@@ -2080,6 +2081,13 @@ void SolveCpModelParallel(const CpModelProto& model_proto,
absl::make_unique<SharedBoundsManager>(num_search_workers, model_proto);
}
// Note: This is shared only if the rins is enabled in the global parameters.
std::unique_ptr<SharedRINSNeighborhoodManager> rins_manager;
if (model->GetOrCreate<SatParameters>()->use_rins_lns()) {
rins_manager = absl::make_unique<SharedRINSNeighborhoodManager>(
model_proto.variables_size());
}
// Collect per-worker parameters and names.
std::vector<SatParameters> worker_parameters;
std::vector<std::string> worker_names;
@@ -2121,12 +2129,17 @@ void SolveCpModelParallel(const CpModelProto& model_proto,
num_search_workers, wall_timer,
&first_solution_found_or_search_finished,
&shared_response_manager, &shared_bounds_manager,
worker_name]() {
worker_name, &rins_manager]() {
Model local_model;
local_model.Add(NewSatParameters(local_params));
local_model.GetOrCreate<TimeLimit>()->RegisterExternalBooleanAsLimit(
stopped);
// Add shared neighborhood only if RINS is enabled in global parameters.
if (rins_manager != nullptr) {
local_model.Register<SharedRINSNeighborhoodManager>(rins_manager.get());
}
// Stores info that will be used for logs in the local model.
WorkerInfo* worker_info = local_model.GetOrCreate<WorkerInfo>();
worker_info->worker_name = worker_name;

View File

@@ -19,6 +19,7 @@
#include "ortools/sat/integer.h"
#include "ortools/sat/linear_programming_constraint.h"
#include "ortools/sat/pseudo_costs.h"
#include "ortools/sat/rins.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/sat_decision.h"
#include "ortools/sat/util.h"
@@ -646,6 +647,7 @@ SatSolver::Status SolveIntegerProblem(Model* model) {
const int64 old_num_conflicts = solver->num_failures();
const int64 conflict_limit =
model->GetOrCreate<SatParameters>()->max_number_of_conflicts();
int64 num_decisions_without_rins = 0;
while (!time_limit->LimitReached() &&
(solver->num_failures() - old_num_conflicts < conflict_limit)) {
// If needed, restart and switch decision_policy.
@@ -705,6 +707,17 @@ SatSolver::Status SolveIntegerProblem(Model* model) {
solver->AdvanceDeterministicTime(time_limit);
if (!solver->ReapplyAssumptionsIfNeeded()) return solver->UnsatStatus();
if (model->GetOrCreate<SolutionDetails>()->solution_count > 0 &&
model->Get<SharedRINSNeighborhoodManager>() != nullptr) {
num_decisions_without_rins++;
// TODO(user): Experiment more around dynamically changing the
// threshold for trigerring RINS. Alternatively expose this as parameter
// so this can be tuned later.
if (num_decisions_without_rins >= 100) {
num_decisions_without_rins = 0;
AddRINSNeighborhood(model);
}
}
}
return SatSolver::Status::LIMIT_REACHED;
}

View File

@@ -113,14 +113,6 @@ LiteralIndex SplitAroundGivenValue(IntegerVariable positive_var,
// does not appear in the LP, this method returns kNoLiteralIndex.
LiteralIndex SplitAroundLpValue(IntegerVariable var, Model* model);
struct SolutionDetails {
int64 solution_count = 0;
gtl::ITIVector<IntegerVariable, IntegerValue> best_solution;
// Loads the solution in best_solution using lower bounds from integer trail.
void LoadFromTrail(const IntegerTrail& integer_trail);
};
// Returns decision corresponding to var <= best_solution[var]. If no solution
// has been found, this method returns kNoLiteralIndex. This was suggested in
// paper: "Solution-Based Phase Saving for CP" (2018) by Emir Demirovic,

View File

@@ -122,6 +122,15 @@ class Model {
return new_t;
}
// Register a non-owned class that will be "singleton" in the model.
// It is an error to call this on an already registered class.
template <typename T>
void Register(T* non_owned_class) {
const size_t type_id = gtl::FastTypeId<T>();
CHECK(!gtl::ContainsKey(singletons_, type_id));
singletons_[type_id] = non_owned_class;
}
private:
// We want to call the constructor T(model*) if it exists or just T() if
// it doesn't. For this we use some template "magic":

115
ortools/sat/rins.cc Normal file
View File

@@ -0,0 +1,115 @@
// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/sat/rins.h"
#include "ortools/sat/cp_model_loader.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/linear_programming_constraint.h"
namespace operations_research {
namespace sat {
bool SharedRINSNeighborhoodManager::AddNeighborhood(
const absl::flat_hash_map<IntegerVariable, IntegerValue>& fixed_vars,
const Model& model) {
auto* mapping = model.Get<CpModelMapping>();
if (mapping == nullptr) {
return false;
}
// Don't store this neighborhood if the current storage is already too large.
// TODO(user): Consider instead removing one of the older neighborhoods.
if (total_num_fixed_vars_ + fixed_vars.size() > max_fixed_vars()) {
return false;
}
RINSNeighborhood rins_neighborhood;
// TODO(user): Use GetProtoVariableFromIntegerVariable instead and change
// the api to take a list of fixed vars instead of a hash map.
for (int i = 0; i < num_model_vars_; ++i) {
if (mapping->IsInteger(i)) {
const IntegerVariable var = mapping->Integer(i);
if (fixed_vars.contains(var)) {
rins_neighborhood.fixed_vars.push_back(
{i, fixed_vars.find(var)->second.value()});
} else if (fixed_vars.contains(NegationOf(var))) {
rins_neighborhood.fixed_vars.push_back(
{i, -fixed_vars.find(var)->second.value()});
}
}
}
absl::MutexLock lock(&mutex_);
total_num_fixed_vars_ += rins_neighborhood.fixed_vars.size();
neighborhoods_.push_back(std::move(rins_neighborhood));
VLOG(1) << "total fixed vars: " << total_num_fixed_vars_;
return true;
}
absl::optional<RINSNeighborhood>
SharedRINSNeighborhoodManager::GetUnexploredNeighborhood() {
absl::MutexLock lock(&mutex_);
if (neighborhoods_.empty()) {
VLOG(2) << "No neighborhood to consume.";
return absl::nullopt;
}
// return the last added neighborhood and remove it.
const RINSNeighborhood neighborhood = std::move(neighborhoods_.back());
neighborhoods_.pop_back();
total_num_fixed_vars_ -= neighborhood.fixed_vars.size();
VLOG(1) << "total fixed vars: " << total_num_fixed_vars_;
return neighborhood;
}
void AddRINSNeighborhood(Model* model) {
IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
absl::flat_hash_map<IntegerVariable, IntegerValue> fixed_vars;
auto* solution_details = model->GetOrCreate<SolutionDetails>();
const int size = solution_details->best_solution.size();
for (IntegerVariable var(0); var < size; ++var) {
auto* lp_dispatcher = model->GetOrCreate<LinearProgrammingDispatcher>();
const IntegerVariable positive_var = PositiveVariable(var);
if (integer_trail->IsCurrentlyIgnored(positive_var)) continue;
// TODO(user): Perform caching to make this more efficient.
LinearProgrammingConstraint* lp =
gtl::FindWithDefault(*lp_dispatcher, positive_var, nullptr);
if (lp == nullptr || !lp->HasSolution()) continue;
const IntegerValue best_solution_value =
solution_details->best_solution[var];
if (std::abs(best_solution_value.value() -
lp->GetSolutionValue(positive_var)) < 1e-4) {
if (best_solution_value >= integer_trail->LowerBound(var) ||
best_solution_value <= integer_trail->UpperBound(var)) {
fixed_vars[positive_var] = best_solution_value;
} else {
// The last lp_solution might not always be up to date.
VLOG(2) << "RINS common value out of bounds: " << best_solution_value
<< " LB: " << integer_trail->LowerBound(var)
<< " UB: " << integer_trail->UpperBound(var);
}
}
}
VLOG(2) << "RINS Fixed: " << fixed_vars.size() << "/" << size;
model->Mutable<SharedRINSNeighborhoodManager>()->AddNeighborhood(fixed_vars,
*model);
}
} // namespace sat
} // namespace operations_research

84
ortools/sat/rins.h Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_SAT_RINS_H_
#define OR_TOOLS_SAT_RINS_H_
#include "absl/container/flat_hash_map.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/optional.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/model.h"
namespace operations_research {
namespace sat {
// TODO(user): This is not an ideal place for the solution details. Move to a
// file with other such stats or create one if needed.
struct SolutionDetails {
int64 solution_count = 0;
gtl::ITIVector<IntegerVariable, IntegerValue> best_solution;
// Loads the solution in best_solution using lower bounds from integer trail.
void LoadFromTrail(const IntegerTrail& integer_trail);
};
struct RINSNeighborhood {
std::vector<std::pair</*var*/ int, /*value*/ int64>> fixed_vars;
};
// Shared object to pass around generated RINS neighborhoods across workers.
class SharedRINSNeighborhoodManager {
public:
explicit SharedRINSNeighborhoodManager(const int64 num_model_vars)
: num_model_vars_(num_model_vars) {}
// Model will be used to get the CpModelMapping for translation. Returns
// true for success.
bool AddNeighborhood(
const absl::flat_hash_map<IntegerVariable, IntegerValue>& fixed_vars,
const Model& model);
// Returns an unexplored RINS neighborhood if any, otherwise returns nullopt.
// TODO(user): This also deletes the returned neighborhood. Consider having
// a different method/logic instead for deletion.
absl::optional<RINSNeighborhood> GetUnexploredNeighborhood();
// This is the memory limit we use to avoid storing too many neighborhoods.
// The sum of the fixed variables of the stored neighborhood will always
// stays smaller than this.
int64 max_fixed_vars() const { return 100 * num_model_vars_; }
private:
// Used while adding and removing neighborhoods.
absl::Mutex mutex_;
// TODO(user): Use better data structure (e.g. queue) to store this
// collection.
std::vector<RINSNeighborhood> neighborhoods_;
// This is the sum of number of fixed variables across all the shared
// neighborhoods. This is used for controlling the size of storage.
int64 total_num_fixed_vars_ = 0;
const int64 num_model_vars_;
};
// Helper method to create a RINS neighborhood by fixing variables with same
// values in lp and last solution.
void AddRINSNeighborhood(Model* model);
} // namespace sat
} // namespace operations_research
#endif // OR_TOOLS_SAT_RINS_H_