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