14#ifndef OR_TOOLS_LP_DATA_MPS_READER_TEMPLATE_H_
15#define OR_TOOLS_LP_DATA_MPS_READER_TEMPLATE_H_
282#include "absl/container/flat_hash_map.h"
283#include "absl/container/flat_hash_set.h"
284#include "absl/container/inlined_vector.h"
285#include "absl/log/check.h"
286#include "absl/status/status.h"
287#include "absl/status/statusor.h"
288#include "absl/strings/ascii.h"
289#include "absl/strings/match.h"
290#include "absl/strings/numbers.h"
291#include "absl/strings/str_cat.h"
292#include "absl/strings/str_split.h"
293#include "absl/strings/string_view.h"
302enum class MPSReaderFormat { kAutoDetect,
kFree, kFixed };
308static constexpr int kNumMpsFields = 6;
311enum class MPSSectionId {
336 static absl::StatusOr<MPSLineInfo> Create(int64_t line_num,
bool free_form,
337 absl::string_view
line);
340 absl::string_view GetLine()
const {
return line_; }
344 bool IsNewSection()
const {
return line_[0] !=
'\0' && line_[0] !=
' '; }
349 int GetFieldsSize()
const {
return fields_.size(); }
353 absl::string_view GetFirstWord()
const;
357 bool IsCommentOrBlank()
const;
366 absl::string_view GetField(
int index)
const {
return fields_[
index]; }
377 int GetFieldOffset()
const {
return free_form_ ? fields_.size() & 1 : 0; }
381 absl::Status InvalidArgumentError(absl::string_view error_message)
const;
385 absl::Status AppendLineToError(
const absl::Status&
status)
const;
388 MPSLineInfo(int64_t line_num,
bool free_form, absl::string_view
line)
389 : free_form_{free_form}, line_num_{line_num}, line_{
line} {}
392 absl::Status SplitLineIntoFields();
395 bool IsFixedFormat()
const;
398 const bool free_form_;
401 absl::InlinedVector<absl::string_view, internal::kNumMpsFields> fields_;
404 const int64_t line_num_;
409 const absl::string_view line_;
493template <
class DataWrapper>
506 absl::StatusOr<MPSReaderFormat> ParseFile(
507 absl::string_view file_name, DataWrapper* data,
508 MPSReaderFormat form = MPSReaderFormat::kAutoDetect);
515 absl::StatusOr<MPSReaderFormat> ParseString(
516 absl::string_view source, DataWrapper* data,
517 MPSReaderFormat form = MPSReaderFormat::kAutoDetect);
520 static constexpr double kInfinity = std::numeric_limits<double>::infinity();
526 void DisplaySummary();
529 absl::Status ProcessLine(absl::string_view
line, DataWrapper* data);
532 absl::Status ProcessObjectiveSenseSection(
533 const internal::MPSLineInfo& line_info, DataWrapper* data);
536 absl::Status ProcessRowsSection(
const internal::MPSLineInfo& line_info,
537 bool is_lazy, DataWrapper* data);
540 absl::Status ProcessColumnsSection(
const internal::MPSLineInfo& line_info,
544 absl::Status ProcessRhsSection(
const internal::MPSLineInfo& line_info,
548 absl::Status ProcessRangesSection(
const internal::MPSLineInfo& line_info,
552 absl::Status ProcessBoundsSection(
const internal::MPSLineInfo& line_info,
556 absl::Status ProcessIndicatorsSection(
const internal::MPSLineInfo& line_info,
561 absl::StatusOr<double> GetDoubleFromString(
562 absl::string_view str,
const internal::MPSLineInfo& line_info);
563 absl::StatusOr<bool> GetBoolFromString(
564 absl::string_view str,
const internal::MPSLineInfo& line_info);
568 enum class BoundTypeId {
581 enum class RowTypeId {
591 absl::Status StoreBound(
const internal::MPSLineInfo& line_info,
592 absl::string_view bound_type_mnemonic,
593 absl::string_view column_name,
594 absl::string_view bound_value, DataWrapper* data);
597 absl::Status StoreCoefficient(
const internal::MPSLineInfo& line_info,
598 IndexType
col, absl::string_view row_name,
599 absl::string_view row_value, DataWrapper* data);
602 absl::Status StoreRightHandSide(
const internal::MPSLineInfo& line_info,
603 absl::string_view row_name,
604 absl::string_view row_value,
608 absl::Status StoreRange(
const internal::MPSLineInfo& line_info,
609 absl::string_view row_name,
610 absl::string_view range_value, DataWrapper* data);
616 std::string objective_name_;
619 internal::MPSSectionId section_;
622 absl::flat_hash_map<std::string, internal::MPSSectionId>
623 section_name_to_id_map_;
626 absl::flat_hash_map<std::string, RowTypeId> row_name_to_id_map_;
629 absl::flat_hash_map<std::string, BoundTypeId> bound_name_to_id_map_;
632 absl::flat_hash_set<std::string> integer_type_names_set_;
639 std::vector<bool> is_binary_by_default_;
644 bool in_integer_section_;
649 IndexType num_unconstrained_rows_;
652template <
class DataWrapper>
654 const absl::string_view file_name, DataWrapper*
const data,
655 const MPSReaderFormat form) {
656 if (data ==
nullptr) {
657 return absl::InvalidArgumentError(
"NULL pointer passed as argument.");
660 if (form != MPSReaderFormat::kFree && form != MPSReaderFormat::kFixed) {
661 if (ParseFile(file_name, data, MPSReaderFormat::kFixed).ok()) {
662 return MPSReaderFormat::kFixed;
664 return ParseFile(file_name, data, MPSReaderFormat::kFree);
667 DCHECK(form == MPSReaderFormat::kFree || form == MPSReaderFormat::kFixed);
668 free_form_ = form == MPSReaderFormat::kFree;
673 for (
const absl::string_view
line :
682template <
class DataWrapper>
684 absl::string_view source, DataWrapper*
const data,
685 const MPSReaderFormat form) {
686 if (form != MPSReaderFormat::kFree && form != MPSReaderFormat::kFixed) {
687 if (ParseString(source, data, MPSReaderFormat::kFixed).ok()) {
688 return MPSReaderFormat::kFixed;
690 return ParseString(source, data, MPSReaderFormat::kFree);
693 DCHECK(form == MPSReaderFormat::kFree || form == MPSReaderFormat::kFixed);
694 free_form_ = form == MPSReaderFormat::kFree;
697 for (absl::string_view
line : absl::StrSplit(source,
'\n')) {
705template <
class DataWrapper>
710 internal::MPSLineInfo::Create(line_num_, free_form_,
line));
711 if (line_info.IsCommentOrBlank()) {
712 return absl::OkStatus();
716 if (line_info.IsNewSection()) {
717 if (
const auto it = section_name_to_id_map_.find(line_info.GetFirstWord());
718 it != section_name_to_id_map_.end()) {
719 section_ = it->second;
721 return line_info.InvalidArgumentError(
"Unknown section.");
723 if (section_ == internal::MPSSectionId::kName) {
731 if (line_info.GetFieldsSize() >= 2) {
732 data->SetName(line_info.GetField(1));
735 const std::vector<absl::string_view> free_fields = absl::StrSplit(
736 line_info.GetLine(), absl::ByAnyChar(
" \t"), absl::SkipEmpty());
737 const absl::string_view free_name =
738 free_fields.size() >= 2 ? free_fields[1] :
"";
739 const absl::string_view fixed_name =
740 line_info.GetFieldsSize() >= 3 ? line_info.GetField(2) :
"";
741 if (free_name != fixed_name) {
742 return line_info.InvalidArgumentError(
743 "Fixed form invalid: name differs between free and fixed "
746 data->SetName(fixed_name);
749 return absl::OkStatus();
752 case internal::MPSSectionId::kName:
753 return line_info.InvalidArgumentError(
"Second NAME field.");
754 case internal::MPSSectionId::kObjsense:
755 return ProcessObjectiveSenseSection(line_info, data);
756 case internal::MPSSectionId::kRows:
757 return ProcessRowsSection(line_info,
false, data);
758 case internal::MPSSectionId::kLazycons:
759 return ProcessRowsSection(line_info,
true, data);
760 case internal::MPSSectionId::kColumns:
761 return ProcessColumnsSection(line_info, data);
762 case internal::MPSSectionId::kRhs:
763 return ProcessRhsSection(line_info, data);
764 case internal::MPSSectionId::kRanges:
765 return ProcessRangesSection(line_info, data);
766 case internal::MPSSectionId::kBounds:
767 return ProcessBoundsSection(line_info, data);
768 case internal::MPSSectionId::kIndicators:
769 return ProcessIndicatorsSection(line_info, data);
770 case internal::MPSSectionId::kEndData:
773 return line_info.InvalidArgumentError(
"Unknown section.");
775 return absl::OkStatus();
778template <
class DataWrapper>
780 const internal::MPSLineInfo& line_info, DataWrapper* data) {
781 absl::string_view field = absl::StripAsciiWhitespace(line_info.GetLine());
782 if (field !=
"MIN" && field !=
"MAX") {
783 return line_info.InvalidArgumentError(
784 "Expected objective sense (MAX or MIN).");
786 data->SetObjectiveDirection(field ==
"MAX");
787 return absl::OkStatus();
790template <
class DataWrapper>
792 const internal::MPSLineInfo& line_info,
bool is_lazy, DataWrapper* data) {
793 if (line_info.GetFieldsSize() < 2) {
794 return line_info.InvalidArgumentError(
"Not enough fields in ROWS section.");
796 const absl::string_view row_type_name = line_info.GetField(0);
797 const absl::string_view row_name = line_info.GetField(1);
798 const auto it = row_name_to_id_map_.find(row_type_name);
799 if (it == row_name_to_id_map_.end()) {
800 return line_info.InvalidArgumentError(
"Unknown row type.");
802 RowTypeId row_type = it->second;
805 if (objective_name_.empty() && row_type == RowTypeId::kNone) {
806 row_type = RowTypeId::kObjective;
807 objective_name_ = std::string(row_name);
809 if (row_type == RowTypeId::kNone) {
810 ++num_unconstrained_rows_;
812 const IndexType
row = data->FindOrCreateConstraint(row_name);
813 if (is_lazy) data->SetIsLazy(
row);
818 case RowTypeId::kLessThan:
819 data->SetConstraintBounds(
row, -kInfinity,
820 data->ConstraintUpperBound(
row));
822 case RowTypeId::kGreaterThan:
823 data->SetConstraintBounds(
row, data->ConstraintLowerBound(
row),
826 case RowTypeId::kNone:
827 data->SetConstraintBounds(
row, -kInfinity, kInfinity);
829 case RowTypeId::kEquality:
834 return absl::OkStatus();
837template <
class DataWrapper>
839 const internal::MPSLineInfo& line_info, DataWrapper* data) {
841 if (absl::StrContains(line_info.GetLine(),
"'MARKER'")) {
842 if (absl::StrContains(line_info.GetLine(),
"'INTORG'")) {
843 VLOG(2) <<
"Entering integer marker.\n" << line_info.GetLine();
844 if (in_integer_section_) {
845 return line_info.InvalidArgumentError(
846 "Found INTORG inside the integer section.");
848 in_integer_section_ =
true;
849 }
else if (absl::StrContains(line_info.GetLine(),
"'INTEND'")) {
850 VLOG(2) <<
"Leaving integer marker.\n" << line_info.GetLine();
851 if (!in_integer_section_) {
852 return line_info.InvalidArgumentError(
853 "Found INTEND without corresponding INTORG.");
855 in_integer_section_ =
false;
857 return absl::OkStatus();
859 const int start_index = free_form_ ? 0 : 1;
860 if (line_info.GetFieldsSize() < start_index + 3) {
861 return line_info.InvalidArgumentError(
862 "Not enough fields in COLUMNS section.");
864 const absl::string_view column_name = line_info.GetField(start_index + 0);
865 const absl::string_view row1_name = line_info.GetField(start_index + 1);
866 const absl::string_view row1_value = line_info.GetField(start_index + 2);
867 const IndexType
col = data->FindOrCreateVariable(column_name);
868 is_binary_by_default_.resize(
col + 1,
false);
869 if (in_integer_section_) {
870 data->SetVariableTypeToInteger(
col);
872 data->SetVariableBounds(
col, 0.0, 1.0);
873 is_binary_by_default_[
col] =
true;
875 data->SetVariableBounds(
col, 0.0, kInfinity);
878 StoreCoefficient(line_info,
col, row1_name, row1_value, data));
879 if (line_info.GetFieldsSize() == start_index + 4) {
880 return line_info.InvalidArgumentError(
"Unexpected number of fields.");
882 if (line_info.GetFieldsSize() - start_index > 4) {
883 const absl::string_view row2_name = line_info.GetField(start_index + 3);
884 const absl::string_view row2_value = line_info.GetField(start_index + 4);
886 StoreCoefficient(line_info,
col, row2_name, row2_value, data));
888 return absl::OkStatus();
891template <
class DataWrapper>
893 const internal::MPSLineInfo& line_info, DataWrapper* data) {
894 const int start_index = free_form_ ? 0 : 2;
895 const int offset = start_index + line_info.GetFieldOffset();
896 if (line_info.GetFieldsSize() < offset + 2) {
897 return line_info.InvalidArgumentError(
"Not enough fields in RHS section.");
900 const absl::string_view row1_name = line_info.GetField(offset);
901 const absl::string_view row1_value = line_info.GetField(offset + 1);
902 RETURN_IF_ERROR(StoreRightHandSide(line_info, row1_name, row1_value, data));
903 if (line_info.GetFieldsSize() - start_index >= 4) {
904 const absl::string_view row2_name = line_info.GetField(offset + 2);
905 const absl::string_view row2_value = line_info.GetField(offset + 3);
906 RETURN_IF_ERROR(StoreRightHandSide(line_info, row2_name, row2_value, data));
908 return absl::OkStatus();
911template <
class DataWrapper>
913 const internal::MPSLineInfo& line_info, DataWrapper* data) {
914 const int start_index = free_form_ ? 0 : 2;
915 const int offset = start_index + line_info.GetFieldOffset();
916 if (line_info.GetFieldsSize() < offset + 2) {
917 return line_info.InvalidArgumentError(
"Not enough fields in RHS section.");
920 const absl::string_view row1_name = line_info.GetField(offset);
921 const absl::string_view row1_value = line_info.GetField(offset + 1);
923 if (line_info.GetFieldsSize() - start_index >= 4) {
924 const absl::string_view row2_name = line_info.GetField(offset + 2);
925 const absl::string_view row2_value = line_info.GetField(offset + 3);
928 return absl::OkStatus();
931template <
class DataWrapper>
933 const internal::MPSLineInfo& line_info, DataWrapper* data) {
934 if (line_info.GetFieldsSize() < 3) {
935 return line_info.InvalidArgumentError(
936 "Not enough fields in BOUNDS section.");
938 const absl::string_view bound_type_mnemonic = line_info.GetField(0);
939 const absl::string_view column_name = line_info.GetField(2);
940 const absl::string_view bound_value =
941 (line_info.GetFieldsSize() >= 4) ? line_info.GetField(3) :
"";
942 return StoreBound(line_info, bound_type_mnemonic, column_name, bound_value,
946template <
class DataWrapper>
948 const internal::MPSLineInfo& line_info, DataWrapper* data) {
952 if (line_info.GetFieldsSize() < 4) {
953 return line_info.InvalidArgumentError(
954 "Not enough fields in INDICATORS section.");
957 const absl::string_view type = line_info.GetField(0);
959 return line_info.InvalidArgumentError(
960 "Indicator constraints must start with \"IF\".");
962 const absl::string_view row_name = line_info.GetField(1);
963 const absl::string_view column_name = line_info.GetField(2);
964 const absl::string_view column_value = line_info.GetField(3);
969 const IndexType
col = data->FindOrCreateVariable(column_name);
971 data->SetVariableTypeToInteger(
col);
972 data->SetVariableBounds(
col, std::max(0.0, data->VariableLowerBound(
col)),
973 std::min(1.0, data->VariableUpperBound(
col)));
976 data->CreateIndicatorConstraint(row_name,
col,
value)));
978 return absl::OkStatus();
981template <
class DataWrapper>
983 const internal::MPSLineInfo& line_info, IndexType
col,
984 absl::string_view row_name, absl::string_view row_value,
986 if (row_name.empty() || row_name ==
"$") {
987 return absl::OkStatus();
992 if (
value == kInfinity ||
value == -kInfinity) {
993 return line_info.InvalidArgumentError(
994 "Constraint coefficients cannot be infinity.");
996 if (
value == 0.0)
return absl::OkStatus();
997 if (row_name == objective_name_) {
998 data->SetObjectiveCoefficient(
col,
value);
1000 const IndexType
row = data->FindOrCreateConstraint(row_name);
1003 return absl::OkStatus();
1006template <
class DataWrapper>
1008 const internal::MPSLineInfo& line_info, absl::string_view row_name,
1009 absl::string_view row_value, DataWrapper* data) {
1010 if (row_name.empty())
return absl::OkStatus();
1012 if (row_name != objective_name_) {
1013 const IndexType
row = data->FindOrCreateConstraint(row_name);
1031 data->SetObjectiveOffset(-
value);
1033 return absl::OkStatus();
1036template <
class DataWrapper>
1038 const internal::MPSLineInfo& line_info, absl::string_view row_name,
1039 absl::string_view range_value, DataWrapper* data) {
1040 if (row_name.empty())
return absl::OkStatus();
1042 const IndexType
row = data->FindOrCreateConstraint(row_name);
1062 return absl::OkStatus();
1065template <
class DataWrapper>
1067 const internal::MPSLineInfo& line_info,
1068 absl::string_view bound_type_mnemonic, absl::string_view column_name,
1069 absl::string_view bound_value, DataWrapper* data) {
1070 const auto it = bound_name_to_id_map_.find(bound_type_mnemonic);
1071 if (it == bound_name_to_id_map_.end()) {
1072 return line_info.InvalidArgumentError(
"Unknown bound type.");
1074 const BoundTypeId bound_type_id = it->second;
1075 const IndexType
col = data->FindOrCreateVariable(column_name);
1076 if (integer_type_names_set_.count(bound_type_mnemonic) != 0) {
1077 data->SetVariableTypeToInteger(
col);
1079 if (is_binary_by_default_.size() <=
col) {
1081 is_binary_by_default_.resize(
col + 1,
false);
1088 if (is_binary_by_default_[
col]) {
1092 switch (bound_type_id) {
1093 case BoundTypeId::kLowerBound: {
1095 GetDoubleFromString(bound_value, line_info));
1098 if (bound_type_mnemonic ==
"LI" &&
lower_bound == 0.0) {
1103 case BoundTypeId::kUpperBound: {
1105 GetDoubleFromString(bound_value, line_info));
1108 case BoundTypeId::kSemiContinuous: {
1110 GetDoubleFromString(bound_value, line_info));
1111 data->SetVariableTypeToSemiContinuous(
col);
1114 case BoundTypeId::kFixedVariable: {
1116 GetDoubleFromString(bound_value, line_info));
1120 case BoundTypeId::kFreeVariable:
1124 case BoundTypeId::kInfiniteLowerBound:
1127 case BoundTypeId::kInfiniteUpperBound:
1130 case BoundTypeId::kBinary:
1134 case BoundTypeId::kUnknownBoundType:
1136 return line_info.InvalidArgumentError(
"Unknown bound type.");
1138 is_binary_by_default_[
col] =
false;
1140 return absl::OkStatus();
1143template <
class DataWrapper>
1146 section_(
internal::MPSSectionId::kUnknownSection),
1147 section_name_to_id_map_(),
1148 row_name_to_id_map_(),
1149 bound_name_to_id_map_(),
1150 integer_type_names_set_(),
1152 in_integer_section_(false),
1153 num_unconstrained_rows_(0) {
1154 section_name_to_id_map_[
"NAME"] = internal::MPSSectionId::kName;
1155 section_name_to_id_map_[
"OBJSENSE"] = internal::MPSSectionId::kObjsense;
1156 section_name_to_id_map_[
"ROWS"] = internal::MPSSectionId::kRows;
1157 section_name_to_id_map_[
"LAZYCONS"] = internal::MPSSectionId::kLazycons;
1158 section_name_to_id_map_[
"COLUMNS"] = internal::MPSSectionId::kColumns;
1159 section_name_to_id_map_[
"RHS"] = internal::MPSSectionId::kRhs;
1160 section_name_to_id_map_[
"RANGES"] = internal::MPSSectionId::kRanges;
1161 section_name_to_id_map_[
"BOUNDS"] = internal::MPSSectionId::kBounds;
1162 section_name_to_id_map_[
"INDICATORS"] = internal::MPSSectionId::kIndicators;
1163 section_name_to_id_map_[
"ENDATA"] = internal::MPSSectionId::kEndData;
1164 row_name_to_id_map_[
"E"] = RowTypeId::kEquality;
1165 row_name_to_id_map_[
"L"] = RowTypeId::kLessThan;
1166 row_name_to_id_map_[
"G"] = RowTypeId::kGreaterThan;
1167 row_name_to_id_map_[
"N"] = RowTypeId::kNone;
1168 bound_name_to_id_map_[
"LO"] = BoundTypeId::kLowerBound;
1169 bound_name_to_id_map_[
"UP"] = BoundTypeId::kUpperBound;
1170 bound_name_to_id_map_[
"FX"] = BoundTypeId::kFixedVariable;
1171 bound_name_to_id_map_[
"FR"] = BoundTypeId::kFreeVariable;
1172 bound_name_to_id_map_[
"MI"] = BoundTypeId::kInfiniteLowerBound;
1173 bound_name_to_id_map_[
"PL"] = BoundTypeId::kInfiniteUpperBound;
1174 bound_name_to_id_map_[
"BV"] = BoundTypeId::kBinary;
1175 bound_name_to_id_map_[
"LI"] = BoundTypeId::kLowerBound;
1176 bound_name_to_id_map_[
"UI"] = BoundTypeId::kUpperBound;
1177 bound_name_to_id_map_[
"SC"] = BoundTypeId::kSemiContinuous;
1179 integer_type_names_set_.insert(
"BV");
1180 integer_type_names_set_.insert(
"LI");
1181 integer_type_names_set_.insert(
"UI");
1184template <
class DataWrapper>
1187 in_integer_section_ =
false;
1188 num_unconstrained_rows_ = 0;
1189 objective_name_.clear();
1192template <
class DataWrapper>
1194 if (num_unconstrained_rows_ > 0) {
1195 VLOG(1) <<
"There are " << num_unconstrained_rows_ + 1
1196 <<
" unconstrained rows. The first of them (" << objective_name_
1197 <<
") was used as the objective.";
1201template <
class DataWrapper>
1203 absl::string_view str,
const internal::MPSLineInfo& line_info) {
1205 if (!absl::SimpleAtod(str, &result)) {
1206 return line_info.InvalidArgumentError(
1207 absl::StrCat(
"Failed to convert \"", str,
"\" to double."));
1209 if (std::isnan(result)) {
1210 return line_info.InvalidArgumentError(
"Found NaN value.");
1215template <
class DataWrapper>
1217 absl::string_view str,
const internal::MPSLineInfo& line_info) {
1219 if (!absl::SimpleAtoi(str, &result) || result < 0 || result > 1) {
1220 return line_info.InvalidArgumentError(
1221 absl::StrCat(
"Failed to convert \"", str,
"\" to bool."));
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
absl::StatusOr< MPSReaderFormat > ParseFile(absl::string_view file_name, DataWrapper *data, MPSReaderFormat form=MPSReaderFormat::kAutoDetect)
typename DataWrapper::IndexType IndexType
Type for row and column indices, as provided by DataWrapper.
absl::StatusOr< MPSReaderFormat > ParseString(absl::string_view source, DataWrapper *data, MPSReaderFormat form=MPSReaderFormat::kAutoDetect)
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().
@ kFree
The variable/constraint is free (it has no finite bounds).
In SWIG mode, we don't want anything besides these top-level includes.
static constexpr double kInfinity
static const int kNone
Special value for task indices meaning 'no such task'.
const std::optional< Range > & range