27#include "absl/memory/memory.h"
28#include "absl/status/status.h"
29#include "absl/status/statusor.h"
30#include "absl/strings/str_cat.h"
31#include "absl/strings/str_join.h"
32#include "absl/strings/str_split.h"
33#include "absl/strings/string_view.h"
34#include "absl/time/time.h"
38#include "ortools/math_opt/callback.pb.h"
42#include "ortools/math_opt/infeasible_subsystem.pb.h"
43#include "ortools/math_opt/model.pb.h"
44#include "ortools/math_opt/model_parameters.pb.h"
45#include "ortools/math_opt/model_update.pb.h"
46#include "ortools/math_opt/parameters.pb.h"
47#include "ortools/math_opt/result.pb.h"
48#include "ortools/math_opt/solution.pb.h"
50#include "ortools/math_opt/sparse_containers.pb.h"
55#include "ortools/pdlp/solve_log.pb.h"
56#include "ortools/pdlp/solvers.pb.h"
63using pdlp::PrimalAndDualSolution;
64using pdlp::PrimalDualHybridGradientParams;
65using pdlp::SolverResult;
69 auto result = absl::WrapUnique(
new PdlpSolver);
75 const SolveParametersProto&
parameters,
const bool has_message_callback) {
76 PrimalDualHybridGradientParams result;
77 std::vector<std::string> warnings;
78 if (
parameters.enable_output() || has_message_callback) {
79 result.set_verbosity_level(3);
85 result.mutable_termination_criteria()->set_time_sec_limit(
86 absl::ToDoubleSeconds(
90 warnings.push_back(
"parameter node_limit not supported for PDLP");
93 warnings.push_back(
"parameter cutoff_limit not supported for PDLP");
96 warnings.push_back(
"parameter best_objective_limit not supported for PDLP");
99 warnings.push_back(
"parameter best_bound_limit not supported for PDLP");
102 warnings.push_back(
"parameter solution_limit not supported for PDLP");
105 warnings.push_back(
"parameter random_seed not supported for PDLP");
107 if (
parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED &&
108 parameters.lp_algorithm() != LP_ALGORITHM_FIRST_ORDER) {
109 warnings.push_back(
"parameter lp_algorithm not supported for PDLP");
111 if (
parameters.presolve() != EMPHASIS_UNSPECIFIED) {
112 warnings.push_back(
"parameter presolve not supported for PDLP");
114 if (
parameters.cuts() != EMPHASIS_UNSPECIFIED) {
115 warnings.push_back(
"parameter cuts not supported for PDLP");
117 if (
parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
118 warnings.push_back(
"parameter heuristics not supported for PDLP");
120 if (
parameters.scaling() != EMPHASIS_UNSPECIFIED) {
121 warnings.push_back(
"parameter scaling not supported for PDLP");
124 const int64_t limit = std::min<int64_t>(std::numeric_limits<int32_t>::max(),
126 result.mutable_termination_criteria()->set_iteration_limit(
127 static_cast<int32_t
>(limit));
130 if (!warnings.empty()) {
131 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
138absl::StatusOr<TerminationProto> ConvertReason(
139 const pdlp::TerminationReason pdlp_reason, absl::string_view pdlp_detail,
140 const bool is_maximize,
const double primal_objective,
141 const double dual_objective) {
142 switch (pdlp_reason) {
143 case pdlp::TERMINATION_REASON_UNSPECIFIED:
145 is_maximize, TERMINATION_REASON_OTHER_ERROR,
146 absl::StrCat(
"PDLP unspecified reason "
147 "(TERMINATION_REASON_UNSPECIFIED): ",
151 case pdlp::TERMINATION_REASON_OPTIMAL:
154 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE:
157 FEASIBILITY_STATUS_UNDETERMINED,
159 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE:
162 FEASIBILITY_STATUS_INFEASIBLE,
164 case pdlp::TERMINATION_REASON_TIME_LIMIT:
166 is_maximize, LIMIT_TIME,
167 std::nullopt, pdlp_detail);
168 case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
170 is_maximize, LIMIT_ITERATION,
171 std::nullopt, pdlp_detail);
172 case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
174 is_maximize, LIMIT_OTHER,
175 std::nullopt, pdlp_detail);
176 case pdlp::TERMINATION_REASON_NUMERICAL_ERROR:
179 case pdlp::TERMINATION_REASON_INTERRUPTED_BY_USER:
181 is_maximize, LIMIT_INTERRUPTED,
182 std::nullopt, pdlp_detail);
183 case pdlp::TERMINATION_REASON_INVALID_PROBLEM:
186 return absl::InternalError(
187 absl::StrCat(
"Invalid problem sent to PDLP solver "
188 "(TERMINATION_REASON_INVALID_PROBLEM): ",
190 case pdlp::TERMINATION_REASON_INVALID_INITIAL_SOLUTION:
191 return absl::InvalidArgumentError(
192 absl::StrCat(
"PDLP solution hint invalid "
193 "(TERMINATION_REASON_INVALID_INITIAL_SOLUTION): ",
195 case pdlp::TERMINATION_REASON_INVALID_PARAMETER:
197 return absl::InvalidArgumentError(absl::StrCat(
198 "PDLP parameters invalid (TERMINATION_REASON_INVALID_PARAMETER): ",
200 case pdlp::TERMINATION_REASON_PRIMAL_OR_DUAL_INFEASIBLE:
203 FEASIBILITY_STATUS_UNDETERMINED,
205 case pdlp::TERMINATION_REASON_OTHER:
209 return absl::InvalidArgumentError(absl::StrCat(
215absl::StatusOr<SolveResultProto> PdlpSolver::MakeSolveResult(
216 const pdlp::SolverResult& pdlp_result,
217 const ModelSolveParametersProto& model_params) {
221 const double objective_scaling_factor =
223 const bool is_maximize = objective_scaling_factor < 0.0;
224 SolveResultProto result;
225 const std::optional<pdlp::ConvergenceInformation> convergence_information =
227 pdlp_result.solve_log.solution_type());
228 if (convergence_information.has_value()) {
229 *result.mutable_pdlp_output()->mutable_convergence_information() =
230 *convergence_information;
233 if (convergence_information.has_value()) {
234 objective_bounds.set_primal_bound(
235 convergence_information->primal_objective());
236 objective_bounds.set_dual_bound(convergence_information->dual_objective());
239 ConvertReason(pdlp_result.solve_log.termination_reason(),
240 pdlp_result.solve_log.termination_string(),
241 is_maximize, objective_bounds.primal_bound(),
242 objective_bounds.dual_bound()));
245 absl::Seconds(pdlp_result.solve_log.solve_time_sec())));
246 result.mutable_solve_stats()->set_first_order_iterations(
247 pdlp_result.solve_log.iteration_count());
249 switch (pdlp_result.solve_log.termination_reason()) {
250 case pdlp::TERMINATION_REASON_OPTIMAL:
251 case pdlp::TERMINATION_REASON_TIME_LIMIT:
252 case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
253 case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
254 case pdlp::TERMINATION_REASON_NUMERICAL_ERROR:
255 case pdlp::TERMINATION_REASON_INTERRUPTED_BY_USER: {
256 SolutionProto* solution_proto = result.add_solutions();
259 pdlp_result.primal_solution, model_params.variable_values_filter());
261 PrimalSolutionProto* primal_proto =
262 solution_proto->mutable_primal_solution();
263 primal_proto->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
264 *primal_proto->mutable_variable_values() = *std::move(maybe_primal);
269 if (pdlp_result.solve_log.termination_reason() ==
270 pdlp::TERMINATION_REASON_OPTIMAL) {
271 primal_proto->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
273 if (convergence_information.has_value()) {
274 primal_proto->set_objective_value(
275 convergence_information->primal_objective());
280 pdlp_result.dual_solution, model_params.dual_values_filter());
283 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
285 DualSolutionProto* dual_proto = solution_proto->mutable_dual_solution();
286 dual_proto->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
287 *dual_proto->mutable_dual_values() = *std::move(maybe_dual);
288 *dual_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
290 if (pdlp_result.solve_log.termination_reason() ==
291 pdlp::TERMINATION_REASON_OPTIMAL) {
292 dual_proto->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
294 if (convergence_information.has_value()) {
295 dual_proto->set_objective_value(
296 convergence_information->dual_objective());
301 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE: {
305 pdlp_result.dual_solution, model_params.dual_values_filter());
308 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
310 DualRayProto* dual_ray_proto = result.add_dual_rays();
311 *dual_ray_proto->mutable_dual_values() = *std::move(maybe_dual);
312 *dual_ray_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
315 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE: {
319 pdlp_result.primal_solution, model_params.variable_values_filter());
321 PrimalRayProto* primal_ray_proto = result.add_primal_rays();
322 *primal_ray_proto->mutable_variable_values() = *std::move(maybe_primal);
333 const ModelSolveParametersProto& model_parameters,
335 const CallbackRegistrationProto& callback_registration,
const Callback,
343 message_cb !=
nullptr));
349 std::atomic<bool> interrupt =
false;
351 interrupter, [&]() { interrupt =
true; });
353 std::optional<PrimalAndDualSolution> initial_solution;
354 if (!model_parameters.solution_hints().empty()) {
356 model_parameters.solution_hints(0));
359 std::function<void(
const std::string&)> pdlp_callback =
nullptr;
360 if (message_cb !=
nullptr) {
361 pdlp_callback = [&message_cb](
const std::string&
message) {
362 message_cb(absl::StrSplit(
message,
'\n'));
367 PrimalDualHybridGradient(pdlp_bridge_.
pdlp_lp(), pdlp_params,
368 initial_solution, &interrupt, pdlp_callback);
369 return MakeSolveResult(pdlp_result, model_parameters);
376absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
380 return absl::UnimplementedError(
381 "PDLP does not provide a method to compute an infeasible subsystem");
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
InvertedBounds ListInvertedBounds() const
Returns the ids of variables and linear constraints with inverted bounds.
absl::StatusOr< SparseDoubleVectorProto > DualVariablesToProto(const Eigen::VectorXd &dual_values, const SparseVectorFilterProto &linear_constraint_filter) const
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
const pdlp::QuadraticProgram & pdlp_lp() const
absl::StatusOr< SparseDoubleVectorProto > PrimalVariablesToProto(const Eigen::VectorXd &primal_values, const SparseVectorFilterProto &variable_filter) const
absl::StatusOr< SparseDoubleVectorProto > ReducedCostsToProto(const Eigen::VectorXd &reduced_costs, const SparseVectorFilterProto &variable_filter) const
pdlp::PrimalAndDualSolution SolutionHintToWarmStart(const SolutionHintProto &solution_hint) const
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *interrupter) override
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *interrupter) override
static absl::StatusOr< pdlp::PrimalDualHybridGradientParams > MergeParameters(const SolveParametersProto ¶meters, bool has_message_callback)
Returns the merged parameters and a list of warnings.
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
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)
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)
In SWIG mode, we don't want anything besides these top-level includes.
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)
absl::Status ToStatus() const
Initialization arguments.
double objective_scaling_factor