sync math_opt
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "ortools/math_opt/callback.pb.h"
|
||||
#include "ortools/math_opt/core/sparse_vector_view.h"
|
||||
#include "ortools/math_opt/result.pb.h"
|
||||
#include "ortools/math_opt/sparse_containers.pb.h"
|
||||
|
||||
namespace operations_research {
|
||||
@@ -75,10 +76,14 @@ absl::flat_hash_set<CallbackEventProto> EventSet(
|
||||
return events;
|
||||
}
|
||||
|
||||
TerminationProto TerminateForLimit(const LimitProto limit,
|
||||
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible,
|
||||
const absl::string_view detail) {
|
||||
TerminationProto result;
|
||||
result.set_reason(TERMINATION_REASON_LIMIT_REACHED);
|
||||
if (feasible) {
|
||||
result.set_reason(TERMINATION_REASON_FEASIBLE);
|
||||
} else {
|
||||
result.set_reason(TERMINATION_REASON_NO_SOLUTION_FOUND);
|
||||
}
|
||||
result.set_limit(limit);
|
||||
if (!detail.empty()) {
|
||||
result.set_detail(std::string(detail));
|
||||
@@ -86,6 +91,16 @@ TerminationProto TerminateForLimit(const LimitProto limit,
|
||||
return result;
|
||||
}
|
||||
|
||||
TerminationProto FeasibleTermination(const LimitProto limit,
|
||||
const absl::string_view detail) {
|
||||
return TerminateForLimit(limit, /*feasible=*/true, detail);
|
||||
}
|
||||
|
||||
TerminationProto NoSolutionFoundTermination(const LimitProto limit,
|
||||
const absl::string_view detail) {
|
||||
return TerminateForLimit(limit, /*feasible=*/false, detail);
|
||||
}
|
||||
|
||||
TerminationProto TerminateForReason(const TerminationReasonProto reason,
|
||||
const absl::string_view detail) {
|
||||
TerminationProto result;
|
||||
|
||||
@@ -91,9 +91,17 @@ class SparseVectorFilterPredicate {
|
||||
absl::flat_hash_set<CallbackEventProto> EventSet(
|
||||
const CallbackRegistrationProto& callback_registration);
|
||||
|
||||
TerminationProto TerminateForLimit(LimitProto limit,
|
||||
// Sets the reason to TERMINATION_REASON_FEASIBLE if feasible = true and
|
||||
// TERMINATION_REASON_NO_SOLUTION_FOUND otherwise.
|
||||
TerminationProto TerminateForLimit(const LimitProto limit, bool feasible,
|
||||
absl::string_view detail = {});
|
||||
|
||||
TerminationProto FeasibleTermination(const LimitProto limit,
|
||||
absl::string_view detail = {});
|
||||
|
||||
TerminationProto NoSolutionFoundTermination(const LimitProto limit,
|
||||
absl::string_view detail = {});
|
||||
|
||||
TerminationProto TerminateForReason(TerminationReasonProto reason,
|
||||
absl::string_view detail = {});
|
||||
|
||||
|
||||
@@ -142,8 +142,8 @@ struct Enum {
|
||||
// input value is not a valid value of the enum.
|
||||
//
|
||||
// The returned string should not include the enum name and should be in
|
||||
// snake_case (e.g. is the enum is kLimitReached, this should return
|
||||
// "limit_reached").
|
||||
// snake_case (e.g. is the enum is kNoSolutionFound, this should return
|
||||
// "no_solution_found").
|
||||
//
|
||||
// Please prefer using the global functions EnumToString() (or
|
||||
// EnumToOptString() if support for invalid values is needed) instead to
|
||||
|
||||
@@ -497,24 +497,47 @@ Matcher<SolveResult> TerminatesWith(const TerminationReason expected,
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithLimit(Limit expected,
|
||||
bool allow_limit_undetermined,
|
||||
bool check_warnings) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(Field(
|
||||
"termination", &SolveResult::termination,
|
||||
Field("reason", &Termination::reason, TerminationReason::kLimitReached)));
|
||||
namespace {
|
||||
testing::Matcher<SolveResult> LimitIs(const Limit expected,
|
||||
const bool allow_limit_undetermined) {
|
||||
if (allow_limit_undetermined) {
|
||||
matchers.push_back(Field("termination", &SolveResult::termination,
|
||||
Field("limit", &Termination::limit,
|
||||
AnyOf(Limit::kUndetermined, expected))));
|
||||
} else {
|
||||
matchers.push_back(Field("termination", &SolveResult::termination,
|
||||
Field("limit", &Termination::limit, expected)));
|
||||
}
|
||||
if (check_warnings) {
|
||||
matchers.push_back(Field("warnings", &SolveResult::warnings, IsEmpty()));
|
||||
return Field("termination", &SolveResult::termination,
|
||||
Field("limit", &Termination::limit,
|
||||
AnyOf(Limit::kUndetermined, expected)));
|
||||
}
|
||||
return Field("termination", &SolveResult::termination,
|
||||
Field("limit", &Termination::limit, expected));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithLimit(
|
||||
const Limit expected, const bool allow_limit_undetermined,
|
||||
const bool check_warnings) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(LimitIs(expected, allow_limit_undetermined));
|
||||
matchers.push_back(TerminatesWithOneOf(
|
||||
{TerminationReason::kFeasible, TerminationReason::kNoSolutionFound},
|
||||
/*check_warnings=*/check_warnings));
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
|
||||
const Limit expected, const bool allow_limit_undetermined,
|
||||
const bool check_warnings) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(LimitIs(expected, allow_limit_undetermined));
|
||||
matchers.push_back(TerminatesWith(TerminationReason::kFeasible,
|
||||
/*check_warnings=*/check_warnings));
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
|
||||
const Limit expected, const bool allow_limit_undetermined,
|
||||
const bool check_warnings) {
|
||||
std::vector<Matcher<SolveResult>> matchers;
|
||||
matchers.push_back(LimitIs(expected, allow_limit_undetermined));
|
||||
matchers.push_back(TerminatesWith(TerminationReason::kNoSolutionFound,
|
||||
/*check_warnings=*/check_warnings));
|
||||
return ::testing::AllOfArray(matchers);
|
||||
}
|
||||
|
||||
|
||||
@@ -212,13 +212,29 @@ testing::Matcher<SolveResult> TerminatesWithOneOf(
|
||||
const std::vector<TerminationReason>& allowed, bool check_warnings = true);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has termination reason kLimitReached.
|
||||
// * The result has termination reason kFeasible or kNoSolutionFound.
|
||||
// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
|
||||
// * If check_warnings, the result has no warnings.
|
||||
testing::Matcher<SolveResult> TerminatesWithLimit(
|
||||
Limit expected, bool allow_limit_undetermined = false,
|
||||
bool check_warnings = true);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has termination reason kFeasible.
|
||||
// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
|
||||
// * If check_warnings, the result has no warnings.
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonFeasible(
|
||||
Limit expected, bool allow_limit_undetermined = false,
|
||||
bool check_warnings = true);
|
||||
|
||||
// Checks the following:
|
||||
// * The result has termination reason kNoSolutionFound.
|
||||
// * The limit is expected, or is kUndetermined if allow_limit_undetermined.
|
||||
// * If check_warnings, the result has no warnings.
|
||||
testing::Matcher<SolveResult> TerminatesWithReasonNoSolutionFound(
|
||||
Limit expected, bool allow_limit_undetermined = false,
|
||||
bool check_warnings = true);
|
||||
|
||||
// SolveResult has a primal solution matching expected within tolerance.
|
||||
testing::Matcher<SolveResult> HasSolution(
|
||||
PrimalSolution expected, double tolerance = kMatcherDefaultTolerance);
|
||||
@@ -373,6 +389,7 @@ void PrintTo(const DualSolution& dual_solution, std::ostream* os);
|
||||
void PrintTo(const PrimalRay& primal_ray, std::ostream* os);
|
||||
void PrintTo(const DualRay& dual_ray, std::ostream* os);
|
||||
void PrintTo(const Basis& basis, std::ostream* os);
|
||||
void PrintTo(const Solution& solution, std::ostream* os);
|
||||
void PrintTo(const SolveResult& result, std::ostream* os);
|
||||
|
||||
// We do not want to rely on ::testing::internal::ContainerPrinter because we
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "absl/types/span.h"
|
||||
#include "ortools/base/linked_hash_map.h"
|
||||
#include "ortools/glop/parameters.pb.h" // IWYU pragma: export
|
||||
#include "ortools/gscip/gscip.pb.h" // IWYU pragma: export
|
||||
#include "ortools/gscip/gscip.pb.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/cpp/enums.h" // IWYU pragma: export
|
||||
#include "ortools/math_opt/parameters.pb.h"
|
||||
#include "ortools/math_opt/solvers/gurobi.pb.h" // IWYU pragma: export
|
||||
@@ -214,8 +214,8 @@ struct SolveParameters {
|
||||
// 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
|
||||
// On an early stop, the solver returns termination reason kNoSolutionFound
|
||||
// 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
|
||||
@@ -225,17 +225,19 @@ struct SolveParameters {
|
||||
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.
|
||||
// with termination reason kFeasible or kNoSolutionFound and limit kObjective.
|
||||
// TODO(b/214567536): maybe it should only be kFeasible.
|
||||
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.
|
||||
// good, with termination reason kFeasible or kNoSolutionFound 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
|
||||
// termination reason kFeasible 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.
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
#ifndef OR_TOOLS_MATH_OPT_CPP_SOLUTION_H_
|
||||
#define OR_TOOLS_MATH_OPT_CPP_SOLUTION_H_
|
||||
|
||||
// IWYU pragma: private, include "ortools/math_opt/cpp/math_opt.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
|
||||
@@ -86,8 +86,10 @@ std::optional<absl::string_view> Enum<TerminationReason>::ToOptString(
|
||||
return "infeasible_or_unbounded";
|
||||
case TerminationReason::kImprecise:
|
||||
return "imprecise";
|
||||
case TerminationReason::kLimitReached:
|
||||
return "limit_reached";
|
||||
case TerminationReason::kFeasible:
|
||||
return "feasible";
|
||||
case TerminationReason::kNoSolutionFound:
|
||||
return "no_solution_found";
|
||||
case TerminationReason::kNumericalError:
|
||||
return "numerical_error";
|
||||
case TerminationReason::kOtherError:
|
||||
@@ -103,7 +105,8 @@ absl::Span<const TerminationReason> Enum<TerminationReason>::AllValues() {
|
||||
TerminationReason::kUnbounded,
|
||||
TerminationReason::kInfeasibleOrUnbounded,
|
||||
TerminationReason::kImprecise,
|
||||
TerminationReason::kLimitReached,
|
||||
TerminationReason::kFeasible,
|
||||
TerminationReason::kNoSolutionFound,
|
||||
TerminationReason::kNumericalError,
|
||||
TerminationReason::kOtherError,
|
||||
};
|
||||
@@ -152,10 +155,18 @@ absl::Span<const Limit> Enum<Limit>::AllValues() {
|
||||
Termination::Termination(const TerminationReason reason, std::string detail)
|
||||
: reason(reason), detail(std::move(detail)) {}
|
||||
|
||||
Termination::Termination(const Limit limit, std::string detail)
|
||||
: reason(TerminationReason::kLimitReached),
|
||||
limit(limit),
|
||||
detail(std::move(detail)) {}
|
||||
Termination Termination::Feasible(const Limit limit, const std::string detail) {
|
||||
Termination termination(TerminationReason::kFeasible, detail);
|
||||
termination.limit = limit;
|
||||
return termination;
|
||||
}
|
||||
|
||||
Termination Termination::NoSolutionFound(const Limit limit,
|
||||
const std::string detail) {
|
||||
Termination termination(TerminationReason::kNoSolutionFound, detail);
|
||||
termination.limit = limit;
|
||||
return termination;
|
||||
}
|
||||
|
||||
TerminationProto Termination::ToProto() const {
|
||||
TerminationProto proto;
|
||||
@@ -167,12 +178,19 @@ TerminationProto Termination::ToProto() const {
|
||||
return proto;
|
||||
}
|
||||
|
||||
bool Termination::limit_reached() const {
|
||||
return reason == TerminationReason::kFeasible ||
|
||||
reason == TerminationReason::kNoSolutionFound;
|
||||
}
|
||||
|
||||
Termination Termination::FromProto(const TerminationProto& termination_proto) {
|
||||
const bool limit_reached =
|
||||
termination_proto.reason() == TERMINATION_REASON_LIMIT_REACHED;
|
||||
termination_proto.reason() == TERMINATION_REASON_FEASIBLE ||
|
||||
termination_proto.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND;
|
||||
const bool has_limit = termination_proto.limit() != LIMIT_UNSPECIFIED;
|
||||
CHECK_EQ(limit_reached, has_limit)
|
||||
<< "Termination reason should be LIMIT_REACHED if and only if limit is "
|
||||
<< "Termination reason should be TERMINATION_REASON_FEASIBLE or "
|
||||
"TERMINATION_REASON_NO_SOLUTION_FOUND if and only if limit is "
|
||||
"specified, but found reason="
|
||||
<< ProtoEnumToString(termination_proto.reason())
|
||||
<< " and limit=" << ProtoEnumToString(termination_proto.limit());
|
||||
@@ -181,7 +199,10 @@ Termination Termination::FromProto(const TerminationProto& termination_proto) {
|
||||
const std::optional<Limit> opt_limit =
|
||||
EnumFromProto(termination_proto.limit());
|
||||
CHECK(opt_limit.has_value());
|
||||
return Termination(*opt_limit, termination_proto.detail());
|
||||
if (termination_proto.reason() == TERMINATION_REASON_FEASIBLE) {
|
||||
return Feasible(*opt_limit, termination_proto.detail());
|
||||
}
|
||||
return NoSolutionFound(*opt_limit, termination_proto.detail());
|
||||
}
|
||||
|
||||
const std::optional<TerminationReason> opt_reason =
|
||||
@@ -320,11 +341,21 @@ bool SolveResult::has_primal_feasible_solution() const {
|
||||
SolutionStatus::kFeasible);
|
||||
}
|
||||
|
||||
double SolveResult::best_objective_bound() const {
|
||||
return solve_stats.best_dual_bound;
|
||||
}
|
||||
|
||||
double SolveResult::objective_value() const {
|
||||
CHECK(has_primal_feasible_solution());
|
||||
return solutions[0].primal_solution->objective_value;
|
||||
}
|
||||
|
||||
bool SolveResult::bounded() const {
|
||||
return solve_stats.problem_status.primal_status ==
|
||||
FeasibilityStatus::kFeasible &&
|
||||
solve_stats.problem_status.dual_status == FeasibilityStatus::kFeasible;
|
||||
}
|
||||
|
||||
const VariableMap<double>& SolveResult::variable_values() const {
|
||||
CHECK(has_primal_feasible_solution());
|
||||
return solutions[0].primal_solution->variable_values;
|
||||
|
||||
@@ -172,9 +172,15 @@ enum class TerminationReason {
|
||||
// they are responsible for dealing with the numerical imprecision.
|
||||
kImprecise = TERMINATION_REASON_IMPRECISE,
|
||||
|
||||
// The optimizer reached some kind of limit. Partial solution information
|
||||
// may be available. See Termination::limit for more detail.
|
||||
kLimitReached = TERMINATION_REASON_LIMIT_REACHED,
|
||||
// The optimizer reached some kind of limit and a primal feasible solution
|
||||
// is returned. See SolveResultProto.limit_detail for detailed description of
|
||||
// the kind of limit that was reached.
|
||||
kFeasible = TERMINATION_REASON_FEASIBLE,
|
||||
|
||||
// The optimizer reached some kind of limit and it did not find a primal
|
||||
// feasible solution. See SolveResultProto.limit_detail for detailed
|
||||
// description of the kind of limit that was reached.
|
||||
kNoSolutionFound = TERMINATION_REASON_NO_SOLUTION_FOUND,
|
||||
|
||||
// The algorithm stopped because it encountered unrecoverable numerical
|
||||
// error. No solution information is available.
|
||||
@@ -187,8 +193,8 @@ enum class TerminationReason {
|
||||
|
||||
MATH_OPT_DEFINE_ENUM(TerminationReason, TERMINATION_REASON_UNSPECIFIED);
|
||||
|
||||
// When a Solve() stops early with TerminationReason kLimitReached, the
|
||||
// specific limit that was hit.
|
||||
// When a Solve() stops early with TerminationReason kFeasible or
|
||||
// kNoSolutionFound, the specific limit that was hit.
|
||||
enum class Limit {
|
||||
// Used if the underlying solver cannot determine which limit was reached, or
|
||||
// as a null value when we terminated not from a limit (e.g. kOptimal).
|
||||
@@ -246,16 +252,13 @@ MATH_OPT_DEFINE_ENUM(Limit, LIMIT_UNSPECIFIED);
|
||||
|
||||
// All information regarding why a call to Solve() terminated.
|
||||
struct Termination {
|
||||
// When the reason is kLimitReached, please prefer using the other
|
||||
// constructor that enables setting the limit.
|
||||
// When the reason is kFeasible or kNoSolutionFound, please use the static
|
||||
// functions Feasible and NoSolutionFound.
|
||||
explicit Termination(TerminationReason reason, std::string detail = {});
|
||||
|
||||
// Sets the reason to kLimitReached.
|
||||
explicit Termination(Limit limit, std::string detail = {});
|
||||
|
||||
TerminationReason reason;
|
||||
|
||||
// Is set iff reason is kLimitReached.
|
||||
// Is set iff reason is kFeasible or kNoSolutionFound.
|
||||
std::optional<Limit> limit;
|
||||
|
||||
// Additional typically solver specific information about termination.
|
||||
@@ -263,11 +266,23 @@ struct Termination {
|
||||
// Limit::kUndetermined is used when the cause cannot be determined.
|
||||
std::string detail;
|
||||
|
||||
// Returns true if a limit was reached (i.e. if reason is kFeasible or
|
||||
// kNoSolutionFound, and limit is not empty).
|
||||
bool limit_reached() const;
|
||||
|
||||
// Will CHECK fail on invalid input, if reason is unspecified, if limit is
|
||||
// set when reason is not LIMIT_REACHED, or if limit is unspecified when
|
||||
// reason is LIMIT_REACHED (see solution_validator.h).
|
||||
// set when reason is not TERMINATION_REASON_FEASIBLE or
|
||||
// TERMINATION_REASON_NO_SOLUTION_FOUND, or if limit is unspecified when
|
||||
// reason is TERMINATION_REASON_FEASIBLE or
|
||||
// TERMINATION_REASON_NO_SOLUTION_FOUND (see solution_validator.h).
|
||||
static Termination FromProto(const TerminationProto& termination_proto);
|
||||
|
||||
// Sets the reason to kFeasible
|
||||
static Termination Feasible(Limit limit, std::string detail = {});
|
||||
|
||||
// Sets the reason to kNoSolutionFound
|
||||
static Termination NoSolutionFound(Limit limit, std::string detail = {});
|
||||
|
||||
TerminationProto ToProto() const;
|
||||
std::string ToString() const;
|
||||
};
|
||||
@@ -330,10 +345,16 @@ struct SolveResult {
|
||||
// if there are no primal feasible solutions.
|
||||
double objective_value() const;
|
||||
|
||||
// A bound on the best possible objective value.
|
||||
double best_objective_bound() const;
|
||||
|
||||
// The variable values from the best primal feasible solution. Will CHECK fail
|
||||
// if there are no primal feasible solutions.
|
||||
const VariableMap<double>& variable_values() const;
|
||||
|
||||
// Returns true only if the problem has been shown to be feasible and bounded.
|
||||
bool bounded() const;
|
||||
|
||||
// Indicates if at least one primal ray is available.
|
||||
//
|
||||
// This is NOT guaranteed to be true when termination.reason is
|
||||
|
||||
@@ -51,7 +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;
|
||||
reserved 5;
|
||||
|
||||
// GNU Linear Programming Kit (GLPK).
|
||||
//
|
||||
@@ -69,7 +69,7 @@ enum SolverTypeProto {
|
||||
// for details.
|
||||
SOLVER_TYPE_GLPK = 6;
|
||||
|
||||
SOLVER_TYPE_RESERVED_SEVEN = 7;
|
||||
reserved 7;
|
||||
}
|
||||
|
||||
// Selects an algorithm for solving linear programs.
|
||||
@@ -172,9 +172,10 @@ message SolveParametersProto {
|
||||
// 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.
|
||||
// On an early stop, the solver returns termination reason
|
||||
// LIMIT_NO_SOLUTION_FOUND 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.
|
||||
@@ -183,17 +184,20 @@ message SolveParametersProto {
|
||||
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.
|
||||
// with termination reason LIMIT_FEASIBLE or LIMIT_NO_SOLUTION_FOUND and
|
||||
// limit LIMIT_OBJECTIVE.
|
||||
// TODO(b/214567536): maybe it should only be LIMIT_FEASIBLE.
|
||||
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.
|
||||
// good, with termination reason LIMIT_FEASIBLE or LIMIT_NO_SOLUTION_FOUND 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
|
||||
// termination reason LIMIT_FEASIBLE 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.
|
||||
|
||||
@@ -145,9 +145,15 @@ enum TerminationReasonProto {
|
||||
// they are responsible for dealing with the numerical imprecision.
|
||||
TERMINATION_REASON_IMPRECISE = 5;
|
||||
|
||||
// The optimizer reached some kind of limit. Partial solution information
|
||||
// may be available. See SolveResultProto.limit_detail for more detail.
|
||||
TERMINATION_REASON_LIMIT_REACHED = 6;
|
||||
// The optimizer reached some kind of limit and a primal feasible solution
|
||||
// is returned. See SolveResultProto.limit_detail for detailed description of
|
||||
// the kind of limit that was reached.
|
||||
TERMINATION_REASON_FEASIBLE = 9;
|
||||
|
||||
// The optimizer reached some kind of limit and it did not find a primal
|
||||
// feasible solution. See SolveResultProto.limit_detail for detailed
|
||||
// description of the kind of limit that was reached.
|
||||
TERMINATION_REASON_NO_SOLUTION_FOUND = 6;
|
||||
|
||||
// The algorithm stopped because it encountered unrecoverable numerical
|
||||
// error. No solution information is available.
|
||||
@@ -158,8 +164,8 @@ enum TerminationReasonProto {
|
||||
TERMINATION_REASON_OTHER_ERROR = 8;
|
||||
}
|
||||
|
||||
// When a Solve() stops early with TerminationReasonProto LIMIT_REACHED, the
|
||||
// specific limit that was hit.
|
||||
// When a Solve() stops early with TerminationReasonProto FEASIBLE or
|
||||
// NO_SOLUTION_FOUND, the specific limit that was hit.
|
||||
enum LimitProto {
|
||||
// Used as a null value when we terminated not from a limit (e.g.
|
||||
// TERMINATION_REASON_OPTIMAL).
|
||||
@@ -223,9 +229,10 @@ enum LimitProto {
|
||||
message TerminationProto {
|
||||
TerminationReasonProto reason = 1;
|
||||
|
||||
// Is LIMIT_UNSPECIFIED unless reason is TERMINATION_REASON_LIMIT_REACHED.
|
||||
// Not all solvers can always determine the limit which caused termination,
|
||||
// LIMIT_UNDETERMINED is used when the cause cannot be determined.
|
||||
// Is LIMIT_UNSPECIFIED unless reason is TERMINATION_REASON_FEASIBLE or
|
||||
// TERMINATION_REASON_NO_SOLUTION_FOUND. Not all solvers can always determine
|
||||
// the limit which caused termination, LIMIT_UNDETERMINED is used when the
|
||||
// cause cannot be determined.
|
||||
LimitProto limit = 2;
|
||||
|
||||
// Additional typically solver specific information about termination.
|
||||
|
||||
@@ -258,7 +258,7 @@ GetTerminationAndStats(const bool is_interrupted, const bool maximize,
|
||||
}
|
||||
break;
|
||||
case MPSOLVER_FEASIBLE:
|
||||
termination = TerminateForLimit(
|
||||
termination = FeasibleTermination(
|
||||
is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
|
||||
response.status_str());
|
||||
solve_stats.mutable_problem_status()->set_primal_status(
|
||||
@@ -271,7 +271,7 @@ GetTerminationAndStats(const bool is_interrupted, const bool maximize,
|
||||
}
|
||||
break;
|
||||
case MPSOLVER_NOT_SOLVED:
|
||||
termination = TerminateForLimit(
|
||||
termination = NoSolutionFoundTermination(
|
||||
is_interrupted ? LIMIT_INTERRUPTED : LIMIT_UNDETERMINED,
|
||||
response.status_str());
|
||||
break;
|
||||
|
||||
@@ -98,15 +98,26 @@ absl::StatusOr<TerminationProto> BuildTermination(
|
||||
case glop::ProblemStatus::INFEASIBLE_OR_UNBOUNDED:
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED);
|
||||
case glop::ProblemStatus::INIT:
|
||||
case glop::ProblemStatus::PRIMAL_FEASIBLE:
|
||||
case glop::ProblemStatus::DUAL_FEASIBLE:
|
||||
// Glop may flip the `interrupt_solve` atomic when it is terminated for a
|
||||
// reason other than interruption so we should ignore its value. Instead
|
||||
// we use the interrupter.
|
||||
return TerminateForLimit(interrupter != nullptr &&
|
||||
interrupter->IsInterrupted()
|
||||
? LIMIT_INTERRUPTED
|
||||
: LIMIT_UNDETERMINED);
|
||||
// A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
|
||||
// comments in FillSolution).
|
||||
return NoSolutionFoundTermination(interrupter != nullptr &&
|
||||
interrupter->IsInterrupted()
|
||||
? LIMIT_INTERRUPTED
|
||||
: LIMIT_UNDETERMINED);
|
||||
case glop::ProblemStatus::PRIMAL_FEASIBLE:
|
||||
// Glop may flip the `interrupt_solve` atomic when it is terminated for a
|
||||
// reason other than interruption so we should ignore its value. Instead
|
||||
// we use the interrupter.
|
||||
// A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
|
||||
// comments in FillSolution).
|
||||
return FeasibleTermination(interrupter != nullptr &&
|
||||
interrupter->IsInterrupted()
|
||||
? LIMIT_INTERRUPTED
|
||||
: LIMIT_UNDETERMINED);
|
||||
case glop::ProblemStatus::IMPRECISE:
|
||||
return TerminateForReason(TERMINATION_REASON_IMPRECISE);
|
||||
case glop::ProblemStatus::ABNORMAL:
|
||||
|
||||
@@ -576,36 +576,44 @@ absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
switch (gscip_status) {
|
||||
case GScipOutput::USER_INTERRUPT:
|
||||
return TerminateForLimit(
|
||||
LIMIT_INTERRUPTED,
|
||||
LIMIT_INTERRUPTED, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: USER_INTERRUPT"));
|
||||
case GScipOutput::NODE_LIMIT:
|
||||
return TerminateForLimit(
|
||||
LIMIT_NODE, JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: NODE_LIMIT"));
|
||||
LIMIT_NODE, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: NODE_LIMIT"));
|
||||
case GScipOutput::TOTAL_NODE_LIMIT:
|
||||
return TerminateForLimit(
|
||||
LIMIT_NODE, JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: TOTAL_NODE_LIMIT"));
|
||||
LIMIT_NODE, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: TOTAL_NODE_LIMIT"));
|
||||
case GScipOutput::STALL_NODE_LIMIT:
|
||||
return TerminateForLimit(LIMIT_SLOW_PROGRESS, gscip_status_detail);
|
||||
return TerminateForLimit(LIMIT_SLOW_PROGRESS,
|
||||
/*feasible=*/has_feasible_solution,
|
||||
gscip_status_detail);
|
||||
case GScipOutput::TIME_LIMIT:
|
||||
return TerminateForLimit(LIMIT_TIME, gscip_status_detail);
|
||||
return TerminateForLimit(LIMIT_TIME, /*feasible=*/has_feasible_solution,
|
||||
gscip_status_detail);
|
||||
case GScipOutput::MEM_LIMIT:
|
||||
return TerminateForLimit(LIMIT_MEMORY, gscip_status_detail);
|
||||
return TerminateForLimit(LIMIT_MEMORY, /*feasible=*/has_feasible_solution,
|
||||
gscip_status_detail);
|
||||
case GScipOutput::SOL_LIMIT:
|
||||
return TerminateForLimit(
|
||||
LIMIT_SOLUTION, JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: SOL_LIMIT"));
|
||||
LIMIT_SOLUTION, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: SOL_LIMIT"));
|
||||
case GScipOutput::BEST_SOL_LIMIT:
|
||||
return TerminateForLimit(
|
||||
LIMIT_SOLUTION,
|
||||
LIMIT_SOLUTION, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: BEST_SOL_LIMIT"));
|
||||
case GScipOutput::RESTART_LIMIT:
|
||||
return TerminateForLimit(
|
||||
LIMIT_OTHER, JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: RESTART_LIMIT"));
|
||||
LIMIT_OTHER, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: RESTART_LIMIT"));
|
||||
case GScipOutput::OPTIMAL:
|
||||
return TerminateForReason(
|
||||
TERMINATION_REASON_OPTIMAL,
|
||||
@@ -617,7 +625,8 @@ absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
"underlying gSCIP status: GAP_LIMIT"));
|
||||
case GScipOutput::INFEASIBLE:
|
||||
if (had_cutoff) {
|
||||
return TerminateForLimit(LIMIT_CUTOFF, gscip_status_detail);
|
||||
return TerminateForLimit(LIMIT_CUTOFF,
|
||||
/*feasible=*/false, gscip_status_detail);
|
||||
} else {
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE,
|
||||
gscip_status_detail);
|
||||
@@ -647,8 +656,9 @@ absl::StatusOr<TerminationProto> ConvertTerminationReason(
|
||||
|
||||
case GScipOutput::TERMINATE:
|
||||
return TerminateForLimit(
|
||||
LIMIT_INTERRUPTED, JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: TERMINATE"));
|
||||
LIMIT_INTERRUPTED, /*feasible=*/has_feasible_solution,
|
||||
JoinDetails(gscip_status_detail,
|
||||
"underlying gSCIP status: TERMINATE"));
|
||||
case GScipOutput::INVALID_SOLVER_PARAMETERS:
|
||||
return absl::InvalidArgumentError(gscip_status_detail);
|
||||
case GScipOutput::UNKNOWN:
|
||||
|
||||
@@ -439,23 +439,40 @@ absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
|
||||
"Gurobi status GRB_INF_OR_UNBD");
|
||||
case GRB_CUTOFF:
|
||||
return TerminateForLimit(LIMIT_CUTOFF, "Gurobi status GRB_CUTOFF");
|
||||
return TerminateForLimit(LIMIT_CUTOFF,
|
||||
/*feasible=*/false, "Gurobi status GRB_CUTOFF");
|
||||
case GRB_ITERATION_LIMIT:
|
||||
return TerminateForLimit(LIMIT_ITERATION);
|
||||
return TerminateForLimit(
|
||||
LIMIT_ITERATION,
|
||||
/*feasible=*/solution_claims.primal_feasible_solution_exists);
|
||||
case GRB_NODE_LIMIT:
|
||||
return TerminateForLimit(LIMIT_NODE);
|
||||
return TerminateForLimit(
|
||||
LIMIT_NODE,
|
||||
/*feasible=*/solution_claims.primal_feasible_solution_exists);
|
||||
case GRB_TIME_LIMIT:
|
||||
return TerminateForLimit(LIMIT_TIME);
|
||||
return TerminateForLimit(
|
||||
LIMIT_TIME,
|
||||
/*feasible=*/solution_claims.primal_feasible_solution_exists);
|
||||
case GRB_SOLUTION_LIMIT:
|
||||
return TerminateForLimit(LIMIT_SOLUTION);
|
||||
return TerminateForLimit(
|
||||
LIMIT_SOLUTION,
|
||||
/*feasible=*/solution_claims.primal_feasible_solution_exists);
|
||||
case GRB_INTERRUPTED:
|
||||
return TerminateForLimit(LIMIT_INTERRUPTED);
|
||||
return TerminateForLimit(
|
||||
LIMIT_INTERRUPTED,
|
||||
/*feasible=*/solution_claims.primal_feasible_solution_exists);
|
||||
case GRB_NUMERIC:
|
||||
return TerminateForReason(TERMINATION_REASON_NUMERICAL_ERROR);
|
||||
case GRB_SUBOPTIMAL:
|
||||
return TerminateForReason(TERMINATION_REASON_IMPRECISE);
|
||||
case GRB_USER_OBJ_LIMIT:
|
||||
return TerminateForLimit(LIMIT_OBJECTIVE);
|
||||
// TODO(b/214567536): maybe we should override
|
||||
// solution_claims.primal_feasible_solution_exists to true or false
|
||||
// depending on whether objective_limit and best_bound_limit triggered
|
||||
// this. Not sure if it's possible to detect this though.
|
||||
return TerminateForLimit(
|
||||
LIMIT_OBJECTIVE,
|
||||
/*feasible=*/solution_claims.primal_feasible_solution_exists);
|
||||
case GRB_LOADED:
|
||||
return absl::InternalError(
|
||||
"Error creating termination reason, unexpected gurobi status code "
|
||||
|
||||
@@ -138,11 +138,11 @@ absl::StatusOr<TerminationProto> ConvertReason(
|
||||
return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
|
||||
pdlp_detail);
|
||||
case pdlp::TERMINATION_REASON_TIME_LIMIT:
|
||||
return TerminateForLimit(LIMIT_TIME, pdlp_detail);
|
||||
return NoSolutionFoundTermination(LIMIT_TIME, pdlp_detail);
|
||||
case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
|
||||
return TerminateForLimit(LIMIT_ITERATION, pdlp_detail);
|
||||
return NoSolutionFoundTermination(LIMIT_ITERATION, pdlp_detail);
|
||||
case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
|
||||
return TerminateForLimit(LIMIT_OTHER, pdlp_detail);
|
||||
return NoSolutionFoundTermination(LIMIT_OTHER, pdlp_detail);
|
||||
case pdlp::TERMINATION_REASON_NUMERICAL_ERROR:
|
||||
return TerminateForReason(TERMINATION_REASON_NUMERICAL_ERROR,
|
||||
pdlp_detail);
|
||||
|
||||
@@ -47,11 +47,17 @@ absl::Status ValidateTermination(const TerminationProto& termination) {
|
||||
if (termination.reason() == TERMINATION_REASON_UNSPECIFIED) {
|
||||
return absl::InvalidArgumentError("termination reason must be specified");
|
||||
}
|
||||
if (termination.reason() == TERMINATION_REASON_LIMIT_REACHED) {
|
||||
if (termination.reason() == TERMINATION_REASON_FEASIBLE ||
|
||||
termination.reason() == TERMINATION_REASON_NO_SOLUTION_FOUND) {
|
||||
if (termination.limit() == LIMIT_UNSPECIFIED) {
|
||||
return absl::InvalidArgumentError(
|
||||
"for reason TERMINATION_REASON_LIMIT_REACHED, limit must be "
|
||||
"specified");
|
||||
absl::StrCat("for reason ", ProtoEnumToString(termination.reason()),
|
||||
", limit must be specified"));
|
||||
}
|
||||
if (termination.limit() == LIMIT_CUTOFF &&
|
||||
termination.reason() == TERMINATION_REASON_FEASIBLE) {
|
||||
return absl::InvalidArgumentError(
|
||||
"For LIMIT_CUTOFF expected no solutions");
|
||||
}
|
||||
} else {
|
||||
if (termination.limit() != LIMIT_UNSPECIFIED) {
|
||||
@@ -233,17 +239,27 @@ absl::Status ValidateTerminationConsistency(const SolveResultProto& result) {
|
||||
case TERMINATION_REASON_IMPRECISE:
|
||||
// TODO(b/211679884): update when imprecise solutions are added.
|
||||
return absl::OkStatus();
|
||||
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));
|
||||
case TERMINATION_REASON_FEASIBLE:
|
||||
RETURN_IF_ERROR(CheckPrimalStatusIs(status, FEASIBILITY_STATUS_FEASIBLE));
|
||||
RETURN_IF_ERROR(CheckHasPrimalSolution(result));
|
||||
RETURN_IF_ERROR(
|
||||
CheckDualStatusIsNot(status, FEASIBILITY_STATUS_INFEASIBLE));
|
||||
// Primal requirement implies primal solution-status consistency so we
|
||||
// only check dual consistency.
|
||||
RETURN_IF_ERROR(CheckDualSolutionAndStatusConsistency(result));
|
||||
// Note if dual status was FEASIBILITY_STATUS_INFEASIBLE, then we would
|
||||
// have TERMINATION_REASON_UNBOUNDED (For MIP this follows tha assumption
|
||||
// that every floating point ray can be scaled to be integer).
|
||||
return absl::OkStatus();
|
||||
case TERMINATION_REASON_NO_SOLUTION_FOUND:
|
||||
RETURN_IF_ERROR(RequireNoPrimalFeasibleSolution(result));
|
||||
RETURN_IF_ERROR(
|
||||
CheckPrimalStatusIsNot(status, FEASIBILITY_STATUS_INFEASIBLE));
|
||||
// Primal requirement implies primal solution-status consistency so we
|
||||
// only check dual consistency.
|
||||
RETURN_IF_ERROR(CheckDualSolutionAndStatusConsistency(result));
|
||||
// Note if primal status was FEASIBILITY_STATUS_INFEASIBLE, then we would
|
||||
// have TERMINATION_REASON_INFEASIBLE.
|
||||
return absl::OkStatus();
|
||||
case TERMINATION_REASON_NUMERICAL_ERROR:
|
||||
case TERMINATION_REASON_OTHER_ERROR: {
|
||||
|
||||
@@ -76,6 +76,32 @@ absl::Status CheckPrimalStatusIs(const ProblemStatusProto& status,
|
||||
ProtoEnumToString(actual_status)));
|
||||
}
|
||||
|
||||
// Assumes ValidateProblemStatus(status) is ok.
|
||||
absl::Status CheckPrimalStatusIsNot(
|
||||
const ProblemStatusProto& status,
|
||||
const FeasibilityStatusProto forbidden_status) {
|
||||
const FeasibilityStatusProto actual_status = status.primal_status();
|
||||
if (actual_status != forbidden_status) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("expected problem_status.primal_status != ",
|
||||
ProtoEnumToString(forbidden_status)));
|
||||
}
|
||||
|
||||
// Assumes ValidateProblemStatus(status) is ok.
|
||||
absl::Status CheckDualStatusIsNot(
|
||||
const ProblemStatusProto& status,
|
||||
const FeasibilityStatusProto forbidden_status) {
|
||||
const FeasibilityStatusProto actual_status = status.dual_status();
|
||||
if (actual_status != forbidden_status) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("expected problem_status.dual_status != ",
|
||||
ProtoEnumToString(forbidden_status)));
|
||||
}
|
||||
|
||||
// Assumes ValidateProblemStatus(status) is ok.
|
||||
absl::Status CheckDualStatusIs(const ProblemStatusProto& status,
|
||||
const FeasibilityStatusProto required_status,
|
||||
|
||||
@@ -28,6 +28,11 @@ absl::Status ValidateSolveStats(const SolveStatsProto& solve_stats);
|
||||
absl::Status CheckPrimalStatusIs(const ProblemStatusProto& status,
|
||||
FeasibilityStatusProto required_status);
|
||||
|
||||
// Returns absl::Ok only if status.primal_status != forbidden_status. Assumes
|
||||
// validateProblemStatus(status) returns absl::Ok.
|
||||
absl::Status CheckPrimalStatusIsNot(const ProblemStatusProto& status,
|
||||
FeasibilityStatusProto forbidden_status);
|
||||
|
||||
// If primal_or_dual_infeasible_also_ok is false, returns absl::Ok only if
|
||||
// status.dual_status = required_status. If primal_or_dual_infeasible_also_ok
|
||||
// is true, it returns absl::Ok when status.dual_status = required_status and
|
||||
@@ -36,6 +41,12 @@ absl::Status CheckPrimalStatusIs(const ProblemStatusProto& status,
|
||||
absl::Status CheckDualStatusIs(const ProblemStatusProto& status,
|
||||
FeasibilityStatusProto required_status,
|
||||
bool primal_or_dual_infeasible_also_ok = false);
|
||||
|
||||
// Returns absl::Ok only if status.dual_status != forbidden_status. Assumes
|
||||
// validateProblemStatus(status) returns absl::Ok.
|
||||
absl::Status CheckDualStatusIsNot(const ProblemStatusProto& status,
|
||||
FeasibilityStatusProto forbidden_status);
|
||||
|
||||
} // namespace math_opt
|
||||
} // namespace operations_research
|
||||
|
||||
|
||||
Reference in New Issue
Block a user