diff --git a/ortools/algorithms/BUILD.bazel b/ortools/algorithms/BUILD.bazel index 25a3579bd3..e6da9cbbae 100644 --- a/ortools/algorithms/BUILD.bazel +++ b/ortools/algorithms/BUILD.bazel @@ -348,6 +348,7 @@ cc_library( srcs = ["set_cover_reader.cc"], hdrs = ["set_cover_reader.h"], deps = [ + ":set_cover_cc_proto", ":set_cover_model", "//ortools/base:file", "//ortools/util:filelineiter", diff --git a/ortools/algorithms/hungarian.cc b/ortools/algorithms/hungarian.cc index 13ce012024..6761d950a5 100644 --- a/ortools/algorithms/hungarian.cc +++ b/ortools/algorithms/hungarian.cc @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -51,7 +52,7 @@ class HungarianOptimizer { private: typedef void (HungarianOptimizer::*Step)(); - typedef enum { NONE, PRIME, STAR } Mark; + typedef enum : uint8_t { NONE, PRIME, STAR } Mark; // Convert the final cost matrix into a set of assignments of preimage->image. // Returns the assignment in the two vectors passed as argument, the same as diff --git a/ortools/algorithms/python/set_cover.cc b/ortools/algorithms/python/set_cover.cc index 5474dc6d8c..141380358e 100644 --- a/ortools/algorithms/python/set_cover.cc +++ b/ortools/algorithms/python/set_cover.cc @@ -39,8 +39,18 @@ using ::operations_research::GuidedLocalSearch; using ::operations_research::GuidedTabuSearch; using ::operations_research::LazyElementDegreeSolutionGenerator; using ::operations_research::RandomSolutionGenerator; -using ::operations_research::ReadBeasleySetCoverProblem; -using ::operations_research::ReadRailSetCoverProblem; +using ::operations_research::ReadFimiDat; +using ::operations_research::ReadOrlibRail; +using ::operations_research::ReadOrlibScp; +using ::operations_research::ReadSetCoverProto; +using ::operations_research::ReadSetCoverSolutionProto; +using ::operations_research::ReadSetCoverSolutionText; +using ::operations_research::WriteOrlibRail; +using ::operations_research::WriteOrlibScp; +using ::operations_research::WriteSetCoverProto; +using ::operations_research::WriteSetCoverSolutionProto; +using ::operations_research::WriteSetCoverSolutionText; + using ::operations_research::SetCoverDecision; using ::operations_research::SetCoverInvariant; using ::operations_research::SetCoverModel; @@ -550,8 +560,17 @@ PYBIND11_MODULE(set_cover, m) { }); // set_cover_reader.h - m.def("read_beasly_set_cover_problem", &ReadBeasleySetCoverProblem); - m.def("read_rail_set_cover_problem", &ReadRailSetCoverProblem); + m.def("read_orlib_scp", &ReadOrlibScp); + m.def("read_orlib_rail", &ReadOrlibRail); + m.def("read_fimi_dat", &ReadFimiDat); + m.def("read_set_cover_proto", &ReadSetCoverProto); + m.def("write_orlib_scp", &WriteOrlibScp); + m.def("write_orlib_rail", &WriteOrlibRail); + m.def("write_set_cover_proto", &WriteSetCoverProto); + m.def("write_set_cover_solution_text", &WriteSetCoverSolutionText); + m.def("write_set_cover_solution_proto", &WriteSetCoverSolutionProto); + m.def("read_set_cover_solution_text", &ReadSetCoverSolutionText); + m.def("read_set_cover_solution_proto", &ReadSetCoverSolutionProto); // set_cover_lagrangian.h // TODO(user): add support for SetCoverLagrangian. diff --git a/ortools/algorithms/set_cover_reader.cc b/ortools/algorithms/set_cover_reader.cc index d773f2ddf5..73e2cfa945 100644 --- a/ortools/algorithms/set_cover_reader.cc +++ b/ortools/algorithms/set_cover_reader.cc @@ -16,12 +16,18 @@ #include #include #include +#include +#include #include "absl/log/check.h" #include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "ortools/algorithms/set_cover.pb.h" #include "ortools/algorithms/set_cover_model.h" #include "ortools/base/file.h" +#include "ortools/base/helpers.h" #include "ortools/base/logging.h" #include "ortools/base/options.h" #include "ortools/util/filelineiter.h" @@ -31,6 +37,8 @@ namespace operations_research { class SetCoverReader { public: explicit SetCoverReader(File* file); + absl::string_view GetLine() { return line_; } + void Advance() { ++line_iter_; } absl::string_view GetNextToken(); double ParseNextDouble(); int64_t ParseNextInteger(); @@ -90,7 +98,8 @@ int64_t SetCoverReader::ParseNextInteger() { return value; } -SetCoverModel ReadBeasleySetCoverProblem(absl::string_view filename) { +// This is a row-based format where the elements are 1-indexed. +SetCoverModel ReadOrlibScp(absl::string_view filename) { SetCoverModel model; File* file(file::OpenOrDie(filename, "r", file::Defaults())); SetCoverReader reader(file); @@ -104,6 +113,7 @@ SetCoverModel ReadBeasleySetCoverProblem(absl::string_view filename) { for (ElementIndex element : ElementRange(num_rows)) { const RowEntryIndex row_size(reader.ParseNextInteger()); for (RowEntryIndex entry(0); entry < row_size; ++entry) { + // Correct the 1-indexing. const int subset(reader.ParseNextInteger() - 1); model.AddElementToSubset(element.value(), subset); } @@ -113,21 +123,23 @@ SetCoverModel ReadBeasleySetCoverProblem(absl::string_view filename) { return model; } -SetCoverModel ReadRailSetCoverProblem(absl::string_view filename) { +// This is a column-based format where the elements are 1-indexed. +SetCoverModel ReadOrlibRail(absl::string_view filename) { SetCoverModel model; File* file(file::OpenOrDie(filename, "r", file::Defaults())); SetCoverReader reader(file); const ElementIndex num_rows(reader.ParseNextInteger()); const BaseInt num_cols(reader.ParseNextInteger()); model.ReserveNumSubsets(num_cols); - for (int i(0); i < num_cols; ++i) { + for (int subset(0); subset < num_cols; ++subset) { const double cost(reader.ParseNextDouble()); - model.SetSubsetCost(i, cost); + model.SetSubsetCost(subset, cost); const ColumnEntryIndex column_size(reader.ParseNextInteger()); - model.ReserveNumElementsInSubset(i, column_size.value()); + model.ReserveNumElementsInSubset(column_size.value(), subset); for (const ColumnEntryIndex _ : ColumnEntryRange(column_size)) { + // Correct the 1-indexing. const ElementIndex element(reader.ParseNextInteger() - 1); - model.AddElementToSubset(element.value(), i); + model.AddElementToSubset(element.value(), subset); } } file->Close(file::Defaults()).IgnoreError(); @@ -135,4 +147,197 @@ SetCoverModel ReadRailSetCoverProblem(absl::string_view filename) { return model; } +SetCoverModel ReadFimiDat(absl::string_view filename) { + SetCoverModel model; + for (const std::string& line : FileLines(filename)) { + SubsetIndex subset(0); + std::vector elements = absl::StrSplit(line, ' '); + if (elements.back().empty() || elements.back()[0] == '\0') { + elements.pop_back(); + } + model.AddEmptySubset(1); + for (const std::string& number : elements) { + BaseInt element; + CHECK(absl::SimpleAtoi(number, &element)); + CHECK_GT(element, 0); + // Correct the 1-indexing. + model.AddElementToLastSubset(ElementIndex(element - 1)); + } + ++subset; + } + model.CreateSparseRowView(); + return model; +} + +SetCoverModel ReadSetCoverProto(absl::string_view filename, bool binary) { + SetCoverModel model; + SetCoverProto message; + if (binary) { + CHECK_OK(file::GetBinaryProto(filename, &message, file::Defaults())); + } else { + CHECK_OK(file::GetTextProto(filename, &message, file::Defaults())); + } + model.ImportModelFromProto(message); + return model; +} + +namespace { +// A class to write a line of text to a file. +// The line is written in chunks of at most max_cols characters. +class LineWriter { + public: + LineWriter(File* file, int max_cols) + : num_cols_(0), max_cols_(max_cols), line_(), file_(file) {} + ~LineWriter() { Close(); } + void Write(BaseInt value) { + const std::string text = absl::StrCat(value, " "); + const int text_size = text.size(); + if (num_cols_ + text_size > max_cols_) { + CHECK_OK(file::WriteString(file_, absl::StrCat(line_, "\n"), + file::Defaults())); + line_.clear(); + } else { + absl::StrAppend(&line_, text); + num_cols_ += text_size; + } + } + void Close() { CHECK_OK(file::WriteString(file_, line_, file::Defaults())); } + + private: + int num_cols_; + int max_cols_; + std::string line_; + File* file_; +}; +} // namespace + +void WriteOrlibScp(const SetCoverModel& model, absl::string_view filename) { + const int kMaxCols = 80; + File* file(file::OpenOrDie(filename, "w", file::Defaults())); + CHECK_OK(file::WriteString( + file, absl::StrCat(model.num_elements(), " ", model.num_subsets(), "\n"), + file::Defaults())); + LineWriter cost_writer(file, kMaxCols); + for (const SubsetIndex subset : model.SubsetRange()) { + cost_writer.Write(model.subset_costs()[subset]); + } + for (const ElementIndex element : model.ElementRange()) { + CHECK_OK(file::WriteString(file, + absl::StrCat(model.rows()[element].size(), "\n"), + file::Defaults())); + LineWriter row_writer(file, kMaxCols); + for (const SubsetIndex subset : model.rows()[element]) { + row_writer.Write(subset.value() + 1); + } + } + file->Close(file::Defaults()).IgnoreError(); +} + +// Beware the fact that elements written are converted to 1-indexed. +void WriteOrlibRail(const SetCoverModel& model, absl::string_view filename) { + const int kMaxCols = 80; + File* file(file::OpenOrDie(filename, "w", file::Defaults())); + CHECK_OK(file::WriteString( + file, absl::StrCat(model.num_elements(), " ", model.num_subsets(), "\n"), + file::Defaults())); + for (const SubsetIndex subset : model.SubsetRange()) { + CHECK_OK( + file::WriteString(file, + absl::StrCat(model.subset_costs()[subset], " ", + model.columns()[subset].size(), "\n"), + file::Defaults())); + LineWriter writer(file, kMaxCols); + for (const ElementIndex element : model.columns()[subset]) { + writer.Write(element.value() + 1); + } + } + file->Close(file::Defaults()).IgnoreError(); +} + +void WriteSetCoverProto(const SetCoverModel& model, absl::string_view filename, + bool binary) { + const SetCoverProto message = model.ExportModelAsProto(); + if (binary) { + CHECK_OK(file::SetBinaryProto(filename, message, file::Defaults())); + } else { + CHECK_OK(file::SetTextProto(filename, message, file::Defaults())); + } +} + +SubsetBoolVector ReadSetCoverSolutionText(absl::string_view filename) { + SubsetBoolVector solution; + File* file(file::OpenOrDie(filename, "r", file::Defaults())); + SetCoverReader reader(file); + const BaseInt num_cols(reader.ParseNextInteger()); + solution.resize(num_cols, false); + const BaseInt cardinality(reader.ParseNextInteger()); + for (int i = 0; i < cardinality; ++i) { + // NOTE(user): The solution is 0-indexed. + const SubsetIndex subset(reader.ParseNextInteger()); + solution[subset] = true; + } + file->Close(file::Defaults()).IgnoreError(); + return solution; +} + +SubsetBoolVector ReadSetCoverSolutionProto(absl::string_view filename, + bool binary) { + SubsetBoolVector solution; + SetCoverSolutionResponse message; + if (binary) { + CHECK_OK(file::GetBinaryProto(filename, &message, file::Defaults())); + } else { + CHECK_OK(file::GetTextProto(filename, &message, file::Defaults())); + } + solution.resize(message.num_subsets(), false); + // NOTE(user): The solution is 0-indexed. + for (const BaseInt subset : message.subset()) { + solution[SubsetIndex(subset)] = true; + } + return solution; +} + +void WriteSetCoverSolutionText(const SetCoverModel& model, + const SubsetBoolVector& solution, + absl::string_view filename) { + File* file(file::OpenOrDie(filename, "w", file::Defaults())); + BaseInt cardinality(0); + Cost cost(0); + for (SubsetIndex subset(0); subset.value() < solution.size(); ++subset) { + if (solution[subset]) { + ++cardinality; + cost += model.subset_costs()[subset]; + } + } + CHECK_OK(file::WriteString( + file, absl::StrCat(solution.size(), " ", cardinality, " ", cost, "\n"), + file::Defaults())); + const int kMaxCols = 80; + LineWriter writer(file, kMaxCols); + for (BaseInt subset(0); subset < solution.size(); ++subset) { + if (solution[SubsetIndex(subset)]) { + writer.Write(subset); + } + } +} + +void WriteSetCoverSolutionProto(const SetCoverModel& model, + const SubsetBoolVector& solution, + absl::string_view filename, bool binary) { + SetCoverSolutionResponse message; + message.set_num_subsets(solution.size()); + Cost cost(0); + for (SubsetIndex subset(0); subset.value() < solution.size(); ++subset) { + if (solution[subset]) { + message.add_subset(subset.value()); + cost += model.subset_costs()[subset]; + } + } + message.set_cost(cost); + if (binary) { + CHECK_OK(file::SetBinaryProto(filename, message, file::Defaults())); + } else { + CHECK_OK(file::SetTextProto(filename, message, file::Defaults())); + } +} } // namespace operations_research diff --git a/ortools/algorithms/set_cover_reader.h b/ortools/algorithms/set_cover_reader.h index 6b56c0ac64..e98a73b73b 100644 --- a/ortools/algorithms/set_cover_reader.h +++ b/ortools/algorithms/set_cover_reader.h @@ -35,7 +35,9 @@ namespace operations_research { // for each column j, (j=1,...,n): the cost of the column c(j) // for each row i (i=1,...,m): the number of columns which cover // row i followed by a list of the columns which cover row i -SetCoverModel ReadBeasleySetCoverProblem(absl::string_view filename); +// The columns and rows are 1-indexed with this file format. +// The translation to 0-indexing is done at read time. +SetCoverModel ReadOrlibScp(absl::string_view filename); // Reads a rail set cover problem and returns a SetCoverModel. // The format of these test problems is: @@ -43,7 +45,64 @@ SetCoverModel ReadBeasleySetCoverProblem(absl::string_view filename); // for each column j (j=1,...,n): the cost of the column, the // number of rows that it covers followed by a list of the rows // that it covers. -SetCoverModel ReadRailSetCoverProblem(absl::string_view filename); +// The columns and rows are 1-indexed with this file format. +// The translation to 0-indexing is done at read time. +SetCoverModel ReadOrlibRail(absl::string_view filename); + +// Reads a file in the FIMI / .dat file format. FIMI stands for "Frequent +// Itemset Mining Implementations". +// The file is given column-by-column, with each column containing a space- +// separated list of elements terminating with a newline. The elements are +// 0-indexed. +// The cost of each subset is 1. +SetCoverModel ReadFimiDat(absl::string_view filename); + +// Reads a set cover problem from a SetCoverProto. +// The proto is either read from a binary (if binary is true) or a text file. +SetCoverModel ReadSetCoverProto(absl::string_view filename, bool binary); + +// Writers for the Beasley and Rail formats. +// The translation of indices from 0 to 1-indexing is done at write time. +void WriteOrlibScp(const SetCoverModel& model, absl::string_view filename); +void WriteOrlibRail(const SetCoverModel& model, absl::string_view filename); + +// Writes a set cover problem to a SetCoverProto. +// The proto is either written to a binary (if binary is true) or a text file. +// The model is modified (its columns are sorted) in-place when the proto is +// generated. +void WriteSetCoverProto(const SetCoverModel& model, absl::string_view filename, + bool binary); + +// Reads a set cover solution from a text file. +// The format of the file is: +// number of columns (n) +// number of selected columns (k) +// for each i (j=1,...,k): 1 if column[i] is selected, 0 otherwise. +// The solution is 0-indexed. +SubsetBoolVector ReadSetCoverSolutionText(absl::string_view filename); + +// Reads a set cover solution from a SetCoverSolutionResponse proto. +// The proto is either read from a binary (if binary is true) or a text file. +// The solution is 0-indexed. +SubsetBoolVector ReadSetCoverSolutionProto(absl::string_view filename, + bool binary); + +// Writes a set cover solution to a text file. +// The format of the file is: +// number of columns (n) +// number of selected columns (k) +// for each i (j=1,...,k): 1 if column[i] is selected, 0 otherwise. +// The solution is 0-indexed. +void WriteSetCoverSolutionText(const SetCoverModel& model, + const SubsetBoolVector& solution, + absl::string_view filename); + +// Writes a set cover solution to a SetCoverSolutionResponse proto. +// The proto is either written to a binary (if binary is true) or a text file. +// The solution is 0-indexed. +void WriteSetCoverSolutionProto(const SetCoverModel& model, + const SubsetBoolVector& solution, + absl::string_view filename, bool binary); } // namespace operations_research diff --git a/ortools/glop/preprocessor.h b/ortools/glop/preprocessor.h index 37723dad6c..dc92063e8b 100644 --- a/ortools/glop/preprocessor.h +++ b/ortools/glop/preprocessor.h @@ -378,7 +378,7 @@ struct MatrixEntry { class SingletonUndo { public: // The type of a given operation. - typedef enum { + typedef enum : uint8_t { ZERO_COST_SINGLETON_COLUMN, SINGLETON_ROW, SINGLETON_COLUMN_IN_EQUALITY, @@ -662,7 +662,7 @@ class DoubletonFreeColumnPreprocessor final : public Preprocessor { void RecoverSolution(ProblemSolution* solution) const final; private: - enum RowChoice { + enum RowChoice : int { DELETED = 0, MODIFIED = 1, // This is just a constant for the number of rows in a doubleton column. @@ -828,7 +828,7 @@ class DoubletonEqualityRowPreprocessor final : public Preprocessor { void RecoverSolution(ProblemSolution* solution) const final; private: - enum ColChoice { + enum ColChoice : int { DELETED = 0, MODIFIED = 1, // For `for()` loops iterating over the ColChoice values and/or arrays. diff --git a/ortools/linear_solver/model_exporter.cc b/ortools/linear_solver/model_exporter.cc index cdccf8b241..b8ecab58ab 100644 --- a/ortools/linear_solver/model_exporter.cc +++ b/ortools/linear_solver/model_exporter.cc @@ -30,6 +30,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "ortools/base/helpers.h" #include "ortools/base/logging.h" #include "ortools/base/options.h" @@ -179,7 +180,7 @@ class MPModelProtoExporter { // The sparse matrix must be passed as a vector of columns ('transpose'). void AppendMpsColumns( bool integrality, - const std::vector>>& transpose, + absl::Span>> transpose, std::string* output); // Appends a line describing the bound of a variablenew-line if two columns @@ -722,7 +723,7 @@ void MPModelProtoExporter::AppendNewLineIfTwoColumns(std::string* output) { void MPModelProtoExporter::AppendMpsColumns( bool integrality, - const std::vector>>& transpose, + absl::Span>> transpose, std::string* output) { current_mps_column_ = 0; for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) { diff --git a/ortools/util/filelineiter.h b/ortools/util/filelineiter.h index 79a2dc5337..ba6111215c 100644 --- a/ortools/util/filelineiter.h +++ b/ortools/util/filelineiter.h @@ -35,7 +35,7 @@ // Implements the minimum interface for a range-based for loop iterator. class FileLineIterator { public: - enum { + enum : int { DEFAULT = 0x0000, REMOVE_LINEFEED = DEFAULT, KEEP_LINEFEED = 0x0001, // Terminating \n in result.