Fix math_opt build
This commit is contained in:
@@ -35,27 +35,41 @@ namespace absl {
|
||||
} else /* NOLINT */ \
|
||||
return ::util::StatusBuilder(status_macro_internal_adaptor)
|
||||
|
||||
// Internal helper for concatenating macro values.
|
||||
#define STATUS_MACROS_CONCAT_NAME_INNER(x, y) x##y
|
||||
#define STATUS_MACROS_CONCAT_NAME(x, y) STATUS_MACROS_CONCAT_NAME_INNER(x, y)
|
||||
|
||||
#define ASSIGN_OR_RETURN_IMPL(statusor, lhs, rexpr) \
|
||||
auto statusor = (rexpr); \
|
||||
RETURN_IF_ERROR(statusor.status()); \
|
||||
lhs = *std::move(statusor)
|
||||
|
||||
// Executes an expression that returns an absl::StatusOr, extracting its value
|
||||
// into the variable defined by lhs (or returning on error).
|
||||
//
|
||||
// Example: Assigning to an existing value
|
||||
// ValueType value;
|
||||
// ASSIGN_OR_RETURN(value, MaybeGetValue(arg));
|
||||
// ASSIGN_OR_RETURN((auto [key, val]), MaybeGetValue(arg));
|
||||
//
|
||||
// WARNING: ASSIGN_OR_RETURN expands into multiple statements; it cannot be used
|
||||
// in a single statement (e.g. as the body of an if statement without {})!
|
||||
#define ASSIGN_OR_RETURN(lhs, rexpr) \
|
||||
ASSIGN_OR_RETURN_IMPL( \
|
||||
STATUS_MACROS_CONCAT_NAME(_status_or_value, __COUNTER__), lhs, rexpr);
|
||||
#define ASSIGN_OR_RETURN(lhs, rexpr) \
|
||||
STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \
|
||||
STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __COUNTER__), lhs, rexpr);
|
||||
|
||||
#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_(statusor, lhs, rexpr) \
|
||||
auto statusor = (rexpr); \
|
||||
RETURN_IF_ERROR(statusor.status()); \
|
||||
STATUS_MACROS_IMPL_UNPARENTHESIS(lhs) = std::move(statusor).value()
|
||||
|
||||
// Internal helpers for macro expansion.
|
||||
#define STATUS_MACROS_IMPL_UNPARENTHESIS_INNER(...) \
|
||||
STATUS_MACROS_IMPL_UNPARENTHESIS_INNER_(__VA_ARGS__)
|
||||
#define STATUS_MACROS_IMPL_UNPARENTHESIS_INNER_(...) \
|
||||
STATUS_MACROS_IMPL_VAN##__VA_ARGS__
|
||||
#define ISH(...) ISH __VA_ARGS__
|
||||
#define STATUS_MACROS_IMPL_VANISH
|
||||
|
||||
// If the input is parenthesized, removes the parentheses. Otherwise expands to
|
||||
// the input unchanged.
|
||||
#define STATUS_MACROS_IMPL_UNPARENTHESIS(...) \
|
||||
STATUS_MACROS_IMPL_UNPARENTHESIS_INNER(ISH __VA_ARGS__)
|
||||
|
||||
// Internal helper for concatenating macro values.
|
||||
#define STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y
|
||||
#define STATUS_MACROS_IMPL_CONCAT_(x, y) STATUS_MACROS_IMPL_CONCAT_INNER_(x, y)
|
||||
|
||||
} // namespace absl
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ cc_library(
|
||||
"//ortools/math_opt/validators:model_parameters_validator",
|
||||
"//ortools/math_opt/validators:model_validator",
|
||||
"//ortools/math_opt/validators:result_validator",
|
||||
"//ortools/math_opt/validators:solver_parameters_validator",
|
||||
"//ortools/math_opt/validators:solve_parameters_validator",
|
||||
"//ortools/port:proto_utils",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/status",
|
||||
|
||||
@@ -36,8 +36,6 @@ namespace math_opt {
|
||||
// * Ids must be unique and increasing (in insertion order).
|
||||
// * Ids are non-negative.
|
||||
// * Ids are not equal to std::numeric_limits<int64_t>::max()
|
||||
// TODO(b/213918209): make sure this is enforced in validators or remove this
|
||||
// restriction.
|
||||
// * Ids removed are never reused.
|
||||
// * Names must be either empty or unique.
|
||||
class IdNameBiMap {
|
||||
@@ -104,10 +102,6 @@ struct ModelSummary {
|
||||
|
||||
void IdNameBiMap::Insert(const int64_t id, std::string name) {
|
||||
CHECK_GE(id, next_free_id_);
|
||||
// TODO(b/213918209): this is not mandatory for a valid model at this point so
|
||||
// this is a bit incorrect. The correct thing to do would be to have an
|
||||
// optional<int64_t> for the next_free_id_ and forbid any new id when we reach
|
||||
// the max but this may be overkill.
|
||||
CHECK_LT(id, std::numeric_limits<int64_t>::max());
|
||||
next_free_id_ = id + 1;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#include "ortools/math_opt/validators/model_parameters_validator.h"
|
||||
#include "ortools/math_opt/validators/model_validator.h"
|
||||
#include "ortools/math_opt/validators/result_validator.h"
|
||||
#include "ortools/math_opt/validators/solver_parameters_validator.h"
|
||||
#include "ortools/math_opt/validators/solve_parameters_validator.h"
|
||||
#include "ortools/port/proto_utils.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
|
||||
@@ -196,7 +196,7 @@ absl::StatusOr<SolveResultProto> Solver::Solve(const SolveArgs& arguments) {
|
||||
// can be filtered, this should be included in the solver_interface
|
||||
// implementations.
|
||||
|
||||
RETURN_IF_ERROR(ValidateSolverParameters(arguments.parameters))
|
||||
RETURN_IF_ERROR(ValidateSolveParameters(arguments.parameters))
|
||||
<< "invalid parameters";
|
||||
RETURN_IF_ERROR(
|
||||
ValidateModelSolveParameters(arguments.model_parameters, model_summary_))
|
||||
|
||||
@@ -242,7 +242,6 @@ cc_library(
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
#"@com_google_absl//absl/types:source_location",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -269,8 +268,6 @@ cc_library(
|
||||
"//ortools/gscip:gscip_cc_proto",
|
||||
"//ortools/math_opt:parameters_cc_proto",
|
||||
"//ortools/math_opt/solvers:gurobi_cc_proto",
|
||||
#"//ortools/math_opt/solvers:osqp_settings_cc_proto",
|
||||
#"//ortools/pdlp:solvers_cc_proto",
|
||||
"//ortools/port:proto_utils",
|
||||
"//ortools/sat:sat_parameters_cc_proto",
|
||||
"@com_google_absl//absl/status",
|
||||
|
||||
@@ -224,15 +224,37 @@ std::optional<E> EnumFromString(const absl::string_view str);
|
||||
//
|
||||
// It calls EnumToOptString(), printing the returned value if not nullopt. When
|
||||
// nullopt it prints the enum numeric value instead.
|
||||
template <typename E, typename>
|
||||
std::ostream& operator<<(std::ostream& out, const E value);
|
||||
template <typename E,
|
||||
// We must use enable_if here to prevent this overload to be selected
|
||||
// for other types than ones that implement Enum<E>.
|
||||
typename = std::enable_if_t<Enum<E>::kIsImplemented>>
|
||||
std::ostream& operator<<(std::ostream& out, const E value) {
|
||||
const std::optional<absl::string_view> opt_str = EnumToOptString(value);
|
||||
if (opt_str.has_value()) {
|
||||
out << *opt_str;
|
||||
} else {
|
||||
out << "<invalid enum (" << static_cast<std::underlying_type_t<E>>(value)
|
||||
<< ")>";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Overload of operator<< for std::optional<E> when Enum<E> is implemented.
|
||||
//
|
||||
// When the value is nullopt, it prints "<unspecified>", else it prints the enum
|
||||
// value.
|
||||
template <typename E, typename>
|
||||
std::ostream& operator<<(std::ostream& out, const std::optional<E> value);
|
||||
template <typename E,
|
||||
// We must use enable_if here to prevent this overload to be selected
|
||||
// for other types than ones that implement Enum<E>.
|
||||
typename = std::enable_if_t<Enum<E>::kIsImplemented>>
|
||||
std::ostream& operator<<(std::ostream& out, const std::optional<E> opt_value) {
|
||||
if (opt_value.has_value()) {
|
||||
out << *opt_value;
|
||||
} else {
|
||||
out << "<unspecified>";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Template functions implementations after this point.
|
||||
@@ -280,34 +302,6 @@ std::optional<E> EnumFromString(const absl::string_view str) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename E,
|
||||
// We must use enable_if here to prevent this overload to be selected
|
||||
// for other types than ones that implement Enum<E>.
|
||||
typename = std::enable_if_t<Enum<E>::kIsImplemented>>
|
||||
std::ostream& operator<<(std::ostream& out, const E value) {
|
||||
const std::optional<absl::string_view> opt_str = EnumToOptString(value);
|
||||
if (opt_str.has_value()) {
|
||||
out << *opt_str;
|
||||
} else {
|
||||
out << "<invalid enum (" << static_cast<std::underlying_type_t<E>>(value)
|
||||
<< ")>";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename E,
|
||||
// We must use enable_if here to prevent this overload to be selected
|
||||
// for other types than ones that implement Enum<E>.
|
||||
typename = std::enable_if_t<Enum<E>::kIsImplemented>>
|
||||
std::ostream& operator<<(std::ostream& out, const std::optional<E> opt_value) {
|
||||
if (opt_value.has_value()) {
|
||||
out << *opt_value;
|
||||
} else {
|
||||
out << "<unspecified>";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Macros that defines the templates specializations for Enum and EnumProto.
|
||||
//
|
||||
// The CppEnum parameter is the name of the C++ enum class which values are the
|
||||
|
||||
@@ -157,6 +157,18 @@ SolveParametersProto SolveParameters::Proto() const {
|
||||
if (iteration_limit.has_value()) {
|
||||
result.set_iteration_limit(*iteration_limit);
|
||||
}
|
||||
if (cutoff_limit.has_value()) {
|
||||
result.set_cutoff_limit(*cutoff_limit);
|
||||
}
|
||||
if (objective_limit.has_value()) {
|
||||
result.set_objective_limit(*objective_limit);
|
||||
}
|
||||
if (best_bound_limit.has_value()) {
|
||||
result.set_best_bound_limit(*best_bound_limit);
|
||||
}
|
||||
if (solution_limit.has_value()) {
|
||||
result.set_solution_limit(*solution_limit);
|
||||
}
|
||||
if (threads.has_value()) {
|
||||
result.set_threads(*threads);
|
||||
}
|
||||
@@ -195,6 +207,18 @@ absl::StatusOr<SolveParameters> SolveParameters::FromProto(
|
||||
if (proto.has_iteration_limit()) {
|
||||
result.iteration_limit = proto.iteration_limit();
|
||||
}
|
||||
if (proto.has_cutoff_limit()) {
|
||||
result.cutoff_limit = proto.cutoff_limit();
|
||||
}
|
||||
if (proto.has_objective_limit()) {
|
||||
result.objective_limit = proto.objective_limit();
|
||||
}
|
||||
if (proto.has_best_bound_limit()) {
|
||||
result.best_bound_limit = proto.best_bound_limit();
|
||||
}
|
||||
if (proto.has_solution_limit()) {
|
||||
result.solution_limit = proto.solution_limit();
|
||||
}
|
||||
if (proto.has_threads()) {
|
||||
result.threads = proto.threads();
|
||||
}
|
||||
|
||||
@@ -211,6 +211,41 @@ struct SolveParameters {
|
||||
std::optional<double> relative_gap_limit;
|
||||
std::optional<double> absolute_gap_limit;
|
||||
|
||||
// The solver stops early if it can prove there are no primal solutions at
|
||||
// least as good as cutoff.
|
||||
//
|
||||
// On an early stop, the solver returns termination reason kLimitReached and
|
||||
// with limit kCutoff and is not required to give any extra solution
|
||||
// information. Has no effect on the return value if there is no early stop.
|
||||
//
|
||||
// It is recommended that you use a tolerance if you want solutions with
|
||||
// objective exactly equal to cutoff to be returned.
|
||||
//
|
||||
// See the user guide for more details and a comparison with best_bound_limit.
|
||||
std::optional<double> cutoff_limit;
|
||||
|
||||
// The solver stops early as soon as it finds a solution at least this good,
|
||||
// with termination reason kLimitReached and limit kObjective.
|
||||
std::optional<double> objective_limit;
|
||||
|
||||
// The solver stops early as soon as it proves the best bound is at least this
|
||||
// good, with termination reason kLimitReached and limit kObjective.
|
||||
//
|
||||
// See the user guide for a comparison with cutoff_limit.
|
||||
std::optional<double> best_bound_limit;
|
||||
|
||||
// The solver stops early after finding this many feasible solutions, with
|
||||
// termination reason kLimitReached and limit kSolution. Must be greater than
|
||||
// zero if set. It is often used get the solver to stop on the first feasible
|
||||
// solution found. Note that there is no guarantee on the objective value for
|
||||
// any of the returned solutions.
|
||||
//
|
||||
// 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.
|
||||
std::optional<int32_t> solution_limit;
|
||||
|
||||
// If unset, use the solver default. If set, it must be >= 1.
|
||||
std::optional<int32_t> threads;
|
||||
|
||||
|
||||
@@ -228,23 +228,6 @@ MessageCallback PrinterMessageCallback(std::ostream& output_stream,
|
||||
[=](const std::vector<std::string>& messages) { impl->Call(messages); };
|
||||
}
|
||||
|
||||
MessageCallback InfoLoggerMessageCallback(const absl::string_view prefix,
|
||||
const absl::SourceLocation loc) {
|
||||
return [=](const std::vector<std::string>& messages) {
|
||||
for (const std::string& message : messages) {
|
||||
LOG(INFO).AtLocation(loc) << prefix << message;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
MessageCallback VLoggerMessageCallback(int level, absl::string_view prefix,
|
||||
absl::SourceLocation loc) {
|
||||
return [=](const std::vector<std::string>& messages) {
|
||||
for (const std::string& message : messages) {
|
||||
VLOG(level).AtLocation(loc) << prefix << message;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
@@ -68,27 +68,6 @@ using MessageCallback = std::function<void(const std::vector<std::string>&)>;
|
||||
MessageCallback PrinterMessageCallback(std::ostream& output_stream = std::cout,
|
||||
absl::string_view prefix = "");
|
||||
|
||||
// Returns a message callback function that prints each line to LOG(INFO),
|
||||
// prefixing each line with the given prefix.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// SolveArguments args;
|
||||
// args.message_callback = InfoLoggerMessageCallback("[solver] ");
|
||||
MessageCallback InfoLoggerMessageCallback(
|
||||
absl::string_view prefix = "",
|
||||
absl::SourceLocation loc = absl::SourceLocation::current());
|
||||
|
||||
// Returns a message callback function that prints each line to VLOG(level),
|
||||
// prefixing each line with the given prefix.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// SolveArguments args;
|
||||
// args.message_callback = VLoggerMessageCallback(1, "[solver] ");
|
||||
MessageCallback VLoggerMessageCallback(
|
||||
int level, absl::string_view prefix = "",
|
||||
absl::SourceLocation loc = absl::SourceLocation::current());
|
||||
|
||||
// Arguments passed to Solve() and IncrementalSolver::New() to control the
|
||||
// instantiation of the solver.
|
||||
|
||||
@@ -124,6 +124,8 @@ std::optional<absl::string_view> Enum<Limit>::ToOptString(Limit value) {
|
||||
return "solution";
|
||||
case Limit::kMemory:
|
||||
return "memory";
|
||||
case Limit::kCutoff:
|
||||
return "cutoff";
|
||||
case Limit::kObjective:
|
||||
return "objective";
|
||||
case Limit::kNorm:
|
||||
@@ -140,10 +142,10 @@ std::optional<absl::string_view> Enum<Limit>::ToOptString(Limit value) {
|
||||
|
||||
absl::Span<const Limit> Enum<Limit>::AllValues() {
|
||||
static constexpr Limit kLimitValues[] = {
|
||||
Limit::kUndetermined, Limit::kIteration, Limit::kTime,
|
||||
Limit::kNode, Limit::kSolution, Limit::kMemory,
|
||||
Limit::kObjective, Limit::kNorm, Limit::kInterrupted,
|
||||
Limit::kSlowProgress, Limit::kOther};
|
||||
Limit::kUndetermined, Limit::kIteration, Limit::kTime,
|
||||
Limit::kNode, Limit::kSolution, Limit::kMemory,
|
||||
Limit::kCutoff, Limit::kObjective, Limit::kNorm,
|
||||
Limit::kInterrupted, Limit::kSlowProgress, Limit::kOther};
|
||||
return absl::MakeConstSpan(kLimitValues);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,13 @@ enum class Limit {
|
||||
// The algorithm stopped because it ran out of memory.
|
||||
kMemory = LIMIT_MEMORY,
|
||||
|
||||
// The solver was run with a cutoff (e.g. SolveParameters.cutoff_limit was
|
||||
// set) on the objective, indicating that the user did not want any solution
|
||||
// worse than the cutoff, and the solver concluded there were no solutions at
|
||||
// least as good as the cutoff. Typically no further solution information is
|
||||
// provided.
|
||||
kCutoff = LIMIT_CUTOFF,
|
||||
|
||||
// The algorithm stopped because it found a solution better than a minimum
|
||||
// limit set by the user.
|
||||
kObjective = LIMIT_OBJECTIVE,
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
|
||||
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
||||
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/base/logging.h"
|
||||
#include "absl/base/attributes.h"
|
||||
#include "ortools/base/map_util.h"
|
||||
#include "ortools/base/int_type.h"
|
||||
#include "ortools/math_opt/core/model_storage.h"
|
||||
#include "ortools/math_opt/cpp/key_types.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
#ifdef MATH_OPT_USE_EXPRESSION_COUNTERS
|
||||
LinearExpression::LinearExpression() { ++num_calls_default_constructor_; }
|
||||
|
||||
@@ -91,36 +91,90 @@ double LinearExpression::EvaluateWithDefaultZero(
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Streaming formatter for a coefficient of a linear/quadratic term, along with
|
||||
// any leading "+"/"-"'s to connect it with preceding terms in a sum, and
|
||||
// potentially a "*" postfix. The `is_first` parameter specifies if the term is
|
||||
// the first appearing in the sum, in which case the handling of the +/-
|
||||
// connectors is different.
|
||||
struct LeadingCoefficientFormatter {
|
||||
LeadingCoefficientFormatter(const double coeff, const bool is_first)
|
||||
: coeff(coeff), is_first(is_first) {}
|
||||
const double coeff;
|
||||
const bool is_first;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out,
|
||||
const LeadingCoefficientFormatter formatter) {
|
||||
const double coeff = formatter.coeff;
|
||||
if (formatter.is_first) {
|
||||
if (coeff == 1.0) {
|
||||
// Do nothing.
|
||||
} else if (coeff == -1.0) {
|
||||
out << "-";
|
||||
} else {
|
||||
out << coeff << "*";
|
||||
}
|
||||
} else {
|
||||
if (coeff == 1.0) {
|
||||
out << " + ";
|
||||
} else if (coeff == -1.0) {
|
||||
out << " - ";
|
||||
} else if (std::isnan(coeff)) {
|
||||
out << " + nan*";
|
||||
} else if (coeff >= 0) {
|
||||
out << " + " << coeff << "*";
|
||||
} else {
|
||||
out << " - " << -coeff << "*";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Streaming formatter for a constant in a linear/quadratic expression.
|
||||
struct ConstantFormatter {
|
||||
ConstantFormatter(const double constant, const bool is_first)
|
||||
: constant(constant), is_first(is_first) {}
|
||||
const double constant;
|
||||
const bool is_first;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const ConstantFormatter formatter) {
|
||||
const double constant = formatter.constant;
|
||||
if (formatter.is_first) {
|
||||
out << constant;
|
||||
} else if (constant == 0) {
|
||||
// Do nothing.
|
||||
} else if (std::isnan(constant)) {
|
||||
out << " + nan";
|
||||
} else if (constant > 0) {
|
||||
out << " + " << constant;
|
||||
} else {
|
||||
out << " - " << -constant;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr,
|
||||
const LinearExpression& expression) {
|
||||
// TODO(b/169415597): improve linear expression format:
|
||||
// - use bijective formatting in base10 of the double factors.
|
||||
// - handle negative coefficients, replacing `... + -3*x ` by `... - 3*x`.
|
||||
// - make sure to quote the variable name so that we support:
|
||||
// * variable names contains +, -, ...
|
||||
// * variable names resembling anonymous variable names.
|
||||
const std::vector<Variable> sorted_variables = expression.terms_.SortedKeys();
|
||||
bool first = true;
|
||||
for (const auto v : sorted_variables) {
|
||||
if (first) {
|
||||
const double coeff = expression.terms_.at(v);
|
||||
if (coeff != 0) {
|
||||
ostr << LeadingCoefficientFormatter(coeff, first) << v;
|
||||
first = false;
|
||||
} else {
|
||||
ostr << " + ";
|
||||
}
|
||||
ostr << expression.terms_.at(v) << "*";
|
||||
const std::string& name =
|
||||
expression.terms_.storage()->variable_name(v.typed_id());
|
||||
if (name.empty()) {
|
||||
ostr << "[" << v << "]";
|
||||
} else {
|
||||
ostr << name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
ostr << " + ";
|
||||
}
|
||||
ostr << expression.offset();
|
||||
ostr << ConstantFormatter(expression.offset(), first);
|
||||
|
||||
return ostr;
|
||||
}
|
||||
@@ -129,9 +183,17 @@ std::ostream& operator<<(std::ostream& ostr,
|
||||
const BoundedLinearExpression& bounded_expression) {
|
||||
// TODO(b/170991498): use bijective conversion from double to base-10 string
|
||||
// to make sure we can reproduce bugs.
|
||||
ostr << bounded_expression.lower_bound
|
||||
<< " <= " << bounded_expression.expression
|
||||
<< " <= " << bounded_expression.upper_bound;
|
||||
const double lb = bounded_expression.lower_bound;
|
||||
const double ub = bounded_expression.upper_bound;
|
||||
if (lb == ub) {
|
||||
ostr << bounded_expression.expression << " = " << lb;
|
||||
} else if (lb == -kInf) {
|
||||
ostr << bounded_expression.expression << " ≤ " << ub;
|
||||
} else if (ub == kInf) {
|
||||
ostr << bounded_expression.expression << " ≥ " << lb;
|
||||
} else {
|
||||
ostr << lb << " ≤ " << bounded_expression.expression << " ≤ " << ub;
|
||||
}
|
||||
return ostr;
|
||||
}
|
||||
|
||||
@@ -173,15 +235,16 @@ double QuadraticExpression::EvaluateWithDefaultZero(
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr, const QuadraticExpression& expr) {
|
||||
// TODO(b/169415597): improve quadratic expression formatting.
|
||||
// TODO(b/169415597): improve quadratic expression formatting. See b/170991498
|
||||
// for desired improvements for LinearExpression streaming which are also
|
||||
// applicable here.
|
||||
bool first = true;
|
||||
for (const auto v : expr.quadratic_terms().SortedKeys()) {
|
||||
if (first) {
|
||||
const double coeff = expr.quadratic_terms().at(v);
|
||||
if (coeff != 0) {
|
||||
ostr << LeadingCoefficientFormatter(coeff, first);
|
||||
first = false;
|
||||
} else {
|
||||
ostr << " + ";
|
||||
}
|
||||
ostr << expr.quadratic_terms().at(v) << "*";
|
||||
const Variable first_variable(expr.quadratic_terms().storage(),
|
||||
v.typed_id().first);
|
||||
const Variable second_variable(expr.quadratic_terms().storage(),
|
||||
@@ -193,19 +256,13 @@ std::ostream& operator<<(std::ostream& ostr, const QuadraticExpression& expr) {
|
||||
}
|
||||
}
|
||||
for (const auto v : expr.linear_terms().SortedKeys()) {
|
||||
if (first) {
|
||||
const double coeff = expr.linear_terms().at(v);
|
||||
if (coeff != 0) {
|
||||
ostr << LeadingCoefficientFormatter(coeff, first) << v;
|
||||
first = false;
|
||||
} else {
|
||||
ostr << " + ";
|
||||
}
|
||||
ostr << expr.linear_terms().at(v) << "*" << v;
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
ostr << " + ";
|
||||
}
|
||||
ostr << expr.offset();
|
||||
|
||||
ostr << ConstantFormatter(expr.offset(), first);
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
@@ -910,9 +910,13 @@ H AbslHashValue(H h, const Variable& variable) {
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& ostr, const Variable& variable) {
|
||||
// TODO(b/170992529): handle the case of empty variable name and quoting when
|
||||
// the variable name contains invalid characters.
|
||||
ostr << variable.name();
|
||||
// TODO(b/170992529): handle quoting of invalid characters in the name.
|
||||
const std::string& name = variable.name();
|
||||
if (name.empty()) {
|
||||
ostr << "__var#" << variable.id() << "__";
|
||||
} else {
|
||||
ostr << name;
|
||||
}
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ortools/base/integral_types.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
@@ -253,14 +253,16 @@ absl::StatusOr<::operations_research::MPModelProto> MathOptModelToMPModelProto(
|
||||
}
|
||||
const SparseDoubleMatrixProto& origin_qp_terms =
|
||||
model.objective().quadratic_coefficients();
|
||||
MPQuadraticObjective& destination_qp_terms =
|
||||
*output.mutable_quadratic_objective();
|
||||
for (int k = 0; k < origin_qp_terms.coefficients().size(); ++k) {
|
||||
destination_qp_terms.add_qvar1_index(
|
||||
variable_id_to_mp_position[origin_qp_terms.row_ids(k)]);
|
||||
destination_qp_terms.add_qvar2_index(
|
||||
variable_id_to_mp_position[origin_qp_terms.column_ids(k)]);
|
||||
destination_qp_terms.add_coefficient(origin_qp_terms.coefficients(k));
|
||||
if (!origin_qp_terms.coefficients().empty()) {
|
||||
MPQuadraticObjective& destination_qp_terms =
|
||||
*output.mutable_quadratic_objective();
|
||||
for (int k = 0; k < origin_qp_terms.coefficients().size(); ++k) {
|
||||
destination_qp_terms.add_qvar1_index(
|
||||
variable_id_to_mp_position[origin_qp_terms.row_ids(k)]);
|
||||
destination_qp_terms.add_qvar2_index(
|
||||
variable_id_to_mp_position[origin_qp_terms.column_ids(k)]);
|
||||
destination_qp_terms.add_coefficient(origin_qp_terms.coefficients(k));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(user): use the constraint iterator from scip_solver.cc here.
|
||||
|
||||
@@ -23,7 +23,8 @@ option java_multiple_files = true;
|
||||
|
||||
// As used below, we define "#variables" = size(VariablesProto.ids).
|
||||
message VariablesProto {
|
||||
// Must be nonnegative and strictly increasing.
|
||||
// Must be nonnegative and strictly increasing. The max(int64) value can't be
|
||||
// used.
|
||||
repeated int64 ids = 1;
|
||||
// Should have length equal to #variables, values in [-inf, inf).
|
||||
repeated double lower_bounds = 2;
|
||||
@@ -73,7 +74,8 @@ message ObjectiveProto {
|
||||
// As used below, we define "#linear constraints" =
|
||||
// size(LinearConstraintsProto.ids).
|
||||
message LinearConstraintsProto {
|
||||
// Must be nonnegative and strictly increasing.
|
||||
// Must be nonnegative and strictly increasing. The max(int64) value can't be
|
||||
// used.
|
||||
repeated int64 ids = 1;
|
||||
// Should have length equal to #linear constraints, values in [-inf, inf).
|
||||
repeated double lower_bounds = 2;
|
||||
|
||||
@@ -51,6 +51,7 @@ enum SolverTypeProto {
|
||||
// It supports solving IPs and can scale MIPs to solve them as IPs.
|
||||
SOLVER_TYPE_CP_SAT = 4;
|
||||
|
||||
SOLVER_TYPE_RESERVED_FIVE = 5;
|
||||
|
||||
// GNU Linear Programming Kit (GLPK).
|
||||
//
|
||||
@@ -68,6 +69,7 @@ enum SolverTypeProto {
|
||||
// for details.
|
||||
SOLVER_TYPE_GLPK = 6;
|
||||
|
||||
SOLVER_TYPE_RESERVED_SEVEN = 7;
|
||||
}
|
||||
|
||||
// Selects an algorithm for solving linear programs.
|
||||
@@ -162,9 +164,46 @@ message SolveParametersProto {
|
||||
// (e.g. it could be the relative GAP divided by the objective value of the
|
||||
// feasible solution if this is non-zero). Solvers consider a solution optimal
|
||||
// if its GAPs are below these limits (most solvers use both versions).
|
||||
// TODO(b/213603287): rename as relative_gap_tolerance and
|
||||
// absolute_gap_tolerance.
|
||||
optional double relative_gap_limit = 17;
|
||||
optional double absolute_gap_limit = 18;
|
||||
|
||||
// The solver stops early if it can prove there are no primal solutions at
|
||||
// least as good as cutoff.
|
||||
//
|
||||
// On an early stop, the solver returns termination reason LIMIT_REACHED and
|
||||
// with limit CUTOFF and is not required to give any extra solution
|
||||
// information. Has no effect on the return value if there is no early stop.
|
||||
//
|
||||
// It is recommended that you use a tolerance if you want solutions with
|
||||
// objective exactly equal to cutoff to be returned.
|
||||
//
|
||||
// See the user guide for more details and a comparison with best_bound_limit.
|
||||
optional double cutoff_limit = 20;
|
||||
|
||||
// The solver stops early as soon as it finds a solution at least this good,
|
||||
// with termination reason LIMIT_REACHED and limit LIMIT_OBJECTIVE.
|
||||
optional double objective_limit = 21;
|
||||
|
||||
// The solver stops early as soon as it proves the best bound is at least this
|
||||
// good, with termination reason LIMIT_REACHED and limit LIMIT_OBJECTIVE.
|
||||
//
|
||||
// See the user guide for more details and a comparison with cutoff_limit.
|
||||
optional double best_bound_limit = 22;
|
||||
|
||||
// The solver stops early after finding this many feasible solutions, with
|
||||
// termination reason LIMIT_REACHED and limit LIMIT_SOLUTION. Must be greater
|
||||
// than zero if set. It is often used get the solver to stop on the first
|
||||
// feasible solution found. Note that there is no guarantee on the objective
|
||||
// value for any of the returned solutions.
|
||||
//
|
||||
// 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.
|
||||
optional int32 solution_limit = 23;
|
||||
|
||||
// Enables printing the solver implementation traces. The location of those
|
||||
// traces depend on the solver. For SCIP and Gurobi this will be the standard
|
||||
// output streams. For Glop and CP-SAT this will LOG(INFO).
|
||||
|
||||
@@ -187,8 +187,16 @@ enum LimitProto {
|
||||
// The algorithm stopped because it ran out of memory.
|
||||
LIMIT_MEMORY = 6;
|
||||
|
||||
// The algorithm stopped because it found a solution better than a minimum
|
||||
// limit set by the user.
|
||||
// The solver was run with a cutoff (e.g. SolveParameters.cutoff_limit was
|
||||
// set) on the objective, indicating that the user did not want any solution
|
||||
// worse than the cutoff, and the solver concluded there were no solutions at
|
||||
// least as good as the cutoff. Typically no further solution information is
|
||||
// provided.
|
||||
LIMIT_CUTOFF = 12;
|
||||
|
||||
// The algorithm stopped because it either found a solution or a bound better
|
||||
// than a limit set by the user (see SolveParameters.objective_limit and
|
||||
// SolveParameters.best_bound_limit).
|
||||
LIMIT_OBJECTIVE = 7;
|
||||
|
||||
// The algorithm stopped because the norm of an iterate became too large.
|
||||
|
||||
@@ -192,7 +192,7 @@ absl::StatusOr<CuttingStockSolution> SolveCuttingStock(
|
||||
ASSIGN_OR_RETURN(math_opt::SolveResult solve_result, solver->Solve());
|
||||
if (solve_result.termination.reason !=
|
||||
math_opt::TerminationReason::kOptimal) {
|
||||
return absl::InternalErrorBuilder()
|
||||
return util::InternalErrorBuilder()
|
||||
<< "Failed to solve leader LP problem at iteration "
|
||||
<< pricing_round << " termination: " << solve_result.termination;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ absl::StatusOr<CuttingStockSolution> SolveCuttingStock(
|
||||
math_opt::Solve(model, math_opt::SolverType::kCpSat));
|
||||
if (solve_result.termination.reason !=
|
||||
math_opt::TerminationReason::kOptimal) {
|
||||
return absl::InternalErrorBuilder()
|
||||
return util::InternalErrorBuilder()
|
||||
<< "Failed to solve final cutting stock MIP, termination: "
|
||||
<< solve_result.termination;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,27 @@ std::vector<std::string> SetSolveParameters(
|
||||
if (parameters.has_absolute_gap_limit()) {
|
||||
sat_parameters.set_absolute_gap_limit(parameters.absolute_gap_limit());
|
||||
}
|
||||
if (parameters.has_cutoff_limit()) {
|
||||
warnings.push_back(
|
||||
"The cutoff_limit parameter is not supported for CP-SAT.");
|
||||
}
|
||||
if (parameters.has_best_bound_limit()) {
|
||||
warnings.push_back(
|
||||
"The best_bound_limit parameter is not supported for CP-SAT.");
|
||||
}
|
||||
if (parameters.has_objective_limit()) {
|
||||
warnings.push_back(
|
||||
"The objective_limit parameter is not supported for CP-SAT.");
|
||||
}
|
||||
if (parameters.has_solution_limit()) {
|
||||
if (parameters.solution_limit() == 1) {
|
||||
sat_parameters.set_stop_after_first_solution(true);
|
||||
} else {
|
||||
warnings.push_back(absl::StrCat(
|
||||
"The CP-SAT solver only supports value 1 for solution_limit, found: ",
|
||||
parameters.solution_limit()));
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
|
||||
warnings.push_back(
|
||||
|
||||
@@ -363,6 +363,18 @@ GlopSolver::MergeSolveParameters(const SolveParametersProto& solver_parameters,
|
||||
"heuristics was set to: ",
|
||||
ProtoEnumToString(solver_parameters.heuristics())));
|
||||
}
|
||||
if (solver_parameters.has_cutoff_limit()) {
|
||||
warnings.push_back("GLOP does not support 'cutoff_limit' parameter");
|
||||
}
|
||||
if (solver_parameters.has_objective_limit()) {
|
||||
warnings.push_back("GLOP does not support 'objective_limit' parameter");
|
||||
}
|
||||
if (solver_parameters.has_best_bound_limit()) {
|
||||
warnings.push_back("GLOP does not support 'best_bound_limit' parameter");
|
||||
}
|
||||
if (solver_parameters.has_solution_limit()) {
|
||||
warnings.push_back("GLOP does not support 'solution_limit' parameter");
|
||||
}
|
||||
return std::make_pair(std::move(result), std::move(warnings));
|
||||
}
|
||||
|
||||
@@ -762,6 +774,11 @@ absl::StatusOr<std::unique_ptr<SolverInterface>> GlopSolver::New(
|
||||
"Glop does not support quadratic objectives");
|
||||
}
|
||||
auto solver = absl::WrapUnique(new GlopSolver);
|
||||
// By default Glop CHECKs that bounds are always consistent (lb < ub); thus it
|
||||
// would fail if the initial model or later updates temporarily set inverted
|
||||
// bounds.
|
||||
solver->linear_program_.SetDcheckBounds(false);
|
||||
|
||||
solver->linear_program_.SetName(model.name());
|
||||
solver->linear_program_.SetMaximizationProblem(model.objective().maximize());
|
||||
solver->linear_program_.SetObjectiveOffset(model.objective().offset());
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
@@ -69,6 +71,8 @@ namespace math_opt {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
int64_t SafeId(const VariablesProto& variables, int index) {
|
||||
if (variables.ids().empty()) {
|
||||
return index;
|
||||
@@ -398,13 +402,14 @@ GScipParameters::MetaParamValue ConvertMathOptEmphasis(EmphasisProto emphasis) {
|
||||
}
|
||||
}
|
||||
|
||||
GScipParameters GScipSolver::MergeParameters(
|
||||
const SolveParametersProto& solve_parameters) {
|
||||
std::pair<GScipParameters, std::vector<std::string>>
|
||||
GScipSolver::MergeParameters(const SolveParametersProto& solve_parameters) {
|
||||
// First build the result by translating common parameters to a
|
||||
// GScipParameters, and then merging with user provided gscip_parameters.
|
||||
// This results in user provided solver specific parameters overwriting
|
||||
// common parameters should there be any conflict.
|
||||
GScipParameters result;
|
||||
std::vector<std::string> warnings;
|
||||
|
||||
// By default SCIP catches Ctrl-C but we don't want this behavior when the
|
||||
// users uses SCIP through MathOpt.
|
||||
@@ -430,6 +435,22 @@ GScipParameters GScipSolver::MergeParameters(
|
||||
solve_parameters.absolute_gap_limit();
|
||||
}
|
||||
|
||||
if (solve_parameters.has_objective_limit()) {
|
||||
warnings.push_back("parameter objective_limit not supported for gSCIP.");
|
||||
}
|
||||
if (solve_parameters.has_best_bound_limit()) {
|
||||
warnings.push_back("parameter best_bound_limit not supported for gSCIP.");
|
||||
}
|
||||
|
||||
if (solve_parameters.has_cutoff_limit()) {
|
||||
result.set_objective_limit(solve_parameters.cutoff_limit());
|
||||
}
|
||||
|
||||
if (solve_parameters.has_solution_limit()) {
|
||||
(*result.mutable_int_params())["limits/solutions"] =
|
||||
solve_parameters.solution_limit();
|
||||
}
|
||||
|
||||
// GScip has also GScipSetOutputEnabled() but this changes the log
|
||||
// level. Setting `silence_output` sets the `quiet` field on the default
|
||||
// message handler of SCIP which removes the output. Here it is important to
|
||||
@@ -496,7 +517,7 @@ GScipParameters GScipSolver::MergeParameters(
|
||||
|
||||
result.MergeFrom(solve_parameters.gscip());
|
||||
|
||||
return result;
|
||||
return {std::move(result), std::move(warnings)};
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -514,7 +535,8 @@ std::string JoinDetails(const std::string& gscip_detail,
|
||||
|
||||
ProblemStatusProto GetProblemStatusProto(const GScipOutput::Status gscip_status,
|
||||
const bool has_feasible_solution,
|
||||
const bool has_finite_dual_bound) {
|
||||
const bool has_finite_dual_bound,
|
||||
const bool was_cutoff) {
|
||||
ProblemStatusProto problem_status;
|
||||
if (has_feasible_solution) {
|
||||
problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
|
||||
@@ -528,7 +550,9 @@ ProblemStatusProto GetProblemStatusProto(const GScipOutput::Status gscip_status,
|
||||
problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
|
||||
break;
|
||||
case GScipOutput::INFEASIBLE:
|
||||
problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
|
||||
if (!was_cutoff) {
|
||||
problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
|
||||
}
|
||||
break;
|
||||
case GScipOutput::UNBOUNDED:
|
||||
problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
|
||||
@@ -547,7 +571,8 @@ ProblemStatusProto GetProblemStatusProto(const GScipOutput::Status gscip_status,
|
||||
|
||||
absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
const GScipOutput::Status gscip_status,
|
||||
const std::string& gscip_status_detail, const bool has_feasible_solution) {
|
||||
const std::string& gscip_status_detail, const bool has_feasible_solution,
|
||||
const bool had_cutoff) {
|
||||
switch (gscip_status) {
|
||||
case GScipOutput::USER_INTERRUPT:
|
||||
return TerminateForLimit(
|
||||
@@ -591,8 +616,12 @@ absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: GAP_LIMIT"));
|
||||
case GScipOutput::INFEASIBLE:
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE,
|
||||
gscip_status_detail);
|
||||
if (had_cutoff) {
|
||||
return TerminateForLimit(LIMIT_CUTOFF, gscip_status_detail);
|
||||
} else {
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE,
|
||||
gscip_status_detail);
|
||||
}
|
||||
case GScipOutput::UNBOUNDED: {
|
||||
if (has_feasible_solution) {
|
||||
return TerminateForReason(
|
||||
@@ -635,21 +664,29 @@ absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
} // namespace
|
||||
|
||||
absl::StatusOr<SolveResultProto> GScipSolver::CreateSolveResultProto(
|
||||
GScipResult gscip_result,
|
||||
const ModelSolveParametersProto& model_parameters) {
|
||||
GScipResult gscip_result, const ModelSolveParametersProto& model_parameters,
|
||||
const std::optional<double> cutoff) {
|
||||
SolveResultProto solve_result;
|
||||
ASSIGN_OR_RETURN(
|
||||
*solve_result.mutable_termination(),
|
||||
ConvertTerminationReason(gscip_result.gscip_output.status(),
|
||||
gscip_result.gscip_output.status_detail(),
|
||||
!gscip_result.solutions.empty()));
|
||||
*solve_result.mutable_solve_stats()->mutable_problem_status() =
|
||||
GetProblemStatusProto(
|
||||
gscip_result.gscip_output.status(), !gscip_result.solutions.empty(),
|
||||
std::isfinite(gscip_result.gscip_output.stats().best_bound()));
|
||||
|
||||
const int num_solutions = gscip_result.solutions.size();
|
||||
CHECK_EQ(num_solutions, gscip_result.objective_values.size());
|
||||
ConvertTerminationReason(
|
||||
gscip_result.gscip_output.status(),
|
||||
gscip_result.gscip_output.status_detail(),
|
||||
/*has_feasible_solution=*/!gscip_result.solutions.empty(),
|
||||
/*had_cutoff=*/cutoff.has_value()));
|
||||
const bool is_maximize = gscip_->ObjectiveIsMaximize();
|
||||
// When an objective limit is set, SCIP returns the solutions worse than the
|
||||
// limit, we need to filter these out manually.
|
||||
const auto meets_cutoff = [cutoff, is_maximize](const double obj_value) {
|
||||
if (!cutoff.has_value()) {
|
||||
return true;
|
||||
}
|
||||
if (is_maximize) {
|
||||
return obj_value >= *cutoff;
|
||||
} else {
|
||||
return obj_value <= *cutoff;
|
||||
}
|
||||
};
|
||||
|
||||
LazyInitialized<std::vector<int64_t>> sorted_variables([&]() {
|
||||
std::vector<int64_t> sorted;
|
||||
@@ -660,7 +697,12 @@ absl::StatusOr<SolveResultProto> GScipSolver::CreateSolveResultProto(
|
||||
std::sort(sorted.begin(), sorted.end());
|
||||
return sorted;
|
||||
});
|
||||
CHECK_EQ(gscip_result.solutions.size(), gscip_result.objective_values.size());
|
||||
for (int i = 0; i < gscip_result.solutions.size(); ++i) {
|
||||
// GScip ensures the solutions are returned best objective first.
|
||||
if (!meets_cutoff(gscip_result.objective_values[i])) {
|
||||
break;
|
||||
}
|
||||
SolutionProto* const solution = solve_result.add_solutions();
|
||||
PrimalSolutionProto* const primal_solution =
|
||||
solution->mutable_primal_solution();
|
||||
@@ -676,12 +718,24 @@ absl::StatusOr<SolveResultProto> GScipSolver::CreateSolveResultProto(
|
||||
gscip_result.primal_ray,
|
||||
model_parameters.variable_values_filter());
|
||||
}
|
||||
// TODO(user): add support for the basis and dual solutions in gscip, then
|
||||
// populate them here.
|
||||
const bool has_feasible_solution = solve_result.solutions_size() > 0;
|
||||
*solve_result.mutable_solve_stats()->mutable_problem_status() =
|
||||
GetProblemStatusProto(
|
||||
gscip_result.gscip_output.status(),
|
||||
/*has_feasible_solution=*/has_feasible_solution,
|
||||
/*has_finite_dual_bound=*/
|
||||
std::isfinite(gscip_result.gscip_output.stats().best_bound()),
|
||||
/*was_cutoff=*/solve_result.termination().limit() == LIMIT_CUTOFF);
|
||||
SolveStatsProto* const common_stats = solve_result.mutable_solve_stats();
|
||||
const GScipSolvingStats& gscip_stats = gscip_result.gscip_output.stats();
|
||||
common_stats->set_best_dual_bound(gscip_stats.best_bound());
|
||||
common_stats->set_best_primal_bound(gscip_stats.best_objective());
|
||||
// If we found no solutions meeting the cutoff, we have no primal bound.
|
||||
if (has_feasible_solution) {
|
||||
common_stats->set_best_primal_bound(gscip_stats.best_objective());
|
||||
} else {
|
||||
common_stats->set_best_primal_bound(is_maximize ? -kInf : kInf);
|
||||
}
|
||||
|
||||
common_stats->set_node_count(gscip_stats.node_count());
|
||||
common_stats->set_simplex_iterations(gscip_stats.primal_simplex_iterations() +
|
||||
gscip_stats.dual_simplex_iterations());
|
||||
@@ -741,7 +795,10 @@ absl::StatusOr<SolveResultProto> GScipSolver::Solve(
|
||||
std::make_unique<GScipSolverMessageCallbackHandler>(message_cb);
|
||||
}
|
||||
|
||||
const GScipParameters gscip_parameters = MergeParameters(parameters);
|
||||
const auto [gscip_parameters, warnings] = MergeParameters(parameters);
|
||||
if (parameters.strictness().bad_parameter() && !warnings.empty()) {
|
||||
return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
|
||||
}
|
||||
// TODO(user): reorganize gscip to respect warning is error argument on bad
|
||||
// parameters.
|
||||
|
||||
@@ -781,7 +838,13 @@ absl::StatusOr<SolveResultProto> GScipSolver::Solve(
|
||||
|
||||
ASSIGN_OR_RETURN(
|
||||
SolveResultProto result,
|
||||
CreateSolveResultProto(std::move(gscip_result), model_parameters));
|
||||
CreateSolveResultProto(std::move(gscip_result), model_parameters,
|
||||
parameters.has_cutoff_limit()
|
||||
? std::make_optional(parameters.cutoff_limit())
|
||||
: std::nullopt));
|
||||
for (const std::string& warning : warnings) {
|
||||
result.add_warnings(warning);
|
||||
}
|
||||
CHECK_OK(util_time::EncodeGoogleApiProto(
|
||||
absl::Now() - start, result.mutable_solve_stats()->mutable_solve_time()));
|
||||
return result;
|
||||
|
||||
@@ -55,7 +55,9 @@ class GScipSolver : public SolverInterface {
|
||||
absl::Status Update(const ModelUpdateProto& model_update) override;
|
||||
bool CanUpdate(const ModelUpdateProto& model_update) override;
|
||||
|
||||
static GScipParameters MergeParameters(
|
||||
// Returns the merged parameters and a list of warnings for unsupported
|
||||
// parameters.
|
||||
static std::pair<GScipParameters, std::vector<std::string>> MergeParameters(
|
||||
const SolveParametersProto& solve_parameters);
|
||||
|
||||
private:
|
||||
@@ -112,7 +114,8 @@ class GScipSolver : public SolverInterface {
|
||||
absl::Span<const int64_t> variable_ids);
|
||||
absl::StatusOr<SolveResultProto> CreateSolveResultProto(
|
||||
GScipResult gscip_result,
|
||||
const ModelSolveParametersProto& model_parameters);
|
||||
const ModelSolveParametersProto& model_parameters,
|
||||
std::optional<double> cutoff);
|
||||
|
||||
const std::unique_ptr<GScip> gscip_;
|
||||
InterruptEventHandler interrupt_event_handler_;
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
|
||||
#include "ortools/math_opt/solvers/gurobi/g_gurobi.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "ortools/base/cleanup.h"
|
||||
#include "absl/status/status.h"
|
||||
|
||||
@@ -93,16 +93,6 @@ absl::StatusOr<std::unique_ptr<Gurobi>> GurobiFromInitArgs(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> RepeatedPtrFieldToVec(
|
||||
const google::protobuf::RepeatedPtrField<std::string>& proto_string_vec) {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(proto_string_vec.size());
|
||||
for (const std::string& str : proto_string_vec) {
|
||||
result.push_back(str);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline BasisStatusProto ConvertVariableStatus(const int status) {
|
||||
switch (status) {
|
||||
case GRB_BASIC:
|
||||
@@ -180,6 +170,34 @@ GurobiParametersProto MergeParameters(
|
||||
parameter->set_value(absl::StrCat(relative_gap_limit));
|
||||
}
|
||||
|
||||
if (solve_parameters.has_cutoff_limit()) {
|
||||
GurobiParametersProto::Parameter* const parameter =
|
||||
merged_parameters.add_parameters();
|
||||
parameter->set_name(GRB_DBL_PAR_CUTOFF);
|
||||
parameter->set_value(absl::StrCat(solve_parameters.cutoff_limit()));
|
||||
}
|
||||
|
||||
if (solve_parameters.has_objective_limit()) {
|
||||
GurobiParametersProto::Parameter* const parameter =
|
||||
merged_parameters.add_parameters();
|
||||
parameter->set_name(GRB_DBL_PAR_BESTOBJSTOP);
|
||||
parameter->set_value(absl::StrCat(solve_parameters.objective_limit()));
|
||||
}
|
||||
|
||||
if (solve_parameters.has_best_bound_limit()) {
|
||||
GurobiParametersProto::Parameter* const parameter =
|
||||
merged_parameters.add_parameters();
|
||||
parameter->set_name(GRB_DBL_PAR_BESTBDSTOP);
|
||||
parameter->set_value(absl::StrCat(solve_parameters.best_bound_limit()));
|
||||
}
|
||||
|
||||
if (solve_parameters.has_solution_limit()) {
|
||||
GurobiParametersProto::Parameter* const parameter =
|
||||
merged_parameters.add_parameters();
|
||||
parameter->set_name(GRB_INT_PAR_SOLUTIONLIMIT);
|
||||
parameter->set_value(absl::StrCat(solve_parameters.solution_limit()));
|
||||
}
|
||||
|
||||
if (solve_parameters.has_random_seed()) {
|
||||
const int random_seed =
|
||||
std::min(GRB_MAXINT, std::max(solve_parameters.random_seed(), 0));
|
||||
@@ -374,6 +392,27 @@ const absl::flat_hash_set<CallbackEventProto>& SupportedLPEvents() {
|
||||
return *kEvents;
|
||||
}
|
||||
|
||||
// Gurobi names (model, variables and constraints) must be no longer than 255
|
||||
// characters; or Gurobi fails with an error.
|
||||
constexpr std::size_t kMaxNameSize = 255;
|
||||
|
||||
// Returns a string of at most kMaxNameSize max size.
|
||||
std::string TruncateName(const std::string_view original_name) {
|
||||
return std::string(
|
||||
original_name.substr(0, std::min(kMaxNameSize, original_name.size())));
|
||||
}
|
||||
|
||||
// Truncate the names of variables and constraints.
|
||||
std::vector<std::string> TruncateNames(
|
||||
const google::protobuf::RepeatedPtrField<std::string>& original_names) {
|
||||
std::vector<std::string> result;
|
||||
result.reserve(original_names.size());
|
||||
for (const std::string& original_name : original_names) {
|
||||
result.push_back(TruncateName(original_name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GurobiSolver::GurobiSolver(std::unique_ptr<Gurobi> g_gurobi)
|
||||
@@ -400,7 +439,7 @@ absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
|
||||
"Gurobi status GRB_INF_OR_UNBD");
|
||||
case GRB_CUTOFF:
|
||||
return TerminateForLimit(LIMIT_OBJECTIVE, "Gurobi status GRB_CUTOFF");
|
||||
return TerminateForLimit(LIMIT_CUTOFF, "Gurobi status GRB_CUTOFF");
|
||||
case GRB_ITERATION_LIMIT:
|
||||
return TerminateForLimit(LIMIT_ITERATION);
|
||||
case GRB_NODE_LIMIT:
|
||||
@@ -416,8 +455,7 @@ absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
|
||||
case GRB_SUBOPTIMAL:
|
||||
return TerminateForReason(TERMINATION_REASON_IMPRECISE);
|
||||
case GRB_USER_OBJ_LIMIT:
|
||||
return TerminateForLimit(LIMIT_OBJECTIVE,
|
||||
"Gurobi status GRB_USR_OBJ_LIMIT");
|
||||
return TerminateForLimit(LIMIT_OBJECTIVE);
|
||||
case GRB_LOADED:
|
||||
return absl::InternalError(
|
||||
"Error creating termination reason, unexpected gurobi status code "
|
||||
@@ -1184,7 +1222,7 @@ absl::Status GurobiSolver::AddNewVariables(
|
||||
// We need to copy the names, RepeatedPtrField cannot be converted to
|
||||
// absl::Span<std::string>.
|
||||
const std::vector<std::string> variable_names =
|
||||
RepeatedPtrFieldToVec(new_variables.names());
|
||||
TruncateNames(new_variables.names());
|
||||
RETURN_IF_ERROR(gurobi_->AddVars(
|
||||
/*obj=*/{},
|
||||
/*lb=*/new_variables.lower_bounds(),
|
||||
@@ -1247,7 +1285,7 @@ absl::Status GurobiSolver::AddNewConstraints(
|
||||
// We need to copy the names, RepeatedPtrField cannot be converted to
|
||||
// absl::Span<std::string>.
|
||||
const std::vector<std::string> constraint_names =
|
||||
RepeatedPtrFieldToVec(constraints.names());
|
||||
TruncateNames(constraints.names());
|
||||
// Constraints are translated into:
|
||||
// 1. ax <= upper_bound (if lower bound <= -GRB_INFINITY, and upper_bound
|
||||
// is finite and less than GRB_INFINITY)
|
||||
@@ -1346,8 +1384,8 @@ absl::Status GurobiSolver::UpdateInt32ListAttribute(
|
||||
|
||||
absl::Status GurobiSolver::LoadModel(const ModelProto& input_model) {
|
||||
CHECK(gurobi_ != nullptr);
|
||||
RETURN_IF_ERROR(
|
||||
gurobi_->SetStringAttr(GRB_STR_ATTR_MODELNAME, input_model.name()));
|
||||
RETURN_IF_ERROR(gurobi_->SetStringAttr(GRB_STR_ATTR_MODELNAME,
|
||||
TruncateName(input_model.name())));
|
||||
RETURN_IF_ERROR(AddNewVariables(input_model.variables()));
|
||||
|
||||
RETURN_IF_ERROR(AddNewConstraints(input_model.linear_constraints()));
|
||||
|
||||
@@ -83,6 +83,18 @@ PdlpSolver::MergeParameters(const SolveParametersProto& parameters) {
|
||||
absl::ToDoubleSeconds(
|
||||
util_time::DecodeGoogleApiProto(parameters.time_limit()).value()));
|
||||
}
|
||||
if (parameters.has_cutoff_limit()) {
|
||||
warnings.push_back("parameter cutoff_limit not supported for PDLP");
|
||||
}
|
||||
if (parameters.has_objective_limit()) {
|
||||
warnings.push_back("parameter best_objective_limit not supported for PDLP");
|
||||
}
|
||||
if (parameters.has_best_bound_limit()) {
|
||||
warnings.push_back("parameter best_bound_limit not supported for PDLP");
|
||||
}
|
||||
if (parameters.has_solution_limit()) {
|
||||
warnings.push_back("parameter solution_limit not supported for PDLP");
|
||||
}
|
||||
if (parameters.has_random_seed()) {
|
||||
warnings.push_back("parameter random_seed not supported for PDLP");
|
||||
}
|
||||
|
||||
@@ -154,9 +154,9 @@ cc_library(
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "solver_parameters_validator",
|
||||
srcs = ["solver_parameters_validator.cc"],
|
||||
hdrs = ["solver_parameters_validator.h"],
|
||||
name = "solve_parameters_validator",
|
||||
srcs = ["solve_parameters_validator.cc"],
|
||||
hdrs = ["solve_parameters_validator.h"],
|
||||
deps = [
|
||||
"//ortools/base:protoutil",
|
||||
"//ortools/base:status_macros",
|
||||
|
||||
@@ -116,25 +116,21 @@ absl::Status CheckSortedIdsNotBad(const absl::Span<const int64_t> ids,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
absl::Status CheckIdsNonnegativeAndStrictlyIncreasing(
|
||||
absl::Span<const int64_t> ids) {
|
||||
absl::Status CheckIdsRangeAndStrictlyIncreasing(absl::Span<const int64_t> ids) {
|
||||
int64_t previous{-1};
|
||||
for (int i = 0; i < ids.size(); ++i) {
|
||||
if (ids[i] <= previous) {
|
||||
std::string error_base = absl::StrCat(
|
||||
"Expected ids to be nonnegative and strictly increasing, but at "
|
||||
"index ",
|
||||
i, ", found id: ", ids[i]);
|
||||
if (i == 0) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< error_base << " (a negative id)";
|
||||
} else {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< error_base << " and at index " << i - 1
|
||||
<< " found id: " << ids[i - 1];
|
||||
}
|
||||
for (int i = 0; i < ids.size(); previous = ids[i], ++i) {
|
||||
if (ids[i] < 0 || ids[i] == std::numeric_limits<int64_t>::max()) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "Expected ids to be nonnegative and not max(int64_t) but at "
|
||||
"index "
|
||||
<< i << " found id: " << ids[i];
|
||||
}
|
||||
if (ids[i] <= previous) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "Expected ids to be strictly increasing, but at index " << i
|
||||
<< " found id: " << ids[i] << " and at index " << i - 1
|
||||
<< " found id: " << ids[i - 1];
|
||||
}
|
||||
previous = ids[i];
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
absl::Status CheckIdsNonnegativeAndStrictlyIncreasing(
|
||||
absl::Span<const int64_t> ids);
|
||||
// Checks that the input ids are in [0, max(int64_t)) range and that their are
|
||||
// strictly increasing.
|
||||
absl::Status CheckIdsRangeAndStrictlyIncreasing(absl::Span<const int64_t> ids);
|
||||
|
||||
// Checks that the elements of ids are a subset of universe.
|
||||
//
|
||||
|
||||
@@ -58,7 +58,7 @@ absl::Status ValidateBranchingPriorities(
|
||||
|
||||
absl::Status ValidateSparseVectorFilter(const SparseVectorFilterProto& v,
|
||||
const IdNameBiMap& valid_ids) {
|
||||
RETURN_IF_ERROR(CheckIdsNonnegativeAndStrictlyIncreasing(v.filtered_ids()));
|
||||
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(v.filtered_ids()));
|
||||
RETURN_IF_ERROR(
|
||||
CheckIdsSubset(v.filtered_ids(), valid_ids, "filtered_ids", "model IDs"));
|
||||
if (!v.filter_by_ids() && !v.filtered_ids().empty()) {
|
||||
|
||||
@@ -46,7 +46,7 @@ constexpr double kInf = std::numeric_limits<double>::infinity();
|
||||
|
||||
absl::Status VariablesValid(const VariablesProto& variables,
|
||||
const bool check_names) {
|
||||
RETURN_IF_ERROR(CheckIdsNonnegativeAndStrictlyIncreasing(variables.ids()))
|
||||
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(variables.ids()))
|
||||
<< "Bad variable ids";
|
||||
RETURN_IF_ERROR(
|
||||
CheckValues(MakeView(variables.ids(), variables.lower_bounds()),
|
||||
@@ -147,8 +147,7 @@ absl::Status ObjectiveUpdatesValidForModel(
|
||||
|
||||
absl::Status LinearConstraintsValid(
|
||||
const LinearConstraintsProto& linear_constraints, const bool check_names) {
|
||||
RETURN_IF_ERROR(
|
||||
CheckIdsNonnegativeAndStrictlyIncreasing(linear_constraints.ids()))
|
||||
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(linear_constraints.ids()))
|
||||
<< "Bad linear constraint ids";
|
||||
RETURN_IF_ERROR(CheckValues(
|
||||
MakeView(linear_constraints.ids(), linear_constraints.lower_bounds()),
|
||||
@@ -229,11 +228,11 @@ absl::Status ValidateModel(const ModelProto& model, const bool check_names) {
|
||||
|
||||
absl::Status ValidateModelUpdate(const ModelUpdateProto& model_update,
|
||||
const bool check_names) {
|
||||
RETURN_IF_ERROR(CheckIdsNonnegativeAndStrictlyIncreasing(
|
||||
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(
|
||||
model_update.deleted_linear_constraint_ids()))
|
||||
<< "ModelUpdateProto.deleted_linear_constraint_ids invalid";
|
||||
RETURN_IF_ERROR(CheckIdsNonnegativeAndStrictlyIncreasing(
|
||||
model_update.deleted_variable_ids()))
|
||||
RETURN_IF_ERROR(
|
||||
CheckIdsRangeAndStrictlyIncreasing(model_update.deleted_variable_ids()))
|
||||
<< "ModelUpdateProto.deleted_variable_ids invalid";
|
||||
RETURN_IF_ERROR(VariableUpdatesValid(model_update.variable_updates()))
|
||||
<< "ModelUpdateProto.variable_updates invalid";
|
||||
|
||||
@@ -236,6 +236,12 @@ absl::Status ValidateTerminationConsistency(const SolveResultProto& result) {
|
||||
case TERMINATION_REASON_LIMIT_REACHED:
|
||||
// TODO(b/211677729): update when TERMINATION_REASON_FEASIBLE is added.
|
||||
// No primal or dual requirements so we check consistency.
|
||||
if (result.termination().limit() == LIMIT_CUTOFF) {
|
||||
if (result.solutions_size() > 0) {
|
||||
return absl::InvalidArgumentError(
|
||||
"For LIMIT_CUTOFF expected no solutions");
|
||||
}
|
||||
}
|
||||
RETURN_IF_ERROR(CheckPrimalSolutionAndStatusConsistency(result));
|
||||
RETURN_IF_ERROR(CheckDualSolutionAndStatusConsistency(result));
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -11,13 +11,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "ortools/math_opt/validators/solver_parameters_validator.h"
|
||||
#include "ortools/math_opt/validators/solve_parameters_validator.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "ortools/math_opt/parameters.pb.h"
|
||||
#include "ortools/base/status_macros.h"
|
||||
@@ -26,34 +22,52 @@
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
absl::Status ValidateSolverParameters(const SolveParametersProto& parameters) {
|
||||
absl::Status ValidateSolveParameters(const SolveParametersProto& parameters) {
|
||||
RETURN_IF_ERROR(
|
||||
util_time::DecodeGoogleApiProto(parameters.time_limit()).status())
|
||||
<< "invalid parameters.time_limit";
|
||||
<< "invalid SolveParameters.time_limit";
|
||||
|
||||
if (parameters.has_threads()) {
|
||||
if (parameters.threads() <= 0) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("parameters.threads = ", parameters.threads(), " <= 0"));
|
||||
return absl::InvalidArgumentError(absl::StrCat(
|
||||
"SolveParameters.threads = ", parameters.threads(), " <= 0"));
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.has_relative_gap_limit()) {
|
||||
if (parameters.relative_gap_limit() < 0) {
|
||||
return absl::InvalidArgumentError(absl::StrCat(
|
||||
"parameters.relative_gap_limit = ", parameters.relative_gap_limit(),
|
||||
" < 0"));
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("SolveParameters.relative_gap_limit = ",
|
||||
parameters.relative_gap_limit(), " < 0"));
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.has_absolute_gap_limit()) {
|
||||
if (parameters.absolute_gap_limit() < 0) {
|
||||
return absl::InvalidArgumentError(absl::StrCat(
|
||||
"parameters.absolute_gap_limit = ", parameters.absolute_gap_limit(),
|
||||
" < 0"));
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("SolveParameters.absolute_gap_limit = ",
|
||||
parameters.absolute_gap_limit(), " < 0"));
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.has_solution_limit() && parameters.solution_limit() <= 0) {
|
||||
return util::InvalidArgumentErrorBuilder()
|
||||
<< "SolveParameters.solution_limit = " << parameters.solution_limit()
|
||||
<< " should be positive.";
|
||||
}
|
||||
|
||||
if (std::isnan(parameters.cutoff_limit())) {
|
||||
return absl::InvalidArgumentError("SolveParameters.cutoff_limit was NaN");
|
||||
}
|
||||
if (std::isnan(parameters.objective_limit())) {
|
||||
return absl::InvalidArgumentError(
|
||||
"SolveParameters.objective_limit was NaN");
|
||||
}
|
||||
if (std::isnan(parameters.best_bound_limit())) {
|
||||
return absl::InvalidArgumentError(
|
||||
"SolveParameters.best_bound_limit was NaN");
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef OR_TOOLS_MATH_OPT_VALIDATORS_SOLVER_PARAMETERS_VALIDATOR_H_
|
||||
#define OR_TOOLS_MATH_OPT_VALIDATORS_SOLVER_PARAMETERS_VALIDATOR_H_
|
||||
#ifndef OR_TOOLS_MATH_OPT_VALIDATORS_SOLVE_PARAMETERS_VALIDATOR_H_
|
||||
#define OR_TOOLS_MATH_OPT_VALIDATORS_SOLVE_PARAMETERS_VALIDATOR_H_
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "ortools/math_opt/parameters.pb.h"
|
||||
@@ -20,9 +20,10 @@
|
||||
namespace operations_research {
|
||||
namespace math_opt {
|
||||
|
||||
absl::Status ValidateSolverParameters(const SolveParametersProto& parameters);
|
||||
// TODO(b/213697045): some parameters are still not validated.
|
||||
absl::Status ValidateSolveParameters(const SolveParametersProto& parameters);
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_MATH_OPT_VALIDATORS_SOLVER_PARAMETERS_VALIDATOR_H_
|
||||
#endif // OR_TOOLS_MATH_OPT_VALIDATORS_SOLVE_PARAMETERS_VALIDATOR_H_
|
||||
@@ -49,7 +49,7 @@ template <typename T,
|
||||
typename = std::enable_if_t<!std::is_floating_point<T>::value> >
|
||||
absl::Status CheckIdsAndValues(const SparseVectorView<T>& vector_view,
|
||||
absl::string_view value_name = "values") {
|
||||
RETURN_IF_ERROR(CheckIdsNonnegativeAndStrictlyIncreasing(vector_view.ids()));
|
||||
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(vector_view.ids()));
|
||||
RETURN_IF_ERROR(CheckValues(vector_view, value_name));
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@@ -73,7 +73,7 @@ template <typename T,
|
||||
absl::Status CheckIdsAndValues(const SparseVectorView<T>& vector_view,
|
||||
const DoubleOptions& options,
|
||||
absl::string_view value_name = "values") {
|
||||
RETURN_IF_ERROR(CheckIdsNonnegativeAndStrictlyIncreasing(vector_view.ids()));
|
||||
RETURN_IF_ERROR(CheckIdsRangeAndStrictlyIncreasing(vector_view.ids()));
|
||||
RETURN_IF_ERROR(CheckValues(vector_view, options, value_name));
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user