16 #include "absl/status/status.h"
17 #include "absl/status/statusor.h"
18 #include "absl/strings/match.h"
19 #include "absl/strings/str_split.h"
33 absl::Status
ParseFile(
const std::string& file_name, Data* data,
38 static const int kNumFields;
41 static const int kFieldStartPos[];
44 static const int kFieldLength[];
47 static const int kSpacePos[];
53 void DisplaySummary();
56 absl::Status SplitLineIntoFields();
62 std::string GetFirstWord()
const;
66 bool IsCommentOrBlank()
const;
69 const std::string& GetField(
int offset,
int index)
const {
70 return fields_[offset +
index];
78 int GetFieldOffset()
const {
return free_form_ ? fields_.size() & 1 : 0; }
81 template <
class DataWrapper>
82 absl::Status ProcessLine(
const std::string& line,
DataWrapper* data);
85 template <
class DataWrapper>
86 absl::Status ProcessObjectiveSenseSection(
DataWrapper* data);
89 template <
class DataWrapper>
90 absl::Status ProcessRowsSection(
bool is_lazy,
DataWrapper* data);
93 template <
class DataWrapper>
94 absl::Status ProcessColumnsSection(
DataWrapper* data);
97 template <
class DataWrapper>
101 template <
class DataWrapper>
102 absl::Status ProcessRangesSection(
DataWrapper* data);
105 template <
class DataWrapper>
106 absl::Status ProcessBoundsSection(
DataWrapper* data);
109 template <
class DataWrapper>
110 absl::Status ProcessIndicatorsSection(
DataWrapper* data);
113 absl::Status ProcessSosSection();
117 absl::StatusOr<double> GetDoubleFromString(
const std::string& str);
118 absl::StatusOr<bool> GetBoolFromString(
const std::string& str);
144 template <
class DataWrapper>
145 absl::Status StoreBound(
const std::string& bound_type_mnemonic,
146 const std::string& column_name,
147 const std::string& bound_value,
DataWrapper* data);
150 template <
class DataWrapper>
151 absl::Status StoreCoefficient(
int col,
const std::string& row_name,
152 const std::string& row_value,
156 template <
class DataWrapper>
157 absl::Status StoreRightHandSide(
const std::string& row_name,
158 const std::string& row_value,
162 template <
class DataWrapper>
163 absl::Status StoreRange(
const std::string& row_name,
164 const std::string& range_value,
DataWrapper* data);
168 absl::Status InvalidArgumentError(
const std::string& error_message);
172 absl::Status AppendLineToError(
const absl::Status& status);
178 std::vector<std::string> fields_;
181 std::string objective_name_;
204 absl::flat_hash_map<std::string, SectionId> section_name_to_id_map_;
207 absl::flat_hash_map<std::string, RowTypeId> row_name_to_id_map_;
210 absl::flat_hash_map<std::string, BoundTypeId> bound_name_to_id_map_;
213 absl::flat_hash_set<std::string> integer_type_names_set_;
223 std::vector<bool> is_binary_by_default_;
228 bool in_integer_section_;
233 int num_unconstrained_rows_;
240 template <
class Data>
249 data_->SetDcheckBounds(
false);
256 data_->SetMaximizationProblem(maximize);
260 return data_->FindOrCreateConstraint(
name).value();
263 data_->SetConstraintBounds(RowIndex(
index), lower_bound, upper_bound);
267 data_->SetCoefficient(RowIndex(row_index), ColIndex(col_index),
272 <<
"LAZYCONS section detected. It will be handled as an extension of "
276 return data_->constraint_lower_bounds()[RowIndex(row_index)];
279 return data_->constraint_upper_bounds()[RowIndex(row_index)];
283 return data_->FindOrCreateVariable(
name).value();
286 data_->SetVariableType(ColIndex(
index),
290 data_->SetVariableBounds(ColIndex(
index), lower_bound, upper_bound);
296 return data_->IsVariableInteger(ColIndex(
index));
299 return data_->variable_lower_bounds()[ColIndex(
index)];
302 return data_->variable_upper_bounds()[ColIndex(
index)];
307 return absl::UnimplementedError(
308 "LinearProgram does not support indicator constraints.");
329 const auto it = constraint_indices_by_name_.find(
name);
330 if (it != constraint_indices_by_name_.end())
return it->second;
332 const int index = data_->constraint_size();
333 MPConstraintProto*
const constraint = data_->add_constraint();
334 constraint->set_lower_bound(0.0);
335 constraint->set_upper_bound(0.0);
336 constraint->set_name(
name);
337 constraint_indices_by_name_[
name] =
index;
341 data_->mutable_constraint(
index)->set_lower_bound(lower_bound);
342 data_->mutable_constraint(
index)->set_upper_bound(upper_bound);
350 MPConstraintProto*
const constraint = data_->mutable_constraint(row_index);
351 constraint->add_var_index(col_index);
355 data_->mutable_constraint(row_index)->set_is_lazy(
true);
358 return data_->constraint(row_index).lower_bound();
361 return data_->constraint(row_index).upper_bound();
365 const auto it = variable_indices_by_name_.find(
name);
366 if (it != variable_indices_by_name_.end())
return it->second;
368 const int index = data_->variable_size();
369 MPVariableProto*
const variable = data_->add_variable();
370 variable->set_lower_bound(0.0);
371 variable->set_name(
name);
376 data_->mutable_variable(
index)->set_is_integer(
true);
379 data_->mutable_variable(
index)->set_lower_bound(lower_bound);
380 data_->mutable_variable(
index)->set_upper_bound(upper_bound);
386 return data_->variable(
index).is_integer();
389 return data_->variable(
index).lower_bound();
392 return data_->variable(
index).upper_bound();
397 const auto it = constraint_indices_by_name_.find(cst_name);
398 if (it == constraint_indices_by_name_.end()) {
399 return absl::InvalidArgumentError(
400 absl::StrCat(
"Constraint \"", cst_name,
"\" doesn't exist."));
402 const int cst_index = it->second;
404 MPGeneralConstraintProto*
const constraint =
405 data_->add_general_constraint();
406 constraint->set_name(
407 absl::StrCat(
"ind_", data_->constraint(cst_index).name()));
408 MPIndicatorConstraint*
const indicator =
409 constraint->mutable_indicator_constraint();
410 *indicator->mutable_constraint() = data_->constraint(cst_index);
411 indicator->set_var_index(var_index);
412 indicator->set_var_value(var_value);
413 constraints_to_delete_.insert(cst_index);
415 return absl::OkStatus();
420 constraints_to_delete_);
426 absl::flat_hash_map<std::string, int> variable_indices_by_name_;
427 absl::flat_hash_map<std::string, int> constraint_indices_by_name_;
428 absl::node_hash_set<int> constraints_to_delete_;
431 template <
class Data>
434 if (data ==
nullptr) {
435 return absl::InvalidArgumentError(
"NULL pointer passed as argument.");
440 return absl::OkStatus();
449 data_wrapper.SetUp();
450 for (
const std::string& line :
454 data_wrapper.CleanUp();
456 return absl::OkStatus();
459 template <
class DataWrapper>
460 absl::Status MPSReaderImpl::ProcessLine(
const std::string& line,
464 if (IsCommentOrBlank()) {
465 return absl::OkStatus();
467 if (!free_form_ && line_.find(
'\t') != std::string::npos) {
468 return InvalidArgumentError(
"File contains tabs.");
471 if (line[0] !=
'\0' && line[0] !=
' ') {
472 section = GetFirstWord();
475 if (section_ == UNKNOWN_SECTION) {
476 return InvalidArgumentError(
"Unknown section.");
478 if (section_ == COMMENT) {
479 return absl::OkStatus();
481 if (section_ == OBJSENSE) {
482 return absl::OkStatus();
484 if (section_ == NAME) {
493 if (fields_.size() >= 2) {
494 data->SetName(fields_[1]);
497 const std::vector<std::string> free_fields =
498 absl::StrSplit(line_, absl::ByAnyChar(
" \t"), absl::SkipEmpty());
499 const std::string free_name =
500 free_fields.size() >= 2 ? free_fields[1] :
"";
501 const std::string fixed_name = fields_.size() >= 3 ? fields_[2] :
"";
502 if (free_name != fixed_name) {
503 return InvalidArgumentError(
504 "Fixed form invalid: name differs between free and fixed "
507 data->SetName(fixed_name);
510 return absl::OkStatus();
515 return InvalidArgumentError(
"Second NAME field.");
517 return ProcessObjectiveSenseSection(data);
519 return ProcessRowsSection(
false, data);
521 return ProcessRowsSection(
true, data);
523 return ProcessColumnsSection(data);
525 return ProcessRhsSection(data);
527 return ProcessRangesSection(data);
529 return ProcessBoundsSection(data);
531 return ProcessIndicatorsSection(data);
533 return ProcessSosSection();
537 return InvalidArgumentError(
"Unknown section.");
539 return absl::OkStatus();
542 template <
class DataWrapper>
543 absl::Status MPSReaderImpl::ProcessObjectiveSenseSection(DataWrapper* data) {
544 if (fields_.size() != 1 && fields_[0] !=
"MIN" && fields_[0] !=
"MAX") {
545 return InvalidArgumentError(
"Expected objective sense (MAX or MIN).");
547 data->SetObjectiveDirection(fields_[0] ==
"MAX");
548 return absl::OkStatus();
551 template <
class DataWrapper>
552 absl::Status MPSReaderImpl::ProcessRowsSection(
bool is_lazy,
554 if (fields_.size() < 2) {
555 return InvalidArgumentError(
"Not enough fields in ROWS section.");
557 const std::string row_type_name = fields_[0];
558 const std::string row_name = fields_[1];
561 if (row_type == UNKNOWN_ROW_TYPE) {
562 return InvalidArgumentError(
"Unknown row type.");
566 if (objective_name_.empty() && row_type == NONE) {
567 row_type = OBJECTIVE;
568 objective_name_ = row_name;
570 if (row_type == NONE) {
571 ++num_unconstrained_rows_;
573 const int row = data->FindOrCreateConstraint(row_name);
574 if (is_lazy) data->SetIsLazy(
row);
581 data->ConstraintUpperBound(
row));
584 data->SetConstraintBounds(
row, data->ConstraintLowerBound(
row),
595 return absl::OkStatus();
598 template <
class DataWrapper>
599 absl::Status MPSReaderImpl::ProcessColumnsSection(DataWrapper* data) {
601 if (absl::StrContains(line_,
"'MARKER'")) {
602 if (absl::StrContains(line_,
"'INTORG'")) {
603 VLOG(2) <<
"Entering integer marker.\n" << line_;
604 if (in_integer_section_) {
605 return InvalidArgumentError(
"Found INTORG inside the integer section.");
607 in_integer_section_ =
true;
608 }
else if (absl::StrContains(line_,
"'INTEND'")) {
609 VLOG(2) <<
"Leaving integer marker.\n" << line_;
610 if (!in_integer_section_) {
611 return InvalidArgumentError(
612 "Found INTEND without corresponding INTORG.");
614 in_integer_section_ =
false;
616 return absl::OkStatus();
618 const int start_index = free_form_ ? 0 : 1;
619 if (fields_.size() < start_index + 3) {
620 return InvalidArgumentError(
"Not enough fields in COLUMNS section.");
622 const std::string& column_name = GetField(start_index, 0);
623 const std::string& row1_name = GetField(start_index, 1);
624 const std::string& row1_value = GetField(start_index, 2);
625 const int col = data->FindOrCreateVariable(column_name);
626 is_binary_by_default_.resize(
col + 1,
false);
627 if (in_integer_section_) {
628 data->SetVariableTypeToInteger(
col);
630 data->SetVariableBounds(
col, 0.0, 1.0);
631 is_binary_by_default_[
col] =
true;
636 if (fields_.size() == start_index + 4) {
637 return InvalidArgumentError(
"Unexpected number of fields.");
639 if (fields_.size() - start_index > 4) {
640 const std::string& row2_name = GetField(start_index, 3);
641 const std::string& row2_value = GetField(start_index, 4);
644 return absl::OkStatus();
647 template <
class DataWrapper>
648 absl::Status MPSReaderImpl::ProcessRhsSection(DataWrapper* data) {
649 const int start_index = free_form_ ? 0 : 2;
650 const int offset = start_index + GetFieldOffset();
651 if (fields_.size() < offset + 2) {
652 return InvalidArgumentError(
"Not enough fields in RHS section.");
655 const std::string& row1_name = GetField(offset, 0);
656 const std::string& row1_value = GetField(offset, 1);
658 if (fields_.size() - start_index >= 4) {
659 const std::string& row2_name = GetField(offset, 2);
660 const std::string& row2_value = GetField(offset, 3);
663 return absl::OkStatus();
666 template <
class DataWrapper>
667 absl::Status MPSReaderImpl::ProcessRangesSection(DataWrapper* data) {
668 const int start_index = free_form_ ? 0 : 2;
669 const int offset = start_index + GetFieldOffset();
670 if (fields_.size() < offset + 2) {
671 return InvalidArgumentError(
"Not enough fields in RHS section.");
674 const std::string& row1_name = GetField(offset, 0);
675 const std::string& row1_value = GetField(offset, 1);
677 if (fields_.size() - start_index >= 4) {
678 const std::string& row2_name = GetField(offset, 2);
679 const std::string& row2_value = GetField(offset, 3);
682 return absl::OkStatus();
685 template <
class DataWrapper>
686 absl::Status MPSReaderImpl::ProcessBoundsSection(DataWrapper* data) {
687 if (fields_.size() < 3) {
688 return InvalidArgumentError(
"Not enough fields in BOUNDS section.");
690 const std::string bound_type_mnemonic = fields_[0];
691 const std::string bound_row_name = fields_[1];
692 const std::string column_name = fields_[2];
693 std::string bound_value;
694 if (fields_.size() >= 4) {
695 bound_value = fields_[3];
697 return StoreBound(bound_type_mnemonic, column_name, bound_value, data);
700 template <
class DataWrapper>
701 absl::Status MPSReaderImpl::ProcessIndicatorsSection(DataWrapper* data) {
705 if (fields_.size() < 4) {
706 return InvalidArgumentError(
"Not enough fields in INDICATORS section.");
709 const std::string type = fields_[0];
711 return InvalidArgumentError(
712 "Indicator constraints must start with \"IF\".");
714 const std::string row_name = fields_[1];
715 const std::string column_name = fields_[2];
716 const std::string column_value = fields_[3];
721 const int col = data->FindOrCreateVariable(column_name);
723 data->SetVariableTypeToInteger(
col);
724 data->SetVariableBounds(
col,
std::max(0.0, data->VariableLowerBound(
col)),
728 AppendLineToError(data->CreateIndicatorConstraint(row_name,
col,
value)));
730 return absl::OkStatus();
733 template <
class DataWrapper>
734 absl::Status MPSReaderImpl::StoreCoefficient(
int col,
735 const std::string& row_name,
736 const std::string& row_value,
738 if (row_name.empty() || row_name ==
"$") {
739 return absl::OkStatus();
745 return InvalidArgumentError(
"Constraint coefficients cannot be infinity.");
747 if (
value == 0.0)
return absl::OkStatus();
748 if (row_name == objective_name_) {
749 data->SetObjectiveCoefficient(
col,
value);
751 const int row = data->FindOrCreateConstraint(row_name);
754 return absl::OkStatus();
757 template <
class DataWrapper>
758 absl::Status MPSReaderImpl::StoreRightHandSide(
const std::string& row_name,
759 const std::string& row_value,
761 if (row_name.empty())
return absl::OkStatus();
763 if (row_name != objective_name_) {
764 const int row = data->FindOrCreateConstraint(row_name);
775 data->SetConstraintBounds(
row, lower_bound, upper_bound);
777 return absl::OkStatus();
780 template <
class DataWrapper>
781 absl::Status MPSReaderImpl::StoreRange(
const std::string& row_name,
782 const std::string& range_value,
784 if (row_name.empty())
return absl::OkStatus();
786 const int row = data->FindOrCreateConstraint(row_name);
792 if (lower_bound == upper_bound) {
794 lower_bound += range;
796 upper_bound += range;
800 lower_bound = upper_bound - fabs(range);
803 upper_bound = lower_bound + fabs(range);
805 data->SetConstraintBounds(
row, lower_bound, upper_bound);
806 return absl::OkStatus();
809 template <
class DataWrapper>
810 absl::Status MPSReaderImpl::StoreBound(
const std::string& bound_type_mnemonic,
811 const std::string& column_name,
812 const std::string& bound_value,
815 bound_name_to_id_map_, bound_type_mnemonic, UNKNOWN_BOUND_TYPE);
816 if (bound_type_id == UNKNOWN_BOUND_TYPE) {
817 return InvalidArgumentError(
"Unknown bound type.");
819 const int col = data->FindOrCreateVariable(column_name);
820 if (integer_type_names_set_.count(bound_type_mnemonic) != 0) {
821 data->SetVariableTypeToInteger(
col);
823 if (is_binary_by_default_.size() <=
col) {
825 is_binary_by_default_.resize(
col + 1,
false);
828 DCHECK(!is_binary_by_default_[
col] || data->VariableIsInteger(
col));
834 if (is_binary_by_default_[
col]) {
838 switch (bound_type_id) {
842 if (bound_type_mnemonic ==
"LI" && lower_bound == 0.0) {
851 case FIXED_VARIABLE: {
853 upper_bound = lower_bound;
872 case UNKNOWN_BOUND_TYPE:
874 return InvalidArgumentError(
"Unknown bound type.");
876 is_binary_by_default_[
col] =
false;
877 data->SetVariableBounds(
col, lower_bound, upper_bound);
878 return absl::OkStatus();
881 const int MPSReaderImpl::kNumFields = 6;
882 const int MPSReaderImpl::kFieldStartPos[kNumFields] = {1, 4, 14, 24, 39, 49};
883 const int MPSReaderImpl::kFieldLength[kNumFields] = {2, 8, 8, 12, 8, 12};
884 const int MPSReaderImpl::kSpacePos[12] = {12, 13, 22, 23, 36, 37,
885 38, 47, 48, 61, 62, 63};
890 section_(UNKNOWN_SECTION),
891 section_name_to_id_map_(),
892 row_name_to_id_map_(),
893 bound_name_to_id_map_(),
894 integer_type_names_set_(),
897 in_integer_section_(false),
898 num_unconstrained_rows_(0) {
899 section_name_to_id_map_[
"*"] = COMMENT;
900 section_name_to_id_map_[
"NAME"] = NAME;
901 section_name_to_id_map_[
"OBJSENSE"] = OBJSENSE;
902 section_name_to_id_map_[
"ROWS"] = ROWS;
903 section_name_to_id_map_[
"LAZYCONS"] = LAZYCONS;
904 section_name_to_id_map_[
"COLUMNS"] = COLUMNS;
905 section_name_to_id_map_[
"RHS"] = RHS;
906 section_name_to_id_map_[
"RANGES"] = RANGES;
907 section_name_to_id_map_[
"BOUNDS"] = BOUNDS;
908 section_name_to_id_map_[
"INDICATORS"] = INDICATORS;
909 section_name_to_id_map_[
"ENDATA"] = ENDATA;
910 row_name_to_id_map_[
"E"] = EQUALITY;
911 row_name_to_id_map_[
"L"] = LESS_THAN;
912 row_name_to_id_map_[
"G"] = GREATER_THAN;
913 row_name_to_id_map_[
"N"] = NONE;
914 bound_name_to_id_map_[
"LO"] = LOWER_BOUND;
915 bound_name_to_id_map_[
"UP"] = UPPER_BOUND;
916 bound_name_to_id_map_[
"FX"] = FIXED_VARIABLE;
917 bound_name_to_id_map_[
"FR"] = FREE_VARIABLE;
918 bound_name_to_id_map_[
"MI"] = NEGATIVE;
919 bound_name_to_id_map_[
"PL"] = POSITIVE;
920 bound_name_to_id_map_[
"BV"] = BINARY;
921 bound_name_to_id_map_[
"LI"] = LOWER_BOUND;
922 bound_name_to_id_map_[
"UI"] = UPPER_BOUND;
923 integer_type_names_set_.insert(
"BV");
924 integer_type_names_set_.insert(
"LI");
925 integer_type_names_set_.insert(
"UI");
928 void MPSReaderImpl::Reset() {
929 fields_.resize(kNumFields);
931 in_integer_section_ =
false;
932 num_unconstrained_rows_ = 0;
933 objective_name_.clear();
936 void MPSReaderImpl::DisplaySummary() {
937 if (num_unconstrained_rows_ > 0) {
938 VLOG(1) <<
"There are " << num_unconstrained_rows_ + 1
939 <<
" unconstrained rows. The first of them (" << objective_name_
940 <<
") was used as the objective.";
944 bool MPSReaderImpl::IsFixedFormat() {
945 for (
const int i : kSpacePos) {
946 if (i >= line_.length())
break;
947 if (line_[i] !=
' ')
return false;
952 absl::Status MPSReaderImpl::SplitLineIntoFields() {
954 fields_ = absl::StrSplit(line_, absl::ByAnyChar(
" \t"), absl::SkipEmpty());
955 if (fields_.size() > kNumFields) {
956 return InvalidArgumentError(
"Found too many fields.");
963 if (section_ != NAME && !IsFixedFormat()) {
964 return InvalidArgumentError(
"Line is not in fixed format.");
966 const int length = line_.length();
967 for (
int i = 0; i < kNumFields; ++i) {
968 if (kFieldStartPos[i] < length) {
969 fields_[i] = line_.substr(kFieldStartPos[i], kFieldLength[i]);
970 fields_[i].erase(fields_[i].find_last_not_of(
" ") + 1);
976 return absl::OkStatus();
979 std::string MPSReaderImpl::GetFirstWord()
const {
980 if (line_[0] ==
' ') {
981 return std::string(
"");
983 const int first_space_pos = line_.find(
' ');
984 const std::string first_word = line_.substr(0, first_space_pos);
988 bool MPSReaderImpl::IsCommentOrBlank()
const {
989 const char* line = line_.c_str();
993 for (; *line !=
'\0'; ++line) {
994 if (*line !=
' ' && *line !=
'\t') {
1001 absl::StatusOr<double> MPSReaderImpl::GetDoubleFromString(
1002 const std::string& str) {
1004 if (!absl::SimpleAtod(str, &result)) {
1005 return InvalidArgumentError(
1006 absl::StrCat(
"Failed to convert \"", str,
"\" to double."));
1008 if (std::isnan(result)) {
1009 return InvalidArgumentError(
"Found NaN value.");
1014 absl::StatusOr<bool> MPSReaderImpl::GetBoolFromString(
const std::string& str) {
1016 if (!absl::SimpleAtoi(str, &result) || result < 0 || result > 1) {
1017 return InvalidArgumentError(
1018 absl::StrCat(
"Failed to convert \"", str,
"\" to bool."));
1023 absl::Status MPSReaderImpl::ProcessSosSection() {
1024 return InvalidArgumentError(
"Section SOS currently not supported.");
1027 absl::Status MPSReaderImpl::InvalidArgumentError(
1028 const std::string& error_message) {
1029 return AppendLineToError(absl::InvalidArgumentError(error_message));
1032 absl::Status MPSReaderImpl::AppendLineToError(
const absl::Status& status) {
1034 <<
" Line " << line_num_ <<
": \"" << line_ <<
"\".";
1044 MPModelProto* data,
Form form) {