OR-Tools  9.3
model_exporter.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 <algorithm>
17#include <cmath>
18#include <limits>
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/ascii.h"
24#include "absl/strings/match.h"
25#include "absl/strings/str_cat.h"
26#include "absl/strings/str_format.h"
31#include "ortools/linear_solver/linear_solver.pb.h"
33
34ABSL_FLAG(bool, lp_log_invalid_name, false, "DEPRECATED.");
35
36namespace operations_research {
37namespace {
38
39constexpr double kInfinity = std::numeric_limits<double>::infinity();
40
41class LineBreaker {
42 public:
43 explicit LineBreaker(int max_line_size)
44 : max_line_size_(max_line_size), line_size_(0), output_() {}
45 // Lines are broken in such a way that:
46 // - Strings that are given to Append() are never split.
47 // - Lines are split so that their length doesn't exceed the max length;
48 // unless a single string given to Append() exceeds that length (in which
49 // case it will be put alone on a single unsplit line).
50 void Append(const std::string& s);
51
52 // Returns true if string s will fit on the current line without adding a
53 // carriage return.
54 bool WillFit(const std::string& s) {
55 return line_size_ + s.size() < max_line_size_;
56 }
57
58 // "Consumes" size characters on the line. Used when starting the constraint
59 // lines.
60 void Consume(int size) { line_size_ += size; }
61
62 std::string GetOutput() const { return output_; }
63
64 private:
65 int max_line_size_;
66 int line_size_;
67 std::string output_;
68};
69
70void LineBreaker::Append(const std::string& s) {
71 line_size_ += s.size();
72 if (line_size_ > max_line_size_) {
73 line_size_ = s.size();
74 absl::StrAppend(&output_, "\n ");
75 }
76 absl::StrAppend(&output_, s);
77}
78
79class MPModelProtoExporter {
80 public:
81 explicit MPModelProtoExporter(const MPModelProto& model);
82 bool ExportModelAsLpFormat(const MPModelExportOptions& options,
83 std::string* output);
84 bool ExportModelAsMpsFormat(const MPModelExportOptions& options,
85 std::string* output);
86
87 private:
88 // Computes the number of continuous, integer and binary variables.
89 // Called by ExportModelAsLpFormat() and ExportModelAsMpsFormat().
90 void Setup();
91
92 // Computes smart column widths for free MPS format.
93 void ComputeMpsSmartColumnWidths(bool obfuscated);
94
95 // Processes all the proto.name() fields and returns the result in a vector.
96 //
97 // If 'obfuscate' is true, none of names are actually used, and this just
98 // returns a vector of 'prefix' + proto index (1-based).
99 //
100 // If it is false, this tries to keep the original names, but:
101 // - if the first character is forbidden (or name is empty), '_' is added at
102 // the beginning of name.
103 // - all the other forbidden characters are replaced by '_'.
104 // To avoid name conflicts, a '_' followed by an integer is appended to the
105 // result.
106 //
107 // If a name is longer than the maximum allowed name length, the obfuscated
108 // name is used.
109 //
110 // Therefore, a name "$20<=40" for proto #3 could be "_$20__40_1".
111 template <class ListOfProtosWithNameFields>
112 std::vector<std::string> ExtractAndProcessNames(
113 const ListOfProtosWithNameFields& proto, const std::string& prefix,
114 bool obfuscate, bool log_invalid_names,
115 const std::string& forbidden_first_chars,
116 const std::string& forbidden_chars);
117
118 // Appends a general "Comment" section with useful metadata about the model
119 // to "output".
120 // Note(user): there may be less variables in output than in the original
121 // model, as unused variables are not shown by default. Similarly, there
122 // may be more constraints in a .lp file as in the original model as
123 // a constraint lhs <= term <= rhs will be output as the two constraints
124 // term >= lhs and term <= rhs.
125 void AppendComments(const std::string& separator, std::string* output) const;
126
127 // Appends an MPConstraintProto to the output text. If the constraint has
128 // both an upper and lower bound that are not equal, it splits the constraint
129 // into two constraints, one for the left hand side (_lhs) and one for right
130 // hand side (_rhs).
131 bool AppendConstraint(const MPConstraintProto& ct_proto,
132 const std::string& name, LineBreaker& line_breaker,
133 std::vector<bool>& show_variable, std::string* output);
134
135 // Clears "output" and writes a term to it, in "LP" format. Returns false on
136 // error (for example, var_index is out of range).
137 bool WriteLpTerm(int var_index, double coefficient,
138 std::string* output) const;
139
140 // Appends a pair name, value to "output", formatted to comply with the MPS
141 // standard.
142 void AppendMpsPair(const std::string& name, double value,
143 std::string* output) const;
144
145 // Appends the head of a line, consisting of an id and a name to output.
146 void AppendMpsLineHeader(const std::string& id, const std::string& name,
147 std::string* output) const;
148
149 // Same as AppendMpsLineHeader. Appends an extra new-line at the end the
150 // string pointed to by output.
151 void AppendMpsLineHeaderWithNewLine(const std::string& id,
152 const std::string& name,
153 std::string* output) const;
154
155 // Appends an MPS term in various contexts. The term consists of a head name,
156 // a name, and a value. If the line is not empty, then only the pair
157 // (name, value) is appended. The number of columns, limited to 2 by the MPS
158 // format is also taken care of.
159 void AppendMpsTermWithContext(const std::string& head_name,
160 const std::string& name, double value,
161 std::string* output);
162
163 // Appends a new-line if two columns are already present on the MPS line.
164 // Used by and in complement to AppendMpsTermWithContext.
165 void AppendNewLineIfTwoColumns(std::string* output);
166
167 // When 'integrality' is true, appends columns corresponding to integer
168 // variables. Appends the columns for non-integer variables otherwise.
169 // The sparse matrix must be passed as a vector of columns ('transpose').
170 void AppendMpsColumns(
171 bool integrality,
172 const std::vector<std::vector<std::pair<int, double>>>& transpose,
173 std::string* output);
174
175 // Appends a line describing the bound of a variablenew-line if two columns
176 // are already present on the MPS line.
177 // Used by and in complement to AppendMpsTermWithContext.
178 void AppendMpsBound(const std::string& bound_type, const std::string& name,
179 double value, std::string* output) const;
180
181 const MPModelProto& proto_;
182
183 // Vector of variable names as they will be exported.
184 std::vector<std::string> exported_variable_names_;
185
186 // Vector of constraint names as they will be exported.
187 std::vector<std::string> exported_constraint_names_;
188
189 // Vector of general constraint names as they will be exported.
190 std::vector<std::string> exported_general_constraint_names_;
191
192 // Number of integer variables in proto_.
193 int num_integer_variables_;
194
195 // Number of binary variables in proto_.
196 int num_binary_variables_;
197
198 // Number of continuous variables in proto_.
199 int num_continuous_variables_;
200
201 // Current MPS file column number.
202 int current_mps_column_;
203
204 // Format for MPS file lines.
205 std::unique_ptr<absl::ParsedFormat<'s', 's'>> mps_header_format_;
206 std::unique_ptr<absl::ParsedFormat<'s', 's'>> mps_format_;
207
208 DISALLOW_COPY_AND_ASSIGN(MPModelProtoExporter);
209};
210
211} // namespace
212
213absl::StatusOr<std::string> ExportModelAsLpFormat(
214 const MPModelProto& model, const MPModelExportOptions& options) {
215 for (const MPGeneralConstraintProto& general_constraint :
216 model.general_constraint()) {
217 if (!general_constraint.has_indicator_constraint()) {
218 return absl::InvalidArgumentError(
219 "Non-indicator general constraints are not supported.");
220 }
221 }
222 MPModelProtoExporter exporter(model);
223 std::string output;
224 if (!exporter.ExportModelAsLpFormat(options, &output)) {
225 return absl::InvalidArgumentError("Unable to export model.");
226 }
227 return output;
228}
229
230absl::StatusOr<std::string> ExportModelAsMpsFormat(
231 const MPModelProto& model, const MPModelExportOptions& options) {
232 if (model.general_constraint_size() > 0) {
233 return absl::InvalidArgumentError("General constraints are not supported.");
234 }
235 MPModelProtoExporter exporter(model);
236 std::string output;
237 if (!exporter.ExportModelAsMpsFormat(options, &output)) {
238 return absl::InvalidArgumentError("Unable to export model.");
239 }
240 return output;
241}
242
243namespace {
244MPModelProtoExporter::MPModelProtoExporter(const MPModelProto& model)
245 : proto_(model),
246 num_integer_variables_(0),
247 num_binary_variables_(0),
248 num_continuous_variables_(0),
249 current_mps_column_(0) {}
250
251namespace {
252class NameManager {
253 public:
254 NameManager() : names_set_(), last_n_(1) {}
255 std::string MakeUniqueName(const std::string& name);
256
257 private:
258 absl::flat_hash_set<std::string> names_set_;
259 int last_n_;
260};
261
262std::string NameManager::MakeUniqueName(const std::string& name) {
263 std::string result = name;
264 // Find the 'n' so that "name_n" does not already exist.
265 int n = last_n_;
266 while (!names_set_.insert(result).second) {
267 result = absl::StrCat(name, "_", n);
268 ++n;
269 }
270 // We keep the last n used to avoid a quadratic behavior in case
271 // all the names are the same initially.
272 last_n_ = n;
273 return result;
274}
275
276// NOTE: As a special case, an empty name is treated as started with a forbidden
277// character (\0).
278std::string MakeExportableName(const std::string& name,
279 const std::string& forbidden_first_chars,
280 const std::string& forbidden_chars,
281 bool* found_forbidden_char) {
282 // Prepend with "_" all the names starting with a forbidden character.
283 *found_forbidden_char =
284 name.empty() || absl::StrContains(forbidden_first_chars, name[0]);
285 std::string exportable_name =
286 *found_forbidden_char ? absl::StrCat("_", name) : name;
287
288 // Replace all the other forbidden characters with "_".
289 for (char& c : exportable_name) {
290 if (absl::StrContains(forbidden_chars, c)) {
291 c = '_';
292 *found_forbidden_char = true;
293 }
294 }
295 return exportable_name;
296}
297} // namespace
298
299template <class ListOfProtosWithNameFields>
300std::vector<std::string> MPModelProtoExporter::ExtractAndProcessNames(
301 const ListOfProtosWithNameFields& proto, const std::string& prefix,
302 bool obfuscate, bool log_invalid_names,
303 const std::string& forbidden_first_chars,
304 const std::string& forbidden_chars) {
305 const int num_items = proto.size();
306 std::vector<std::string> result(num_items);
307 NameManager namer;
308 const int num_digits = absl::StrCat(num_items).size();
309 int i = 0;
310 for (const auto& item : proto) {
311 const std::string obfuscated_name =
312 absl::StrFormat("%s%0*d", prefix, num_digits, i);
313 if (obfuscate || !item.has_name()) {
314 result[i] = namer.MakeUniqueName(obfuscated_name);
315 LOG_IF(WARNING, log_invalid_names && !item.has_name())
316 << "Empty name detected, created new name: " << result[i];
317 } else {
318 bool found_forbidden_char = false;
319 const std::string exportable_name =
320 MakeExportableName(item.name(), forbidden_first_chars,
321 forbidden_chars, &found_forbidden_char);
322 result[i] = namer.MakeUniqueName(exportable_name);
323 LOG_IF(WARNING, log_invalid_names && found_forbidden_char)
324 << "Invalid character detected in " << item.name() << ". Changed to "
325 << result[i];
326 // If the name is too long, use the obfuscated name that is guaranteed
327 // to fit. If ever we are able to solve problems with 2^64 variables,
328 // their obfuscated names would fit within 20 characters.
329 const int kMaxNameLength = 255;
330 // Take care of "_rhs" or "_lhs" that may be added in the case of
331 // constraints with both right-hand side and left-hand side.
332 const int kMargin = 4;
333 if (result[i].size() > kMaxNameLength - kMargin) {
334 const std::string old_name = std::move(result[i]);
335 result[i] = namer.MakeUniqueName(obfuscated_name);
336 LOG_IF(WARNING, log_invalid_names) << "Name is too long: " << old_name
337 << " exported as: " << result[i];
338 }
339 }
340
341 // Prepare for the next round.
342 ++i;
343 }
344 return result;
345}
346
347void MPModelProtoExporter::AppendComments(const std::string& separator,
348 std::string* output) const {
349 const char* const sep = separator.c_str();
350 absl::StrAppendFormat(output, "%s Generated by MPModelProtoExporter\n", sep);
351 absl::StrAppendFormat(output, "%s %-16s : %s\n", sep, "Name",
352 proto_.has_name() ? proto_.name().c_str() : "NoName");
353 absl::StrAppendFormat(output, "%s %-16s : %s\n", sep, "Format", "Free");
354 absl::StrAppendFormat(
355 output, "%s %-16s : %d\n", sep, "Constraints",
356 proto_.constraint_size() + proto_.general_constraint_size());
357 absl::StrAppendFormat(output, "%s %-16s : %d\n", sep, "Variables",
358 proto_.variable_size());
359 absl::StrAppendFormat(output, "%s %-14s : %d\n", sep, "Binary",
360 num_binary_variables_);
361 absl::StrAppendFormat(output, "%s %-14s : %d\n", sep, "Integer",
362 num_integer_variables_);
363 absl::StrAppendFormat(output, "%s %-14s : %d\n", sep, "Continuous",
364 num_continuous_variables_);
365}
366
367namespace {
368
369std::string DoubleToStringWithForcedSign(double d) {
370 return absl::StrCat((d < 0 ? "" : "+"), (d));
371}
372
373std::string DoubleToString(double d) { return absl::StrCat((d)); }
374
375} // namespace
376
377bool MPModelProtoExporter::AppendConstraint(const MPConstraintProto& ct_proto,
378 const std::string& name,
379 LineBreaker& line_breaker,
380 std::vector<bool>& show_variable,
381 std::string* output) {
382 for (int i = 0; i < ct_proto.var_index_size(); ++i) {
383 const int var_index = ct_proto.var_index(i);
384 const double coeff = ct_proto.coefficient(i);
385 std::string term;
386 if (!WriteLpTerm(var_index, coeff, &term)) {
387 return false;
388 }
389 line_breaker.Append(term);
390 show_variable[var_index] = coeff != 0.0 || show_variable[var_index];
391 }
392
393 const double lb = ct_proto.lower_bound();
394 const double ub = ct_proto.upper_bound();
395 if (lb == ub) {
396 line_breaker.Append(absl::StrCat(" = ", DoubleToString(ub), "\n"));
397 absl::StrAppend(output, " ", name, ": ", line_breaker.GetOutput());
398 } else {
399 if (ub != +kInfinity) {
400 std::string rhs_name = name;
401 if (lb != -kInfinity) {
402 absl::StrAppend(&rhs_name, "_rhs");
403 }
404 absl::StrAppend(output, " ", rhs_name, ": ", line_breaker.GetOutput());
405 const std::string relation =
406 absl::StrCat(" <= ", DoubleToString(ub), "\n");
407 // Here we have to make sure we do not add the relation to the contents
408 // of line_breaker, which may be used in the subsequent clause.
409 if (!line_breaker.WillFit(relation)) absl::StrAppend(output, "\n ");
410 absl::StrAppend(output, relation);
411 }
412 if (lb != -kInfinity) {
413 std::string lhs_name = name;
414 if (ub != +kInfinity) {
415 absl::StrAppend(&lhs_name, "_lhs");
416 }
417 absl::StrAppend(output, " ", lhs_name, ": ", line_breaker.GetOutput());
418 const std::string relation =
419 absl::StrCat(" >= ", DoubleToString(lb), "\n");
420 if (!line_breaker.WillFit(relation)) absl::StrAppend(output, "\n ");
421 absl::StrAppend(output, relation);
422 }
423 }
424
425 return true;
426}
427
428bool MPModelProtoExporter::WriteLpTerm(int var_index, double coefficient,
429 std::string* output) const {
430 output->clear();
431 if (var_index < 0 || var_index >= proto_.variable_size()) {
432 LOG(DFATAL) << "Reference to out-of-bounds variable index # " << var_index;
433 return false;
434 }
435 if (coefficient != 0.0) {
436 *output = absl::StrCat(DoubleToStringWithForcedSign(coefficient), " ",
437 exported_variable_names_[var_index], " ");
438 }
439 return true;
440}
441
442namespace {
443bool IsBoolean(const MPVariableProto& var) {
444 return var.is_integer() && ceil(var.lower_bound()) == 0.0 &&
445 floor(var.upper_bound()) == 1.0;
446}
447
448void UpdateMaxSize(const std::string& new_string, int* size) {
449 if (new_string.size() > *size) *size = new_string.size();
450}
451
452void UpdateMaxSize(double new_number, int* size) {
453 UpdateMaxSize(DoubleToString(new_number), size);
454}
455} // namespace
456
457void MPModelProtoExporter::Setup() {
458 if (absl::GetFlag(FLAGS_lp_log_invalid_name)) {
459 LOG(WARNING) << "The \"lp_log_invalid_name\" flag is deprecated. Use "
460 "MPModelProtoExportOptions instead.";
461 }
462 num_binary_variables_ = 0;
463 num_integer_variables_ = 0;
464 for (const MPVariableProto& var : proto_.variable()) {
465 if (var.is_integer()) {
466 if (IsBoolean(var)) {
467 ++num_binary_variables_;
468 } else {
469 ++num_integer_variables_;
470 }
471 }
472 }
473 num_continuous_variables_ =
474 proto_.variable_size() - num_binary_variables_ - num_integer_variables_;
475}
476
477void MPModelProtoExporter::ComputeMpsSmartColumnWidths(bool obfuscated) {
478 // Minimum values for aesthetics (if columns are too narrow, MPS files are
479 // difficult to read).
480 int string_field_size = 6;
481 int number_field_size = 6;
482
483 for (const MPVariableProto& var : proto_.variable()) {
484 UpdateMaxSize(var.name(), &string_field_size);
485 UpdateMaxSize(var.objective_coefficient(), &number_field_size);
486 UpdateMaxSize(var.lower_bound(), &number_field_size);
487 UpdateMaxSize(var.upper_bound(), &number_field_size);
488 }
489
490 for (const MPConstraintProto& cst : proto_.constraint()) {
491 UpdateMaxSize(cst.name(), &string_field_size);
492 UpdateMaxSize(cst.lower_bound(), &number_field_size);
493 UpdateMaxSize(cst.upper_bound(), &number_field_size);
494 for (const double coeff : cst.coefficient()) {
495 UpdateMaxSize(coeff, &number_field_size);
496 }
497 }
498
499 // Maximum values for aesthetics. These are also the values used by other
500 // solvers.
501 string_field_size = std::min(string_field_size, 255);
502 number_field_size = std::min(number_field_size, 255);
503
504 // If the model is obfuscated, all names will have the same size, which we
505 // compute here.
506 if (obfuscated) {
507 int max_digits =
508 absl::StrCat(
509 std::max(proto_.variable_size(), proto_.constraint_size()) - 1)
510 .size();
511 string_field_size = std::max(6, max_digits + 1);
512 }
513
514 mps_header_format_ = absl::ParsedFormat<'s', 's'>::New(
515 absl::StrCat(" %-2s %-", string_field_size, "s"));
516 mps_format_ = absl::ParsedFormat<'s', 's'>::New(
517 absl::StrCat(" %-", string_field_size, "s %", number_field_size, "s"));
518}
519
521 const MPModelExportOptions& options, std::string* output) {
522 output->clear();
523 Setup();
524 const std::string kForbiddenFirstChars = "$.0123456789";
525 const std::string kForbiddenChars = " +-*/<>=:\\";
526 exported_constraint_names_ = ExtractAndProcessNames(
527 proto_.constraint(), "C", options.obfuscate, options.log_invalid_names,
528 kForbiddenFirstChars, kForbiddenChars);
529 exported_general_constraint_names_ = ExtractAndProcessNames(
530 proto_.general_constraint(), "C", options.obfuscate,
531 options.log_invalid_names, kForbiddenFirstChars, kForbiddenChars);
532 exported_variable_names_ = ExtractAndProcessNames(
533 proto_.variable(), "V", options.obfuscate, options.log_invalid_names,
534 kForbiddenFirstChars, kForbiddenChars);
535
536 // Comments section.
537 AppendComments("\\", output);
538 if (options.show_unused_variables) {
539 absl::StrAppendFormat(output, "\\ Unused variables are shown\n");
540 }
541
542 // Objective
543 absl::StrAppend(output, proto_.maximize() ? "Maximize\n" : "Minimize\n");
544 LineBreaker obj_line_breaker(options.max_line_length);
545 obj_line_breaker.Append(" Obj: ");
546 if (proto_.objective_offset() != 0.0) {
547 obj_line_breaker.Append(absl::StrCat(
548 DoubleToStringWithForcedSign(proto_.objective_offset()), " Constant "));
549 }
550 std::vector<bool> show_variable(proto_.variable_size(),
551 options.show_unused_variables);
552 for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
553 const double coeff = proto_.variable(var_index).objective_coefficient();
554 std::string term;
555 if (!WriteLpTerm(var_index, coeff, &term)) {
556 return false;
557 }
558 obj_line_breaker.Append(term);
559 show_variable[var_index] = coeff != 0.0 || show_variable[var_index];
560 }
561 // Linear Constraints
562 absl::StrAppend(output, obj_line_breaker.GetOutput(), "\nSubject to\n");
563 for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
564 const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
565 const std::string& name = exported_constraint_names_[cst_index];
566 LineBreaker line_breaker(options.max_line_length);
567 const int kNumFormattingChars = 10; // Overevaluated.
568 // Account for the size of the constraint name + possibly "_rhs" +
569 // the formatting characters here.
570 line_breaker.Consume(kNumFormattingChars + name.size());
571 if (!AppendConstraint(ct_proto, name, line_breaker, show_variable,
572 output)) {
573 return false;
574 }
575 }
576
577 // General Constraints
578 for (int cst_index = 0; cst_index < proto_.general_constraint_size();
579 ++cst_index) {
580 const MPGeneralConstraintProto& ct_proto =
581 proto_.general_constraint(cst_index);
582 const std::string& name = exported_general_constraint_names_[cst_index];
583 LineBreaker line_breaker(options.max_line_length);
584 const int kNumFormattingChars = 10; // Overevaluated.
585 // Account for the size of the constraint name + possibly "_rhs" +
586 // the formatting characters here.
587 line_breaker.Consume(kNumFormattingChars + name.size());
588
589 if (!ct_proto.has_indicator_constraint()) return false;
590 const MPIndicatorConstraint& indicator_ct = ct_proto.indicator_constraint();
591 const int binary_var_index = indicator_ct.var_index();
592 const int binary_var_value = indicator_ct.var_value();
593 if (binary_var_index < 0 || binary_var_index >= proto_.variable_size()) {
594 return false;
595 }
596 line_breaker.Append(absl::StrFormat(
597 "%s = %d -> ", exported_variable_names_[binary_var_index],
598 binary_var_value));
599 if (!AppendConstraint(indicator_ct.constraint(), name, line_breaker,
600 show_variable, output)) {
601 return false;
602 }
603 }
604
605 // Bounds
606 absl::StrAppend(output, "Bounds\n");
607 if (proto_.objective_offset() != 0.0) {
608 absl::StrAppend(output, " 1 <= Constant <= 1\n");
609 }
610 for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
611 if (!show_variable[var_index]) continue;
612 const MPVariableProto& var_proto = proto_.variable(var_index);
613 const double lb = var_proto.lower_bound();
614 const double ub = var_proto.upper_bound();
615 if (var_proto.is_integer() && lb == round(lb) && ub == round(ub)) {
616 absl::StrAppendFormat(output, " %.0f <= %s <= %.0f\n", lb,
617 exported_variable_names_[var_index], ub);
618 } else {
619 absl::StrAppend(output, " ");
620 if (lb == -kInfinity && ub == kInfinity) {
621 absl::StrAppend(output, exported_variable_names_[var_index], " free");
622 } else {
623 if (lb != -kInfinity) {
624 absl::StrAppend(output, DoubleToString(lb), " <= ");
625 }
626 absl::StrAppend(output, exported_variable_names_[var_index]);
627 if (ub != kInfinity) {
628 absl::StrAppend(output, " <= ", DoubleToString(ub));
629 }
630 }
631 absl::StrAppend(output, "\n");
632 }
633 }
634
635 // Binaries
636 if (num_binary_variables_ > 0) {
637 absl::StrAppend(output, "Binaries\n");
638 for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
639 if (!show_variable[var_index]) continue;
640 const MPVariableProto& var_proto = proto_.variable(var_index);
641 if (IsBoolean(var_proto)) {
642 absl::StrAppendFormat(output, " %s\n",
643 exported_variable_names_[var_index]);
644 }
645 }
646 }
647
648 // Generals
649 if (num_integer_variables_ > 0) {
650 absl::StrAppend(output, "Generals\n");
651 for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
652 if (!show_variable[var_index]) continue;
653 const MPVariableProto& var_proto = proto_.variable(var_index);
654 if (var_proto.is_integer() && !IsBoolean(var_proto)) {
655 absl::StrAppend(output, " ", exported_variable_names_[var_index], "\n");
656 }
657 }
658 }
659 absl::StrAppend(output, "End\n");
660 return true;
661}
662
663void MPModelProtoExporter::AppendMpsPair(const std::string& name, double value,
664 std::string* output) const {
665 absl::StrAppendFormat(output, *mps_format_, name, DoubleToString(value));
666}
667
668void MPModelProtoExporter::AppendMpsLineHeader(const std::string& id,
669 const std::string& name,
670 std::string* output) const {
671 absl::StrAppendFormat(output, *mps_header_format_, id, name);
672}
673
674void MPModelProtoExporter::AppendMpsLineHeaderWithNewLine(
675 const std::string& id, const std::string& name, std::string* output) const {
676 AppendMpsLineHeader(id, name, output);
677 absl::StripTrailingAsciiWhitespace(output);
678 absl::StrAppend(output, "\n");
679}
680
681void MPModelProtoExporter::AppendMpsTermWithContext(
682 const std::string& head_name, const std::string& name, double value,
683 std::string* output) {
684 if (current_mps_column_ == 0) {
685 AppendMpsLineHeader("", head_name, output);
686 }
687 AppendMpsPair(name, value, output);
688 AppendNewLineIfTwoColumns(output);
689}
690
691void MPModelProtoExporter::AppendMpsBound(const std::string& bound_type,
692 const std::string& name, double value,
693 std::string* output) const {
694 AppendMpsLineHeader(bound_type, "BOUND", output);
695 AppendMpsPair(name, value, output);
696 absl::StripTrailingAsciiWhitespace(output);
697 absl::StrAppend(output, "\n");
698}
699
700void MPModelProtoExporter::AppendNewLineIfTwoColumns(std::string* output) {
701 ++current_mps_column_;
702 if (current_mps_column_ == 2) {
703 absl::StripTrailingAsciiWhitespace(output);
704 absl::StrAppend(output, "\n");
705 current_mps_column_ = 0;
706 }
707}
708
709void MPModelProtoExporter::AppendMpsColumns(
710 bool integrality,
711 const std::vector<std::vector<std::pair<int, double>>>& transpose,
712 std::string* output) {
713 current_mps_column_ = 0;
714 for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
715 const MPVariableProto& var_proto = proto_.variable(var_index);
716 if (var_proto.is_integer() != integrality) continue;
717 const std::string& var_name = exported_variable_names_[var_index];
718 current_mps_column_ = 0;
719 if (var_proto.objective_coefficient() != 0.0) {
720 AppendMpsTermWithContext(var_name, "COST",
721 var_proto.objective_coefficient(), output);
722 }
723 for (const std::pair<int, double>& cst_index_and_coeff :
724 transpose[var_index]) {
725 const std::string& cst_name =
726 exported_constraint_names_[cst_index_and_coeff.first];
727 AppendMpsTermWithContext(var_name, cst_name, cst_index_and_coeff.second,
728 output);
729 }
730 AppendNewLineIfTwoColumns(output);
731 }
732}
733
735 const MPModelExportOptions& options, std::string* output) {
736 output->clear();
737 Setup();
738 ComputeMpsSmartColumnWidths(options.obfuscate);
739 const std::string kForbiddenFirstChars = "";
740 const std::string kForbiddenChars = " ";
741 exported_constraint_names_ = ExtractAndProcessNames(
742 proto_.constraint(), "C", options.obfuscate, options.log_invalid_names,
743 kForbiddenFirstChars, kForbiddenChars);
744 exported_variable_names_ = ExtractAndProcessNames(
745 proto_.variable(), "V", options.obfuscate, options.log_invalid_names,
746 kForbiddenFirstChars, kForbiddenChars);
747
748 // Comments.
749 AppendComments("*", output);
750
751 // NAME section.
752 // TODO(user): Obfuscate the model name too if `obfuscate` is true.
753 absl::StrAppendFormat(output, "%-14s%s\n", "NAME", proto_.name());
754
755 if (proto_.maximize()) {
756 absl::StrAppendFormat(output, "OBJSENSE\n MAX\n");
757 }
758
759 // ROWS section.
760 current_mps_column_ = 0;
761 std::string rows_section;
762 AppendMpsLineHeaderWithNewLine("N", "COST", &rows_section);
763 for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
764 const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
765 const double lb = ct_proto.lower_bound();
766 const double ub = ct_proto.upper_bound();
767 const std::string& cst_name = exported_constraint_names_[cst_index];
768 if (lb == -kInfinity && ub == kInfinity) {
769 AppendMpsLineHeaderWithNewLine("N", cst_name, &rows_section);
770 } else if (lb == ub) {
771 AppendMpsLineHeaderWithNewLine("E", cst_name, &rows_section);
772 } else if (lb == -kInfinity) {
773 AppendMpsLineHeaderWithNewLine("L", cst_name, &rows_section);
774 } else {
775 AppendMpsLineHeaderWithNewLine("G", cst_name, &rows_section);
776 }
777 }
778 if (!rows_section.empty()) {
779 absl::StrAppend(output, "ROWS\n", rows_section);
780 }
781
782 // As the information regarding a column needs to be contiguous, we create
783 // a vector associating a variable index to a vector containing the indices
784 // of the constraints where this variable appears.
785 std::vector<std::vector<std::pair<int, double>>> transpose(
786 proto_.variable_size());
787 for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
788 const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
789 for (int k = 0; k < ct_proto.var_index_size(); ++k) {
790 const int var_index = ct_proto.var_index(k);
791 if (var_index < 0 || var_index >= proto_.variable_size()) {
792 LOG(DFATAL) << "In constraint #" << cst_index << ", var_index #" << k
793 << " is " << var_index << ", which is out of bounds.";
794 return false;
795 }
796 const double coeff = ct_proto.coefficient(k);
797 if (coeff != 0.0) {
798 transpose[var_index].push_back(
799 std::pair<int, double>(cst_index, coeff));
800 }
801 }
802 }
803
804 // COLUMNS section.
805 std::string columns_section;
806 AppendMpsColumns(/*integrality=*/true, transpose, &columns_section);
807 if (!columns_section.empty()) {
808 constexpr const char kIntMarkerFormat[] = " %-10s%-36s%-8s\n";
809 columns_section =
810 absl::StrFormat(kIntMarkerFormat, "INTSTART", "'MARKER'", "'INTORG'") +
811 columns_section;
812 absl::StrAppendFormat(&columns_section, kIntMarkerFormat, "INTEND",
813 "'MARKER'", "'INTEND'");
814 }
815 AppendMpsColumns(/*integrality=*/false, transpose, &columns_section);
816 if (!columns_section.empty()) {
817 absl::StrAppend(output, "COLUMNS\n", columns_section);
818 }
819
820 // RHS (right-hand-side) section.
821 current_mps_column_ = 0;
822 std::string rhs_section;
823 for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
824 const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
825 const double lb = ct_proto.lower_bound();
826 const double ub = ct_proto.upper_bound();
827 const std::string& cst_name = exported_constraint_names_[cst_index];
828 if (lb != -kInfinity) {
829 AppendMpsTermWithContext("RHS", cst_name, lb, &rhs_section);
830 } else if (ub != +kInfinity) {
831 AppendMpsTermWithContext("RHS", cst_name, ub, &rhs_section);
832 }
833 }
834 AppendNewLineIfTwoColumns(&rhs_section);
835 if (!rhs_section.empty()) {
836 absl::StrAppend(output, "RHS\n", rhs_section);
837 }
838
839 // RANGES section.
840 current_mps_column_ = 0;
841 std::string ranges_section;
842 for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
843 const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
844 const double range = fabs(ct_proto.upper_bound() - ct_proto.lower_bound());
845 if (range != 0.0 && range != +kInfinity) {
846 const std::string& cst_name = exported_constraint_names_[cst_index];
847 AppendMpsTermWithContext("RANGE", cst_name, range, &ranges_section);
848 }
849 }
850 AppendNewLineIfTwoColumns(&ranges_section);
851 if (!ranges_section.empty()) {
852 absl::StrAppend(output, "RANGES\n", ranges_section);
853 }
854
855 // BOUNDS section.
856 current_mps_column_ = 0;
857 std::string bounds_section;
858 for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
859 const MPVariableProto& var_proto = proto_.variable(var_index);
860 const double lb = var_proto.lower_bound();
861 const double ub = var_proto.upper_bound();
862 const std::string& var_name = exported_variable_names_[var_index];
863
864 if (lb == -kInfinity && ub == +kInfinity) {
865 AppendMpsLineHeader("FR", "BOUND", &bounds_section);
866 absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
867 continue;
868 }
869
870 if (var_proto.is_integer()) {
871 if (IsBoolean(var_proto)) {
872 AppendMpsLineHeader("BV", "BOUND", &bounds_section);
873 absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
874 } else {
875 if (lb == ub) {
876 AppendMpsBound("FX", var_name, lb, &bounds_section);
877 } else {
878 if (lb == -kInfinity) {
879 AppendMpsLineHeader("MI", "BOUND", &bounds_section);
880 absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
881 } else if (lb != 0.0 || ub == kInfinity) {
882 // "LI" can be skipped if it's 0.
883 // There is one exception to that rule: if UI=+inf, we can't skip
884 // LI=0 or the variable will be parsed as binary.
885 AppendMpsBound("LI", var_name, lb, &bounds_section);
886 }
887 if (ub != kInfinity) {
888 AppendMpsBound("UI", var_name, ub, &bounds_section);
889 }
890 }
891 }
892 } else {
893 if (lb == ub) {
894 AppendMpsBound("FX", var_name, lb, &bounds_section);
895 } else {
896 if (lb == -kInfinity) {
897 AppendMpsLineHeader("MI", "BOUND", &bounds_section);
898 absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
899 } else if (lb != 0.0) {
900 AppendMpsBound("LO", var_name, lb, &bounds_section);
901 }
902 if (lb == 0.0 && ub == +kInfinity) {
903 AppendMpsLineHeader("PL", "BOUND", &bounds_section);
904 absl::StrAppendFormat(&bounds_section, " %s\n", var_name);
905 } else if (ub != +kInfinity) {
906 AppendMpsBound("UP", var_name, ub, &bounds_section);
907 }
908 }
909 }
910 }
911 if (!bounds_section.empty()) {
912 absl::StrAppend(output, "BOUNDS\n", bounds_section);
913 }
914
915 absl::StrAppend(output, "ENDATA\n");
916 return true;
917}
918
919} // namespace
920} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define LOG_IF(severity, condition)
Definition: base/logging.h:479
#define LOG(severity)
Definition: base/logging.h:420
CpModelProto proto
const std::string name
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
const int WARNING
Definition: log_severity.h:31
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:29
ABSL_FLAG(bool, lp_log_invalid_name, false, "DEPRECATED.")
const double kInfinity
Definition: lp_types.h:84
Collection of objects used to extend the Constraint Solver library.
absl::StatusOr< std::string > ExportModelAsMpsFormat(const MPModelProto &model, const MPModelExportOptions &options)
Outputs the current model (variables, constraints, objective) as a string encoded in MPS file format,...
absl::StatusOr< std::string > ExportModelAsLpFormat(const MPModelProto &model, const MPModelExportOptions &options)
Outputs the current model (variables, constraints, objective) as a string encoded in the so-called "C...
int64_t coefficient
const double coeff