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