OR-Tools  9.3
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 <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <optional>
23#include <string>
24#include <utility>
25#include <vector>
26
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/time/time.h"
33#include "google/protobuf/duration.pb.h"
37#include "ortools/math_opt/callback.pb.h"
42#include "ortools/math_opt/model.pb.h"
43#include "ortools/math_opt/model_parameters.pb.h"
44#include "ortools/math_opt/model_update.pb.h"
45#include "ortools/math_opt/parameters.pb.h"
46#include "ortools/math_opt/result.pb.h"
47#include "ortools/math_opt/solution.pb.h"
49#include "ortools/math_opt/sparse_containers.pb.h"
54#include "ortools/pdlp/solve_log.pb.h"
55#include "ortools/pdlp/solvers.pb.h"
57
58namespace operations_research {
59namespace math_opt {
60
61using pdlp::PrimalDualHybridGradientParams;
62using pdlp::SolverResult;
63
64absl::StatusOr<std::unique_ptr<SolverInterface>> PdlpSolver::New(
65 const ModelProto& model, const InitArgs& init_args) {
66 auto result = absl::WrapUnique(new PdlpSolver);
67 ASSIGN_OR_RETURN(result->pdlp_bridge_, PdlpBridge::FromProto(model));
68 return result;
69}
70
71absl::StatusOr<PrimalDualHybridGradientParams> PdlpSolver::MergeParameters(
72 const SolveParametersProto& parameters) {
73 PrimalDualHybridGradientParams result;
74 std::vector<std::string> warnings;
75 if (parameters.enable_output()) {
76 result.set_verbosity_level(3);
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_node_limit()) {
87 warnings.push_back("parameter node_limit not supported for PDLP");
88 }
89 if (parameters.has_cutoff_limit()) {
90 warnings.push_back("parameter cutoff_limit not supported for PDLP");
91 }
92 if (parameters.has_objective_limit()) {
93 warnings.push_back("parameter best_objective_limit not supported for PDLP");
94 }
95 if (parameters.has_best_bound_limit()) {
96 warnings.push_back("parameter best_bound_limit not supported for PDLP");
97 }
98 if (parameters.has_solution_limit()) {
99 warnings.push_back("parameter solution_limit not supported for PDLP");
100 }
101 if (parameters.has_random_seed()) {
102 warnings.push_back("parameter random_seed not supported for PDLP");
103 }
104 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
105 warnings.push_back("parameter lp_algorithm not supported for PDLP");
106 }
107 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
108 warnings.push_back("parameter presolve not supported for PDLP");
109 }
110 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
111 warnings.push_back("parameter cuts not supported for PDLP");
112 }
113 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
114 warnings.push_back("parameter heuristics not supported for PDLP");
115 }
116 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
117 warnings.push_back("parameter scaling not supported for PDLP");
118 }
119 if (parameters.has_iteration_limit()) {
120 const int64_t limit = std::min<int64_t>(std::numeric_limits<int32_t>::max(),
121 parameters.iteration_limit());
122 result.mutable_termination_criteria()->set_iteration_limit(
123 static_cast<int32_t>(limit));
124 }
125 result.MergeFrom(parameters.pdlp());
126 if (!warnings.empty()) {
127 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
128 }
129 return result;
130}
131
132namespace {
133
134absl::StatusOr<TerminationProto> ConvertReason(
135 const pdlp::TerminationReason pdlp_reason, const std::string& pdlp_detail) {
136 switch (pdlp_reason) {
137 case pdlp::TERMINATION_REASON_UNSPECIFIED:
138 return TerminateForReason(TERMINATION_REASON_UNSPECIFIED, pdlp_detail);
139 case pdlp::TERMINATION_REASON_OPTIMAL:
140 return TerminateForReason(TERMINATION_REASON_OPTIMAL, pdlp_detail);
141 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE:
142 return TerminateForReason(TERMINATION_REASON_INFEASIBLE, pdlp_detail);
143 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE:
144 return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
145 pdlp_detail);
146 case pdlp::TERMINATION_REASON_TIME_LIMIT:
147 return NoSolutionFoundTermination(LIMIT_TIME, pdlp_detail);
148 case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
149 return NoSolutionFoundTermination(LIMIT_ITERATION, pdlp_detail);
150 case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
151 return NoSolutionFoundTermination(LIMIT_OTHER, pdlp_detail);
152 case pdlp::TERMINATION_REASON_NUMERICAL_ERROR:
153 return TerminateForReason(TERMINATION_REASON_NUMERICAL_ERROR,
154 pdlp_detail);
155 case pdlp::TERMINATION_REASON_INTERRUPTED_BY_USER:
156 return NoSolutionFoundTermination(LIMIT_INTERRUPTED, pdlp_detail);
157 case pdlp::TERMINATION_REASON_INVALID_PROBLEM:
158 // Indicates that the solver detected invalid problem data, e.g.,
159 // inconsistent bounds.
160 return absl::InternalError(
161 absl::StrCat("Invalid problem sent to PDLP solver "
162 "(TERMINATION_REASON_INVALID_PROBLEM): ",
163 pdlp_detail));
164 // Indicates that an invalid value for the parameters was detected.
165 case pdlp::TERMINATION_REASON_INVALID_PARAMETER:
166 return absl::InvalidArgumentError(absl::StrCat(
167 "PDLP parameters invalid (TERMINATION_REASON_INVALID_PARAMETER): ",
168 pdlp_detail));
169 case pdlp::TERMINATION_REASON_OTHER:
170 return TerminateForReason(TERMINATION_REASON_OTHER_ERROR, pdlp_detail);
171 default:
172 LOG(FATAL) << "PDLP status: " << ProtoEnumToString(pdlp_reason)
173 << " not implemented.";
174 }
175}
176
177ProblemStatusProto GetProblemStatus(const pdlp::TerminationReason pdlp_reason,
178 const bool has_finite_dual_bound) {
179 ProblemStatusProto problem_status;
180
181 switch (pdlp_reason) {
182 case pdlp::TERMINATION_REASON_OPTIMAL:
183 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
184 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
185 break;
186 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE:
187 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
188 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
189 break;
190 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE:
191 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
192 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
193 break;
194 case pdlp::TERMINATION_REASON_PRIMAL_OR_DUAL_INFEASIBLE:
195 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
196 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
197 problem_status.set_primal_or_dual_infeasible(true);
198 break;
199 default:
200 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
201 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
202 break;
203 }
204 if (has_finite_dual_bound) {
205 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
206 }
207 return problem_status;
208}
209
210} // namespace
211
212absl::StatusOr<SolveResultProto> PdlpSolver::MakeSolveResult(
213 const pdlp::SolverResult& pdlp_result,
214 const ModelSolveParametersProto& model_params) {
215 SolveResultProto result;
216 ASSIGN_OR_RETURN(*result.mutable_termination(),
217 ConvertReason(pdlp_result.solve_log.termination_reason(),
218 pdlp_result.solve_log.termination_string()));
219 ASSIGN_OR_RETURN(*result.mutable_solve_stats()->mutable_solve_time(),
221 absl::Seconds(pdlp_result.solve_log.solve_time_sec())));
222 result.mutable_solve_stats()->set_first_order_iterations(
223 pdlp_result.solve_log.iteration_count());
224 const std::optional<pdlp::ConvergenceInformation> convergence_information =
225 pdlp::GetConvergenceInformation(pdlp_result.solve_log.solution_stats(),
226 pdlp_result.solve_log.solution_type());
227
228 // TODO(b/195295177): update description after changes to bounds below.
229 // Set default infinite primal/dual bounds. PDLP's default is a minimization
230 // problem for which the default primal and dual bounds are infinity and
231 // -infinity respectively. PDLP provides a scaling factor to flip the signs
232 // for maximization problems. Note that PDLP does not consider solutions that
233 // are feasible up to the solver's tolerances to update these bounds. PDLP
234 // provides a correction function for dual solutions that yields a true dual
235 // bound, but does not provide this function for primal solutions.
236 const double objective_scaling_factor =
237 pdlp_bridge_.pdlp_lp().objective_scaling_factor;
238 result.mutable_solve_stats()->set_best_primal_bound(
239 objective_scaling_factor * std::numeric_limits<double>::infinity());
240 result.mutable_solve_stats()->set_best_dual_bound(
241 -objective_scaling_factor * std::numeric_limits<double>::infinity());
242
243 switch (pdlp_result.solve_log.termination_reason()) {
244 case pdlp::TERMINATION_REASON_OPTIMAL:
245 case pdlp::TERMINATION_REASON_TIME_LIMIT:
246 case pdlp::TERMINATION_REASON_ITERATION_LIMIT:
247 case pdlp::TERMINATION_REASON_KKT_MATRIX_PASS_LIMIT:
248 case pdlp::TERMINATION_REASON_NUMERICAL_ERROR:
249 case pdlp::TERMINATION_REASON_INTERRUPTED_BY_USER: {
250 SolutionProto* solution_proto = result.add_solutions();
251 {
252 auto maybe_primal = pdlp_bridge_.PrimalVariablesToProto(
253 pdlp_result.primal_solution, model_params.variable_values_filter());
254 RETURN_IF_ERROR(maybe_primal.status());
255 PrimalSolutionProto* primal_proto =
256 solution_proto->mutable_primal_solution();
257 primal_proto->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
258 *primal_proto->mutable_variable_values() = *std::move(maybe_primal);
259 // Note: the solution could be primal feasible for termination reasons
260 // other than TERMINATION_REASON_OPTIMAL, but in theory, PDLP could
261 // also be modified to return the best feasible solution encounered in
262 // an early termination run (if any).
263 if (pdlp_result.solve_log.termination_reason() ==
264 pdlp::TERMINATION_REASON_OPTIMAL) {
265 primal_proto->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
266 }
267 if (convergence_information.has_value()) {
268 primal_proto->set_objective_value(
269 convergence_information->primal_objective());
270 // TODO(b/195295177): update to return bounds.
271 // PDLP does not have a primal objective correction function so we
272 // skip the primal bound update.
273 }
274 }
275 {
276 auto maybe_dual = pdlp_bridge_.DualVariablesToProto(
277 pdlp_result.dual_solution, model_params.dual_values_filter());
278 RETURN_IF_ERROR(maybe_dual.status());
279 auto maybe_reduced = pdlp_bridge_.ReducedCostsToProto(
280 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
281 RETURN_IF_ERROR(maybe_reduced.status());
282 DualSolutionProto* dual_proto = solution_proto->mutable_dual_solution();
283 dual_proto->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
284 *dual_proto->mutable_dual_values() = *std::move(maybe_dual);
285 *dual_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
286 // Note: same comment on primal solution status holds here.
287 if (pdlp_result.solve_log.termination_reason() ==
288 pdlp::TERMINATION_REASON_OPTIMAL) {
289 dual_proto->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
290 }
291 if (convergence_information.has_value()) {
292 const double dual_obj = convergence_information->dual_objective();
293 dual_proto->set_objective_value(dual_obj);
294 // TODO(b/195295177): update to use dual_obj or corrected_dual_bound.
295 // Using PDLP's corrected dual objective to update dual bounds.
296 const double corrected_dual_bound =
297 convergence_information->corrected_dual_objective();
298 result.mutable_solve_stats()->set_best_dual_bound(
299 corrected_dual_bound);
300 }
301 }
302 break;
303 }
304 case pdlp::TERMINATION_REASON_PRIMAL_INFEASIBLE: {
305 // NOTE: for primal infeasible problems, PDLP stores the infeasibility
306 // certificate (dual ray) in the dual variables and reduced costs.
307 auto maybe_dual = pdlp_bridge_.DualVariablesToProto(
308 pdlp_result.dual_solution, model_params.dual_values_filter());
309 RETURN_IF_ERROR(maybe_dual.status());
310 auto maybe_reduced = pdlp_bridge_.ReducedCostsToProto(
311 pdlp_result.reduced_costs, model_params.reduced_costs_filter());
312 RETURN_IF_ERROR(maybe_reduced.status());
313 DualRayProto* dual_ray_proto = result.add_dual_rays();
314 *dual_ray_proto->mutable_dual_values() = *std::move(maybe_dual);
315 *dual_ray_proto->mutable_reduced_costs() = *std::move(maybe_reduced);
316 break;
317 }
318 case pdlp::TERMINATION_REASON_DUAL_INFEASIBLE: {
319 // NOTE: for dual infeasible problems, PDLP stores the infeasibility
320 // certificate (primal ray) in the primal variables.
321 auto maybe_primal = pdlp_bridge_.PrimalVariablesToProto(
322 pdlp_result.primal_solution, model_params.variable_values_filter());
323 RETURN_IF_ERROR(maybe_primal.status());
324 PrimalRayProto* primal_ray_proto = result.add_primal_rays();
325 *primal_ray_proto->mutable_variable_values() = *std::move(maybe_primal);
326 break;
327 }
328 default:
329 break;
330 }
331 *result.mutable_solve_stats()->mutable_problem_status() =
332 GetProblemStatus(pdlp_result.solve_log.termination_reason(),
333 std::isfinite(result.solve_stats().best_dual_bound()));
334 return result;
335}
336
337absl::StatusOr<SolveResultProto> PdlpSolver::Solve(
338 const SolveParametersProto& parameters,
339 const ModelSolveParametersProto& model_parameters,
340 const MessageCallback message_cb,
341 const CallbackRegistrationProto& callback_registration, const Callback cb,
342 SolveInterrupter* const interrupter) {
343 // TODO(b/183502493): Implement message callback when PDLP supports that.
344 if (message_cb != nullptr) {
345 return absl::InvalidArgumentError(internal::kMessageCallbackNotSupported);
346 }
347
349 /*supported_events=*/{}));
350
351 ASSIGN_OR_RETURN(auto pdlp_params, MergeParameters(parameters));
352
353 // PDLP returns `(TERMINATION_REASON_INVALID_PROBLEM): The input problem has
354 // inconsistent bounds.` but we want a more detailed error.
356
357 std::atomic<bool> interrupt = false;
358 const ScopedSolveInterrupterCallback set_interrupt(
359 interrupter, [&]() { interrupt = true; });
360
361 const SolverResult pdlp_result =
362 PrimalDualHybridGradient(pdlp_bridge_.pdlp_lp(), pdlp_params, &interrupt);
363 return MakeSolveResult(pdlp_result, model_parameters);
364}
365absl::Status PdlpSolver::Update(const ModelUpdateProto& model_update) {
366 // This function should never be called since CanUpdate() always returns
367 // false.
368 return absl::InternalError("PDLP solver does not support incrementalism");
369}
370bool PdlpSolver::CanUpdate(const ModelUpdateProto& model_update) {
371 return false;
372}
373
375
376} // namespace math_opt
377} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define LOG(severity)
Definition: base/logging.h:420
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
const pdlp::QuadraticProgram & pdlp_lp() const
Definition: pdlp_bridge.h:50
absl::StatusOr< SparseDoubleVectorProto > DualVariablesToProto(const Eigen::VectorXd &dual_values, const SparseVectorFilterProto &linear_constraint_filter) const
Definition: pdlp_bridge.cc:178
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
Definition: pdlp_bridge.cc:61
InvertedBounds ListInvertedBounds() const
Definition: pdlp_bridge.cc:152
absl::StatusOr< SparseDoubleVectorProto > PrimalVariablesToProto(const Eigen::VectorXd &primal_values, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:172
absl::StatusOr< SparseDoubleVectorProto > ReducedCostsToProto(const Eigen::VectorXd &reduced_costs, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:185
bool CanUpdate(const ModelUpdateProto &model_update) override
Definition: pdlp_solver.cc:370
absl::Status Update(const ModelUpdateProto &model_update) override
Definition: pdlp_solver.cc:365
static absl::StatusOr< pdlp::PrimalDualHybridGradientParams > MergeParameters(const SolveParametersProto &parameters)
Definition: pdlp_solver.cc:71
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
Definition: pdlp_solver.cc:64
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:337
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
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)
absl::optional< ConvergenceInformation > GetConvergenceInformation(const IterationStats &stats, PointType candidate_type)
SolverResult PrimalDualHybridGradient(QuadraticProgram qp, const PrimalDualHybridGradientParams &params, const std::atomic< bool > *interrupt_solve, IterationStatsCallback iteration_stats_callback)
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