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"
31#include "ortools/linear_solver/linear_solver.pb.h"
40using ::absl::StatusOr;
55bool TokenIsBound(TokenType token_type) {
56 if (token_type == TokenType::VALUE || token_type == TokenType::INF) {
68 ABSL_MUST_USE_RESULT
bool Parse(absl::string_view
model, LinearProgram* lp);
71 bool ParseEmptyLine(absl::string_view line);
72 bool ParseObjective(absl::string_view objective);
73 bool ParseIntegerVariablesList(absl::string_view line);
75 TokenType ConsumeToken(absl::string_view* sp);
84 std::string consumed_name_;
87 std::set<ColIndex> bounded_variables_;
90bool LPParser::Parse(absl::string_view
model, LinearProgram* lp) {
92 bounded_variables_.clear();
95 std::vector<absl::string_view> lines =
96 absl::StrSplit(
model,
';', absl::SkipEmpty());
97 bool has_objective =
false;
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;
112 if (bounded_variables_.find(
col) == bounded_variables_.end()) {
121bool LPParser::ParseEmptyLine(absl::string_view line) {
122 if (ConsumeToken(&line) == TokenType::END)
return true;
126bool LPParser::ParseObjective(absl::string_view objective) {
128 if (ConsumeToken(&objective) != TokenType::NAME)
return false;
129 if (absl::EqualsIgnoreCase(consumed_name_,
"min")) {
131 }
else if (absl::EqualsIgnoreCase(consumed_name_,
"max")) {
138 TokenType token_type = ConsumeToken(&objective);
139 if (token_type == TokenType::VALUE) {
141 token_type = ConsumeToken(&objective);
147 while (token_type == TokenType::ADDAND) {
151 token_type = ConsumeToken(&objective);
153 return token_type == TokenType::END;
156bool LPParser::ParseIntegerVariablesList(absl::string_view line) {
158 bool binary_list =
false;
159 if (ConsumeToken(&line) != TokenType::NAME)
return false;
160 if (absl::EqualsIgnoreCase(consumed_name_,
"bin")) {
162 }
else if (!absl::EqualsIgnoreCase(consumed_name_,
"int")) {
167 TokenType token_type = ConsumeToken(&line);
168 while (token_type == TokenType::ADDAND) {
169 if (consumed_coeff_ != 1.0)
return false;
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);
180 if (token_type != TokenType::END)
return false;
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();
192 if (parsed_constraint.name.empty() &&
193 parsed_constraint.coefficients.size() == 1 &&
194 parsed_constraint.coefficients[0] == 1.0) {
197 if (!SetVariableBounds(
col, parsed_constraint.lower_bound,
198 parsed_constraint.upper_bound)) {
202 const RowIndex num_constraints_before_adding_variable =
210 parsed_constraint.name.empty()
213 if (lp_->
num_constraints() == num_constraints_before_adding_variable) {
215 LOG(
INFO) <<
"Two constraints with the same name: "
216 << parsed_constraint.name;
220 parsed_constraint.upper_bound)) {
224 parsed_constraint.upper_bound);
225 for (
int i = 0; i < parsed_constraint.variable_names.size(); ++i) {
226 const ColIndex variable =
235 if (bounded_variables_.find(
col) == bounded_variables_.end()) {
237 bounded_variables_.insert(
col);
248TokenType ConsumeToken(absl::string_view* sp, std::string* consumed_name,
249 double* consumed_coeff) {
250 DCHECK(consumed_name !=
nullptr);
251 DCHECK(consumed_coeff !=
nullptr);
255 static const LazyRE2 kEndPattern = {R
"(\s*)"};
258 if (sp->empty() || RE2::FullMatch(*sp, *kEndPattern)) {
259 return TokenType::END;
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;
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;
280 static const LazyRE2 kComaPattern = {R
"(\s*\,)"};
281 if (RE2::Consume(sp, *kComaPattern))
return TokenType::COMA;
286 static const LazyRE2 kSignPattern = {R
"(\s*([-+]{1}))"};
287 while (RE2::Consume(sp, *kSignPattern, &sign)) {
288 if (sign ==
"-") minus_count++;
292 static const LazyRE2 kInfPattern = {R
"((?i)\s*inf)"};
293 if (RE2::Consume(sp, *kInfPattern)) {
295 return TokenType::INF;
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)) {
307 LOG(ERROR) <<
"Text: " <<
coeff <<
" was matched by RE2 to be "
308 <<
"a floating point number, but absl::SimpleAtod() failed.";
312 VLOG(1) <<
"Value " <<
coeff <<
" treated as infinite.";
313 return TokenType::INF;
317 *consumed_coeff = 1.0;
319 if (minus_count % 2 == 1) *consumed_coeff *= -1.0;
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)) {
328 return TokenType::ADDAND;
329 }
else if (has_value) {
330 return TokenType::VALUE;
336TokenType LPParser::ConsumeToken(absl::string_view* sp) {
337 using ::operations_research::glop::ConsumeToken;
338 return ConsumeToken(sp, &consumed_name_, &consumed_coeff_);
346 absl::string_view constraint_copy(constraint);
347 std::string consumed_name;
349 if (ConsumeToken(&constraint_copy, &consumed_name, &consumed_coeff) ==
351 parsed_constraint.
name = consumed_name;
352 constraint = constraint_copy;
357 TokenType left_sign(TokenType::END);
358 TokenType right_sign(TokenType::END);
359 absl::flat_hash_set<std::string> used_variables;
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.");
372 token_type = ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
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));
381 used_variables.insert(consumed_name);
383 parsed_constraint.
coefficients.push_back(consumed_coeff);
384 token_type = ConsumeToken(&constraint, &consumed_name, &consumed_coeff);
388 if (left_sign == TokenType::SIGN_EQ && token_type != TokenType::END) {
389 return absl::InvalidArgumentError(
390 "Equality constraints can have only one bound.");
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.");
402 if (left_sign != TokenType::END && right_sign == TokenType::SIGN_EQ) {
403 return absl::InvalidArgumentError(
404 "Equality constraints can have only one bound.");
407 ConsumeToken(&constraint, &consumed_name, &consumed_coeff))) {
408 return absl::InvalidArgumentError(
"Bound value was expected.");
410 right_bound = consumed_coeff;
411 if (ConsumeToken(&constraint, &consumed_name, &consumed_coeff) !=
413 return absl::InvalidArgumentError(
414 absl::StrCat(
"End of input was expected, found: ", constraint));
419 if (left_sign == TokenType::END && right_sign == TokenType::END) {
420 return absl::InvalidArgumentError(
"The input constraint was empty.");
426 if (left_sign == TokenType::SIGN_LE || left_sign == TokenType::SIGN_EQ) {
429 if (left_sign == TokenType::SIGN_GE || left_sign == TokenType::SIGN_EQ) {
432 if (right_sign == TokenType::SIGN_LE || right_sign == TokenType::SIGN_EQ) {
436 if (right_sign == TokenType::SIGN_GE || right_sign == TokenType::SIGN_EQ) {
440 return parsed_constraint;
445 return parser.Parse(
model, lp);
453 return absl::InvalidArgumentError(
"Parsing error, see LOGs for details.");
#define DCHECK(condition)
#define VLOG(verboselevel)
void SetVariableBounds(ColIndex col, Fractional lower_bound, Fractional upper_bound)
void SetObjectiveOffset(Fractional objective_offset)
void SetCoefficient(RowIndex row, ColIndex col, Fractional value)
ColIndex FindOrCreateVariable(const std::string &variable_id)
void SetConstraintBounds(RowIndex row, Fractional lower_bound, Fractional upper_bound)
RowIndex FindOrCreateConstraint(const std::string &constraint_id)
const DenseRow & variable_upper_bounds() const
void SetVariableType(ColIndex col, VariableType type)
const DenseRow & objective_coefficients() const
void SetObjectiveCoefficient(ColIndex col, Fractional value)
ColIndex num_variables() const
RowIndex CreateNewConstraint()
const DenseRow & variable_lower_bounds() const
void SetMaximizationProblem(bool maximize)
RowIndex num_constraints() const
CpModelProto const * model_proto
bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound)
StatusOr< ParsedConstraint > ParseConstraint(absl::string_view constraint)
bool ParseLp(absl::string_view model, LinearProgram *lp)
void LinearProgramToMPModelProto(const LinearProgram &input, MPModelProto *output)
bool IsFinite(Fractional value)
Collection of objects used to extend the Constraint Solver library.
absl::StatusOr< MPModelProto > ModelProtoFromLpFormat(absl::string_view model)
std::vector< std::string > variable_names
std::vector< Fractional > coefficients