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