math_opt: backport from main
This commit is contained in:
@@ -111,6 +111,7 @@ proto_library(
|
||||
"//ortools/math_opt/solvers:highs_proto",
|
||||
"//ortools/math_opt/solvers:osqp_proto",
|
||||
"//ortools/math_opt/solvers/gscip:gscip_proto",
|
||||
"//ortools/math_opt/solvers:xpress_proto",
|
||||
"//ortools/pdlp:solvers_proto",
|
||||
"//ortools/sat:sat_parameters_proto",
|
||||
"@protobuf//:duration_proto",
|
||||
|
||||
@@ -481,6 +481,8 @@ class Model {
|
||||
// The `weights` are an implementation detail in the solver used to order the
|
||||
// `expressions`; see the Gurobi documentation for more detail:
|
||||
// https://www.gurobi.com/documentation/9.5/refman/constraints.html#subsubsection:SOSConstraints
|
||||
// For Xpress see
|
||||
// https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/XPRSaddsets.html
|
||||
//
|
||||
// These `weights` must either be empty or the same length as `expressions`.
|
||||
// If it is empty, default weights of 1, 2, ... will be used.
|
||||
@@ -540,6 +542,8 @@ class Model {
|
||||
// The `weights` are an implementation detail in the solver used to order the
|
||||
// `expressions`; see the Gurobi documentation for more detail:
|
||||
// https://www.gurobi.com/documentation/9.5/refman/constraints.html#subsubsection:SOSConstraints
|
||||
// For Xpress see
|
||||
// https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/XPRSaddsets.html
|
||||
//
|
||||
// These `weights` must either be empty or the same length as `expressions`.
|
||||
// If it is empty, default weights of 1, 2, ... will be used.
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "ortools/math_opt/solvers/glpk.pb.h"
|
||||
#include "ortools/math_opt/solvers/gurobi.pb.h"
|
||||
#include "ortools/math_opt/solvers/highs.pb.h"
|
||||
#include "ortools/math_opt/solvers/xpress.pb.h"
|
||||
#include "ortools/port/proto_utils.h"
|
||||
#include "ortools/util/status_macros.h"
|
||||
|
||||
@@ -213,6 +214,25 @@ GlpkParameters GlpkParameters::FromProto(const GlpkParametersProto& proto) {
|
||||
return result;
|
||||
}
|
||||
|
||||
XpressParametersProto XpressParameters::Proto() const {
|
||||
XpressParametersProto result;
|
||||
for (const auto& [key, val] : param_values) {
|
||||
XpressParametersProto::Parameter& p = *result.add_parameters();
|
||||
p.set_name(key);
|
||||
p.set_value(val);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
XpressParameters XpressParameters::FromProto(
|
||||
const XpressParametersProto& proto) {
|
||||
XpressParameters result;
|
||||
for (const XpressParametersProto::Parameter& p : proto.parameters()) {
|
||||
result.param_values[p.name()] = p.value();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
SolveParametersProto SolveParameters::Proto() const {
|
||||
SolveParametersProto result;
|
||||
result.set_enable_output(enable_output);
|
||||
@@ -265,6 +285,7 @@ SolveParametersProto SolveParameters::Proto() const {
|
||||
*result.mutable_pdlp() = pdlp;
|
||||
*result.mutable_glpk() = glpk.Proto();
|
||||
*result.mutable_highs() = highs;
|
||||
*result.mutable_xpress() = xpress.Proto();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -324,6 +345,7 @@ absl::StatusOr<SolveParameters> SolveParameters::FromProto(
|
||||
result.pdlp = proto.pdlp();
|
||||
result.glpk = GlpkParameters::FromProto(proto.glpk());
|
||||
result.highs = proto.highs();
|
||||
result.xpress = XpressParameters::FromProto(proto.xpress());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,44 @@ struct GlpkParameters {
|
||||
static GlpkParameters FromProto(const GlpkParametersProto& proto);
|
||||
};
|
||||
|
||||
// Xpress specific parameters for solving. See
|
||||
// https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/chapter7.html
|
||||
// for a list of possible parameters (called "controls" in Xpress).
|
||||
// In addition to all Xpress controls, the following special parameters are
|
||||
// also supported:
|
||||
// "EXPORT_MODEL"(string) If present then the low level Xpress model
|
||||
// (the XPRSprob instance) is written to that file
|
||||
// right before XPRSoptimize() is called. This can
|
||||
// be useful for debugging.
|
||||
// "FORCE_POSTSOLVE"(int) If set to a non-zero value then the low-level code
|
||||
// will call XPRSpostsolve() right after calling
|
||||
// XPRSoptimize(). If not set or set to zero then
|
||||
// calling XPRSpostsolve() is delayed to the latest
|
||||
// possible point in time to enable incremental
|
||||
// solves.
|
||||
// "STOP_AFTER_LP"(int) If set to a non-zero value then the solve will be
|
||||
// stopped right after solving the root relaxation.
|
||||
// This is the same as passing the ' l' (ell) flag
|
||||
// to XPRSoptimize() and stops the process earlier
|
||||
// than a limit like MAXNODE=0.
|
||||
//
|
||||
// Example use:
|
||||
// XpressParameters xpress;
|
||||
// xpress.param_values["BarIterLimit"] = "10";
|
||||
//
|
||||
// Parameters are applied in the following order:
|
||||
// * Any parameters derived from ortools parameters (like LP algorithm).
|
||||
// * param_values in iteration order (insertion order).
|
||||
struct XpressParameters {
|
||||
// Parameter name-value pairs to set in insertion order.
|
||||
gtl::linked_hash_map<std::string, std::string> param_values;
|
||||
|
||||
XpressParametersProto Proto() const;
|
||||
static XpressParameters FromProto(const XpressParametersProto& proto);
|
||||
|
||||
bool empty() const { return param_values.empty(); }
|
||||
};
|
||||
|
||||
// Parameters to control a single solve.
|
||||
//
|
||||
// Contains both parameters common to all solvers, e.g. time_limit, and
|
||||
@@ -330,7 +368,8 @@ struct SolveParameters {
|
||||
// Solvers will typically not return more solutions than the solution limit,
|
||||
// but this is not enforced by MathOpt, see also b/214041169.
|
||||
//
|
||||
// Currently supported for Gurobi and SCIP, and for CP-SAT only with value 1.
|
||||
// Currently supported for Gurobi, Xpress and SCIP, and for CP-SAT only with
|
||||
// value 1.
|
||||
std::optional<int32_t> solution_limit;
|
||||
|
||||
// If unset, use the solver default. If set, it must be >= 1.
|
||||
@@ -347,6 +386,7 @@ struct SolveParameters {
|
||||
// - Gurobi: [0:GRB_MAXINT] (which as of Gurobi 9.0 is 2x10^9).
|
||||
// - GSCIP: [0:2147483647] (which is MAX_INT or kint32max or 2^31-1).
|
||||
// - GLOP: [0:2147483647] (same as above)
|
||||
// - Xpress: Any 32bit signed integer is allowed
|
||||
// In all cases, the solver will receive a value equal to:
|
||||
// MAX(0, MIN(MAX_VALID_VALUE_FOR_SOLVER, random_seed)).
|
||||
std::optional<int32_t> random_seed;
|
||||
@@ -425,6 +465,7 @@ struct SolveParameters {
|
||||
|
||||
GlpkParameters glpk;
|
||||
HighsOptionsProto highs;
|
||||
XpressParameters xpress;
|
||||
|
||||
SolveParametersProto Proto() const;
|
||||
static absl::StatusOr<SolveParameters> FromProto(
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "absl/status/statusor.h"
|
||||
#include "ortools/math_opt/parameters.pb.h"
|
||||
#include "ortools/math_opt/solvers/gurobi.pb.h"
|
||||
#include "ortools/math_opt/solvers/xpress.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
@@ -60,12 +61,34 @@ StreamableGurobiInitArguments StreamableGurobiInitArguments::FromProto(
|
||||
return args;
|
||||
}
|
||||
|
||||
XpressInitializerProto StreamableXpressInitArguments::Proto() const {
|
||||
XpressInitializerProto params_proto;
|
||||
|
||||
if (extract_names.has_value()) {
|
||||
params_proto.set_extract_names(extract_names.value());
|
||||
}
|
||||
|
||||
return params_proto;
|
||||
}
|
||||
|
||||
StreamableXpressInitArguments StreamableXpressInitArguments::FromProto(
|
||||
const XpressInitializerProto& args_proto) {
|
||||
StreamableXpressInitArguments args;
|
||||
if (args_proto.has_extract_names()) {
|
||||
args.extract_names = args_proto.extract_names();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
SolverInitializerProto StreamableSolverInitArguments::Proto() const {
|
||||
SolverInitializerProto params_proto;
|
||||
|
||||
if (gurobi) {
|
||||
*params_proto.mutable_gurobi() = gurobi->Proto();
|
||||
}
|
||||
if (xpress) {
|
||||
*params_proto.mutable_xpress() = xpress->Proto();
|
||||
}
|
||||
|
||||
return params_proto;
|
||||
}
|
||||
@@ -77,6 +100,9 @@ StreamableSolverInitArguments::FromProto(
|
||||
if (args_proto.has_gurobi()) {
|
||||
args.gurobi = StreamableGurobiInitArguments::FromProto(args_proto.gurobi());
|
||||
}
|
||||
if (args_proto.has_xpress()) {
|
||||
args.xpress = StreamableXpressInitArguments::FromProto(args_proto.xpress());
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,22 @@ struct StreamableGurobiInitArguments {
|
||||
const GurobiInitializerProto& args_proto);
|
||||
};
|
||||
|
||||
// Streamable Xpress specific parameters for solver instantiation.
|
||||
struct StreamableXpressInitArguments {
|
||||
// If present and set to true then variable and constraint names are
|
||||
// extracted into the underlying Xpress instance. This can help debugging
|
||||
// (especially if models are exported to disk) but also incurs runtime and
|
||||
// memory overhead.
|
||||
std::optional<bool> extract_names;
|
||||
|
||||
// Returns the proto corresponding to these parameters.
|
||||
XpressInitializerProto Proto() const;
|
||||
|
||||
// Parses the proto corresponding to these parameters.
|
||||
static StreamableXpressInitArguments FromProto(
|
||||
const XpressInitializerProto& args_proto);
|
||||
};
|
||||
|
||||
// Solver initialization parameters that can be streamed to be exchanged with
|
||||
// another process.
|
||||
//
|
||||
@@ -89,6 +105,7 @@ struct StreamableSolverInitArguments {
|
||||
std::optional<StreamableGlopInitArguments> glop;
|
||||
std::optional<StreamableGlpkInitArguments> glpk;
|
||||
std::optional<StreamableGurobiInitArguments> gurobi;
|
||||
std::optional<StreamableXpressInitArguments> xpress;
|
||||
|
||||
// Returns the proto corresponding to these parameters.
|
||||
SolverInitializerProto Proto() const;
|
||||
|
||||
@@ -24,6 +24,7 @@ import "ortools/math_opt/solvers/gscip/gscip.proto";
|
||||
import "ortools/math_opt/solvers/gurobi.proto";
|
||||
import "ortools/math_opt/solvers/highs.proto";
|
||||
import "ortools/math_opt/solvers/osqp.proto";
|
||||
import "ortools/math_opt/solvers/xpress.proto";
|
||||
import "ortools/sat/sat_parameters.proto";
|
||||
|
||||
option java_package = "com.google.ortools.mathopt";
|
||||
@@ -176,6 +177,7 @@ message StrictnessProto {
|
||||
message SolverInitializerProto {
|
||||
GurobiInitializerProto gurobi = 1;
|
||||
reserved 2;
|
||||
XpressInitializerProto xpress = 3;
|
||||
}
|
||||
|
||||
// Parameters to control a single solve.
|
||||
@@ -376,5 +378,7 @@ message SolveParametersProto {
|
||||
|
||||
HighsOptionsProto highs = 27;
|
||||
|
||||
XpressParametersProto xpress = 28;
|
||||
|
||||
reserved 11; // Deleted
|
||||
}
|
||||
|
||||
@@ -151,6 +151,17 @@ TEST_P(MipSolutionHintTest, TwoHintTest) {
|
||||
if (!TwoHintParams().has_value()) {
|
||||
GTEST_SKIP() << "Multiple hints not supported. Ignoring this test.";
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// Xpress has no configuration options to "just complete" a partial
|
||||
// solution hint. For an incomplete solution it will always run simple
|
||||
// heuristics to find a solution. The effort of this heuristic can be
|
||||
// controlled via the USERSOLHEURISTIC control, but both values
|
||||
// 0 (off) and 1 (light) make the test fail: with off no heuristic
|
||||
// is applied on the provided solution and hence the expected solutions
|
||||
// are not found. With light the heuristic finds the optimal solution
|
||||
// from the solution hint.
|
||||
GTEST_SKIP() << "Xpress cannot be forced to only complete a solution.";
|
||||
}
|
||||
|
||||
ModelSolveParameters model_parameters;
|
||||
|
||||
@@ -281,6 +292,12 @@ TEST_P(BranchPrioritiesTest, PrioritiesAreSetProperly) {
|
||||
|
||||
// See PrioritiesAreSetProperly for details on the model and solve parameters.
|
||||
TEST_P(BranchPrioritiesTest, PrioritiesClearedAfterIncrementalSolve) {
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// This test does not work with Xpress since Xpress does not clear/reset
|
||||
// model parameters after a solve. See the comment in XpressSolver::Solve
|
||||
// in xpress_solver.cc.
|
||||
GTEST_SKIP() << "Xpress does not clear model parameters in Solve().";
|
||||
}
|
||||
Model model;
|
||||
Variable x = model.AddContinuousVariable(-3.0, 1.0, "x");
|
||||
Variable y = model.AddContinuousVariable(0.0, 3.0, "y");
|
||||
@@ -400,6 +417,12 @@ TEST_P(LazyConstraintsTest, AnnotationsAreSetProperly) {
|
||||
// without. If the annotations are cleared after the first, then we expect the
|
||||
// second to solve the entire LP (including c and d), giving a dual bound of 0.
|
||||
TEST_P(LazyConstraintsTest, AnnotationsAreClearedAfterSolve) {
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// For the AnnotationsAreSetProperly we set STOP_AFTER_LP=1 which stops
|
||||
// Xpress right after the relaxation. Since the same parameters are
|
||||
// also used for the test here, this settings kills the test.
|
||||
GTEST_SKIP() << "Xpress stops too early with shared parameter settings.";
|
||||
}
|
||||
Model model;
|
||||
Variable x = model.AddIntegerVariable(-1, 1, "x");
|
||||
Variable y = model.AddIntegerVariable(-1, 1, "y");
|
||||
|
||||
@@ -36,7 +36,8 @@ LogicalConstraintTestParameters::LogicalConstraintTestParameters(
|
||||
const bool supports_incremental_add_and_deletes,
|
||||
const bool supports_incremental_variable_deletions,
|
||||
const bool supports_deleting_indicator_variables,
|
||||
const bool supports_updating_binary_variables)
|
||||
const bool supports_updating_binary_variables,
|
||||
const bool supports_sos_on_expressions)
|
||||
: solver_type(solver_type),
|
||||
parameters(std::move(parameters)),
|
||||
supports_integer_variables(supports_integer_variables),
|
||||
@@ -49,7 +50,8 @@ LogicalConstraintTestParameters::LogicalConstraintTestParameters(
|
||||
supports_incremental_variable_deletions),
|
||||
supports_deleting_indicator_variables(
|
||||
supports_deleting_indicator_variables),
|
||||
supports_updating_binary_variables(supports_updating_binary_variables) {}
|
||||
supports_updating_binary_variables(supports_updating_binary_variables),
|
||||
supports_sos_on_expressions(supports_sos_on_expressions) {}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out,
|
||||
const LogicalConstraintTestParameters& params) {
|
||||
@@ -68,7 +70,9 @@ std::ostream& operator<<(std::ostream& out,
|
||||
<< ", supports_deleting_indicator_variables: "
|
||||
<< (params.supports_deleting_indicator_variables ? "true" : "false")
|
||||
<< ", supports_updating_binary_variables: "
|
||||
<< (params.supports_updating_binary_variables ? "true" : "false") << " }";
|
||||
<< (params.supports_updating_binary_variables ? "true" : "false")
|
||||
<< ", supports_sos_on_expressions: "
|
||||
<< (params.supports_sos_on_expressions ? "true" : "false") << " }";
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -86,9 +90,21 @@ constexpr absl::string_view no_sos2_support_message =
|
||||
constexpr absl::string_view no_indicator_support_message =
|
||||
"This test is disabled as the solver does not support indicator "
|
||||
"constraints";
|
||||
constexpr absl::string_view no_updating_binary_variables_message =
|
||||
"This test is disabled as the solver does not support updating "
|
||||
"binary variables";
|
||||
constexpr absl::string_view no_deleting_indicator_variables_message =
|
||||
"This test is disabled as the solver does not support deleting "
|
||||
"indicator variables";
|
||||
constexpr absl::string_view no_incremental_add_and_deletes_message =
|
||||
"This test is disabled as the solver does not support incremental "
|
||||
"add/delete";
|
||||
|
||||
// We test SOS1 constraints with both explicit weights and default weights.
|
||||
TEST_P(SimpleLogicalConstraintTest, CanBuildSos1Model) {
|
||||
if (!GetParam().supports_sos_on_expressions) {
|
||||
GTEST_SKIP() << "skipped since SOS on expressions are not supported";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
model.AddSos1Constraint({3.0 * x + 2.0}, {3.0});
|
||||
@@ -105,6 +121,9 @@ TEST_P(SimpleLogicalConstraintTest, CanBuildSos1Model) {
|
||||
|
||||
// We test SOS2 constraints with both explicit weights and default weights.
|
||||
TEST_P(SimpleLogicalConstraintTest, CanBuildSos2Model) {
|
||||
if (!GetParam().supports_sos_on_expressions) {
|
||||
GTEST_SKIP() << "skipped since SOS on expressions are not supported";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
model.AddSos2Constraint({3.0 * x + 2.0}, {3.0});
|
||||
@@ -284,6 +303,9 @@ TEST_P(SimpleLogicalConstraintTest, Sos2VariableInMultipleTerms) {
|
||||
// The optimal solution for the modified problem is (x*, y*) = (0, 1) with
|
||||
// objective value 2.
|
||||
TEST_P(IncrementalLogicalConstraintTest, LinearToSos1Update) {
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << no_incremental_add_and_deletes_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -335,6 +357,9 @@ TEST_P(IncrementalLogicalConstraintTest, LinearToSos1Update) {
|
||||
// The optimal solution for the modified problem is (x*, y*, z*) = (0, 1, 1)
|
||||
// with objective value 4.
|
||||
TEST_P(IncrementalLogicalConstraintTest, LinearToSos2Update) {
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << no_incremental_add_and_deletes_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -760,6 +785,13 @@ TEST_P(SimpleLogicalConstraintTest, IndicatorsWithOddButValidBounds) {
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// This test does not work with Xpress because the Xpress library will
|
||||
// refuse to create an indicator constraint if the indicator variable is
|
||||
// not a binary variable (it will raise error 1029);
|
||||
GTEST_SKIP() << "This test is disabled as Xpress does not support "
|
||||
"indicator constraints on non-binary variables";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddIntegerVariable(0.0, 0.0, "x");
|
||||
const Variable y = model.AddIntegerVariable(1.0, 1.0, "y");
|
||||
@@ -855,6 +887,9 @@ TEST_P(IncrementalLogicalConstraintTest, UpdateDeletesIndicatorConstraint) {
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << no_incremental_add_and_deletes_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddBinaryVariable("x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -898,6 +933,9 @@ TEST_P(IncrementalLogicalConstraintTest,
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << no_incremental_add_and_deletes_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable indicator = model.AddBinaryVariable("indicator");
|
||||
@@ -942,6 +980,9 @@ TEST_P(IncrementalLogicalConstraintTest, UpdateDeletesIndicatorVariable) {
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (!GetParam().supports_deleting_indicator_variables) {
|
||||
GTEST_SKIP() << no_deleting_indicator_variables_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddBinaryVariable("x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -1022,6 +1063,9 @@ TEST_P(IncrementalLogicalConstraintTest,
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (!GetParam().supports_updating_binary_variables) {
|
||||
GTEST_SKIP() << no_updating_binary_variables_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddBinaryVariable("x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -1069,6 +1113,9 @@ TEST_P(IncrementalLogicalConstraintTest, UpdateChangesIndicatorVariableBound) {
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (!GetParam().supports_updating_binary_variables) {
|
||||
GTEST_SKIP() << no_updating_binary_variables_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddIntegerVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddIntegerVariable(0.0, 1.0, "y");
|
||||
@@ -1133,6 +1180,9 @@ TEST_P(IncrementalLogicalConstraintTest,
|
||||
if (!GetParam().supports_indicator_constraints) {
|
||||
GTEST_SKIP() << no_indicator_support_message;
|
||||
}
|
||||
if (!GetParam().supports_updating_binary_variables) {
|
||||
GTEST_SKIP() << no_updating_binary_variables_message;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddIntegerVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
|
||||
@@ -30,7 +30,8 @@ struct LogicalConstraintTestParameters {
|
||||
bool supports_incremental_add_and_deletes,
|
||||
bool supports_incremental_variable_deletions,
|
||||
bool supports_deleting_indicator_variables,
|
||||
bool supports_updating_binary_variables);
|
||||
bool supports_updating_binary_variables,
|
||||
bool supports_sos_on_expressions = true);
|
||||
|
||||
// The tested solver.
|
||||
SolverType solver_type;
|
||||
@@ -62,6 +63,10 @@ struct LogicalConstraintTestParameters {
|
||||
// True if the solver supports updates (changing bounds or vartype) to binary
|
||||
// variables.
|
||||
bool supports_updating_binary_variables;
|
||||
|
||||
// True if the solver supports SOS constraints on expressions. False if
|
||||
// SOS constraints are only supported on singleton variables.
|
||||
bool supports_sos_on_expressions;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out,
|
||||
|
||||
@@ -360,6 +360,11 @@ TEST_P(LpParameterTest, IterationLimitFirstOrder) {
|
||||
if (!SupportsFirstOrder()) {
|
||||
GTEST_SKIP() << "First order methods not supported. Ignoring this test.";
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// Xpress is too smart for the model just here. EVen with n=300 it solves
|
||||
// the problem to optimality (within tolerances) in the first iteration.
|
||||
GTEST_SKIP() << "Test skipped for Xpress since model solves too easily.";
|
||||
}
|
||||
ASSERT_OK_AND_ASSIGN(
|
||||
const SolveResult result,
|
||||
LPForIterationLimit(TestedSolver(), LPAlgorithm::kFirstOrder, 3,
|
||||
|
||||
@@ -143,6 +143,12 @@ TEST_P(SimpleMipTest, FractionalBoundsContainNoInteger) {
|
||||
// TODO(b/272298816): Gurobi bindings are broken here.
|
||||
GTEST_SKIP() << "TODO(b/272298816): Gurobi bindings are broken here.";
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// Xpress rounds bounds of integer variables on input, so the bounds
|
||||
// specified here result in [1,0]. Xpress also checks that bounds are
|
||||
// not contradicting, so it rejects creation of such a variable.
|
||||
GTEST_SKIP() << "Xpress does not support contradictory bounds.";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddIntegerVariable(0.5, 0.6, "x");
|
||||
model.Maximize(x);
|
||||
|
||||
@@ -371,6 +371,10 @@ TEST_P(SimpleMultiObjectiveTest,
|
||||
if (!GetParam().supports_integer_variables) {
|
||||
GTEST_SKIP() << kNoIntegerVariableSupportMessage;
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
GTEST_SKIP() << "Ignoring this test because Xpress does not support per "
|
||||
"objective time limits at the moment";
|
||||
}
|
||||
ASSERT_OK_AND_ASSIGN(const std::unique_ptr<Model> model,
|
||||
Load23588MiplibInstance());
|
||||
const Objective aux_obj = model->AddMaximizationObjective(
|
||||
@@ -402,6 +406,10 @@ TEST_P(SimpleMultiObjectiveTest,
|
||||
if (!GetParam().supports_integer_variables) {
|
||||
GTEST_SKIP() << kNoIntegerVariableSupportMessage;
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
GTEST_SKIP() << "Ignoring this test because Xpress does not support per "
|
||||
"objective time limits at the moment";
|
||||
}
|
||||
ASSERT_OK_AND_ASSIGN(const std::unique_ptr<Model> model,
|
||||
Load23588MiplibInstance());
|
||||
model->AddMaximizationObjective(0, /*priority=*/1);
|
||||
@@ -463,6 +471,10 @@ TEST_P(SimpleMultiObjectiveTest,
|
||||
if (!GetParam().supports_integer_variables) {
|
||||
GTEST_SKIP() << kNoIntegerVariableSupportMessage;
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
GTEST_SKIP() << "Ignoring this test because Xpress does not support per "
|
||||
"objective time limits at the moment";
|
||||
}
|
||||
ASSERT_OK_AND_ASSIGN(const std::unique_ptr<Model> model,
|
||||
Load23588MiplibInstance());
|
||||
SolveArguments args = {
|
||||
@@ -583,6 +595,10 @@ TEST_P(IncrementalMultiObjectiveTest, AddObjectiveToMultiObjectiveModel) {
|
||||
if (!GetParam().supports_auxiliary_objectives) {
|
||||
GTEST_SKIP() << kNoMultiObjectiveSupportMessage;
|
||||
}
|
||||
if (!GetParam().supports_incremental_objective_add_and_delete) {
|
||||
GTEST_SKIP()
|
||||
<< "Ignoring this test as it requires support for incremental solve";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -636,6 +652,10 @@ TEST_P(IncrementalMultiObjectiveTest, DeleteObjectiveFromMultiObjectiveModel) {
|
||||
if (!GetParam().supports_auxiliary_objectives) {
|
||||
GTEST_SKIP() << kNoMultiObjectiveSupportMessage;
|
||||
}
|
||||
if (!GetParam().supports_incremental_objective_add_and_delete) {
|
||||
GTEST_SKIP()
|
||||
<< "Ignoring this test as it requires support for incremental solve";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
|
||||
@@ -171,6 +171,13 @@ TEST_P(SimpleQcTest, SolveHalfEllipseQc) {
|
||||
// Additionally, if we impose integrality on x, then the optimal solution is
|
||||
// x = 0 with objective value 1.
|
||||
TEST_P(IncrementalQcTest, LinearToQuadraticUpdate) {
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// This test suffers from a bug in Xpress 9.6.0 (bug introduced in 9.6.0,
|
||||
// fixed in 9.6.1).
|
||||
// We have no easy way to check the Xpress version here, so we just skip
|
||||
// the test.
|
||||
GTEST_SKIP() << "Skip test due to bug in Xpress 9.6.0.";
|
||||
}
|
||||
Model model;
|
||||
const Variable x =
|
||||
model.AddVariable(0.0, 1.0, GetParam().use_integer_variables, "x");
|
||||
|
||||
@@ -62,6 +62,9 @@ constexpr double kTolerance = 1.0e-3;
|
||||
constexpr absl::string_view kNoSocSupportMessage =
|
||||
"This test is disabled as the solver does not support second-order cone "
|
||||
"constraints";
|
||||
constexpr absl::string_view kNoIncrementalAddAndDeletes =
|
||||
"This test is disabled as the solver does not support incremental add and "
|
||||
"deletes";
|
||||
|
||||
// Builds the simple (and uninteresting) SOC model:
|
||||
//
|
||||
@@ -69,6 +72,14 @@ constexpr absl::string_view kNoSocSupportMessage =
|
||||
// s.t. ||x||_2 <= 2x
|
||||
// 0 <= x <= 1.
|
||||
TEST_P(SimpleSecondOrderConeTest, CanBuildSecondOrderConeModel) {
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
// For Xpress the second order cone constraint results in
|
||||
// x^2 - 4x^2 <= 0
|
||||
// This has two entries for x and will thus be rejected by the library.
|
||||
// Hence we have to skip the test.
|
||||
GTEST_SKIP()
|
||||
<< "This test is disabled as Xpress rejects duplicate Q entries";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
model.AddSecondOrderConeConstraint({x}, 2 * x);
|
||||
@@ -94,6 +105,10 @@ TEST_P(SimpleSecondOrderConeTest, SolveSimpleSocModel) {
|
||||
if (!GetParam().supports_soc_constraints) {
|
||||
GTEST_SKIP() << kNoSocSupportMessage;
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
GTEST_SKIP() << "This test is disabled as Xpress only supports second "
|
||||
"order cone constraints on singletons";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -117,6 +132,10 @@ TEST_P(SimpleSecondOrderConeTest, SolveMultipleSocConstraintModel) {
|
||||
if (!GetParam().supports_soc_constraints) {
|
||||
GTEST_SKIP() << kNoSocSupportMessage;
|
||||
}
|
||||
if (GetParam().solver_type == SolverType::kXpress) {
|
||||
GTEST_SKIP() << "This test is disabled as Xpress only supports second "
|
||||
"order cone constraints on singletons";
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -169,6 +188,9 @@ TEST_P(SimpleSecondOrderConeTest, SolveModelWithSocAndLinearConstraints) {
|
||||
// The unique optimal solution is then (x*, y*) = (0.5, 0.5) with objective
|
||||
// value 1.
|
||||
TEST_P(IncrementalSecondOrderConeTest, LinearToSecondOrderConeUpdate) {
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << kNoIncrementalAddAndDeletes;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -232,6 +254,9 @@ TEST_P(IncrementalSecondOrderConeTest, UpdateDeletesSecondOrderConeConstraint) {
|
||||
if (!GetParam().supports_soc_constraints) {
|
||||
GTEST_SKIP() << kNoSocSupportMessage;
|
||||
}
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << kNoIncrementalAddAndDeletes;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -319,6 +344,9 @@ TEST_P(IncrementalSecondOrderConeTest,
|
||||
if (!GetParam().supports_soc_constraints) {
|
||||
GTEST_SKIP() << kNoSocSupportMessage;
|
||||
}
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << kNoIncrementalAddAndDeletes;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(0.0, 2.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -361,6 +389,9 @@ TEST_P(IncrementalSecondOrderConeTest, UpdateDeletesVariableThatIsAnArgument) {
|
||||
if (!GetParam().supports_soc_constraints) {
|
||||
GTEST_SKIP() << kNoSocSupportMessage;
|
||||
}
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << kNoIncrementalAddAndDeletes;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(1.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 1.0, "y");
|
||||
@@ -403,6 +434,9 @@ TEST_P(IncrementalSecondOrderConeTest, UpdateDeletesVariableInAnArgument) {
|
||||
if (!GetParam().supports_soc_constraints) {
|
||||
GTEST_SKIP() << kNoSocSupportMessage;
|
||||
}
|
||||
if (!GetParam().supports_incremental_add_and_deletes) {
|
||||
GTEST_SKIP() << kNoIncrementalAddAndDeletes;
|
||||
}
|
||||
Model model;
|
||||
const Variable x = model.AddContinuousVariable(1.0, 1.0, "x");
|
||||
const Variable y = model.AddContinuousVariable(0.0, 2.0, "y");
|
||||
|
||||
@@ -658,6 +658,23 @@ py_proto_library(
|
||||
deps = [":highs_proto"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "xpress_proto",
|
||||
srcs = ["xpress.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "xpress_cc_proto",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [":xpress_proto"],
|
||||
)
|
||||
|
||||
py_proto_library(
|
||||
name = "xpress_py_pb2",
|
||||
deps = [":xpress_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "xpress_solver",
|
||||
srcs = [
|
||||
@@ -683,6 +700,7 @@ cc_library(
|
||||
"//ortools/math_opt/core:math_opt_proto_utils",
|
||||
"//ortools/math_opt/core:solver_interface",
|
||||
"//ortools/math_opt/core:sparse_vector_view",
|
||||
"//ortools/math_opt/cpp:math_opt",
|
||||
"//ortools/math_opt/solvers/xpress:g_xpress",
|
||||
"//ortools/math_opt/validators:callback_validator",
|
||||
"//ortools/port:proto_utils",
|
||||
@@ -710,11 +728,13 @@ cc_test(
|
||||
"//ortools/math_opt/solver_tests:generic_tests",
|
||||
"//ortools/math_opt/solver_tests:infeasible_subsystem_tests",
|
||||
"//ortools/math_opt/solver_tests:invalid_input_tests",
|
||||
"//ortools/math_opt/solver_tests:ip_model_solve_parameters_tests",
|
||||
"//ortools/math_opt/solver_tests:logical_constraint_tests",
|
||||
"//ortools/math_opt/solver_tests:lp_incomplete_solve_tests",
|
||||
"//ortools/math_opt/solver_tests:lp_model_solve_parameters_tests",
|
||||
"//ortools/math_opt/solver_tests:lp_parameter_tests",
|
||||
"//ortools/math_opt/solver_tests:lp_tests",
|
||||
"//ortools/math_opt/solver_tests:mip_tests",
|
||||
"//ortools/math_opt/solver_tests:multi_objective_tests",
|
||||
"//ortools/math_opt/solver_tests:qc_tests",
|
||||
"//ortools/math_opt/solver_tests:qp_tests",
|
||||
|
||||
59
ortools/math_opt/solvers/xpress.proto
Normal file
59
ortools/math_opt/solvers/xpress.proto
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2010-2025 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.
|
||||
|
||||
// Proto messages specific to Xpress.
|
||||
syntax = "proto3";
|
||||
|
||||
package operations_research.math_opt;
|
||||
|
||||
// Parameters used to initialize the Xpress solver.
|
||||
message XpressInitializerProto {
|
||||
optional bool extract_names = 1;
|
||||
}
|
||||
|
||||
|
||||
// Xpress specific parameters for solving. See
|
||||
// https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/HTML/chapter7.html
|
||||
// for a list of possible parameters (called "controls" in Xpress).
|
||||
// In addition to all Xpress controls, the following special parameters are
|
||||
// also supported:
|
||||
// "EXPORT_MODEL"(string) If present then the low level Xpress model
|
||||
// (the XPRSprob instance) is written to that file
|
||||
// right before XPRSoptimize() is called. This can
|
||||
// be useful for debugging.
|
||||
// "FORCE_POSTSOLVE"(int) If set to a non-zero value then the low-level code
|
||||
// will call XPRSpostsolve() right after calling
|
||||
// XPRSoptimize(). If not set or set to zero then
|
||||
// calling XPRSpostsolve() is delayed to the latest
|
||||
// possible point in time to enable incremental
|
||||
// solves.
|
||||
// "STOP_AFTER_LP"(int) If set to a non-zero value then the solve will be
|
||||
// stopped right after solving the root relaxation.
|
||||
// This is the same as passing the ' l' (ell) flag
|
||||
// to XPRSoptimize() and stops the process earlier
|
||||
// than a limit like MAXNODE=0.
|
||||
//
|
||||
// Example use:
|
||||
// XpressParameters xpress;
|
||||
// xpress.param_values["BarIterLimit"] = "10";
|
||||
//
|
||||
// Parameters are applied in the following order:
|
||||
// * Any parameters derived from ortools parameters (like LP algorithm).
|
||||
// * param_values in iteration order (insertion order).
|
||||
message XpressParametersProto {
|
||||
message Parameter {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
repeated Parameter parameters = 1;
|
||||
}
|
||||
@@ -36,6 +36,11 @@ namespace operations_research::math_opt {
|
||||
|
||||
namespace {
|
||||
bool checkInt32Overflow(const std::size_t value) { return value > INT32_MAX; }
|
||||
template <typename T>
|
||||
/** Forward an optional span to the C API. */
|
||||
T* forwardSpan(std::optional<absl::Span<T>> const& span) {
|
||||
return span.has_value() ? span.value().data() : nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
constexpr int kXpressOk = 0;
|
||||
@@ -64,7 +69,6 @@ absl::StatusOr<std::unique_ptr<Xpress>> Xpress::New(absl::string_view) {
|
||||
CHECK(correctlyLoaded);
|
||||
XPRSprob model;
|
||||
CHECK_EQ(kXpressOk, XPRScreateprob(&model));
|
||||
CHECK_EQ(kXpressOk, XPRSaddcbmessage(model, printXpressMessage, nullptr, 0));
|
||||
return absl::WrapUnique(new Xpress(model));
|
||||
}
|
||||
|
||||
@@ -77,11 +81,26 @@ absl::Status Xpress::SetProbName(absl::string_view name) {
|
||||
return ToStatus(XPRSsetprobname(xpress_model_, truncated.c_str()));
|
||||
}
|
||||
|
||||
void XPRS_CC Xpress::printXpressMessage(XPRSprob, void*, const char* sMsg, int,
|
||||
int) {
|
||||
if (sMsg) {
|
||||
LOG(INFO) << sMsg;
|
||||
}
|
||||
absl::Status Xpress::AddCbMessage(void(XPRS_CC* cb)(XPRSprob, void*,
|
||||
char const*, int, int),
|
||||
void* cbdata, int prio) {
|
||||
return ToStatus(XPRSaddcbmessage(xpress_model_, cb, cbdata, prio));
|
||||
}
|
||||
|
||||
absl::Status Xpress::RemoveCbMessage(void(XPRS_CC* cb)(XPRSprob, void*,
|
||||
char const*, int, int),
|
||||
void* cbdata) {
|
||||
return ToStatus(XPRSremovecbmessage(xpress_model_, cb, cbdata));
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddCbChecktime(int(XPRS_CC* cb)(XPRSprob, void*),
|
||||
void* cbdata, int prio) {
|
||||
return ToStatus(XPRSaddcbchecktime(xpress_model_, cb, cbdata, prio));
|
||||
}
|
||||
|
||||
absl::Status Xpress::RemoveCbChecktime(int(XPRS_CC* cb)(XPRSprob, void*),
|
||||
void* cbdata) {
|
||||
return ToStatus(XPRSremovecbchecktime(xpress_model_, cb, cbdata));
|
||||
}
|
||||
|
||||
Xpress::~Xpress() {
|
||||
@@ -96,38 +115,67 @@ void Xpress::initIntControlDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddVars(const absl::Span<const double> obj,
|
||||
const absl::Span<const double> lb,
|
||||
const absl::Span<const double> ub,
|
||||
const absl::Span<const char> vtype) {
|
||||
return AddVars({}, {}, {}, obj, lb, ub, vtype);
|
||||
absl::Status Xpress::AddNames(int type, absl::Span<const char> names, int first,
|
||||
int last) {
|
||||
return ToStatus(XPRSaddnames(xpress_model_, type, names.data(), first, last));
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddVars(const absl::Span<const int> vbegin,
|
||||
const absl::Span<const int> vind,
|
||||
const absl::Span<const double> vval,
|
||||
// All span arguments can be missing to indicate "use default values".
|
||||
// Default objective value: 0
|
||||
// Default lower bound: 0
|
||||
// Default upper bound: infinity
|
||||
// Default type: continuous
|
||||
absl::Status Xpress::AddVars(std::size_t count,
|
||||
const absl::Span<const double> obj,
|
||||
const absl::Span<const double> lb,
|
||||
const absl::Span<const double> ub,
|
||||
const absl::Span<const char> vtype) {
|
||||
if (checkInt32Overflow(lb.size())) {
|
||||
ASSIGN_OR_RETURN(int const oldCols, GetIntAttr(XPRS_ORIGINALCOLS));
|
||||
if (checkInt32Overflow(count) ||
|
||||
checkInt32Overflow(std::size_t(oldCols) + std::size_t(count))) {
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 variables");
|
||||
}
|
||||
const int num_vars = static_cast<int>(lb.size());
|
||||
if (vind.size() != vval.size() || ub.size() != num_vars ||
|
||||
vtype.size() != num_vars || (!obj.empty() && obj.size() != num_vars) ||
|
||||
(!vbegin.empty() && vbegin.size() != num_vars)) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Xpress::AddVars arguments are of inconsistent sizes");
|
||||
}
|
||||
double* c_obj = nullptr;
|
||||
const int num_vars = static_cast<int>(count);
|
||||
double const* c_obj = nullptr;
|
||||
if (!obj.empty()) {
|
||||
c_obj = const_cast<double*>(obj.data());
|
||||
if (obj.size() != count)
|
||||
return absl::InvalidArgumentError(
|
||||
"Xpress::AddVars objective argument has bad size");
|
||||
c_obj = obj.data();
|
||||
}
|
||||
// TODO: look into int64_t support for number of vars (use XPRSaddcols64)
|
||||
return ToStatus(XPRSaddcols(xpress_model_, num_vars, 0, c_obj, nullptr,
|
||||
nullptr, nullptr, lb.data(), ub.data()));
|
||||
if (!lb.empty() && lb.size() != count)
|
||||
return absl::InvalidArgumentError(
|
||||
"Xpress::AddVars lower bound argument has bad size");
|
||||
if (!ub.empty() && ub.size() != count)
|
||||
return absl::InvalidArgumentError(
|
||||
"Xpress::AddVars upper bound argument has bad size");
|
||||
std::vector<int> colind;
|
||||
if (!vtype.empty()) {
|
||||
if (vtype.size() != count)
|
||||
return absl::InvalidArgumentError(
|
||||
"Xpress::AddVars type argument has bad size");
|
||||
colind.reserve(count); // So that we don't OOM after adding
|
||||
}
|
||||
// XPRSaddcols64() allows to add variables with more than INT_MAX
|
||||
// non-zero coefficients here. It does NOT allow adding more than INT_MAX
|
||||
// variables.
|
||||
// Since we don't add any non-zeros here, it is safe to use XPRSaddcols().
|
||||
RETURN_IF_ERROR(ToStatus(XPRSaddcols(
|
||||
xpress_model_, num_vars, 0, c_obj, nullptr, nullptr, nullptr,
|
||||
lb.size() ? lb.data() : nullptr, ub.size() ? ub.data() : nullptr)));
|
||||
if (!vtype.empty()) {
|
||||
for (int i = 0; i < num_vars; ++i) colind.push_back(oldCols + i);
|
||||
int const ret =
|
||||
XPRSchgcoltype(xpress_model_, num_vars, colind.data(), vtype.data());
|
||||
if (ret != 0) {
|
||||
// Changing the column type failed. We must roll back XPRSaddcols() and
|
||||
// then return an error.
|
||||
XPRSdelcols(xpress_model_, num_vars, colind.data());
|
||||
}
|
||||
return ToStatus(ret);
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddConstrs(const absl::Span<const char> sense,
|
||||
@@ -216,12 +264,19 @@ absl::Status Xpress::PostSolve() {
|
||||
return ToStatus(XPRSpostsolve(xpress_model_));
|
||||
}
|
||||
|
||||
absl::Status Xpress::MipOptimize() {
|
||||
return ToStatus(XPRSmipoptimize(xpress_model_, nullptr));
|
||||
absl::Status Xpress::Optimize(std::string const& flags, int* p_solvestatus,
|
||||
int* p_solstatus) {
|
||||
return ToStatus(
|
||||
XPRSoptimize(xpress_model_, flags.c_str(), p_solvestatus, p_solstatus));
|
||||
}
|
||||
|
||||
void Xpress::Terminate() { XPRSinterrupt(xpress_model_, XPRS_STOP_USER); };
|
||||
|
||||
absl::Status Xpress::GetControlInfo(char const* name, int* p_id,
|
||||
int* p_type) const {
|
||||
return ToStatus(XPRSgetcontrolinfo(xpress_model_, name, p_id, p_type));
|
||||
}
|
||||
|
||||
absl::StatusOr<int> Xpress::GetIntControl(int control) const {
|
||||
int result;
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetintcontrol(xpress_model_, control, &result)))
|
||||
@@ -243,6 +298,45 @@ absl::Status Xpress::ResetIntControl(int control) {
|
||||
", consider adding it to Xpress::initIntControlDefaults");
|
||||
}
|
||||
|
||||
absl::StatusOr<int64_t> Xpress::GetIntControl64(int control) const {
|
||||
XPRSint64 result;
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetintcontrol64(xpress_model_, control, &result)))
|
||||
<< "Error getting Xpress int64 control: " << control;
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetIntControl64(int control, int64_t value) {
|
||||
return ToStatus(XPRSsetintcontrol64(xpress_model_, control, value));
|
||||
}
|
||||
|
||||
absl::StatusOr<double> Xpress::GetDblControl(int control) const {
|
||||
double result;
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetdblcontrol(xpress_model_, control, &result)))
|
||||
<< "Error getting Xpress double control: " << control;
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetDblControl(int control, double value) {
|
||||
return ToStatus(XPRSsetdblcontrol(xpress_model_, control, value));
|
||||
}
|
||||
|
||||
absl::StatusOr<std::string> Xpress::GetStrControl(int control) const {
|
||||
int nbytes = 0;
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetstringcontrol(xpress_model_, control, NULL, 0, &nbytes)));
|
||||
std::vector<char> result(nbytes,
|
||||
'\0'); // nbytes CONTAINS the terminating nul!
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetstringcontrol(
|
||||
xpress_model_, control, result.data(), nbytes, &nbytes)))
|
||||
<< "Error getting Xpress string control: " << control;
|
||||
return std::string(result.data(), nbytes);
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetStrControl(int control, std::string const& value) {
|
||||
return ToStatus(XPRSsetstrcontrol(xpress_model_, control, value.c_str()));
|
||||
}
|
||||
|
||||
absl::StatusOr<int> Xpress::GetIntAttr(int attribute) const {
|
||||
int result;
|
||||
RETURN_IF_ERROR(ToStatus(XPRSgetintattrib(xpress_model_, attribute, &result)))
|
||||
@@ -257,16 +351,13 @@ absl::StatusOr<double> Xpress::GetDoubleAttr(int attribute) const {
|
||||
return result;
|
||||
}
|
||||
|
||||
int Xpress::GetNumberOfConstraints() const {
|
||||
int n;
|
||||
XPRSgetintattrib(xpress_model_, XPRS_ROWS, &n);
|
||||
return n;
|
||||
}
|
||||
|
||||
int Xpress::GetNumberOfVariables() const {
|
||||
int n;
|
||||
XPRSgetintattrib(xpress_model_, XPRS_COLS, &n);
|
||||
return n;
|
||||
absl::StatusOr<double> Xpress::GetObjectiveDoubleAttr(int objidx,
|
||||
int attribute) const {
|
||||
double result = 0.0;
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetobjdblattrib(xpress_model_, objidx, attribute, &result)))
|
||||
<< "Error getting Xpress objective double attribute: " << attribute;
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::StatusOr<int> Xpress::GetDualStatus() const {
|
||||
@@ -281,8 +372,10 @@ absl::StatusOr<int> Xpress::GetDualStatus() const {
|
||||
|
||||
absl::Status Xpress::GetBasis(std::vector<int>& rowBasis,
|
||||
std::vector<int>& colBasis) const {
|
||||
rowBasis.resize(GetNumberOfConstraints());
|
||||
colBasis.resize(GetNumberOfVariables());
|
||||
ASSIGN_OR_RETURN(int const rows, GetIntAttr(XPRS_ORIGINALROWS));
|
||||
ASSIGN_OR_RETURN(int const cols, GetIntAttr(XPRS_ORIGINALCOLS));
|
||||
rowBasis.resize(rows);
|
||||
colBasis.resize(cols);
|
||||
return ToStatus(
|
||||
XPRSgetbasis(xpress_model_, rowBasis.data(), colBasis.data()));
|
||||
}
|
||||
@@ -298,22 +391,251 @@ absl::Status Xpress::SetStartingBasis(std::vector<int>& rowBasis,
|
||||
}
|
||||
|
||||
absl::StatusOr<std::vector<double>> Xpress::GetVarLb() const {
|
||||
int nVars = GetNumberOfVariables();
|
||||
ASSIGN_OR_RETURN(int const cols, GetIntAttr(XPRS_ORIGINALCOLS));
|
||||
std::vector<double> bounds;
|
||||
bounds.reserve(nVars);
|
||||
bounds.reserve(cols);
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetlb(xpress_model_, bounds.data(), 0, nVars - 1)))
|
||||
ToStatus(XPRSgetlb(xpress_model_, bounds.data(), 0, cols - 1)))
|
||||
<< "Failed to retrieve variable LB from XPRESS";
|
||||
return bounds;
|
||||
}
|
||||
absl::StatusOr<std::vector<double>> Xpress::GetVarUb() const {
|
||||
int nVars = GetNumberOfVariables();
|
||||
ASSIGN_OR_RETURN(int const cols, GetIntAttr(XPRS_ORIGINALCOLS));
|
||||
std::vector<double> bounds;
|
||||
bounds.reserve(nVars);
|
||||
bounds.reserve(cols);
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSgetub(xpress_model_, bounds.data(), 0, nVars - 1)))
|
||||
ToStatus(XPRSgetub(xpress_model_, bounds.data(), 0, cols - 1)))
|
||||
<< "Failed to retrieve variable UB from XPRESS";
|
||||
return bounds;
|
||||
}
|
||||
|
||||
absl::Status Xpress::Interrupt(int reason) {
|
||||
return ToStatus(XPRSinterrupt(xpress_model_, reason));
|
||||
}
|
||||
|
||||
absl::StatusOr<bool> Xpress::IsMIP() const {
|
||||
ASSIGN_OR_RETURN(int ents, GetIntAttr(XPRS_ORIGINALMIPENTS));
|
||||
if (ents != 0) return true;
|
||||
ASSIGN_OR_RETURN(int sets, GetIntAttr(XPRS_ORIGINALSETS));
|
||||
if (sets != 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
absl::Status Xpress::GetDuals(int* p_status,
|
||||
std::optional<absl::Span<double>> const& duals,
|
||||
int first, int last) {
|
||||
return ToStatus(
|
||||
XPRSgetduals(xpress_model_, p_status, forwardSpan(duals), first, last));
|
||||
}
|
||||
absl::Status Xpress::GetSolution(int* p_status,
|
||||
std::optional<absl::Span<double>> const& x,
|
||||
int first, int last) {
|
||||
return ToStatus(
|
||||
XPRSgetsolution(xpress_model_, p_status, forwardSpan(x), first, last));
|
||||
}
|
||||
absl::Status Xpress::GetRedCosts(int* p_status,
|
||||
std::optional<absl::Span<double>> const& dj,
|
||||
int first, int last) {
|
||||
return ToStatus(
|
||||
XPRSgetredcosts(xpress_model_, p_status, forwardSpan(dj), first, last));
|
||||
}
|
||||
|
||||
/** Add a mip start that is specified in the original space, i.e., in terms of
|
||||
* ortools variables.
|
||||
*/
|
||||
absl::Status Xpress::AddMIPSol(absl::Span<double const> vals,
|
||||
absl::Span<int const> colind, char const* name) {
|
||||
if (checkInt32Overflow(colind.size()))
|
||||
return absl::InvalidArgumentError("more start values than columns");
|
||||
if (colind.size() != vals.size())
|
||||
return absl::InvalidArgumentError("inconsitent data to AddMIPSol()");
|
||||
// XPRSaddmipsol() supports colind=nullptr, but we do not support that here
|
||||
// since we don't need it.
|
||||
return ToStatus(XPRSaddmipsol(xpress_model_, static_cast<int>(colind.size()),
|
||||
vals.data(), colind.data(), name));
|
||||
}
|
||||
|
||||
absl::Status Xpress::LoadDelayedRows(absl::Span<int const> rows) {
|
||||
if (checkInt32Overflow(rows.size()))
|
||||
return absl::InvalidArgumentError("more delayed rows than rows");
|
||||
return ToStatus(XPRSloaddelayedrows(
|
||||
xpress_model_, static_cast<int>(rows.size()), rows.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::LoadDirs(
|
||||
absl::Span<int const> cols,
|
||||
std::optional<absl::Span<int const>> const& prio,
|
||||
std::optional<absl::Span<char const>> const& dir,
|
||||
std::optional<absl::Span<double const>> const& up,
|
||||
std::optional<absl::Span<double const>> const& down) {
|
||||
if (checkInt32Overflow(cols.size()))
|
||||
return absl::InvalidArgumentError("more directions than columns");
|
||||
return ToStatus(XPRSloaddirs(xpress_model_, static_cast<int>(cols.size()),
|
||||
cols.data(), forwardSpan(prio), forwardSpan(dir),
|
||||
forwardSpan(up), forwardSpan(down)));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetObjectiveIntControl(int obj, int control, int value) {
|
||||
return ToStatus(XPRSsetobjintcontrol(xpress_model_, obj, control, value));
|
||||
}
|
||||
absl::Status Xpress::SetObjectiveDoubleControl(int obj, int control,
|
||||
double value) {
|
||||
return ToStatus(XPRSsetobjdblcontrol(xpress_model_, obj, control, value));
|
||||
}
|
||||
absl::StatusOr<int> Xpress::AddObjective(double constant, int ncols,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> objcoef,
|
||||
int priority, double weight) {
|
||||
ASSIGN_OR_RETURN(int const objs, GetIntAttr(XPRS_OBJECTIVES));
|
||||
if (objs == INT_MAX) {
|
||||
return util::StatusBuilder(absl::StatusCode::kInvalidArgument)
|
||||
<< "too many objectives";
|
||||
}
|
||||
int ret = XPRSaddobj(xpress_model_, ncols, colind.data(), objcoef.data(),
|
||||
priority, weight);
|
||||
if (ret) {
|
||||
return ToStatus(ret);
|
||||
}
|
||||
if (constant != 0.0) {
|
||||
ret =
|
||||
XPRSsetobjdblcontrol(xpress_model_, objs, XPRS_OBJECTIVE_RHS, constant);
|
||||
if (ret) {
|
||||
XPRSdelobj(xpress_model_, objs);
|
||||
return ToStatus(ret);
|
||||
}
|
||||
}
|
||||
return objs;
|
||||
}
|
||||
|
||||
absl::StatusOr<double> Xpress::CalculateObjectiveN(int objidx,
|
||||
double const* solution) {
|
||||
double objval = 0.0;
|
||||
int ret = XPRScalcobjn(xpress_model_, objidx, solution, &objval);
|
||||
if (ret) {
|
||||
return ToStatus(ret);
|
||||
}
|
||||
return objval;
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddSets(absl::Span<char const> settype,
|
||||
absl::Span<XPRSint64 const> start,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> refval) {
|
||||
ASSIGN_OR_RETURN(int const oldSets, GetIntAttr(XPRS_ORIGINALSETS));
|
||||
if (checkInt32Overflow(settype.size()) ||
|
||||
checkInt32Overflow(std::size_t(oldSets) + settype.size())) {
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 SOSs");
|
||||
}
|
||||
return ToStatus(XPRSaddsets64(xpress_model_, static_cast<int>(settype.size()),
|
||||
colind.size(), settype.data(), start.data(),
|
||||
colind.data(), refval.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SetIndicators(absl::Span<int const> rowind,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<int const> complement) {
|
||||
ASSIGN_OR_RETURN(int const oldInds, GetIntAttr(XPRS_ORIGINALINDICATORS));
|
||||
if (checkInt32Overflow(rowind.size()) ||
|
||||
checkInt32Overflow(std::size_t(oldInds) + rowind.size())) {
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 indicators");
|
||||
}
|
||||
if (rowind.size() != colind.size() || rowind.size() != complement.size()) {
|
||||
return absl::InvalidArgumentError(
|
||||
"inconsistent arguments to SetInidicators");
|
||||
}
|
||||
return ToStatus(
|
||||
XPRSsetindicators(xpress_model_, static_cast<int>(rowind.size()),
|
||||
rowind.data(), colind.data(), complement.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddRows(absl::Span<char const> rowtype,
|
||||
absl::Span<double const> rhs,
|
||||
absl::Span<double const> rng,
|
||||
absl::Span<XPRSint64 const> start,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> rowcoef) {
|
||||
ASSIGN_OR_RETURN(int const oldRows, GetIntAttr(XPRS_ORIGINALROWS));
|
||||
if (checkInt32Overflow(rowtype.size()) ||
|
||||
checkInt32Overflow(std::size_t(oldRows) + rowtype.size())) {
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 rows");
|
||||
}
|
||||
if (rowtype.size() != rhs.size() || rowtype.size() != rng.size() ||
|
||||
rowtype.size() != start.size() || colind.size() != rowcoef.size())
|
||||
return absl::InvalidArgumentError("inconsistent arguments to AddRows");
|
||||
return ToStatus(XPRSaddrows64(xpress_model_, static_cast<int>(rowtype.size()),
|
||||
colind.size(), rowtype.data(), rhs.data(),
|
||||
rng.data(), start.data(), colind.data(),
|
||||
rowcoef.data()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::AddQRow(char sense, double rhs, double rng,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> rowcoef,
|
||||
absl::Span<int const> qcol1,
|
||||
absl::Span<int const> qcol2,
|
||||
absl::Span<double const> qcoef) {
|
||||
ASSIGN_OR_RETURN(int const oldRows, GetIntAttr(XPRS_ORIGINALROWS));
|
||||
if (checkInt32Overflow(std::size_t(oldRows) + 1))
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 rows");
|
||||
XPRSint64 const start = 0;
|
||||
RETURN_IF_ERROR(
|
||||
ToStatus(XPRSaddrows64(xpress_model_, 1, colind.size(), &sense, &rhs,
|
||||
&rng, &start, colind.data(), rowcoef.data())));
|
||||
if (qcol1.size() > 0) {
|
||||
int const ret = XPRSaddqmatrix64(xpress_model_, oldRows, qcol1.size(),
|
||||
qcol1.data(), qcol2.data(), qcoef.data());
|
||||
if (ret != 0) {
|
||||
XPRSdelrows(xpress_model_, 1, &oldRows);
|
||||
return ToStatus(ret);
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Xpress::WriteProb(std::string const& filename,
|
||||
std::string const& flags) {
|
||||
return ToStatus(
|
||||
XPRSwriteprob(xpress_model_, filename.c_str(), flags.c_str()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::SaveAs(std::string const& filename) {
|
||||
return ToStatus(XPRSsaveas(xpress_model_, filename.c_str()));
|
||||
}
|
||||
|
||||
absl::Status Xpress::GetLB(absl::Span<double> lb, int first, int last) {
|
||||
return ToStatus(XPRSgetlb(xpress_model_, lb.data(), first, last));
|
||||
}
|
||||
absl::Status Xpress::GetUB(absl::Span<double> ub, int first, int last) {
|
||||
return ToStatus(XPRSgetub(xpress_model_, ub.data(), first, last));
|
||||
}
|
||||
absl::Status Xpress::GetColType(absl::Span<char> ctype, int first, int last) {
|
||||
return ToStatus(XPRSgetcoltype(xpress_model_, ctype.data(), first, last));
|
||||
}
|
||||
|
||||
absl::Status Xpress::ChgBounds(absl::Span<int const> colind,
|
||||
absl::Span<char const> bndtype,
|
||||
absl::Span<double const> bndval) {
|
||||
if (colind.size() != bndtype.size() || colind.size() != bndval.size())
|
||||
return absl::InvalidArgumentError("inconsitent data to ChgBounds()");
|
||||
if (checkInt32Overflow(colind.size()))
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 bound changes");
|
||||
return ToStatus(XPRSchgbounds(xpress_model_, colind.size(), colind.data(),
|
||||
bndtype.data(), bndval.data()));
|
||||
}
|
||||
absl::Status Xpress::ChgColType(absl::Span<int const> colind,
|
||||
absl::Span<char const> coltype) {
|
||||
if (colind.size() != coltype.size())
|
||||
return absl::InvalidArgumentError("inconsitent data to ChgColType()");
|
||||
if (checkInt32Overflow(colind.size()))
|
||||
return absl::InvalidArgumentError(
|
||||
"XPRESS cannot handle more than 2^31 type changes");
|
||||
return ToStatus(XPRSchgcoltype(xpress_model_, colind.size(), colind.data(),
|
||||
coltype.data()));
|
||||
}
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -49,23 +50,32 @@ class Xpress {
|
||||
|
||||
~Xpress();
|
||||
|
||||
absl::Status GetControlInfo(char const* name, int* p_id, int* p_type) const;
|
||||
|
||||
absl::StatusOr<int> GetIntControl(int control) const;
|
||||
absl::Status SetIntControl(int control, int value);
|
||||
absl::Status ResetIntControl(int control); // reset to default value
|
||||
|
||||
absl::StatusOr<int64_t> GetIntControl64(int control) const;
|
||||
absl::Status SetIntControl64(int control, int64_t value);
|
||||
|
||||
absl::StatusOr<double> GetDblControl(int control) const;
|
||||
absl::Status SetDblControl(int control, double value);
|
||||
|
||||
absl::StatusOr<std::string> GetStrControl(int control) const;
|
||||
absl::Status SetStrControl(int control, std::string const& value);
|
||||
|
||||
absl::StatusOr<int> GetIntAttr(int attribute) const;
|
||||
|
||||
absl::StatusOr<double> GetDoubleAttr(int attribute) const;
|
||||
absl::StatusOr<double> GetObjectiveDoubleAttr(int objidx,
|
||||
int attribute) const;
|
||||
|
||||
absl::Status AddVars(absl::Span<const double> obj,
|
||||
absl::Span<const double> lb, absl::Span<const double> ub,
|
||||
absl::Span<const char> vtype);
|
||||
|
||||
absl::Status AddVars(absl::Span<const int> vbegin, absl::Span<const int> vind,
|
||||
absl::Span<const double> vval,
|
||||
absl::Span<const double> obj,
|
||||
absl::Status AddVars(std::size_t count, absl::Span<const double> obj,
|
||||
absl::Span<const double> lb, absl::Span<const double> ub,
|
||||
absl::Span<const char> vtype);
|
||||
absl::Status AddNames(int type, absl::Span<const char> names, int first,
|
||||
int last);
|
||||
|
||||
absl::Status AddConstrs(absl::Span<const char> sense,
|
||||
absl::Span<const double> rhs,
|
||||
@@ -95,9 +105,21 @@ class Xpress {
|
||||
// size (nVars, nCons, and nVars respectively)
|
||||
absl::Status GetLpSol(absl::Span<double> primals, absl::Span<double> duals,
|
||||
absl::Span<double> reducedCosts);
|
||||
absl::Status MipOptimize();
|
||||
absl::Status Optimize(std::string const& flags = "",
|
||||
int* p_solvestatus = nullptr,
|
||||
int* p_solstatus = nullptr);
|
||||
absl::Status PostSolve();
|
||||
|
||||
absl::Status GetLB(absl::Span<double> lb, int first, int last);
|
||||
absl::Status GetUB(absl::Span<double> ub, int first, int last);
|
||||
absl::Status GetColType(absl::Span<char> ctype, int first, int last);
|
||||
|
||||
absl::Status ChgBounds(absl::Span<int const> colind,
|
||||
absl::Span<char const> bndtype,
|
||||
absl::Span<double const> bndval);
|
||||
absl::Status ChgColType(absl::Span<int const> colind,
|
||||
absl::Span<char const> coltype);
|
||||
|
||||
void Terminate();
|
||||
|
||||
absl::StatusOr<int> GetDualStatus() const;
|
||||
@@ -106,16 +128,73 @@ class Xpress {
|
||||
absl::Status SetStartingBasis(std::vector<int>& rowBasis,
|
||||
std::vector<int>& colBasis) const;
|
||||
|
||||
static void XPRS_CC printXpressMessage(XPRSprob prob, void* data,
|
||||
const char* sMsg, int nLen,
|
||||
int nMsgLvl);
|
||||
|
||||
int GetNumberOfConstraints() const;
|
||||
int GetNumberOfVariables() const;
|
||||
absl::Status AddCbMessage(void(XPRS_CC* cb)(XPRSprob, void*, char const*, int,
|
||||
int),
|
||||
void* cbdata, int prio = 0);
|
||||
absl::Status RemoveCbMessage(void(XPRS_CC* cb)(XPRSprob, void*, char const*,
|
||||
int, int),
|
||||
void* cbdata = nullptr);
|
||||
absl::Status AddCbChecktime(int(XPRS_CC* cb)(XPRSprob, void*), void* cbdata,
|
||||
int prio = 0);
|
||||
absl::Status RemoveCbChecktime(int(XPRS_CC* cb)(XPRSprob, void*),
|
||||
void* cbdata = nullptr);
|
||||
|
||||
absl::StatusOr<std::vector<double>> GetVarLb() const;
|
||||
absl::StatusOr<std::vector<double>> GetVarUb() const;
|
||||
|
||||
absl::Status Interrupt(int reason);
|
||||
|
||||
absl::StatusOr<bool> IsMIP() const;
|
||||
absl::Status GetDuals(int* p_status,
|
||||
std::optional<absl::Span<double>> const& duals,
|
||||
int first, int last);
|
||||
absl::Status GetSolution(int* p_status,
|
||||
std::optional<absl::Span<double>> const& x,
|
||||
int first, int last);
|
||||
absl::Status GetRedCosts(int* p_status,
|
||||
std::optional<absl::Span<double>> const& dj,
|
||||
int first, int last);
|
||||
|
||||
absl::Status AddMIPSol(absl::Span<double const> vals,
|
||||
absl::Span<int const> colind,
|
||||
char const* name = nullptr);
|
||||
absl::Status LoadDelayedRows(absl::Span<int const> rows);
|
||||
absl::Status LoadDirs(absl::Span<int const> cols,
|
||||
std::optional<absl::Span<int const>> const& prio,
|
||||
std::optional<absl::Span<char const>> const& dir,
|
||||
std::optional<absl::Span<double const>> const& up,
|
||||
std::optional<absl::Span<double const>> const& down);
|
||||
|
||||
absl::Status SetObjectiveIntControl(int obj, int control, int value);
|
||||
absl::Status SetObjectiveDoubleControl(int obj, int control, double value);
|
||||
absl::StatusOr<int> AddObjective(double constant, int ncols,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> objcoef,
|
||||
int priority, double weight);
|
||||
absl::StatusOr<double> CalculateObjectiveN(int objidx,
|
||||
double const* solution);
|
||||
absl::Status AddSets(absl::Span<char const> settype,
|
||||
absl::Span<XPRSint64 const> start,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> refval);
|
||||
absl::Status SetIndicators(absl::Span<int const> rowind,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<int const> complement);
|
||||
absl::Status AddRows(absl::Span<char const> rowtype,
|
||||
absl::Span<double const> rhs,
|
||||
absl::Span<double const> rng,
|
||||
absl::Span<XPRSint64 const> start,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> rowcoef);
|
||||
absl::Status AddQRow(char sense, double rhs, double rng,
|
||||
absl::Span<int const> colind,
|
||||
absl::Span<double const> rowcoef,
|
||||
absl::Span<int const> qcol1, absl::Span<int const> qcol2,
|
||||
absl::Span<double const> qcoef);
|
||||
absl::Status WriteProb(std::string const& filename,
|
||||
std::string const& flags = "");
|
||||
absl::Status SaveAs(std::string const& filename);
|
||||
|
||||
private:
|
||||
XPRSprob xpress_model_;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,6 @@
|
||||
namespace operations_research::math_opt {
|
||||
|
||||
// Interface to FICO XPRESS solver
|
||||
// Largely inspired by the Gurobi interface
|
||||
class XpressSolver : public SolverInterface {
|
||||
public:
|
||||
// Creates the XPRESS solver and loads the model into it
|
||||
@@ -68,8 +67,9 @@ class XpressSolver : public SolverInterface {
|
||||
const SolveInterrupter* interrupter) override;
|
||||
|
||||
private:
|
||||
explicit XpressSolver(std::unique_ptr<Xpress> g_xpress);
|
||||
explicit XpressSolver(std::unique_ptr<Xpress> g_xpress, bool extract_names);
|
||||
|
||||
public:
|
||||
// For easing reading the code, we declare these types:
|
||||
using VarId = int64_t;
|
||||
using AuxiliaryObjectiveId = int64_t;
|
||||
@@ -88,6 +88,7 @@ class XpressSolver : public SolverInterface {
|
||||
using XpressGeneralConstraintIndex = int;
|
||||
using XpressAnyConstraintIndex = int;
|
||||
|
||||
private:
|
||||
static constexpr XpressVariableIndex kUnspecifiedIndex = -1;
|
||||
static constexpr XpressAnyConstraintIndex kUnspecifiedConstraint = -2;
|
||||
static constexpr double kPlusInf = XPRS_PLUSINFINITY;
|
||||
@@ -98,18 +99,21 @@ class XpressSolver : public SolverInterface {
|
||||
}
|
||||
|
||||
// Data associated with each linear constraint
|
||||
public:
|
||||
struct LinearConstraintData {
|
||||
XpressLinearConstraintIndex constraint_index = kUnspecifiedConstraint;
|
||||
double lower_bound = kMinusInf;
|
||||
double upper_bound = kPlusInf;
|
||||
};
|
||||
|
||||
private:
|
||||
absl::StatusOr<SolveResultProto> ExtractSolveResultProto(
|
||||
absl::Time start, const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
absl::StatusOr<SolutionProto> GetSolution(
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
absl::Status ExtendWithMultiobj(SolutionProto& solution);
|
||||
absl::Status AppendSolution(SolveResultProto& solve_result,
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
absl::StatusOr<SolveStatsProto> GetSolveStats(absl::Time start) const;
|
||||
|
||||
absl::StatusOr<double> GetBestPrimalBound() const;
|
||||
@@ -118,26 +122,45 @@ class XpressSolver : public SolverInterface {
|
||||
absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
double best_primal_bound, double best_dual_bound) const;
|
||||
|
||||
absl::StatusOr<SolutionProto> GetLpSolution(
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
const SolveParametersProto& solve_parameters);
|
||||
bool isPrimalFeasible() const;
|
||||
bool isDualFeasible() const;
|
||||
|
||||
void ExtractBounds(double lb, double ub, char& sense, double& rhs,
|
||||
double& rng);
|
||||
void ExtractLinear(SparseDoubleVectorProto const& expr,
|
||||
std::vector<int>& colind, std::vector<double>& coef);
|
||||
void ExtractQuadratic(QuadraticConstraintProto const& expr,
|
||||
std::vector<int>& lin_colind,
|
||||
std::vector<double>& lin_coef,
|
||||
std::vector<int>& quad_col1,
|
||||
std::vector<int>& quad_col2,
|
||||
std::vector<double>& quad_coef);
|
||||
|
||||
absl::StatusOr<std::optional<BasisProto>> GetBasisIfAvailable(
|
||||
const SolveParametersProto& parameters);
|
||||
|
||||
absl::Status AddNewLinearConstraints(
|
||||
const LinearConstraintsProto& constraints);
|
||||
absl::Status AddNewVariables(const VariablesProto& new_variables);
|
||||
absl::Status AddSingleObjective(const ObjectiveProto& objective);
|
||||
absl::Status AddObjective(const ObjectiveProto& objective,
|
||||
std::optional<AuxiliaryObjectiveId> objective_id,
|
||||
bool multiobj);
|
||||
absl::Status AddSOS(
|
||||
const google::protobuf::Map<AnyConstraintId, SosConstraintProto>& sets,
|
||||
bool sos1);
|
||||
absl::Status AddIndicators(
|
||||
const google::protobuf::Map<IndicatorConstraintId,
|
||||
IndicatorConstraintProto>& indicators);
|
||||
absl::Status AddQuadraticConstraints(
|
||||
const google::protobuf::Map<QuadraticConstraintId,
|
||||
QuadraticConstraintProto>& constraints);
|
||||
absl::Status AddSecondOrderConeConstraints(
|
||||
const google::protobuf::Map<SecondOrderConeConstraintId,
|
||||
SecondOrderConeConstraintProto>& constraints);
|
||||
absl::Status ChangeCoefficients(const SparseDoubleMatrixProto& matrix);
|
||||
|
||||
absl::Status LoadModel(const ModelProto& input_model);
|
||||
|
||||
std::string GetLpOptimizationFlags(const SolveParametersProto& parameters);
|
||||
absl::Status CallXpressSolve(const SolveParametersProto& parameters);
|
||||
|
||||
// Fills in result with the values in xpress_values aided by the index
|
||||
// conversion from map which should be either variables_map_ or
|
||||
// linear_constraints_map_ as appropriate. Only key/value pairs that passes
|
||||
@@ -149,6 +172,7 @@ class XpressSolver : public SolverInterface {
|
||||
const SparseVectorFilterProto& filter) const;
|
||||
|
||||
const std::unique_ptr<Xpress> xpress_;
|
||||
bool const extract_names_;
|
||||
|
||||
// Internal correspondence from variable proto IDs to Xpress-numbered
|
||||
// variables.
|
||||
@@ -157,28 +181,59 @@ class XpressSolver : public SolverInterface {
|
||||
// Xpress-numbered linear constraint and extra information.
|
||||
gtl::linked_hash_map<LinearConstraintId, LinearConstraintData>
|
||||
linear_constraints_map_;
|
||||
// Internal correspondence from objective proto IDs to Xpress-numbered
|
||||
// objectives.
|
||||
gtl::linked_hash_map<AuxiliaryObjectiveId, XpressMultiObjectiveIndex>
|
||||
objectives_map_;
|
||||
// Internal correspondence from SOS1 proto IDs to Xpress-numbered
|
||||
// SOS1 constraints.
|
||||
gtl::linked_hash_map<Sos1ConstraintId, XpressSosConstraintIndex> sos1_map_;
|
||||
// Internal correspondence from SOS2 proto IDs to Xpress-numbered
|
||||
// SOS2 constraints.
|
||||
gtl::linked_hash_map<Sos2ConstraintId, XpressSosConstraintIndex> sos2_map_;
|
||||
// Internal correspondence from indicator proto IDs to Xpress-numbered
|
||||
// indicators.
|
||||
gtl::linked_hash_map<IndicatorConstraintId, LinearConstraintData>
|
||||
indicator_map_;
|
||||
// Internal correspondence from quadratic proto IDs to Xpress-numbered
|
||||
// rows.
|
||||
gtl::linked_hash_map<QuadraticConstraintId, LinearConstraintData>
|
||||
quad_constraints_map_;
|
||||
// Internal correspondence from second order cone constraint proto IDs to
|
||||
// Xpress-numbered rows.
|
||||
gtl::linked_hash_map<QuadraticConstraintId, LinearConstraintData> soc_map_;
|
||||
|
||||
int get_model_index(XpressVariableIndex index) const { return index; }
|
||||
int get_model_index(const LinearConstraintData& index) const {
|
||||
return index.constraint_index;
|
||||
}
|
||||
SolutionStatusProto getLpSolutionStatus() const;
|
||||
SolutionStatusProto getPrimalSolutionStatus() const;
|
||||
SolutionStatusProto getDualSolutionStatus() const;
|
||||
absl::StatusOr<InvertedBounds> ListInvertedBounds() const;
|
||||
absl::Status SetXpressStartingBasis(const BasisProto& basis);
|
||||
absl::Status SetLpIterLimits(const SolveParametersProto& parameters);
|
||||
|
||||
/** Whether to force an XPRSpostsolve() after solving. */
|
||||
bool force_postsolve_ = false;
|
||||
/** Stop immediately after the initial LP in MIPs. */
|
||||
bool stop_after_lp_ = false;
|
||||
/** Whether the model has a non-binary indicator variable.
|
||||
* The behavior expected by ortools is that
|
||||
* - we can happily create a model with non-binary indicators
|
||||
* - this must fail at _solve_ time
|
||||
* Xpress implicitly converts indicator variables to binaries, though,
|
||||
* so we must keep track of this fact at build time and raise an error
|
||||
* only at solve time.
|
||||
*/
|
||||
bool nonbinary_indicator_ = false;
|
||||
bool is_multiobj_ = false;
|
||||
bool is_mip_ = false;
|
||||
bool is_maximize_ = false;
|
||||
|
||||
struct LpStatus {
|
||||
int primal_status = 0;
|
||||
int dual_status = 0;
|
||||
};
|
||||
LpStatus xpress_lp_status_;
|
||||
LPAlgorithmProto lp_algorithm_ = LP_ALGORITHM_UNSPECIFIED;
|
||||
|
||||
int xpress_mip_status_ = 0;
|
||||
// Results of the last solve
|
||||
int primal_sol_avail_ = XPRS_SOLAVAILABLE_NOTFOUND;
|
||||
int dual_sol_avail_ = XPRS_SOLAVAILABLE_NOTFOUND;
|
||||
// Information queried right after a solve and stored for solution reporting
|
||||
int solvestatus_ = XPRS_SOLVESTATUS_UNSTARTED;
|
||||
int solstatus_ = XPRS_SOLSTATUS_NOTFOUND;
|
||||
int algorithm_ = XPRS_ALG_DEFAULT;
|
||||
int optimizetypeused_ = -1;
|
||||
};
|
||||
|
||||
} // namespace operations_research::math_opt
|
||||
|
||||
@@ -25,11 +25,13 @@
|
||||
#include "ortools/math_opt/solver_tests/generic_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/infeasible_subsystem_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/invalid_input_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/ip_model_solve_parameters_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/logical_constraint_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_incomplete_solve_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_model_solve_parameters_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_parameter_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/lp_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/mip_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/multi_objective_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/qc_tests.h"
|
||||
#include "ortools/math_opt/solver_tests/qp_tests.h"
|
||||
@@ -37,6 +39,15 @@
|
||||
#include "ortools/math_opt/solver_tests/status_tests.h"
|
||||
#include "ortools/third_party_solvers/xpress_environment.h"
|
||||
|
||||
/** A string in the log file that indicates that the solution process
|
||||
* finished successfully and found the optimal solution for LPs.
|
||||
*/
|
||||
#define OPTIMAL_SOLUTION_FOUND_LP "Optimal solution found"
|
||||
/** A string in the log file that indicates that the solution process
|
||||
* finished successfully and found the optimal solution for MIPs.
|
||||
*/
|
||||
#define OPTIMAL_SOLUTION_FOUND_MIP "*** Search completed ***"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
namespace {
|
||||
@@ -59,16 +70,18 @@ INSTANTIATE_TEST_SUITE_P(XpressLpModelSolveParametersTest,
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressLpParameterTest, LpParameterTest,
|
||||
testing::Values(LpParameterTestParams(SolverType::kXpress,
|
||||
/*supports_simplex=*/true,
|
||||
/*supports_barrier=*/true,
|
||||
/*supports_first_order=*/false,
|
||||
/*supports_random_seed=*/false,
|
||||
/*supports_presolve=*/false,
|
||||
/*supports_cutoff=*/false,
|
||||
/*supports_objective_limit=*/false,
|
||||
/*supports_best_bound_limit=*/false,
|
||||
/*reports_limits=*/false)));
|
||||
testing::Values(LpParameterTestParams(
|
||||
SolverType::kXpress,
|
||||
/*supports_simplex=*/true,
|
||||
/*supports_barrier=*/true,
|
||||
/*supports_first_order=*/true,
|
||||
/*supports_random_seed=*/false, // Xpress supports this but it does not
|
||||
// generate enough variability for this
|
||||
/*supports_presolve=*/true,
|
||||
/*supports_cutoff=*/true,
|
||||
/*supports_objective_limit=*/false, // See comments in xpress_solver.cc
|
||||
/*supports_best_bound_limit=*/false,
|
||||
/*reports_limits=*/false)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressPrimalSimplexLpIncompleteSolveTest, LpIncompleteSolveTest,
|
||||
@@ -77,7 +90,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
/*lp_algorithm=*/LPAlgorithm::kPrimalSimplex,
|
||||
/*supports_iteration_limit=*/true, /*supports_initial_basis=*/false,
|
||||
/*supports_incremental_solve=*/false, /*supports_basis=*/true,
|
||||
/*supports_presolve=*/false, /*check_primal_objective=*/true,
|
||||
/*supports_presolve=*/true, /*check_primal_objective=*/true,
|
||||
/*primal_solution_status_always_set=*/true,
|
||||
/*dual_solution_status_always_set=*/true)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@@ -87,49 +100,93 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
/*lp_algorithm=*/LPAlgorithm::kDualSimplex,
|
||||
/*supports_iteration_limit=*/true, /*supports_initial_basis=*/false,
|
||||
/*supports_incremental_solve=*/false, /*supports_basis=*/true,
|
||||
/*supports_presolve=*/false, /*check_primal_objective=*/true,
|
||||
/*supports_presolve=*/true, /*check_primal_objective=*/true,
|
||||
/*primal_solution_status_always_set=*/true,
|
||||
/*dual_solution_status_always_set=*/true)));
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IncrementalLpTest);
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressMessageCallbackTest, MessageCallbackTest,
|
||||
testing::Values(MessageCallbackTestParams(
|
||||
SolverType::kXpress,
|
||||
/*support_message_callback=*/false,
|
||||
/*support_interrupter=*/false,
|
||||
/*integer_variables=*/false, "")));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressMessageCallbackTest, MessageCallbackTest,
|
||||
testing::ValuesIn({MessageCallbackTestParams(
|
||||
SolverType::kXpress,
|
||||
/*support_message_callback=*/true,
|
||||
/*support_interrupter=*/true,
|
||||
/*integer_variables=*/false,
|
||||
/*ending_substring*/ OPTIMAL_SOLUTION_FOUND_LP),
|
||||
MessageCallbackTestParams(
|
||||
SolverType::kXpress,
|
||||
/*support_message_callback=*/true,
|
||||
/*support_interrupter=*/true,
|
||||
/*integer_variables=*/true,
|
||||
/*ending_substring*/ OPTIMAL_SOLUTION_FOUND_MIP)}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressCallbackTest, CallbackTest,
|
||||
testing::Values(CallbackTestParams(SolverType::kXpress,
|
||||
/*integer_variables=*/false,
|
||||
/*add_lazy_constraints=*/false,
|
||||
/*add_cuts=*/false,
|
||||
/*supported_events=*/{},
|
||||
/*all_solutions=*/std::nullopt,
|
||||
/*reaches_cut_callback*/ std::nullopt)));
|
||||
testing::ValuesIn(
|
||||
{CallbackTestParams(SolverType::kXpress,
|
||||
/*integer_variables=*/false,
|
||||
/*add_lazy_constraints=*/false,
|
||||
/*add_cuts=*/false,
|
||||
/*supported_events=*/{},
|
||||
/*all_solutions=*/std::nullopt,
|
||||
/*reaches_cut_callback*/ std::nullopt),
|
||||
CallbackTestParams(SolverType::kXpress,
|
||||
/*integer_variables=*/true,
|
||||
/*add_lazy_constraints=*/false,
|
||||
/*add_cuts=*/false,
|
||||
/*supported_events=*/{},
|
||||
/*all_solutions=*/std::nullopt,
|
||||
/*reaches_cut_callback*/ std::nullopt)}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressInvalidInputTest, InvalidInputTest,
|
||||
testing::Values(InvalidInputTestParameters(
|
||||
SolverType::kXpress,
|
||||
/*use_integer_variables=*/false)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressInvalidInputTest, InvalidInputTest,
|
||||
testing::ValuesIn(
|
||||
{InvalidInputTestParameters(SolverType::kXpress,
|
||||
/*use_integer_variables=*/true),
|
||||
InvalidInputTestParameters(SolverType::kXpress,
|
||||
/*use_integer_variables=*/false)}));
|
||||
|
||||
InvalidParameterTestParams InvalidThreadsParameters() {
|
||||
InvalidParameterTestParams InvalidObjectiveLimitParameters() {
|
||||
SolveParameters params;
|
||||
params.threads = 2;
|
||||
return InvalidParameterTestParams(SolverType::kXpress, std::move(params),
|
||||
{"only supports parameters.threads = 1"});
|
||||
params.objective_limit = 1.5;
|
||||
return InvalidParameterTestParams(
|
||||
SolverType::kXpress, std::move(params),
|
||||
{"XpressSolver does not support objective_limit"});
|
||||
}
|
||||
|
||||
InvalidParameterTestParams InvalidBestBoundLimitParameters() {
|
||||
SolveParameters params;
|
||||
params.best_bound_limit = 1.5;
|
||||
return InvalidParameterTestParams(
|
||||
SolverType::kXpress, std::move(params),
|
||||
{"XpressSolver does not support best_bound_limit"});
|
||||
}
|
||||
|
||||
InvalidParameterTestParams InvalidSolutionPoolSizeParameters() {
|
||||
SolveParameters params;
|
||||
params.solution_pool_size = 2;
|
||||
return InvalidParameterTestParams(
|
||||
SolverType::kXpress, std::move(params),
|
||||
{"XpressSolver does not support solution_pool_size"});
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressInvalidParameterTest, InvalidParameterTest,
|
||||
ValuesIn({InvalidThreadsParameters()}));
|
||||
ValuesIn({InvalidObjectiveLimitParameters(),
|
||||
InvalidBestBoundLimitParameters(),
|
||||
InvalidSolutionPoolSizeParameters()}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressGenericTest, GenericTest,
|
||||
testing::Values(GenericTestParameters(
|
||||
SolverType::kXpress, /*support_interrupter=*/false,
|
||||
/*integer_variables=*/false,
|
||||
/*expected_log=*/"Optimal solution found")));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
XpressGenericTest, GenericTest,
|
||||
testing::ValuesIn(
|
||||
{GenericTestParameters(SolverType::kXpress,
|
||||
/*support_interrupter=*/true,
|
||||
/*integer_variables=*/false,
|
||||
/*expected_log=*/OPTIMAL_SOLUTION_FOUND_LP),
|
||||
GenericTestParameters(SolverType::kXpress,
|
||||
/*support_interrupter=*/true,
|
||||
/*integer_variables=*/true,
|
||||
/*expected_log=*/OPTIMAL_SOLUTION_FOUND_MIP)}));
|
||||
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TimeLimitTest);
|
||||
|
||||
@@ -142,23 +199,93 @@ INSTANTIATE_TEST_SUITE_P(XpressInfeasibleSubsystemTest, InfeasibleSubsystemTest,
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IpModelSolveParametersTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IpParameterTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LargeInstanceIpParameterTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SimpleMipTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(IncrementalMipTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipSolutionHintTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BranchPrioritiesTest);
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LazyConstraintsTest);
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressSimpleMipTest, SimpleMipTest,
|
||||
testing::Values(SolverType::kXpress));
|
||||
|
||||
SolveParameters GetXpressSingleHintParams() {
|
||||
// Parameters to stop on the first solution that is created from extending
|
||||
// the solution hints.
|
||||
SolveParameters params;
|
||||
params.solution_limit = 1;
|
||||
params.xpress.param_values["PRESOLVE"] = "0";
|
||||
params.xpress.param_values["HEUREMPHASIS"] = "0";
|
||||
return params;
|
||||
}
|
||||
SolveParameters GetXpressTwoHintParams() {
|
||||
// Parameters to stop on the second solution that is created from extending
|
||||
// the solution hints.
|
||||
SolveParameters params;
|
||||
params.solution_limit = 2;
|
||||
params.xpress.param_values["PRESOLVE"] = "0";
|
||||
params.xpress.param_values["HEUREMPHASIS"] = "0";
|
||||
return params;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressMipSolutionHintTest, MipSolutionHintTest,
|
||||
testing::Values(SolutionHintTestParams(
|
||||
SolverType::kXpress, GetXpressSingleHintParams(),
|
||||
GetXpressTwoHintParams(),
|
||||
"User solution (.*) stored")));
|
||||
|
||||
SolveParameters GetXpressLazyConstraintsParams() {
|
||||
SolveParameters params;
|
||||
// Disable heuristics since they may interfere with expected results.
|
||||
params.xpress.param_values["HEUREMPHASIS"] = "0";
|
||||
params.xpress.param_values["PRESOLVE"] = "0";
|
||||
params.xpress.param_values["CUTSTRATEGY"] = "0";
|
||||
// Without STOP_AFTER_LP Xpress will not stop right after the relaxation
|
||||
// but will start the cut loop and inject the lazy constraints, which is
|
||||
// unexpected in .
|
||||
// On the other hand, these parameters are also used for test
|
||||
// LazyConstraintsTest.AnnotationsAreClearedAfterSolve/0 which then fails
|
||||
// because that test expects to finish of the root node. Therefore that
|
||||
// test is disabled.
|
||||
params.xpress.param_values["STOP_AFTER_LP"] = "1";
|
||||
return params;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressLazyConstraintsTest, LazyConstraintsTest,
|
||||
testing::Values(LazyConstraintsTestParams(
|
||||
SolverType::kXpress,
|
||||
GetXpressLazyConstraintsParams())));
|
||||
|
||||
SolveParameters GetXpressBranchPrioritiesParams() {
|
||||
SolveParameters params;
|
||||
// Disable anything that is different from plain branch & bound
|
||||
params.xpress.param_values["HEUREMPHASIS"] = "0";
|
||||
params.xpress.param_values["PRESOLVE"] = "0";
|
||||
params.xpress.param_values["CUTSTRATEGY"] = "0";
|
||||
params.xpress.param_values["NODEPROBINGEFFORT"] = "0.0";
|
||||
// For BranchPrioritiesTest.PrioritiesClearedAfterIncrementalSolve,
|
||||
// otherwise we attempt to set branching priorities on a problem in
|
||||
// presolved state, which is not allowed.
|
||||
params.xpress.param_values["FORCE_POSTSOLVE"] = "1";
|
||||
return params;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(XpressBranchPrioritiesTest, BranchPrioritiesTest,
|
||||
testing::Values(BranchPrioritiesTestParams(
|
||||
SolverType::kXpress,
|
||||
GetXpressBranchPrioritiesParams())));
|
||||
|
||||
LogicalConstraintTestParameters GetXpressLogicalConstraintTestParameters() {
|
||||
return LogicalConstraintTestParameters(
|
||||
SolverType::kXpress, SolveParameters(),
|
||||
/*supports_integer_variables=*/false,
|
||||
/*supports_integer_variables=*/true,
|
||||
// Note: Xpress supports SOS, but it only supports SOSs that comprise
|
||||
// solely of variables (not expressions) and it does not support
|
||||
// duplicate entries. Many of the SOS tests construct things
|
||||
// like this, so we skip them.
|
||||
/*supports_sos1=*/false,
|
||||
/*supports_sos2=*/false,
|
||||
/*supports_indicator_constraints=*/false,
|
||||
/*supports_indicator_constraints=*/true,
|
||||
/*supports_incremental_add_and_deletes=*/false,
|
||||
/*supports_incremental_variable_deletions=*/false,
|
||||
/*supports_deleting_indicator_variables=*/false,
|
||||
/*supports_updating_binary_variables=*/false);
|
||||
/*supports_updating_binary_variables=*/false,
|
||||
/*supports_sos_on_expressions=*/false);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@@ -171,10 +298,10 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
MultiObjectiveTestParameters GetXpressMultiObjectiveTestParameters() {
|
||||
return MultiObjectiveTestParameters(
|
||||
/*solver_type=*/SolverType::kXpress, /*parameters=*/SolveParameters(),
|
||||
/*supports_auxiliary_objectives=*/false,
|
||||
/*supports_auxiliary_objectives=*/true,
|
||||
/*supports_incremental_objective_add_and_delete=*/false,
|
||||
/*supports_incremental_objective_modification=*/false,
|
||||
/*supports_integer_variables=*/false);
|
||||
/*supports_integer_variables=*/true);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@@ -185,37 +312,48 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
XpressIncrementalMultiObjectiveTest, IncrementalMultiObjectiveTest,
|
||||
testing::Values(GetXpressMultiObjectiveTestParameters()));
|
||||
|
||||
QpTestParameters GetXpressQpTestParameters() {
|
||||
return QpTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*qp_support=*/QpSupportType::kConvexQp,
|
||||
/*supports_incrementalism_not_modifying_qp=*/false,
|
||||
/*supports_qp_incrementalism=*/false,
|
||||
/*use_integer_variables=*/false);
|
||||
std::vector<QpTestParameters> GetXpressQpTestParameters() {
|
||||
// TODO: Xpress also supports non-convex QP.
|
||||
return {QpTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*qp_support=*/QpSupportType::kConvexQp,
|
||||
/*supports_incrementalism_not_modifying_qp=*/false,
|
||||
/*supports_qp_incrementalism=*/false,
|
||||
/*use_integer_variables=*/true),
|
||||
QpTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*qp_support=*/QpSupportType::kConvexQp,
|
||||
/*supports_incrementalism_not_modifying_qp=*/false,
|
||||
/*supports_qp_incrementalism=*/false,
|
||||
/*use_integer_variables=*/false)};
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(XpressSimpleQpTest, SimpleQpTest,
|
||||
testing::Values(GetXpressQpTestParameters()));
|
||||
testing::ValuesIn(GetXpressQpTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(XpressIncrementalQpTest, IncrementalQpTest,
|
||||
testing::Values(GetXpressQpTestParameters()));
|
||||
testing::ValuesIn(GetXpressQpTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(XpressQpDualsTest, QpDualsTest,
|
||||
testing::Values(GetXpressQpTestParameters()));
|
||||
testing::ValuesIn(GetXpressQpTestParameters()));
|
||||
|
||||
QcTestParameters GetXpressQcTestParameters() {
|
||||
return QcTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*supports_qc=*/false,
|
||||
/*supports_incremental_add_and_deletes=*/false,
|
||||
/*supports_incremental_variable_deletions=*/false,
|
||||
/*use_integer_variables=*/false);
|
||||
std::vector<QcTestParameters> GetXpressQcTestParameters() {
|
||||
return {QcTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*supports_qc=*/true,
|
||||
/*supports_incremental_add_and_deletes=*/false,
|
||||
/*supports_incremental_variable_deletions=*/false,
|
||||
/*use_integer_variables=*/true),
|
||||
QcTestParameters(SolverType::kXpress, SolveParameters(),
|
||||
/*supports_qc=*/true,
|
||||
/*supports_incremental_add_and_deletes=*/false,
|
||||
/*supports_incremental_variable_deletions=*/false,
|
||||
/*use_integer_variables=*/false)};
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(XpressSimpleQcTest, SimpleQcTest,
|
||||
testing::Values(GetXpressQcTestParameters()));
|
||||
testing::ValuesIn(GetXpressQcTestParameters()));
|
||||
INSTANTIATE_TEST_SUITE_P(XpressIncrementalQcTest, IncrementalQcTest,
|
||||
testing::Values(GetXpressQcTestParameters()));
|
||||
testing::ValuesIn(GetXpressQcTestParameters()));
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(QcDualsTest);
|
||||
|
||||
SecondOrderConeTestParameters GetXpressSecondOrderConeTestParameters() {
|
||||
return SecondOrderConeTestParameters(
|
||||
SolverType::kXpress, SolveParameters(),
|
||||
/*supports_soc_constraints=*/false,
|
||||
/*supports_soc_constraints=*/true,
|
||||
/*supports_incremental_add_and_deletes=*/false);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
@@ -234,11 +372,20 @@ std::vector<StatusTestParameters> MakeStatusTestConfigs() {
|
||||
test_parameters.push_back(StatusTestParameters(
|
||||
SolverType::kXpress, solve_parameters,
|
||||
/*disallow_primal_or_dual_infeasible=*/false,
|
||||
/*supports_iteration_limit=*/false,
|
||||
/*supports_iteration_limit=*/true,
|
||||
/*use_integer_variables=*/false,
|
||||
/*supports_node_limit=*/false,
|
||||
/*support_interrupter=*/false, /*supports_one_thread=*/true));
|
||||
/*supports_node_limit=*/true,
|
||||
/*support_interrupter=*/true, /*supports_one_thread=*/true));
|
||||
}
|
||||
// Add a test with default LP algorithm and integer variables
|
||||
SolveParameters solve_parameters = {.lp_algorithm = std::nullopt};
|
||||
test_parameters.push_back(StatusTestParameters(
|
||||
SolverType::kXpress, solve_parameters,
|
||||
/*disallow_primal_or_dual_infeasible=*/false,
|
||||
/*supports_iteration_limit=*/true,
|
||||
/*use_integer_variables=*/true,
|
||||
/*supports_node_limit=*/true,
|
||||
/*support_interrupter=*/true, /*supports_one_thread=*/true));
|
||||
return test_parameters;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user