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