OR-Tools  9.6
primal_dual_hybrid_gradient.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 
14 #ifndef PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
15 #define PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
16 
17 #include <atomic>
18 #include <functional>
19 #include <optional>
20 
21 #include "Eigen/Core"
24 #include "ortools/pdlp/solve_log.pb.h"
25 #include "ortools/pdlp/solvers.pb.h"
27 
28 namespace operations_research::pdlp {
29 
31  Eigen::VectorXd primal_solution;
32  Eigen::VectorXd dual_solution;
33 };
34 
35 // The following table defines the interpretation of the result vectors
36 // depending on the value of `solve_log.termination_reason`: (the
37 // TERMINATION_REASON_ prefix is omitted for brevity):
38 //
39 // * OPTIMAL: the vectors satisfy the termination criteria for optimality.
40 // * PRIMAL_INFEASIBLE: dual_solution and reduced_costs provide an approximate
41 // certificate of primal infeasibility; see
42 // https://developers.google.com/optimization/lp/pdlp_math#infeasibility_identification
43 // for more information.
44 // * DUAL_INFEASIBLE: `primal_solution` provides an approximate certificate of
45 // dual infeasibility; see
46 // https://developers.google.com/optimization/lp/pdlp_math#infeasibility_identification
47 // for more information.
48 // * PRIMAL_OR_DUAL_INFEASIBLE: the problem was shown to be primal and/or dual
49 // infeasible but no certificate of infeasibility is available. The
50 // `primal_solution` and `dual_solution` have no meaning. This status is only
51 // used when presolve is enabled.
52 // * TIME_LIMIT, ITERATION_LIMIT, KKT_MATRIX_PASS_LIMIT, NUMERICAL_ERROR,
53 // INTERRUPTED_BY_USER: the vectors contain an iterate at the time that the
54 // respective event occurred. Their values may or may not be meaningful. In
55 // some cases solution quality information is available; see documentation for
56 // `solve_log.solution_type`.
57 // * INVALID_PROBLEM, INVALID_PARAMETER, OTHER: the solution vectors are
58 // meaningless and may not have lengths consistent with the input problem.
59 struct SolverResult {
60  Eigen::VectorXd primal_solution;
61  // See https://developers.google.com/optimization/lp/pdlp_math for the
62  // interpretation of `dual_solution` and `reduced_costs`.
63  Eigen::VectorXd dual_solution;
64  Eigen::VectorXd reduced_costs;
65  SolveLog solve_log;
66 };
67 
69  TerminationCriteria termination_criteria;
70  IterationStats iteration_stats;
72 };
73 
74 // Solves the given QP using PDLP (Primal-Dual hybrid gradient enhanced for LP).
75 //
76 // All operations that are repeated each iteration are executed in parallel
77 // using `params.num_threads()` threads.
78 //
79 // The algorithm generally follows the description in
80 // https://arxiv.org/pdf/2106.04756.pdf, with further enhancements for QP.
81 // Notation here and in the implementation follows Chambolle and Pock, "On the
82 // ergodic convergence rates of a first-order primal-dual algorithm"
83 // (http://www.optimization-online.org/DB_FILE/2014/09/4532.pdf).
84 // That paper doesn't explicitly use the terminology "primal-dual hybrid
85 // gradient" but their Theorem 1 is analyzing PDHG. See
86 // https://developers.google.com/optimization/lp/pdlp_math#saddle-point_formulation
87 // for the saddle-point formulation of the QP we use that is compatible with
88 // Chambolle and Pock.
89 //
90 // We use 0.5 ||.||^2 for both the primal and dual distance functions.
91 //
92 // We parameterize the primal and dual step sizes (tau and sigma in Chambolle
93 // and Pock) as:
94 // primal_stepsize = step_size / primal_weight
95 // dual_stepsize = step_size * primal_weight
96 // where step_size and primal_weight are parameters.
97 // `params.linesearch_rule` determines the update rule for step_size.
98 // `params.initial_primal_weight` specifies how primal_weight is initialized
99 // and `params.primal_weight_update_smoothing` controls how primal_weight is
100 // updated.
101 //
102 // If `interrupt_solve` is not nullptr, then the solver will periodically check
103 // if `interrupt_solve->load()` is true, in which case the solve will terminate
104 // with `TERMINATION_REASON_INTERRUPTED_BY_USER`.
105 //
106 // If `iteration_stats_callback` is not nullptr, then at each termination step
107 // (when iteration stats are logged), `iteration_stats_callback` will also
108 // be called with those iteration stats.
109 //
110 // Callers MUST check `solve_log.termination_reason` before using the vectors in
111 // the `SolverResult`. See the comment on `SolverResult` for interpreting the
112 // termination reason.
113 //
114 // All objective values reported by the algorithm are transformed by using
115 // `QuadraticProgram::ApplyObjectiveScalingAndOffset`.
116 //
117 // NOTE: `qp` is intentionally passed by value, because
118 // `PrimalDualHybridGradient` modifies its copy.
120  QuadraticProgram qp, const PrimalDualHybridGradientParams& params,
121  const std::atomic<bool>* interrupt_solve = nullptr,
122  std::function<void(const IterationCallbackInfo&)> iteration_stats_callback =
123  nullptr);
124 
125 // Like above but optionally starts with the given `initial_solution`. If no
126 // `initial_solution` is given the zero vector is used. In either case
127 // `initial_solution` is projected onto the primal and dual variable bounds
128 // before use. Convergence should be faster if `initial_solution` is close to an
129 // optimal solution. NOTE: `initial_solution` is intentionally passed by value.
131  QuadraticProgram qp, const PrimalDualHybridGradientParams& params,
132  std::optional<PrimalAndDualSolution> initial_solution,
133  const std::atomic<bool>* interrupt_solve = nullptr,
134  std::function<void(const IterationCallbackInfo&)> iteration_stats_callback =
135  nullptr);
136 
137 namespace internal {
138 
139 // Computes variable and constraint statuses. This determines if primal
140 // variables are at their bounds based on exact comparisons and therefore may
141 // not work with unscaled solutions. The primal and dual solution in the
142 // returned `ProblemSolution` are NOT set.
144  const PrimalAndDualSolution& solution);
145 
146 } // namespace internal
147 
148 } // namespace operations_research::pdlp
149 
150 #endif // PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
glop::ProblemSolution ComputeStatuses(const QuadraticProgram &qp, const PrimalAndDualSolution &solution)
SolverResult PrimalDualHybridGradient(QuadraticProgram qp, const PrimalDualHybridGradientParams &params, const std::atomic< bool > *interrupt_solve, IterationStatsCallback iteration_stats_callback)