OR-Tools  9.3
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"
31#include "ortools/linear_solver/linear_solver.pb.h"
33#include "re2/re2.h"
34
35namespace operations_research {
36namespace glop {
37
38namespace {
39
40using ::absl::StatusOr;
41
42enum 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
55bool 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.
63class 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
90bool 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
121bool LPParser::ParseEmptyLine(absl::string_view line) {
122 if (ConsumeToken(&line) == TokenType::END) return true;
123 return false;
124}
125
126bool 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
156bool 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
184bool 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
234bool 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
248TokenType 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
336TokenType 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
343StatusOr<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
443bool ParseLp(absl::string_view model, LinearProgram* lp) {
444 LPParser parser;
445 return parser.Parse(model, lp);
446}
447
448} // namespace glop
449
450absl::StatusOr<MPModelProto> ModelProtoFromLpFormat(absl::string_view model) {
452 if (!ParseLp(model, &lp)) {
453 return absl::InvalidArgumentError("Parsing error, see LOGs for details.");
454 }
455 MPModelProto model_proto;
457 return model_proto;
458}
459
460} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define VLOG(verboselevel)
Definition: base/logging.h:984
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:249
void SetObjectiveOffset(Fractional objective_offset)
Definition: lp_data.cc:331
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
Definition: lp_data.cc:317
ColIndex FindOrCreateVariable(const std::string &variable_id)
Definition: lp_data.cc:205
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.cc:309
RowIndex FindOrCreateConstraint(const std::string &constraint_id)
Definition: lp_data.cc:218
const DenseRow & variable_upper_bounds() const
Definition: lp_data.h:232
void SetVariableType(ColIndex col, VariableType type)
Definition: lp_data.cc:236
const DenseRow & objective_coefficients() const
Definition: lp_data.h:223
void SetObjectiveCoefficient(ColIndex col, Fractional value)
Definition: lp_data.cc:326
const DenseRow & variable_lower_bounds() const
Definition: lp_data.h:229
void SetMaximizationProblem(bool maximize)
Definition: lp_data.cc:343
CpModelProto const * model_proto
GRBmodel * model
const int INFO
Definition: log_severity.h:31
const int ERROR
Definition: log_severity.h:32
ColIndex col
Definition: markowitz.cc:183
RowIndex row
Definition: markowitz.cc:182
bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound)
Definition: lp_data.h:696
StatusOr< ParsedConstraint > ParseConstraint(absl::string_view constraint)
Definition: lp_parser.cc:343
bool ParseLp(absl::string_view model, LinearProgram *lp)
Definition: lp_parser.cc:443
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
Definition: proto_utils.cc:20
bool IsFinite(Fractional value)
Definition: lp_types.h:91
const double kInfinity
Definition: lp_types.h:84
Collection of objects used to extend the Constraint Solver library.
absl::StatusOr< MPModelProto > ModelProtoFromLpFormat(absl::string_view model)
Definition: lp_parser.cc:450
std::vector< std::string > variable_names
Definition: lp_parser.h:103
std::vector< Fractional > coefficients
Definition: lp_parser.h:107
const double coeff