27#include "absl/base/nullability.h"
28#include "absl/log/log.h"
29#include "absl/memory/memory.h"
30#include "absl/status/status.h"
31#include "absl/status/statusor.h"
32#include "absl/strings/str_cat.h"
33#include "absl/strings/str_join.h"
34#include "absl/strings/str_split.h"
35#include "absl/strings/string_view.h"
36#include "absl/time/time.h"
64using pdlp::PrimalAndDualSolution;
65using pdlp::PrimalDualHybridGradientParams;
66using pdlp::SolverResult;
73 auto result = absl::WrapUnique(
new PdlpSolver);
81 std::vector<std::string> warnings;
90 absl::ToDoubleSeconds(
94 warnings.push_back(
"parameter node_limit not supported for PDLP");
97 warnings.push_back(
"parameter cutoff_limit not supported for PDLP");
100 warnings.push_back(
"parameter best_objective_limit not supported for PDLP");
103 warnings.push_back(
"parameter best_bound_limit not supported for PDLP");
106 warnings.push_back(
"parameter solution_limit not supported for PDLP");
109 warnings.push_back(
"parameter random_seed not supported for PDLP");
113 warnings.push_back(
"parameter lp_algorithm not supported for PDLP");
116 warnings.push_back(
"parameter presolve not supported for PDLP");
119 warnings.push_back(
"parameter cuts not supported for PDLP");
122 warnings.push_back(
"parameter heuristics not supported for PDLP");
125 warnings.push_back(
"parameter scaling not supported for PDLP");
128 const int64_t limit = std::min<int64_t>(std::numeric_limits<int32_t>::max(),
131 static_cast<int32_t
>(limit));
134 if (!warnings.empty()) {
135 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
142absl::StatusOr<TerminationProto> ConvertReason(
144 const bool is_maximize,
const double primal_objective,
145 const double dual_objective) {
146 switch (pdlp_reason) {
150 absl::StrCat(
"PDLP unspecified reason "
151 "(TERMINATION_REASON_UNSPECIFIED): ",
171 std::nullopt, pdlp_detail);
175 std::nullopt, pdlp_detail);
179 std::nullopt, pdlp_detail);
186 std::nullopt, pdlp_detail);
190 return absl::InternalError(
191 absl::StrCat(
"Invalid problem sent to PDLP solver "
192 "(TERMINATION_REASON_INVALID_PROBLEM): ",
195 return absl::InvalidArgumentError(
196 absl::StrCat(
"PDLP solution hint invalid "
197 "(TERMINATION_REASON_INVALID_INITIAL_SOLUTION): ",
201 return absl::InvalidArgumentError(absl::StrCat(
202 "PDLP parameters invalid (TERMINATION_REASON_INVALID_PARAMETER): ",
213 return absl::InvalidArgumentError(absl::StrCat(
219absl::StatusOr<SolveResultProto> PdlpSolver::MakeSolveResult(
220 const pdlp::SolverResult& pdlp_result,
225 const double objective_scaling_factor =
226 pdlp_bridge_.pdlp_lp().objective_scaling_factor;
227 const bool is_maximize = objective_scaling_factor < 0.0;
228 SolveResultProto result;
229 const std::optional<pdlp::ConvergenceInformation> convergence_information =
231 pdlp_result.solve_log.solution_type());
232 if (convergence_information.has_value()) {
233 *result.mutable_pdlp_output()->mutable_convergence_information() =
234 *convergence_information;
237 if (convergence_information.has_value()) {
238 objective_bounds.set_primal_bound(
239 convergence_information->primal_objective());
240 objective_bounds.set_dual_bound(convergence_information->dual_objective());
243 ConvertReason(pdlp_result.solve_log.termination_reason(),
244 pdlp_result.solve_log.termination_string(),
245 is_maximize, objective_bounds.primal_bound(),
246 objective_bounds.dual_bound()));
249 absl::Seconds(pdlp_result.solve_log.solve_time_sec())));
250 result.mutable_solve_stats()->set_first_order_iterations(
251 pdlp_result.solve_log.iteration_count());
253 switch (pdlp_result.solve_log.termination_reason()) {
260 SolutionProto* solution_proto = result.add_solutions();
262 auto maybe_primal = pdlp_bridge_.PrimalVariablesToProto(
263 pdlp_result.primal_solution, model_params.variable_values_filter());
265 PrimalSolutionProto* primal_proto =
266 solution_proto->mutable_primal_solution();
268 *primal_proto->mutable_variable_values() = *std::move(maybe_primal);
273 if (pdlp_result.solve_log.termination_reason() ==
277 if (convergence_information.has_value()) {
278 primal_proto->set_objective_value(
279 convergence_information->primal_objective());
283 auto maybe_dual = pdlp_bridge_.DualVariablesToProto(
284 pdlp_result.dual_solution, model_params.dual_values_filter());
286 auto maybe_reduced = pdlp_bridge_.ReducedCostsToProto(
287 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
289 DualSolutionProto* dual_proto = solution_proto->mutable_dual_solution();
291 *dual_proto->mutable_dual_values() = *std::move(maybe_dual);
292 *dual_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
294 if (pdlp_result.solve_log.termination_reason() ==
298 if (convergence_information.has_value()) {
299 dual_proto->set_objective_value(
300 convergence_information->dual_objective());
308 auto maybe_dual = pdlp_bridge_.DualVariablesToProto(
309 pdlp_result.dual_solution, model_params.dual_values_filter());
311 auto maybe_reduced = pdlp_bridge_.ReducedCostsToProto(
312 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
314 DualRayProto* dual_ray_proto = result.add_dual_rays();
315 *dual_ray_proto->mutable_dual_values() = *std::move(maybe_dual);
316 *dual_ray_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
322 auto maybe_primal = pdlp_bridge_.PrimalVariablesToProto(
323 pdlp_result.primal_solution, model_params.variable_values_filter());
325 PrimalRayProto* primal_ray_proto = result.add_primal_rays();
326 *primal_ray_proto->mutable_variable_values() = *std::move(maybe_primal);
349 message_cb !=
nullptr));
355 std::atomic<bool> interrupt =
false;
357 interrupter, [&]() { interrupt =
true; });
359 std::optional<PrimalAndDualSolution> initial_solution;
361 initial_solution = pdlp_bridge_.SolutionHintToWarmStart(
365 std::function<void(
const std::string&)> pdlp_callback =
nullptr;
366 if (message_cb !=
nullptr) {
367 pdlp_callback = [&message_cb](
const std::string& message) {
368 message_cb(absl::StrSplit(message,
'\n'));
373 PrimalDualHybridGradient(pdlp_bridge_.pdlp_lp(), pdlp_params,
374 initial_solution, &interrupt, pdlp_callback);
375 return MakeSolveResult(pdlp_result, model_parameters);
382absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
386 return absl::UnimplementedError(
387 "PDLP does not provide a method to compute an infeasible subsystem");
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
const ::operations_research::math_opt::SolutionHintProto & solution_hints(int index) const
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
static absl::StatusOr< pdlp::PrimalDualHybridGradientParams > MergeParameters(const SolveParametersProto ¶meters, bool has_message_callback)
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
::operations_research::math_opt::EmphasisProto presolve() const
bool has_cutoff_limit() const
::int32_t threads() const
bool has_objective_limit() const
bool has_random_seed() const
bool has_solution_limit() const
::operations_research::math_opt::EmphasisProto scaling() const
::operations_research::math_opt::EmphasisProto heuristics() const
bool has_node_limit() const
bool enable_output() const
::operations_research::math_opt::LPAlgorithmProto lp_algorithm() const
const ::operations_research::pdlp::PrimalDualHybridGradientParams & pdlp() const
::int64_t iteration_limit() const
bool has_best_bound_limit() const
const ::google::protobuf::Duration & time_limit() const
bool has_time_limit() const
bool has_iteration_limit() const
::operations_research::math_opt::EmphasisProto cuts() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
::operations_research::pdlp::TerminationCriteria *PROTOBUF_NONNULL mutable_termination_criteria()
void set_num_threads(::int32_t value)
void MergeFrom(const PrimalDualHybridGradientParams &from)
void set_verbosity_level(::int32_t value)
void set_time_sec_limit(double value)
void set_iteration_limit(::int32_t value)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
@ TERMINATION_REASON_OTHER_ERROR
@ TERMINATION_REASON_NUMERICAL_ERROR
@ TERMINATION_REASON_UNSPECIFIED
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ SOLUTION_STATUS_UNDETERMINED
@ SOLUTION_STATUS_FEASIBLE
@ LP_ALGORITHM_FIRST_ORDER
@ LP_ALGORITHM_UNSPECIFIED
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
@ FEASIBILITY_STATUS_UNDETERMINED
@ FEASIBILITY_STATUS_INFEASIBLE
TerminationProto InfeasibleOrUnboundedTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
constexpr SupportedProblemStructures kPdlpSupportedStructures
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
ObjectiveBoundsProto MakeTrivialBounds(const bool is_maximize)
std::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
@ TERMINATION_REASON_DUAL_INFEASIBLE
@ TERMINATION_REASON_TIME_LIMIT
@ TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT
@ TERMINATION_REASON_PRIMAL_OR_DUAL_INFEASIBLE
@ TERMINATION_REASON_INVALID_PROBLEM
@ TERMINATION_REASON_PRIMAL_INFEASIBLE
@ TERMINATION_REASON_OPTIMAL
@ TERMINATION_REASON_ITERATION_LIMIT
@ TERMINATION_REASON_INVALID_INITIAL_SOLUTION
@ TERMINATION_REASON_UNSPECIFIED
@ TERMINATION_REASON_INVALID_PARAMETER
@ TERMINATION_REASON_NUMERICAL_ERROR
@ TERMINATION_REASON_OTHER
@ TERMINATION_REASON_INTERRUPTED_BY_USER
std::string ProtoEnumToString(ProtoEnumType enum_value)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)