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