16#include <initializer_list>
25#include "absl/log/check.h"
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/escaping.h"
29#include "absl/strings/str_cat.h"
30#include "absl/strings/str_join.h"
31#include "absl/strings/string_view.h"
32#include "absl/time/time.h"
33#include "absl/types/span.h"
39#include "ortools/math_opt/result.pb.h"
40#include "ortools/math_opt/solution.pb.h"
48constexpr double kInf = std::numeric_limits<double>::infinity();
52 ObjectiveBoundsProto
proto;
59 const ObjectiveBoundsProto& objective_bounds_proto) {
61 result.
primal_bound = objective_bounds_proto.primal_bound();
62 result.
dual_bound = objective_bounds_proto.dual_bound();
68 ostr <<
"{primal_bound: "
70 ostr <<
", dual_bound: "
77 std::ostringstream stream;
119 return "undetermined";
134 return absl::MakeConstSpan(kFeasibilityStatus);
147 return "infeasible_or_unbounded";
153 return "no_solution_found";
155 return "numerical_error";
157 return "other_error";
174 return absl::MakeConstSpan(kTerminationReasonValues);
180 return "undetermined";
198 return "interrupted";
200 return "slow_progress";
208 static constexpr Limit kLimitValues[] = {
213 return absl::MakeConstSpan(kLimitValues);
222Termination Termination::Optimal(
const double primal_objective_value,
223 const double dual_objective_value,
224 const std::string
detail) {
227 termination.objective_bounds.primal_bound = primal_objective_value;
228 termination.objective_bounds.dual_bound = dual_objective_value;
235 const std::string
detail) {
239Termination Termination::Infeasible(
const bool is_maximize,
241 const std::string
detail) {
248 termination.problem_status.dual_status = dual_feasibility_status;
252Termination Termination::InfeasibleOrUnbounded(
254 const std::string
detail) {
258 termination.problem_status.dual_status = dual_feasibility_status;
260 termination.problem_status.primal_or_dual_infeasible =
true;
265Termination Termination::Unbounded(
const bool is_maximize,
266 const std::string
detail) {
274Termination Termination::NoSolutionFound(
275 const bool is_maximize,
Limit limit,
276 const std::optional<double> optional_dual_objective,
277 const std::string
detail) {
282 if (optional_dual_objective.has_value()) {
283 termination.objective_bounds.dual_bound = *optional_dual_objective;
290Termination Termination::Feasible(
291 const bool is_maximize,
const Limit limit,
292 const double finite_primal_objective,
293 const std::optional<double> optional_dual_objective,
294 const std::string
detail) {
297 termination.objective_bounds.primal_bound = finite_primal_objective;
299 if (optional_dual_objective.has_value()) {
300 termination.objective_bounds.dual_bound = *optional_dual_objective;
307Termination Termination::Cutoff(
const bool is_maximize,
308 const std::string
detail) {
313TerminationProto Termination::Proto()
const {
314 TerminationProto
proto;
329 if (this->reason ==
reason)
return absl::OkStatus();
331 <<
"expected termination reason '" <<
reason <<
"' but got " << *
this;
334absl::Status Termination::EnsureReasonIsAnyOf(
335 std::initializer_list<TerminationReason> reasons)
const {
337 if (this->reason ==
reason)
return absl::OkStatus();
340 <<
"expected termination reason in {"
341 << absl::StrJoin(reasons,
", ",
343 absl::StrAppend(out,
"'");
344 absl::StreamFormatter()(out,
reason);
345 absl::StrAppend(out,
"'");
347 <<
"} but got " << *
this;
350absl::Status Termination::EnsureIsOptimal()
const {
354bool Termination::IsOptimalOrFeasible()
const {
360 return EnsureReasonIsAnyOf(
364bool Termination::IsOptimal()
const {
368absl::StatusOr<Termination> Termination::FromProto(
369 const TerminationProto& termination_proto) {
370 const std::optional<TerminationReason>
reason =
372 if (!
reason.has_value()) {
373 return absl::InvalidArgumentError(
"reason must be specified");
376 termination_proto.detail());
379 result.problem_status,
381 _ <<
"invalid problem_status");
382 result.objective_bounds =
393 ostr <<
", detail: " <<
'"' << absl::CEscape(
termination.detail) <<
'"';
395 ostr <<
", problem_status: " <<
termination.problem_status;
396 ostr <<
", objective_bounds: " <<
termination.objective_bounds;
401std::string Termination::ToString()
const {
402 std::ostringstream stream;
408 ProblemStatusProto
proto;
416 const ProblemStatusProto& problem_status_proto) {
420 return absl::InvalidArgumentError(
"primal_status must be specified");
422 const std::optional<FeasibilityStatus>
dual_status =
425 return absl::InvalidArgumentError(
"dual_status must be specified");
429 .primal_or_dual_infeasible =
430 problem_status_proto.primal_or_dual_infeasible()};
436 ostr <<
", dual_status: " << problem_status.
dual_status;
437 ostr <<
", primal_or_dual_infeasible: "
444 std::ostringstream stream;
450 SolveStatsProto
proto;
453 <<
"invalid solve_time (value must be finite)";
462 const SolveStatsProto& solve_stats_proto) {
467 _ <<
"invalid solve_time");
471 result.
node_count = solve_stats_proto.node_count();
476 ostr <<
"{solve_time: " << solve_stats.
solve_time;
480 ostr <<
", node_count: " << solve_stats.
node_count;
486 std::ostringstream stream;
492 if (result.solver_specific_output_case() ==
493 SolveResultProto::SOLVER_SPECIFIC_OUTPUT_NOT_SET) {
494 return absl::OkStatus();
497 <<
"cannot set solver specific output twice, was already "
498 <<
static_cast<int>(result.solver_specific_output_case());
501absl::StatusOr<SolveResultProto> SolveResult::Proto()
const {
502 SolveResultProto result;
503 *result.mutable_termination() =
termination.Proto();
505 _ <<
"invalid solve_stats");
506 for (
const Solution&
solution : solutions) {
507 *result.add_solutions() =
solution.Proto();
509 for (
const PrimalRay& primal_ray : primal_rays) {
510 *result.add_primal_rays() = primal_ray.Proto();
512 for (
const DualRay& dual_ray : dual_rays) {
513 *result.add_dual_rays() = dual_ray.Proto();
516 if (gscip_solver_specific_output.ByteSizeLong() > 0) {
517 *result.mutable_gscip_output() = gscip_solver_specific_output;
519 if (pdlp_solver_specific_output.ByteSizeLong() > 0) {
520 *result.mutable_pdlp_output() = pdlp_solver_specific_output;
525TerminationProto UpgradedTerminationProtoForStatsMigration(
526 const SolveResultProto& solve_result_proto) {
528 termination.set_reason(solve_result_proto.termination().reason());
529 termination.set_limit(solve_result_proto.termination().limit());
530 termination.set_detail(solve_result_proto.termination().detail());
538absl::StatusOr<SolveResult> SolveResult::FromProto(
539 const ModelStorage*
model,
const SolveResultProto& solve_result_proto) {
542 Termination::FromProto(
547 UpgradedTerminationProtoForStatsMigration(solve_result_proto)),
548 _ <<
"invalid termination");
552 _ <<
"invalid solve_stats");
554 for (
int i = 0;
i < solve_result_proto.solutions_size(); ++
i) {
558 _ <<
"invalid solution at index " << i);
559 result.solutions.push_back(std::move(
solution));
561 for (
int i = 0;
i < solve_result_proto.primal_rays_size(); ++
i) {
565 _ <<
"invalid primal ray at index " << i);
566 result.primal_rays.push_back(std::move(primal_ray));
568 for (
int i = 0;
i < solve_result_proto.dual_rays_size(); ++
i) {
572 _ <<
"invalid dual ray at index " << i);
573 result.dual_rays.push_back(std::move(dual_ray));
575 switch (solve_result_proto.solver_specific_output_case()) {
576 case SolveResultProto::kGscipOutput:
577 result.gscip_solver_specific_output = solve_result_proto.gscip_output();
579 case SolveResultProto::kPdlpOutput:
580 result.pdlp_solver_specific_output = solve_result_proto.pdlp_output();
582 case SolveResultProto::SOLVER_SPECIFIC_OUTPUT_NOT_SET:
586 <<
"unexpected value of solver_specific_output_case "
587 << solve_result_proto.solver_specific_output_case();
590bool SolveResult::has_primal_feasible_solution()
const {
591 return !solutions.empty() && solutions[0].primal_solution.has_value() &&
592 (solutions[0].primal_solution->feasibility_status ==
596const PrimalSolution& SolveResult::best_primal_solution()
const {
597 CHECK(has_primal_feasible_solution());
598 return *solutions.front().primal_solution;
601double SolveResult::best_objective_bound()
const {
605double SolveResult::primal_bound()
const {
609double SolveResult::dual_bound()
const {
613double SolveResult::objective_value()
const {
614 CHECK(has_primal_feasible_solution());
615 return solutions[0].primal_solution->objective_value;
618double SolveResult::objective_value(
const Objective objective)
const {
619 CHECK(has_primal_feasible_solution());
620 return solutions[0].primal_solution->get_objective_value(objective);
623bool SolveResult::bounded()
const {
630 CHECK(has_primal_feasible_solution());
631 return solutions[0].primal_solution->variable_values;
636 return primal_rays[0].variable_values;
639bool SolveResult::has_dual_feasible_solution()
const {
640 return !solutions.empty() && solutions[0].dual_solution.has_value() &&
641 (solutions[0].dual_solution->feasibility_status ==
646 CHECK(has_dual_feasible_solution());
647 return solutions[0].dual_solution->dual_values;
651 CHECK(has_dual_feasible_solution());
652 return solutions[0].dual_solution->reduced_costs;
655 CHECK(has_dual_ray());
656 return dual_rays[0].dual_values;
660 CHECK(has_dual_ray());
661 return dual_rays[0].reduced_costs;
664bool SolveResult::has_basis()
const {
665 return !solutions.empty() && solutions[0].basis.has_value();
670 return solutions[0].basis->constraint_status;
675 return solutions[0].basis->variable_status;
681void PrintVectorSize(std::ostream& out,
const std::vector<T>& v) {
684 out << v.size() <<
" available";
690std::ostream&
operator<<(std::ostream& out,
const SolveResult& result) {
691 out <<
"{termination: " << result.termination
692 <<
", solve_stats: " << result.solve_stats <<
", solutions: ";
693 PrintVectorSize(out, result.solutions);
694 out <<
", primal_rays: ";
695 PrintVectorSize(out, result.primal_rays);
696 out <<
", dual_rays: ";
697 PrintVectorSize(out, result.dual_rays);
699 const std::string gscip_specific_output =
701 if (!gscip_specific_output.empty()) {
702 out <<
", gscip_solver_specific_output: " << gscip_specific_output;
#define RETURN_IF_ERROR(expr)
CpModelProto proto
The output proto.
TerminationReason termination
An object oriented wrapper for quadratic constraints in ModelStorage.
absl::Status CheckSolverSpecificOutputEmpty(const SolveResultProto &result)
absl::flat_hash_map< Variable, V > VariableMap
TerminationReason
The reason a call to Solve() terminates.
@ kOptimal
A provably optimal solution (up to numerical tolerances) has been found.
@ kInfeasible
The primal problem has no feasible solutions.
absl::flat_hash_map< LinearConstraint, V > LinearConstraintMap
std::optional< typename EnumProto< P >::Cpp > EnumFromProto(P proto_value)
std::ostream & operator<<(std::ostream &ostr, const IndicatorConstraint &constraint)
ProblemStatusProto GetProblemStatus(const SolveResultProto &solve_result)
ObjectiveBoundsProto GetObjectiveBounds(const SolveResultProto &solve_result)
@ kFeasible
Solver claims the solution is feasible.
Enum< E >::Proto EnumToProto(std::optional< E > value)
@ kMemory
The algorithm stopped because it ran out of memory.
@ kTime
The algorithm stopped after a user-specified computation time.
@ kNorm
The algorithm stopped because the norm of an iterate became too large.
@ kUndetermined
Solver does not claim a status.
@ kFeasible
Solver claims the problem is feasible.
@ kInfeasible
Solver claims the problem is infeasible.
std::string ProtobufShortDebugString(const P &message)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InternalErrorBuilder()
StatusBuilder InvalidArgumentErrorBuilder()
static absl::StatusOr< DualRay > FromProto(const ModelStorage *model, const DualRayProto &dual_ray_proto)
static absl::Span< const E > AllValues()
Returns all possible values of the enum.
static std::optional< absl::string_view > ToOptString(E value)
Bounds on the optimal objective value.
static ObjectiveBounds MakeTrivial(bool is_maximize)
ObjectiveBoundsProto Proto() const
static ObjectiveBounds MinimizeMakeTrivial()
static ObjectiveBounds MakeUnbounded(bool is_maximize)
static ObjectiveBounds MinimizeMakeUnbounded()
static ObjectiveBounds MakeOptimal(double objective_value)
Sets both bounds to objective_value.
static ObjectiveBounds FromProto(const ObjectiveBoundsProto &objective_bounds_proto)
static ObjectiveBounds MaximizeMakeTrivial()
static ObjectiveBounds MaximizeMakeUnbounded()
std::string ToString() const
static absl::StatusOr< PrimalRay > FromProto(const ModelStorage *model, const PrimalRayProto &primal_ray_proto)
ProblemStatusProto Proto() const
std::string ToString() const
FeasibilityStatus primal_status
Status for the primal problem.
bool primal_or_dual_infeasible
static absl::StatusOr< ProblemStatus > FromProto(const ProblemStatusProto &problem_status_proto)
Returns an error if the primal_status or dual_status is unspecified.
FeasibilityStatus dual_status
Status for the dual problem (or for the dual of a continuous relaxation).
static absl::StatusOr< Solution > FromProto(const ModelStorage *model, const SolutionProto &solution_proto)
int first_order_iterations
absl::StatusOr< SolveStatsProto > Proto() const
Will return an error if solve_time is not finite.
std::string ToString() const
absl::Duration solve_time
static absl::StatusOr< SolveStats > FromProto(const SolveStatsProto &solve_stats_proto)
Returns an error if converting the problem_status or solve_time fails.
All information regarding why a call to Solve() terminated.
Termination(bool is_maximize, TerminationReason reason, std::string detail={})
absl::Status EnsureIsOptimalOrFeasible() const
bool limit_reached() const
double objective_value() const
ProblemStatus problem_status
Feasibility statuses for primal and dual problems.
std::optional< Limit > limit
ObjectiveBounds objective_bounds
Bounds on the optimal objective value.
double objective_value
The value objective_vector^T * (solution - center_point).
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)