From dafea0aded026a6e016fd01d25765fa01a1fe5d2 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Wed, 24 Apr 2019 17:51:30 +0200 Subject: [PATCH] move mps reader to .cc --- ortools/lp_data/mps_reader.cc | 876 +++++++++++++++++++++++++++++++++- ortools/lp_data/mps_reader.h | 826 +------------------------------- 2 files changed, 865 insertions(+), 837 deletions(-) diff --git a/ortools/lp_data/mps_reader.cc b/ortools/lp_data/mps_reader.cc index a964f7ebc0..c70dbb9458 100644 --- a/ortools/lp_data/mps_reader.cc +++ b/ortools/lp_data/mps_reader.cc @@ -21,13 +21,842 @@ namespace operations_research { namespace glop { -const int MPSReader::kNumFields = 6; -const int MPSReader::kFieldStartPos[kNumFields] = {1, 4, 14, 24, 39, 49}; -const int MPSReader::kFieldLength[kNumFields] = {2, 8, 8, 12, 8, 12}; -const int MPSReader::kSpacePos[12] = {12, 13, 22, 23, 36, 37, +class MPSReaderAux { + public: + MPSReaderAux(); + + // Parses instance from a file. We currently support LinearProgram and + // MpModelProto for the Data type, but it should be easy to add more. + template + util::Status ParseFile(const std::string& file_name, Data* data, + MPSReader::Form form); + + private: + // Number of fields in one line of MPS file. + static const int kNumFields; + + // Starting positions of each of the fields for fixed format. + static const int kFieldStartPos[]; + + // Lengths of each of the fields for fixed format. + static const int kFieldLength[]; + + // Positions where there should be spaces for fixed format. + static const int kSpacePos[]; + + // Resets the object to its initial value before reading a new file. + void Reset(); + + // Displays some information on the last loaded file. + void DisplaySummary(); + + // Get each field for a given line. + util::Status SplitLineIntoFields(); + + // Returns true if the line matches the fixed format. + bool IsFixedFormat(); + + // Get the first word in a line. + std::string GetFirstWord() const; + + // Returns true if the line contains a comment (starting with '*') or + // if it is a blank line. + bool IsCommentOrBlank() const; + + // Helper function that returns fields_[offset + index]. + const std::string& GetField(int offset, int index) const { + return fields_[offset + index]; + } + + // Returns the offset at which to start the parsing of fields_. + // If in fixed form, the offset is 0. + // If in fixed form and the number of fields is odd, it is 1, + // otherwise it is 0. + // This is useful when processing RANGES and RHS sections. + int GetFieldOffset() const { return free_form_ ? fields_.size() & 1 : 0; } + + // Line processor. + template + util::Status ProcessLine(const std::string& line, DataWrapper* data); + + // Process section ROWS in the MPS file. + template + util::Status ProcessRowsSection(bool is_lazy, DataWrapper* data); + + // Process section COLUMNS in the MPS file. + template + util::Status ProcessColumnsSection(DataWrapper* data); + + // Process section RHS in the MPS file. + template + util::Status ProcessRhsSection(DataWrapper* data); + + // Process section RANGES in the MPS file. + template + util::Status ProcessRangesSection(DataWrapper* data); + + // Process section BOUNDS in the MPS file. + template + util::Status ProcessBoundsSection(DataWrapper* data); + + // Process section INDICATORS in the MPS file. + template + util::Status ProcessIndicatorsSection(DataWrapper* data); + + // Process section SOS in the MPS file. + util::Status ProcessSosSection(); + + // Safely converts a std::string to a numerical type. Returns an error if the + // std::string passed as parameter is ill-formed. + util::StatusOr GetDoubleFromString(const std::string& str); + util::StatusOr GetBoolFromString(const std::string& str); + + // Different types of variables, as defined in the MPS file specification. + // Note these are more precise than the ones in PrimalSimplex. + enum BoundTypeId { + UNKNOWN_BOUND_TYPE, + LOWER_BOUND, + UPPER_BOUND, + FIXED_VARIABLE, + FREE_VARIABLE, + NEGATIVE, + POSITIVE, + BINARY + }; + + // Different types of constraints for a given row. + enum RowTypeId { + UNKNOWN_ROW_TYPE, + EQUALITY, + LESS_THAN, + GREATER_THAN, + OBJECTIVE, + NONE + }; + + // Stores a bound value of a given type, for a given column name. + template + util::Status StoreBound(const std::string& bound_type_mnemonic, + const std::string& column_name, + const std::string& bound_value, DataWrapper* data); + + // Stores a coefficient value for a column number and a row name. + template + util::Status StoreCoefficient(int col, const std::string& row_name, + const std::string& row_value, + DataWrapper* data); + + // Stores a right-hand-side value for a row name. + template + util::Status StoreRightHandSide(const std::string& row_name, + const std::string& row_value, + DataWrapper* data); + + // Stores a range constraint of value row_value for a row name. + template + util::Status StoreRange(const std::string& row_name, + const std::string& range_value, DataWrapper* data); + + // Returns an InvalidArgumentError with the given error message, postfixed by + // the current line of the .mps file (number and contents). + util::Status InvalidArgumentError(const std::string& error_message); + + // Appends the current line of the .mps file (number and contents) to the + // status if it's an error message. + // util::Status AppendLineToError(const util::Status& status); + + // Boolean set to true if the reader expects a free-form MPS file. + bool free_form_; + + // Storage of the fields for a line of the MPS file. + std::vector fields_; + + // Stores the name of the objective row. + std::string objective_name_; + + // Enum for section ids. + typedef enum { + UNKNOWN_SECTION, + COMMENT, + NAME, + ROWS, + LAZYCONS, + COLUMNS, + RHS, + RANGES, + BOUNDS, + INDICATORS, + SOS, + ENDATA + } SectionId; + + // Id of the current section of MPS file. + SectionId section_; + + // Maps section mnemonic --> section id. + absl::flat_hash_map section_name_to_id_map_; + + // Maps row type mnemonic --> row type id. + absl::flat_hash_map row_name_to_id_map_; + + // Maps bound type mnemonic --> bound type id. + absl::flat_hash_map bound_name_to_id_map_; + + // Set of bound type mnemonics that constrain variables to be integer. + absl::flat_hash_set integer_type_names_set_; + + // The current line number in the file being parsed. + int64 line_num_; + + // The current line in the file being parsed. + std::string line_; + + // A row of Booleans. is_binary_by_default_[col] is true if col + // appeared within a scope started by INTORG and ended with INTEND markers. + std::vector is_binary_by_default_; + + // True if the next variable has to be interpreted as an integer variable. + // This is used to support the marker INTORG that starts an integer section + // and INTEND that ends it. + bool in_integer_section_; + + // We keep track of the number of unconstrained rows so we can display it to + // the user because other solvers usually ignore them and we don't (they will + // be removed in the preprocessor). + int num_unconstrained_rows_; + + DISALLOW_COPY_AND_ASSIGN(MPSReaderAux); +}; + +// Data templates. + +template +class DataWrapper {}; + +template <> +class DataWrapper { + public: + explicit DataWrapper(LinearProgram* data) { data_ = data; } + + void SetUp() { + data_->SetDcheckBounds(false); + data_->Clear(); + } + + void SetName(const std::string& name) { data_->SetName(name); } + + int FindOrCreateConstraint(const std::string& name) { + return data_->FindOrCreateConstraint(name).value(); + } + void SetConstraintBounds(int index, double lower_bound, double upper_bound) { + data_->SetConstraintBounds(RowIndex(index), lower_bound, upper_bound); + } + void SetConstraintCoefficient(int row_index, int col_index, + double coefficient) { + data_->SetCoefficient(RowIndex(row_index), ColIndex(col_index), + coefficient); + } + void SetIsLazy(int row_index) { + LOG_FIRST_N(WARNING, 1) + << "LAZYCONS section detected. It will be handled as an extension of " + "the ROWS section."; + } + double ConstraintLowerBound(int row_index) { + return data_->constraint_lower_bounds()[RowIndex(row_index)]; + } + double ConstraintUpperBound(int row_index) { + return data_->constraint_upper_bounds()[RowIndex(row_index)]; + } + + int FindOrCreateVariable(const std::string& name) { + return data_->FindOrCreateVariable(name).value(); + } + void SetVariableTypeToInteger(int index) { + data_->SetVariableType(ColIndex(index), + LinearProgram::VariableType::INTEGER); + } + void SetVariableBounds(int index, double lower_bound, double upper_bound) { + data_->SetVariableBounds(ColIndex(index), lower_bound, upper_bound); + } + void SetObjectiveCoefficient(int index, double coefficient) { + data_->SetObjectiveCoefficient(ColIndex(index), coefficient); + } + bool VariableIsInteger(int index) { + return data_->IsVariableInteger(ColIndex(index)); + } + double VariableLowerBound(int index) { + return data_->variable_lower_bounds()[ColIndex(index)]; + } + double VariableUpperBound(int index) { + return data_->variable_upper_bounds()[ColIndex(index)]; + } + + util::Status CreateIndicatorConstraint(std::string row_name, int col_index, + bool col_value) { + return util::UnimplementedError( + "LinearProgram does not support indicator constraints."); + } + + void CleanUp() { data_->CleanUp(); } + + private: + LinearProgram* data_; +}; + +template <> +class DataWrapper { + public: + explicit DataWrapper(MPModelProto* data) { data_ = data; } + + void SetUp() { data_->Clear(); } + + void SetName(const std::string& name) { data_->set_name(name); } + + int FindOrCreateConstraint(const std::string& name) { + const auto it = constraint_indices_by_name_.find(name); + if (it != constraint_indices_by_name_.end()) return it->second; + + const int index = data_->constraint_size(); + MPConstraintProto* const constraint = data_->add_constraint(); + constraint->set_lower_bound(0.0); + constraint->set_upper_bound(0.0); + constraint->set_name(name); + constraint_indices_by_name_[name] = index; + return index; + } + void SetConstraintBounds(int index, double lower_bound, double upper_bound) { + data_->mutable_constraint(index)->set_lower_bound(lower_bound); + data_->mutable_constraint(index)->set_upper_bound(upper_bound); + } + void SetConstraintCoefficient(int row_index, int col_index, + double coefficient) { + MPConstraintProto* const constraint = data_->mutable_constraint(row_index); + for (int i = 0; i < constraint->var_index_size(); ++i) { + if (constraint->var_index(i) == col_index) { + constraint->set_coefficient(i, coefficient); + return; + } + } + constraint->add_var_index(col_index); + constraint->add_coefficient(coefficient); + } + void SetIsLazy(int row_index) { + data_->mutable_constraint(row_index)->set_is_lazy(true); + } + double ConstraintLowerBound(int row_index) { + return data_->constraint(row_index).lower_bound(); + } + double ConstraintUpperBound(int row_index) { + return data_->constraint(row_index).upper_bound(); + } + + int FindOrCreateVariable(const std::string& name) { + const auto it = variable_indices_by_name_.find(name); + if (it != variable_indices_by_name_.end()) return it->second; + + const int index = data_->variable_size(); + MPVariableProto* const variable = data_->add_variable(); + variable->set_lower_bound(0.0); + variable->set_name(name); + variable_indices_by_name_[name] = index; + return index; + } + void SetVariableTypeToInteger(int index) { + data_->mutable_variable(index)->set_is_integer(true); + } + void SetVariableBounds(int index, double lower_bound, double upper_bound) { + data_->mutable_variable(index)->set_lower_bound(lower_bound); + data_->mutable_variable(index)->set_upper_bound(upper_bound); + } + void SetObjectiveCoefficient(int index, double coefficient) { + data_->mutable_variable(index)->set_objective_coefficient(coefficient); + } + bool VariableIsInteger(int index) { + return data_->variable(index).is_integer(); + } + double VariableLowerBound(int index) { + return data_->variable(index).lower_bound(); + } + double VariableUpperBound(int index) { + return data_->variable(index).upper_bound(); + } + + util::Status CreateIndicatorConstraint(std::string cst_name, int var_index, + bool var_value) { + const auto it = constraint_indices_by_name_.find(cst_name); + if (it == constraint_indices_by_name_.end()) { + return util::InvalidArgumentError( + absl::StrCat("Constraint \"", cst_name, "\" doesn't exist.")); + } + const int cst_index = it->second; + + MPGeneralConstraintProto* const constraint = + data_->add_general_constraint(); + constraint->set_name( + absl::StrCat("ind_", data_->constraint(cst_index).name())); + MPIndicatorConstraint* const indicator = + constraint->mutable_indicator_constraint(); + *indicator->mutable_constraint() = data_->constraint(cst_index); + indicator->set_var_index(var_index); + indicator->set_var_value(var_value); + constraints_to_delete_.insert(cst_index); + + return util::OkStatus(); + } + + void CleanUp() { + google::protobuf::util::RemoveAt(data_->mutable_constraint(), + constraints_to_delete_); + } + + private: + MPModelProto* data_; + + absl::flat_hash_map variable_indices_by_name_; + absl::flat_hash_map constraint_indices_by_name_; + std::set constraints_to_delete_; +}; + +template +util::Status MPSReaderAux::ParseFile(const std::string& file_name, Data* data, + MPSReader::Form form) { + if (data == nullptr) { + return util::InvalidArgumentError("NULL pointer passed as argument."); + } + + if (form == MPSReader::AUTO_DETECT) { + if (ParseFile(file_name, data, MPSReader::FIXED).ok()) { + return util::OkStatus(); + } + return ParseFile(file_name, data, MPSReader::FREE); + } + + // TODO(user): Use the form directly. + free_form_ = form == MPSReader::FREE; + Reset(); + DataWrapper data_wrapper(data); + data_wrapper.SetUp(); + for (const std::string& line : + FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) { + RETURN_IF_ERROR(ProcessLine(line, &data_wrapper)); + } + data_wrapper.CleanUp(); + DisplaySummary(); + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::ProcessLine(const std::string& line, + DataWrapper* data) { + ++line_num_; + line_ = line; + if (IsCommentOrBlank()) { + return util::OkStatus(); // Skip blank lines and comments. + } + if (!free_form_ && line_.find('\t') != std::string::npos) { + return InvalidArgumentError("File contains tabs."); + } + std::string section; + if (line[0] != '\0' && line[0] != ' ') { + section = GetFirstWord(); + section_ = + gtl::FindWithDefault(section_name_to_id_map_, section, UNKNOWN_SECTION); + if (section_ == UNKNOWN_SECTION) { + return InvalidArgumentError("Unknown section."); + } + if (section_ == COMMENT) { + return util::OkStatus(); + } + if (section_ == NAME) { + RETURN_IF_ERROR(SplitLineIntoFields()); + // NOTE(user): The name may differ between fixed and free forms. In + // fixed form, the name has at most 8 characters, and starts at a specific + // position in the NAME line. For MIPLIB2010 problems (eg, air04, glass4), + // the name in fixed form ends up being preceded with a whitespace. + // TODO(user,user): Return an error for fixed form if the problem name + // does not fit. + if (free_form_) { + if (fields_.size() >= 2) { + data->SetName(fields_[1]); + } + } else { + const std::vector free_fields = + absl::StrSplit(line_, absl::ByAnyChar(" \t"), absl::SkipEmpty()); + const std::string free_name = + free_fields.size() >= 2 ? free_fields[1] : ""; + const std::string fixed_name = fields_.size() >= 3 ? fields_[2] : ""; + if (free_name != fixed_name) { + return InvalidArgumentError( + "Fixed form invalid: name differs between free and fixed " + "forms."); + } + data->SetName(fixed_name); + } + } + return util::OkStatus(); + } + RETURN_IF_ERROR(SplitLineIntoFields()); + switch (section_) { + case NAME: + return InvalidArgumentError("Second NAME field."); + case ROWS: + return ProcessRowsSection(/*is_lazy=*/false, data); + case LAZYCONS: + return ProcessRowsSection(/*is_lazy=*/true, data); + case COLUMNS: + return ProcessColumnsSection(data); + case RHS: + return ProcessRhsSection(data); + case RANGES: + return ProcessRangesSection(data); + case BOUNDS: + return ProcessBoundsSection(data); + case INDICATORS: + return ProcessIndicatorsSection(data); + case SOS: + return ProcessSosSection(); + case ENDATA: // Do nothing. + break; + default: + return InvalidArgumentError("Unknown section."); + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::ProcessRowsSection(bool is_lazy, DataWrapper* data) { + if (fields_.size() < 2) { + return InvalidArgumentError("Not enough fields in ROWS section."); + } + const std::string row_type_name = fields_[0]; + const std::string row_name = fields_[1]; + RowTypeId row_type = gtl::FindWithDefault(row_name_to_id_map_, row_type_name, + UNKNOWN_ROW_TYPE); + if (row_type == UNKNOWN_ROW_TYPE) { + return InvalidArgumentError("Unknown row type."); + } + + // The first NONE constraint is used as the objective. + if (objective_name_.empty() && row_type == NONE) { + row_type = OBJECTIVE; + objective_name_ = row_name; + } else { + if (row_type == NONE) { + ++num_unconstrained_rows_; + } + const int row = data->FindOrCreateConstraint(row_name); + if (is_lazy) data->SetIsLazy(row); + + // The initial row range is [0, 0]. We encode the type in the range by + // setting one of the bounds to +/- infinity. + switch (row_type) { + case LESS_THAN: + data->SetConstraintBounds(row, -kInfinity, + data->ConstraintUpperBound(row)); + break; + case GREATER_THAN: + data->SetConstraintBounds(row, data->ConstraintLowerBound(row), + kInfinity); + break; + case NONE: + data->SetConstraintBounds(row, -kInfinity, kInfinity); + break; + case EQUALITY: + default: + break; + } + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::ProcessColumnsSection(DataWrapper* data) { + // Take into account the INTORG and INTEND markers. + if (line_.find("'MARKER'") != std::string::npos) { + if (line_.find("'INTORG'") != std::string::npos) { + VLOG(2) << "Entering integer marker.\n" << line_; + if (in_integer_section_) { + return InvalidArgumentError("Found INTORG inside the integer section."); + } + in_integer_section_ = true; + } else if (line_.find("'INTEND'") != std::string::npos) { + VLOG(2) << "Leaving integer marker.\n" << line_; + if (!in_integer_section_) { + return InvalidArgumentError( + "Found INTEND without corresponding INTORG."); + } + in_integer_section_ = false; + } + return util::OkStatus(); + } + const int start_index = free_form_ ? 0 : 1; + if (fields_.size() < start_index + 3) { + return InvalidArgumentError("Not enough fields in COLUMNS section."); + } + const std::string& column_name = GetField(start_index, 0); + const std::string& row1_name = GetField(start_index, 1); + const std::string& row1_value = GetField(start_index, 2); + const int col = data->FindOrCreateVariable(column_name); + is_binary_by_default_.resize(col + 1, false); + if (in_integer_section_) { + data->SetVariableTypeToInteger(col); + // The default bounds for integer variables are [0, 1]. + data->SetVariableBounds(col, 0.0, 1.0); + is_binary_by_default_[col] = true; + } else { + data->SetVariableBounds(col, 0.0, kInfinity); + } + RETURN_IF_ERROR(StoreCoefficient(col, row1_name, row1_value, data)); + if (fields_.size() == start_index + 4) { + return InvalidArgumentError("Unexpected number of fields."); + } + if (fields_.size() - start_index > 4) { + const std::string& row2_name = GetField(start_index, 3); + const std::string& row2_value = GetField(start_index, 4); + RETURN_IF_ERROR(StoreCoefficient(col, row2_name, row2_value, data)); + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::ProcessRhsSection(DataWrapper* data) { + const int start_index = free_form_ ? 0 : 2; + const int offset = start_index + GetFieldOffset(); + if (fields_.size() < offset + 2) { + return InvalidArgumentError("Not enough fields in RHS section."); + } + // const std::string& rhs_name = fields_[0]; is not used + const std::string& row1_name = GetField(offset, 0); + const std::string& row1_value = GetField(offset, 1); + RETURN_IF_ERROR(StoreRightHandSide(row1_name, row1_value, data)); + if (fields_.size() - start_index >= 4) { + const std::string& row2_name = GetField(offset, 2); + const std::string& row2_value = GetField(offset, 3); + RETURN_IF_ERROR(StoreRightHandSide(row2_name, row2_value, data)); + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::ProcessRangesSection(DataWrapper* data) { + const int start_index = free_form_ ? 0 : 2; + const int offset = start_index + GetFieldOffset(); + if (fields_.size() < offset + 2) { + return InvalidArgumentError("Not enough fields in RHS section."); + } + // const std::string& range_name = fields_[0]; is not used + const std::string& row1_name = GetField(offset, 0); + const std::string& row1_value = GetField(offset, 1); + RETURN_IF_ERROR(StoreRange(row1_name, row1_value, data)); + if (fields_.size() - start_index >= 4) { + const std::string& row2_name = GetField(offset, 2); + const std::string& row2_value = GetField(offset, 3); + RETURN_IF_ERROR(StoreRange(row2_name, row2_value, data)); + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::ProcessBoundsSection(DataWrapper* data) { + if (fields_.size() < 3) { + return InvalidArgumentError("Not enough fields in BOUNDS section."); + } + const std::string bound_type_mnemonic = fields_[0]; + const std::string bound_row_name = fields_[1]; + const std::string column_name = fields_[2]; + std::string bound_value; + if (fields_.size() >= 4) { + bound_value = fields_[3]; + } + return StoreBound(bound_type_mnemonic, column_name, bound_value, data); +} + +template +util::Status MPSReaderAux::ProcessIndicatorsSection(DataWrapper* data) { + // TODO(user): Enforce section order. This section must come after + // anything related to constraints, or we'll have partial data inside the + // indicator constraints. + if (fields_.size() < 4) { + return InvalidArgumentError("Not enough fields in INDICATORS section."); + } + + const std::string type = fields_[0]; + if (type != "IF") { + return InvalidArgumentError( + "Indicator constraints must start with \"IF\"."); + } + const std::string row_name = fields_[1]; + const std::string column_name = fields_[2]; + const std::string column_value = fields_[3]; + + bool value; + ASSIGN_OR_RETURN(value, GetBoolFromString(column_value)); + + const int col = data->FindOrCreateVariable(column_name); + // Variables used in indicator constraints become Boolean by default. + data->SetVariableTypeToInteger(col); + data->SetVariableBounds(col, std::max(0.0, data->VariableLowerBound(col)), + std::min(1.0, data->VariableUpperBound(col))); + + RETURN_IF_ERROR(data->CreateIndicatorConstraint(row_name, col, value)); + + // RETURN_IF_ERROR( + // AppendLineToError(data->CreateIndicatorConstraint(row_name, col, value))); + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::StoreCoefficient(int col, const std::string& row_name, + const std::string& row_value, + DataWrapper* data) { + if (row_name.empty() || row_name == "$") { + return util::OkStatus(); + } + double value; + ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value)); + if (value == kInfinity || value == -kInfinity) { + return InvalidArgumentError("Constraint coefficients cannot be infinity."); + } + if (value == 0.0) return util::OkStatus(); + if (row_name == objective_name_) { + data->SetObjectiveCoefficient(col, value); + } else { + const int row = data->FindOrCreateConstraint(row_name); + data->SetConstraintCoefficient(row, col, value); + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::StoreRightHandSide(const std::string& row_name, + const std::string& row_value, + DataWrapper* data) { + if (row_name.empty()) return util::OkStatus(); + + if (row_name != objective_name_) { + const int row = data->FindOrCreateConstraint(row_name); + Fractional value; + ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value)); + + // The row type is encoded in the bounds, so at this point we have either + // (-kInfinity, 0.0], [0.0, 0.0] or [0.0, kInfinity). We use the right + // hand side to change any finite bound. + const Fractional lower_bound = + (data->ConstraintLowerBound(row) == -kInfinity) ? -kInfinity : value; + const Fractional upper_bound = + (data->ConstraintUpperBound(row) == kInfinity) ? kInfinity : value; + data->SetConstraintBounds(row, lower_bound, upper_bound); + } + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::StoreRange(const std::string& row_name, + const std::string& range_value, + DataWrapper* data) { + if (row_name.empty()) return util::OkStatus(); + + const int row = data->FindOrCreateConstraint(row_name); + Fractional range; + ASSIGN_OR_RETURN(range, GetDoubleFromString(range_value)); + + Fractional lower_bound = data->ConstraintLowerBound(row); + Fractional upper_bound = data->ConstraintUpperBound(row); + if (lower_bound == upper_bound) { + if (range < 0.0) { + lower_bound += range; + } else { + upper_bound += range; + } + } + if (lower_bound == -kInfinity) { + lower_bound = upper_bound - fabs(range); + } + if (upper_bound == kInfinity) { + upper_bound = lower_bound + fabs(range); + } + data->SetConstraintBounds(row, lower_bound, upper_bound); + return util::OkStatus(); +} + +template +util::Status MPSReaderAux::StoreBound(const std::string& bound_type_mnemonic, + const std::string& column_name, + const std::string& bound_value, + DataWrapper* data) { + const BoundTypeId bound_type_id = gtl::FindWithDefault( + bound_name_to_id_map_, bound_type_mnemonic, UNKNOWN_BOUND_TYPE); + if (bound_type_id == UNKNOWN_BOUND_TYPE) { + return InvalidArgumentError("Unknown bound type."); + } + const int col = data->FindOrCreateVariable(column_name); + if (integer_type_names_set_.count(bound_type_mnemonic) != 0) { + data->SetVariableTypeToInteger(col); + } + // Resize the is_binary_by_default_ in case it is the first time this column + // is encountered. + is_binary_by_default_.resize(col + 1, false); + // Check that "binary by default" implies "integer". + DCHECK(!is_binary_by_default_[col] || data->VariableIsInteger(col)); + Fractional lower_bound = data->VariableLowerBound(col); + Fractional upper_bound = data->VariableUpperBound(col); + // If a variable is binary by default, its status is reset if any bound + // is set on it. We take care to restore the default bounds for general + // integer variables. + if (is_binary_by_default_[col]) { + lower_bound = Fractional(0.0); + upper_bound = kInfinity; + } + switch (bound_type_id) { + case LOWER_BOUND: { + ASSIGN_OR_RETURN(lower_bound, GetDoubleFromString(bound_value)); + // LI with the value 0.0 specifies general integers with no upper bound. + if (bound_type_mnemonic == "LI" && lower_bound == 0.0) { + upper_bound = kInfinity; + } + break; + } + case UPPER_BOUND: { + ASSIGN_OR_RETURN(upper_bound, GetDoubleFromString(bound_value)); + break; + } + case FIXED_VARIABLE: { + ASSIGN_OR_RETURN(lower_bound, GetDoubleFromString(bound_value)); + upper_bound = lower_bound; + break; + } + case FREE_VARIABLE: + lower_bound = -kInfinity; + upper_bound = +kInfinity; + break; + case NEGATIVE: + lower_bound = -kInfinity; + upper_bound = Fractional(0.0); + break; + case POSITIVE: + lower_bound = Fractional(0.0); + upper_bound = +kInfinity; + break; + case BINARY: + lower_bound = Fractional(0.0); + upper_bound = Fractional(1.0); + break; + case UNKNOWN_BOUND_TYPE: + default: + return InvalidArgumentError("Unknown bound type."); + } + is_binary_by_default_[col] = false; + data->SetVariableBounds(col, lower_bound, upper_bound); + return util::OkStatus(); +} + +const int MPSReaderAux::kNumFields = 6; +const int MPSReaderAux::kFieldStartPos[kNumFields] = {1, 4, 14, 24, 39, 49}; +const int MPSReaderAux::kFieldLength[kNumFields] = {2, 8, 8, 12, 8, 12}; +const int MPSReaderAux::kSpacePos[12] = {12, 13, 22, 23, 36, 37, 38, 47, 48, 61, 62, 63}; -MPSReader::MPSReader() +MPSReaderAux::MPSReaderAux() : free_form_(true), fields_(kNumFields), section_(UNKNOWN_SECTION), @@ -67,7 +896,7 @@ MPSReader::MPSReader() integer_type_names_set_.insert("UI"); } -void MPSReader::Reset() { +void MPSReaderAux::Reset() { fields_.resize(kNumFields); line_num_ = 0; in_integer_section_ = false; @@ -75,7 +904,7 @@ void MPSReader::Reset() { objective_name_.clear(); } -void MPSReader::DisplaySummary() { +void MPSReaderAux::DisplaySummary() { if (num_unconstrained_rows_ > 0) { VLOG(1) << "There are " << num_unconstrained_rows_ + 1 << " unconstrained rows. The first of them (" << objective_name_ @@ -83,7 +912,7 @@ void MPSReader::DisplaySummary() { } } -bool MPSReader::IsFixedFormat() { +bool MPSReaderAux::IsFixedFormat() { for (const int i : kSpacePos) { if (i >= line_.length()) break; if (line_[i] != ' ') return false; @@ -91,7 +920,7 @@ bool MPSReader::IsFixedFormat() { return true; } -util::Status MPSReader::SplitLineIntoFields() { +util::Status MPSReaderAux::SplitLineIntoFields() { if (free_form_) { fields_ = absl::StrSplit(line_, absl::ByAnyChar(" \t"), absl::SkipEmpty()); if (fields_.size() > kNumFields) { @@ -118,7 +947,7 @@ util::Status MPSReader::SplitLineIntoFields() { return util::OkStatus(); } -std::string MPSReader::GetFirstWord() const { +std::string MPSReaderAux::GetFirstWord() const { if (line_[0] == ' ') { return std::string(""); } @@ -127,7 +956,7 @@ std::string MPSReader::GetFirstWord() const { return first_word; } -bool MPSReader::IsCommentOrBlank() const { +bool MPSReaderAux::IsCommentOrBlank() const { const char* line = line_.c_str(); if (*line == '*') { return true; @@ -140,7 +969,7 @@ bool MPSReader::IsCommentOrBlank() const { return true; } -util::StatusOr MPSReader::GetDoubleFromString(const std::string& str) { +util::StatusOr MPSReaderAux::GetDoubleFromString(const std::string& str) { double result; if (!absl::SimpleAtod(str, &result)) { return InvalidArgumentError( @@ -152,7 +981,7 @@ util::StatusOr MPSReader::GetDoubleFromString(const std::string& str) { return result; } -util::StatusOr MPSReader::GetBoolFromString(const std::string& str) { +util::StatusOr MPSReaderAux::GetBoolFromString(const std::string& str) { int result; if (!absl::SimpleAtoi(str, &result) || result < 0 || result > 1) { return InvalidArgumentError( @@ -164,15 +993,30 @@ util::StatusOr MPSReader::GetBoolFromString(const std::string& str) { return result; } -util::Status MPSReader::ProcessSosSection() { +util::Status MPSReaderAux::ProcessSosSection() { return InvalidArgumentError("Section SOS currently not supported."); } -util::Status MPSReader::InvalidArgumentError(const std::string& error_message) { +util::Status MPSReaderAux::InvalidArgumentError(const std::string& error_message) { return util::InvalidArgumentError(error_message); } -// util::Status MPSReader::AppendLineToError(const util::Status& status) { +// Parses instance from a file. +util::Status MPSReader::ParseFile(const std::string& file_name, + LinearProgram* data, + Form form) { + MPSReaderAux aux_reader; + return aux_reader.ParseFile(file_name, data, form); +} + +util::Status MPSReader::ParseFile(const std::string& file_name, + MPModelProto* data, + Form form) { + MPSReaderAux aux_reader; + return aux_reader.ParseFile(file_name, data, form); +} + +// util::Status MPSReaderAux::AppendLineToError(const util::Status& status) { // return util::StatusBuilder(status, GTL_LOC).SetAppend() // << " Line " << line_num_ << ": \"" << line_ << "\"."; // } diff --git a/ortools/lp_data/mps_reader.h b/ortools/lp_data/mps_reader.h index 1cb88869b5..f6a7888d0b 100644 --- a/ortools/lp_data/mps_reader.h +++ b/ortools/lp_data/mps_reader.h @@ -60,835 +60,19 @@ namespace glop { // All Parse() methods clear the previously parsed instance and store the result // in the given Data class. They return false in case of failure to read the // instance. + class MPSReader { public: enum Form { AUTO_DETECT, FREE, FIXED }; - MPSReader(); - - // Parses instance from a file. We currently support LinearProgram and - // MpModelProto for the Data type, but it should be easy to add more. - template - util::Status ParseFile(const std::string& file_name, Data* data, + // Parses instance from a file. + util::Status ParseFile(const std::string& file_name, LinearProgram* data, Form form = AUTO_DETECT); - private: - // Number of fields in one line of MPS file. - static const int kNumFields; - - // Starting positions of each of the fields for fixed format. - static const int kFieldStartPos[]; - - // Lengths of each of the fields for fixed format. - static const int kFieldLength[]; - - // Positions where there should be spaces for fixed format. - static const int kSpacePos[]; - - // Resets the object to its initial value before reading a new file. - void Reset(); - - // Displays some information on the last loaded file. - void DisplaySummary(); - - // Get each field for a given line. - util::Status SplitLineIntoFields(); - - // Returns true if the line matches the fixed format. - bool IsFixedFormat(); - - // Get the first word in a line. - std::string GetFirstWord() const; - - // Returns true if the line contains a comment (starting with '*') or - // if it is a blank line. - bool IsCommentOrBlank() const; - - // Helper function that returns fields_[offset + index]. - const std::string& GetField(int offset, int index) const { - return fields_[offset + index]; - } - - // Returns the offset at which to start the parsing of fields_. - // If in fixed form, the offset is 0. - // If in fixed form and the number of fields is odd, it is 1, - // otherwise it is 0. - // This is useful when processing RANGES and RHS sections. - int GetFieldOffset() const { return free_form_ ? fields_.size() & 1 : 0; } - - // Line processor. - template - util::Status ProcessLine(const std::string& line, DataWrapper* data); - - // Process section ROWS in the MPS file. - template - util::Status ProcessRowsSection(bool is_lazy, DataWrapper* data); - - // Process section COLUMNS in the MPS file. - template - util::Status ProcessColumnsSection(DataWrapper* data); - - // Process section RHS in the MPS file. - template - util::Status ProcessRhsSection(DataWrapper* data); - - // Process section RANGES in the MPS file. - template - util::Status ProcessRangesSection(DataWrapper* data); - - // Process section BOUNDS in the MPS file. - template - util::Status ProcessBoundsSection(DataWrapper* data); - - // Process section INDICATORS in the MPS file. - template - util::Status ProcessIndicatorsSection(DataWrapper* data); - - // Process section SOS in the MPS file. - util::Status ProcessSosSection(); - - // Safely converts a std::string to a numerical type. Returns an error if the - // std::string passed as parameter is ill-formed. - util::StatusOr GetDoubleFromString(const std::string& str); - util::StatusOr GetBoolFromString(const std::string& str); - - // Different types of variables, as defined in the MPS file specification. - // Note these are more precise than the ones in PrimalSimplex. - enum BoundTypeId { - UNKNOWN_BOUND_TYPE, - LOWER_BOUND, - UPPER_BOUND, - FIXED_VARIABLE, - FREE_VARIABLE, - NEGATIVE, - POSITIVE, - BINARY - }; - - // Different types of constraints for a given row. - enum RowTypeId { - UNKNOWN_ROW_TYPE, - EQUALITY, - LESS_THAN, - GREATER_THAN, - OBJECTIVE, - NONE - }; - - // Stores a bound value of a given type, for a given column name. - template - util::Status StoreBound(const std::string& bound_type_mnemonic, - const std::string& column_name, - const std::string& bound_value, DataWrapper* data); - - // Stores a coefficient value for a column number and a row name. - template - util::Status StoreCoefficient(int col, const std::string& row_name, - const std::string& row_value, - DataWrapper* data); - - // Stores a right-hand-side value for a row name. - template - util::Status StoreRightHandSide(const std::string& row_name, - const std::string& row_value, - DataWrapper* data); - - // Stores a range constraint of value row_value for a row name. - template - util::Status StoreRange(const std::string& row_name, - const std::string& range_value, DataWrapper* data); - - // Returns an InvalidArgumentError with the given error message, postfixed by - // the current line of the .mps file (number and contents). - util::Status InvalidArgumentError(const std::string& error_message); - - // Appends the current line of the .mps file (number and contents) to the - // status if it's an error message. - // util::Status AppendLineToError(const util::Status& status); - - // Boolean set to true if the reader expects a free-form MPS file. - bool free_form_; - - // Storage of the fields for a line of the MPS file. - std::vector fields_; - - // Stores the name of the objective row. - std::string objective_name_; - - // Enum for section ids. - typedef enum { - UNKNOWN_SECTION, - COMMENT, - NAME, - ROWS, - LAZYCONS, - COLUMNS, - RHS, - RANGES, - BOUNDS, - INDICATORS, - SOS, - ENDATA - } SectionId; - - // Id of the current section of MPS file. - SectionId section_; - - // Maps section mnemonic --> section id. - absl::flat_hash_map section_name_to_id_map_; - - // Maps row type mnemonic --> row type id. - absl::flat_hash_map row_name_to_id_map_; - - // Maps bound type mnemonic --> bound type id. - absl::flat_hash_map bound_name_to_id_map_; - - // Set of bound type mnemonics that constrain variables to be integer. - absl::flat_hash_set integer_type_names_set_; - - // The current line number in the file being parsed. - int64 line_num_; - - // The current line in the file being parsed. - std::string line_; - - // A row of Booleans. is_binary_by_default_[col] is true if col - // appeared within a scope started by INTORG and ended with INTEND markers. - std::vector is_binary_by_default_; - - // True if the next variable has to be interpreted as an integer variable. - // This is used to support the marker INTORG that starts an integer section - // and INTEND that ends it. - bool in_integer_section_; - - // We keep track of the number of unconstrained rows so we can display it to - // the user because other solvers usually ignore them and we don't (they will - // be removed in the preprocessor). - int num_unconstrained_rows_; - - DISALLOW_COPY_AND_ASSIGN(MPSReader); + util::Status ParseFile(const std::string& file_name, MPModelProto* data, + Form form = AUTO_DETECT); }; -// Data templates. - -template -class DataWrapper {}; - -template <> -class DataWrapper { - public: - explicit DataWrapper(LinearProgram* data) { data_ = data; } - - void SetUp() { - data_->SetDcheckBounds(false); - data_->Clear(); - } - - void SetName(const std::string& name) { data_->SetName(name); } - - int FindOrCreateConstraint(const std::string& name) { - return data_->FindOrCreateConstraint(name).value(); - } - void SetConstraintBounds(int index, double lower_bound, double upper_bound) { - data_->SetConstraintBounds(RowIndex(index), lower_bound, upper_bound); - } - void SetConstraintCoefficient(int row_index, int col_index, - double coefficient) { - data_->SetCoefficient(RowIndex(row_index), ColIndex(col_index), - coefficient); - } - void SetIsLazy(int row_index) { - LOG_FIRST_N(WARNING, 1) - << "LAZYCONS section detected. It will be handled as an extension of " - "the ROWS section."; - } - double ConstraintLowerBound(int row_index) { - return data_->constraint_lower_bounds()[RowIndex(row_index)]; - } - double ConstraintUpperBound(int row_index) { - return data_->constraint_upper_bounds()[RowIndex(row_index)]; - } - - int FindOrCreateVariable(const std::string& name) { - return data_->FindOrCreateVariable(name).value(); - } - void SetVariableTypeToInteger(int index) { - data_->SetVariableType(ColIndex(index), - LinearProgram::VariableType::INTEGER); - } - void SetVariableBounds(int index, double lower_bound, double upper_bound) { - data_->SetVariableBounds(ColIndex(index), lower_bound, upper_bound); - } - void SetObjectiveCoefficient(int index, double coefficient) { - data_->SetObjectiveCoefficient(ColIndex(index), coefficient); - } - bool VariableIsInteger(int index) { - return data_->IsVariableInteger(ColIndex(index)); - } - double VariableLowerBound(int index) { - return data_->variable_lower_bounds()[ColIndex(index)]; - } - double VariableUpperBound(int index) { - return data_->variable_upper_bounds()[ColIndex(index)]; - } - - util::Status CreateIndicatorConstraint(std::string row_name, int col_index, - bool col_value) { - return util::UnimplementedError( - "LinearProgram does not support indicator constraints."); - } - - void CleanUp() { data_->CleanUp(); } - - private: - LinearProgram* data_; -}; - -template <> -class DataWrapper { - public: - explicit DataWrapper(MPModelProto* data) { data_ = data; } - - void SetUp() { data_->Clear(); } - - void SetName(const std::string& name) { data_->set_name(name); } - - int FindOrCreateConstraint(const std::string& name) { - const auto it = constraint_indices_by_name_.find(name); - if (it != constraint_indices_by_name_.end()) return it->second; - - const int index = data_->constraint_size(); - MPConstraintProto* const constraint = data_->add_constraint(); - constraint->set_lower_bound(0.0); - constraint->set_upper_bound(0.0); - constraint->set_name(name); - constraint_indices_by_name_[name] = index; - return index; - } - void SetConstraintBounds(int index, double lower_bound, double upper_bound) { - data_->mutable_constraint(index)->set_lower_bound(lower_bound); - data_->mutable_constraint(index)->set_upper_bound(upper_bound); - } - void SetConstraintCoefficient(int row_index, int col_index, - double coefficient) { - MPConstraintProto* const constraint = data_->mutable_constraint(row_index); - for (int i = 0; i < constraint->var_index_size(); ++i) { - if (constraint->var_index(i) == col_index) { - constraint->set_coefficient(i, coefficient); - return; - } - } - constraint->add_var_index(col_index); - constraint->add_coefficient(coefficient); - } - void SetIsLazy(int row_index) { - data_->mutable_constraint(row_index)->set_is_lazy(true); - } - double ConstraintLowerBound(int row_index) { - return data_->constraint(row_index).lower_bound(); - } - double ConstraintUpperBound(int row_index) { - return data_->constraint(row_index).upper_bound(); - } - - int FindOrCreateVariable(const std::string& name) { - const auto it = variable_indices_by_name_.find(name); - if (it != variable_indices_by_name_.end()) return it->second; - - const int index = data_->variable_size(); - MPVariableProto* const variable = data_->add_variable(); - variable->set_lower_bound(0.0); - variable->set_name(name); - variable_indices_by_name_[name] = index; - return index; - } - void SetVariableTypeToInteger(int index) { - data_->mutable_variable(index)->set_is_integer(true); - } - void SetVariableBounds(int index, double lower_bound, double upper_bound) { - data_->mutable_variable(index)->set_lower_bound(lower_bound); - data_->mutable_variable(index)->set_upper_bound(upper_bound); - } - void SetObjectiveCoefficient(int index, double coefficient) { - data_->mutable_variable(index)->set_objective_coefficient(coefficient); - } - bool VariableIsInteger(int index) { - return data_->variable(index).is_integer(); - } - double VariableLowerBound(int index) { - return data_->variable(index).lower_bound(); - } - double VariableUpperBound(int index) { - return data_->variable(index).upper_bound(); - } - - util::Status CreateIndicatorConstraint(std::string cst_name, int var_index, - bool var_value) { - const auto it = constraint_indices_by_name_.find(cst_name); - if (it == constraint_indices_by_name_.end()) { - return util::InvalidArgumentError( - absl::StrCat("Constraint \"", cst_name, "\" doesn't exist.")); - } - const int cst_index = it->second; - - MPGeneralConstraintProto* const constraint = - data_->add_general_constraint(); - constraint->set_name( - absl::StrCat("ind_", data_->constraint(cst_index).name())); - MPIndicatorConstraint* const indicator = - constraint->mutable_indicator_constraint(); - *indicator->mutable_constraint() = data_->constraint(cst_index); - indicator->set_var_index(var_index); - indicator->set_var_value(var_value); - constraints_to_delete_.insert(cst_index); - - return util::OkStatus(); - } - - void CleanUp() { - google::protobuf::util::RemoveAt(data_->mutable_constraint(), - constraints_to_delete_); - } - - private: - MPModelProto* data_; - - absl::flat_hash_map variable_indices_by_name_; - absl::flat_hash_map constraint_indices_by_name_; - std::set constraints_to_delete_; -}; - -template -util::Status MPSReader::ParseFile(const std::string& file_name, Data* data, - Form form) { - if (data == nullptr) { - return util::InvalidArgumentError("NULL pointer passed as argument."); - } - - if (form == AUTO_DETECT) { - if (ParseFile(file_name, data, FIXED).ok()) return util::OkStatus(); - return ParseFile(file_name, data, FREE); - } - - // TODO(user): Use the form directly. - free_form_ = form == FREE; - Reset(); - DataWrapper data_wrapper(data); - data_wrapper.SetUp(); - for (const std::string& line : - FileLines(file_name, FileLineIterator::REMOVE_INLINE_CR)) { - RETURN_IF_ERROR(ProcessLine(line, &data_wrapper)); - } - data_wrapper.CleanUp(); - DisplaySummary(); - return util::OkStatus(); -} - -template -util::Status MPSReader::ProcessLine(const std::string& line, - DataWrapper* data) { - ++line_num_; - line_ = line; - if (IsCommentOrBlank()) { - return util::OkStatus(); // Skip blank lines and comments. - } - if (!free_form_ && line_.find('\t') != std::string::npos) { - return InvalidArgumentError("File contains tabs."); - } - std::string section; - if (line[0] != '\0' && line[0] != ' ') { - section = GetFirstWord(); - section_ = - gtl::FindWithDefault(section_name_to_id_map_, section, UNKNOWN_SECTION); - if (section_ == UNKNOWN_SECTION) { - return InvalidArgumentError("Unknown section."); - } - if (section_ == COMMENT) { - return util::OkStatus(); - } - if (section_ == NAME) { - RETURN_IF_ERROR(SplitLineIntoFields()); - // NOTE(user): The name may differ between fixed and free forms. In - // fixed form, the name has at most 8 characters, and starts at a specific - // position in the NAME line. For MIPLIB2010 problems (eg, air04, glass4), - // the name in fixed form ends up being preceded with a whitespace. - // TODO(user,user): Return an error for fixed form if the problem name - // does not fit. - if (free_form_) { - if (fields_.size() >= 2) { - data->SetName(fields_[1]); - } - } else { - const std::vector free_fields = - absl::StrSplit(line_, absl::ByAnyChar(" \t"), absl::SkipEmpty()); - const std::string free_name = - free_fields.size() >= 2 ? free_fields[1] : ""; - const std::string fixed_name = fields_.size() >= 3 ? fields_[2] : ""; - if (free_name != fixed_name) { - return InvalidArgumentError( - "Fixed form invalid: name differs between free and fixed " - "forms."); - } - data->SetName(fixed_name); - } - } - return util::OkStatus(); - } - RETURN_IF_ERROR(SplitLineIntoFields()); - switch (section_) { - case NAME: - return InvalidArgumentError("Second NAME field."); - case ROWS: - return ProcessRowsSection(/*is_lazy=*/false, data); - case LAZYCONS: - return ProcessRowsSection(/*is_lazy=*/true, data); - case COLUMNS: - return ProcessColumnsSection(data); - case RHS: - return ProcessRhsSection(data); - case RANGES: - return ProcessRangesSection(data); - case BOUNDS: - return ProcessBoundsSection(data); - case INDICATORS: - return ProcessIndicatorsSection(data); - case SOS: - return ProcessSosSection(); - case ENDATA: // Do nothing. - break; - default: - return InvalidArgumentError("Unknown section."); - } - return util::OkStatus(); -} - -template -util::Status MPSReader::ProcessRowsSection(bool is_lazy, DataWrapper* data) { - if (fields_.size() < 2) { - return InvalidArgumentError("Not enough fields in ROWS section."); - } - const std::string row_type_name = fields_[0]; - const std::string row_name = fields_[1]; - RowTypeId row_type = gtl::FindWithDefault(row_name_to_id_map_, row_type_name, - UNKNOWN_ROW_TYPE); - if (row_type == UNKNOWN_ROW_TYPE) { - return InvalidArgumentError("Unknown row type."); - } - - // The first NONE constraint is used as the objective. - if (objective_name_.empty() && row_type == NONE) { - row_type = OBJECTIVE; - objective_name_ = row_name; - } else { - if (row_type == NONE) { - ++num_unconstrained_rows_; - } - const int row = data->FindOrCreateConstraint(row_name); - if (is_lazy) data->SetIsLazy(row); - - // The initial row range is [0, 0]. We encode the type in the range by - // setting one of the bounds to +/- infinity. - switch (row_type) { - case LESS_THAN: - data->SetConstraintBounds(row, -kInfinity, - data->ConstraintUpperBound(row)); - break; - case GREATER_THAN: - data->SetConstraintBounds(row, data->ConstraintLowerBound(row), - kInfinity); - break; - case NONE: - data->SetConstraintBounds(row, -kInfinity, kInfinity); - break; - case EQUALITY: - default: - break; - } - } - return util::OkStatus(); -} - -template -util::Status MPSReader::ProcessColumnsSection(DataWrapper* data) { - // Take into account the INTORG and INTEND markers. - if (line_.find("'MARKER'") != std::string::npos) { - if (line_.find("'INTORG'") != std::string::npos) { - VLOG(2) << "Entering integer marker.\n" << line_; - if (in_integer_section_) { - return InvalidArgumentError("Found INTORG inside the integer section."); - } - in_integer_section_ = true; - } else if (line_.find("'INTEND'") != std::string::npos) { - VLOG(2) << "Leaving integer marker.\n" << line_; - if (!in_integer_section_) { - return InvalidArgumentError( - "Found INTEND without corresponding INTORG."); - } - in_integer_section_ = false; - } - return util::OkStatus(); - } - const int start_index = free_form_ ? 0 : 1; - if (fields_.size() < start_index + 3) { - return InvalidArgumentError("Not enough fields in COLUMNS section."); - } - const std::string& column_name = GetField(start_index, 0); - const std::string& row1_name = GetField(start_index, 1); - const std::string& row1_value = GetField(start_index, 2); - const int col = data->FindOrCreateVariable(column_name); - is_binary_by_default_.resize(col + 1, false); - if (in_integer_section_) { - data->SetVariableTypeToInteger(col); - // The default bounds for integer variables are [0, 1]. - data->SetVariableBounds(col, 0.0, 1.0); - is_binary_by_default_[col] = true; - } else { - data->SetVariableBounds(col, 0.0, kInfinity); - } - RETURN_IF_ERROR(StoreCoefficient(col, row1_name, row1_value, data)); - if (fields_.size() == start_index + 4) { - return InvalidArgumentError("Unexpected number of fields."); - } - if (fields_.size() - start_index > 4) { - const std::string& row2_name = GetField(start_index, 3); - const std::string& row2_value = GetField(start_index, 4); - RETURN_IF_ERROR(StoreCoefficient(col, row2_name, row2_value, data)); - } - return util::OkStatus(); -} - -template -util::Status MPSReader::ProcessRhsSection(DataWrapper* data) { - const int start_index = free_form_ ? 0 : 2; - const int offset = start_index + GetFieldOffset(); - if (fields_.size() < offset + 2) { - return InvalidArgumentError("Not enough fields in RHS section."); - } - // const std::string& rhs_name = fields_[0]; is not used - const std::string& row1_name = GetField(offset, 0); - const std::string& row1_value = GetField(offset, 1); - RETURN_IF_ERROR(StoreRightHandSide(row1_name, row1_value, data)); - if (fields_.size() - start_index >= 4) { - const std::string& row2_name = GetField(offset, 2); - const std::string& row2_value = GetField(offset, 3); - RETURN_IF_ERROR(StoreRightHandSide(row2_name, row2_value, data)); - } - return util::OkStatus(); -} - -template -util::Status MPSReader::ProcessRangesSection(DataWrapper* data) { - const int start_index = free_form_ ? 0 : 2; - const int offset = start_index + GetFieldOffset(); - if (fields_.size() < offset + 2) { - return InvalidArgumentError("Not enough fields in RHS section."); - } - // const std::string& range_name = fields_[0]; is not used - const std::string& row1_name = GetField(offset, 0); - const std::string& row1_value = GetField(offset, 1); - RETURN_IF_ERROR(StoreRange(row1_name, row1_value, data)); - if (fields_.size() - start_index >= 4) { - const std::string& row2_name = GetField(offset, 2); - const std::string& row2_value = GetField(offset, 3); - RETURN_IF_ERROR(StoreRange(row2_name, row2_value, data)); - } - return util::OkStatus(); -} - -template -util::Status MPSReader::ProcessBoundsSection(DataWrapper* data) { - if (fields_.size() < 3) { - return InvalidArgumentError("Not enough fields in BOUNDS section."); - } - const std::string bound_type_mnemonic = fields_[0]; - const std::string bound_row_name = fields_[1]; - const std::string column_name = fields_[2]; - std::string bound_value; - if (fields_.size() >= 4) { - bound_value = fields_[3]; - } - return StoreBound(bound_type_mnemonic, column_name, bound_value, data); -} - -template -util::Status MPSReader::ProcessIndicatorsSection(DataWrapper* data) { - // TODO(user): Enforce section order. This section must come after - // anything related to constraints, or we'll have partial data inside the - // indicator constraints. - if (fields_.size() < 4) { - return InvalidArgumentError("Not enough fields in INDICATORS section."); - } - - const std::string type = fields_[0]; - if (type != "IF") { - return InvalidArgumentError( - "Indicator constraints must start with \"IF\"."); - } - const std::string row_name = fields_[1]; - const std::string column_name = fields_[2]; - const std::string column_value = fields_[3]; - - bool value; - ASSIGN_OR_RETURN(value, GetBoolFromString(column_value)); - - const int col = data->FindOrCreateVariable(column_name); - // Variables used in indicator constraints become Boolean by default. - data->SetVariableTypeToInteger(col); - data->SetVariableBounds(col, std::max(0.0, data->VariableLowerBound(col)), - std::min(1.0, data->VariableUpperBound(col))); - - RETURN_IF_ERROR(data->CreateIndicatorConstraint(row_name, col, value)); - - // RETURN_IF_ERROR( - // AppendLineToError(data->CreateIndicatorConstraint(row_name, col, value))); - return util::OkStatus(); -} - -template -util::Status MPSReader::StoreCoefficient(int col, const std::string& row_name, - const std::string& row_value, - DataWrapper* data) { - if (row_name.empty() || row_name == "$") { - return util::OkStatus(); - } - double value; - ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value)); - if (value == kInfinity || value == -kInfinity) { - return InvalidArgumentError("Constraint coefficients cannot be infinity."); - } - if (value == 0.0) return util::OkStatus(); - if (row_name == objective_name_) { - data->SetObjectiveCoefficient(col, value); - } else { - const int row = data->FindOrCreateConstraint(row_name); - data->SetConstraintCoefficient(row, col, value); - } - return util::OkStatus(); -} - -template -util::Status MPSReader::StoreRightHandSide(const std::string& row_name, - const std::string& row_value, - DataWrapper* data) { - if (row_name.empty()) return util::OkStatus(); - - if (row_name != objective_name_) { - const int row = data->FindOrCreateConstraint(row_name); - Fractional value; - ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value)); - - // The row type is encoded in the bounds, so at this point we have either - // (-kInfinity, 0.0], [0.0, 0.0] or [0.0, kInfinity). We use the right - // hand side to change any finite bound. - const Fractional lower_bound = - (data->ConstraintLowerBound(row) == -kInfinity) ? -kInfinity : value; - const Fractional upper_bound = - (data->ConstraintUpperBound(row) == kInfinity) ? kInfinity : value; - data->SetConstraintBounds(row, lower_bound, upper_bound); - } - return util::OkStatus(); -} - -template -util::Status MPSReader::StoreRange(const std::string& row_name, - const std::string& range_value, - DataWrapper* data) { - if (row_name.empty()) return util::OkStatus(); - - const int row = data->FindOrCreateConstraint(row_name); - Fractional range; - ASSIGN_OR_RETURN(range, GetDoubleFromString(range_value)); - - Fractional lower_bound = data->ConstraintLowerBound(row); - Fractional upper_bound = data->ConstraintUpperBound(row); - if (lower_bound == upper_bound) { - if (range < 0.0) { - lower_bound += range; - } else { - upper_bound += range; - } - } - if (lower_bound == -kInfinity) { - lower_bound = upper_bound - fabs(range); - } - if (upper_bound == kInfinity) { - upper_bound = lower_bound + fabs(range); - } - data->SetConstraintBounds(row, lower_bound, upper_bound); - return util::OkStatus(); -} - -template -util::Status MPSReader::StoreBound(const std::string& bound_type_mnemonic, - const std::string& column_name, - const std::string& bound_value, - DataWrapper* data) { - const BoundTypeId bound_type_id = gtl::FindWithDefault( - bound_name_to_id_map_, bound_type_mnemonic, UNKNOWN_BOUND_TYPE); - if (bound_type_id == UNKNOWN_BOUND_TYPE) { - return InvalidArgumentError("Unknown bound type."); - } - const int col = data->FindOrCreateVariable(column_name); - if (integer_type_names_set_.count(bound_type_mnemonic) != 0) { - data->SetVariableTypeToInteger(col); - } - // Resize the is_binary_by_default_ in case it is the first time this column - // is encountered. - is_binary_by_default_.resize(col + 1, false); - // Check that "binary by default" implies "integer". - DCHECK(!is_binary_by_default_[col] || data->VariableIsInteger(col)); - Fractional lower_bound = data->VariableLowerBound(col); - Fractional upper_bound = data->VariableUpperBound(col); - // If a variable is binary by default, its status is reset if any bound - // is set on it. We take care to restore the default bounds for general - // integer variables. - if (is_binary_by_default_[col]) { - lower_bound = Fractional(0.0); - upper_bound = kInfinity; - } - switch (bound_type_id) { - case LOWER_BOUND: { - ASSIGN_OR_RETURN(lower_bound, GetDoubleFromString(bound_value)); - // LI with the value 0.0 specifies general integers with no upper bound. - if (bound_type_mnemonic == "LI" && lower_bound == 0.0) { - upper_bound = kInfinity; - } - break; - } - case UPPER_BOUND: { - ASSIGN_OR_RETURN(upper_bound, GetDoubleFromString(bound_value)); - break; - } - case FIXED_VARIABLE: { - ASSIGN_OR_RETURN(lower_bound, GetDoubleFromString(bound_value)); - upper_bound = lower_bound; - break; - } - case FREE_VARIABLE: - lower_bound = -kInfinity; - upper_bound = +kInfinity; - break; - case NEGATIVE: - lower_bound = -kInfinity; - upper_bound = Fractional(0.0); - break; - case POSITIVE: - lower_bound = Fractional(0.0); - upper_bound = +kInfinity; - break; - case BINARY: - lower_bound = Fractional(0.0); - upper_bound = Fractional(1.0); - break; - case UNKNOWN_BOUND_TYPE: - default: - return InvalidArgumentError("Unknown bound type."); - } - is_binary_by_default_[col] = false; - data->SetVariableBounds(col, lower_bound, upper_bound); - return util::OkStatus(); -} - } // namespace glop } // namespace operations_research