OR-Tools  9.1
mps_reader.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <cstdint>
17 
18 #include "absl/status/status.h"
19 #include "absl/status/statusor.h"
20 #include "absl/strings/match.h"
21 #include "absl/strings/str_split.h"
24 
25 namespace operations_research {
26 namespace glop {
27 
29  public:
30  MPSReaderImpl();
31 
32  // Parses instance from a file. We currently support LinearProgram and
33  // MpModelProto for the Data type, but it should be easy to add more.
34  template <class Data>
35  absl::Status ParseFile(const std::string& file_name, Data* data,
36  MPSReader::Form form);
37 
38  private:
39  // Number of fields in one line of MPS file.
40  static const int kNumFields;
41 
42  // Starting positions of each of the fields for fixed format.
43  static const int kFieldStartPos[];
44 
45  // Lengths of each of the fields for fixed format.
46  static const int kFieldLength[];
47 
48  // Positions where there should be spaces for fixed format.
49  static const int kSpacePos[];
50 
51  // Resets the object to its initial value before reading a new file.
52  void Reset();
53 
54  // Displays some information on the last loaded file.
55  void DisplaySummary();
56 
57  // Get each field for a given line.
58  absl::Status SplitLineIntoFields();
59 
60  // Returns true if the line matches the fixed format.
61  bool IsFixedFormat();
62 
63  // Get the first word in a line.
64  std::string GetFirstWord() const;
65 
66  // Returns true if the line contains a comment (starting with '*') or
67  // if it is a blank line.
68  bool IsCommentOrBlank() const;
69 
70  // Helper function that returns fields_[offset + index].
71  const std::string& GetField(int offset, int index) const {
72  return fields_[offset + index];
73  }
74 
75  // Returns the offset at which to start the parsing of fields_.
76  // If in fixed form, the offset is 0.
77  // If in fixed form and the number of fields is odd, it is 1,
78  // otherwise it is 0.
79  // This is useful when processing RANGES and RHS sections.
80  int GetFieldOffset() const { return free_form_ ? fields_.size() & 1 : 0; }
81 
82  // Line processor.
83  template <class DataWrapper>
84  absl::Status ProcessLine(const std::string& line, DataWrapper* data);
85 
86  // Process section OBJSENSE in MPS file.
87  template <class DataWrapper>
88  absl::Status ProcessObjectiveSenseSection(DataWrapper* data);
89 
90  // Process section ROWS in the MPS file.
91  template <class DataWrapper>
92  absl::Status ProcessRowsSection(bool is_lazy, DataWrapper* data);
93 
94  // Process section COLUMNS in the MPS file.
95  template <class DataWrapper>
96  absl::Status ProcessColumnsSection(DataWrapper* data);
97 
98  // Process section RHS in the MPS file.
99  template <class DataWrapper>
100  absl::Status ProcessRhsSection(DataWrapper* data);
101 
102  // Process section RANGES in the MPS file.
103  template <class DataWrapper>
104  absl::Status ProcessRangesSection(DataWrapper* data);
105 
106  // Process section BOUNDS in the MPS file.
107  template <class DataWrapper>
108  absl::Status ProcessBoundsSection(DataWrapper* data);
109 
110  // Process section INDICATORS in the MPS file.
111  template <class DataWrapper>
112  absl::Status ProcessIndicatorsSection(DataWrapper* data);
113 
114  // Process section SOS in the MPS file.
115  absl::Status ProcessSosSection();
116 
117  // Safely converts a string to a numerical type. Returns an error if the
118  // string passed as parameter is ill-formed.
119  absl::StatusOr<double> GetDoubleFromString(const std::string& str);
120  absl::StatusOr<bool> GetBoolFromString(const std::string& str);
121 
122  // Different types of variables, as defined in the MPS file specification.
123  // Note these are more precise than the ones in PrimalSimplex.
124  enum BoundTypeId {
125  UNKNOWN_BOUND_TYPE,
126  LOWER_BOUND,
127  UPPER_BOUND,
128  FIXED_VARIABLE,
129  FREE_VARIABLE,
130  INFINITE_LOWER_BOUND,
131  INFINITE_UPPER_BOUND,
132  BINARY
133  };
134 
135  // Different types of constraints for a given row.
136  enum RowTypeId {
137  UNKNOWN_ROW_TYPE,
138  EQUALITY,
139  LESS_THAN,
140  GREATER_THAN,
141  OBJECTIVE,
142  NONE
143  };
144 
145  // Stores a bound value of a given type, for a given column name.
146  template <class DataWrapper>
147  absl::Status StoreBound(const std::string& bound_type_mnemonic,
148  const std::string& column_name,
149  const std::string& bound_value, DataWrapper* data);
150 
151  // Stores a coefficient value for a column number and a row name.
152  template <class DataWrapper>
153  absl::Status StoreCoefficient(int col, const std::string& row_name,
154  const std::string& row_value,
155  DataWrapper* data);
156 
157  // Stores a right-hand-side value for a row name.
158  template <class DataWrapper>
159  absl::Status StoreRightHandSide(const std::string& row_name,
160  const std::string& row_value,
161  DataWrapper* data);
162 
163  // Stores a range constraint of value row_value for a row name.
164  template <class DataWrapper>
165  absl::Status StoreRange(const std::string& row_name,
166  const std::string& range_value, DataWrapper* data);
167 
168  // Returns an InvalidArgumentError with the given error message, postfixed by
169  // the current line of the .mps file (number and contents).
170  absl::Status InvalidArgumentError(const std::string& error_message);
171 
172  // Appends the current line of the .mps file (number and contents) to the
173  // status if it's an error message.
174  absl::Status AppendLineToError(const absl::Status& status);
175 
176  // Boolean set to true if the reader expects a free-form MPS file.
177  bool free_form_;
178 
179  // Storage of the fields for a line of the MPS file.
180  std::vector<std::string> fields_;
181 
182  // Stores the name of the objective row.
183  std::string objective_name_;
184 
185  // Enum for section ids.
186  typedef enum {
187  UNKNOWN_SECTION,
188  COMMENT,
189  NAME,
190  OBJSENSE,
191  ROWS,
192  LAZYCONS,
193  COLUMNS,
194  RHS,
195  RANGES,
196  BOUNDS,
197  INDICATORS,
198  SOS,
199  ENDATA
200  } SectionId;
201 
202  // Id of the current section of MPS file.
203  SectionId section_;
204 
205  // Maps section mnemonic --> section id.
206  absl::flat_hash_map<std::string, SectionId> section_name_to_id_map_;
207 
208  // Maps row type mnemonic --> row type id.
209  absl::flat_hash_map<std::string, RowTypeId> row_name_to_id_map_;
210 
211  // Maps bound type mnemonic --> bound type id.
212  absl::flat_hash_map<std::string, BoundTypeId> bound_name_to_id_map_;
213 
214  // Set of bound type mnemonics that constrain variables to be integer.
215  absl::flat_hash_set<std::string> integer_type_names_set_;
216 
217  // The current line number in the file being parsed.
218  int64_t line_num_;
219 
220  // The current line in the file being parsed.
221  std::string line_;
222 
223  // A row of Booleans. is_binary_by_default_[col] is true if col
224  // appeared within a scope started by INTORG and ended with INTEND markers.
225  std::vector<bool> is_binary_by_default_;
226 
227  // True if the next variable has to be interpreted as an integer variable.
228  // This is used to support the marker INTORG that starts an integer section
229  // and INTEND that ends it.
230  bool in_integer_section_;
231 
232  // We keep track of the number of unconstrained rows so we can display it to
233  // the user because other solvers usually ignore them and we don't (they will
234  // be removed in the preprocessor).
235  int num_unconstrained_rows_;
236 
237  DISALLOW_COPY_AND_ASSIGN(MPSReaderImpl);
238 };
239 
240 // Data templates.
241 
242 template <class Data>
243 class DataWrapper {};
244 
245 template <>
247  public:
248  explicit DataWrapper(LinearProgram* data) { data_ = data; }
249 
250  void SetUp() {
251  data_->SetDcheckBounds(false);
252  data_->Clear();
253  }
254 
255  void SetName(const std::string& name) { data_->SetName(name); }
256 
257  void SetObjectiveDirection(bool maximize) {
258  data_->SetMaximizationProblem(maximize);
259  }
260 
261  int FindOrCreateConstraint(const std::string& name) {
262  return data_->FindOrCreateConstraint(name).value();
263  }
264  void SetConstraintBounds(int index, double lower_bound, double upper_bound) {
265  data_->SetConstraintBounds(RowIndex(index), lower_bound, upper_bound);
266  }
267  void SetConstraintCoefficient(int row_index, int col_index,
268  double coefficient) {
269  data_->SetCoefficient(RowIndex(row_index), ColIndex(col_index),
270  coefficient);
271  }
272  void SetIsLazy(int row_index) {
273  LOG_FIRST_N(WARNING, 1)
274  << "LAZYCONS section detected. It will be handled as an extension of "
275  "the ROWS section.";
276  }
277  double ConstraintLowerBound(int row_index) {
278  return data_->constraint_lower_bounds()[RowIndex(row_index)];
279  }
280  double ConstraintUpperBound(int row_index) {
281  return data_->constraint_upper_bounds()[RowIndex(row_index)];
282  }
283 
284  int FindOrCreateVariable(const std::string& name) {
285  return data_->FindOrCreateVariable(name).value();
286  }
288  data_->SetVariableType(ColIndex(index),
290  }
291  void SetVariableBounds(int index, double lower_bound, double upper_bound) {
292  data_->SetVariableBounds(ColIndex(index), lower_bound, upper_bound);
293  }
295  data_->SetObjectiveCoefficient(ColIndex(index), coefficient);
296  }
298  return data_->IsVariableInteger(ColIndex(index));
299  }
300  double VariableLowerBound(int index) {
301  return data_->variable_lower_bounds()[ColIndex(index)];
302  }
303  double VariableUpperBound(int index) {
304  return data_->variable_upper_bounds()[ColIndex(index)];
305  }
306 
307  absl::Status CreateIndicatorConstraint(std::string row_name, int col_index,
308  bool col_value) {
309  return absl::UnimplementedError(
310  "LinearProgram does not support indicator constraints.");
311  }
312 
313  void CleanUp() { data_->CleanUp(); }
314 
315  private:
316  LinearProgram* data_;
317 };
318 
319 template <>
321  public:
322  explicit DataWrapper(MPModelProto* data) { data_ = data; }
323 
324  void SetUp() { data_->Clear(); }
325 
326  void SetName(const std::string& name) { data_->set_name(name); }
327 
328  void SetObjectiveDirection(bool maximize) { data_->set_maximize(maximize); }
329 
330  int FindOrCreateConstraint(const std::string& name) {
331  const auto it = constraint_indices_by_name_.find(name);
332  if (it != constraint_indices_by_name_.end()) return it->second;
333 
334  const int index = data_->constraint_size();
335  MPConstraintProto* const constraint = data_->add_constraint();
336  constraint->set_lower_bound(0.0);
337  constraint->set_upper_bound(0.0);
338  constraint->set_name(name);
339  constraint_indices_by_name_[name] = index;
340  return index;
341  }
342  void SetConstraintBounds(int index, double lower_bound, double upper_bound) {
343  data_->mutable_constraint(index)->set_lower_bound(lower_bound);
344  data_->mutable_constraint(index)->set_upper_bound(upper_bound);
345  }
346  void SetConstraintCoefficient(int row_index, int col_index,
347  double coefficient) {
348  // Note that we assume that there is no duplicate in the mps file format. If
349  // there is, we will just add more than one entry from the same variable in
350  // a constraint, and we let any program that ingests an MPModelProto handle
351  // it.
352  MPConstraintProto* const constraint = data_->mutable_constraint(row_index);
353  constraint->add_var_index(col_index);
354  constraint->add_coefficient(coefficient);
355  }
356  void SetIsLazy(int row_index) {
357  data_->mutable_constraint(row_index)->set_is_lazy(true);
358  }
359  double ConstraintLowerBound(int row_index) {
360  return data_->constraint(row_index).lower_bound();
361  }
362  double ConstraintUpperBound(int row_index) {
363  return data_->constraint(row_index).upper_bound();
364  }
365 
366  int FindOrCreateVariable(const std::string& name) {
367  const auto it = variable_indices_by_name_.find(name);
368  if (it != variable_indices_by_name_.end()) return it->second;
369 
370  const int index = data_->variable_size();
371  MPVariableProto* const variable = data_->add_variable();
372  variable->set_lower_bound(0.0);
373  variable->set_name(name);
374  variable_indices_by_name_[name] = index;
375  return index;
376  }
378  data_->mutable_variable(index)->set_is_integer(true);
379  }
380  void SetVariableBounds(int index, double lower_bound, double upper_bound) {
381  data_->mutable_variable(index)->set_lower_bound(lower_bound);
382  data_->mutable_variable(index)->set_upper_bound(upper_bound);
383  }
385  data_->mutable_variable(index)->set_objective_coefficient(coefficient);
386  }
388  return data_->variable(index).is_integer();
389  }
390  double VariableLowerBound(int index) {
391  return data_->variable(index).lower_bound();
392  }
393  double VariableUpperBound(int index) {
394  return data_->variable(index).upper_bound();
395  }
396 
397  absl::Status CreateIndicatorConstraint(std::string cst_name, int var_index,
398  bool var_value) {
399  const auto it = constraint_indices_by_name_.find(cst_name);
400  if (it == constraint_indices_by_name_.end()) {
401  return absl::InvalidArgumentError(
402  absl::StrCat("Constraint \"", cst_name, "\" doesn't exist."));
403  }
404  const int cst_index = it->second;
405 
406  MPGeneralConstraintProto* const constraint =
407  data_->add_general_constraint();
408  constraint->set_name(
409  absl::StrCat("ind_", data_->constraint(cst_index).name()));
410  MPIndicatorConstraint* const indicator =
411  constraint->mutable_indicator_constraint();
412  *indicator->mutable_constraint() = data_->constraint(cst_index);
413  indicator->set_var_index(var_index);
414  indicator->set_var_value(var_value);
415  constraints_to_delete_.insert(cst_index);
416 
417  return absl::OkStatus();
418  }
419 
420  void CleanUp() {
421  google::protobuf::util::RemoveAt(data_->mutable_constraint(),
422  constraints_to_delete_);
423  }
424 
425  private:
426  MPModelProto* data_;
427 
428  absl::flat_hash_map<std::string, int> variable_indices_by_name_;
429  absl::flat_hash_map<std::string, int> constraint_indices_by_name_;
430  absl::node_hash_set<int> constraints_to_delete_;
431 };
432 
433 template <class Data>
434 absl::Status MPSReaderImpl::ParseFile(const std::string& file_name, Data* data,
435  MPSReader::Form form) {
436  if (data == nullptr) {
437  return absl::InvalidArgumentError("NULL pointer passed as argument.");
438  }
439 
440  if (form == MPSReader::AUTO_DETECT) {
441  if (ParseFile(file_name, data, MPSReader::FIXED).ok()) {
442  return absl::OkStatus();
443  }
444  return ParseFile(file_name, data, MPSReader::FREE);
445  }
446 
447  // TODO(user): Use the form directly.
448  free_form_ = form == MPSReader::FREE;
449  Reset();
450  DataWrapper<Data> data_wrapper(data);
451  data_wrapper.SetUp();
452  for (const std::string& line :
454  RETURN_IF_ERROR(ProcessLine(line, &data_wrapper));
455  }
456  data_wrapper.CleanUp();
457  DisplaySummary();
458  return absl::OkStatus();
459 }
460 
461 template <class DataWrapper>
462 absl::Status MPSReaderImpl::ProcessLine(const std::string& line,
463  DataWrapper* data) {
464  ++line_num_;
465  line_ = line;
466  if (IsCommentOrBlank()) {
467  return absl::OkStatus(); // Skip blank lines and comments.
468  }
469  if (!free_form_ && absl::StrContains(line_, '\t')) {
470  return InvalidArgumentError("File contains tabs.");
471  }
472  std::string section;
473  if (line[0] != '\0' && line[0] != ' ') {
474  section = GetFirstWord();
475  section_ =
476  gtl::FindWithDefault(section_name_to_id_map_, section, UNKNOWN_SECTION);
477  if (section_ == UNKNOWN_SECTION) {
478  return InvalidArgumentError("Unknown section.");
479  }
480  if (section_ == COMMENT) {
481  return absl::OkStatus();
482  }
483  if (section_ == OBJSENSE) {
484  return absl::OkStatus();
485  }
486  if (section_ == NAME) {
487  RETURN_IF_ERROR(SplitLineIntoFields());
488  // NOTE(user): The name may differ between fixed and free forms. In
489  // fixed form, the name has at most 8 characters, and starts at a specific
490  // position in the NAME line. For MIPLIB2010 problems (eg, air04, glass4),
491  // the name in fixed form ends up being preceded with a whitespace.
492  // TODO(user): Return an error for fixed form if the problem name
493  // does not fit.
494  if (free_form_) {
495  if (fields_.size() >= 2) {
496  data->SetName(fields_[1]);
497  }
498  } else {
499  const std::vector<std::string> free_fields =
500  absl::StrSplit(line_, absl::ByAnyChar(" \t"), absl::SkipEmpty());
501  const std::string free_name =
502  free_fields.size() >= 2 ? free_fields[1] : "";
503  const std::string fixed_name = fields_.size() >= 3 ? fields_[2] : "";
504  if (free_name != fixed_name) {
505  return InvalidArgumentError(
506  "Fixed form invalid: name differs between free and fixed "
507  "forms.");
508  }
509  data->SetName(fixed_name);
510  }
511  }
512  return absl::OkStatus();
513  }
514  RETURN_IF_ERROR(SplitLineIntoFields());
515  switch (section_) {
516  case NAME:
517  return InvalidArgumentError("Second NAME field.");
518  case OBJSENSE:
519  return ProcessObjectiveSenseSection(data);
520  case ROWS:
521  return ProcessRowsSection(/*is_lazy=*/false, data);
522  case LAZYCONS:
523  return ProcessRowsSection(/*is_lazy=*/true, data);
524  case COLUMNS:
525  return ProcessColumnsSection(data);
526  case RHS:
527  return ProcessRhsSection(data);
528  case RANGES:
529  return ProcessRangesSection(data);
530  case BOUNDS:
531  return ProcessBoundsSection(data);
532  case INDICATORS:
533  return ProcessIndicatorsSection(data);
534  case SOS:
535  return ProcessSosSection();
536  case ENDATA: // Do nothing.
537  break;
538  default:
539  return InvalidArgumentError("Unknown section.");
540  }
541  return absl::OkStatus();
542 }
543 
544 template <class DataWrapper>
545 absl::Status MPSReaderImpl::ProcessObjectiveSenseSection(DataWrapper* data) {
546  if (fields_.size() != 1 && fields_[0] != "MIN" && fields_[0] != "MAX") {
547  return InvalidArgumentError("Expected objective sense (MAX or MIN).");
548  }
549  data->SetObjectiveDirection(/*maximize=*/fields_[0] == "MAX");
550  return absl::OkStatus();
551 }
552 
553 template <class DataWrapper>
554 absl::Status MPSReaderImpl::ProcessRowsSection(bool is_lazy,
555  DataWrapper* data) {
556  if (fields_.size() < 2) {
557  return InvalidArgumentError("Not enough fields in ROWS section.");
558  }
559  const std::string row_type_name = fields_[0];
560  const std::string row_name = fields_[1];
561  RowTypeId row_type = gtl::FindWithDefault(row_name_to_id_map_, row_type_name,
562  UNKNOWN_ROW_TYPE);
563  if (row_type == UNKNOWN_ROW_TYPE) {
564  return InvalidArgumentError("Unknown row type.");
565  }
566 
567  // The first NONE constraint is used as the objective.
568  if (objective_name_.empty() && row_type == NONE) {
569  row_type = OBJECTIVE;
570  objective_name_ = row_name;
571  } else {
572  if (row_type == NONE) {
573  ++num_unconstrained_rows_;
574  }
575  const int row = data->FindOrCreateConstraint(row_name);
576  if (is_lazy) data->SetIsLazy(row);
577 
578  // The initial row range is [0, 0]. We encode the type in the range by
579  // setting one of the bounds to +/- infinity.
580  switch (row_type) {
581  case LESS_THAN:
582  data->SetConstraintBounds(row, -kInfinity,
583  data->ConstraintUpperBound(row));
584  break;
585  case GREATER_THAN:
586  data->SetConstraintBounds(row, data->ConstraintLowerBound(row),
587  kInfinity);
588  break;
589  case NONE:
590  data->SetConstraintBounds(row, -kInfinity, kInfinity);
591  break;
592  case EQUALITY:
593  default:
594  break;
595  }
596  }
597  return absl::OkStatus();
598 }
599 
600 template <class DataWrapper>
601 absl::Status MPSReaderImpl::ProcessColumnsSection(DataWrapper* data) {
602  // Take into account the INTORG and INTEND markers.
603  if (absl::StrContains(line_, "'MARKER'")) {
604  if (absl::StrContains(line_, "'INTORG'")) {
605  VLOG(2) << "Entering integer marker.\n" << line_;
606  if (in_integer_section_) {
607  return InvalidArgumentError("Found INTORG inside the integer section.");
608  }
609  in_integer_section_ = true;
610  } else if (absl::StrContains(line_, "'INTEND'")) {
611  VLOG(2) << "Leaving integer marker.\n" << line_;
612  if (!in_integer_section_) {
613  return InvalidArgumentError(
614  "Found INTEND without corresponding INTORG.");
615  }
616  in_integer_section_ = false;
617  }
618  return absl::OkStatus();
619  }
620  const int start_index = free_form_ ? 0 : 1;
621  if (fields_.size() < start_index + 3) {
622  return InvalidArgumentError("Not enough fields in COLUMNS section.");
623  }
624  const std::string& column_name = GetField(start_index, 0);
625  const std::string& row1_name = GetField(start_index, 1);
626  const std::string& row1_value = GetField(start_index, 2);
627  const int col = data->FindOrCreateVariable(column_name);
628  is_binary_by_default_.resize(col + 1, false);
629  if (in_integer_section_) {
630  data->SetVariableTypeToInteger(col);
631  // The default bounds for integer variables are [0, 1].
632  data->SetVariableBounds(col, 0.0, 1.0);
633  is_binary_by_default_[col] = true;
634  } else {
635  data->SetVariableBounds(col, 0.0, kInfinity);
636  }
637  RETURN_IF_ERROR(StoreCoefficient(col, row1_name, row1_value, data));
638  if (fields_.size() == start_index + 4) {
639  return InvalidArgumentError("Unexpected number of fields.");
640  }
641  if (fields_.size() - start_index > 4) {
642  const std::string& row2_name = GetField(start_index, 3);
643  const std::string& row2_value = GetField(start_index, 4);
644  RETURN_IF_ERROR(StoreCoefficient(col, row2_name, row2_value, data));
645  }
646  return absl::OkStatus();
647 }
648 
649 template <class DataWrapper>
650 absl::Status MPSReaderImpl::ProcessRhsSection(DataWrapper* data) {
651  const int start_index = free_form_ ? 0 : 2;
652  const int offset = start_index + GetFieldOffset();
653  if (fields_.size() < offset + 2) {
654  return InvalidArgumentError("Not enough fields in RHS section.");
655  }
656  // const std::string& rhs_name = fields_[0]; is not used
657  const std::string& row1_name = GetField(offset, 0);
658  const std::string& row1_value = GetField(offset, 1);
659  RETURN_IF_ERROR(StoreRightHandSide(row1_name, row1_value, data));
660  if (fields_.size() - start_index >= 4) {
661  const std::string& row2_name = GetField(offset, 2);
662  const std::string& row2_value = GetField(offset, 3);
663  RETURN_IF_ERROR(StoreRightHandSide(row2_name, row2_value, data));
664  }
665  return absl::OkStatus();
666 }
667 
668 template <class DataWrapper>
669 absl::Status MPSReaderImpl::ProcessRangesSection(DataWrapper* data) {
670  const int start_index = free_form_ ? 0 : 2;
671  const int offset = start_index + GetFieldOffset();
672  if (fields_.size() < offset + 2) {
673  return InvalidArgumentError("Not enough fields in RHS section.");
674  }
675  // const std::string& range_name = fields_[0]; is not used
676  const std::string& row1_name = GetField(offset, 0);
677  const std::string& row1_value = GetField(offset, 1);
678  RETURN_IF_ERROR(StoreRange(row1_name, row1_value, data));
679  if (fields_.size() - start_index >= 4) {
680  const std::string& row2_name = GetField(offset, 2);
681  const std::string& row2_value = GetField(offset, 3);
682  RETURN_IF_ERROR(StoreRange(row2_name, row2_value, data));
683  }
684  return absl::OkStatus();
685 }
686 
687 template <class DataWrapper>
688 absl::Status MPSReaderImpl::ProcessBoundsSection(DataWrapper* data) {
689  if (fields_.size() < 3) {
690  return InvalidArgumentError("Not enough fields in BOUNDS section.");
691  }
692  const std::string bound_type_mnemonic = fields_[0];
693  const std::string bound_row_name = fields_[1];
694  const std::string column_name = fields_[2];
695  std::string bound_value;
696  if (fields_.size() >= 4) {
697  bound_value = fields_[3];
698  }
699  return StoreBound(bound_type_mnemonic, column_name, bound_value, data);
700 }
701 
702 template <class DataWrapper>
703 absl::Status MPSReaderImpl::ProcessIndicatorsSection(DataWrapper* data) {
704  // TODO(user): Enforce section order. This section must come after
705  // anything related to constraints, or we'll have partial data inside the
706  // indicator constraints.
707  if (fields_.size() < 4) {
708  return InvalidArgumentError("Not enough fields in INDICATORS section.");
709  }
710 
711  const std::string type = fields_[0];
712  if (type != "IF") {
713  return InvalidArgumentError(
714  "Indicator constraints must start with \"IF\".");
715  }
716  const std::string row_name = fields_[1];
717  const std::string column_name = fields_[2];
718  const std::string column_value = fields_[3];
719 
720  bool value;
721  ASSIGN_OR_RETURN(value, GetBoolFromString(column_value));
722 
723  const int col = data->FindOrCreateVariable(column_name);
724  // Variables used in indicator constraints become Boolean by default.
725  data->SetVariableTypeToInteger(col);
726  data->SetVariableBounds(col, std::max(0.0, data->VariableLowerBound(col)),
727  std::min(1.0, data->VariableUpperBound(col)));
728 
730  AppendLineToError(data->CreateIndicatorConstraint(row_name, col, value)));
731 
732  return absl::OkStatus();
733 }
734 
735 template <class DataWrapper>
736 absl::Status MPSReaderImpl::StoreCoefficient(int col,
737  const std::string& row_name,
738  const std::string& row_value,
739  DataWrapper* data) {
740  if (row_name.empty() || row_name == "$") {
741  return absl::OkStatus();
742  }
743 
744  double value;
745  ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value));
746  if (value == kInfinity || value == -kInfinity) {
747  return InvalidArgumentError("Constraint coefficients cannot be infinity.");
748  }
749  if (value == 0.0) return absl::OkStatus();
750  if (row_name == objective_name_) {
751  data->SetObjectiveCoefficient(col, value);
752  } else {
753  const int row = data->FindOrCreateConstraint(row_name);
754  data->SetConstraintCoefficient(row, col, value);
755  }
756  return absl::OkStatus();
757 }
758 
759 template <class DataWrapper>
760 absl::Status MPSReaderImpl::StoreRightHandSide(const std::string& row_name,
761  const std::string& row_value,
762  DataWrapper* data) {
763  if (row_name.empty()) return absl::OkStatus();
764 
765  if (row_name != objective_name_) {
766  const int row = data->FindOrCreateConstraint(row_name);
768  ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value));
769 
770  // The row type is encoded in the bounds, so at this point we have either
771  // (-kInfinity, 0.0], [0.0, 0.0] or [0.0, kInfinity). We use the right
772  // hand side to change any finite bound.
773  const Fractional lower_bound =
774  (data->ConstraintLowerBound(row) == -kInfinity) ? -kInfinity : value;
775  const Fractional upper_bound =
776  (data->ConstraintUpperBound(row) == kInfinity) ? kInfinity : value;
777  data->SetConstraintBounds(row, lower_bound, upper_bound);
778  }
779  return absl::OkStatus();
780 }
781 
782 template <class DataWrapper>
783 absl::Status MPSReaderImpl::StoreRange(const std::string& row_name,
784  const std::string& range_value,
785  DataWrapper* data) {
786  if (row_name.empty()) return absl::OkStatus();
787 
788  const int row = data->FindOrCreateConstraint(row_name);
789  Fractional range;
790  ASSIGN_OR_RETURN(range, GetDoubleFromString(range_value));
791 
792  Fractional lower_bound = data->ConstraintLowerBound(row);
793  Fractional upper_bound = data->ConstraintUpperBound(row);
794  if (lower_bound == upper_bound) {
795  if (range < 0.0) {
796  lower_bound += range;
797  } else {
798  upper_bound += range;
799  }
800  }
801  if (lower_bound == -kInfinity) {
802  lower_bound = upper_bound - fabs(range);
803  }
804  if (upper_bound == kInfinity) {
805  upper_bound = lower_bound + fabs(range);
806  }
807  data->SetConstraintBounds(row, lower_bound, upper_bound);
808  return absl::OkStatus();
809 }
810 
811 template <class DataWrapper>
812 absl::Status MPSReaderImpl::StoreBound(const std::string& bound_type_mnemonic,
813  const std::string& column_name,
814  const std::string& bound_value,
815  DataWrapper* data) {
816  const BoundTypeId bound_type_id = gtl::FindWithDefault(
817  bound_name_to_id_map_, bound_type_mnemonic, UNKNOWN_BOUND_TYPE);
818  if (bound_type_id == UNKNOWN_BOUND_TYPE) {
819  return InvalidArgumentError("Unknown bound type.");
820  }
821  const int col = data->FindOrCreateVariable(column_name);
822  if (integer_type_names_set_.count(bound_type_mnemonic) != 0) {
823  data->SetVariableTypeToInteger(col);
824  }
825  if (is_binary_by_default_.size() <= col) {
826  // This is the first time that this column has been encountered.
827  is_binary_by_default_.resize(col + 1, false);
828  }
829  // Check that "binary by default" implies "integer".
830  DCHECK(!is_binary_by_default_[col] || data->VariableIsInteger(col));
831  Fractional lower_bound = data->VariableLowerBound(col);
832  Fractional upper_bound = data->VariableUpperBound(col);
833  // If a variable is binary by default, its status is reset if any bound
834  // is set on it. We take care to restore the default bounds for general
835  // integer variables.
836  if (is_binary_by_default_[col]) {
837  lower_bound = Fractional(0.0);
839  }
840  switch (bound_type_id) {
841  case LOWER_BOUND: {
842  ASSIGN_OR_RETURN(lower_bound, GetDoubleFromString(bound_value));
843  // LI with the value 0.0 specifies general integers with no upper bound.
844  if (bound_type_mnemonic == "LI" && lower_bound == 0.0) {
846  }
847  break;
848  }
849  case UPPER_BOUND: {
850  ASSIGN_OR_RETURN(upper_bound, GetDoubleFromString(bound_value));
851  break;
852  }
853  case FIXED_VARIABLE: {
854  ASSIGN_OR_RETURN(lower_bound, GetDoubleFromString(bound_value));
856  break;
857  }
858  case FREE_VARIABLE:
861  break;
862  case INFINITE_LOWER_BOUND:
864  break;
865  case INFINITE_UPPER_BOUND:
867  break;
868  case BINARY:
869  lower_bound = Fractional(0.0);
870  upper_bound = Fractional(1.0);
871  break;
872  case UNKNOWN_BOUND_TYPE:
873  default:
874  return InvalidArgumentError("Unknown bound type.");
875  }
876  is_binary_by_default_[col] = false;
877  data->SetVariableBounds(col, lower_bound, upper_bound);
878  return absl::OkStatus();
879 }
880 
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};
886 
888  : free_form_(true),
889  fields_(kNumFields),
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_(),
895  line_num_(0),
896  line_(),
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"] = INFINITE_LOWER_BOUND;
919  bound_name_to_id_map_["PL"] = INFINITE_UPPER_BOUND;
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");
926 }
927 
928 void MPSReaderImpl::Reset() {
929  fields_.resize(kNumFields);
930  line_num_ = 0;
931  in_integer_section_ = false;
932  num_unconstrained_rows_ = 0;
933  objective_name_.clear();
934 }
935 
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.";
941  }
942 }
943 
944 bool MPSReaderImpl::IsFixedFormat() {
945  for (const int i : kSpacePos) {
946  if (i >= line_.length()) break;
947  if (line_[i] != ' ') return false;
948  }
949  return true;
950 }
951 
952 absl::Status MPSReaderImpl::SplitLineIntoFields() {
953  if (free_form_) {
954  fields_ = absl::StrSplit(line_, absl::ByAnyChar(" \t"), absl::SkipEmpty());
955  if (fields_.size() > kNumFields) {
956  return InvalidArgumentError("Found too many fields.");
957  }
958  } else {
959  // Note: the name should also comply with the fixed format guidelines
960  // (maximum 8 characters) but in practice there are many problem files in
961  // our netlib archive that are in fixed format and have a long name. We
962  // choose to ignore these cases and treat them as fixed format anyway.
963  if (section_ != NAME && !IsFixedFormat()) {
964  return InvalidArgumentError("Line is not in fixed format.");
965  }
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);
971  } else {
972  fields_[i] = "";
973  }
974  }
975  }
976  return absl::OkStatus();
977 }
978 
979 std::string MPSReaderImpl::GetFirstWord() const {
980  if (line_[0] == ' ') {
981  return std::string("");
982  }
983  const int first_space_pos = line_.find(' ');
984  const std::string first_word = line_.substr(0, first_space_pos);
985  return first_word;
986 }
987 
988 bool MPSReaderImpl::IsCommentOrBlank() const {
989  const char* line = line_.c_str();
990  if (*line == '*') {
991  return true;
992  }
993  for (; *line != '\0'; ++line) {
994  if (*line != ' ' && *line != '\t') {
995  return false;
996  }
997  }
998  return true;
999 }
1000 
1001 absl::StatusOr<double> MPSReaderImpl::GetDoubleFromString(
1002  const std::string& str) {
1003  double result;
1004  if (!absl::SimpleAtod(str, &result)) {
1005  return InvalidArgumentError(
1006  absl::StrCat("Failed to convert \"", str, "\" to double."));
1007  }
1008  if (std::isnan(result)) {
1009  return InvalidArgumentError("Found NaN value.");
1010  }
1011  return result;
1012 }
1013 
1014 absl::StatusOr<bool> MPSReaderImpl::GetBoolFromString(const std::string& str) {
1015  int result;
1016  if (!absl::SimpleAtoi(str, &result) || result < 0 || result > 1) {
1017  return InvalidArgumentError(
1018  absl::StrCat("Failed to convert \"", str, "\" to bool."));
1019  }
1020  return result;
1021 }
1022 
1023 absl::Status MPSReaderImpl::ProcessSosSection() {
1024  return InvalidArgumentError("Section SOS currently not supported.");
1025 }
1026 
1027 absl::Status MPSReaderImpl::InvalidArgumentError(
1028  const std::string& error_message) {
1029  return AppendLineToError(absl::InvalidArgumentError(error_message));
1030 }
1031 
1032 absl::Status MPSReaderImpl::AppendLineToError(const absl::Status& status) {
1033  return util::StatusBuilder(status).SetAppend()
1034  << " Line " << line_num_ << ": \"" << line_ << "\".";
1035 }
1036 
1037 // Parses instance from a file.
1038 absl::Status MPSReader::ParseFile(const std::string& file_name,
1039  LinearProgram* data, Form form) {
1040  return MPSReaderImpl().ParseFile(file_name, data, form);
1041 }
1042 
1043 absl::Status MPSReader::ParseFile(const std::string& file_name,
1044  MPModelProto* data, Form form) {
1045  return MPSReaderImpl().ParseFile(file_name, data, form);
1046 }
1047 
1048 } // namespace glop
1049 } // namespace operations_research
::operations_research::MPIndicatorConstraint * mutable_indicator_constraint()
::operations_research::MPConstraintProto * mutable_constraint()
void SetObjectiveCoefficient(int index, double coefficient)
Definition: mps_reader.cc:294
StatusBuilder & SetAppend()
int64_t min
Definition: alldiff_cst.cc:139
void SetConstraintBounds(int index, double lower_bound, double upper_bound)
Definition: mps_reader.cc:342
void SetConstraintCoefficient(int row_index, int col_index, double coefficient)
Definition: mps_reader.cc:267
#define VLOG(verboselevel)
Definition: base/logging.h:979
void SetConstraintBounds(int index, double lower_bound, double upper_bound)
Definition: mps_reader.cc:264
const std::string name
void set_name(ArgT0 &&arg0, ArgT... args)
ColIndex col
Definition: markowitz.cc:183
void set_var_index(::PROTOBUF_NAMESPACE_ID::int32 value)
void SetVariableBounds(int index, double lower_bound, double upper_bound)
Definition: mps_reader.cc:380
void SetConstraintCoefficient(int row_index, int col_index, double coefficient)
Definition: mps_reader.cc:346
RowIndex row
Definition: markowitz.cc:182
int64_t coefficient
void SetVariableBounds(int index, double lower_bound, double upper_bound)
Definition: mps_reader.cc:291
int64_t max
Definition: alldiff_cst.cc:140
void set_var_value(::PROTOBUF_NAMESPACE_ID::int32 value)
double upper_bound
const int WARNING
Definition: log_severity.h:31
absl::Status ParseFile(const std::string &file_name, Data *data, MPSReader::Form form)
Definition: mps_reader.cc:434
void SetObjectiveCoefficient(int index, double coefficient)
Definition: mps_reader.cc:384
double lower_bound
const double kInfinity
Definition: lp_types.h:84
int index
Definition: pack.cc:509
absl::Status CreateIndicatorConstraint(std::string row_name, int col_index, bool col_value)
Definition: mps_reader.cc:307
void set_name(ArgT0 &&arg0, ArgT... args)
#define DCHECK(condition)
Definition: base/logging.h:885
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
#define LOG_FIRST_N(severity, n)
Definition: base/logging.h:850
absl::Status ParseFile(const std::string &file_name, LinearProgram *data, Form form=AUTO_DETECT)
Definition: mps_reader.cc:1038
void set_name(ArgT0 &&arg0, ArgT... args)
Collection of objects used to extend the Constraint Solver library.
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29
int64_t value
void add_var_index(::PROTOBUF_NAMESPACE_ID::int32 value)
#define ASSIGN_OR_RETURN(lhs, rexpr)
Definition: status_macros.h:55
int RemoveAt(RepeatedType *array, const IndexContainer &indices)
Definition: protobuf_util.h:40
absl::Status CreateIndicatorConstraint(std::string cst_name, int var_index, bool var_value)
Definition: mps_reader.cc:397