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