2022-06-17 08:40:20 +02:00
|
|
|
// Copyright 2010-2022 Google LLC
|
2011-06-22 08:38:43 +00:00
|
|
|
// 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.
|
2014-07-09 15:18:27 +00:00
|
|
|
|
2015-04-29 19:01:13 +02:00
|
|
|
#if defined(USE_SCIP)
|
|
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
#include <stddef.h>
|
2019-04-18 16:44:47 +02:00
|
|
|
|
2015-06-18 14:08:50 +02:00
|
|
|
#include <algorithm>
|
2023-04-05 14:00:30 +02:00
|
|
|
#include <atomic>
|
2021-03-12 16:59:39 +01:00
|
|
|
#include <cstdint>
|
2019-04-18 16:44:47 +02:00
|
|
|
#include <limits>
|
2015-08-13 16:00:54 +02:00
|
|
|
#include <memory>
|
2022-05-18 16:36:05 +02:00
|
|
|
#include <optional>
|
2011-06-22 08:38:43 +00:00
|
|
|
#include <string>
|
2022-05-18 16:36:05 +02:00
|
|
|
#include <utility>
|
2011-06-22 08:38:43 +00:00
|
|
|
#include <vector>
|
|
|
|
|
|
2021-11-28 13:01:55 +01:00
|
|
|
#include "absl/base/attributes.h"
|
2020-05-06 18:22:10 +02:00
|
|
|
#include "absl/status/status.h"
|
2019-07-24 16:46:19 -07:00
|
|
|
#include "absl/strings/str_format.h"
|
2019-07-18 05:53:45 -07:00
|
|
|
#include "absl/types/optional.h"
|
2021-01-08 09:48:52 +01:00
|
|
|
#include "ortools/base/cleanup.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/base/commandlineflags.h"
|
2018-06-08 16:40:43 +02:00
|
|
|
#include "ortools/base/hash.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/base/logging.h"
|
2019-01-02 16:35:40 +01:00
|
|
|
#include "ortools/base/status_macros.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/base/timer.h"
|
2020-10-18 16:38:25 +02:00
|
|
|
#include "ortools/gscip/legacy_scip_params.h"
|
2018-06-08 16:40:43 +02:00
|
|
|
#include "ortools/linear_solver/linear_solver.h"
|
2019-07-18 05:53:45 -07:00
|
|
|
#include "ortools/linear_solver/linear_solver.pb.h"
|
2020-10-18 16:38:25 +02:00
|
|
|
#include "ortools/linear_solver/linear_solver_callback.h"
|
2022-09-12 11:28:52 +02:00
|
|
|
#include "ortools/linear_solver/proto_solver/scip_proto_solver.h"
|
2020-10-18 16:38:25 +02:00
|
|
|
#include "ortools/linear_solver/scip_callback.h"
|
2019-07-18 05:53:45 -07:00
|
|
|
#include "ortools/linear_solver/scip_helper_macros.h"
|
|
|
|
|
#include "scip/cons_indicator.h"
|
2011-06-22 08:38:43 +00:00
|
|
|
#include "scip/scip.h"
|
2021-01-08 09:48:52 +01:00
|
|
|
#include "scip/scip_copy.h"
|
2023-07-20 08:52:32 -07:00
|
|
|
#include "scip/scip_numerics.h"
|
2020-10-18 16:38:25 +02:00
|
|
|
#include "scip/scip_param.h"
|
2019-07-24 16:46:19 -07:00
|
|
|
#include "scip/scip_prob.h"
|
2011-06-22 08:38:43 +00:00
|
|
|
#include "scip/scipdefplugins.h"
|
|
|
|
|
|
2020-10-21 15:51:44 +02:00
|
|
|
ABSL_FLAG(bool, scip_feasibility_emphasis, false,
|
|
|
|
|
"When true, emphasize search towards feasibility. This may or "
|
|
|
|
|
"may not result in speedups in some problems.");
|
2011-06-22 08:38:43 +00:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
2020-10-18 16:38:25 +02:00
|
|
|
namespace {
|
|
|
|
|
// See the class ScipConstraintHandlerForMPCallback below.
|
2020-10-22 23:36:58 +02:00
|
|
|
struct EmptyStruct {};
|
|
|
|
|
} // namespace
|
2020-10-18 16:38:25 +02:00
|
|
|
|
|
|
|
|
class ScipConstraintHandlerForMPCallback;
|
2020-04-29 18:06:18 +02:00
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
class SCIPInterface : public MPSolverInterface {
|
2020-10-22 23:36:58 +02:00
|
|
|
public:
|
2020-10-28 13:42:36 +01:00
|
|
|
explicit SCIPInterface(MPSolver* solver);
|
2015-04-16 16:21:56 +02:00
|
|
|
~SCIPInterface() override;
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2015-04-16 16:21:56 +02:00
|
|
|
void SetOptimizationDirection(bool maximize) override;
|
2020-10-28 13:42:36 +01:00
|
|
|
MPSolver::ResultStatus Solve(const MPSolverParameters& param) override;
|
2022-05-18 16:38:59 +02:00
|
|
|
std::optional<MPSolutionResponse> DirectlySolveProto(
|
2021-09-23 14:30:01 +02:00
|
|
|
const MPModelRequest& request, std::atomic<bool>* interrupt) override;
|
2015-04-16 16:21:56 +02:00
|
|
|
void Reset() override;
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2023-07-20 08:52:32 -07:00
|
|
|
double infinity() override;
|
|
|
|
|
|
2015-04-16 16:21:56 +02:00
|
|
|
void SetVariableBounds(int var_index, double lb, double ub) override;
|
|
|
|
|
void SetVariableInteger(int var_index, bool integer) override;
|
|
|
|
|
void SetConstraintBounds(int row_index, double lb, double ub) override;
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void AddRowConstraint(MPConstraint* ct) override;
|
|
|
|
|
bool AddIndicatorConstraint(MPConstraint* ct) override;
|
|
|
|
|
void AddVariable(MPVariable* var) override;
|
|
|
|
|
void SetCoefficient(MPConstraint* constraint, const MPVariable* variable,
|
2017-04-19 16:20:56 +02:00
|
|
|
double new_value, double old_value) override;
|
2020-10-28 13:42:36 +01:00
|
|
|
void ClearConstraint(MPConstraint* constraint) override;
|
|
|
|
|
void SetObjectiveCoefficient(const MPVariable* variable,
|
2020-10-22 23:36:58 +02:00
|
|
|
double coefficient) override;
|
2015-04-16 16:21:56 +02:00
|
|
|
void SetObjectiveOffset(double value) override;
|
|
|
|
|
void ClearObjective() override;
|
2019-06-17 11:35:17 +02:00
|
|
|
void BranchingPriorityChangedForVariable(int var_index) override;
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2021-03-12 16:59:39 +01:00
|
|
|
int64_t iterations() const override;
|
|
|
|
|
int64_t nodes() const override;
|
2015-04-16 16:21:56 +02:00
|
|
|
MPSolver::BasisStatus row_status(int constraint_index) const override {
|
2019-01-02 16:35:40 +01:00
|
|
|
LOG(DFATAL) << "Basis status only available for continuous problems";
|
2011-09-21 15:48:33 +00:00
|
|
|
return MPSolver::FREE;
|
2011-09-05 13:42:36 +00:00
|
|
|
}
|
2015-04-16 16:21:56 +02:00
|
|
|
MPSolver::BasisStatus column_status(int variable_index) const override {
|
2019-01-02 16:35:40 +01:00
|
|
|
LOG(DFATAL) << "Basis status only available for continuous problems";
|
2011-09-21 15:48:33 +00:00
|
|
|
return MPSolver::FREE;
|
2011-09-05 13:42:36 +00:00
|
|
|
}
|
|
|
|
|
|
2015-04-16 16:21:56 +02:00
|
|
|
bool IsContinuous() const override { return false; }
|
|
|
|
|
bool IsLP() const override { return false; }
|
|
|
|
|
bool IsMIP() const override { return true; }
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2015-04-16 16:21:56 +02:00
|
|
|
void ExtractNewVariables() override;
|
|
|
|
|
void ExtractNewConstraints() override;
|
|
|
|
|
void ExtractObjective() override;
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2015-04-16 16:21:56 +02:00
|
|
|
std::string SolverVersion() const override {
|
2018-08-28 11:19:49 +02:00
|
|
|
return absl::StrFormat("SCIP %d.%d.%d [LP solver: %s]", SCIPmajorVersion(),
|
|
|
|
|
SCIPminorVersion(), SCIPtechVersion(),
|
|
|
|
|
SCIPlpiGetSolverName());
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2014-12-16 11:35:09 +00:00
|
|
|
bool InterruptSolve() override {
|
2021-01-08 09:48:52 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2019-01-02 16:35:40 +01:00
|
|
|
return SCIPinterruptSolve(scip_) == SCIP_OKAY;
|
2014-12-16 11:35:09 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void* underlying_solver() override { return reinterpret_cast<void*>(scip_); }
|
2011-08-11 18:44:23 +00:00
|
|
|
|
2020-10-18 16:38:25 +02:00
|
|
|
// MULTIPLE SOLUTIONS SUPPORT
|
|
|
|
|
// The default behavior of scip is to store the top incidentally generated
|
|
|
|
|
// integer solutions in the solution pool. The default maximum size is 100.
|
|
|
|
|
// This can be adjusted by setting the param limits/maxsol. There is no way
|
|
|
|
|
// to ensure that the pool will actually be full.
|
|
|
|
|
//
|
|
|
|
|
// You can also ask SCIP to enumerate all feasible solutions. Combined with
|
|
|
|
|
// an equality or inequality constraint on the objective (after solving once
|
|
|
|
|
// to find the optimal solution), you can use this to find all high quality
|
|
|
|
|
// solutions. See https://scip.zib.de/doc/html/COUNTER.php. This behavior is
|
|
|
|
|
// not supported directly through MPSolver, but in theory can be controlled
|
|
|
|
|
// entirely through scip parameters.
|
|
|
|
|
bool NextSolution() override;
|
|
|
|
|
|
|
|
|
|
// CALLBACK SUPPORT:
|
|
|
|
|
// * We support MPSolver's callback API via MPCallback.
|
|
|
|
|
// See ./linear_solver_callback.h.
|
|
|
|
|
// * We also support SCIP's more general callback interface, built on
|
|
|
|
|
// 'constraint handlers'. See ./scip_callback.h and test, these are added
|
|
|
|
|
// directly to the underlying SCIP object, bypassing SCIPInterface.
|
|
|
|
|
// The former works by calling the latter. See go/scip-callbacks for
|
|
|
|
|
// a complete documentation of this design.
|
|
|
|
|
|
|
|
|
|
// MPCallback API
|
2020-10-28 13:42:36 +01:00
|
|
|
void SetCallback(MPCallback* mp_callback) override;
|
2020-10-18 16:38:25 +02:00
|
|
|
bool SupportsCallbacks() const override { return true; }
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
private:
|
2020-10-28 13:42:36 +01:00
|
|
|
void SetParameters(const MPSolverParameters& param) override;
|
2015-04-16 16:21:56 +02:00
|
|
|
void SetRelativeMipGap(double value) override;
|
|
|
|
|
void SetPrimalTolerance(double value) override;
|
|
|
|
|
void SetDualTolerance(double value) override;
|
2019-11-19 14:37:31 -08:00
|
|
|
void SetPresolveMode(int presolve) override;
|
|
|
|
|
void SetScalingMode(int scaling) override;
|
|
|
|
|
void SetLpAlgorithm(int lp_algorithm) override;
|
2015-04-16 16:21:56 +02:00
|
|
|
|
2019-01-02 16:35:40 +01:00
|
|
|
// SCIP parameters allow to lower and upper bound the number of threads used
|
|
|
|
|
// (via "parallel/minnthreads" and "parallel/maxnthread", respectively). Here,
|
|
|
|
|
// we interpret "num_threads" to mean "parallel/maxnthreads", as this is what
|
|
|
|
|
// most clients probably want to do. To change "parallel/minnthreads" use
|
|
|
|
|
// SetSolverSpecificParametersAsString(). However, one must change
|
|
|
|
|
// "parallel/maxnthread" with SetNumThreads() because only this will inform
|
|
|
|
|
// the interface to run SCIPsolveConcurrent() instead of SCIPsolve() which is
|
|
|
|
|
// necessery to enable multi-threading.
|
2020-05-06 18:22:10 +02:00
|
|
|
absl::Status SetNumThreads(int num_threads) override;
|
2019-01-02 16:35:40 +01:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
bool SetSolverSpecificParametersAsString(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::string& parameters) override;
|
2013-06-11 14:49:45 +00:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
void SetUnsupportedIntegerParam(
|
|
|
|
|
MPSolverParameters::IntegerParam param) override;
|
2019-01-02 16:35:40 +01:00
|
|
|
void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param,
|
|
|
|
|
int value) override;
|
2020-10-18 16:38:25 +02:00
|
|
|
// How many solutions SCIP found.
|
|
|
|
|
int SolutionCount();
|
2020-05-06 18:22:10 +02:00
|
|
|
// Copy sol from SCIP to MPSolver.
|
2020-10-28 13:42:36 +01:00
|
|
|
void SetSolution(SCIP_SOL* solution);
|
2020-10-18 16:38:25 +02:00
|
|
|
|
2020-05-06 18:22:10 +02:00
|
|
|
absl::Status CreateSCIP();
|
2021-01-08 09:48:52 +01:00
|
|
|
// 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);
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2019-01-02 16:35:40 +01:00
|
|
|
// SCIP has many internal checks (many of which are numerical) that can fail
|
|
|
|
|
// during various phases: upon startup, when loading the model, when solving,
|
|
|
|
|
// etc. Often, the user is meant to stop at the first error, but since most
|
|
|
|
|
// of the linear solver interface API doesn't support "error reporting", we
|
|
|
|
|
// store a potential error status here.
|
|
|
|
|
// If this status isn't OK, then most operations will silently be cancelled.
|
2020-05-06 18:22:10 +02:00
|
|
|
absl::Status status_;
|
2019-01-02 16:35:40 +01:00
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP* scip_;
|
|
|
|
|
std::vector<SCIP_VAR*> scip_variables_;
|
|
|
|
|
std::vector<SCIP_CONS*> scip_constraints_;
|
2020-10-18 16:38:25 +02:00
|
|
|
int current_solution_index_ = 0;
|
2020-10-28 13:42:36 +01:00
|
|
|
MPCallback* callback_ = nullptr;
|
2020-10-18 16:38:25 +02:00
|
|
|
std::unique_ptr<ScipConstraintHandlerForMPCallback> scip_constraint_handler_;
|
|
|
|
|
// See ScipConstraintHandlerForMPCallback below.
|
|
|
|
|
EmptyStruct constraint_data_for_handler_;
|
2020-04-29 18:06:18 +02:00
|
|
|
bool branching_priority_reset_ = false;
|
2020-10-18 16:38:25 +02:00
|
|
|
bool callback_reset_ = false;
|
2021-01-08 09:48:52 +01:00
|
|
|
|
|
|
|
|
// 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_;
|
2020-10-18 16:38:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ScipConstraintHandlerForMPCallback
|
|
|
|
|
: public ScipConstraintHandler<EmptyStruct> {
|
2020-10-22 23:36:58 +02:00
|
|
|
public:
|
2020-10-28 13:42:36 +01:00
|
|
|
explicit ScipConstraintHandlerForMPCallback(MPCallback* mp_callback);
|
2020-10-18 16:38:25 +02:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
std::vector<CallbackRangeConstraint> SeparateFractionalSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
|
2020-10-18 16:38:25 +02:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
std::vector<CallbackRangeConstraint> SeparateIntegerSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
|
2020-10-18 16:38:25 +02:00
|
|
|
|
2023-05-24 15:33:27 +02:00
|
|
|
MPCallback* mp_callback() const { return mp_callback_; }
|
2021-01-08 09:48:52 +01:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
private:
|
|
|
|
|
std::vector<CallbackRangeConstraint> SeparateSolution(
|
2023-05-24 15:33:27 +02:00
|
|
|
const ScipConstraintHandlerContext& context, bool at_integer_solution);
|
2020-10-18 16:38:25 +02:00
|
|
|
|
2021-01-08 09:48:52 +01:00
|
|
|
MPCallback* const mp_callback_;
|
2011-06-22 08:38:43 +00:00
|
|
|
};
|
|
|
|
|
|
2021-01-08 09:48:52 +01:00
|
|
|
#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)
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIPInterface::SCIPInterface(MPSolver* solver)
|
2016-07-08 15:04:07 +02:00
|
|
|
: MPSolverInterface(solver), scip_(nullptr) {
|
2019-01-02 16:35:40 +01:00
|
|
|
status_ = CreateSCIP();
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2014-01-08 12:01:58 +00:00
|
|
|
SCIPInterface::~SCIPInterface() { DeleteSCIP(); }
|
2011-06-22 08:38:43 +00:00
|
|
|
|
|
|
|
|
void SCIPInterface::Reset() {
|
2021-01-08 09:48:52 +01:00
|
|
|
// 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); });
|
|
|
|
|
|
2020-10-18 16:38:25 +02:00
|
|
|
scip_constraint_handler_.reset();
|
2011-06-22 08:38:43 +00:00
|
|
|
ResetExtractionInformation();
|
2021-01-08 09:48:52 +01:00
|
|
|
|
|
|
|
|
// 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_));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2020-05-06 18:22:10 +02:00
|
|
|
absl::Status SCIPInterface::CreateSCIP() {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_IF_SCIP_ERROR(SCIPcreate(&scip_));
|
|
|
|
|
RETURN_IF_SCIP_ERROR(SCIPincludeDefaultPlugins(scip_));
|
2015-01-16 17:02:32 +00:00
|
|
|
// Set the emphasis to enum SCIP_PARAMEMPHASIS_FEASIBILITY. Do not print
|
|
|
|
|
// the new parameter (quiet = true).
|
2020-10-21 00:21:54 +02:00
|
|
|
if (absl::GetFlag(FLAGS_scip_feasibility_emphasis)) {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_IF_SCIP_ERROR(SCIPsetEmphasis(scip_, SCIP_PARAMEMPHASIS_FEASIBILITY,
|
2020-10-22 23:36:58 +02:00
|
|
|
/*quiet=*/true));
|
2015-01-16 17:02:32 +00:00
|
|
|
}
|
2017-04-19 16:20:56 +02:00
|
|
|
// Default clock type. We use wall clock time because getting CPU user seconds
|
|
|
|
|
// involves calling times() which is very expensive.
|
2019-09-11 12:50:11 +02:00
|
|
|
// NOTE(user): Also, time limit based on CPU user seconds is *NOT* thread
|
|
|
|
|
// safe. We observed that different instances of SCIP running concurrently
|
|
|
|
|
// in different threads consume the time limit *together*. E.g., 2 threads
|
|
|
|
|
// running SCIP with time limit 10s each will both terminate after ~5s.
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_IF_SCIP_ERROR(
|
|
|
|
|
SCIPsetIntParam(scip_, "timing/clocktype", SCIP_CLOCKTYPE_WALL));
|
2020-10-22 23:36:58 +02:00
|
|
|
RETURN_IF_SCIP_ERROR(SCIPcreateProb(scip_, solver_->name_.c_str(), nullptr,
|
|
|
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
|
|
|
nullptr, nullptr));
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_IF_SCIP_ERROR(SCIPsetObjsense(
|
|
|
|
|
scip_, maximize_ ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
|
2020-05-06 18:22:10 +02:00
|
|
|
return absl::OkStatus();
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-20 08:52:32 -07:00
|
|
|
double SCIPInterface::infinity() { return SCIPinfinity(scip_); }
|
|
|
|
|
|
2021-01-08 09:48:52 +01:00
|
|
|
SCIP* SCIPInterface::DeleteSCIP(bool return_scip) {
|
2019-01-02 16:35:40 +01:00
|
|
|
// 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
|
|
|
|
|
// hopefully they'll never be triggered in practice.
|
2016-07-08 15:04:07 +02:00
|
|
|
CHECK(scip_ != nullptr);
|
2011-06-22 08:38:43 +00:00
|
|
|
for (int i = 0; i < scip_variables_.size(); ++i) {
|
2019-01-02 16:35:40 +01:00
|
|
|
CHECK_EQ(SCIPreleaseVar(scip_, &scip_variables_[i]), SCIP_OKAY);
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
scip_variables_.clear();
|
|
|
|
|
for (int j = 0; j < scip_constraints_.size(); ++j) {
|
2019-01-02 16:35:40 +01:00
|
|
|
CHECK_EQ(SCIPreleaseCons(scip_, &scip_constraints_[j]), SCIP_OKAY);
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
scip_constraints_.clear();
|
2021-01-08 09:48:52 +01:00
|
|
|
|
|
|
|
|
SCIP* old_scip = scip_;
|
2016-07-08 15:04:07 +02:00
|
|
|
scip_ = nullptr;
|
2021-01-08 09:48:52 +01:00
|
|
|
if (!return_scip) {
|
|
|
|
|
CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY);
|
|
|
|
|
}
|
|
|
|
|
return old_scip;
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2017-04-19 16:20:56 +02:00
|
|
|
// Not cached.
|
2011-06-22 08:38:43 +00:00
|
|
|
void SCIPInterface::SetOptimizationDirection(bool maximize) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPsetObjsense(
|
2011-06-22 08:38:43 +00:00
|
|
|
scip_, maximize ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::SetVariableBounds(int var_index, double lb, double ub) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
2015-06-18 14:08:50 +02:00
|
|
|
if (variable_is_extracted(var_index)) {
|
2017-04-19 16:20:56 +02:00
|
|
|
// Not cached if the variable has been extracted.
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK_LT(var_index, last_variable_index_);
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
|
|
|
|
SCIPchgVarLb(scip_, scip_variables_[var_index], lb));
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
|
|
|
|
SCIPchgVarUb(scip_, scip_variables_[var_index], ub));
|
2011-06-22 08:38:43 +00:00
|
|
|
} else {
|
|
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::SetVariableInteger(int var_index, bool integer) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
2015-06-18 14:08:50 +02:00
|
|
|
if (variable_is_extracted(var_index)) {
|
2011-06-22 08:38:43 +00:00
|
|
|
// Not cached if the variable has been extracted.
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
2011-11-01 09:34:36 +00:00
|
|
|
#if (SCIP_VERSION >= 210)
|
|
|
|
|
SCIP_Bool infeasible = false;
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPchgVarType(
|
2011-11-01 09:34:36 +00:00
|
|
|
scip_, scip_variables_[var_index],
|
2014-01-08 12:01:58 +00:00
|
|
|
integer ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS, &infeasible));
|
2011-11-01 09:34:36 +00:00
|
|
|
#else
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPchgVarType(
|
2011-06-22 08:38:43 +00:00
|
|
|
scip_, scip_variables_[var_index],
|
|
|
|
|
integer ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS));
|
2020-10-22 23:36:58 +02:00
|
|
|
#endif // SCIP_VERSION >= 210
|
2011-06-22 08:38:43 +00:00
|
|
|
} else {
|
|
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::SetConstraintBounds(int index, double lb, double ub) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
2015-06-18 14:08:50 +02:00
|
|
|
if (constraint_is_extracted(index)) {
|
2017-04-19 16:20:56 +02:00
|
|
|
// Not cached if the row has been extracted.
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK_LT(index, last_constraint_index_);
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
|
|
|
|
SCIPchgLhsLinear(scip_, scip_constraints_[index], lb));
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
|
|
|
|
SCIPchgRhsLinear(scip_, scip_constraints_[index], ub));
|
2011-06-22 08:38:43 +00:00
|
|
|
} else {
|
|
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::SetCoefficient(MPConstraint* constraint,
|
|
|
|
|
const MPVariable* variable, double new_value,
|
2017-04-19 16:20:56 +02:00
|
|
|
double old_value) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
2015-06-18 14:08:50 +02:00
|
|
|
if (variable_is_extracted(variable->index()) &&
|
|
|
|
|
constraint_is_extracted(constraint->index())) {
|
2011-06-22 08:38:43 +00:00
|
|
|
// The modification of the coefficient for an extracted row and
|
|
|
|
|
// variable is not cached.
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK_LT(constraint->index(), last_constraint_index_);
|
|
|
|
|
DCHECK_LT(variable->index(), last_variable_index_);
|
2011-06-22 08:38:43 +00:00
|
|
|
// SCIP does not allow to set a coefficient directly, so we add the
|
|
|
|
|
// difference between the new and the old value instead.
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCoefLinear(
|
2015-06-18 14:08:50 +02:00
|
|
|
scip_, scip_constraints_[constraint->index()],
|
|
|
|
|
scip_variables_[variable->index()], new_value - old_value));
|
2011-06-22 08:38:43 +00:00
|
|
|
} else {
|
|
|
|
|
// The modification of an unextracted row or variable is cached
|
|
|
|
|
// and handled in ExtractModel.
|
|
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Not cached
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::ClearConstraint(MPConstraint* constraint) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
|
|
|
|
const int constraint_index = constraint->index();
|
|
|
|
|
// Constraint may not have been extracted yet.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (!constraint_is_extracted(constraint_index)) return;
|
2020-10-28 13:42:36 +01:00
|
|
|
for (const auto& entry : constraint->coefficients_) {
|
2015-06-18 14:08:50 +02:00
|
|
|
const int var_index = entry.first->index();
|
|
|
|
|
const double old_coef_value = entry.second;
|
|
|
|
|
DCHECK(variable_is_extracted(var_index));
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
2020-10-28 13:42:36 +01:00
|
|
|
// Set coefficient to zero by subtracting the old coefficient value.
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
2015-06-18 14:08:50 +02:00
|
|
|
SCIPaddCoefLinear(scip_, scip_constraints_[constraint_index],
|
|
|
|
|
scip_variables_[var_index], -old_coef_value));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cached
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::SetObjectiveCoefficient(const MPVariable* variable,
|
2014-01-08 12:01:58 +00:00
|
|
|
double coefficient) {
|
2011-06-22 08:38:43 +00:00
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cached
|
|
|
|
|
void SCIPInterface::SetObjectiveOffset(double value) {
|
|
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear objective of all its terms.
|
|
|
|
|
void SCIPInterface::ClearObjective() {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2019-07-18 05:53:45 -07:00
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
InvalidateSolutionSynchronization();
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
2011-06-22 08:38:43 +00:00
|
|
|
// Clear linear terms
|
2020-10-28 13:42:36 +01:00
|
|
|
for (const auto& entry : solver_->objective_->coefficients_) {
|
2013-10-10 15:23:20 +00:00
|
|
|
const int var_index = entry.first->index();
|
2011-06-22 08:38:43 +00:00
|
|
|
// Variable may have not been extracted yet.
|
2015-06-18 14:08:50 +02:00
|
|
|
if (!variable_is_extracted(var_index)) {
|
2011-06-22 08:38:43 +00:00
|
|
|
DCHECK_NE(MODEL_SYNCHRONIZED, sync_status_);
|
|
|
|
|
} else {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
2019-01-02 16:35:40 +01:00
|
|
|
SCIPchgVarObj(scip_, scip_variables_[var_index], 0.0));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-18 05:53:45 -07:00
|
|
|
// Note: we don't clear the objective offset here because it's not necessary
|
|
|
|
|
// (it's always reset anyway in ExtractObjective) and we sometimes run into
|
|
|
|
|
// crashes when clearing the whole model (see
|
|
|
|
|
// http://test/OCL:253365573:BASE:253566457:1560777456754:e181f4ab).
|
|
|
|
|
// It's not worth to spend time investigating this issue.
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2019-06-17 11:35:17 +02:00
|
|
|
void SCIPInterface::BranchingPriorityChangedForVariable(int var_index) {
|
|
|
|
|
// As of 2019-05, SCIP does not support setting branching priority for
|
|
|
|
|
// variables in models that have already been solved. Therefore, we force
|
|
|
|
|
// reset the model when setting the priority on an already extracted variable.
|
|
|
|
|
// Note that this is a more drastic step than merely changing the sync_status.
|
|
|
|
|
// This may be slightly conservative, as it is technically possible that
|
|
|
|
|
// the extraction has occurred without a call to Solve().
|
|
|
|
|
if (variable_is_extracted(var_index)) {
|
|
|
|
|
branching_priority_reset_ = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::AddRowConstraint(MPConstraint* ct) {
|
2011-06-22 08:38:43 +00:00
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
bool SCIPInterface::AddIndicatorConstraint(MPConstraint* ct) {
|
2019-04-18 16:44:47 +02:00
|
|
|
sync_status_ = MUST_RELOAD;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::AddVariable(MPVariable* var) { sync_status_ = MUST_RELOAD; }
|
2011-06-22 08:38:43 +00:00
|
|
|
|
|
|
|
|
void SCIPInterface::ExtractNewVariables() {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
int total_num_vars = solver_->variables_.size();
|
|
|
|
|
if (total_num_vars > last_variable_index_) {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
2011-06-22 08:38:43 +00:00
|
|
|
// Define new variables
|
|
|
|
|
for (int j = last_variable_index_; j < total_num_vars; ++j) {
|
2020-10-28 13:42:36 +01:00
|
|
|
MPVariable* const var = solver_->variables_[j];
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK(!variable_is_extracted(j));
|
|
|
|
|
set_variable_as_extracted(j, true);
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP_VAR* scip_var = nullptr;
|
2011-06-22 08:38:43 +00:00
|
|
|
// The true objective coefficient will be set later in ExtractObjective.
|
|
|
|
|
double tmp_obj_coef = 0.0;
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateVar(
|
2014-01-08 12:01:58 +00:00
|
|
|
scip_, &scip_var, var->name().c_str(), var->lb(), var->ub(),
|
|
|
|
|
tmp_obj_coef,
|
|
|
|
|
var->integer() ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS, true,
|
2016-07-08 15:04:07 +02:00
|
|
|
false, nullptr, nullptr, nullptr, nullptr, nullptr));
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddVar(scip_, scip_var));
|
2011-06-22 08:38:43 +00:00
|
|
|
scip_variables_.push_back(scip_var);
|
2019-06-17 11:35:17 +02:00
|
|
|
const int branching_priority = var->branching_priority();
|
|
|
|
|
if (branching_priority != 0) {
|
|
|
|
|
const int index = var->index();
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPchgVarBranchPriority(
|
2019-06-17 11:35:17 +02:00
|
|
|
scip_, scip_variables_[index], branching_priority));
|
|
|
|
|
}
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
// Add new variables to existing constraints.
|
|
|
|
|
for (int i = 0; i < last_constraint_index_; i++) {
|
2020-10-28 13:42:36 +01:00
|
|
|
MPConstraint* const ct = solver_->constraints_[i];
|
|
|
|
|
for (const auto& entry : ct->coefficients_) {
|
2013-10-10 15:23:20 +00:00
|
|
|
const int var_index = entry.first->index();
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK(variable_is_extracted(var_index));
|
|
|
|
|
if (var_index >= last_variable_index_) {
|
|
|
|
|
// The variable is new, so we know the previous coefficient
|
2011-06-22 08:38:43 +00:00
|
|
|
// value was 0 and we can directly add the coefficient.
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
|
|
|
|
SCIPaddCoefLinear(scip_, scip_constraints_[i],
|
|
|
|
|
scip_variables_[var_index], entry.second));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::ExtractNewConstraints() {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2011-06-22 08:38:43 +00:00
|
|
|
int total_num_rows = solver_->constraints_.size();
|
|
|
|
|
if (last_constraint_index_ < total_num_rows) {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
2011-06-22 08:38:43 +00:00
|
|
|
// Find the length of the longest row.
|
|
|
|
|
int max_row_length = 0;
|
|
|
|
|
for (int i = last_constraint_index_; i < total_num_rows; ++i) {
|
2020-10-28 13:42:36 +01:00
|
|
|
MPConstraint* const ct = solver_->constraints_[i];
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK(!constraint_is_extracted(i));
|
|
|
|
|
set_constraint_as_extracted(i, true);
|
2011-06-22 08:38:43 +00:00
|
|
|
if (ct->coefficients_.size() > max_row_length) {
|
|
|
|
|
max_row_length = ct->coefficients_.size();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-28 13:42:36 +01:00
|
|
|
std::unique_ptr<SCIP_VAR*[]> vars(new SCIP_VAR*[max_row_length]);
|
2019-04-18 16:44:47 +02:00
|
|
|
std::unique_ptr<double[]> coeffs(new double[max_row_length]);
|
2011-06-22 08:38:43 +00:00
|
|
|
// Add each new constraint.
|
|
|
|
|
for (int i = last_constraint_index_; i < total_num_rows; ++i) {
|
2020-10-28 13:42:36 +01:00
|
|
|
MPConstraint* const ct = solver_->constraints_[i];
|
2015-06-18 14:08:50 +02:00
|
|
|
DCHECK(constraint_is_extracted(i));
|
2017-04-19 16:20:56 +02:00
|
|
|
const int size = ct->coefficients_.size();
|
2011-06-22 08:38:43 +00:00
|
|
|
int j = 0;
|
2020-10-28 13:42:36 +01:00
|
|
|
for (const auto& entry : ct->coefficients_) {
|
2015-06-18 14:08:50 +02:00
|
|
|
const int var_index = entry.first->index();
|
|
|
|
|
DCHECK(variable_is_extracted(var_index));
|
|
|
|
|
vars[j] = scip_variables_[var_index];
|
2019-04-18 16:44:47 +02:00
|
|
|
coeffs[j] = entry.second;
|
2011-06-22 08:38:43 +00:00
|
|
|
j++;
|
|
|
|
|
}
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP_CONS* scip_constraint = nullptr;
|
2013-10-10 15:23:20 +00:00
|
|
|
const bool is_lazy = ct->is_lazy();
|
2019-04-18 16:44:47 +02:00
|
|
|
if (ct->indicator_variable() != nullptr) {
|
|
|
|
|
const int ind_index = ct->indicator_variable()->index();
|
|
|
|
|
DCHECK(variable_is_extracted(ind_index));
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP_VAR* ind_var = scip_variables_[ind_index];
|
2019-04-18 16:44:47 +02:00
|
|
|
if (ct->indicator_value() == 0) {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
2019-04-18 16:44:47 +02:00
|
|
|
SCIPgetNegatedVar(scip_, scip_variables_[ind_index], &ind_var));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ct->ub() < std::numeric_limits<double>::infinity()) {
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateConsIndicator(
|
2019-04-18 16:44:47 +02:00
|
|
|
scip_, &scip_constraint, ct->name().c_str(), ind_var, size,
|
2020-10-28 13:42:36 +01:00
|
|
|
vars.get(), coeffs.get(), ct->ub(),
|
|
|
|
|
/*initial=*/!is_lazy,
|
|
|
|
|
/*separate=*/true,
|
|
|
|
|
/*enforce=*/true,
|
|
|
|
|
/*check=*/true,
|
|
|
|
|
/*propagate=*/true,
|
|
|
|
|
/*local=*/false,
|
|
|
|
|
/*dynamic=*/false,
|
|
|
|
|
/*removable=*/is_lazy,
|
|
|
|
|
/*stickingatnode=*/false));
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCons(scip_, scip_constraint));
|
2019-04-18 16:44:47 +02:00
|
|
|
scip_constraints_.push_back(scip_constraint);
|
|
|
|
|
}
|
|
|
|
|
if (ct->lb() > -std::numeric_limits<double>::infinity()) {
|
|
|
|
|
for (int i = 0; i < size; ++i) {
|
|
|
|
|
coeffs[i] *= -1;
|
|
|
|
|
}
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateConsIndicator(
|
2019-04-18 16:44:47 +02:00
|
|
|
scip_, &scip_constraint, ct->name().c_str(), ind_var, size,
|
2020-10-28 13:42:36 +01:00
|
|
|
vars.get(), coeffs.get(), -ct->lb(),
|
|
|
|
|
/*initial=*/!is_lazy,
|
|
|
|
|
/*separate=*/true,
|
|
|
|
|
/*enforce=*/true,
|
|
|
|
|
/*check=*/true,
|
|
|
|
|
/*propagate=*/true,
|
|
|
|
|
/*local=*/false,
|
|
|
|
|
/*dynamic=*/false,
|
|
|
|
|
/*removable=*/is_lazy,
|
|
|
|
|
/*stickingatnode=*/false));
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCons(scip_, scip_constraint));
|
2019-04-18 16:44:47 +02:00
|
|
|
scip_constraints_.push_back(scip_constraint);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// See
|
|
|
|
|
// http://scip.zib.de/doc/html/cons__linear_8h.php#aa7aed137a4130b35b168812414413481
|
|
|
|
|
// for an explanation of the parameters.
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateConsLinear(
|
2019-04-18 16:44:47 +02:00
|
|
|
scip_, &scip_constraint, ct->name().c_str(), size, vars.get(),
|
2020-10-28 13:42:36 +01:00
|
|
|
coeffs.get(), ct->lb(), ct->ub(),
|
|
|
|
|
/*initial=*/!is_lazy,
|
|
|
|
|
/*separate=*/true,
|
|
|
|
|
/*enforce=*/true,
|
|
|
|
|
/*check=*/true,
|
|
|
|
|
/*propagate=*/true,
|
|
|
|
|
/*local=*/false,
|
|
|
|
|
/*modifiable=*/false,
|
|
|
|
|
/*dynamic=*/false,
|
|
|
|
|
/*removable=*/is_lazy,
|
2020-10-22 23:36:58 +02:00
|
|
|
/*stickingatnode=*/false));
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCons(scip_, scip_constraint));
|
2019-04-18 16:44:47 +02:00
|
|
|
scip_constraints_.push_back(scip_constraint);
|
|
|
|
|
}
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::ExtractObjective() {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_IF_ALREADY_IN_ERROR_STATE;
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
|
2017-04-19 16:20:56 +02:00
|
|
|
// Linear objective: set objective coefficients for all variables (some might
|
|
|
|
|
// have been modified).
|
2020-10-28 13:42:36 +01:00
|
|
|
for (const auto& entry : solver_->objective_->coefficients_) {
|
2017-04-19 16:20:56 +02:00
|
|
|
const int var_index = entry.first->index();
|
|
|
|
|
const double obj_coef = entry.second;
|
2019-07-18 05:53:45 -07:00
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(
|
2014-01-08 12:01:58 +00:00
|
|
|
SCIPchgVarObj(scip_, scip_variables_[var_index], obj_coef));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-18 05:53:45 -07:00
|
|
|
// Constant term: change objective offset.
|
|
|
|
|
RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddOrigObjoffset(
|
|
|
|
|
scip_, solver_->Objective().offset() - SCIPgetOrigObjoffset(scip_)));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
#define RETURN_ABNORMAL_IF_BAD_STATUS \
|
|
|
|
|
do { \
|
|
|
|
|
if (!status_.ok()) { \
|
|
|
|
|
LOG_IF(INFO, solver_->OutputIsEnabled()) \
|
|
|
|
|
<< "Invalid SCIP status: " << status_; \
|
|
|
|
|
return result_status_ = MPSolver::ABNORMAL; \
|
|
|
|
|
} \
|
2019-01-02 16:35:40 +01:00
|
|
|
} while (false)
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
#define RETURN_ABNORMAL_IF_SCIP_ERROR(x) \
|
|
|
|
|
do { \
|
|
|
|
|
RETURN_ABNORMAL_IF_BAD_STATUS; \
|
|
|
|
|
status_ = SCIP_TO_STATUS(x); \
|
|
|
|
|
RETURN_ABNORMAL_IF_BAD_STATUS; \
|
2019-01-02 16:35:40 +01:00
|
|
|
} while (false);
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
MPSolver::ResultStatus SCIPInterface::Solve(const MPSolverParameters& param) {
|
2019-01-02 16:35:40 +01:00
|
|
|
// "status_" may encode a variety of failure scenarios, many of which would
|
|
|
|
|
// correspond to another MPResultStatus than ABNORMAL, but since SCIP is a
|
|
|
|
|
// moving target, we use the most likely error code here (abnormalities,
|
|
|
|
|
// often numeric), and rely on the user enabling output to see more details.
|
|
|
|
|
RETURN_ABNORMAL_IF_BAD_STATUS;
|
|
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
WallTimer timer;
|
|
|
|
|
timer.Start();
|
|
|
|
|
|
2011-09-08 00:34:13 +00:00
|
|
|
// Note that SCIP does not provide any incrementality.
|
2019-01-02 16:35:40 +01:00
|
|
|
// TODO(user): Is that still true now (2018) ?
|
2011-09-08 00:34:13 +00:00
|
|
|
if (param.GetIntegerParam(MPSolverParameters::INCREMENTALITY) ==
|
2020-10-22 23:36:58 +02:00
|
|
|
MPSolverParameters::INCREMENTALITY_OFF ||
|
|
|
|
|
branching_priority_reset_ || callback_reset_) {
|
2011-09-08 00:34:13 +00:00
|
|
|
Reset();
|
2020-04-29 18:06:18 +02:00
|
|
|
branching_priority_reset_ = false;
|
2020-10-18 16:38:25 +02:00
|
|
|
callback_reset_ = false;
|
2011-09-08 00:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
// Set log level.
|
2012-11-19 16:23:35 +00:00
|
|
|
SCIPsetMessagehdlrQuiet(scip_, quiet_);
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2017-04-19 16:20:56 +02:00
|
|
|
// Special case if the model is empty since SCIP expects a non-empty model.
|
|
|
|
|
if (solver_->variables_.empty() && solver_->constraints_.empty()) {
|
2011-06-22 08:38:43 +00:00
|
|
|
sync_status_ = SOLUTION_SYNCHRONIZED;
|
|
|
|
|
result_status_ = MPSolver::OPTIMAL;
|
2011-12-16 21:02:01 +00:00
|
|
|
objective_value_ = solver_->Objective().offset();
|
2020-11-02 18:48:31 +01:00
|
|
|
best_objective_bound_ = solver_->Objective().offset();
|
2011-06-22 08:38:43 +00:00
|
|
|
return result_status_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExtractModel();
|
2018-10-31 16:18:18 +01:00
|
|
|
VLOG(1) << absl::StrFormat("Model built in %s.",
|
|
|
|
|
absl::FormatDuration(timer.GetDuration()));
|
2021-01-08 09:48:52 +01:00
|
|
|
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) {
|
2020-10-18 16:38:25 +02:00
|
|
|
scip_constraint_handler_ =
|
2022-03-07 11:31:58 +01:00
|
|
|
std::make_unique<ScipConstraintHandlerForMPCallback>(callback_);
|
2020-10-18 16:38:25 +02:00
|
|
|
RegisterConstraintHandler<EmptyStruct>(scip_constraint_handler_.get(),
|
|
|
|
|
scip_);
|
|
|
|
|
AddCallbackConstraint<EmptyStruct>(scip_, scip_constraint_handler_.get(),
|
|
|
|
|
"mp_solver_callback_constraint_for_scip",
|
|
|
|
|
&constraint_data_for_handler_,
|
|
|
|
|
ScipCallbackConstraintOptions());
|
|
|
|
|
}
|
2011-06-22 08:38:43 +00:00
|
|
|
|
|
|
|
|
// Time limit.
|
2013-10-10 15:23:20 +00:00
|
|
|
if (solver_->time_limit() != 0) {
|
2011-06-22 08:38:43 +00:00
|
|
|
VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(
|
2014-01-08 12:01:58 +00:00
|
|
|
SCIPsetRealParam(scip_, "limits/time", solver_->time_limit_in_secs()));
|
2011-06-22 08:38:43 +00:00
|
|
|
} else {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPresetParam(scip_, "limits/time"));
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2017-04-19 16:20:56 +02:00
|
|
|
// We first set our internal MPSolverParameters from param and then set any
|
|
|
|
|
// user specified internal solver, ie. SCIP, parameters via
|
2015-12-18 16:43:52 +01:00
|
|
|
// solver_specific_parameter_string_.
|
|
|
|
|
// Default MPSolverParameters can override custom parameters (for example for
|
|
|
|
|
// presolving) and therefore we apply MPSolverParameters first.
|
|
|
|
|
SetParameters(param);
|
2013-10-10 15:23:20 +00:00
|
|
|
solver_->SetSolverSpecificParametersAsString(
|
|
|
|
|
solver_->solver_specific_parameter_string_);
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2015-06-18 14:08:50 +02:00
|
|
|
// Use the solution hint if any.
|
|
|
|
|
if (!solver_->solution_hint_.empty()) {
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP_SOL* solution;
|
2017-04-19 16:20:56 +02:00
|
|
|
bool is_solution_partial = false;
|
2015-06-18 14:08:50 +02:00
|
|
|
const int num_vars = solver_->variables_.size();
|
|
|
|
|
if (solver_->solution_hint_.size() != num_vars) {
|
2017-04-19 16:20:56 +02:00
|
|
|
// We start by creating an empty partial solution.
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(
|
|
|
|
|
SCIPcreatePartialSol(scip_, &solution, nullptr));
|
2017-04-19 16:20:56 +02:00
|
|
|
is_solution_partial = true;
|
|
|
|
|
} else {
|
|
|
|
|
// We start by creating the all-zero solution.
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPcreateSol(scip_, &solution, nullptr));
|
2015-06-18 14:08:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill the other variables from the given solution hint.
|
2020-10-28 13:42:36 +01:00
|
|
|
for (const std::pair<const MPVariable*, double>& p :
|
2019-01-02 16:35:40 +01:00
|
|
|
solver_->solution_hint_) {
|
|
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPsetSolVal(
|
2015-06-18 14:08:50 +02:00
|
|
|
scip_, solution, scip_variables_[p.first->index()], p.second));
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-19 16:20:56 +02:00
|
|
|
if (!is_solution_partial) {
|
|
|
|
|
SCIP_Bool is_feasible;
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPcheckSol(
|
2020-10-22 23:36:58 +02:00
|
|
|
scip_, solution, /*printreason=*/false, /*completely=*/true,
|
2020-10-28 13:42:36 +01:00
|
|
|
/*checkbounds=*/true, /*checkintegrality=*/true, /*checklprows=*/true,
|
|
|
|
|
&is_feasible));
|
2017-04-19 16:20:56 +02:00
|
|
|
VLOG(1) << "Solution hint is "
|
|
|
|
|
<< (is_feasible ? "FEASIBLE" : "INFEASIBLE");
|
|
|
|
|
}
|
2015-06-18 14:08:50 +02:00
|
|
|
|
|
|
|
|
// TODO(user): I more or less copied this from the SCIPreadSol() code that
|
|
|
|
|
// reads a solution from a file. I am not sure what SCIPisTransformed() is
|
|
|
|
|
// or what is the difference between the try and add version. In any case
|
|
|
|
|
// this seems to always call SCIPaddSolFree() for now and it works.
|
|
|
|
|
SCIP_Bool is_stored;
|
2017-04-19 16:20:56 +02:00
|
|
|
if (!is_solution_partial && SCIPisTransformed(scip_)) {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPtrySolFree(
|
2020-10-22 23:36:58 +02:00
|
|
|
scip_, &solution, /*printreason=*/false, /*completely=*/true,
|
2020-10-28 13:42:36 +01:00
|
|
|
/*checkbounds=*/true, /*checkintegrality=*/true, /*checklprows=*/true,
|
|
|
|
|
&is_stored));
|
2015-06-18 14:08:50 +02:00
|
|
|
} else {
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(
|
|
|
|
|
SCIPaddSolFree(scip_, &solution, &is_stored));
|
2015-06-18 14:08:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
// Solve.
|
|
|
|
|
timer.Restart();
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(solver_->GetNumThreads() > 1
|
|
|
|
|
? SCIPsolveConcurrent(scip_)
|
|
|
|
|
: SCIPsolve(scip_));
|
2018-10-31 16:18:18 +01:00
|
|
|
VLOG(1) << absl::StrFormat("Solved in %s.",
|
|
|
|
|
absl::FormatDuration(timer.GetDuration()));
|
2020-10-18 16:38:25 +02:00
|
|
|
current_solution_index_ = 0;
|
2011-06-22 08:38:43 +00:00
|
|
|
// Get the results.
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP_SOL* const solution = SCIPgetBestSol(scip_);
|
2016-07-08 15:04:07 +02:00
|
|
|
if (solution != nullptr) {
|
2017-04-19 16:20:56 +02:00
|
|
|
// If optimal or feasible solution is found.
|
2020-05-06 18:22:10 +02:00
|
|
|
SetSolution(solution);
|
2011-06-22 08:38:43 +00:00
|
|
|
} else {
|
|
|
|
|
VLOG(1) << "No feasible solution found.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check the status: optimal, infeasible, etc.
|
|
|
|
|
SCIP_STATUS scip_status = SCIPgetStatus(scip_);
|
|
|
|
|
switch (scip_status) {
|
2020-10-22 23:36:58 +02:00
|
|
|
case SCIP_STATUS_OPTIMAL:
|
|
|
|
|
result_status_ = MPSolver::OPTIMAL;
|
|
|
|
|
break;
|
|
|
|
|
case SCIP_STATUS_GAPLIMIT:
|
|
|
|
|
// To be consistent with the other solvers.
|
|
|
|
|
result_status_ = MPSolver::OPTIMAL;
|
|
|
|
|
break;
|
|
|
|
|
case SCIP_STATUS_INFEASIBLE:
|
|
|
|
|
result_status_ = MPSolver::INFEASIBLE;
|
|
|
|
|
break;
|
|
|
|
|
case SCIP_STATUS_UNBOUNDED:
|
|
|
|
|
result_status_ = MPSolver::UNBOUNDED;
|
|
|
|
|
break;
|
|
|
|
|
case SCIP_STATUS_INFORUNBD:
|
|
|
|
|
// TODO(user): We could introduce our own "infeasible or
|
|
|
|
|
// unbounded" status.
|
|
|
|
|
result_status_ = MPSolver::INFEASIBLE;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (solution != nullptr) {
|
|
|
|
|
result_status_ = MPSolver::FEASIBLE;
|
|
|
|
|
} else if (scip_status == SCIP_STATUS_TIMELIMIT ||
|
|
|
|
|
scip_status == SCIP_STATUS_TOTALNODELIMIT) {
|
|
|
|
|
result_status_ = MPSolver::NOT_SOLVED;
|
|
|
|
|
} else {
|
|
|
|
|
result_status_ = MPSolver::ABNORMAL;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2019-01-02 16:35:40 +01:00
|
|
|
RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPresetParams(scip_));
|
2011-06-22 08:38:43 +00:00
|
|
|
|
|
|
|
|
sync_status_ = SOLUTION_SYNCHRONIZED;
|
|
|
|
|
return result_status_;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::SetSolution(SCIP_SOL* solution) {
|
2020-05-06 18:22:10 +02:00
|
|
|
objective_value_ = SCIPgetSolOrigObj(scip_, solution);
|
2020-11-02 18:48:31 +01:00
|
|
|
best_objective_bound_ = SCIPgetDualbound(scip_);
|
|
|
|
|
VLOG(1) << "objective=" << objective_value_
|
|
|
|
|
<< ", bound=" << best_objective_bound_;
|
2020-05-06 18:22:10 +02:00
|
|
|
for (int i = 0; i < solver_->variables_.size(); ++i) {
|
2020-10-28 13:42:36 +01:00
|
|
|
MPVariable* const var = solver_->variables_[i];
|
2020-05-06 18:22:10 +02:00
|
|
|
const int var_index = var->index();
|
|
|
|
|
const double val =
|
|
|
|
|
SCIPgetSolVal(scip_, solution, scip_variables_[var_index]);
|
|
|
|
|
var->set_solution_value(val);
|
|
|
|
|
VLOG(3) << var->name() << "=" << val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 16:38:59 +02:00
|
|
|
std::optional<MPSolutionResponse> SCIPInterface::DirectlySolveProto(
|
2021-09-23 14:30:01 +02:00
|
|
|
const MPModelRequest& request, std::atomic<bool>* interrupt) {
|
2019-07-24 16:46:19 -07:00
|
|
|
// ScipSolveProto doesn't solve concurrently.
|
2022-05-18 16:38:59 +02:00
|
|
|
if (solver_->GetNumThreads() > 1) return std::nullopt;
|
2019-07-24 16:46:19 -07:00
|
|
|
|
2021-09-23 14:30:01 +02:00
|
|
|
// Interruption via atomic<bool> is not directly supported by SCIP.
|
2022-05-18 16:38:59 +02:00
|
|
|
if (interrupt != nullptr) return std::nullopt;
|
2021-09-23 14:30:01 +02:00
|
|
|
|
2019-07-18 05:53:45 -07:00
|
|
|
const auto status_or = ScipSolveProto(request);
|
2020-10-22 23:36:58 +02:00
|
|
|
if (status_or.ok()) return status_or.value();
|
2019-07-18 05:53:45 -07:00
|
|
|
// Special case: if something is not implemented yet, fall back to solving
|
|
|
|
|
// through MPSolver.
|
2022-05-18 16:38:59 +02:00
|
|
|
if (absl::IsUnimplemented(status_or.status())) return std::nullopt;
|
2019-07-18 05:53:45 -07:00
|
|
|
|
|
|
|
|
if (request.enable_internal_solver_output()) {
|
|
|
|
|
LOG(INFO) << "Invalid SCIP status: " << status_or.status();
|
|
|
|
|
}
|
|
|
|
|
MPSolutionResponse response;
|
|
|
|
|
response.set_status(MPSOLVER_NOT_SOLVED);
|
|
|
|
|
response.set_status_str(status_or.status().ToString());
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-18 16:38:25 +02:00
|
|
|
int SCIPInterface::SolutionCount() { return SCIPgetNSols(scip_); }
|
|
|
|
|
|
|
|
|
|
bool SCIPInterface::NextSolution() {
|
|
|
|
|
// Make sure we have successfully solved the problem and not modified it.
|
|
|
|
|
if (!CheckSolutionIsSynchronizedAndExists()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (current_solution_index_ + 1 >= SolutionCount()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
current_solution_index_++;
|
2020-10-28 13:42:36 +01:00
|
|
|
SCIP_SOL** all_solutions = SCIPgetSols(scip_);
|
2020-10-18 16:38:25 +02:00
|
|
|
SetSolution(all_solutions[current_solution_index_]);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 16:59:39 +01:00
|
|
|
int64_t SCIPInterface::iterations() const {
|
2019-01-02 16:35:40 +01:00
|
|
|
// NOTE(user): As of 2018-12 it doesn't run in the stubby server, and is
|
|
|
|
|
// a specialized call, so it's ok to crash if the status is broken.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations;
|
2011-06-22 08:38:43 +00:00
|
|
|
return SCIPgetNLPIterations(scip_);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 16:59:39 +01:00
|
|
|
int64_t SCIPInterface::nodes() const {
|
2019-01-02 16:35:40 +01:00
|
|
|
// NOTE(user): Same story as iterations(): it's OK to crash here.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes;
|
2018-10-31 16:18:18 +01:00
|
|
|
// This is the total number of nodes used in the solve, potentially across
|
|
|
|
|
// multiple branch-and-bound trees. Use limits/totalnodes (rather than
|
|
|
|
|
// limits/nodes) to control this value.
|
|
|
|
|
return SCIPgetNTotalNodes(scip_);
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::SetParameters(const MPSolverParameters& param) {
|
2011-06-22 08:38:43 +00:00
|
|
|
SetCommonParameters(param);
|
|
|
|
|
SetMIPParameters(param);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::SetRelativeMipGap(double value) {
|
2019-01-02 16:35:40 +01:00
|
|
|
// NOTE(user): We don't want to call RETURN_IF_ALREADY_IN_ERROR_STATE here,
|
|
|
|
|
// because even if the solver is in an error state, the user might be setting
|
|
|
|
|
// some parameters and then "restoring" the solver to a non-error state by
|
|
|
|
|
// calling Reset(), which should *not* reset the parameters.
|
|
|
|
|
// So we want the parameter-setting functions to be resistant to being in an
|
|
|
|
|
// error state, essentially. What we do is:
|
|
|
|
|
// - we call the parameter-setting function anyway (I'm assuming that SCIP
|
|
|
|
|
// won't crash even if we're in an error state. I did *not* verify this).
|
|
|
|
|
// - if that call yielded an error *and* we weren't already in an error state,
|
|
|
|
|
// set the state to that error we just got.
|
2019-07-18 05:53:45 -07:00
|
|
|
const auto status =
|
|
|
|
|
SCIP_TO_STATUS(SCIPsetRealParam(scip_, "limits/gap", value));
|
2020-10-22 23:36:58 +02:00
|
|
|
if (status_.ok()) status_ = status;
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
|
2011-09-08 00:34:13 +00:00
|
|
|
void SCIPInterface::SetPrimalTolerance(double value) {
|
2019-01-02 16:35:40 +01:00
|
|
|
// See the NOTE on SetRelativeMipGap().
|
|
|
|
|
const auto status =
|
2019-07-18 05:53:45 -07:00
|
|
|
SCIP_TO_STATUS(SCIPsetRealParam(scip_, "numerics/feastol", value));
|
2020-10-22 23:36:58 +02:00
|
|
|
if (status_.ok()) status_ = status;
|
2011-09-08 00:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::SetDualTolerance(double value) {
|
2019-01-02 16:35:40 +01:00
|
|
|
const auto status =
|
2019-07-18 05:53:45 -07:00
|
|
|
SCIP_TO_STATUS(SCIPsetRealParam(scip_, "numerics/dualfeastol", value));
|
2020-10-22 23:36:58 +02:00
|
|
|
if (status_.ok()) status_ = status;
|
2011-09-08 00:34:13 +00:00
|
|
|
}
|
|
|
|
|
|
2019-11-19 14:37:31 -08:00
|
|
|
void SCIPInterface::SetPresolveMode(int presolve) {
|
2019-01-02 16:35:40 +01:00
|
|
|
// See the NOTE on SetRelativeMipGap().
|
2019-11-19 14:37:31 -08:00
|
|
|
switch (presolve) {
|
2020-10-22 23:36:58 +02:00
|
|
|
case MPSolverParameters::PRESOLVE_OFF: {
|
|
|
|
|
const auto status =
|
|
|
|
|
SCIP_TO_STATUS(SCIPsetIntParam(scip_, "presolving/maxrounds", 0));
|
|
|
|
|
if (status_.ok()) status_ = status;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case MPSolverParameters::PRESOLVE_ON: {
|
|
|
|
|
const auto status =
|
|
|
|
|
SCIP_TO_STATUS(SCIPsetIntParam(scip_, "presolving/maxrounds", -1));
|
|
|
|
|
if (status_.ok()) status_ = status;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, presolve);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-19 14:37:31 -08:00
|
|
|
void SCIPInterface::SetScalingMode(int scaling) {
|
2012-09-11 17:23:15 +00:00
|
|
|
SetUnsupportedIntegerParam(MPSolverParameters::SCALING);
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-22 08:38:43 +00:00
|
|
|
// Only the root LP algorithm is set as setting the node LP to a
|
|
|
|
|
// non-default value rarely is beneficial. The node LP algorithm could
|
|
|
|
|
// be set as well with "lp/resolvealgorithm".
|
2019-11-19 14:37:31 -08:00
|
|
|
void SCIPInterface::SetLpAlgorithm(int lp_algorithm) {
|
2019-01-02 16:35:40 +01:00
|
|
|
// See the NOTE on SetRelativeMipGap().
|
2019-11-19 14:37:31 -08:00
|
|
|
switch (lp_algorithm) {
|
2020-10-22 23:36:58 +02:00
|
|
|
case MPSolverParameters::DUAL: {
|
|
|
|
|
const auto status =
|
|
|
|
|
SCIP_TO_STATUS(SCIPsetCharParam(scip_, "lp/initalgorithm", 'd'));
|
|
|
|
|
if (status_.ok()) status_ = status;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case MPSolverParameters::PRIMAL: {
|
|
|
|
|
const auto status =
|
|
|
|
|
SCIP_TO_STATUS(SCIPsetCharParam(scip_, "lp/initalgorithm", 'p'));
|
|
|
|
|
if (status_.ok()) status_ = status;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case MPSolverParameters::BARRIER: {
|
|
|
|
|
// Barrier with crossover.
|
|
|
|
|
const auto status =
|
|
|
|
|
SCIP_TO_STATUS(SCIPsetCharParam(scip_, "lp/initalgorithm", 'p'));
|
|
|
|
|
if (status_.ok()) status_ = status;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
SetIntegerParamToUnsupportedValue(MPSolverParameters::LP_ALGORITHM,
|
|
|
|
|
lp_algorithm);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2011-06-22 08:38:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-02 16:35:40 +01:00
|
|
|
void SCIPInterface::SetUnsupportedIntegerParam(
|
|
|
|
|
MPSolverParameters::IntegerParam param) {
|
|
|
|
|
MPSolverInterface::SetUnsupportedIntegerParam(param);
|
|
|
|
|
if (status_.ok()) {
|
2020-05-19 17:04:37 +02:00
|
|
|
status_ = absl::InvalidArgumentError(absl::StrFormat(
|
2019-01-02 16:35:40 +01:00
|
|
|
"Tried to set unsupported integer parameter %d", param));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SCIPInterface::SetIntegerParamToUnsupportedValue(
|
|
|
|
|
MPSolverParameters::IntegerParam param, int value) {
|
|
|
|
|
MPSolverInterface::SetIntegerParamToUnsupportedValue(param, value);
|
|
|
|
|
if (status_.ok()) {
|
2020-05-19 17:04:37 +02:00
|
|
|
status_ = absl::InvalidArgumentError(absl::StrFormat(
|
2019-01-02 16:35:40 +01:00
|
|
|
"Tried to set integer parameter %d to unsupported value %d", param,
|
|
|
|
|
value));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-06 18:22:10 +02:00
|
|
|
absl::Status SCIPInterface::SetNumThreads(int num_threads) {
|
2019-01-02 16:35:40 +01:00
|
|
|
if (SetSolverSpecificParametersAsString(
|
|
|
|
|
absl::StrFormat("parallel/maxnthreads = %d\n", num_threads))) {
|
2020-05-06 18:22:10 +02:00
|
|
|
return absl::OkStatus();
|
2019-01-02 16:35:40 +01:00
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
return absl::InternalError(
|
|
|
|
|
"Could not set parallel/maxnthreads, which may "
|
|
|
|
|
"indicate that SCIP API has changed.");
|
2019-01-02 16:35:40 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-24 16:46:19 -07:00
|
|
|
bool SCIPInterface::SetSolverSpecificParametersAsString(
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::string& parameters) {
|
2020-05-06 18:22:10 +02:00
|
|
|
const absl::Status s =
|
2020-10-18 16:38:25 +02:00
|
|
|
LegacyScipSetSolverSpecificParameters(parameters, scip_);
|
2020-03-09 17:52:25 +01:00
|
|
|
if (!s.ok()) {
|
|
|
|
|
LOG(WARNING) << "Failed to set SCIP parameter string: " << parameters
|
|
|
|
|
<< ", error is: " << s;
|
|
|
|
|
}
|
|
|
|
|
return s.ok();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-18 16:38:25 +02:00
|
|
|
class ScipMPCallbackContext : public MPCallbackContext {
|
2020-10-22 23:36:58 +02:00
|
|
|
public:
|
2020-10-28 13:42:36 +01:00
|
|
|
ScipMPCallbackContext(const ScipConstraintHandlerContext* scip_context,
|
2020-10-18 16:38:25 +02:00
|
|
|
bool at_integer_solution)
|
2020-10-22 23:36:58 +02:00
|
|
|
: scip_context_(scip_context),
|
|
|
|
|
at_integer_solution_(at_integer_solution) {}
|
2020-10-18 16:38:25 +02:00
|
|
|
|
|
|
|
|
MPCallbackEvent Event() override {
|
|
|
|
|
if (at_integer_solution_) {
|
|
|
|
|
return MPCallbackEvent::kMipSolution;
|
|
|
|
|
}
|
|
|
|
|
return MPCallbackEvent::kMipNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CanQueryVariableValues() override {
|
|
|
|
|
return !scip_context_->is_pseudo_solution();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
double VariableValue(const MPVariable* variable) override {
|
2020-10-18 16:38:25 +02:00
|
|
|
CHECK(CanQueryVariableValues());
|
|
|
|
|
return scip_context_->VariableValue(variable);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void AddCut(const LinearRange& cutting_plane) override {
|
2020-10-18 16:38:25 +02:00
|
|
|
CallbackRangeConstraint constraint;
|
|
|
|
|
constraint.is_cut = true;
|
|
|
|
|
constraint.range = cutting_plane;
|
|
|
|
|
constraint.local = false;
|
|
|
|
|
constraints_added_.push_back(std::move(constraint));
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void AddLazyConstraint(const LinearRange& lazy_constraint) override {
|
2020-10-18 16:38:25 +02:00
|
|
|
CallbackRangeConstraint constraint;
|
|
|
|
|
constraint.is_cut = false;
|
|
|
|
|
constraint.range = lazy_constraint;
|
|
|
|
|
constraint.local = false;
|
|
|
|
|
constraints_added_.push_back(std::move(constraint));
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
double SuggestSolution(
|
|
|
|
|
const absl::flat_hash_map<const MPVariable*, double>& solution) override {
|
2020-10-18 16:38:25 +02:00
|
|
|
LOG(FATAL) << "SuggestSolution() not currently supported for SCIP.";
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 16:59:39 +01:00
|
|
|
int64_t NumExploredNodes() override {
|
2020-10-22 23:36:58 +02:00
|
|
|
// scip_context_->NumNodesProcessed() returns:
|
2020-10-28 13:42:36 +01:00
|
|
|
// 0 before the root node is solved, e.g. if a heuristic finds a solution.
|
2020-10-22 23:36:58 +02:00
|
|
|
// 1 at the root node
|
|
|
|
|
// > 1 after the root node.
|
|
|
|
|
// The NumExploredNodes spec requires that we return 0 at the root node,
|
2020-10-28 13:42:36 +01:00
|
|
|
// (this is consistent with gurobi). Below is a bandaid to try and make the
|
2020-10-22 23:36:58 +02:00
|
|
|
// behavior consistent, although some information is lost.
|
2021-03-12 16:59:39 +01:00
|
|
|
return std::max(int64_t{0}, scip_context_->NumNodesProcessed() - 1);
|
2020-10-18 16:38:25 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
const std::vector<CallbackRangeConstraint>& constraints_added() {
|
2020-10-18 16:38:25 +02:00
|
|
|
return constraints_added_;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
private:
|
2020-10-28 13:42:36 +01:00
|
|
|
const ScipConstraintHandlerContext* scip_context_;
|
2020-10-18 16:38:25 +02:00
|
|
|
bool at_integer_solution_;
|
|
|
|
|
// second value of pair is true for cuts and false for lazy constraints.
|
|
|
|
|
std::vector<CallbackRangeConstraint> constraints_added_;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ScipConstraintHandlerForMPCallback::ScipConstraintHandlerForMPCallback(
|
2020-10-28 13:42:36 +01:00
|
|
|
MPCallback* mp_callback)
|
2023-09-28 14:27:37 +02:00
|
|
|
: ScipConstraintHandler<EmptyStruct>(ScipConstraintHandlerDescription()),
|
2020-10-18 16:38:25 +02:00
|
|
|
mp_callback_(mp_callback) {}
|
|
|
|
|
|
|
|
|
|
std::vector<CallbackRangeConstraint>
|
|
|
|
|
ScipConstraintHandlerForMPCallback::SeparateFractionalSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
const ScipConstraintHandlerContext& context, const EmptyStruct&) {
|
2020-10-22 23:36:58 +02:00
|
|
|
return SeparateSolution(context, /*at_integer_solution=*/false);
|
2020-10-18 16:38:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<CallbackRangeConstraint>
|
|
|
|
|
ScipConstraintHandlerForMPCallback::SeparateIntegerSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
const ScipConstraintHandlerContext& context, const EmptyStruct&) {
|
2020-10-22 23:36:58 +02:00
|
|
|
return SeparateSolution(context, /*at_integer_solution=*/true);
|
2020-10-18 16:38:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<CallbackRangeConstraint>
|
|
|
|
|
ScipConstraintHandlerForMPCallback::SeparateSolution(
|
2020-10-28 13:42:36 +01:00
|
|
|
const ScipConstraintHandlerContext& context,
|
2020-10-18 16:38:25 +02:00
|
|
|
const bool at_integer_solution) {
|
|
|
|
|
ScipMPCallbackContext mp_context(&context, at_integer_solution);
|
|
|
|
|
mp_callback_->RunCallback(&mp_context);
|
|
|
|
|
return mp_context.constraints_added();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
void SCIPInterface::SetCallback(MPCallback* mp_callback) {
|
2020-10-18 16:38:25 +02:00
|
|
|
if (callback_ != nullptr) {
|
|
|
|
|
callback_reset_ = true;
|
|
|
|
|
}
|
|
|
|
|
callback_ = mp_callback;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-28 13:42:36 +01:00
|
|
|
MPSolverInterface* BuildSCIPInterface(MPSolver* const solver) {
|
2013-01-10 17:00:09 +00:00
|
|
|
return new SCIPInterface(solver);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
} // namespace operations_research
|
|
|
|
|
#endif // #if defined(USE_SCIP)
|
2011-06-22 08:38:43 +00:00
|
|
|
|
2019-07-18 05:53:45 -07:00
|
|
|
#undef RETURN_AND_STORE_IF_SCIP_ERROR
|
2019-01-02 16:35:40 +01:00
|
|
|
#undef RETURN_IF_ALREADY_IN_ERROR_STATE
|
|
|
|
|
#undef RETURN_ABNORMAL_IF_BAD_STATUS
|
|
|
|
|
#undef RETURN_ABNORMAL_IF_SCIP_ERROR
|