OR-Tools  9.2
lp_parser.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <set>
17 #include <string>
18 #include <vector>
19 
20 #include "absl/container/flat_hash_set.h"
21 #include "absl/status/status.h"
22 #include "absl/status/statusor.h"
23 #include "absl/strings/match.h"
24 #include "absl/strings/numbers.h"
25 #include "absl/strings/str_cat.h"
26 #include "absl/strings/str_split.h"
27 #include "absl/strings/string_view.h"
28 #include "absl/strings/strip.h"
29 #include "ortools/base/case.h"
30 #include "ortools/base/map_util.h"
33 #include "re2/re2.h"
34 
35 namespace operations_research {
36 namespace glop {
37 
38 namespace {
39 
40 using ::absl::StatusOr;
41 
42 enum class TokenType {
43  ERROR,
44  END,
45  ADDAND,
46  VALUE,
47  INF,
48  NAME,
49  SIGN_LE,
50  SIGN_EQ,
51  SIGN_GE,
52  COMA,
53 };
54 
55 bool TokenIsBound(TokenType token_type) {
56  if (token_type == TokenType::VALUE || token_type == TokenType::INF) {
57  return true;
58  }
59  return false;
60 }
61 
62 // Not thread safe.
63 class LPParser {
64  public:
65  // Accepts the string in LP file format (used by LinearProgram::Dump()).
66  // On success, populates the linear program *lp and returns true. Otherwise,
67  // returns false and leaves *lp in an unspecified state.
68  ABSL_MUST_USE_RESULT bool Parse(absl::string_view model, LinearProgram* lp);
69 
70  private:
71  bool ParseEmptyLine(absl::string_view line);
72  bool ParseObjective(absl::string_view objective);
73  bool ParseIntegerVariablesList(absl::string_view line);
74  bool ParseConstraint(absl::string_view constraint);
75  TokenType ConsumeToken(absl::string_view* sp);
76  bool SetVariableBounds(ColIndex col, Fractional lb, Fractional ub);
77 
78  // Linear program populated by the Parse() method. Not owned.
79  LinearProgram* lp_;
80 
81  // Contains the last consumed coefficient and name. The name can be the
82  // optimization direction, a constraint name, or a variable name.
83  Fractional consumed_coeff_;
84  std::string consumed_name_;
85 
86  // To remember whether the variable bounds had already been set.
87  std::set<ColIndex> bounded_variables_;
88 };
89 
90 bool LPParser::Parse(absl::string_view model, LinearProgram* lp) {
91  lp_ = lp;
92  bounded_variables_.clear();
93  lp_->Clear();
94 
95  std::vector<absl::string_view> lines =
96  absl::StrSplit(model, ';', absl::SkipEmpty());
97  bool has_objective = false;
98 
99  for (absl::string_view line : lines) {
100  if (!has_objective && ParseObjective(line)) {
101  has_objective = true;
102  } else if (!ParseConstraint(line) && !ParseIntegerVariablesList(line) &&
103  !ParseEmptyLine(line)) {
104  LOG(INFO) << "Error in line: " << line;
105  return false;
106  }
107  }
108 
109  // Bound the non-bounded variables between -inf and +inf. We need to do this,
110  // as glop bounds a variable by default between 0 and +inf.
111  for (ColIndex col(0); col < lp_->num_variables(); ++col) {
112  if (bounded_variables_.find(col) == bounded_variables_.end()) {
114  }
115  }
116 
117  lp_->CleanUp();
118  return true;
119 }
120 
121 bool LPParser::ParseEmptyLine(absl::string_view line) {
122  if (ConsumeToken(&line) == TokenType::END) return true;
123  return false;
124 }
125 
126 bool LPParser::ParseObjective(absl::string_view objective) {
127  // Get the required optimization direction.
128  if (ConsumeToken(&objective) != TokenType::NAME) return false;
129  if (absl::EqualsIgnoreCase(consumed_name_, "min")) {
130  lp_->SetMaximizationProblem(false);
131  } else if (absl::EqualsIgnoreCase(consumed_name_, "max")) {
132  lp_->SetMaximizationProblem(true);
133  } else {
134  return false;
135  }
136 
137  // Get the optional offset.
138  TokenType token_type = ConsumeToken(&objective);
139  if (token_type == TokenType::VALUE) {
140  lp_->SetObjectiveOffset(consumed_coeff_);
141  token_type = ConsumeToken(&objective);
142  } else {
143  lp_->SetObjectiveOffset(0.0);
144  }
145 
146  // Get the addands.
147  while (token_type == TokenType::ADDAND) {
148  const ColIndex col = lp_->FindOrCreateVariable(consumed_name_);
149  if (lp_->objective_coefficients()[col] != 0.0) return false;
150  lp_->SetObjectiveCoefficient(col, consumed_coeff_);
151  token_type = ConsumeToken(&objective);
152  }
153  return token_type == TokenType::END;
154 }
155 
156 bool LPParser::ParseIntegerVariablesList(absl::string_view line) {
157  // Get the required "int" or "bin" keyword.
158  bool binary_list = false;
159  if (ConsumeToken(&line) != TokenType::NAME) return false;
160  if (absl::EqualsIgnoreCase(consumed_name_, "bin")) {
161  binary_list = true;
162  } else if (!absl::EqualsIgnoreCase(consumed_name_, "int")) {
163  return false;
164  }
165 
166  // Get the list of integer variables, separated by optional comas.
167  TokenType token_type = ConsumeToken(&line);
168  while (token_type == TokenType::ADDAND) {
169  if (consumed_coeff_ != 1.0) return false;
170  const ColIndex col = lp_->FindOrCreateVariable(consumed_name_);
172  if (binary_list && !SetVariableBounds(col, 0.0, 1.0)) return false;
173  token_type = ConsumeToken(&line);
174  if (token_type == TokenType::COMA) {
175  token_type = ConsumeToken(&line);
176  }
177  }
178 
179  // The last token must be END.
180  if (token_type != TokenType::END) return false;
181  return true;
182 }
183 
184 bool LPParser::ParseConstraint(absl::string_view constraint) {
185  const StatusOr<ParsedConstraint> parsed_constraint_or_status =
187  if (!parsed_constraint_or_status.ok()) return false;
188  const ParsedConstraint& parsed_constraint =
189  parsed_constraint_or_status.value();
190 
191  // Set the variables bounds without creating new constraints.
192  if (parsed_constraint.name.empty() &&
193  parsed_constraint.coefficients.size() == 1 &&
194  parsed_constraint.coefficients[0] == 1.0) {
195  const ColIndex col =
196  lp_->FindOrCreateVariable(parsed_constraint.variable_names[0]);
197  if (!SetVariableBounds(col, parsed_constraint.lower_bound,
198  parsed_constraint.upper_bound)) {
199  return false;
200  }
201  } else {
202  const RowIndex num_constraints_before_adding_variable =
203  lp_->num_constraints();
204  // The constaint has a name, or there are more than variable, or the
205  // coefficient is not 1. Thus, create and fill a new constraint.
206  // We don't use SetConstraintName() because constraints named that way
207  // cannot be found via FindOrCreateConstraint() (see comment on
208  // SetConstraintName()), which can be useful for tests using ParseLP.
209  const RowIndex row =
210  parsed_constraint.name.empty()
211  ? lp_->CreateNewConstraint()
212  : lp_->FindOrCreateConstraint(parsed_constraint.name);
213  if (lp_->num_constraints() == num_constraints_before_adding_variable) {
214  // No constraints were added, meaning we found one.
215  LOG(INFO) << "Two constraints with the same name: "
216  << parsed_constraint.name;
217  return false;
218  }
219  if (!AreBoundsValid(parsed_constraint.lower_bound,
220  parsed_constraint.upper_bound)) {
221  return false;
222  }
223  lp_->SetConstraintBounds(row, parsed_constraint.lower_bound,
224  parsed_constraint.upper_bound);
225  for (int i = 0; i < parsed_constraint.variable_names.size(); ++i) {
226  const ColIndex variable =
227  lp_->FindOrCreateVariable(parsed_constraint.variable_names[i]);
228  lp_->SetCoefficient(row, variable, parsed_constraint.coefficients[i]);
229  }
230  }
231  return true;
232 }
233 
234 bool LPParser::SetVariableBounds(ColIndex col, Fractional lb, Fractional ub) {
235  if (bounded_variables_.find(col) == bounded_variables_.end()) {
236  // The variable was not bounded yet, thus reset its bounds.
237  bounded_variables_.insert(col);
239  }
240  // Set the bounds only if their stricter and valid.
241  lb = std::max(lb, lp_->variable_lower_bounds()[col]);
242  ub = std::min(ub, lp_->variable_upper_bounds()[col]);
243  if (!AreBoundsValid(lb, ub)) return false;
244  lp_->SetVariableBounds(col, lb, ub);
245  return true;
246 }
247 
248 TokenType ConsumeToken(absl::string_view* sp, std::string* consumed_name,
249  double* consumed_coeff) {
250  DCHECK(consumed_name != nullptr);
251  DCHECK(consumed_coeff != nullptr);
252  // We use LazyRE2 everywhere so that all the patterns are just compiled once
253  // when they are needed for the first time. This speed up the code
254  // significantly. Note that the use of LazyRE2 is thread safe.
255  static const LazyRE2 kEndPattern = {R"(\s*)"};
256 
257  // There is nothing more to consume.
258  if (sp->empty() || RE2::FullMatch(*sp, *kEndPattern)) {
259  return TokenType::END;
260  }
261 
262  // Return NAME if the next token is a line name, or integer variable list
263  // indicator.
264  static const LazyRE2 kNamePattern1 = {R"(\s*(\w[\w[\]]*):)"};
265  static const LazyRE2 kNamePattern2 = {R"((?i)\s*(int)\s*:?)"};
266  static const LazyRE2 kNamePattern3 = {R"((?i)\s*(bin)\s*:?)"};
267  if (RE2::Consume(sp, *kNamePattern1, consumed_name)) return TokenType::NAME;
268  if (RE2::Consume(sp, *kNamePattern2, consumed_name)) return TokenType::NAME;
269  if (RE2::Consume(sp, *kNamePattern3, consumed_name)) return TokenType::NAME;
270 
271  // Return SIGN_* if the next token is a relation sign.
272  static const LazyRE2 kLePattern = {R"(\s*<=?)"};
273  if (RE2::Consume(sp, *kLePattern)) return TokenType::SIGN_LE;
274  static const LazyRE2 kEqPattern = {R"(\s*=)"};
275  if (RE2::Consume(sp, *kEqPattern)) return TokenType::SIGN_EQ;
276  static const LazyRE2 kGePattern = {R"(\s*>=?)"};
277  if (RE2::Consume(sp, *kGePattern)) return TokenType::SIGN_GE;
278 
279  // Return COMA if the next token is a coma.
280  static const LazyRE2 kComaPattern = {R"(\s*\,)"};
281  if (RE2::Consume(sp, *kComaPattern)) return TokenType::COMA;
282 
283  // Consume all plus and minus signs.
284  std::string sign;
285  int minus_count = 0;
286  static const LazyRE2 kSignPattern = {R"(\s*([-+]{1}))"};
287  while (RE2::Consume(sp, *kSignPattern, &sign)) {
288  if (sign == "-") minus_count++;
289  }
290 
291  // Return INF if the next token is an infinite value.
292  static const LazyRE2 kInfPattern = {R"((?i)\s*inf)"};
293  if (RE2::Consume(sp, *kInfPattern)) {
294  *consumed_coeff = minus_count % 2 == 0 ? kInfinity : -kInfinity;
295  return TokenType::INF;
296  }
297 
298  // Check if the next token is a value. If it is infinite return INF.
299  std::string coeff;
300  bool has_value = false;
301  static const LazyRE2 kValuePattern = {
302  R"(\s*([0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?))"};
303  if (RE2::Consume(sp, *kValuePattern, &coeff)) {
304  if (!absl::SimpleAtod(coeff, consumed_coeff)) {
305  // Note: If absl::SimpleAtod(), Consume(), and kValuePattern are correct,
306  // this should never happen.
307  LOG(ERROR) << "Text: " << coeff << " was matched by RE2 to be "
308  << "a floating point number, but absl::SimpleAtod() failed.";
309  return TokenType::ERROR;
310  }
311  if (!IsFinite(*consumed_coeff)) {
312  VLOG(1) << "Value " << coeff << " treated as infinite.";
313  return TokenType::INF;
314  }
315  has_value = true;
316  } else {
317  *consumed_coeff = 1.0;
318  }
319  if (minus_count % 2 == 1) *consumed_coeff *= -1.0;
320 
321  // Return ADDAND (coefficient and name) if the next token is a variable name.
322  // Otherwise, if we found a finite value previously, return VALUE.
323  // Otherwise, return ERROR.
324  std::string multiplication;
325  static const LazyRE2 kAddandPattern = {R"(\s*(\*?)\s*([a-zA-Z_)][\w[\])]*))"};
326  if (RE2::Consume(sp, *kAddandPattern, &multiplication, consumed_name)) {
327  if (!multiplication.empty() && !has_value) return TokenType::ERROR;
328  return TokenType::ADDAND;
329  } else if (has_value) {
330  return TokenType::VALUE;
331  }
332 
333  return TokenType::ERROR;
334 }
335 
336 TokenType LPParser::ConsumeToken(absl::string_view* sp) {
337  using ::operations_research::glop::ConsumeToken;
338  return ConsumeToken(sp, &consumed_name_, &consumed_coeff_);
339 }
340 
341 } // namespace
342 
343 StatusOr<ParsedConstraint> ParseConstraint(absl::string_view constraint) {
344  ParsedConstraint parsed_constraint;
345  // Get the name, if present.
346  absl::string_view constraint_copy(constraint);
347  std::string consumed_name;
348  Fractional consumed_coeff;
349  if (ConsumeToken(&constraint_copy, &consumed_name, &consumed_coeff) ==
350  TokenType::NAME) {
351  parsed_constraint.name = consumed_name;
352  constraint = constraint_copy;
353  }
354 
355  Fractional left_bound;
356  Fractional right_bound;
357  TokenType left_sign(TokenType::END);
358  TokenType right_sign(TokenType::END);
359  absl::flat_hash_set<std::string> used_variables;
360 
361  // Get the left bound and the relation sign, if present.
362  TokenType token_type =
363  ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
364  if (TokenIsBound(token_type)) {
365  left_bound = consumed_coeff;
366  left_sign = ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
367  if (left_sign != TokenType::SIGN_LE && left_sign != TokenType::SIGN_EQ &&
368  left_sign != TokenType::SIGN_GE) {
369  return absl::InvalidArgumentError(
370  "Expected an equality/inequality sign for the left bound.");
371  }
372  token_type = ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
373  }
374 
375  // Get the addands, if present.
376  while (token_type == TokenType::ADDAND) {
377  if (used_variables.contains(consumed_name)) {
378  return absl::InvalidArgumentError(
379  absl::StrCat("Duplicate variable name: ", consumed_name));
380  }
381  used_variables.insert(consumed_name);
382  parsed_constraint.variable_names.push_back(consumed_name);
383  parsed_constraint.coefficients.push_back(consumed_coeff);
384  token_type = ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
385  }
386 
387  // If the left sign was EQ there can be no right side.
388  if (left_sign == TokenType::SIGN_EQ && token_type != TokenType::END) {
389  return absl::InvalidArgumentError(
390  "Equality constraints can have only one bound.");
391  }
392 
393  // Get the right sign and the right bound, if present.
394  if (token_type != TokenType::END) {
395  right_sign = token_type;
396  if (right_sign != TokenType::SIGN_LE && right_sign != TokenType::SIGN_EQ &&
397  right_sign != TokenType::SIGN_GE) {
398  return absl::InvalidArgumentError(
399  "Expected an equality/inequality sign for the right bound.");
400  }
401  // If the right sign is EQ, there can be no left side.
402  if (left_sign != TokenType::END && right_sign == TokenType::SIGN_EQ) {
403  return absl::InvalidArgumentError(
404  "Equality constraints can have only one bound.");
405  }
406  if (!TokenIsBound(
407  ConsumeToken(&constraint, &consumed_name, &consumed_coeff))) {
408  return absl::InvalidArgumentError("Bound value was expected.");
409  }
410  right_bound = consumed_coeff;
411  if (ConsumeToken(&constraint, &consumed_name, &consumed_coeff) !=
412  TokenType::END) {
413  return absl::InvalidArgumentError(
414  absl::StrCat("End of input was expected, found: ", constraint));
415  }
416  }
417 
418  // There was no constraint!
419  if (left_sign == TokenType::END && right_sign == TokenType::END) {
420  return absl::InvalidArgumentError("The input constraint was empty.");
421  }
422 
423  // Calculate bounds to set.
424  parsed_constraint.lower_bound = -kInfinity;
425  parsed_constraint.upper_bound = kInfinity;
426  if (left_sign == TokenType::SIGN_LE || left_sign == TokenType::SIGN_EQ) {
427  parsed_constraint.lower_bound = left_bound;
428  }
429  if (left_sign == TokenType::SIGN_GE || left_sign == TokenType::SIGN_EQ) {
430  parsed_constraint.upper_bound = left_bound;
431  }
432  if (right_sign == TokenType::SIGN_LE || right_sign == TokenType::SIGN_EQ) {
433  parsed_constraint.upper_bound =
434  std::min(parsed_constraint.upper_bound, right_bound);
435  }
436  if (right_sign == TokenType::SIGN_GE || right_sign == TokenType::SIGN_EQ) {
437  parsed_constraint.lower_bound =
438  std::max(parsed_constraint.lower_bound, right_bound);
439  }
440  return parsed_constraint;
441 }
442 
443 bool ParseLp(absl::string_view model, LinearProgram* lp) {
444  LPParser parser;
445  return parser.Parse(model, lp);
446 }
447 
448 } // namespace glop
449 
450 absl::StatusOr<MPModelProto> ModelProtoFromLpFormat(absl::string_view model) {
452  if (!ParseLp(model, &lp)) {
453  return absl::InvalidArgumentError("Parsing error, see LOGs for details.");
454  }
457  return model_proto;
458 }
459 
460 } // namespace operations_research
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
Definition: proto_utils.cc:20
int64_t min
Definition: alldiff_cst.cc:139
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:326
ColIndex FindOrCreateVariable(const std::string &variable_id)
Definition: lp_data.cc:205
bool ParseLp(absl::string_view model, LinearProgram *lp)
Definition: lp_parser.cc:443
#define VLOG(verboselevel)
Definition: base/logging.h:983
const int ERROR
Definition: log_severity.h:32
void SetVariableType(ColIndex col, VariableType type)
Definition: lp_data.cc:236
#define LOG(severity)
Definition: base/logging.h:420
ColIndex col
Definition: markowitz.cc:183
GRBmodel * model
RowIndex FindOrCreateConstraint(const std::string &constraint_id)
Definition: lp_data.cc:218
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:309
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:331
RowIndex row
Definition: markowitz.cc:182
bool IsFinite(Fractional value)
Definition: lp_types.h:91
bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.h:693
const DenseRow & objective_coefficients() const
Definition: lp_data.h:223
int64_t max
Definition: alldiff_cst.cc:140
std::vector< Fractional > coefficients
Definition: lp_parser.h:107
std::vector< std::string > variable_names
Definition: lp_parser.h:103
const double kInfinity
Definition: lp_types.h:84
CpModelProto const * model_proto
#define DCHECK(condition)
Definition: base/logging.h:889
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:249
absl::StatusOr< MPModelProto > ModelProtoFromLpFormat(absl::string_view model)
Definition: lp_parser.cc:450
void SetMaximizationProblem(bool maximize)
Definition: lp_data.cc:343
Collection of objects used to extend the Constraint Solver library.
StatusOr< ParsedConstraint > ParseConstraint(absl::string_view constraint)
Definition: lp_parser.cc:343
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Definition: lp_data.cc:317
const int INFO
Definition: log_severity.h:31