forked from google/or-tools
-
Notifications
You must be signed in to change notification settings - Fork 9
/
primal_dual_hybrid_gradient.h
184 lines (169 loc) · 8.26 KB
/
primal_dual_hybrid_gradient.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
#define PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_
#include <atomic>
#include <functional>
#include <optional>
#include <string>
#include "Eigen/Core"
#include "ortools/lp_data/lp_data.h"
#include "ortools/pdlp/quadratic_program.h"
#include "ortools/pdlp/solve_log.pb.h"
#include "ortools/pdlp/solvers.pb.h"
#include "ortools/pdlp/termination.h"
namespace operations_research::pdlp {
struct PrimalAndDualSolution {
Eigen::VectorXd primal_solution;
Eigen::VectorXd dual_solution;
};
// The following table defines the interpretation of the result vectors
// depending on the value of `solve_log.termination_reason`: (the
// TERMINATION_REASON_ prefix is omitted for brevity):
//
// * OPTIMAL: the vectors satisfy the termination criteria for optimality.
// * PRIMAL_INFEASIBLE: dual_solution and reduced_costs provide an approximate
// certificate of primal infeasibility; see
// https://developers.google.com/optimization/lp/pdlp_math#infeasibility_identification
// for more information.
// * DUAL_INFEASIBLE: `primal_solution` provides an approximate certificate of
// dual infeasibility; see
// https://developers.google.com/optimization/lp/pdlp_math#infeasibility_identification
// for more information.
// * PRIMAL_OR_DUAL_INFEASIBLE: the problem was shown to be primal and/or dual
// infeasible but no certificate of infeasibility is available. The
// `primal_solution` and `dual_solution` have no meaning. This status is only
// used when presolve is enabled.
// * TIME_LIMIT, ITERATION_LIMIT, KKT_MATRIX_PASS_LIMIT, NUMERICAL_ERROR,
// INTERRUPTED_BY_USER: the vectors contain an iterate at the time that the
// respective event occurred. Their values may or may not be meaningful. In
// some cases solution quality information is available; see documentation for
// `solve_log.solution_type`.
// * INVALID_PROBLEM, INVALID_PARAMETER, OTHER: the solution vectors are
// meaningless and may not have lengths consistent with the input problem.
struct SolverResult {
Eigen::VectorXd primal_solution;
// See https://developers.google.com/optimization/lp/pdlp_math for the
// interpretation of `dual_solution` and `reduced_costs`.
Eigen::VectorXd dual_solution;
// NOTE: The definition of reduced_costs changed in OR-tools version 9.8.
// See
// https://developers.google.com/optimization/lp/pdlp_math#reduced_costs_dual_residuals_and_the_corrected_dual_objective
// for details.
Eigen::VectorXd reduced_costs;
SolveLog solve_log;
};
// Identifies the iteration type in a callback. The callback is called both for
// intermediate iterations and upon termination.
enum class IterationType {
// An intermediate iteration in the "main" phase.
kNormal,
// An intermediate iteration during a primal feasibility polishing phase.
kPrimalFeasibility,
// An intermediate iteration during a dual feasibility polishing phase.
kDualFeasibility,
// Terminating with a solution found by presolve.
kPresolveTermination,
// Terminating with a solution found by the "main" phase.
kNormalTermination,
// Terminating with a solution found by feasibility polishing.
kFeasibilityPolishingTermination,
};
struct IterationCallbackInfo {
IterationType iteration_type;
TerminationCriteria termination_criteria;
// Note that if feasibility polishing is enabled, `iteration_stats` will only
// describe the iteration within the current phase (main, primal feasibility
// polishing, or dual feasibility polishing), and not the total work.
IterationStats iteration_stats;
QuadraticProgramBoundNorms bound_norms;
};
// Solves the given QP using PDLP (Primal-Dual hybrid gradient enhanced for LP).
//
// All operations that are repeated each iteration are executed in parallel
// using `params.num_threads()` threads.
//
// The algorithm generally follows the description in
// https://arxiv.org/pdf/2106.04756.pdf, with further enhancements for QP.
// Notation here and in the implementation follows Chambolle and Pock, "On the
// ergodic convergence rates of a first-order primal-dual algorithm"
// (http://www.optimization-online.org/DB_FILE/2014/09/4532.pdf).
// That paper doesn't explicitly use the terminology "primal-dual hybrid
// gradient" but their Theorem 1 is analyzing PDHG. See
// https://developers.google.com/optimization/lp/pdlp_math#saddle-point_formulation
// for the saddle-point formulation of the QP we use that is compatible with
// Chambolle and Pock.
//
// We use 0.5 ||.||^2 for both the primal and dual distance functions.
//
// We parameterize the primal and dual step sizes (tau and sigma in Chambolle
// and Pock) as:
// primal_stepsize = step_size / primal_weight
// dual_stepsize = step_size * primal_weight
// where step_size and primal_weight are parameters.
// `params.linesearch_rule` determines the update rule for step_size.
// `params.initial_primal_weight` specifies how primal_weight is initialized
// and `params.primal_weight_update_smoothing` controls how primal_weight is
// updated.
//
// If `interrupt_solve` is not nullptr, then the solver will periodically check
// if `interrupt_solve->load()` is true, in which case the solve will terminate
// with `TERMINATION_REASON_INTERRUPTED_BY_USER`.
//
// If `message_callback` is not nullptr, solver logging will be passed to
// `message_callback`. Otherwise solver logging will be written to stdout.
// In either case, the amount of logging is controlled by `verbosity_level`.
// In particular, if `verbosity_level == 0`, there will be no logging in either
// case.
//
// If `iteration_stats_callback` is not nullptr, then at each termination step
// (when iteration stats are logged), `iteration_stats_callback` will also
// be called with those iteration stats.
//
// Callers MUST check `solve_log.termination_reason` before using the vectors in
// the `SolverResult`. See the comment on `SolverResult` for interpreting the
// termination reason.
//
// All objective values reported by the algorithm are transformed by using
// `QuadraticProgram::ApplyObjectiveScalingAndOffset`.
//
// NOTE: `qp` is intentionally passed by value, because
// `PrimalDualHybridGradient` modifies its copy.
SolverResult PrimalDualHybridGradient(
QuadraticProgram qp, const PrimalDualHybridGradientParams& params,
const std::atomic<bool>* interrupt_solve = nullptr,
std::function<void(const std::string&)> message_callback = nullptr,
std::function<void(const IterationCallbackInfo&)> iteration_stats_callback =
nullptr);
// Like above but optionally starts with the given `initial_solution`. If no
// `initial_solution` is given the zero vector is used. In either case
// `initial_solution` is projected onto the primal and dual variable bounds
// before use. Convergence should be faster if `initial_solution` is close to an
// optimal solution. NOTE: `initial_solution` is intentionally passed by value.
SolverResult PrimalDualHybridGradient(
QuadraticProgram qp, const PrimalDualHybridGradientParams& params,
std::optional<PrimalAndDualSolution> initial_solution,
const std::atomic<bool>* interrupt_solve = nullptr,
std::function<void(const std::string&)> message_callback = nullptr,
std::function<void(const IterationCallbackInfo&)> iteration_stats_callback =
nullptr);
namespace internal {
// Computes variable and constraint statuses. This determines if primal
// variables are at their bounds based on exact comparisons and therefore may
// not work with unscaled solutions. The primal and dual solution in the
// returned `ProblemSolution` are NOT set.
glop::ProblemSolution ComputeStatuses(const QuadraticProgram& qp,
const PrimalAndDualSolution& solution);
} // namespace internal
} // namespace operations_research::pdlp
#endif // PDLP_PRIMAL_DUAL_HYBRID_GRADIENT_H_