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