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