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