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)
35 #if defined(USE_LP_PARSER)
41 using StringPiece = ::re2::StringPiece;
42 using ::absl::StatusOr;
44 enum class TokenType {
57 bool TokenIsBound(TokenType token_type) {
58 if (token_type == TokenType::VALUE || token_type == TokenType::INF) {
70 ABSL_MUST_USE_RESULT
bool Parse(absl::string_view
model, LinearProgram* lp);
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);
86 std::string consumed_name_;
89 std::set<ColIndex> bounded_variables_;
92 bool LPParser::Parse(absl::string_view
model, LinearProgram* lp) {
94 bounded_variables_.clear();
97 std::vector<StringPiece> lines =
98 absl::StrSplit(
model,
';', absl::SkipEmpty());
99 bool has_objective =
false;
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;
113 for (ColIndex
col(0);
col < lp_->num_variables(); ++
col) {
114 if (bounded_variables_.find(
col) == bounded_variables_.end()) {
123 bool LPParser::ParseEmptyLine(StringPiece
line) {
124 if (ConsumeToken(&
line) == TokenType::END)
return true;
128 bool LPParser::ParseObjective(StringPiece objective) {
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);
140 TokenType token_type = ConsumeToken(&objective);
141 if (token_type == TokenType::VALUE) {
142 lp_->SetObjectiveOffset(consumed_coeff_);
143 token_type = ConsumeToken(&objective);
145 lp_->SetObjectiveOffset(0.0);
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);
155 return token_type == TokenType::END;
158 bool LPParser::ParseIntegerVariablesList(StringPiece
line) {
160 bool binary_list =
false;
161 if (ConsumeToken(&
line) != TokenType::NAME)
return false;
162 if (absl::EqualsIgnoreCase(consumed_name_,
"bin")) {
164 }
else if (!absl::EqualsIgnoreCase(consumed_name_,
"int")) {
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_);
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);
182 if (token_type != TokenType::END)
return false;
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();
194 if (parsed_constraint.name.empty() &&
195 parsed_constraint.coefficients.size() == 1 &&
196 parsed_constraint.coefficients[0] == 1.0) {
198 lp_->FindOrCreateVariable(parsed_constraint.variable_names[0]);
199 if (!SetVariableBounds(
col, parsed_constraint.lower_bound,
200 parsed_constraint.upper_bound)) {
204 const RowIndex num_constraints_before_adding_variable =
205 lp_->num_constraints();
212 parsed_constraint.name.empty()
213 ? lp_->CreateNewConstraint()
214 : lp_->FindOrCreateConstraint(parsed_constraint.name);
215 if (lp_->num_constraints() == num_constraints_before_adding_variable) {
217 LOG(INFO) <<
"Two constraints with the same name: "
218 << parsed_constraint.name;
222 parsed_constraint.upper_bound)) {
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]);
237 if (bounded_variables_.find(
col) == bounded_variables_.end()) {
239 bounded_variables_.insert(
col);
243 lb =
std::max(lb, lp_->variable_lower_bounds()[
col]);
244 ub =
std::min(ub, lp_->variable_upper_bounds()[
col]);
246 lp_->SetVariableBounds(
col, lb, ub);
250 TokenType ConsumeToken(StringPiece* sp, std::string* consumed_name,
251 double* consumed_coeff) {
252 DCHECK(consumed_name !=
nullptr);
253 DCHECK(consumed_coeff !=
nullptr);
257 static const LazyRE2 kEndPattern = {R
"(\s*)"};
260 if (sp->empty() || RE2::FullMatch(*sp, *kEndPattern)) {
261 return TokenType::END;
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;
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;
282 static const LazyRE2 kComaPattern = {R
"(\s*\,)"};
283 if (RE2::Consume(sp, *kComaPattern))
return TokenType::COMA;
288 static const LazyRE2 kSignPattern = {R
"(\s*([-+]{1}))"};
289 while (RE2::Consume(sp, *kSignPattern, &sign)) {
290 if (sign ==
"-") minus_count++;
294 static const LazyRE2 kInfPattern = {R
"((?i)\s*inf)"};
295 if (RE2::Consume(sp, *kInfPattern)) {
297 return TokenType::INF;
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)) {
309 LOG(ERROR) <<
"Text: " << coeff <<
" was matched by RE2 to be "
310 <<
"a floating point number, but absl::SimpleAtod() failed.";
311 return TokenType::ERROR;
314 VLOG(1) <<
"Value " << coeff <<
" treated as infinite.";
315 return TokenType::INF;
319 *consumed_coeff = 1.0;
321 if (minus_count % 2 == 1) *consumed_coeff *= -1.0;
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;
335 return TokenType::ERROR;
338 TokenType LPParser::ConsumeToken(StringPiece* sp) {
339 using ::operations_research::glop::ConsumeToken;
340 return ConsumeToken(sp, &consumed_name_, &consumed_coeff_);
345 StatusOr<ParsedConstraint> ParseConstraint(absl::string_view constraint_view) {
346 ParsedConstraint parsed_constraint;
348 StringPiece constraint{constraint_view};
349 StringPiece constraint_copy{constraint};
350 std::string consumed_name;
352 if (ConsumeToken(&constraint_copy, &consumed_name, &consumed_coeff) ==
354 parsed_constraint.name = consumed_name;
355 constraint = constraint_copy;
360 TokenType left_sign(TokenType::END);
361 TokenType right_sign(TokenType::END);
362 absl::flat_hash_set<std::string> used_variables;
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.");
375 token_type = ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
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));
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);
391 if (left_sign == TokenType::SIGN_EQ && token_type != TokenType::END) {
392 return absl::InvalidArgumentError(
393 "Equality constraints can have only one bound.");
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.");
405 if (left_sign != TokenType::END && right_sign == TokenType::SIGN_EQ) {
406 return absl::InvalidArgumentError(
407 "Equality constraints can have only one bound.");
410 ConsumeToken(&constraint, &consumed_name, &consumed_coeff))) {
411 return absl::InvalidArgumentError(
"Bound value was expected.");
413 right_bound = consumed_coeff;
414 if (ConsumeToken(&constraint, &consumed_name, &consumed_coeff) !=
416 return absl::InvalidArgumentError(absl::StrCat(
417 "End of input was expected, found: ", constraint.as_string()));
422 if (left_sign == TokenType::END && right_sign == TokenType::END) {
423 return absl::InvalidArgumentError(
"The input constraint was empty.");
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;
432 if (left_sign == TokenType::SIGN_GE || left_sign == TokenType::SIGN_EQ) {
433 parsed_constraint.upper_bound = left_bound;
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);
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);
443 return parsed_constraint;
446 bool ParseLp(absl::string_view
model, LinearProgram* lp) {
448 return parser.Parse(
model, lp);
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.");
CpModelProto const * model_proto
bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound)
constexpr double kInfinity
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
bool IsFinite(Fractional value)
Collection of objects used to extend the Constraint Solver library.
#define VLOG(verboselevel)