Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
mps_reader_template.h
Go to the documentation of this file.
1// Copyright 2010-2024 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
14#ifndef OR_TOOLS_LP_DATA_MPS_READER_TEMPLATE_H_
15#define OR_TOOLS_LP_DATA_MPS_READER_TEMPLATE_H_
16
17// A templated-reader for MPS (Mathematical Programming System) format.
18//
19// From Wikipedia https://en.wikipedia.org/wiki/MPS_(format):
20// ```
21// The format was named after an early IBM LP product[1] and has emerged as a de
22// facto standard ASCII medium among most of the commercial LP solvers.
23//
24// MPS is column-oriented (as opposed to entering the model as equations), and
25// all model components (variables, rows, etc.) receive names. MPS is an old
26// format, so it is set up for punch cards: Fields start in column 2, 5, 15, 25,
27// 40 and 50. Sections of an MPS file are marked by so-called header cards,
28// which are distinguished by their starting in column 1. Although it is typical
29// to use upper-case throughout the file for historical reasons, many
30// MPS-readers will accept mixed-case for anything except the header cards, and
31// some allow mixed-case anywhere. The names that you choose for the individual
32// entities (constraints or variables) are not important to the solver; one
33// should pick meaningful names, or easy names for a post-processing code to
34// read.
35// ```
36//
37// For example:
38// ```
39// NAME TESTPROB
40// ROWS
41// N COST
42// L LIM1
43// G LIM2
44// E MYEQN
45// COLUMNS
46// XONE COST 1 LIM1 1
47// XONE LIM2 1
48// YTWO COST 4 LIM1 1
49// YTWO MYEQN -1
50// ZTHREE COST 9 LIM2 1
51// ZTHREE MYEQN 1
52// RHS
53// RHS1 LIM1 5 LIM2 10
54// RHS1 MYEQN 7
55// BOUNDS
56// UP BND1 XONE 4
57// LO BND1 YTWO -1
58// UP BND1 YTWO 1
59// ENDATA
60// ```
61//
62// Note that the example, and the previous paragraph, mention that data must
63// start at given columns in the text. This is commonly referred to as 'fixed'
64// (width) format. In this version of the format, variable and constraint names
65// can contain white space, but they are limited to a maximum width of eight
66// characters, and each `section` marker must start at column 1.
67//
68// A common alternative is the so-called `free` format; where names
69// can have (in principle) arbitrary length, but no white space, and where each
70// data or section line can start with or without white space.
71// In both cases the number of fields in each line remain unchanged.
72// This implementation supports both `fixed` and `free` (width) format.
73//
74// TODO(b/284163180): The current behavior is that in free format header lines
75// do not start with white space, and data lines must start with at least one
76// white space.
77//
78// Although there is no `one` format (as many solvers have expanded
79// it over time to support their own generalizations to MIP; i.e. Mixed Integer
80// (Linear) Programming; most support the sections shown in the previous
81// example.
82//
83// In what follows, we describe the format and requirements for each of the
84// supported sections. Note that sections must appear in the order in
85// this description, and that optional sections can be skipped altogether, but
86// if they do appear, the must do so in the order in this description.
87//
88// Note that variables and constraints are declared in the order in which they
89// appear in the file.
90// Lines whose first character is '*' are considered comments and ignored;
91// empty lines are also ignored.
92//
93// Section order and data within each section:
94//
95// - NAME
96//
97// This optional section has the format:
98// 'NAME <optional_name>`
99// In fixed format, <optional_name> must start at column 15.
100//
101// - OBJSENSE
102//
103// This optional section specifies the objective direction of the problem (min
104// or max), by a single line containing either 'MIN' or 'MAX'. In fixed format,
105// this field must start at column 2. If no OBJSENSE section is present, the
106// problem should be treated as a minimization problem (this is the most widely
107// used convention, but the actual behavior is implementation defined).
108//
109// - ROWS
110//
111// This is mandatory section, and each following data line is composed of
112// lines with two fields:
113// ' T RowName'
114// where T is one of:
115// - N: for no constraint type, usually used to describe objective
116// coefficients. The first row of type N is used as objective function. If
117// no row is of type N, then the objective function is zero, and the
118// problem can be seen a feasibility problem.
119// - L: for less than or equal,
120// - G: for greater than or equal,
121// - E: for equality constraints.
122// Right hand side of constraints are zero by default (these can be overridden
123// in sections RHS and RANGES). Repeating a 'RowName' is undefined behavior. In
124// fixed format, the type appears in column 2 and the row name starts in
125// column 5.
126//
127// - LAZYCONS
128//
129// This section is optional, and has the same format (and meaning) as the ROWS
130// section, i.e. each constraint mentioned here must be new, and each one of
131// them defines a constraint of the problem. The only difference is that
132// constraints defined in this section are marked as 'lazy', meaning that there
133// might be an advantage, when solving the problem, to dynamically add them to
134// the solving process on the fly.
135//
136// - COLUMNS
137//
138// This is a mandatory section, and each of the following data lines is
139// composed of three or five fields with the format:
140// ' <ColName> <RowName> <Value> <RowName2> <Value2>'
141// where 'RowName' and 'RowName2' are constraints defined in the ROWS or
142// LAZYCONS section; 'Value' and 'Value2' are finite values; 'RowName2' and
143// 'Value2' are optional. The triplet <RowName,ColName,Value> is added to
144// constraint matrix; and, if present, the triplet <RowName2,ColName,Value2> is
145// added to the constraint matrix. Note that there is no explicit requirement
146// that triplets are unique (and how to treat duplicates is
147// implementation-defined) nor sorted.
148//
149// In fixed format, the column name starts in column 5, the row name for the
150// first non-zero starts in column 15, and the value for the first non-zero
151// starts in column 25. If a second non-zero is present, the row name starts in
152// column 40 and the value starts in column 50.
153//
154// The COLUMNS section can optionally include (possibly multiple) integrality
155// markers. Variables mentioned between a pair of markers are assigned type
156// 'Integer' with binary bounds by default (even if the variable appears for the
157// first time outside a pair of integrality markers, thus changing its default
158// bounds). Refer to the BOUNDS section for how to change these bounds.
159//
160// The format of these markers is (excluding double quotes):
161// - " <IgnoredField> 'MARKER' 'INTORG'",
162// - " <ColName> <RowName> <Value> <RowName2> <Value2>"
163// ...
164// - " <ColName> <RowName> <Value> <RowName2> <Value2>"
165// - " <IgnoredField> 'MARKER' 'INTEND'",
166// Where the first field is ignored. In fixed format, the fields start in
167// columns 5, 15 and 40, respectively. Note that the second field must exactly
168// match 'MARKER', and the third field must be 'INTORG' for opening an integer
169// section, and 'INTEND' for closing an integer section.
170//
171// - RHS
172//
173// This is a mandatory section, and each of the following data lines is
174// composed of three or five fields with the format:
175// ' <Ignored_Field> <RowName1> <Value1> <OptionalRow2> <OptionalValue2>',
176// where the first field is ignored, and <RowName> must have been defined in
177// sections ROWS or LAZYCONS with type E, L or G, and where <Value1> is the
178// right hand side of <RowName>, and must be a finite value. If <OptionalRow2>
179// and <OptionalValue2> are present, the same constraints and behavior applies.
180// In fixed format fields start at columns 2, 5, 15, 40 and 50.
181//
182// You can specify an objective 'Offset' by adding the pair <Objective_Name>
183// <Minus_Offset> in one of the data lines of the RHS section.
184//
185// - RANGES
186//
187// This is an optional section, and each of the following data lines is
188// composed of three or five fields:
189// ' <IgnoredField> <RowName> <Range1> <OptionalRowName2> <OptionalRange2>',
190// where the first field is ignored, and <RowName> must have been defined in
191// sections ROWS or LAZYCONS with type E, L or G, and <Range1> must be a finite
192// value. In fixed format fields must start in columns 2, 5, 15, 40 and 50.
193//
194// The effect of specifying a range depends on the sense of the
195// specified row and whether the range has a positive or negative <Range1>:
196//
197// Row_type Range_value_sign rhs_lower_limit rhs_upper_limit
198// G + or - rhs rhs + |range|
199// L + or - rhs - |range| rhs
200// E + rhs rhs + range
201// E - rhs + range rhs
202//
203// If <OptionalRowName2> and <OptionalRange2> are present, the same constraints
204// and behavior applies.
205//
206// - BOUNDS
207//
208// Each variable has by default a lower bound of zero, and an upper bound of
209// infinity, except if the variable is mentioned between integrality markers and
210// is not mentioned in this section, in which case its lower bound is zero, and
211// its upper bound is one.
212//
213// This is a mandatory section, and each of the following data lines is composed
214// of three or four fields with the format:
215// ' <BoundType> <IgnoredField> <ColName> <Value>',
216// - LO: lower bound for variable, <Value> is mandatory, and
217// the data line has the effect of setting <Value> <= <ColName>,
218// - UP: upper bound for variable, <Value> is mandatory, and the
219// data line has the effect of setting <ColName> <= <Value>,
220// - FX: for fixed variable, <Value> is mandatory, and the data line has the
221// effect of setting <Value> <= <ColName> <= <Value>,
222// - FR: for `free` variable, <Value> is optional and ignored if present, and
223// the data line has the effect of setting −∞ <= <ColName> <= ∞,
224// - MI: infinity lower bound, <Value> is optional and ignored if present, and
225// the data line has the effect of setting −∞ <= <ColName>,
226// - PL: infinity upper bound, <Value> is optional and ignored if present, and
227// the data line has the effect of setting <ColName> <= ∞,
228// - BV: binary variable, <Value> is optional and ignored if present, and the
229// data line has the effect of setting 0 <= <ColName> <= 1,
230// - LI: lower bound for integer variables, same constraints and effect as LO.
231// - UI: upper bound for integer variables, same constraints and effect as UP.
232// - SC: stands for semi-continuous and indicates that the variable may be zero,
233// but if not must be equal to at least the value given (this is not a
234// common type of variable, and can easily be described in terms of a
235// binary plus a continuous variable and a constraint linking the two, an
236// implementation may choose not to support this kind of variable);
237// <Value> is mandatory, and is only meaningful if it is strictly
238// positive.
239// No extra constraints or assumptions are imposed on the order, or the number
240// of bound constraints on a variable. Each data line is processed sequentially
241// and its effects enforced; regardless of previously set bounds, explicitly or
242// by default.
243//
244// In fixed format, fields start in columns 2, 5, 15 and 25.
245//
246// - INDICATORS
247//
248// This is an optional section, and each of the following data lines is
249// composed of four fields with the format:
250// ' IF <RowName> <ColName> <BinaryValue>',
251// where <RowName> is a row defined either in the ROWS or LAZYCONS sections,
252// <ColName> is forced to be a binary variable (intersecting previously set
253// bounds with the interval [0,1], and requiring it to be integer); the effect
254// of the data line is to remove <RowName> from the set of common linear
255// constraints (which must be satisfied for all feasible solutions), and
256// instead require the constraint to be satisfied only if the binary variable
257// <ColName> holds the value <BinaryValue>.
258// Note that integer/primal tolerances on variables have surprising effects: if
259// a binary variable has the value (1+-tolerance), it is considered to be at
260// value 1 for the purposes of indicator constraints.
261//
262// - ENDDATA
263//
264// This is a mandatory section, and it should be the last line in a MPS
265// file/string. What happens with lines after this section is undefined
266// behavior.
267//
268// Some extended versions (often times incompatible between themselves) of the
269// format can be seen here:
270//
271// https://www.gurobi.com/documentation/10.0/refman/mps_format.html
272// https://www.ibm.com/docs/en/icos/22.1.0?topic=standard-records-in-mps-format
273// https://lpsolve.sourceforge.net/5.0/mps-format.htm
274
275#include <algorithm>
276#include <cmath>
277#include <cstdint>
278#include <limits>
279#include <string>
280#include <vector>
281
282#include "absl/container/flat_hash_map.h"
283#include "absl/container/flat_hash_set.h"
284#include "absl/container/inlined_vector.h"
285#include "absl/log/check.h"
286#include "absl/status/status.h"
287#include "absl/status/statusor.h"
288#include "absl/strings/ascii.h"
289#include "absl/strings/match.h"
290#include "absl/strings/numbers.h"
291#include "absl/strings/str_cat.h"
292#include "absl/strings/str_split.h"
293#include "absl/strings/string_view.h"
294#include "ortools/base/logging.h"
297
298namespace operations_research {
299
300// Forms of MPS format supported, either detected automatically, or free
301// format, or fixed (width) format.
302enum class MPSReaderFormat { kAutoDetect, kFree, kFixed };
303
304// Implementation details.
305namespace internal {
306
307// Maximum number of 'fields' in an MPS line, either in fixed or free format.
308static constexpr int kNumMpsFields = 6;
309
310// Enum for MPS section ids.
311enum class MPSSectionId {
312 kUnknownSection,
313 kName,
314 kObjsense,
315 kRows,
316 kLazycons,
317 kColumns,
318 kRhs,
319 kRanges,
320 kBounds,
321 kIndicators,
322 kEndData
323};
324
325// Represents a single line of an MPS file (or string), and its corresponding
326// fields.
327class MPSLineInfo {
328 public:
329 // Creates an `MPSLineInfo` for `line`, which should outlive this object. If
330 // the line is a comment line, does not split the line into fields. Returns
331 // InvalidArgumentError if:
332 // * `free_form == false` and `line` contains a forbidden character ('\t')
333 // after stripping trailing whitespace,
334 // * `free_form == false` and `line` is not in fixed format, or
335 // * if when splitting the line into fields too many fields are detected.
336 static absl::StatusOr<MPSLineInfo> Create(int64_t line_num, bool free_form,
337 absl::string_view line);
338
339 // Returns a view of the line.
340 absl::string_view GetLine() const { return line_; }
341
342 // TODO(b/284163180): Fix handling of sections and data in `free_form`.
343 // Returns true if the line defines a new section.
344 bool IsNewSection() const { return line_[0] != '\0' && line_[0] != ' '; }
345
346 // Returns the number of fields in the line. What constitutes a 'field'
347 // depends on the format (fixed or free) used at creation time. See the
348 // previous description of MPS fixed and free formats for more details.
349 int GetFieldsSize() const { return fields_.size(); }
350
351 // Returns the word starting at position 0 in the line. If the line is empty,
352 // or if the line starts with a space, returns `""`.
353 absl::string_view GetFirstWord() const;
354
355 // Returns true if the line contains a comment (starting with '*') or
356 // if it is a blank line.
357 bool IsCommentOrBlank() const;
358
359 // Helper function that returns the index-th field in the line.
360 // Note that what constitutes a 'field' depends on the format of the line
361 // (i.e. free form or fixed form). See the previous description of fixed and
362 // free MPS format for more details. Note that `index` must be smaller than
363 // `GetFieldsSize()` and non negative, otherwise the behavior is undefined.
364 // Furthermore, until `SplitLineIntoFields` is called, `GetFieldsSize()` is
365 // always zero.
366 absl::string_view GetField(int index) const { return fields_[index]; }
367
368 // After calling `SplitLineIntoFields` has been called, returns the offset at
369 // which to start the parsing of fields within the line. See the preceding
370 // discussion on free and fixed MPS format for details on what constitutes a
371 // field in a line.
372 // If in fixed form, the offset is 0.
373 // If in fixed form and the number of fields is odd, it is 1,
374 // otherwise it is 0.
375 // This is useful when processing RANGES and RHS sections.
376 // If `SplitLineIntoFields` has not been called before, returns 0.
377 int GetFieldOffset() const { return free_form_ ? fields_.size() & 1 : 0; }
378
379 // Returns an absl::InvalidArgumentError with the given error message,
380 // postfixed by the line of the .mps file (number and contents).
381 absl::Status InvalidArgumentError(absl::string_view error_message) const;
382
383 // Appends the line of the .mps file (number and contents) to the
384 // status if it's an error message.
385 absl::Status AppendLineToError(const absl::Status& status) const;
386
387 private:
388 MPSLineInfo(int64_t line_num, bool free_form, absl::string_view line)
389 : free_form_{free_form}, line_num_{line_num}, line_{line} {}
390
391 // Splits into fields the line.
392 absl::Status SplitLineIntoFields();
393
394 // Returns true if the line matches the fixed format.
395 bool IsFixedFormat() const;
396
397 // Boolean set to true if the reader expects a free-form MPS file.
398 const bool free_form_;
399
400 // Storage of the fields for the line.
401 absl::InlinedVector<absl::string_view, internal::kNumMpsFields> fields_;
402
403 // The current line number (passed at construction time).
404 const int64_t line_num_;
405
406 // The line being parsed (with ASCII trailing white space removed, that
407 // includes windows end of line, new line, space, vertical tab and horizontal
408 // tab).
409 const absl::string_view line_;
410};
411
412} // namespace internal
413
414// Templated `MPS` reader. The template class `DataWrapper` must provide:
415// type:
416// - `IndexType`: for indices of rows and columns.
417// functions:
418// - `void SetUp()`: Called before parsing. After this function call, the
419// internal state of the object should be the same as in creation.
420// Note that this function can be called more than once if using
421// `MPSReaderFormat::kAutoDetect` format.
422// - `void CleanUp()`: Called once, after parsing has been successful, to
423// perform any internal clean up if needed.
424// - `double ConstraintLowerBound(IndexType index)`: Returns the (currently
425// stored) lower bound for 'Constraint[index]'; where 'index' is
426// a value previously returned by `FindOrCreateConstraint`.
427// - `double ConstraintUpperBound(IndexType index)`: Returns the (currently
428// stored) upper bound for 'Constraint[index]'; where 'index' is
429// a value previously returned by `FindOrCreateConstraint`.
430// - `IndexType FindOrCreateConstraint(absl::string_view row_name)`: Returns
431// the (internally assigned) index to the constraint of the given
432// name. If `row_name` is new, the constraint must be created with
433// a zero lower bound and a zero upper bound.
434// - `IndexType FindOrCreateVariable(absl::string_view col_name)`: Returns the
435// (internally assigned) index to the variable of the given name.
436// Newly created variables should have a zero objective, zero
437// lower bound, infinity upper bound, and be considered as
438// 'continuous'. If `col_name` is new, the variable must be
439// created with a zero objective coefficient, a zero lower bound,
440// and an infinity upper bound.
441// - `void SetConstraintBounds(IndexType index,double lower_bound, double
442// upper_bound)`: Stores lower and upper bounds for
443// 'Constraint[index]'. `index` is a value previously returned by
444// `FindOrCreateConstraint`.
445// - `void SetConstraintCoefficient(IndexType row_index, IndexType col_index,
446// double coefficient)`: Stores/Adds a new coefficient for the
447// constraint matrix entry (row_index,col_index); where
448// `row_index` is a value previously returned by
449// `FindOrCreateConstraint`, and `col_index` is a value
450// previously returned by `FindOrCreateVariable`.
451// - `void SetIsLazy(IndexType row_index)`: Marks 'Constraint[row_index]' as a
452// `lazy constraint`, meaning that the constraint is part of the
453// problem definition, but it might be advantageous to add them
454// as 'cuts' when solving the problem; where `row_index` is a
455// value previously returned by `FindOrCreateConstraint`.
456// - `void SetName(absl::string_view)`: Stores the model's name.
457// - 'void SetObjectiveCoefficient(IndexType index, double coefficient)`:
458// Stores `coefficient` as the new objective coefficient for
459// 'Variable[index]'; where `index` is a value previously
460// returned by `FindOrCreateVariable`.
461// - `void SetObjectiveDirection(bool maximize)`: If `maximize==true` the
462// parsed model represents a maximization problem, otherwise, or
463// if the function is never called, the model is a minimization
464// problem.
465// - `void SetObjectiveOffset(double)`: Stores the objective offset of the
466// model.
467// - `void SetVariableTypeToInteger(IndexType index)`: Marks 'Variable[index]'
468// as 'integer'; where `index` is a value previously returned by
469// `FindOrCreateVariable`.
470// - `void SetVariableTypeToSemiContinuous(IndexType index)`: Marks
471// 'Variable[index]' as 'semi continuous'; where `index` is a
472// value previously returned by `FindOrCreateVariable`.
473// - `void SetVariableBounds(IndexType index, double lower_bound, double
474// upper_bound)`: Stores the lower and upper bounds for
475// 'Variable[index]'; where `index` is a value previously
476// returned by `FindOrCreateVariable`.
477// - `double VariableLowerBound(IndexType index)`: Returns the (currently)
478// stored lower bound for 'Variable[index'; where `index` is a
479// value previously returned by `FindOrCreateVariable`.
480// - `double VariableUpperBound(IndexType index)`: Returns the (currently)
481// stored upper bound for 'Variable[index'; where `index` is a
482// value previously returned by `FindOrCreateVariable`.
483// - `absl::Status CreateIndicatorConstraint(absl::string_view row_name,
484// IndexType col_index, bool var_value)`: Marks constraint named
485// `row_name` to be an 'indicator constraint', that should hold
486// if 'Variable[col_index]' holds value 0 if `var_value`==false,
487// or when 'Variable[col_index]' holds value 1 if
488// `var_value`==true. Where `row_name` must have been an argument
489// in a previous call to `FindOrCreateConstraint`, and
490// `col_index` is a value previously returned by
491// `FindOrCreateVariable`. Note that 'Variable[col_index]' should
492// be marked as integer and have bounds in {0,1}.
493template <class DataWrapper>
495 public:
496 // Type for row and column indices, as provided by `DataWrapper`.
497 using IndexType = typename DataWrapper::IndexType;
498
500
501 // Parses a file in MPS format; if successful, returns the type of MPS
502 // format detected (one of `kFree` or `kFixed`). If `form` is either `kFixed`
503 // or `kFree`, the function will either return `kFixed` (or `kFree`
504 // respectivelly) if the input data satisfies the format, or an
505 // `absl::InvalidArgumentError` otherwise.
506 absl::StatusOr<MPSReaderFormat> ParseFile(
507 absl::string_view file_name, DataWrapper* data,
508 MPSReaderFormat form = MPSReaderFormat::kAutoDetect);
509
510 // Parses a string in MPS format; if successful, returns the type of MPS
511 // format detected (one of `kFree` or `kFixed`). If `form` is either `kFixed`
512 // or `kFree`, the function will either return `kFixed` (or `kFree`
513 // respectivelly) if the input data satisfies the format, or an
514 // `absl::InvalidArgumentError` otherwise.
515 absl::StatusOr<MPSReaderFormat> ParseString(
516 absl::string_view source, DataWrapper* data,
517 MPSReaderFormat form = MPSReaderFormat::kAutoDetect);
518
519 private:
520 static constexpr double kInfinity = std::numeric_limits<double>::infinity();
521
522 // Resets the object to its initial value before reading a new file.
523 void Reset();
524
525 // Displays some information on the last loaded file.
526 void DisplaySummary();
527
528 // Line processor.
529 absl::Status ProcessLine(absl::string_view line, DataWrapper* data);
530
531 // Process section OBJSENSE in MPS file.
532 absl::Status ProcessObjectiveSenseSection(
533 const internal::MPSLineInfo& line_info, DataWrapper* data);
534
535 // Process section ROWS in the MPS file.
536 absl::Status ProcessRowsSection(const internal::MPSLineInfo& line_info,
537 bool is_lazy, DataWrapper* data);
538
539 // Process section COLUMNS in the MPS file.
540 absl::Status ProcessColumnsSection(const internal::MPSLineInfo& line_info,
541 DataWrapper* data);
542
543 // Process section RHS in the MPS file.
544 absl::Status ProcessRhsSection(const internal::MPSLineInfo& line_info,
545 DataWrapper* data);
546
547 // Process section RANGES in the MPS file.
548 absl::Status ProcessRangesSection(const internal::MPSLineInfo& line_info,
549 DataWrapper* data);
550
551 // Process section BOUNDS in the MPS file.
552 absl::Status ProcessBoundsSection(const internal::MPSLineInfo& line_info,
553 DataWrapper* data);
554
555 // Process section INDICATORS in the MPS file.
556 absl::Status ProcessIndicatorsSection(const internal::MPSLineInfo& line_info,
557 DataWrapper* data);
558
559 // Safely converts a string to a numerical type. Returns an error if the
560 // string passed as parameter is ill-formed.
561 absl::StatusOr<double> GetDoubleFromString(
562 absl::string_view str, const internal::MPSLineInfo& line_info);
563 absl::StatusOr<bool> GetBoolFromString(
564 absl::string_view str, const internal::MPSLineInfo& line_info);
565
566 // Different types of variables, as defined in the MPS file specification.
567 // Note these are more precise than the ones in PrimalSimplex.
568 enum class BoundTypeId {
569 kUnknownBoundType,
570 kLowerBound,
571 kUpperBound,
572 kFixedVariable,
573 kFreeVariable,
574 kInfiniteLowerBound,
575 kInfiniteUpperBound,
576 kBinary,
577 kSemiContinuous
578 };
579
580 // Different types of constraints for a given row.
581 enum class RowTypeId {
582 kUnknownRowType,
583 kEquality,
584 kLessThan,
585 kGreaterThan,
586 kObjective,
587 kNone
588 };
589
590 // Stores a bound value of a given type, for a given column name.
591 absl::Status StoreBound(const internal::MPSLineInfo& line_info,
592 absl::string_view bound_type_mnemonic,
593 absl::string_view column_name,
594 absl::string_view bound_value, DataWrapper* data);
595
596 // Stores a coefficient value for a column number and a row name.
597 absl::Status StoreCoefficient(const internal::MPSLineInfo& line_info,
598 IndexType col, absl::string_view row_name,
599 absl::string_view row_value, DataWrapper* data);
600
601 // Stores a right-hand-side value for a row name.
602 absl::Status StoreRightHandSide(const internal::MPSLineInfo& line_info,
603 absl::string_view row_name,
604 absl::string_view row_value,
605 DataWrapper* data);
606
607 // Stores a range constraint of value row_value for a row name.
608 absl::Status StoreRange(const internal::MPSLineInfo& line_info,
609 absl::string_view row_name,
610 absl::string_view range_value, DataWrapper* data);
611
612 // Boolean set to true if the reader expects a free-form MPS file.
613 bool free_form_;
614
615 // Stores the name of the objective row.
616 std::string objective_name_;
617
618 // Id of the current section of MPS file.
619 internal::MPSSectionId section_;
620
621 // Maps section mnemonic --> section id.
622 absl::flat_hash_map<std::string, internal::MPSSectionId>
623 section_name_to_id_map_;
624
625 // Maps row type mnemonic --> row type id.
626 absl::flat_hash_map<std::string, RowTypeId> row_name_to_id_map_;
627
628 // Maps bound type mnemonic --> bound type id.
629 absl::flat_hash_map<std::string, BoundTypeId> bound_name_to_id_map_;
630
631 // Set of bound type mnemonics that constrain variables to be integer.
632 absl::flat_hash_set<std::string> integer_type_names_set_;
633
634 // The current line number in the file being parsed.
635 int64_t line_num_;
636
637 // A row of Booleans. is_binary_by_default_[col] is true if col
638 // appeared within a scope started by INTORG and ended with INTEND markers.
639 std::vector<bool> is_binary_by_default_;
640
641 // True if the next variable has to be interpreted as an integer variable.
642 // This is used to support the marker INTORG that starts an integer section
643 // and INTEND that ends it.
644 bool in_integer_section_;
645
646 // We keep track of the number of unconstrained rows so we can display it to
647 // the user because other solvers usually ignore them and we don't (they will
648 // be removed in the preprocessor).
649 IndexType num_unconstrained_rows_;
650};
651
652template <class DataWrapper>
653absl::StatusOr<MPSReaderFormat> MPSReaderTemplate<DataWrapper>::ParseFile(
654 const absl::string_view file_name, DataWrapper* const data,
655 const MPSReaderFormat form) {
656 if (data == nullptr) {
657 return absl::InvalidArgumentError("NULL pointer passed as argument.");
658 }
659
660 if (form != MPSReaderFormat::kFree && form != MPSReaderFormat::kFixed) {
661 if (ParseFile(file_name, data, MPSReaderFormat::kFixed).ok()) {
662 return MPSReaderFormat::kFixed;
663 }
664 return ParseFile(file_name, data, MPSReaderFormat::kFree);
665 }
666
667 DCHECK(form == MPSReaderFormat::kFree || form == MPSReaderFormat::kFixed);
668 free_form_ = form == MPSReaderFormat::kFree;
669 Reset();
670 data->SetUp();
671 File* file = nullptr;
672 RETURN_IF_ERROR(file::Open(file_name, "r", &file, file::Defaults()));
673 for (const absl::string_view line :
675 RETURN_IF_ERROR(ProcessLine(line, data));
676 }
677 data->CleanUp();
678 DisplaySummary();
679 return form;
680}
681
682template <class DataWrapper>
683absl::StatusOr<MPSReaderFormat> MPSReaderTemplate<DataWrapper>::ParseString(
684 absl::string_view source, DataWrapper* const data,
685 const MPSReaderFormat form) {
686 if (form != MPSReaderFormat::kFree && form != MPSReaderFormat::kFixed) {
687 if (ParseString(source, data, MPSReaderFormat::kFixed).ok()) {
688 return MPSReaderFormat::kFixed;
689 }
690 return ParseString(source, data, MPSReaderFormat::kFree);
691 }
692
693 DCHECK(form == MPSReaderFormat::kFree || form == MPSReaderFormat::kFixed);
694 free_form_ = form == MPSReaderFormat::kFree;
695 Reset();
696 data->SetUp();
697 for (absl::string_view line : absl::StrSplit(source, '\n')) {
698 RETURN_IF_ERROR(ProcessLine(line, data));
699 }
700 data->CleanUp();
701 DisplaySummary();
702 return form;
703}
704
705template <class DataWrapper>
706absl::Status MPSReaderTemplate<DataWrapper>::ProcessLine(absl::string_view line,
707 DataWrapper* data) {
708 ++line_num_;
709 ASSIGN_OR_RETURN(const internal::MPSLineInfo line_info,
710 internal::MPSLineInfo::Create(line_num_, free_form_, line));
711 if (line_info.IsCommentOrBlank()) {
712 return absl::OkStatus(); // Skip blank lines and comments.
713 }
714
715 // TODO(b/284163180): Fix handling of sections and data in `free_form`.
716 if (line_info.IsNewSection()) {
717 if (const auto it = section_name_to_id_map_.find(line_info.GetFirstWord());
718 it != section_name_to_id_map_.end()) {
719 section_ = it->second;
720 } else {
721 return line_info.InvalidArgumentError("Unknown section.");
722 }
723 if (section_ == internal::MPSSectionId::kName) {
724 // NOTE(user): The name may differ between fixed and free forms. In
725 // fixed form, the name has at most 8 characters, and starts at a specific
726 // position in the NAME line. For MIPLIB2010 problems (eg, air04, glass4),
727 // the name in fixed form ends up being preceded with a whitespace.
728 // TODO(user): Return an error for fixed form if the problem name
729 // does not fit.
730 if (free_form_) {
731 if (line_info.GetFieldsSize() >= 2) {
732 data->SetName(line_info.GetField(1));
733 }
734 } else {
735 const std::vector<absl::string_view> free_fields = absl::StrSplit(
736 line_info.GetLine(), absl::ByAnyChar(" \t"), absl::SkipEmpty());
737 const absl::string_view free_name =
738 free_fields.size() >= 2 ? free_fields[1] : "";
739 const absl::string_view fixed_name =
740 line_info.GetFieldsSize() >= 3 ? line_info.GetField(2) : "";
741 if (free_name != fixed_name) {
742 return line_info.InvalidArgumentError(
743 "Fixed form invalid: name differs between free and fixed "
744 "forms.");
745 }
746 data->SetName(fixed_name);
747 }
748 }
749 return absl::OkStatus();
750 }
751 switch (section_) {
752 case internal::MPSSectionId::kName:
753 return line_info.InvalidArgumentError("Second NAME field.");
754 case internal::MPSSectionId::kObjsense:
755 return ProcessObjectiveSenseSection(line_info, data);
756 case internal::MPSSectionId::kRows:
757 return ProcessRowsSection(line_info, /*is_lazy=*/false, data);
758 case internal::MPSSectionId::kLazycons:
759 return ProcessRowsSection(line_info, /*is_lazy=*/true, data);
760 case internal::MPSSectionId::kColumns:
761 return ProcessColumnsSection(line_info, data);
762 case internal::MPSSectionId::kRhs:
763 return ProcessRhsSection(line_info, data);
764 case internal::MPSSectionId::kRanges:
765 return ProcessRangesSection(line_info, data);
766 case internal::MPSSectionId::kBounds:
767 return ProcessBoundsSection(line_info, data);
768 case internal::MPSSectionId::kIndicators:
769 return ProcessIndicatorsSection(line_info, data);
770 case internal::MPSSectionId::kEndData: // Do nothing.
771 break;
772 default:
773 return line_info.InvalidArgumentError("Unknown section.");
774 }
775 return absl::OkStatus();
776}
777
778template <class DataWrapper>
780 const internal::MPSLineInfo& line_info, DataWrapper* data) {
781 absl::string_view field = absl::StripAsciiWhitespace(line_info.GetLine());
782 if (field != "MIN" && field != "MAX") {
783 return line_info.InvalidArgumentError(
784 "Expected objective sense (MAX or MIN).");
785 }
786 data->SetObjectiveDirection(/*maximize=*/field == "MAX");
787 return absl::OkStatus();
788}
789
790template <class DataWrapper>
792 const internal::MPSLineInfo& line_info, bool is_lazy, DataWrapper* data) {
793 if (line_info.GetFieldsSize() < 2) {
794 return line_info.InvalidArgumentError("Not enough fields in ROWS section.");
795 }
796 const absl::string_view row_type_name = line_info.GetField(0);
797 const absl::string_view row_name = line_info.GetField(1);
798 const auto it = row_name_to_id_map_.find(row_type_name);
799 if (it == row_name_to_id_map_.end()) {
800 return line_info.InvalidArgumentError("Unknown row type.");
801 }
802 RowTypeId row_type = it->second;
803
804 // The first NONE constraint is used as the objective.
805 if (objective_name_.empty() && row_type == RowTypeId::kNone) {
806 row_type = RowTypeId::kObjective;
807 objective_name_ = std::string(row_name);
808 } else {
809 if (row_type == RowTypeId::kNone) {
810 ++num_unconstrained_rows_;
811 }
812 const IndexType row = data->FindOrCreateConstraint(row_name);
813 if (is_lazy) data->SetIsLazy(row);
814
815 // The initial row range is [0, 0]. We encode the type in the range by
816 // setting one of the bounds to +/- infinity.
817 switch (row_type) {
818 case RowTypeId::kLessThan:
819 data->SetConstraintBounds(row, -kInfinity,
820 data->ConstraintUpperBound(row));
821 break;
822 case RowTypeId::kGreaterThan:
823 data->SetConstraintBounds(row, data->ConstraintLowerBound(row),
824 kInfinity);
825 break;
826 case RowTypeId::kNone:
827 data->SetConstraintBounds(row, -kInfinity, kInfinity);
828 break;
829 case RowTypeId::kEquality:
830 default:
831 break;
832 }
833 }
834 return absl::OkStatus();
835}
836
837template <class DataWrapper>
839 const internal::MPSLineInfo& line_info, DataWrapper* data) {
840 // Take into account the INTORG and INTEND markers.
841 if (absl::StrContains(line_info.GetLine(), "'MARKER'")) {
842 if (absl::StrContains(line_info.GetLine(), "'INTORG'")) {
843 VLOG(2) << "Entering integer marker.\n" << line_info.GetLine();
844 if (in_integer_section_) {
845 return line_info.InvalidArgumentError(
846 "Found INTORG inside the integer section.");
847 }
848 in_integer_section_ = true;
849 } else if (absl::StrContains(line_info.GetLine(), "'INTEND'")) {
850 VLOG(2) << "Leaving integer marker.\n" << line_info.GetLine();
851 if (!in_integer_section_) {
852 return line_info.InvalidArgumentError(
853 "Found INTEND without corresponding INTORG.");
854 }
855 in_integer_section_ = false;
856 }
857 return absl::OkStatus();
858 }
859 const int start_index = free_form_ ? 0 : 1;
860 if (line_info.GetFieldsSize() < start_index + 3) {
861 return line_info.InvalidArgumentError(
862 "Not enough fields in COLUMNS section.");
863 }
864 const absl::string_view column_name = line_info.GetField(start_index + 0);
865 const absl::string_view row1_name = line_info.GetField(start_index + 1);
866 const absl::string_view row1_value = line_info.GetField(start_index + 2);
867 const IndexType col = data->FindOrCreateVariable(column_name);
868 is_binary_by_default_.resize(col + 1, false);
869 if (in_integer_section_) {
870 data->SetVariableTypeToInteger(col);
871 // The default bounds for integer variables are [0, 1].
872 data->SetVariableBounds(col, 0.0, 1.0);
873 is_binary_by_default_[col] = true;
874 } else {
875 data->SetVariableBounds(col, 0.0, kInfinity);
876 }
878 StoreCoefficient(line_info, col, row1_name, row1_value, data));
879 if (line_info.GetFieldsSize() == start_index + 4) {
880 return line_info.InvalidArgumentError("Unexpected number of fields.");
881 }
882 if (line_info.GetFieldsSize() - start_index > 4) {
883 const absl::string_view row2_name = line_info.GetField(start_index + 3);
884 const absl::string_view row2_value = line_info.GetField(start_index + 4);
886 StoreCoefficient(line_info, col, row2_name, row2_value, data));
887 }
888 return absl::OkStatus();
889}
890
891template <class DataWrapper>
893 const internal::MPSLineInfo& line_info, DataWrapper* data) {
894 const int start_index = free_form_ ? 0 : 2;
895 const int offset = start_index + line_info.GetFieldOffset();
896 if (line_info.GetFieldsSize() < offset + 2) {
897 return line_info.InvalidArgumentError("Not enough fields in RHS section.");
898 }
899 // const absl::string_view rhs_name = line_info.GetField(0); is not used
900 const absl::string_view row1_name = line_info.GetField(offset);
901 const absl::string_view row1_value = line_info.GetField(offset + 1);
902 RETURN_IF_ERROR(StoreRightHandSide(line_info, row1_name, row1_value, data));
903 if (line_info.GetFieldsSize() - start_index >= 4) {
904 const absl::string_view row2_name = line_info.GetField(offset + 2);
905 const absl::string_view row2_value = line_info.GetField(offset + 3);
906 RETURN_IF_ERROR(StoreRightHandSide(line_info, row2_name, row2_value, data));
907 }
908 return absl::OkStatus();
909}
910
911template <class DataWrapper>
913 const internal::MPSLineInfo& line_info, DataWrapper* data) {
914 const int start_index = free_form_ ? 0 : 2;
915 const int offset = start_index + line_info.GetFieldOffset();
916 if (line_info.GetFieldsSize() < offset + 2) {
917 return line_info.InvalidArgumentError("Not enough fields in RHS section.");
918 }
919 // const absl::string_view range_name = line_info.GetField(0); is not used
920 const absl::string_view row1_name = line_info.GetField(offset);
921 const absl::string_view row1_value = line_info.GetField(offset + 1);
922 RETURN_IF_ERROR(StoreRange(line_info, row1_name, row1_value, data));
923 if (line_info.GetFieldsSize() - start_index >= 4) {
924 const absl::string_view row2_name = line_info.GetField(offset + 2);
925 const absl::string_view row2_value = line_info.GetField(offset + 3);
926 RETURN_IF_ERROR(StoreRange(line_info, row2_name, row2_value, data));
927 }
928 return absl::OkStatus();
929}
930
931template <class DataWrapper>
933 const internal::MPSLineInfo& line_info, DataWrapper* data) {
934 if (line_info.GetFieldsSize() < 3) {
935 return line_info.InvalidArgumentError(
936 "Not enough fields in BOUNDS section.");
937 }
938 const absl::string_view bound_type_mnemonic = line_info.GetField(0);
939 const absl::string_view column_name = line_info.GetField(2);
940 const absl::string_view bound_value =
941 (line_info.GetFieldsSize() >= 4) ? line_info.GetField(3) : "";
942 return StoreBound(line_info, bound_type_mnemonic, column_name, bound_value,
943 data);
944}
945
946template <class DataWrapper>
948 const internal::MPSLineInfo& line_info, DataWrapper* data) {
949 // TODO(user): Enforce section order. This section must come after
950 // anything related to constraints, or we'll have partial data inside the
951 // indicator constraints.
952 if (line_info.GetFieldsSize() < 4) {
953 return line_info.InvalidArgumentError(
954 "Not enough fields in INDICATORS section.");
955 }
956
957 const absl::string_view type = line_info.GetField(0);
958 if (type != "IF") {
959 return line_info.InvalidArgumentError(
960 "Indicator constraints must start with \"IF\".");
961 }
962 const absl::string_view row_name = line_info.GetField(1);
963 const absl::string_view column_name = line_info.GetField(2);
964 const absl::string_view column_value = line_info.GetField(3);
965
966 bool value;
967 ASSIGN_OR_RETURN(value, GetBoolFromString(column_value, line_info));
968
969 const IndexType col = data->FindOrCreateVariable(column_name);
970 // Variables used in indicator constraints become Boolean by default.
971 data->SetVariableTypeToInteger(col);
972 data->SetVariableBounds(col, std::max(0.0, data->VariableLowerBound(col)),
973 std::min(1.0, data->VariableUpperBound(col)));
974
975 RETURN_IF_ERROR(line_info.AppendLineToError(
976 data->CreateIndicatorConstraint(row_name, col, value)));
977
978 return absl::OkStatus();
979}
980
981template <class DataWrapper>
983 const internal::MPSLineInfo& line_info, IndexType col,
984 absl::string_view row_name, absl::string_view row_value,
985 DataWrapper* data) {
986 if (row_name.empty() || row_name == "$") {
987 return absl::OkStatus();
988 }
989
990 double value;
991 ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value, line_info));
992 if (value == kInfinity || value == -kInfinity) {
993 return line_info.InvalidArgumentError(
994 "Constraint coefficients cannot be infinity.");
995 }
996 if (value == 0.0) return absl::OkStatus();
997 if (row_name == objective_name_) {
998 data->SetObjectiveCoefficient(col, value);
999 } else {
1000 const IndexType row = data->FindOrCreateConstraint(row_name);
1001 data->SetConstraintCoefficient(row, col, value);
1002 }
1003 return absl::OkStatus();
1004}
1005
1006template <class DataWrapper>
1008 const internal::MPSLineInfo& line_info, absl::string_view row_name,
1009 absl::string_view row_value, DataWrapper* data) {
1010 if (row_name.empty()) return absl::OkStatus();
1011
1012 if (row_name != objective_name_) {
1013 const IndexType row = data->FindOrCreateConstraint(row_name);
1014 double value;
1015 ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value, line_info));
1016
1017 // The row type is encoded in the bounds, so at this point we have either
1018 // (-kInfinity, 0.0], [0.0, 0.0] or [0.0, kInfinity). We use the right
1019 // hand side to change any finite bound.
1020 const double lower_bound =
1021 (data->ConstraintLowerBound(row) == -kInfinity) ? -kInfinity : value;
1022 const double upper_bound =
1023 (data->ConstraintUpperBound(row) == kInfinity) ? kInfinity : value;
1024 data->SetConstraintBounds(row, lower_bound, upper_bound);
1025 } else {
1026 // We treat minus the right hand side of COST as the objective offset, in
1027 // line with what the MPS writer does and what Gurobi's MPS format
1028 // expects.
1029 double value;
1030 ASSIGN_OR_RETURN(value, GetDoubleFromString(row_value, line_info));
1031 data->SetObjectiveOffset(-value);
1032 }
1033 return absl::OkStatus();
1034}
1035
1036template <class DataWrapper>
1038 const internal::MPSLineInfo& line_info, absl::string_view row_name,
1039 absl::string_view range_value, DataWrapper* data) {
1040 if (row_name.empty()) return absl::OkStatus();
1041
1042 const IndexType row = data->FindOrCreateConstraint(row_name);
1043 double range;
1044 ASSIGN_OR_RETURN(range, GetDoubleFromString(range_value, line_info));
1045
1046 double lower_bound = data->ConstraintLowerBound(row);
1047 double upper_bound = data->ConstraintUpperBound(row);
1048 if (lower_bound == upper_bound) {
1049 if (range < 0.0) {
1050 lower_bound += range;
1051 } else {
1052 upper_bound += range;
1053 }
1054 }
1055 if (lower_bound == -kInfinity) {
1056 lower_bound = upper_bound - fabs(range);
1057 }
1058 if (upper_bound == kInfinity) {
1059 upper_bound = lower_bound + fabs(range);
1060 }
1061 data->SetConstraintBounds(row, lower_bound, upper_bound);
1062 return absl::OkStatus();
1063}
1064
1065template <class DataWrapper>
1067 const internal::MPSLineInfo& line_info,
1068 absl::string_view bound_type_mnemonic, absl::string_view column_name,
1069 absl::string_view bound_value, DataWrapper* data) {
1070 const auto it = bound_name_to_id_map_.find(bound_type_mnemonic);
1071 if (it == bound_name_to_id_map_.end()) {
1072 return line_info.InvalidArgumentError("Unknown bound type.");
1073 }
1074 const BoundTypeId bound_type_id = it->second;
1075 const IndexType col = data->FindOrCreateVariable(column_name);
1076 if (integer_type_names_set_.count(bound_type_mnemonic) != 0) {
1077 data->SetVariableTypeToInteger(col);
1078 }
1079 if (is_binary_by_default_.size() <= col) {
1080 // This is the first time that this column has been encountered.
1081 is_binary_by_default_.resize(col + 1, false);
1082 }
1083 double lower_bound = data->VariableLowerBound(col);
1084 double upper_bound = data->VariableUpperBound(col);
1085 // If a variable is binary by default, its status is reset if any bound
1086 // is set on it. We take care to restore the default bounds for general
1087 // integer variables.
1088 if (is_binary_by_default_[col]) {
1089 lower_bound = 0.0;
1091 }
1092 switch (bound_type_id) {
1093 case BoundTypeId::kLowerBound: {
1095 GetDoubleFromString(bound_value, line_info));
1096 // TODO(b/285121446): Decide to keep or remove this corner case behavior.
1097 // LI with the value 0.0 specifies general integers with no upper bound.
1098 if (bound_type_mnemonic == "LI" && lower_bound == 0.0) {
1100 }
1101 break;
1102 }
1103 case BoundTypeId::kUpperBound: {
1105 GetDoubleFromString(bound_value, line_info));
1106 break;
1107 }
1108 case BoundTypeId::kSemiContinuous: {
1110 GetDoubleFromString(bound_value, line_info));
1111 data->SetVariableTypeToSemiContinuous(col);
1112 break;
1113 }
1114 case BoundTypeId::kFixedVariable: {
1116 GetDoubleFromString(bound_value, line_info));
1118 break;
1119 }
1120 case BoundTypeId::kFreeVariable:
1123 break;
1124 case BoundTypeId::kInfiniteLowerBound:
1126 break;
1127 case BoundTypeId::kInfiniteUpperBound:
1129 break;
1130 case BoundTypeId::kBinary:
1131 lower_bound = 0.0;
1132 upper_bound = 1.0;
1133 break;
1134 case BoundTypeId::kUnknownBoundType:
1135 default:
1136 return line_info.InvalidArgumentError("Unknown bound type.");
1137 }
1138 is_binary_by_default_[col] = false;
1139 data->SetVariableBounds(col, lower_bound, upper_bound);
1140 return absl::OkStatus();
1141}
1142
1143template <class DataWrapper>
1145 : free_form_(true),
1146 section_(internal::MPSSectionId::kUnknownSection),
1147 section_name_to_id_map_(),
1148 row_name_to_id_map_(),
1149 bound_name_to_id_map_(),
1150 integer_type_names_set_(),
1151 line_num_(0),
1152 in_integer_section_(false),
1153 num_unconstrained_rows_(0) {
1154 section_name_to_id_map_["NAME"] = internal::MPSSectionId::kName;
1155 section_name_to_id_map_["OBJSENSE"] = internal::MPSSectionId::kObjsense;
1156 section_name_to_id_map_["ROWS"] = internal::MPSSectionId::kRows;
1157 section_name_to_id_map_["LAZYCONS"] = internal::MPSSectionId::kLazycons;
1158 section_name_to_id_map_["COLUMNS"] = internal::MPSSectionId::kColumns;
1159 section_name_to_id_map_["RHS"] = internal::MPSSectionId::kRhs;
1160 section_name_to_id_map_["RANGES"] = internal::MPSSectionId::kRanges;
1161 section_name_to_id_map_["BOUNDS"] = internal::MPSSectionId::kBounds;
1162 section_name_to_id_map_["INDICATORS"] = internal::MPSSectionId::kIndicators;
1163 section_name_to_id_map_["ENDATA"] = internal::MPSSectionId::kEndData;
1164 row_name_to_id_map_["E"] = RowTypeId::kEquality;
1165 row_name_to_id_map_["L"] = RowTypeId::kLessThan;
1166 row_name_to_id_map_["G"] = RowTypeId::kGreaterThan;
1167 row_name_to_id_map_["N"] = RowTypeId::kNone;
1168 bound_name_to_id_map_["LO"] = BoundTypeId::kLowerBound;
1169 bound_name_to_id_map_["UP"] = BoundTypeId::kUpperBound;
1170 bound_name_to_id_map_["FX"] = BoundTypeId::kFixedVariable;
1171 bound_name_to_id_map_["FR"] = BoundTypeId::kFreeVariable;
1172 bound_name_to_id_map_["MI"] = BoundTypeId::kInfiniteLowerBound;
1173 bound_name_to_id_map_["PL"] = BoundTypeId::kInfiniteUpperBound;
1174 bound_name_to_id_map_["BV"] = BoundTypeId::kBinary;
1175 bound_name_to_id_map_["LI"] = BoundTypeId::kLowerBound;
1176 bound_name_to_id_map_["UI"] = BoundTypeId::kUpperBound;
1177 bound_name_to_id_map_["SC"] = BoundTypeId::kSemiContinuous;
1178 // TODO(user): Support 'SI' (semi integer).
1179 integer_type_names_set_.insert("BV");
1180 integer_type_names_set_.insert("LI");
1181 integer_type_names_set_.insert("UI");
1182}
1183
1184template <class DataWrapper>
1186 line_num_ = 0;
1187 in_integer_section_ = false;
1188 num_unconstrained_rows_ = 0;
1189 objective_name_.clear();
1190}
1191
1192template <class DataWrapper>
1194 if (num_unconstrained_rows_ > 0) {
1195 VLOG(1) << "There are " << num_unconstrained_rows_ + 1
1196 << " unconstrained rows. The first of them (" << objective_name_
1197 << ") was used as the objective.";
1198 }
1199}
1200
1201template <class DataWrapper>
1203 absl::string_view str, const internal::MPSLineInfo& line_info) {
1204 double result;
1205 if (!absl::SimpleAtod(str, &result)) {
1206 return line_info.InvalidArgumentError(
1207 absl::StrCat("Failed to convert \"", str, "\" to double."));
1208 }
1209 if (std::isnan(result)) {
1210 return line_info.InvalidArgumentError("Found NaN value.");
1211 }
1212 return result;
1213}
1214
1215template <class DataWrapper>
1217 absl::string_view str, const internal::MPSLineInfo& line_info) {
1218 int result;
1219 if (!absl::SimpleAtoi(str, &result) || result < 0 || result > 1) {
1220 return line_info.InvalidArgumentError(
1221 absl::StrCat("Failed to convert \"", str, "\" to bool."));
1222 }
1223 return result;
1224}
1225
1226} // namespace operations_research
1227#endif // OR_TOOLS_LP_DATA_MPS_READER_TEMPLATE_H_
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
Definition file.h:30
absl::StatusOr< MPSReaderFormat > ParseFile(absl::string_view file_name, DataWrapper *data, MPSReaderFormat form=MPSReaderFormat::kAutoDetect)
typename DataWrapper::IndexType IndexType
Type for row and column indices, as provided by DataWrapper.
absl::StatusOr< MPSReaderFormat > ParseString(absl::string_view source, DataWrapper *data, MPSReaderFormat form=MPSReaderFormat::kAutoDetect)
int64_t value
absl::Status status
Definition g_gurobi.cc:44
double upper_bound
double lower_bound
int index
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
Definition file.cc:169
absl::Status Open(absl::string_view filename, absl::string_view mode, File **f, Options options)
As of 2016-01, these methods can only be used with flags = file::Defaults().
Definition file.cc:170
Options Defaults()
Definition file.h:109
@ kFree
The variable/constraint is free (it has no finite bounds).
In SWIG mode, we don't want anything besides these top-level includes.
static constexpr double kInfinity
int line
static const int kNone
Special value for task indices meaning 'no such task'.
Definition resource.cc:236
const std::optional< Range > & range
Definition statistics.cc:37