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