improve StopSearch() robustness
This commit is contained in:
@@ -329,6 +329,7 @@ void GLOPInterface::SetStartingLpBasis(
|
||||
|
||||
void GLOPInterface::SetParameters(const MPSolverParameters& param) {
|
||||
parameters_.Clear();
|
||||
parameters_.set_log_search_progress(!quiet_);
|
||||
SetCommonParameters(param);
|
||||
SetScalingMode(param.GetIntegerParam(MPSolverParameters::SCALING));
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ std::function<int(GRBenv*, GRBmodel**, const char*, int numvars, double*,
|
||||
double*, double*, char*, char**)>
|
||||
GRBnewmodel = nullptr;
|
||||
std::function<int(GRBmodel*)> GRBoptimize = nullptr;
|
||||
std::function<int(GRBenv *dest, GRBenv *src)> GRBcopyparams = nullptr;
|
||||
std::function<int(GRBenv*, const char*)> GRBreadparams = nullptr;
|
||||
std::function<int(GRBenv*)> GRBresetparams = nullptr;
|
||||
std::function<int(GRBmodel*, const char*, int, char)> GRBsetcharattrelement =
|
||||
@@ -158,6 +159,7 @@ void LoadGurobiFunctions() {
|
||||
gurobi_dynamic_library->GetFunction(&GRBloadenv, NAMEOF(GRBloadenv));
|
||||
gurobi_dynamic_library->GetFunction(&GRBnewmodel, NAMEOF(GRBnewmodel));
|
||||
gurobi_dynamic_library->GetFunction(&GRBoptimize, NAMEOF(GRBoptimize));
|
||||
gurobi_dynamic_library->GetFunction(&GRBcopyparams, NAMEOF(GRBcopyparams));
|
||||
gurobi_dynamic_library->GetFunction(&GRBreadparams, NAMEOF(GRBreadparams));
|
||||
gurobi_dynamic_library->GetFunction(&GRBresetparams, NAMEOF(GRBresetparams));
|
||||
gurobi_dynamic_library->GetFunction(&GRBsetcharattrelement,
|
||||
|
||||
@@ -72,6 +72,7 @@ extern std::function<int(GRBenv*, GRBmodel**, const char*, int numvars, double*,
|
||||
extern std::function<int(GRBmodel*)> GRBoptimize;
|
||||
extern std::function<int(GRBenv*, const char*)> GRBreadparams;
|
||||
extern std::function<int(GRBenv*)> GRBresetparams;
|
||||
extern std::function<int(GRBenv *dest, GRBenv *src)> GRBcopyparams;
|
||||
extern std::function<int(GRBmodel*, const char*, int, char)>
|
||||
GRBsetcharattrelement;
|
||||
extern std::function<int(GRBmodel*, const char*, double)> GRBsetdblattr;
|
||||
|
||||
@@ -145,6 +145,7 @@ class GurobiInterface : public MPSolverInterface {
|
||||
}
|
||||
|
||||
bool InterruptSolve() override {
|
||||
const absl::MutexLock lock(&hold_interruptions_mutex_);
|
||||
if (model_ != nullptr) GRBterminate(model_);
|
||||
return true;
|
||||
}
|
||||
@@ -204,9 +205,6 @@ class GurobiInterface : public MPSolverInterface {
|
||||
void SetScalingMode(int value) override;
|
||||
void SetLpAlgorithm(int value) override;
|
||||
|
||||
bool ReadParameterFile(const std::string& filename) override;
|
||||
std::string ValidFileExtensionForParameterFile() const override;
|
||||
|
||||
MPSolver::BasisStatus TransformGRBVarBasisStatus(
|
||||
int gurobi_basis_status) const;
|
||||
MPSolver::BasisStatus TransformGRBConstraintBasisStatus(
|
||||
@@ -255,6 +253,11 @@ class GurobiInterface : public MPSolverInterface {
|
||||
int num_gurobi_linear_cons_ = 0;
|
||||
// See the implementation note at the top of file on incrementalism.
|
||||
bool had_nonincremental_change_ = false;
|
||||
|
||||
// Mutex is held to prevent InterruptSolve() to call GRBterminate() when
|
||||
// model_ is not completely built. It also prevents model_ to be changed
|
||||
// during the execution of GRBterminate().
|
||||
mutable absl::Mutex hold_interruptions_mutex_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -621,7 +624,10 @@ GurobiInterface::~GurobiInterface() {
|
||||
// ------ Model modifications and extraction -----
|
||||
|
||||
void GurobiInterface::Reset() {
|
||||
CheckedGurobiCall(GRBfreemodel(model_));
|
||||
// We hold calls to GRBterminate() until the new model_ is ready.
|
||||
const absl::MutexLock lock(&hold_interruptions_mutex_);
|
||||
|
||||
GRBmodel* old_model = model_;
|
||||
CheckedGurobiCall(GRBnewmodel(env_, &model_, solver_->name_.c_str(),
|
||||
0, // numvars
|
||||
nullptr, // obj
|
||||
@@ -629,6 +635,20 @@ void GurobiInterface::Reset() {
|
||||
nullptr, // ub
|
||||
nullptr, // vtype
|
||||
nullptr)); // varnames
|
||||
|
||||
// Copy all existing parameters from the previous model to the new one. This
|
||||
// ensures that if a user calls multiple times
|
||||
// SetSolverSpecificParametersAsString() and then Reset() is called, we still
|
||||
// take into account all parameters.
|
||||
//
|
||||
// The current code only reapplies the parameters stored in
|
||||
// solver_specific_parameter_string_ at the start of the solve; other
|
||||
// parameters set by previous calls are only kept in the Gurobi model.
|
||||
CheckedGurobiCall(GRBcopyparams(GRBgetenv(model_), GRBgetenv(old_model)));
|
||||
|
||||
CheckedGurobiCall(GRBfreemodel(old_model));
|
||||
old_model = nullptr;
|
||||
|
||||
ResetExtractionInformation();
|
||||
mp_var_to_gurobi_var_.clear();
|
||||
mp_cons_to_gurobi_linear_cons_.clear();
|
||||
@@ -1359,15 +1379,6 @@ void GurobiInterface::Write(const std::string& filename) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GurobiInterface::ReadParameterFile(const std::string& filename) {
|
||||
// A non-zero return value indicates that a problem occurred.
|
||||
return GRBreadparams(GRBgetenv(model_), filename.c_str()) == 0;
|
||||
}
|
||||
|
||||
std::string GurobiInterface::ValidFileExtensionForParameterFile() const {
|
||||
return ".prm";
|
||||
}
|
||||
|
||||
MPSolverInterface* BuildGurobiInterface(bool mip, MPSolver* const solver) {
|
||||
return new GurobiInterface(solver, mip);
|
||||
}
|
||||
|
||||
@@ -1784,51 +1784,15 @@ absl::Status MPSolverInterface::SetNumThreads(int num_threads) {
|
||||
|
||||
bool MPSolverInterface::SetSolverSpecificParametersAsString(
|
||||
const std::string& parameters) {
|
||||
// Note(user): this method needs to return a success/failure boolean
|
||||
// immediately, so we also perform the actual parameter parsing right away.
|
||||
// Some implementations will keep them forever and won't need to re-parse
|
||||
// them; some (eg. Gurobi) need to re-parse the parameters every time they do
|
||||
// Solve(). We just store the parameters string anyway.
|
||||
//
|
||||
// Note(user): This is not implemented on Android because there is no
|
||||
// temporary directory to write files to without a pointer to the Java
|
||||
// environment.
|
||||
if (parameters.empty()) return true;
|
||||
if (parameters.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string extension = ValidFileExtensionForParameterFile();
|
||||
std::string filename;
|
||||
bool no_error_so_far = PortableTemporaryFile(nullptr, &filename);
|
||||
filename += extension;
|
||||
if (no_error_so_far) {
|
||||
no_error_so_far = PortableFileSetContents(filename, parameters).ok();
|
||||
}
|
||||
if (no_error_so_far) {
|
||||
no_error_so_far = ReadParameterFile(filename);
|
||||
// We need to clean up the file even if ReadParameterFile() returned
|
||||
// false. In production we can continue even if the deletion failed.
|
||||
if (!PortableDeleteFile(filename).ok()) {
|
||||
LOG(DFATAL) << "Couldn't delete temporary parameters file: " << filename;
|
||||
}
|
||||
}
|
||||
if (!no_error_so_far) {
|
||||
LOG(WARNING) << "Error in SetSolverSpecificParametersAsString() "
|
||||
<< "for solver type: "
|
||||
<< ProtoEnumToString<MPModelRequest::SolverType>(
|
||||
static_cast<MPModelRequest::SolverType>(
|
||||
solver_->ProblemType()));
|
||||
}
|
||||
return no_error_so_far;
|
||||
}
|
||||
|
||||
bool MPSolverInterface::ReadParameterFile(const std::string& filename) {
|
||||
LOG(WARNING) << "ReadParameterFile() not supported by this solver.";
|
||||
LOG(WARNING) << "SetSolverSpecificParametersAsString() not supported by "
|
||||
<< SolverVersion();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string MPSolverInterface::ValidFileExtensionForParameterFile() const {
|
||||
return ".tmp";
|
||||
}
|
||||
|
||||
// ---------- MPSolverParameters ----------
|
||||
|
||||
const double MPSolverParameters::kDefaultRelativeMipGap = 1e-4;
|
||||
|
||||
@@ -1782,22 +1782,11 @@ class MPSolverInterface {
|
||||
// solver-specific and is the same as the corresponding solver configuration
|
||||
// file format. Returns true if the operation was successful.
|
||||
//
|
||||
// The default implementation of this method stores the parameters in a
|
||||
// temporary file and calls ReadParameterFile to import the parameter file
|
||||
// into the solver. Solvers that support passing the parameters directly can
|
||||
// override this method to skip the temporary file logic.
|
||||
// Default implementation returns true if the input is empty. It returns false
|
||||
// and logs a WARNING if the input is not empty.
|
||||
virtual bool SetSolverSpecificParametersAsString(
|
||||
const std::string& parameters);
|
||||
|
||||
// Reads a solver-specific file of parameters and set them.
|
||||
// Returns true if there was no errors.
|
||||
virtual bool ReadParameterFile(const std::string& filename);
|
||||
|
||||
// Returns a file extension like ".tmp", this is needed because some solvers
|
||||
// require a given extension for the ReadParameterFile() filename and we need
|
||||
// to know it to generate a temporary parameter file.
|
||||
virtual std::string ValidFileExtensionForParameterFile() const;
|
||||
|
||||
// Sets the scaling mode.
|
||||
virtual void SetScalingMode(int value) = 0;
|
||||
virtual void SetLpAlgorithm(int value) = 0;
|
||||
|
||||
@@ -17,15 +17,16 @@
|
||||
#define OR_TOOLS_LINEAR_SOLVER_LINEAR_SOLVER_CALLBACK_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "ortools/base/integral_types.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
class MPVariable;
|
||||
class LinearExpr;
|
||||
class LinearRange;
|
||||
class MPVariable;
|
||||
|
||||
// The current state of the solver when the callback is invoked.
|
||||
//
|
||||
|
||||
@@ -310,6 +310,7 @@ PY_CONVERT(MPVariable);
|
||||
%newobject operations_research::MPSolver::CreateSolver;
|
||||
%unignore operations_research::MPSolver::CreateSolver;
|
||||
%unignore operations_research::MPSolver::ParseAndCheckSupportForProblemType;
|
||||
|
||||
%unignore operations_research::MPSolver::Solve;
|
||||
%unignore operations_research::MPSolver::VerifySolution;
|
||||
%unignore operations_research::MPSolver::infinity;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "ortools/base/cleanup.h"
|
||||
#include "ortools/base/commandlineflags.h"
|
||||
#include "ortools/base/hash.h"
|
||||
#include "ortools/base/integral_types.h"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "ortools/linear_solver/scip_proto_solver.h"
|
||||
#include "scip/cons_indicator.h"
|
||||
#include "scip/scip.h"
|
||||
#include "scip/scip_copy.h"
|
||||
#include "scip/scip_param.h"
|
||||
#include "scip/scip_prob.h"
|
||||
#include "scip/scipdefplugins.h"
|
||||
@@ -108,7 +110,12 @@ class SCIPInterface : public MPSolverInterface {
|
||||
}
|
||||
|
||||
bool InterruptSolve() override {
|
||||
if (scip_ == nullptr) return true; // NOTE(user): Is this weird?
|
||||
const absl::MutexLock lock(&hold_interruptions_mutex_);
|
||||
if (scip_ == nullptr) {
|
||||
LOG_IF(DFATAL, status_.ok()) << "scip_ is null is unexpected here, since "
|
||||
"status_ did not report any error";
|
||||
return true;
|
||||
}
|
||||
return SCIPinterruptSolve(scip_) == SCIP_OKAY;
|
||||
}
|
||||
|
||||
@@ -173,7 +180,10 @@ class SCIPInterface : public MPSolverInterface {
|
||||
void SetSolution(SCIP_SOL* solution);
|
||||
|
||||
absl::Status CreateSCIP();
|
||||
void DeleteSCIP();
|
||||
// Deletes variables and constraints from scip_ and reset scip_ to null. If
|
||||
// return_scip is false, deletes the SCIP object; if true, returns it (but
|
||||
// scip_ is still set to null).
|
||||
SCIP* DeleteSCIP(bool return_scip = false);
|
||||
|
||||
// SCIP has many internal checks (many of which are numerical) that can fail
|
||||
// during various phases: upon startup, when loading the model, when solving,
|
||||
@@ -193,6 +203,11 @@ class SCIPInterface : public MPSolverInterface {
|
||||
EmptyStruct constraint_data_for_handler_;
|
||||
bool branching_priority_reset_ = false;
|
||||
bool callback_reset_ = false;
|
||||
|
||||
// Mutex that is held to prevent InterruptSolve() to call SCIPinterruptSolve()
|
||||
// when scip_ is being built. It also prevents rebuilding scip_ until
|
||||
// SCIPinterruptSolve() has returned.
|
||||
mutable absl::Mutex hold_interruptions_mutex_;
|
||||
};
|
||||
|
||||
class ScipConstraintHandlerForMPCallback
|
||||
@@ -206,14 +221,30 @@ class ScipConstraintHandlerForMPCallback
|
||||
std::vector<CallbackRangeConstraint> SeparateIntegerSolution(
|
||||
const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
|
||||
|
||||
MPCallback* const mp_callback() const { return mp_callback_; }
|
||||
|
||||
private:
|
||||
std::vector<CallbackRangeConstraint> SeparateSolution(
|
||||
const ScipConstraintHandlerContext& context,
|
||||
const bool at_integer_solution);
|
||||
|
||||
MPCallback* mp_callback_;
|
||||
MPCallback* const mp_callback_;
|
||||
};
|
||||
|
||||
#define RETURN_IF_ALREADY_IN_ERROR_STATE \
|
||||
do { \
|
||||
if (!status_.ok()) { \
|
||||
VLOG_EVERY_N(1, 10) << "Early abort: SCIP is in error state."; \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define RETURN_AND_STORE_IF_SCIP_ERROR(x) \
|
||||
do { \
|
||||
status_ = SCIP_TO_STATUS(x); \
|
||||
if (!status_.ok()) return; \
|
||||
} while (false)
|
||||
|
||||
SCIPInterface::SCIPInterface(MPSolver* solver)
|
||||
: MPSolverInterface(solver), scip_(nullptr) {
|
||||
status_ = CreateSCIP();
|
||||
@@ -222,10 +253,30 @@ SCIPInterface::SCIPInterface(MPSolver* solver)
|
||||
SCIPInterface::~SCIPInterface() { DeleteSCIP(); }
|
||||
|
||||
void SCIPInterface::Reset() {
|
||||
DeleteSCIP();
|
||||
// We hold calls to SCIPinterruptSolve() until the new scip_ is fully built.
|
||||
const absl::MutexLock lock(&hold_interruptions_mutex_);
|
||||
|
||||
// Remove existing one but keep it alive to copy parameters from it.
|
||||
SCIP* old_scip = DeleteSCIP(/*return_scip=*/true);
|
||||
const auto scip_deleter = absl::MakeCleanup(
|
||||
[&old_scip]() { CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY); });
|
||||
|
||||
scip_constraint_handler_.reset();
|
||||
status_ = CreateSCIP();
|
||||
ResetExtractionInformation();
|
||||
|
||||
// Install the new one.
|
||||
status_ = CreateSCIP();
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy all existing parameters from the previous SCIP to the new one. This
|
||||
// ensures that if a user calls multiple times
|
||||
// SetSolverSpecificParametersAsString() and then Reset() is called, we still
|
||||
// take into account all parameters. Note though that at the end of Solve(),
|
||||
// parameters are reset so after Solve() has been called, only the last set
|
||||
// parameters are kept.
|
||||
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcopyParamSettings(old_scip, scip_));
|
||||
}
|
||||
|
||||
absl::Status SCIPInterface::CreateSCIP() {
|
||||
@@ -253,7 +304,7 @@ absl::Status SCIPInterface::CreateSCIP() {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void SCIPInterface::DeleteSCIP() {
|
||||
SCIP* SCIPInterface::DeleteSCIP(bool return_scip) {
|
||||
// NOTE(user): DeleteSCIP() shouldn't "give up" mid-stage if it fails, since
|
||||
// it might be the user's chance to reset the solver to start fresh without
|
||||
// errors. The current code isn't perfect, since some CHECKs() remain, but
|
||||
@@ -267,24 +318,15 @@ void SCIPInterface::DeleteSCIP() {
|
||||
CHECK_EQ(SCIPreleaseCons(scip_, &scip_constraints_[j]), SCIP_OKAY);
|
||||
}
|
||||
scip_constraints_.clear();
|
||||
CHECK_EQ(SCIPfree(&scip_), SCIP_OKAY);
|
||||
|
||||
SCIP* old_scip = scip_;
|
||||
scip_ = nullptr;
|
||||
if (!return_scip) {
|
||||
CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY);
|
||||
}
|
||||
return old_scip;
|
||||
}
|
||||
|
||||
#define RETURN_IF_ALREADY_IN_ERROR_STATE \
|
||||
do { \
|
||||
if (!status_.ok()) { \
|
||||
VLOG_EVERY_N(1, 10) << "Early abort: SCIP is in error state."; \
|
||||
return; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define RETURN_AND_STORE_IF_SCIP_ERROR(x) \
|
||||
do { \
|
||||
status_ = SCIP_TO_STATUS(x); \
|
||||
if (!status_.ok()) return; \
|
||||
} while (false)
|
||||
|
||||
// Not cached.
|
||||
void SCIPInterface::SetOptimizationDirection(bool maximize) {
|
||||
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
||||
@@ -662,7 +704,13 @@ MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
|
||||
ExtractModel();
|
||||
VLOG(1) << absl::StrFormat("Model built in %s.",
|
||||
absl::FormatDuration(timer.GetDuration()));
|
||||
if (callback_ != nullptr) {
|
||||
if (scip_constraint_handler_ != nullptr) {
|
||||
// When the value of `callback_` is changed, `callback_reset_` is set and
|
||||
// code above you call Reset() that should have cleared
|
||||
// `scip_constraint_handler_`. Here we assert that if this has not happened
|
||||
// then `callback_` value has not changed.
|
||||
CHECK_EQ(scip_constraint_handler_->mp_callback(), callback_);
|
||||
} else if (callback_ != nullptr) {
|
||||
scip_constraint_handler_ =
|
||||
absl::make_unique<ScipConstraintHandlerForMPCallback>(callback_);
|
||||
RegisterConstraintHandler<EmptyStruct>(scip_constraint_handler_.get(),
|
||||
|
||||
Reference in New Issue
Block a user