OR-Tools  9.6
quadratic_program.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_QUADRATIC_PROGRAM_H_
15 #define PDLP_QUADRATIC_PROGRAM_H_
16 
17 #include <cstdint>
18 #include <limits>
19 #include <optional>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include "Eigen/Core"
25 #include "Eigen/SparseCore"
26 #include "absl/status/status.h"
27 #include "absl/status/statusor.h"
28 #include "ortools/linear_solver/linear_solver.pb.h"
29 
30 namespace operations_research::pdlp {
31 
32 // Represents the quadratic program (QP):
33 // min_x (`objective_vector`^T x + (1/2) x^T `objective_matrix` x) s.t.
34 // `constraint_lower_bounds` <= `constraint_matrix` x
35 // <= `constraint_upper_bounds`
36 // `variable_lower_bounds` <= x <= `variable_upper_bounds`
37 //
38 // `constraint_lower_bounds` and `variable_lower_bounds` may include negative
39 // infinities. `constraint_upper_bounds` and `variable_upper_bounds` may
40 // contain positive infinities. Other than that all entries of all fields must
41 // be finite. The `objective_matrix` must be diagonal and non-negative.
42 //
43 // For convenience, the struct also stores `scaling_factor` and
44 // `objective_offset`. These factors can be used to transform objective values
45 // based on the problem definition above into objective values that are
46 // meaningful for the user. See `ApplyObjectiveScalingAndOffset`.
47 //
48 // This struct is also intended for use with linear programs (LPs), which are
49 // QPs with a zero `objective_matrix`.
50 //
51 // The dual is documented at
52 // https://developers.google.com/optimization/lp/pdlp_math.
54  QuadraticProgram(int64_t num_variables, int64_t num_constraints) {
55  ResizeAndInitialize(num_variables, num_constraints);
56  }
58 
59  // `QuadraticProgram` may be copied or moved. `Eigen::SparseMatrix` doesn't
60  // have move operations so we use custom implementations based on swap.
61  QuadraticProgram(const QuadraticProgram& other) = default;
63  : objective_vector(std::move(other.objective_vector)),
64  objective_matrix(std::move(other.objective_matrix)),
65  constraint_lower_bounds(std::move(other.constraint_lower_bounds)),
66  constraint_upper_bounds(std::move(other.constraint_upper_bounds)),
67  variable_lower_bounds(std::move(other.variable_lower_bounds)),
68  variable_upper_bounds(std::move(other.variable_upper_bounds)),
69  problem_name(std::move(other.problem_name)),
70  variable_names(std::move(other.variable_names)),
71  constraint_names(std::move(other.constraint_names)),
72  objective_offset(other.objective_offset),
73  objective_scaling_factor(other.objective_scaling_factor) {
74  constraint_matrix.swap(other.constraint_matrix);
75  }
76  QuadraticProgram& operator=(const QuadraticProgram& other) = default;
78  objective_vector = std::move(other.objective_vector);
79  objective_matrix = std::move(other.objective_matrix);
80  constraint_matrix.swap(other.constraint_matrix);
81  constraint_lower_bounds = std::move(other.constraint_lower_bounds);
82  constraint_upper_bounds = std::move(other.constraint_upper_bounds);
83  variable_lower_bounds = std::move(other.variable_lower_bounds);
84  variable_upper_bounds = std::move(other.variable_upper_bounds);
85  problem_name = std::move(other.problem_name);
86  variable_names = std::move(other.variable_names);
87  constraint_names = std::move(other.constraint_names);
88  objective_offset = other.objective_offset;
89  objective_scaling_factor = other.objective_scaling_factor;
90  return *this;
91  }
92 
93  // Initializes the quadratic program with `num_variables` variables and
94  // `num_constraints` constraints. Lower and upper bounds are set to negative
95  // and positive infinity, repectively. `objective_matrix` is cleared. All
96  // other matrices and vectors are set to zero. Resets the optional names
97  // (`program_name`, `variable_names`, and `constraint_names`).
98  // `objective_offset` is set to 0 and `objective_scaling_factor` is set to 1.
99  void ResizeAndInitialize(int64_t num_variables, int64_t num_constraints) {
100  constexpr double kInfinity = std::numeric_limits<double>::infinity();
101  objective_vector = Eigen::VectorXd::Zero(num_variables);
102  objective_matrix.reset();
103  constraint_matrix.resize(num_constraints, num_variables);
105  Eigen::VectorXd::Constant(num_constraints, -kInfinity);
107  Eigen::VectorXd::Constant(num_constraints, kInfinity);
109  Eigen::VectorXd::Constant(num_variables, -kInfinity);
110  variable_upper_bounds = Eigen::VectorXd::Constant(num_variables, kInfinity);
111  problem_name.reset();
112  variable_names.reset();
113  constraint_names.reset();
114  objective_offset = 0.0;
116  }
117 
118  // Returns `objective_scaling_factor * (objective + objective_offset)`.
119  // `objective_scaling_factor` is useful for modeling maximization problems.
120  // For example, max c^T x = -1 * min (-c)^T x. `objective_offset` can be a
121  // by-product of presolve transformations that eliminate variables.
122  double ApplyObjectiveScalingAndOffset(double objective) const {
123  return objective_scaling_factor * (objective + objective_offset);
124  }
125 
126  Eigen::VectorXd objective_vector;
127  // If this field isn't set, the `objective matrix` is interpreted to be zero,
128  // i.e., this is a linear programming problem.
129  std::optional<Eigen::DiagonalMatrix<double, Eigen::Dynamic>> objective_matrix;
130  Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t> constraint_matrix;
133 
134  std::optional<std::string> problem_name;
135  std::optional<std::vector<std::string>> variable_names;
136  std::optional<std::vector<std::string>> constraint_names;
137 
138  // These fields are provided for convenience; they don't change the
139  // mathematical definition of the problem, but they change the objective
140  // values reported to the user.
143 };
144 
145 // Returns `InvalidArgumentError` if vector or matrix dimensions are
146 // inconsistent. Returns `OkStatus` otherwise.
148 
149 inline bool IsLinearProgram(const QuadraticProgram& qp) {
150  return !qp.objective_matrix.has_value();
151 }
152 
153 // Checks if the lower and upper bounds of the problem are consistent, i.e. for
154 // each variable and constraint bound we have `lower_bound <= upper_bound`. If
155 // the input is consistent the method returns true, otherwise it returns false.
156 // See also `HasValidBounds(const ShardedQuadraticProgram&)`.
157 bool HasValidBounds(const QuadraticProgram& qp);
158 
159 // Converts an `MPModelProto` into a `QuadraticProgram`.
160 // Returns an error if general constraints are present.
161 // If `relax_integer_variables` is true integer variables are relaxed to
162 // continuous; otherwise integer variables are an error.
163 // If `include_names` is true (the default is false), the problem, constraint,
164 // and variable names are included in the `QuadraticProgram`; otherwise they are
165 // left empty.
166 // Maximization problems are converted to minimization by negating the
167 // objective and setting `objective_scaling_factor` to -1, which preserves the
168 // reported objective values.
169 absl::StatusOr<QuadraticProgram> QpFromMpModelProto(
170  const MPModelProto& proto, bool relax_integer_variables,
171  bool include_names = false);
172 
173 // Returns `InvalidArgumentError` if `qp` is too large to convert to
174 // `MPModelProto` and `OkStatus` otherwise.
175 absl::Status CanFitInMpModelProto(const QuadraticProgram& qp);
176 
177 // Converts a `QuadraticProgram` into an `MPModelProto`. To preserve objective
178 // values in the conversion, `objective_vector`, `objective_matrix`, and
179 // `objective_offset` are scaled by `objective_scaling_factor`, and if
180 // `objective_scaling_factor` is negative, then the proto is a maximization
181 // problem (otherwise it's a minimization problem). Returns
182 // `InvalidArgumentError` if `objective_scaling_factor` is zero or if
183 // `CanFitInMpModelProto()` fails.
184 absl::StatusOr<MPModelProto> QpToMpModelProto(const QuadraticProgram& qp);
185 
186 // Like `matrix.setFromTriplets(triplets)`, except that `setFromTriplets`
187 // results in having three copies of the nonzeros in memory at the same time,
188 // because it first fills one matrix from triplets, and then transposes it into
189 // another. This avoids having the third copy in memory by sorting the triplets,
190 // reserving space in the matrix, and then inserting in sorted order.
191 // Compresses the matrix (`SparseMatrix.makeCompressed()`) after loading it.
192 // NOTE: This intentionally passes `triplets` by value, because it modifies
193 // them. To avoid the copy, pass a move reference.
195  std::vector<Eigen::Triplet<double, int64_t>> triplets,
196  Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t>& matrix);
197 
198 // Utility functions for internal use only.
199 namespace internal {
200 // Like `CanFitInMpModelProto()` but has an extra argument for the largest
201 // number of variables, constraints, or objective non-zeros that should be
202 // counted as convertible. `CanFitInMpModelProto()` passes 2^31 - 1 for this
203 // argument and unit tests pass small values.
204 absl::Status TestableCanFitInMpModelProto(const QuadraticProgram& qp,
205  int64_t largest_ok_size);
206 
207 // Modifies `triplets` in place, combining consecutive entries with the same row
208 // and column, summing their values. This is most effective if `triplets` are
209 // sorted by row and column, so that multiple entries for the same entry will be
210 // consecutive.
212  std::vector<Eigen::Triplet<double, int64_t>>& triplets);
213 } // namespace internal
214 } // namespace operations_research::pdlp
215 
216 #endif // PDLP_QUADRATIC_PROGRAM_H_
CpModelProto proto
void CombineRepeatedTripletsInPlace(std::vector< Eigen::Triplet< double, int64_t >> &triplets)
absl::Status TestableCanFitInMpModelProto(const QuadraticProgram &qp, const int64_t largest_ok_size)
absl::StatusOr< QuadraticProgram > QpFromMpModelProto(const MPModelProto &proto, bool relax_integer_variables, bool include_names)
absl::Status ValidateQuadraticProgramDimensions(const QuadraticProgram &qp)
void SetEigenMatrixFromTriplets(std::vector< Eigen::Triplet< double, int64_t >> triplets, Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix)
absl::Status CanFitInMpModelProto(const QuadraticProgram &qp)
bool HasValidBounds(const QuadraticProgram &qp)
absl::StatusOr< MPModelProto > QpToMpModelProto(const QuadraticProgram &qp)
bool IsLinearProgram(const QuadraticProgram &qp)
int64_t Zero()
NOLINT.
double ApplyObjectiveScalingAndOffset(double objective) const
QuadraticProgram(QuadraticProgram &&other) noexcept
std::optional< std::vector< std::string > > constraint_names
QuadraticProgram(const QuadraticProgram &other)=default
QuadraticProgram(int64_t num_variables, int64_t num_constraints)
QuadraticProgram & operator=(const QuadraticProgram &other)=default
std::optional< std::vector< std::string > > variable_names
void ResizeAndInitialize(int64_t num_variables, int64_t num_constraints)
Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > constraint_matrix
std::optional< Eigen::DiagonalMatrix< double, Eigen::Dynamic > > objective_matrix
QuadraticProgram & operator=(QuadraticProgram &&other)