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"
37 constexpr
double kInfinity = std::numeric_limits<double>::infinity();
39 class MPModelProtoExporter {
41 explicit MPModelProtoExporter(
const MPModelProto&
model);
53 void ComputeMpsSmartColumnWidths(
bool obfuscated);
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);
85 void AppendComments(
const std::string& separator, std::string* output)
const;
90 std::string* output)
const;
94 void AppendMpsPair(
const std::string&
name,
double value,
95 std::string* output)
const;
98 void AppendMpsLineHeader(
const std::string&
id,
const std::string&
name,
99 std::string* output)
const;
103 void AppendMpsLineHeaderWithNewLine(
const std::string&
id,
104 const std::string&
name,
105 std::string* output)
const;
111 void AppendMpsTermWithContext(
const std::string& head_name,
113 std::string* output);
117 void AppendNewLineIfTwoColumns(std::string* output);
122 void AppendMpsColumns(
124 const std::vector<std::vector<std::pair<int, double>>>& transpose,
125 std::string* output);
130 void AppendMpsBound(
const std::string& bound_type,
const std::string&
name,
131 double value, std::string* output)
const;
133 const MPModelProto& proto_;
136 std::vector<std::string> exported_variable_names_;
139 std::vector<std::string> exported_constraint_names_;
142 int num_integer_variables_;
145 int num_binary_variables_;
148 int num_continuous_variables_;
151 int current_mps_column_;
154 std::unique_ptr<absl::ParsedFormat<'s', 's'>> mps_header_format_;
155 std::unique_ptr<absl::ParsedFormat<'s', 's'>> mps_format_;
163 if (
model.general_constraint_size() > 0) {
164 return absl::InvalidArgumentError(
"General constraints are not supported.");
166 MPModelProtoExporter exporter(
model);
168 if (!exporter.ExportModelAsLpFormat(options, &output)) {
169 return absl::InvalidArgumentError(
"Unable to export model.");
176 if (
model.general_constraint_size() > 0) {
177 return absl::InvalidArgumentError(
"General constraints are not supported.");
179 MPModelProtoExporter exporter(
model);
181 if (!exporter.ExportModelAsMpsFormat(options, &output)) {
182 return absl::InvalidArgumentError(
"Unable to export model.");
188 MPModelProtoExporter::MPModelProtoExporter(
const MPModelProto&
model)
190 num_integer_variables_(0),
191 num_binary_variables_(0),
192 num_continuous_variables_(0),
193 current_mps_column_(0) {}
198 NameManager() : names_set_(), last_n_(1) {}
199 std::string MakeUniqueName(
const std::string&
name);
202 absl::flat_hash_set<std::string> names_set_;
206 std::string NameManager::MakeUniqueName(
const std::string&
name) {
207 std::string result =
name;
210 while (!names_set_.insert(result).second) {
211 result = absl::StrCat(
name,
"_", n);
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) {
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;
231 for (
char& c : exportable_name) {
232 if (forbidden_chars.find(c) != std::string::npos) {
234 *found_forbidden_char =
true;
237 return exportable_name;
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);
250 const int num_digits = absl::StrCat(num_items).size();
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];
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 "
271 const int kMaxNameLength = 255;
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];
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_);
311 explicit LineBreaker(
int max_line_size)
312 : max_line_size_(max_line_size), line_size_(0), output_() {}
318 void Append(
const std::string& s);
322 bool WillFit(
const std::string& s) {
323 return line_size_ + s.size() < max_line_size_;
328 void Consume(
int size) { line_size_ += size; }
330 std::string GetOutput()
const {
return output_; }
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 ");
344 absl::StrAppend(&output_, s);
347 std::string DoubleToStringWithForcedSign(
double d) {
348 return absl::StrCat((d < 0 ?
"" :
"+"), (d));
351 std::string DoubleToString(
double d) {
return absl::StrCat((d)); }
355 bool MPModelProtoExporter::WriteLpTerm(
int var_index,
double coefficient,
356 std::string* output)
const {
358 if (var_index < 0 || var_index >= proto_.variable_size()) {
359 LOG(DFATAL) <<
"Reference to out-of-bounds variable index # " << var_index;
363 *output = absl::StrCat(DoubleToStringWithForcedSign(
coefficient),
" ",
364 exported_variable_names_[var_index],
" ");
370 bool IsBoolean(
const MPVariableProto&
var) {
371 return var.is_integer() && ceil(
var.lower_bound()) == 0.0 &&
372 floor(
var.upper_bound()) == 1.0;
375 void UpdateMaxSize(
const std::string& new_string,
int* size) {
376 if (new_string.size() > *size) *size = new_string.size();
379 void UpdateMaxSize(
double new_number,
int* size) {
380 UpdateMaxSize(DoubleToString(new_number), size);
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.";
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_;
396 ++num_integer_variables_;
400 num_continuous_variables_ =
401 proto_.variable_size() - num_binary_variables_ - num_integer_variables_;
404 void MPModelProtoExporter::ComputeMpsSmartColumnWidths(
bool obfuscated) {
407 int string_field_size = 6;
408 int number_field_size = 6;
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);
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);
428 string_field_size =
std::min(string_field_size, 255);
429 number_field_size =
std::min(number_field_size, 255);
436 std::max(proto_.variable_size(), proto_.constraint_size()) - 1)
438 string_field_size =
std::max(6, max_digits + 1);
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"));
448 const MPModelExportOptions& options, std::string* output) {
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);
461 AppendComments(
"\\", output);
462 if (options.show_unused_variables) {
463 absl::StrAppendFormat(output,
"\\ Unused variables are shown\n");
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 "));
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();
479 if (!WriteLpTerm(var_index, coeff, &term)) {
482 obj_line_breaker.Append(term);
483 show_variable[var_index] = coeff != 0.0 || options.show_unused_variables;
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;
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);
499 if (!WriteLpTerm(var_index, coeff, &term)) {
502 line_breaker.Append(term);
503 show_variable[var_index] = coeff != 0.0 || options.show_unused_variables;
505 const double lb = ct_proto.lower_bound();
506 const double ub = ct_proto.upper_bound();
508 line_breaker.Append(absl::StrCat(
" = ", DoubleToString(ub),
"\n"));
509 absl::StrAppend(output,
" ",
name,
": ", line_breaker.GetOutput());
512 std::string rhs_name =
name;
514 absl::StrAppend(&rhs_name,
"_rhs");
516 absl::StrAppend(output,
" ", rhs_name,
": ", line_breaker.GetOutput());
517 const std::string relation =
518 absl::StrCat(
" <= ", DoubleToString(ub),
"\n");
521 if (!line_breaker.WillFit(relation)) absl::StrAppend(output,
"\n ");
522 absl::StrAppend(output, relation);
525 std::string lhs_name =
name;
527 absl::StrAppend(&lhs_name,
"_lhs");
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);
539 absl::StrAppend(output,
"Bounds\n");
540 if (proto_.objective_offset() != 0.0) {
541 absl::StrAppend(output,
" 1 <= Constant <= 1\n");
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);
552 absl::StrAppend(output,
" ");
554 absl::StrAppend(output, exported_variable_names_[var_index],
" free");
557 absl::StrAppend(output, DoubleToString(lb),
" <= ");
559 absl::StrAppend(output, exported_variable_names_[var_index]);
561 absl::StrAppend(output,
" <= ", DoubleToString(ub));
564 absl::StrAppend(output,
"\n");
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]);
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");
592 absl::StrAppend(output,
"End\n");
596 void MPModelProtoExporter::AppendMpsPair(
const std::string&
name,
double value,
597 std::string* output)
const {
598 absl::StrAppendFormat(output, *mps_format_,
name, DoubleToString(
value));
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);
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");
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);
621 AppendNewLineIfTwoColumns(output);
624 void MPModelProtoExporter::AppendMpsBound(
const std::string& bound_type,
626 std::string* output)
const {
627 AppendMpsLineHeader(bound_type,
"BOUND", output);
629 absl::StripTrailingAsciiWhitespace(output);
630 absl::StrAppend(output,
"\n");
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;
642 void MPModelProtoExporter::AppendMpsColumns(
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);
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,
663 AppendNewLineIfTwoColumns(output);
668 const MPModelExportOptions& options, std::string* output) {
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);
684 if (proto_.maximize()) {
685 LOG(DFATAL) <<
"MPS cannot represent maximization objectives.";
689 AppendComments(
"*", output);
693 absl::StrAppendFormat(output,
"%-14s%s\n",
"NAME", proto_.name());
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];
705 AppendMpsLineHeaderWithNewLine(
"N", cst_name, &rows_section);
706 }
else if (lb == ub) {
707 AppendMpsLineHeaderWithNewLine(
"E", cst_name, &rows_section);
709 AppendMpsLineHeaderWithNewLine(
"L", cst_name, &rows_section);
711 AppendMpsLineHeaderWithNewLine(
"G", cst_name, &rows_section);
714 if (!rows_section.empty()) {
715 absl::StrAppend(output,
"ROWS\n", rows_section);
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.";
732 const double coeff = ct_proto.coefficient(k);
734 transpose[var_index].push_back(
735 std::pair<int, double>(cst_index, coeff));
741 std::string columns_section;
742 AppendMpsColumns(
true, transpose, &columns_section);
743 if (!columns_section.empty()) {
744 constexpr
const char kIntMarkerFormat[] =
" %-10s%-36s%-8s\n";
746 absl::StrFormat(kIntMarkerFormat,
"INTSTART",
"'MARKER'",
"'INTORG'") +
748 absl::StrAppendFormat(&columns_section, kIntMarkerFormat,
"INTEND",
749 "'MARKER'",
"'INTEND'");
751 AppendMpsColumns(
false, transpose, &columns_section);
752 if (!columns_section.empty()) {
753 absl::StrAppend(output,
"COLUMNS\n", columns_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];
765 AppendMpsTermWithContext(
"RHS", cst_name, lb, &rhs_section);
767 AppendMpsTermWithContext(
"RHS", cst_name, ub, &rhs_section);
770 AppendNewLineIfTwoColumns(&rhs_section);
771 if (!rhs_section.empty()) {
772 absl::StrAppend(output,
"RHS\n", rhs_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);
786 AppendNewLineIfTwoColumns(&ranges_section);
787 if (!ranges_section.empty()) {
788 absl::StrAppend(output,
"RANGES\n", ranges_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];
801 AppendMpsLineHeader(
"FR",
"BOUND", &bounds_section);
802 absl::StrAppendFormat(&bounds_section,
" %s\n", var_name);
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);
812 AppendMpsBound(
"FX", var_name, lb, &bounds_section);
815 AppendMpsLineHeader(
"MI",
"BOUND", &bounds_section);
816 absl::StrAppendFormat(&bounds_section,
" %s\n", var_name);
817 }
else if (lb != 0.0 || ub ==
kInfinity) {
821 AppendMpsBound(
"LI", var_name, lb, &bounds_section);
824 AppendMpsBound(
"UI", var_name, ub, &bounds_section);
830 AppendMpsBound(
"FX", var_name, lb, &bounds_section);
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);
839 AppendMpsLineHeader(
"PL",
"BOUND", &bounds_section);
840 absl::StrAppendFormat(&bounds_section,
" %s\n", var_name);
842 AppendMpsBound(
"UP", var_name, ub, &bounds_section);
847 if (!bounds_section.empty()) {
848 absl::StrAppend(output,
"BOUNDS\n", bounds_section);
851 absl::StrAppend(output,
"ENDATA\n");