From 32cdaa73f72e2f71d4f50ce6b0e675daa56e8ce8 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Fri, 12 Jul 2024 17:45:41 +0200 Subject: [PATCH] add WriteToMpsFile in model_builder, model_exporter --- .../ortools/modelbuilder/ModelBuilder.java | 4 ++ ortools/linear_solver/BUILD.bazel | 2 + ortools/linear_solver/csharp/ModelBuilder.cs | 5 ++ ortools/linear_solver/csharp/model_builder.i | 7 +++ ortools/linear_solver/java/modelbuilder.i | 7 +++ ortools/linear_solver/model_exporter.cc | 50 ++++++++++++++++--- ortools/linear_solver/model_exporter.h | 31 ++++++++++++ ortools/linear_solver/python/model_builder.py | 5 ++ .../python/model_builder_helper.cc | 2 + ortools/linear_solver/solve.cc | 10 ++++ .../wrappers/model_builder_helper.cc | 5 ++ .../wrappers/model_builder_helper.h | 3 ++ 12 files changed, 125 insertions(+), 6 deletions(-) diff --git a/ortools/java/com/google/ortools/modelbuilder/ModelBuilder.java b/ortools/java/com/google/ortools/modelbuilder/ModelBuilder.java index c38766aa8e..de15dbbaa8 100644 --- a/ortools/java/com/google/ortools/modelbuilder/ModelBuilder.java +++ b/ortools/java/com/google/ortools/modelbuilder/ModelBuilder.java @@ -338,6 +338,10 @@ public final class ModelBuilder { return helper.exportToLpString(obfuscate); } + public boolean writeToMpsFile(String filename, boolean obfuscate) { + return helper.writeToMpsFile(filename, obfuscate); + } + public boolean importFromMpsString(String mpsString) { return helper.importFromMpsString(mpsString); } diff --git a/ortools/linear_solver/BUILD.bazel b/ortools/linear_solver/BUILD.bazel index b05f16c931..4d1b63d88e 100644 --- a/ortools/linear_solver/BUILD.bazel +++ b/ortools/linear_solver/BUILD.bazel @@ -376,6 +376,7 @@ cc_library( deps = [ ":linear_solver_cc_proto", "//ortools/base", + "//ortools/base:file", "//ortools/base:hash", "//ortools/base:map_util", "//ortools/util:fp_utils", @@ -395,6 +396,7 @@ cc_binary( ":linear_solver", ":linear_solver_cc_proto", "//ortools/base", + "//ortools/base:file", "//ortools/lp_data:lp_parser", "//ortools/lp_data:model_reader", "//ortools/lp_data:mps_reader", diff --git a/ortools/linear_solver/csharp/ModelBuilder.cs b/ortools/linear_solver/csharp/ModelBuilder.cs index d858bd174a..41082ea80a 100644 --- a/ortools/linear_solver/csharp/ModelBuilder.cs +++ b/ortools/linear_solver/csharp/ModelBuilder.cs @@ -399,6 +399,11 @@ public class Model return helper_.ExportToLpString(obfuscate); } + public bool WriteToMpsFile(String filename, bool obfuscate) + { + return helper_.WriteToMpsFile(filename, obfuscate); + } + public bool ImportFromMpsString(String mpsString) { return helper_.ImportFromMpsString(mpsString); diff --git a/ortools/linear_solver/csharp/model_builder.i b/ortools/linear_solver/csharp/model_builder.i index 2a8cff5e03..d82b9f7893 100644 --- a/ortools/linear_solver/csharp/model_builder.i +++ b/ortools/linear_solver/csharp/model_builder.i @@ -39,6 +39,12 @@ VECTOR_AS_CSHARP_ARRAY(double, double, double, DoubleVector); options.obfuscate = obfuscate; return $self->ExportToLpString(options); } + + bool WriteToMpsFile(const std::string& filename, bool obfuscate) { + operations_research::MPModelExportOptions options; + options.obfuscate = obfuscate; + return $self->WriteToMpsFile(filename, options); + } } // Extend operations_research::ModelBuilderHelper %ignoreall @@ -120,6 +126,7 @@ VECTOR_AS_CSHARP_ARRAY(double, double, double, DoubleVector); %unignore operations_research::ModelBuilderHelper::ImportFromMpsFile; %unignore operations_research::ModelBuilderHelper::ImportFromLpString; %unignore operations_research::ModelBuilderHelper::ImportFromLpFile; +%unignore operations_research::ModelBuilderHelper::WriteToMpsFile(std::string, bool); %unignore operations_research::ModelBuilderHelper::ExportToMpsString(bool); %unignore operations_research::ModelBuilderHelper::ExportToLpString(bool); %unignore operations_research::ModelBuilderHelper::OverwriteModel; diff --git a/ortools/linear_solver/java/modelbuilder.i b/ortools/linear_solver/java/modelbuilder.i index 93d062dbac..e1b17ce7d9 100644 --- a/ortools/linear_solver/java/modelbuilder.i +++ b/ortools/linear_solver/java/modelbuilder.i @@ -99,6 +99,12 @@ class GlobalRefGuard { options.obfuscate = obfuscate; return $self->ExportToLpString(options); } + + bool writeToMpsFile(const std::string& filename, bool obfuscate) { + operations_research::MPModelExportOptions options; + options.obfuscate = obfuscate; + return $self->WriteToMpsFile(filename, options); + } } // Extend operations_research::ModelBuilderHelper %ignoreall @@ -182,6 +188,7 @@ class GlobalRefGuard { %rename (importFromLpFile) operations_research::ModelBuilderHelper::ImportFromLpFile; %unignore operations_research::ModelBuilderHelper::exportToMpsString; %unignore operations_research::ModelBuilderHelper::exportToLpString; +%unignore operations_research::ModelBuilderHelper::writeToMpsFile; %rename (overwriteModel) operations_research::ModelBuilderHelper::OverwriteModel; %unignore operations_research::ModelSolverHelper; diff --git a/ortools/linear_solver/model_exporter.cc b/ortools/linear_solver/model_exporter.cc index 02c1e68f7c..dab49a8c0c 100644 --- a/ortools/linear_solver/model_exporter.cc +++ b/ortools/linear_solver/model_exporter.cc @@ -22,6 +22,7 @@ #include #include "absl/container/flat_hash_set.h" +#include "absl/flags/flag.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/ascii.h" @@ -29,11 +30,12 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" -#include "ortools/base/commandlineflags.h" +#include "ortools/base/file.h" +#include "ortools/base/helpers.h" #include "ortools/base/logging.h" -#include "ortools/base/map_util.h" +#include "ortools/base/options.h" +#include "ortools/base/status_macros.h" #include "ortools/linear_solver/linear_solver.pb.h" -#include "ortools/util/fp_utils.h" ABSL_FLAG(bool, lp_log_invalid_name, false, "DEPRECATED."); @@ -91,7 +93,8 @@ class MPModelProtoExporter { bool ExportModelAsLpFormat(const MPModelExportOptions& options, std::string* output); bool ExportModelAsMpsFormat(const MPModelExportOptions& options, - std::string* output); + std::string* output, File* output_file); + void FlushAndResetString(std::string* output, File* file); private: // Computes the number of continuous, integer and binary variables. @@ -241,12 +244,32 @@ absl::StatusOr ExportModelAsMpsFormat( } MPModelProtoExporter exporter(model); std::string output; - if (!exporter.ExportModelAsMpsFormat(options, &output)) { + if (!exporter.ExportModelAsMpsFormat(options, &output, nullptr)) { return absl::InvalidArgumentError("Unable to export model."); } return output; } +absl::Status WriteModelToMpsFile(absl::string_view filename, + const MPModelProto& model, + const MPModelExportOptions& options) { + if (model.general_constraint_size() > 0) { + return absl::InvalidArgumentError("General constraints are not supported."); + } + File* file; + RETURN_IF_ERROR(file::Open(filename, "w", &file, file::Defaults())); + + absl::Status status = absl::OkStatus(); + MPModelProtoExporter exporter(model); + std::string output; + if (!exporter.ExportModelAsMpsFormat(options, &output, file)) { + status.Update(file->Close(file::Defaults())); // Even if Write() fails! + return absl::InvalidArgumentError("Unable to export model."); + } + status.Update(file->Close(file::Defaults())); + return status; +} + namespace { MPModelProtoExporter::MPModelProtoExporter(const MPModelProto& model) : proto_(model), @@ -740,8 +763,17 @@ void MPModelProtoExporter::AppendMpsColumns( } } +void MPModelProtoExporter::FlushAndResetString(std::string* output, + File* output_file) { + if (output_file == nullptr) return; + if (output->empty()) return; + CHECK_OK(file::WriteString(output_file, *output, file::Defaults())); + output->clear(); +} + bool MPModelProtoExporter::ExportModelAsMpsFormat( - const MPModelExportOptions& options, std::string* output) { + const MPModelExportOptions& options, std::string* output, + File* output_file) { output->clear(); Setup(); ComputeMpsSmartColumnWidths(options.obfuscate); @@ -787,6 +819,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat( if (!rows_section.empty()) { absl::StrAppend(output, "ROWS\n", rows_section); } + FlushAndResetString(output, output_file); // As the information regarding a column needs to be contiguous, we create // a vector associating a variable index to a vector containing the indices @@ -825,6 +858,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat( if (!columns_section.empty()) { absl::StrAppend(output, "COLUMNS\n", columns_section); } + FlushAndResetString(output, output_file); // RHS (right-hand-side) section. current_mps_column_ = 0; @@ -850,6 +884,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat( if (!rhs_section.empty()) { absl::StrAppend(output, "RHS\n", rhs_section); } + FlushAndResetString(output, output_file); // RANGES section. current_mps_column_ = 0; @@ -866,6 +901,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat( if (!ranges_section.empty()) { absl::StrAppend(output, "RANGES\n", ranges_section); } + FlushAndResetString(output, output_file); // BOUNDS section. current_mps_column_ = 0; @@ -926,8 +962,10 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat( if (!bounds_section.empty()) { absl::StrAppend(output, "BOUNDS\n", bounds_section); } + FlushAndResetString(output, output_file); absl::StrAppend(output, "ENDATA\n"); + FlushAndResetString(output, output_file); return true; } diff --git a/ortools/linear_solver/model_exporter.h b/ortools/linear_solver/model_exporter.h index 840c56eb6d..6123b0b35e 100644 --- a/ortools/linear_solver/model_exporter.h +++ b/ortools/linear_solver/model_exporter.h @@ -16,7 +16,9 @@ #include +#include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/string_view.h" #include "ortools/linear_solver/linear_solver.pb.h" namespace operations_research { @@ -95,6 +97,35 @@ absl::StatusOr ExportModelAsMpsFormat( const MPModelProto& model, const MPModelExportOptions& options = MPModelExportOptions()); +/** + * Write the current model (variables, constraints, objective) to a file in MPS + * file format, using the "free" MPS format. + * + * Returns false if some error has occurred during execution. Models with + * maximization objectives trigger an error, because MPS can encode only + * minimization problems. + * + * The validity of names is automatically checked. If a variable name or a + * constraint name is invalid or non-existent, a new valid name is + * automatically generated. + * + * Name validity and obfuscation works exactly as in ExportModelAsLpFormat(). + * + * For more information about the MPS format: + * http://en.wikipedia.org/wiki/MPS_(format) + * A close-to-original description coming from OSL: + * http://tinyurl.com/mps-format-by-osl + * A recent description from CPLEX: + * http://tinyurl.com/mps-format-by-cplex + * CPLEX extensions: + * http://tinyurl.com/mps-extensions-by-cplex + * Gurobi's description: + * http://www.gurobi.com/documentation/5.1/reference-manual/node869 + */ +absl::Status WriteModelToMpsFile( + absl::string_view filename, const MPModelProto& model, + const MPModelExportOptions& options = MPModelExportOptions()); + } // namespace operations_research #endif // OR_TOOLS_LINEAR_SOLVER_MODEL_EXPORTER_H_ diff --git a/ortools/linear_solver/python/model_builder.py b/ortools/linear_solver/python/model_builder.py index 9a759ceb2f..bca0e6df2c 100644 --- a/ortools/linear_solver/python/model_builder.py +++ b/ortools/linear_solver/python/model_builder.py @@ -1581,6 +1581,11 @@ class Model: options.obfuscate = obfuscate return self.__helper.export_to_mps_string(options) + def write_to_mps_file(self, filename: str, obfuscate: bool = False) -> bool: + options: mbh.MPModelExportOptions = mbh.MPModelExportOptions() + options.obfuscate = obfuscate + return self.__helper.write_to_mps_file(filename, options) + def export_to_proto(self) -> linear_solver_pb2.MPModelProto: """Exports the optimization model to a ProtoBuf format.""" return mbh.to_mpmodel_proto(self.__helper) diff --git a/ortools/linear_solver/python/model_builder_helper.cc b/ortools/linear_solver/python/model_builder_helper.cc index 4fbc135fa6..c83ef804a3 100644 --- a/ortools/linear_solver/python/model_builder_helper.cc +++ b/ortools/linear_solver/python/model_builder_helper.cc @@ -175,6 +175,8 @@ PYBIND11_MODULE(model_builder_helper, m) { arg("options") = MPModelExportOptions()) .def("export_to_lp_string", &ModelBuilderHelper::ExportToLpString, arg("options") = MPModelExportOptions()) + .def("write_to_mps_file", &ModelBuilderHelper::WriteToMpsFile, + arg("filename"), arg("options") = MPModelExportOptions()) .def("read_model_from_proto_file", &ModelBuilderHelper::ReadModelFromProtoFile, arg("filename")) .def("write_model_to_proto_file", diff --git a/ortools/linear_solver/solve.cc b/ortools/linear_solver/solve.cc index b5e914fe9e..325853635a 100644 --- a/ortools/linear_solver/solve.cc +++ b/ortools/linear_solver/solve.cc @@ -55,10 +55,12 @@ #include #include +#include "absl/flags/declare.h" #include "absl/flags/flag.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" #include "ortools/base/file.h" @@ -68,6 +70,7 @@ #include "ortools/base/options.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/linear_solver.pb.h" +#include "ortools/linear_solver/model_exporter.h" #include "ortools/lp_data/lp_parser.h" #include "ortools/lp_data/mps_reader.h" #include "ortools/lp_data/sol_reader.h" @@ -114,6 +117,8 @@ ABSL_FLAG(std::string, dump_response, "", "If non-empty, dumps MPSolutionResponse there."); ABSL_FLAG(std::string, sol_file, "", "If non-empty, output the best solution in Miplib .sol format."); +ABSL_FLAG(std::string, dump_mps, "", + "If non-empty, dumps the model in mps format there."); ABSL_DECLARE_FLAG(bool, verify_solution); // Defined in ./linear_solver.cc ABSL_DECLARE_FLAG(bool, @@ -310,6 +315,11 @@ void Run() { << absl::GetFlag(FLAGS_dump_format); } + if (!absl::GetFlag(FLAGS_dump_mps).empty()) { + CHECK_OK(WriteModelToMpsFile(absl::GetFlag(FLAGS_dump_mps), + request_proto.model())); + } + // Set or override request proto options from the command line flags. if (type.has_value() || !request_proto.has_solver_type()) { request_proto.set_solver_type(static_cast( diff --git a/ortools/linear_solver/wrappers/model_builder_helper.cc b/ortools/linear_solver/wrappers/model_builder_helper.cc index 438eac97de..3f2c77114e 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.cc +++ b/ortools/linear_solver/wrappers/model_builder_helper.cc @@ -69,6 +69,11 @@ std::string ModelBuilderHelper::ExportToLpString( .value_or(""); } +bool ModelBuilderHelper::WriteToMpsFile(const std::string& filename, + const MPModelExportOptions& options) { + return WriteModelToMpsFile(filename, model_, options).ok(); +} + bool ModelBuilderHelper::ReadModelFromProtoFile(const std::string& filename) { if (file::GetTextProto(filename, &model_, file::Defaults()).ok() || file::GetBinaryProto(filename, &model_, file::Defaults()).ok()) { diff --git a/ortools/linear_solver/wrappers/model_builder_helper.h b/ortools/linear_solver/wrappers/model_builder_helper.h index 24525afaa8..6fa20539c1 100644 --- a/ortools/linear_solver/wrappers/model_builder_helper.h +++ b/ortools/linear_solver/wrappers/model_builder_helper.h @@ -51,6 +51,9 @@ class ModelBuilderHelper { options = MPModelExportOptions()); std::string ExportToLpString(const operations_research::MPModelExportOptions& options = MPModelExportOptions()); + bool WriteToMpsFile(const std::string& filename, + const operations_research::MPModelExportOptions& options = + MPModelExportOptions()); bool ReadModelFromProtoFile(const std::string& filename); bool WriteModelToProtoFile(const std::string& filename);