OR-Tools  9.2
pdlp_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cmath>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <memory>
21#include <optional>
22#include <string>
23#include <utility>
24#include <vector>
25
27#include "google/protobuf/duration.pb.h"
28#include "absl/memory/memory.h"
29#include "absl/status/status.h"
30#include "absl/status/statusor.h"
31#include "absl/strings/str_cat.h"
32#include "absl/strings/str_join.h"
33#include "absl/time/time.h"
34#include "ortools/pdlp/iteration_stats.h"
35#include "ortools/pdlp/primal_dual_hybrid_gradient.h"
36#include "ortools/pdlp/quadratic_program.h"
37#include "ortools/pdlp/solve_log.pb.h"
38#include "ortools/pdlp/solvers.pb.h"
39#include "ortools/math_opt/callback.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
56namespace operations_research {
57namespace math_opt {
58
59using pdlp::PrimalDualHybridGradientParams;
60using pdlp::SolverResult;
61
62absl::StatusOr<std::unique_ptr<SolverInterface>> PdlpSolver::New(
63 const ModelProto& model, const InitArgs& init_args) {
64 auto result = absl::WrapUnique(new PdlpSolver);
65 ASSIGN_OR_RETURN(result->pdlp_bridge_, PdlpBridge::FromProto(model));
66 return result;
67}
68
69std::pair<PrimalDualHybridGradientParams, std::vector<std::string>>
70PdlpSolver::MergeParameters(const SolveParametersProto& parameters) {
71 PrimalDualHybridGradientParams result;
72 std::vector<std::string> warnings;
73 if (parameters.enable_output()) {
74 // TODO(b/183502493): this is not a robust solution. It is not thread safe
75 // and will interfere with the global vlog state.
76 SetVLOGLevel("primal_dual_hybrid_gradient", 2);
77 }
78 if (parameters.has_threads()) {
79 result.set_num_threads(parameters.threads());
80 }
81 if (parameters.has_time_limit()) {
82 result.mutable_termination_criteria()->set_time_sec_limit(
83 absl::ToDoubleSeconds(
84 util_time::DecodeGoogleApiProto(parameters.time_limit()).value()));
85 }
86 if (parameters.has_cutoff_limit()) {
87 warnings.push_back("parameter cutoff_limit not supported for PDLP");
88 }
89 if (parameters.has_objective_limit()) {
90 warnings.push_back("parameter best_objective_limit not supported for PDLP");
91 }
92 if (parameters.has_best_bound_limit()) {
93 warnings.push_back("parameter best_bound_limit not supported for PDLP");
94 }
95 if (parameters.has_solution_limit()) {
96 warnings.push_back("parameter solution_limit not supported for PDLP");
97 }
98 if (parameters.has_random_seed()) {
99 warnings.push_back("parameter random_seed not supported for PDLP");
100 }
101 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
102 warnings.push_back("parameter lp_algorithm not supported for PDLP");
103 }
104 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
105 warnings.push_back("parameter presolve not supported for PDLP");
106 }
107 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
108 warnings.push_back("parameter cuts not supported for PDLP");
109 }
110 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
111 warnings.push_back("parameter heuristics not supported for PDLP");
112 }
113 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
114 warnings.push_back("parameter scaling not supported for PDLP");
115 }
116 if (parameters.has_iteration_limit()) {
117 const int64_t limit = std::min<int64_t>(std::numeric_limits<int32_t>::max(),
118 parameters.iteration_limit());
119 result.mutable_termination_criteria()->set_iteration_limit(
120 static_cast<int32_t>(limit));
121 }
122 result.MergeFrom(parameters.pdlp());
123 return {std::move(result), std::move(warnings)};
124}
125
126namespace {
127
128absl::StatusOr<TerminationProto> ConvertReason(
129 const pdlp::TerminationReason pdlp_reason, const std::string& pdlp_detail) {
130 switch (pdlp_reason) {
131 case pdlp::TERMINATION_REASON_UNSPECIFIED:
132 return TerminateForReason(TERMINATION_REASON_UNSPECIFIED, pdlp_detail);
133 case pdlp::TERMINATION_REASON_OPTIMAL:
134 return TerminateForReason(TERMINATION_REASON_OPTIMAL, pdlp_detail);
135 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE:
136 return TerminateForReason(TERMINATION_REASON_INFEASIBLE, pdlp_detail);
137 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE:
138 return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
139 pdlp_detail);
140 case pdlp::TERMINATION_REASON_TIME_LIMIT:
141 return NoSolutionFoundTermination(LIMIT_TIME, pdlp_detail);
142 case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
143 return NoSolutionFoundTermination(LIMIT_ITERATION, pdlp_detail);
144 case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
145 return NoSolutionFoundTermination(LIMIT_OTHER, pdlp_detail);
146 case pdlp::TERMINATION_REASON_NUMERICAL_ERROR:
147 return TerminateForReason(TERMINATION_REASON_NUMERICAL_ERROR,
148 pdlp_detail);
149 case pdlp::TERMINATION_REASON_INVALID_PROBLEM:
150 // Indicates that the solver detected invalid problem data, e.g.,
151 // inconsistent bounds.
152 return absl::InternalError(
153 absl::StrCat("Invalid problem sent to PDLP solver "
154 "(TERMINATION_REASON_INVALID_PROBLEM): ",
155 pdlp_detail));
156 // Indicates that an invalid value for the parameters was detected.
157 case pdlp::TERMINATION_REASON_INVALID_PARAMETER:
158 return absl::InvalidArgumentError(absl::StrCat(
159 "PDLP parameters invalid (TERMINATION_REASON_INVALID_PARAMETER): ",
160 pdlp_detail));
161 case pdlp::TERMINATION_REASON_OTHER:
162 return TerminateForReason(TERMINATION_REASON_OTHER_ERROR, pdlp_detail);
163 default:
164 LOG(FATAL) << "PDLP status: " << ProtoEnumToString(pdlp_reason)
165 << " not implemented.";
166 }
167}
168
169ProblemStatusProto GetProblemStatus(const pdlp::TerminationReason pdlp_reason,
170 const bool has_finite_dual_bound) {
171 ProblemStatusProto problem_status;
172
173 switch (pdlp_reason) {
174 case pdlp::TERMINATION_REASON_OPTIMAL:
175 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
176 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
177 break;
178 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE:
179 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
180 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
181 break;
182 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE:
183 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
184 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
185 break;
186 case pdlp::TERMINATION_REASON_PRIMAL_OR_DUAL_INFEASIBLE:
187 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
188 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
189 problem_status.set_primal_or_dual_infeasible(true);
190 break;
191 default:
192 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
193 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
194 break;
195 }
196 if (has_finite_dual_bound) {
197 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
198 }
199 return problem_status;
200}
201
202} // namespace
203
204absl::Status PdlpSolver::FillSolveResult(
205 const pdlp::SolverResult& pdlp_result,
206 const ModelSolveParametersProto& model_params, SolveResultProto& result) {
207 ASSIGN_OR_RETURN(*result.mutable_termination(),
208 ConvertReason(pdlp_result.solve_log.termination_reason(),
209 pdlp_result.solve_log.termination_string()));
210 ASSIGN_OR_RETURN(*result.mutable_solve_stats()->mutable_solve_time(),
212 absl::Seconds(pdlp_result.solve_log.solve_time_sec())));
213 const std::optional<pdlp::ConvergenceInformation> convergence_information =
214 pdlp::GetConvergenceInformation(pdlp_result.solve_log.solution_stats(),
215 pdlp_result.solve_log.solution_type());
216
217 // TODO(b/195295177): update description after changes to bounds below.
218 // Set default infinite primal/dual bounds. PDLP's default is a minimization
219 // problem for which the default primal and dual bounds are infinity and
220 // -infinity respectively. PDLP provides a scaling factor to flip the signs
221 // for maximization problems. Note that PDLP does not consider solutions that
222 // are feasible up to the solver's tolerances to update these bounds. PDLP
223 // provides a correction function for dual solutions that yields a true dual
224 // bound, but does not provide this function for primal solutions.
225 const double objective_scaling_factor =
226 pdlp_bridge_.pdlp_lp().objective_scaling_factor;
227 result.mutable_solve_stats()->set_best_primal_bound(
228 objective_scaling_factor * std::numeric_limits<double>::infinity());
229 result.mutable_solve_stats()->set_best_dual_bound(
230 -objective_scaling_factor * std::numeric_limits<double>::infinity());
231
232 switch (pdlp_result.solve_log.termination_reason()) {
233 case pdlp::TERMINATION_REASON_OPTIMAL:
234 case pdlp::TERMINATION_REASON_TIME_LIMIT:
235 case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
236 case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
237 case pdlp::TERMINATION_REASON_NUMERICAL_ERROR: {
238 SolutionProto* solution_proto = result.add_solutions();
239 {
240 auto maybe_primal = pdlp_bridge_.PrimalVariablesToProto(
241 pdlp_result.primal_solution, model_params.variable_values_filter());
242 RETURN_IF_ERROR(maybe_primal.status());
243 PrimalSolutionProto* primal_proto =
244 solution_proto->mutable_primal_solution();
245 primal_proto->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
246 *primal_proto->mutable_variable_values() = *std::move(maybe_primal);
247 // Note: the solution could be primal feasible for termination reasons
248 // other than TERMINATION_REASON_OPTIMAL, but in theory, PDLP could
249 // also be modified to return the best feasible solution encounered in
250 // an early termination run (if any).
251 if (pdlp_result.solve_log.termination_reason() ==
252 pdlp::TERMINATION_REASON_OPTIMAL) {
253 primal_proto->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
254 }
255 if (convergence_information.has_value()) {
256 primal_proto->set_objective_value(
257 convergence_information->primal_objective());
258 // TODO(b/195295177): update to return bounds.
259 // PDLP does not have a primal objective correction function so we
260 // skip the primal bound update.
261 }
262 }
263 {
264 auto maybe_dual = pdlp_bridge_.DualVariablesToProto(
265 pdlp_result.dual_solution, model_params.dual_values_filter());
266 RETURN_IF_ERROR(maybe_dual.status());
267 auto maybe_reduced = pdlp_bridge_.ReducedCostsToProto(
268 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
269 RETURN_IF_ERROR(maybe_reduced.status());
270 DualSolutionProto* dual_proto = solution_proto->mutable_dual_solution();
271 dual_proto->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
272 *dual_proto->mutable_dual_values() = *std::move(maybe_dual);
273 *dual_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
274 // Note: same comment on primal solution status holds here.
275 if (pdlp_result.solve_log.termination_reason() ==
276 pdlp::TERMINATION_REASON_OPTIMAL) {
277 dual_proto->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
278 }
279 if (convergence_information.has_value()) {
280 const double dual_obj = convergence_information->dual_objective();
281 dual_proto->set_objective_value(dual_obj);
282 // TODO(b/195295177): update to use dual_obj or corrected_dual_bound.
283 // Using PDLP's corrected dual objective to update dual bounds.
284 const double corrected_dual_bound =
285 convergence_information->corrected_dual_objective();
286 result.mutable_solve_stats()->set_best_dual_bound(
287 corrected_dual_bound);
288 }
289 }
290 break;
291 }
292 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE: {
293 // NOTE: for primal infeasible problems, PDLP stores the infeasibility
294 // certificate (dual ray) in the dual variables and reduced costs.
295 auto maybe_dual = pdlp_bridge_.DualVariablesToProto(
296 pdlp_result.dual_solution, model_params.dual_values_filter());
297 RETURN_IF_ERROR(maybe_dual.status());
298 auto maybe_reduced = pdlp_bridge_.ReducedCostsToProto(
299 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
300 RETURN_IF_ERROR(maybe_reduced.status());
301 DualRayProto* dual_ray_proto = result.add_dual_rays();
302 *dual_ray_proto->mutable_dual_values() = *std::move(maybe_dual);
303 *dual_ray_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
304 break;
305 }
306 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE: {
307 // NOTE: for dual infeasible problems, PDLP stores the infeasibility
308 // certificate (primal ray) in the primal variables.
309 auto maybe_primal = pdlp_bridge_.PrimalVariablesToProto(
310 pdlp_result.primal_solution, model_params.variable_values_filter());
311 RETURN_IF_ERROR(maybe_primal.status());
312 PrimalRayProto* primal_ray_proto = result.add_primal_rays();
313 *primal_ray_proto->mutable_variable_values() = *std::move(maybe_primal);
314 break;
315 }
316 default:
317 break;
318 }
319 *result.mutable_solve_stats()->mutable_problem_status() =
320 GetProblemStatus(pdlp_result.solve_log.termination_reason(),
321 std::isfinite(result.solve_stats().best_dual_bound()));
322 return absl::OkStatus();
323}
324
325absl::StatusOr<SolveResultProto> PdlpSolver::Solve(
326 const SolveParametersProto& parameters,
327 const ModelSolveParametersProto& model_parameters,
328 const MessageCallback message_cb,
329 const CallbackRegistrationProto& callback_registration, const Callback cb,
330 SolveInterrupter* const interrupter) {
331 // TODO(b/192274409): Use interrupter if PDLP supports interruption.
332
333 // TODO(b/183502493): Implement message callback when PDLP supports that.
334 if (message_cb != nullptr) {
335 return absl::InvalidArgumentError(internal::kMessageCallbackNotSupported);
336 }
337
339 /*supported_events=*/{}));
340
341 SolveResultProto result;
342 auto [pdlp_params, param_warnings] = MergeParameters(parameters);
343 if (!param_warnings.empty()) {
344 if (parameters.strictness().bad_parameter()) {
345 return absl::InvalidArgumentError(absl::StrJoin(param_warnings, "; "));
346 } else {
347 for (std::string& warning : param_warnings) {
348 result.add_warnings(std::move(warning));
349 }
350 }
351 }
352 const SolverResult pdlp_result =
353 PrimalDualHybridGradient(pdlp_bridge_.pdlp_lp(), pdlp_params);
354 RETURN_IF_ERROR(FillSolveResult(pdlp_result, model_parameters, result));
355 return result;
356}
357absl::Status PdlpSolver::Update(const ModelUpdateProto& model_update) {
358 // This function should never be called since CanUpdate() always returns
359 // false.
360 return absl::InternalError("PDLP solver does not support incrementalism");
361}
362bool PdlpSolver::CanUpdate(const ModelUpdateProto& model_update) {
363 return false;
364}
365
367
368} // namespace math_opt
369} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define LOG(severity)
Definition: base/logging.h:420
const pdlp::QuadraticProgram & pdlp_lp() const
Definition: pdlp_bridge.h:49
absl::StatusOr< SparseDoubleVectorProto > DualVariablesToProto(const Eigen::VectorXd &dual_values, const SparseVectorFilterProto &linear_constraint_filter) const
Definition: pdlp_bridge.cc:154
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
Definition: pdlp_bridge.cc:60
absl::StatusOr< SparseDoubleVectorProto > PrimalVariablesToProto(const Eigen::VectorXd &primal_values, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:148
absl::StatusOr< SparseDoubleVectorProto > ReducedCostsToProto(const Eigen::VectorXd &reduced_costs, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:161
bool CanUpdate(const ModelUpdateProto &model_update) override
Definition: pdlp_solver.cc:362
absl::Status Update(const ModelUpdateProto &model_update) override
Definition: pdlp_solver.cc:357
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
Definition: pdlp_solver.cc:62
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, SolveInterrupter *interrupter) override
Definition: pdlp_solver.cc:325
static std::pair< pdlp::PrimalDualHybridGradientParams, std::vector< std::string > > MergeParameters(const SolveParametersProto &parameters)
Definition: pdlp_solver.cc:70
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SatParameters parameters
GRBmodel * model
const int FATAL
Definition: log_severity.h:32
int SetVLOGLevel(const char *module_pattern, int log_level)
Definition: vlog_is_on.cc:147
constexpr absl::string_view kMessageCallbackNotSupported
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
TerminationProto NoSolutionFoundTermination(const LimitProto limit, const absl::string_view detail)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition: protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
#define ASSIGN_OR_RETURN(lhs, rexpr)
Definition: status_macros.h:48
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29