sync math_opt

This commit is contained in:
Corentin Le Molgat
2022-01-17 09:27:29 +01:00
parent 44fc0c45e4
commit e35faf68cf
19 changed files with 332 additions and 115 deletions

View File

@@ -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;

View File

@@ -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 = {});

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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"

View File

@@ -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;

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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;

View File

@@ -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:

View File

@@ -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:

View File

@@ -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 "

View File

@@ -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);

View File

@@ -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: {

View File

@@ -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,

View File

@@ -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