Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
quadratic_program_io.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <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/base/casts.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/log/check.h"
29#include "absl/status/status.h"
30#include "absl/status/statusor.h"
31#include "absl/strings/match.h"
32#include "absl/strings/str_format.h"
33#include "absl/strings/string_view.h"
34#include "ortools/base/file.h"
41#include "ortools/linear_solver/linear_solver.pb.h"
46
48
49// TODO(user): Update internal helper functions to use references instead of
50// pointers.
51
52QuadraticProgram ReadQuadraticProgramOrDie(const std::string& filename,
53 bool include_names) {
54 if (absl::EndsWith(filename, ".mps") || absl::EndsWith(filename, ".mps.gz") ||
55 absl::EndsWith(filename, ".mps.bz2")) {
56 return ReadMpsLinearProgramOrDie(filename, include_names);
57 }
58 if (absl::EndsWith(filename, ".pb") ||
59 absl::EndsWith(filename, ".textproto") ||
60 absl::EndsWith(filename, ".json") ||
61 absl::EndsWith(filename, ".json.gz")) {
62 return ReadMPModelProtoFileOrDie(filename, include_names);
63 }
64 LOG(QFATAL) << "Invalid filename suffix in " << filename
65 << ". Valid suffixes are .mps, .mps.gz, .pb, .textproto,"
66 << ".json, and .json.gz";
67}
68
70 const std::string& mpmodel_proto_file, bool include_names) {
71 MPModelProto lp_proto;
72 QCHECK_OK(ReadFileToProto(mpmodel_proto_file, &lp_proto))
73 << mpmodel_proto_file;
74 auto result = QpFromMpModelProto(lp_proto, /*relax_integer_variables=*/true,
75 include_names);
76 QCHECK_OK(result);
77 return *std::move(result);
78}
79
80absl::Status WriteLinearProgramToMps(const QuadraticProgram& linear_program,
81 const std::string& mps_file) {
82 if (!IsLinearProgram(linear_program)) {
83 return absl::InvalidArgumentError(
84 "'linear_program' has a quadratic objective");
85 }
86 ASSIGN_OR_RETURN(MPModelProto proto, QpToMpModelProto(linear_program));
87 ASSIGN_OR_RETURN(std::string mps_export, ExportModelAsMpsFormat(proto));
88 File* file;
89 RETURN_IF_ERROR(file::Open(mps_file, "w", &file, file::Defaults()));
90 auto status = file::WriteString(file, mps_export, file::Defaults());
91 status.Update(file->Close(file::Defaults()));
92 return status;
93}
94
96 const QuadraticProgram& quadratic_program,
97 const std::string& mpmodel_proto_file) {
98 ASSIGN_OR_RETURN(MPModelProto proto, QpToMpModelProto(quadratic_program));
99
100 return file::SetBinaryProto(mpmodel_proto_file, proto, file::Defaults());
101}
102
103namespace {
104
105// Class implementing the
106// `ortools/lp_data/mps_reader_template.h` interface that only
107// stores the names of rows and columns, and the number of non-zeros found.
108class MpsReaderDimensionAndNames {
109 public:
110 using IndexType = int64_t;
111 void SetUp() {
112 read_or_parse_failed_ = false;
113 col_name_to_index_.clear();
114 row_name_to_index_.clear();
115 added_non_zeros_ = 0;
116 }
117 void CleanUp() {}
118 double ConstraintLowerBound(IndexType index) { return 0; }
119 double ConstraintUpperBound(IndexType index) { return 0; }
120 IndexType FindOrCreateConstraint(absl::string_view row_name) {
121 const auto [it, success_unused] = row_name_to_index_.try_emplace(
122 row_name, static_cast<IndexType>(row_name_to_index_.size()));
123 return it->second;
124 }
125 IndexType FindOrCreateVariable(absl::string_view col_name) {
126 const auto [it, success_unused] = col_name_to_index_.try_emplace(
127 col_name, static_cast<IndexType>(col_name_to_index_.size()));
128 return it->second;
129 }
130 void SetConstraintBounds(IndexType row_index, double lower_bound,
131 double upper_bound) {}
132 void SetConstraintCoefficient(IndexType row_index, IndexType col_index,
133 double coefficient) {
134 ++added_non_zeros_;
135 }
136 void SetIsLazy(IndexType row_index) {}
137 void SetName(absl::string_view problem_name) {}
138 void SetObjectiveCoefficient(IndexType index, double coefficient) {}
139 void SetObjectiveDirection(bool maximize) {}
140 void SetObjectiveOffset(double offset) {}
141 void SetVariableTypeToInteger(IndexType index) {}
142 void SetVariableTypeToSemiContinuous(IndexType index) {
143 LOG_FIRST_N(WARNING, 1)
144 << "Semi-continuous variables not supported, failed to parse file";
145 read_or_parse_failed_ = true;
146 }
147 void SetVariableBounds(IndexType index, double lower_bound,
148 double upper_bound) {}
149 double VariableLowerBound(IndexType index) { return 0; }
150 double VariableUpperBound(IndexType index) { return 0; }
151 absl::Status CreateIndicatorConstraint(absl::string_view row_name,
152 IndexType col_index, bool var_value) {
153 absl::string_view message =
154 "Indicator constraints not supported, failed to parse file";
155 LOG_FIRST_N(WARNING, 1) << message;
156 return absl::InvalidArgumentError(message);
157 }
158
159 // Lookup constant functions. They assume that `row_name` or `col_name`,
160 // respectively, have been previously added to the internal set of rows or
161 // variables. The functions QFAIL if this is not true.
162 IndexType FindVariableOrDie(absl::string_view col_name) const {
163 const auto it = col_name_to_index_.find(col_name);
164 if (it == col_name_to_index_.end()) {
165 LOG(QFATAL) << absl::StrFormat(
166 "column `%s` not previously found in file, internal error?",
167 col_name);
168 }
169 return it->second;
170 }
171 IndexType FindConstraintOrDie(absl::string_view row_name) const {
172 const auto it = row_name_to_index_.find(row_name);
173 if (it == row_name_to_index_.end()) {
174 LOG(QFATAL) << absl::StrFormat(
175 "row `%s` not previously found in file, internal error?", row_name);
176 }
177 return it->second;
178 }
179
180 // Number of non-zeros added so-far.
181 int64_t AddedNonZeros() const { return added_non_zeros_; }
182
183 // Number of variables added so-far.
184 int64_t NumVariables() const { return col_name_to_index_.size(); }
185
186 // Number of constraints added so-far.
187 int64_t NumConstraints() const { return row_name_to_index_.size(); }
188
189 // Return `true` if an unsupported MPS section was found.
190 bool FailedToParse() const { return read_or_parse_failed_; }
191
192 // Return a const-hash map of col names to indices.
193 const absl::flat_hash_map<std::string, IndexType>& ColNameIndexMap() const {
194 return col_name_to_index_;
195 }
196
197 // Return a const-hash map of row names to indices.
198 const absl::flat_hash_map<std::string, IndexType>& RowNameIndexMap() const {
199 return row_name_to_index_;
200 }
201
202 private:
203 bool read_or_parse_failed_ = false;
204 absl::flat_hash_map<std::string, IndexType> col_name_to_index_;
205 absl::flat_hash_map<std::string, IndexType> row_name_to_index_;
206 int64_t added_non_zeros_ = 0;
207};
208
209// Class implementing the
210// `ortools/lp_data/mps_reader_template.h` interface.
211// It is intended to be used in conjunction with `MpsReaderDimensionAndNames`
212// as follows:
213//
214// // Retrieve dimension and name information from file.
215// MpsReaderDimensionAndNames dimension_and_name;
216// MPSReaderTemplate<MpsReaderDimensionAndNames> dimension_parser;
217// dimension_parser.ParseFile(file_name, &dimension_and_name);
218// // Store QP problem coefficients.
219// MpsReaderQpDataWrapper qp_wrapper(&dimension_and_name, include_names);
220// MPSReaderTemplate<MpsReaderQpDataWrapper> qp_parser;
221// qp_parser.ParseFile(file_name, &qp_wrapper);
222// // Retrieve fully assembled QP.
223// QuadraticProgram result = qp_wrapper.GetAndClearQuadraticProgram();
224class MpsReaderQpDataWrapper {
225 public:
226 using IndexType = int64_t;
227 // Constructor, `*dimension_and_names` must outlive the class, and be
228 // constant during the object's lifetime. If `include_names`=true, the
229 // resulting QuadraticProgram from `GetAndClearQuadraticProgram()` will
230 // include name information from the MPS file.
231 // NOTE: The code assumes that the file to be read is the same file already
232 // read using the `*dimension_and_names` argument.
233 MpsReaderQpDataWrapper(const MpsReaderDimensionAndNames* dimension_and_names,
234 bool include_names)
235 : include_names_{include_names},
236 dimension_and_names_{*dimension_and_names} {}
237
238 // Implements the `MPSReaderTemplate` interface.
239 void SetUp() {
240 const int64_t num_variables = dimension_and_names_.NumVariables();
241 const int64_t num_constraints = dimension_and_names_.NumConstraints();
242 triplets_.reserve(dimension_and_names_.AddedNonZeros());
243 quadratic_program_ = QuadraticProgram(/*num_variables=*/num_variables,
244 /*num_constraints=*/num_constraints);
245 // Default variables in MPS files have a zero lower bound, an infinity
246 // upper bound, and a zero objective; while default constraints are
247 // 'equal to zero' constraints.
248 quadratic_program_.constraint_lower_bounds =
249 Eigen::VectorXd::Zero(num_constraints);
250 quadratic_program_.constraint_upper_bounds =
251 Eigen::VectorXd::Zero(num_constraints);
252 quadratic_program_.variable_lower_bounds =
253 Eigen::VectorXd::Zero(num_variables);
254 }
255 void CleanUp() {
256 SetEigenMatrixFromTriplets(std::move(triplets_),
257 quadratic_program_.constraint_matrix);
258 // Deal with maximization problems.
259 if (quadratic_program_.objective_scaling_factor == -1) {
260 quadratic_program_.objective_offset *= -1;
261 for (double& objective_coefficient :
262 quadratic_program_.objective_vector) {
263 objective_coefficient *= -1;
264 }
265 }
266 if (include_names_) {
267 quadratic_program_.variable_names =
268 std::vector<std::string>(dimension_and_names_.NumVariables());
269 quadratic_program_.constraint_names =
270 std::vector<std::string>(dimension_and_names_.NumConstraints());
271 for (const auto& [name, index] : dimension_and_names_.ColNameIndexMap()) {
272 (*quadratic_program_.variable_names)[index] = name;
273 }
274 for (const auto& [name, index] : dimension_and_names_.RowNameIndexMap()) {
275 (*quadratic_program_.constraint_names)[index] = name;
276 }
277 }
278 }
279 double ConstraintLowerBound(IndexType index) {
280 return quadratic_program_.constraint_lower_bounds[index];
281 }
282 double ConstraintUpperBound(IndexType index) {
283 return quadratic_program_.constraint_upper_bounds[index];
284 }
285 IndexType FindOrCreateConstraint(absl::string_view row_name) {
286 return dimension_and_names_.FindConstraintOrDie(row_name);
287 }
288 IndexType FindOrCreateVariable(absl::string_view col_name) {
289 return dimension_and_names_.FindVariableOrDie(col_name);
290 }
291 void SetConstraintBounds(IndexType index, double lower_bound,
292 double upper_bound) {
293 quadratic_program_.constraint_lower_bounds[index] = lower_bound;
294 quadratic_program_.constraint_upper_bounds[index] = upper_bound;
295 }
296 void SetConstraintCoefficient(IndexType row_index, IndexType col_index,
297 double coefficient) {
298 DCHECK_LT(triplets_.size(), triplets_.capacity());
299 triplets_.emplace_back(row_index, col_index, coefficient);
300 }
301 void SetIsLazy(IndexType row_index) {
302 LOG_FIRST_N(WARNING, 1) << "Lazy constraint information lost, treated as "
303 "regular constraint instead";
304 }
305 void SetName(absl::string_view problem_name) {
306 if (include_names_) {
307 quadratic_program_.problem_name = std::string(problem_name);
308 }
309 }
310 void SetObjectiveCoefficient(IndexType index, double coefficient) {
311 quadratic_program_.objective_vector[index] = coefficient;
312 }
313 void SetObjectiveDirection(bool maximize) {
314 quadratic_program_.objective_scaling_factor = (maximize ? -1 : 1);
315 }
316 void SetObjectiveOffset(double offset) {
317 quadratic_program_.objective_offset = offset;
318 }
319 void SetVariableTypeToInteger(IndexType index) {
320 LOG_FIRST_N(WARNING, 1) << "Dropping integrality requirements, all "
321 "variables treated as continuous";
322 }
323 void SetVariableTypeToSemiContinuous(IndexType index) {
324 // This line should never execute, as the code must fail on
325 // `MpsReaderDimensionAndNames::SetVariableTypeToSemiContinuous` before.
326 LOG(QFATAL) << "Semi-continuous variables are unsupported, and should have "
327 "been detected before (in MpsReaderDimensionAndNames)";
328 }
329 void SetVariableBounds(IndexType index, double lower_bound,
330 double upper_bound) {
331 quadratic_program_.variable_lower_bounds[index] = lower_bound;
332 quadratic_program_.variable_upper_bounds[index] = upper_bound;
333 }
334 double VariableLowerBound(IndexType index) {
335 return quadratic_program_.variable_lower_bounds[index];
336 }
337 double VariableUpperBound(IndexType index) {
338 return quadratic_program_.variable_upper_bounds[index];
339 }
340 absl::Status CreateIndicatorConstraint(absl::string_view row_name,
341 IndexType col_index, bool var_value) {
342 // This function should never be called, as the code must fail on
343 // `MpsReaderDimensionAndNames::CreateIndicatorConstraint` before.
344 LOG(QFATAL) << "Indicator constraints are unsupported, and should have "
345 "been detected before (in MpsReaderDimensionAndNames)";
346 }
347
348 // Returns a `QuadraticProgram` holding all information read by the
349 // `mps_reader_template.h` interface. It leaves the internal quadratic
350 // program in an indeterminate state.
351 QuadraticProgram&& GetAndClearQuadraticProgram() {
352 return std::move(quadratic_program_);
353 }
354
355 private:
356 bool include_names_;
357 QuadraticProgram quadratic_program_;
358 const MpsReaderDimensionAndNames& dimension_and_names_;
359 std::vector<Eigen::Triplet<double, int64_t>> triplets_;
360};
361
362} // namespace
363
364absl::StatusOr<QuadraticProgram> ReadMpsLinearProgram(
365 const std::string& lp_file, bool include_names) {
366 MpsReaderDimensionAndNames dimension_and_names;
367 // Collect MPS format, sizes and names.
369 const absl::StatusOr<MPSReaderFormat> pass_one_format =
370 pass_one_reader.ParseFile(lp_file, &dimension_and_names,
371 MPSReaderFormat::kAutoDetect);
372 if (!pass_one_format.ok()) {
373 return util::StatusBuilder(pass_one_format.status()) << absl::StrFormat(
374 "Could not read or parse file `%s` as an MPS file", lp_file);
375 }
376 if (dimension_and_names.FailedToParse()) {
377 return absl::InvalidArgumentError(absl::StrFormat(
378 "Could not read or parse file `%s` as an MPS file, or unsupported "
379 "features/sections found",
380 lp_file));
381 }
382 DCHECK(*pass_one_format == MPSReaderFormat::kFixed ||
383 *pass_one_format == MPSReaderFormat::kFree);
384 // Populate the QP with pre-allocated sizes.
385 MpsReaderQpDataWrapper qp_data_wrapper(&dimension_and_names, include_names);
387 const absl::StatusOr<MPSReaderFormat> pass_two_format =
388 pass_two_reader.ParseFile(lp_file, &qp_data_wrapper, *pass_one_format);
389 if (!pass_two_format.ok()) {
390 return util::StatusBuilder(pass_two_format.status()) << absl::StrFormat(
391 "Could not read or parse file `%s` as an MPS file "
392 "(maybe file changed between reads?)",
393 lp_file);
394 }
395 DCHECK(*pass_one_format == *pass_two_format);
396 return qp_data_wrapper.GetAndClearQuadraticProgram();
397}
398
400 bool include_names) {
401 const absl::StatusOr<QuadraticProgram> result =
402 ReadMpsLinearProgram(lp_file, include_names);
403 if (!result.ok()) {
404 LOG(QFATAL) << "Error reading MPS Linear Program from " << lp_file << ": "
405 << result.status().message();
406 }
407 return *result;
408}
409
410} // namespace operations_research::pdlp
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
Definition file.h:30
absl::StatusOr< MPSReaderFormat > ParseFile(absl::string_view file_name, DataWrapper *data, MPSReaderFormat form=MPSReaderFormat::kAutoDetect)
CpModelProto proto
The output proto.
const std::string name
A name for logging purposes.
absl::Status status
Definition g_gurobi.cc:44
double upper_bound
double lower_bound
int index
Definition file.cc:169
absl::Status Open(absl::string_view filename, absl::string_view mode, File **f, Options options)
As of 2016-01, these methods can only be used with flags = file::Defaults().
Definition file.cc:170
absl::Status SetBinaryProto(absl::string_view filename, const google::protobuf::Message &proto, Options options)
Definition file.cc:360
absl::Status WriteString(File *file, absl::string_view contents, Options options)
Definition file.cc:231
Options Defaults()
Definition file.h:109
int NumVariables(const VariablesProto &variables)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
Validation utilities for solvers.proto.
absl::StatusOr< MPModelProto > QpToMpModelProto(const QuadraticProgram &qp)
QuadraticProgram ReadMPModelProtoFileOrDie(const std::string &mpmodel_proto_file, bool include_names)
absl::StatusOr< QuadraticProgram > QpFromMpModelProto(const MPModelProto &proto, bool relax_integer_variables, bool include_names)
QuadraticProgram ReadQuadraticProgramOrDie(const std::string &filename, bool include_names)
bool IsLinearProgram(const QuadraticProgram &qp)
absl::Status WriteLinearProgramToMps(const QuadraticProgram &linear_program, const std::string &mps_file)
QuadraticProgram ReadMpsLinearProgramOrDie(const std::string &lp_file, bool include_names)
void SetEigenMatrixFromTriplets(std::vector< Eigen::Triplet< double, int64_t > > triplets, Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > &matrix)
absl::Status WriteQuadraticProgramToMPModelProto(const QuadraticProgram &quadratic_program, const std::string &mpmodel_proto_file)
absl::StatusOr< QuadraticProgram > ReadMpsLinearProgram(const std::string &lp_file, bool include_names)
absl::StatusOr< std::string > ExportModelAsMpsFormat(const MPModelProto &model, const MPModelExportOptions &options)
absl::Status ReadFileToProto(absl::string_view filename, google::protobuf::Message *proto, bool allow_partial)
Definition file_util.cc:53
int64_t coefficient
std::string message
Definition trace.cc:397