16 #include "absl/status/status.h"
17 #include "absl/strings/match.h"
18 #include "absl/strings/str_split.h"
31 absl::Status
ParseFile(
const std::string& file_name, Data* data,
36 static const int kNumFields;
39 static const int kFieldStartPos[];
42 static const int kFieldLength[];
45 static const int kSpacePos[];
51 void DisplaySummary();
54 absl::Status SplitLineIntoFields();
60 std::string GetFirstWord()
const;
64 bool IsCommentOrBlank()
const;
67 const std::string& GetField(
int offset,
int index)
const {
68 return fields_[offset +
index];
76 int GetFieldOffset()
const {
return free_form_ ? fields_.size() & 1 : 0; }
79 template <
class DataWrapper>
80 absl::Status ProcessLine(
const std::string& line,
DataWrapper* data);
83 template <
class DataWrapper>
84 absl::Status ProcessObjectiveSenseSection(
DataWrapper* data);
87 template <
class DataWrapper>
88 absl::Status ProcessRowsSection(
bool is_lazy,
DataWrapper* data);
91 template <
class DataWrapper>
92 absl::Status ProcessColumnsSection(
DataWrapper* data);
95 template <
class DataWrapper>
99 template <
class DataWrapper>
100 absl::Status ProcessRangesSection(
DataWrapper* data);
103 template <
class DataWrapper>
104 absl::Status ProcessBoundsSection(
DataWrapper* data);
107 template <
class DataWrapper>
108 absl::Status ProcessIndicatorsSection(
DataWrapper* data);
111 absl::Status ProcessSosSection();
142 template <
class DataWrapper>
143 absl::Status StoreBound(
const std::string& bound_type_mnemonic,
144 const std::string& column_name,
145 const std::string& bound_value,
DataWrapper* data);
148 template <
class DataWrapper>
149 absl::Status StoreCoefficient(
int col,
const std::string& row_name,
150 const std::string& row_value,
154 template <
class DataWrapper>
155 absl::Status StoreRightHandSide(
const std::string& row_name,
156 const std::string& row_value,
160 template <
class DataWrapper>
161 absl::Status StoreRange(
const std::string& row_name,
162 const std::string& range_value,
DataWrapper* data);
166 absl::Status InvalidArgumentError(
const std::string& error_message);
170 absl::Status AppendLineToError(
const absl::Status& status);
176 std::vector<std::string> fields_;
179 std::string objective_name_;
202 absl::flat_hash_map<std::string, SectionId> section_name_to_id_map_;
205 absl::flat_hash_map<std::string, RowTypeId> row_name_to_id_map_;
208 absl::flat_hash_map<std::string, BoundTypeId> bound_name_to_id_map_;
211 absl::flat_hash_set<std::string> integer_type_names_set_;
221 std::vector<bool> is_binary_by_default_;
226 bool in_integer_section_;
231 int num_unconstrained_rows_;
238 template <
class Data>
247 data_->SetDcheckBounds(
false);
254 data_->SetMaximizationProblem(maximize);
258 return data_->FindOrCreateConstraint(
name).value();
261 data_->SetConstraintBounds(RowIndex(
index), lower_bound, upper_bound);
265 data_->SetCoefficient(RowIndex(row_index), ColIndex(col_index),
269 LOG_FIRST_N(WARNING, 1)
270 <<
"LAZYCONS section detected. It will be handled as an extension of "
274 return data_->constraint_lower_bounds()[RowIndex(row_index)];
277 return data_->constraint_upper_bounds()[RowIndex(row_index)];
281 return data_->FindOrCreateVariable(
name).value();
284 data_->SetVariableType(ColIndex(
index),
288 data_->SetVariableBounds(ColIndex(
index), lower_bound, upper_bound);
294 return data_->IsVariableInteger(ColIndex(
index));
297 return data_->variable_lower_bounds()[ColIndex(
index)];
300 return data_->variable_upper_bounds()[ColIndex(
index)];
305 return absl::UnimplementedError(
306 "LinearProgram does not support indicator constraints.");
327 const auto it = constraint_indices_by_name_.find(
name);
328 if (it != constraint_indices_by_name_.end())
return it->second;
330 const int index = data_->constraint_size();
331 MPConstraintProto*
const constraint = data_->add_constraint();
332 constraint->set_lower_bound(0.0);
333 constraint->set_upper_bound(0.0);
334 constraint->set_name(
name);
335 constraint_indices_by_name_[
name] =
index;
339 data_->mutable_constraint(
index)->set_lower_bound(lower_bound);
340 data_->mutable_constraint(
index)->set_upper_bound(upper_bound);
348 MPConstraintProto*
const constraint = data_->mutable_constraint(row_index);
349 constraint->add_var_index(col_index);
353 data_->mutable_constraint(row_index)->set_is_lazy(
true);
356 return data_->constraint(row_index).lower_bound();
359 return data_->constraint(row_index).upper_bound();
363 const auto it = variable_indices_by_name_.find(
name);
364 if (it != variable_indices_by_name_.end())
return it->second;
366 const int index = data_->variable_size();
367 MPVariableProto*
const variable = data_->add_variable();
368 variable->set_lower_bound(0.0);
369 variable->set_name(
name);
374 data_->mutable_variable(
index)->set_is_integer(
true);
377 data_->mutable_variable(
index)->set_lower_bound(lower_bound);
378 data_->mutable_variable(
index)->set_upper_bound(upper_bound);
384 return data_->variable(
index).is_integer();
387 return data_->variable(
index).lower_bound();
390 return data_->variable(
index).upper_bound();
395 const auto it = constraint_indices_by_name_.find(cst_name);
396 if (it == constraint_indices_by_name_.end()) {
397 return absl::InvalidArgumentError(
398 absl::StrCat(
"Constraint \"", cst_name,
"\" doesn't exist."));
400 const int cst_index = it->second;
402 MPGeneralConstraintProto*
const constraint =
403 data_->add_general_constraint();
404 constraint->set_name(
405 absl::StrCat(
"ind_", data_->constraint(cst_index).name()));
406 MPIndicatorConstraint*
const indicator =
407 constraint->mutable_indicator_constraint();
408 *indicator->mutable_constraint() = data_->constraint(cst_index);
409 indicator->set_var_index(var_index);
410 indicator->set_var_value(var_value);
411 constraints_to_delete_.insert(cst_index);
413 return absl::OkStatus();
418 constraints_to_delete_);
424 absl::flat_hash_map<std::string, int> variable_indices_by_name_;
425 absl::flat_hash_map<std::string, int> constraint_indices_by_name_;
426 std::set<int> constraints_to_delete_;
429 template <
class Data>
432 if (data ==
nullptr) {
433 return absl::InvalidArgumentError(
"NULL pointer passed as argument.");
438 return absl::OkStatus();
447 data_wrapper.SetUp();
448 for (
const std::string& line :
452 data_wrapper.CleanUp();
454 return absl::OkStatus();
457 template <
class DataWrapper>
458 absl::Status MPSReaderImpl::ProcessLine(
const std::string& line,
462 if (IsCommentOrBlank()) {
463 return absl::OkStatus();
465 if (!free_form_ && line_.find(
'\t') != std::string::npos) {
466 return InvalidArgumentError(
"File contains tabs.");
469 if (line[0] !=
'\0' && line[0] !=
' ') {
470 section = GetFirstWord();
473 if (section_ == UNKNOWN_SECTION) {
474 return InvalidArgumentError(
"Unknown section.");
476 if (section_ == COMMENT) {
477 return absl::OkStatus();
479 if (section_ == OBJSENSE) {
480 return absl::OkStatus();
482 if (section_ == NAME) {
491 if (fields_.size() >= 2) {
492 data->SetName(fields_[1]);
495 const std::vector<std::string> free_fields =
496 absl::StrSplit(line_, absl::ByAnyChar(
" \t"), absl::SkipEmpty());
497 const std::string free_name =
498 free_fields.size() >= 2 ? free_fields[1] :
"";
499 const std::string fixed_name = fields_.size() >= 3 ? fields_[2] :
"";
500 if (free_name != fixed_name) {
501 return InvalidArgumentError(
502 "Fixed form invalid: name differs between free and fixed "
505 data->SetName(fixed_name);
508 return absl::OkStatus();
513 return InvalidArgumentError(
"Second NAME field.");
515 return ProcessObjectiveSenseSection(data);
517 return ProcessRowsSection(
false, data);
519 return ProcessRowsSection(
true, data);
521 return ProcessColumnsSection(data);
523 return ProcessRhsSection(data);
525 return ProcessRangesSection(data);
527 return ProcessBoundsSection(data);
529 return ProcessIndicatorsSection(data);
531 return ProcessSosSection();
535 return InvalidArgumentError(
"Unknown section.");
537 return absl::OkStatus();
540 template <
class DataWrapper>
541 absl::Status MPSReaderImpl::ProcessObjectiveSenseSection(DataWrapper* data) {
542 if (fields_.size() != 1 && fields_[0] !=
"MIN" && fields_[0] !=
"MAX") {
543 return InvalidArgumentError(
"Expected objective sense (MAX or MIN).");
545 data->SetObjectiveDirection(fields_[0] ==
"MAX");
546 return absl::OkStatus();
549 template <
class DataWrapper>
550 absl::Status MPSReaderImpl::ProcessRowsSection(
bool is_lazy,
552 if (fields_.size() < 2) {
553 return InvalidArgumentError(
"Not enough fields in ROWS section.");
555 const std::string row_type_name = fields_[0];
556 const std::string row_name = fields_[1];
559 if (row_type == UNKNOWN_ROW_TYPE) {
560 return InvalidArgumentError(
"Unknown row type.");
564 if (objective_name_.empty() && row_type == NONE) {
565 row_type = OBJECTIVE;
566 objective_name_ = row_name;
568 if (row_type == NONE) {
569 ++num_unconstrained_rows_;
571 const int row = data->FindOrCreateConstraint(row_name);
572 if (is_lazy) data->SetIsLazy(
row);
579 data->ConstraintUpperBound(
row));
582 data->SetConstraintBounds(
row, data->ConstraintLowerBound(
row),
593 return absl::OkStatus();
596 template <
class DataWrapper>
597 absl::Status MPSReaderImpl::ProcessColumnsSection(DataWrapper* data) {
599 if (line_.find(
"'MARKER'") != std::string::npos) {
600 if (line_.find(
"'INTORG'") != std::string::npos) {
601 VLOG(2) <<
"Entering integer marker.\n" << line_;
602 if (in_integer_section_) {
603 return InvalidArgumentError(
"Found INTORG inside the integer section.");
605 in_integer_section_ =
true;
606 }
else if (line_.find(
"'INTEND'") != std::string::npos) {
607 VLOG(2) <<
"Leaving integer marker.\n" << line_;
608 if (!in_integer_section_) {
609 return InvalidArgumentError(
610 "Found INTEND without corresponding INTORG.");
612 in_integer_section_ =
false;
614 return absl::OkStatus();
616 const int start_index = free_form_ ? 0 : 1;
617 if (fields_.size() < start_index + 3) {
618 return InvalidArgumentError(
"Not enough fields in COLUMNS section.");
620 const std::string& column_name = GetField(start_index, 0);
621 const std::string& row1_name = GetField(start_index, 1);
622 const std::string& row1_value = GetField(start_index, 2);
623 const int col = data->FindOrCreateVariable(column_name);
624 is_binary_by_default_.resize(
col + 1,
false);
625 if (in_integer_section_) {
626 data->SetVariableTypeToInteger(
col);
628 data->SetVariableBounds(
col, 0.0, 1.0);
629 is_binary_by_default_[
col] =
true;
634 if (fields_.size() == start_index + 4) {
635 return InvalidArgumentError(
"Unexpected number of fields.");
637 if (fields_.size() - start_index > 4) {
638 const std::string& row2_name = GetField(start_index, 3);
639 const std::string& row2_value = GetField(start_index, 4);
642 return absl::OkStatus();
645 template <
class DataWrapper>
646 absl::Status MPSReaderImpl::ProcessRhsSection(DataWrapper* data) {
647 const int start_index = free_form_ ? 0 : 2;
648 const int offset = start_index + GetFieldOffset();
649 if (fields_.size() < offset + 2) {
650 return InvalidArgumentError(
"Not enough fields in RHS section.");
653 const std::string& row1_name = GetField(offset, 0);
654 const std::string& row1_value = GetField(offset, 1);
656 if (fields_.size() - start_index >= 4) {
657 const std::string& row2_name = GetField(offset, 2);
658 const std::string& row2_value = GetField(offset, 3);
661 return absl::OkStatus();
664 template <
class DataWrapper>
665 absl::Status MPSReaderImpl::ProcessRangesSection(DataWrapper* data) {
666 const int start_index = free_form_ ? 0 : 2;
667 const int offset = start_index + GetFieldOffset();
668 if (fields_.size() < offset + 2) {
669 return InvalidArgumentError(
"Not enough fields in RHS section.");
672 const std::string& row1_name = GetField(offset, 0);
673 const std::string& row1_value = GetField(offset, 1);
675 if (fields_.size() - start_index >= 4) {
676 const std::string& row2_name = GetField(offset, 2);
677 const std::string& row2_value = GetField(offset, 3);
680 return absl::OkStatus();
683 template <
class DataWrapper>
684 absl::Status MPSReaderImpl::ProcessBoundsSection(DataWrapper* data) {
685 if (fields_.size() < 3) {
686 return InvalidArgumentError(
"Not enough fields in BOUNDS section.");
688 const std::string bound_type_mnemonic = fields_[0];
689 const std::string bound_row_name = fields_[1];
690 const std::string column_name = fields_[2];
691 std::string bound_value;
692 if (fields_.size() >= 4) {
693 bound_value = fields_[3];
695 return StoreBound(bound_type_mnemonic, column_name, bound_value, data);
698 template <
class DataWrapper>
699 absl::Status MPSReaderImpl::ProcessIndicatorsSection(DataWrapper* data) {
703 if (fields_.size() < 4) {
704 return InvalidArgumentError(
"Not enough fields in INDICATORS section.");
707 const std::string type = fields_[0];
709 return InvalidArgumentError(
710 "Indicator constraints must start with \"IF\".");
712 const std::string row_name = fields_[1];
713 const std::string column_name = fields_[2];
714 const std::string column_value = fields_[3];
719 const int col = data->FindOrCreateVariable(column_name);
721 data->SetVariableTypeToInteger(
col);
722 data->SetVariableBounds(
col,
std::max(0.0, data->VariableLowerBound(
col)),
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();
744 return InvalidArgumentError(
"Constraint coefficients cannot be infinity.");
746 if (
value == 0.0)
return absl::OkStatus();
747 if (row_name == objective_name_) {
748 data->SetObjectiveCoefficient(
col,
value);
750 const int row = data->FindOrCreateConstraint(row_name);
753 return absl::OkStatus();
756 template <
class DataWrapper>
757 absl::Status MPSReaderImpl::StoreRightHandSide(
const std::string& row_name,
758 const std::string& row_value,
760 if (row_name.empty())
return absl::OkStatus();
762 if (row_name != objective_name_) {
763 const int row = data->FindOrCreateConstraint(row_name);
774 data->SetConstraintBounds(
row, lower_bound, upper_bound);
776 return absl::OkStatus();
779 template <
class DataWrapper>
780 absl::Status MPSReaderImpl::StoreRange(
const std::string& row_name,
781 const std::string& range_value,
783 if (row_name.empty())
return absl::OkStatus();
785 const int row = data->FindOrCreateConstraint(row_name);
791 if (lower_bound == upper_bound) {
793 lower_bound += range;
795 upper_bound += range;
799 lower_bound = upper_bound - fabs(range);
802 upper_bound = lower_bound + fabs(range);
804 data->SetConstraintBounds(
row, lower_bound, upper_bound);
805 return absl::OkStatus();
808 template <
class DataWrapper>
809 absl::Status MPSReaderImpl::StoreBound(
const std::string& bound_type_mnemonic,
810 const std::string& column_name,
811 const std::string& bound_value,
814 bound_name_to_id_map_, bound_type_mnemonic, UNKNOWN_BOUND_TYPE);
815 if (bound_type_id == UNKNOWN_BOUND_TYPE) {
816 return InvalidArgumentError(
"Unknown bound type.");
818 const int col = data->FindOrCreateVariable(column_name);
819 if (integer_type_names_set_.count(bound_type_mnemonic) != 0) {
820 data->SetVariableTypeToInteger(
col);
822 if (is_binary_by_default_.size() <=
col) {
824 is_binary_by_default_.resize(
col + 1,
false);
827 DCHECK(!is_binary_by_default_[
col] || data->VariableIsInteger(
col));
833 if (is_binary_by_default_[
col]) {
837 switch (bound_type_id) {
841 if (bound_type_mnemonic ==
"LI" && lower_bound == 0.0) {
850 case FIXED_VARIABLE: {
852 upper_bound = lower_bound;
871 case UNKNOWN_BOUND_TYPE:
873 return InvalidArgumentError(
"Unknown bound type.");
875 is_binary_by_default_[
col] =
false;
876 data->SetVariableBounds(
col, lower_bound, upper_bound);
877 return absl::OkStatus();
880 const int MPSReaderImpl::kNumFields = 6;
881 const int MPSReaderImpl::kFieldStartPos[kNumFields] = {1, 4, 14, 24, 39, 49};
882 const int MPSReaderImpl::kFieldLength[kNumFields] = {2, 8, 8, 12, 8, 12};
883 const int MPSReaderImpl::kSpacePos[12] = {12, 13, 22, 23, 36, 37,
884 38, 47, 48, 61, 62, 63};
889 section_(UNKNOWN_SECTION),
890 section_name_to_id_map_(),
891 row_name_to_id_map_(),
892 bound_name_to_id_map_(),
893 integer_type_names_set_(),
896 in_integer_section_(false),
897 num_unconstrained_rows_(0) {
898 section_name_to_id_map_[
"*"] = COMMENT;
899 section_name_to_id_map_[
"NAME"] = NAME;
900 section_name_to_id_map_[
"OBJSENSE"] = OBJSENSE;
901 section_name_to_id_map_[
"ROWS"] = ROWS;
902 section_name_to_id_map_[
"LAZYCONS"] = LAZYCONS;
903 section_name_to_id_map_[
"COLUMNS"] = COLUMNS;
904 section_name_to_id_map_[
"RHS"] = RHS;
905 section_name_to_id_map_[
"RANGES"] = RANGES;
906 section_name_to_id_map_[
"BOUNDS"] = BOUNDS;
907 section_name_to_id_map_[
"INDICATORS"] = INDICATORS;
908 section_name_to_id_map_[
"ENDATA"] = ENDATA;
909 row_name_to_id_map_[
"E"] = EQUALITY;
910 row_name_to_id_map_[
"L"] = LESS_THAN;
911 row_name_to_id_map_[
"G"] = GREATER_THAN;
912 row_name_to_id_map_[
"N"] = NONE;
913 bound_name_to_id_map_[
"LO"] = LOWER_BOUND;
914 bound_name_to_id_map_[
"UP"] = UPPER_BOUND;
915 bound_name_to_id_map_[
"FX"] = FIXED_VARIABLE;
916 bound_name_to_id_map_[
"FR"] = FREE_VARIABLE;
917 bound_name_to_id_map_[
"MI"] = NEGATIVE;
918 bound_name_to_id_map_[
"PL"] = POSITIVE;
919 bound_name_to_id_map_[
"BV"] = BINARY;
920 bound_name_to_id_map_[
"LI"] = LOWER_BOUND;
921 bound_name_to_id_map_[
"UI"] = UPPER_BOUND;
922 integer_type_names_set_.insert(
"BV");
923 integer_type_names_set_.insert(
"LI");
924 integer_type_names_set_.insert(
"UI");
927 void MPSReaderImpl::Reset() {
928 fields_.resize(kNumFields);
930 in_integer_section_ =
false;
931 num_unconstrained_rows_ = 0;
932 objective_name_.clear();
935 void MPSReaderImpl::DisplaySummary() {
936 if (num_unconstrained_rows_ > 0) {
937 VLOG(1) <<
"There are " << num_unconstrained_rows_ + 1
938 <<
" unconstrained rows. The first of them (" << objective_name_
939 <<
") was used as the objective.";
943 bool MPSReaderImpl::IsFixedFormat() {
944 for (
const int i : kSpacePos) {
945 if (i >= line_.length())
break;
946 if (line_[i] !=
' ')
return false;
951 absl::Status MPSReaderImpl::SplitLineIntoFields() {
953 fields_ = absl::StrSplit(line_, absl::ByAnyChar(
" \t"), absl::SkipEmpty());
954 if (fields_.size() > kNumFields) {
955 return InvalidArgumentError(
"Found too many fields.");
962 if (section_ != NAME && !IsFixedFormat()) {
963 return InvalidArgumentError(
"Line is not in fixed format.");
965 const int length = line_.length();
966 for (
int i = 0; i < kNumFields; ++i) {
967 if (kFieldStartPos[i] < length) {
968 fields_[i] = line_.substr(kFieldStartPos[i], kFieldLength[i]);
969 fields_[i].erase(fields_[i].find_last_not_of(
" ") + 1);
975 return absl::OkStatus();
978 std::string MPSReaderImpl::GetFirstWord()
const {
979 if (line_[0] ==
' ') {
980 return std::string(
"");
982 const int first_space_pos = line_.find(
' ');
983 const std::string first_word = line_.substr(0, first_space_pos);
987 bool MPSReaderImpl::IsCommentOrBlank()
const {
988 const char* line = line_.c_str();
992 for (; *line !=
'\0'; ++line) {
993 if (*line !=
' ' && *line !=
'\t') {
1001 const std::string& str) {
1003 if (!absl::SimpleAtod(str, &result)) {
1004 return InvalidArgumentError(
1005 absl::StrCat(
"Failed to convert \"", str,
"\" to double."));
1007 if (std::isnan(result)) {
1008 return InvalidArgumentError(
"Found NaN value.");
1015 if (!absl::SimpleAtoi(str, &result) || result < 0 || result > 1) {
1016 return InvalidArgumentError(
1017 absl::StrCat(
"Failed to convert \"", str,
"\" to bool."));
1022 absl::Status MPSReaderImpl::ProcessSosSection() {
1023 return InvalidArgumentError(
"Section SOS currently not supported.");
1026 absl::Status MPSReaderImpl::InvalidArgumentError(
1027 const std::string& error_message) {
1028 return absl::InvalidArgumentError(error_message);
1043 MPModelProto* data,
Form form) {