631 lines
24 KiB
C++
631 lines
24 KiB
C++
// Copyright 2010-2014 Google
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "ortools/linear_solver/model_exporter.h"
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <unordered_set>
|
|
|
|
#include "ortools/base/commandlineflags.h"
|
|
#include "ortools/base/integral_types.h"
|
|
#include "ortools/base/logging.h"
|
|
#include "ortools/base/stringprintf.h"
|
|
#include "ortools/base/join.h"
|
|
#include "ortools/base/strutil.h"
|
|
#include "ortools/base/map_util.h"
|
|
#include "ortools/linear_solver/linear_solver.pb.h"
|
|
#include "ortools/util/fp_utils.h"
|
|
|
|
DEFINE_bool(lp_shows_unused_variables, false,
|
|
"Decides wether variable unused in the objective and constraints"
|
|
" are shown when exported to a file using the lp format.");
|
|
|
|
DEFINE_int32(lp_max_line_length, 10000,
|
|
"Maximum line length in exported .lp files. The default was chosen"
|
|
" so that SCIP can read the files.");
|
|
|
|
DEFINE_bool(lp_log_invalid_name, false,
|
|
"Whether to log invalid variable and contraint names.");
|
|
|
|
namespace operations_research {
|
|
|
|
MPModelProtoExporter::MPModelProtoExporter(const MPModelProto& proto)
|
|
: proto_(proto),
|
|
num_integer_variables_(0),
|
|
num_binary_variables_(0),
|
|
num_continuous_variables_(0),
|
|
current_mps_column_(0),
|
|
use_fixed_mps_format_(false),
|
|
use_obfuscated_names_(false) {}
|
|
|
|
namespace {
|
|
class NameManager {
|
|
public:
|
|
NameManager() : names_set_(), last_n_(1) {}
|
|
std::string MakeUniqueName(const std::string& name);
|
|
|
|
private:
|
|
std::unordered_set<std::string> names_set_;
|
|
int last_n_;
|
|
};
|
|
|
|
std::string NameManager::MakeUniqueName(const std::string& name) {
|
|
std::string result = name;
|
|
// Find the 'n' so that "name_n" does not already exist.
|
|
int n = last_n_;
|
|
while (!names_set_.insert(result).second) {
|
|
result = StrCat(name, "_", n);
|
|
++n;
|
|
}
|
|
// We keep the last n used to avoid a quadratic behavior in case
|
|
// all the names are the same initially.
|
|
last_n_ = n;
|
|
return result;
|
|
}
|
|
|
|
std::string MakeExportableName(const std::string& name, bool* found_forbidden_char) {
|
|
// Prepend with "_" all the names starting with a forbidden character.
|
|
const std::string kForbiddenFirstChars = "$.0123456789";
|
|
*found_forbidden_char = kForbiddenFirstChars.find(name[0]) != std::string::npos;
|
|
std::string exportable_name = *found_forbidden_char ? StrCat("_", name) : name;
|
|
|
|
// Replace all the other forbidden characters with "_".
|
|
const std::string kForbiddenChars = " +-*/<>=:\\";
|
|
for (char& c : exportable_name) {
|
|
if (kForbiddenChars.find(c) != std::string::npos) {
|
|
c = '_';
|
|
*found_forbidden_char = true;
|
|
}
|
|
}
|
|
return exportable_name;
|
|
}
|
|
} // namespace
|
|
|
|
template <class ListOfProtosWithNameFields>
|
|
std::vector<std::string> MPModelProtoExporter::ExtractAndProcessNames(
|
|
const ListOfProtosWithNameFields& proto, const std::string& prefix,
|
|
bool obfuscate) {
|
|
const int num_items = proto.size();
|
|
std::vector<std::string> result(num_items);
|
|
NameManager namer;
|
|
const int num_digits = StrCat(num_items).size();
|
|
int i = 0;
|
|
for (const auto& item : proto) {
|
|
const std::string obfuscated_name =
|
|
StringPrintf("%s%0*d", prefix.c_str(), num_digits, i);
|
|
if (obfuscate || !item.has_name()) {
|
|
result[i] = namer.MakeUniqueName(obfuscated_name);
|
|
LOG_IF(WARNING, FLAGS_lp_log_invalid_name && !item.has_name())
|
|
<< "Empty name detected, created new name: " << result[i];
|
|
} else {
|
|
bool found_forbidden_char = false;
|
|
const std::string exportable_name =
|
|
MakeExportableName(item.name(), &found_forbidden_char);
|
|
result[i] = namer.MakeUniqueName(exportable_name);
|
|
LOG_IF(WARNING, FLAGS_lp_log_invalid_name && found_forbidden_char)
|
|
<< "Invalid character detected in " << item.name() << ". Changed to "
|
|
<< result[i];
|
|
// If the name is too long, use the obfuscated name that is guaranteed
|
|
// to fit. If ever we are able to solve problems with 2^64 variables,
|
|
// their obfuscated names would fit within 20 characters.
|
|
const int kMaxNameLength = 255;
|
|
// Take care of "_rhs" or "_lhs" that may be added in the case of
|
|
// constraints with both right-hand side and left-hand side.
|
|
const int kMargin = 4;
|
|
if (result[i].size() > kMaxNameLength - kMargin) {
|
|
const std::string old_name = std::move(result[i]);
|
|
result[i] = namer.MakeUniqueName(obfuscated_name);
|
|
LOG_IF(WARNING, FLAGS_lp_log_invalid_name)
|
|
<< "Name is too long: " << old_name
|
|
<< " exported as: " << result[i];
|
|
}
|
|
}
|
|
// Update whether we can use the names in a fixed-format MPS file.
|
|
const int kFixedMpsFieldSize = 8;
|
|
use_fixed_mps_format_ =
|
|
use_fixed_mps_format_ && (result[i].size() <= kFixedMpsFieldSize);
|
|
|
|
// Prepare for the next round.
|
|
++i;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendComments(const std::string& separator,
|
|
std::string* output) const {
|
|
const char* const sep = separator.c_str();
|
|
StringAppendF(output, "%s Generated by MPModelProtoExporter\n", sep);
|
|
StringAppendF(output, "%s %-16s : %s\n", sep, "Name",
|
|
proto_.has_name() ? proto_.name().c_str() : "NoName");
|
|
StringAppendF(output, "%s %-16s : %s\n", sep, "Format",
|
|
use_fixed_mps_format_ ? "Fixed" : "Free");
|
|
StringAppendF(output, "%s %-16s : %d\n", sep, "Constraints",
|
|
proto_.constraint_size());
|
|
StringAppendF(output, "%s %-16s : %d\n", sep, "Variables",
|
|
proto_.variable_size());
|
|
StringAppendF(output, "%s %-14s : %d\n", sep, "Binary",
|
|
num_binary_variables_);
|
|
StringAppendF(output, "%s %-14s : %d\n", sep, "Integer",
|
|
num_integer_variables_);
|
|
StringAppendF(output, "%s %-14s : %d\n", sep, "Continuous",
|
|
num_continuous_variables_);
|
|
if (FLAGS_lp_shows_unused_variables) {
|
|
StringAppendF(output, "%s Unused variables are shown\n", sep);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
class LineBreaker {
|
|
public:
|
|
explicit LineBreaker(int max_line_size) :
|
|
max_line_size_(max_line_size), line_size_(0), output_() {}
|
|
// Lines are broken in such a way that:
|
|
// - Strings that are given to Append() are never split.
|
|
// - Lines are split so that their length doesn't exceed the max length;
|
|
// unless a single std::string given to Append() exceeds that length (in which
|
|
// case it will be put alone on a single unsplit line).
|
|
void Append(const std::string& s);
|
|
|
|
// Returns true if std::string s will fit on the current line without adding
|
|
// a carriage return.
|
|
bool WillFit(const std::string& s) {
|
|
return line_size_ + s.size() < max_line_size_;
|
|
}
|
|
|
|
// "Consumes" size characters on the line. Used when starting the constraint
|
|
// lines.
|
|
void Consume(int size) { line_size_ += size; }
|
|
|
|
std::string GetOutput() const { return output_; }
|
|
|
|
private:
|
|
int max_line_size_;
|
|
int line_size_;
|
|
std::string output_;
|
|
};
|
|
|
|
void LineBreaker::Append(const std::string& s) {
|
|
line_size_ += s.size();
|
|
if (line_size_ > max_line_size_) {
|
|
line_size_ = s.size();
|
|
StrAppend(&output_, "\n ");
|
|
}
|
|
StrAppend(&output_, s);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool MPModelProtoExporter::WriteLpTerm(int var_index, double coefficient,
|
|
std::string* output) const {
|
|
output->clear();
|
|
if (var_index < 0 || var_index >= proto_.variable_size()) {
|
|
LOG(DFATAL) << "Reference to out-of-bounds variable index # " << var_index;
|
|
return false;
|
|
}
|
|
if (coefficient != 0.0) {
|
|
*output = StringPrintf("%+.16G %-s ", coefficient,
|
|
exported_variable_names_[var_index].c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
bool IsBoolean(const MPVariableProto& var) {
|
|
return var.is_integer() && ceil(var.lower_bound()) == 0.0 &&
|
|
floor(var.upper_bound()) == 1.0;
|
|
}
|
|
} // namespace
|
|
|
|
void MPModelProtoExporter::Setup() {
|
|
num_binary_variables_ = 0;
|
|
num_integer_variables_ = 0;
|
|
for (const MPVariableProto& var : proto_.variable()) {
|
|
if (var.is_integer()) {
|
|
if (IsBoolean(var)) {
|
|
++num_binary_variables_;
|
|
} else {
|
|
++num_integer_variables_;
|
|
}
|
|
}
|
|
}
|
|
num_continuous_variables_ =
|
|
proto_.variable_size() - num_binary_variables_ - num_integer_variables_;
|
|
}
|
|
|
|
bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated,
|
|
std::string* output) {
|
|
output->clear();
|
|
Setup();
|
|
exported_constraint_names_ =
|
|
ExtractAndProcessNames(proto_.constraint(), "C", obfuscated);
|
|
exported_variable_names_ =
|
|
ExtractAndProcessNames(proto_.variable(), "V", obfuscated);
|
|
|
|
// Comments section.
|
|
AppendComments("\\", output);
|
|
|
|
// Objective
|
|
StrAppend(output, proto_.maximize() ? "Maximize\n" : "Minimize\n");
|
|
LineBreaker obj_line_breaker(FLAGS_lp_max_line_length);
|
|
obj_line_breaker.Append(" Obj: ");
|
|
if (proto_.objective_offset() != 0.0) {
|
|
obj_line_breaker.Append(StringPrintf("%-+.16G Constant ",
|
|
proto_.objective_offset()));
|
|
}
|
|
std::vector<bool> show_variable(proto_.variable_size(),
|
|
FLAGS_lp_shows_unused_variables);
|
|
for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
|
|
const double coeff = proto_.variable(var_index).objective_coefficient();
|
|
std::string term;
|
|
if (!WriteLpTerm(var_index, coeff, &term)) {
|
|
return false;
|
|
}
|
|
obj_line_breaker.Append(term);
|
|
show_variable[var_index] = coeff != 0.0 || FLAGS_lp_shows_unused_variables;
|
|
}
|
|
// Constraints
|
|
StrAppend(output, obj_line_breaker.GetOutput(), "\nSubject to\n");
|
|
for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
|
|
const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
|
|
const std::string& name = exported_constraint_names_[cst_index];
|
|
LineBreaker line_breaker(FLAGS_lp_max_line_length);
|
|
const int kNumFormattingChars = 10; // Overevaluated.
|
|
// Account for the size of the constraint name + possibly "_rhs" +
|
|
// the formatting characters here.
|
|
line_breaker.Consume(kNumFormattingChars + name.size());
|
|
for (int i = 0; i < ct_proto.var_index_size(); ++i) {
|
|
const int var_index = ct_proto.var_index(i);
|
|
const double coeff = ct_proto.coefficient(i);
|
|
std::string term;
|
|
if (!WriteLpTerm(var_index, coeff, &term)) {
|
|
return false;
|
|
}
|
|
line_breaker.Append(term);
|
|
show_variable[var_index] =
|
|
coeff != 0.0 || FLAGS_lp_shows_unused_variables;
|
|
}
|
|
const double lb = ct_proto.lower_bound();
|
|
const double ub = ct_proto.upper_bound();
|
|
if (lb == ub) {
|
|
line_breaker.Append(StringPrintf(" = %-.16G\n", ub));
|
|
StrAppend(output, " ", name, ": ", line_breaker.GetOutput());
|
|
} else {
|
|
if (ub != +std::numeric_limits<double>::infinity()) {
|
|
std::string rhs_name = name;
|
|
if (lb != -std::numeric_limits<double>::infinity()) {
|
|
rhs_name += "_rhs";
|
|
}
|
|
StrAppend(output, " ", rhs_name, ": ", line_breaker.GetOutput());
|
|
const std::string relation = StringPrintf(" <= %-.16G\n", ub);
|
|
// Here we have to make sure we do not add the relation to the contents
|
|
// of line_breaker, which may be used in the subsequent clause.
|
|
if (!line_breaker.WillFit(relation)) StrAppend(output, "\n ");
|
|
StrAppend(output, relation);
|
|
}
|
|
if (lb != -std::numeric_limits<double>::infinity()) {
|
|
std::string lhs_name = name;
|
|
if (ub != +std::numeric_limits<double>::infinity()) {
|
|
lhs_name += "_lhs";
|
|
}
|
|
StrAppend(output, " ", lhs_name, ": ", line_breaker.GetOutput());
|
|
const std::string relation = StringPrintf(" >= %-.16G\n", lb);
|
|
if (!line_breaker.WillFit(relation)) StrAppend(output, "\n ");
|
|
StrAppend(output, relation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bounds
|
|
output->append("Bounds\n");
|
|
if (proto_.objective_offset() != 0.0) {
|
|
output->append(" 1 <= Constant <= 1\n");
|
|
}
|
|
for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
|
|
if (!show_variable[var_index]) continue;
|
|
const MPVariableProto& var_proto = proto_.variable(var_index);
|
|
const double lb = var_proto.lower_bound();
|
|
const double ub = var_proto.upper_bound();
|
|
if (var_proto.is_integer() && lb == round(lb) && ub == round(ub)) {
|
|
StringAppendF(output, " %.0f <= %s <= %.0f\n", lb,
|
|
exported_variable_names_[var_index].c_str(), ub);
|
|
} else {
|
|
if (lb != -std::numeric_limits<double>::infinity()) {
|
|
StringAppendF(output, " %-.16G <= ", lb);
|
|
}
|
|
output->append(exported_variable_names_[var_index]);
|
|
if (ub != std::numeric_limits<double>::infinity()) {
|
|
StringAppendF(output, " <= %-.16G", ub);
|
|
}
|
|
output->append("\n");
|
|
}
|
|
}
|
|
|
|
// Binaries
|
|
if (num_binary_variables_ > 0) {
|
|
output->append("Binaries\n");
|
|
for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
|
|
if (!show_variable[var_index]) continue;
|
|
const MPVariableProto& var_proto = proto_.variable(var_index);
|
|
if (IsBoolean(var_proto)) {
|
|
StringAppendF(output, " %s\n",
|
|
exported_variable_names_[var_index].c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generals
|
|
if (num_integer_variables_ > 0) {
|
|
output->append("Generals\n");
|
|
for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
|
|
if (!show_variable[var_index]) continue;
|
|
const MPVariableProto& var_proto = proto_.variable(var_index);
|
|
if (var_proto.is_integer() && !IsBoolean(var_proto)) {
|
|
StringAppendF(output, " %s\n",
|
|
exported_variable_names_[var_index].c_str());
|
|
}
|
|
}
|
|
}
|
|
output->append("End\n");
|
|
return true;
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendMpsPair(const std::string& name, double value,
|
|
std::string* output) const {
|
|
const int kFixedMpsDoubleWidth = 12;
|
|
if (use_fixed_mps_format_) {
|
|
int precision = kFixedMpsDoubleWidth;
|
|
std::string value_str = StringPrintf("%.*G", precision, value);
|
|
// Use the largest precision that can fit into the field witdh.
|
|
while (value_str.size() > kFixedMpsDoubleWidth) {
|
|
--precision;
|
|
value_str = StringPrintf("%.*G", precision, value);
|
|
}
|
|
StringAppendF(output, " %-8s %*s ", name.c_str(), kFixedMpsDoubleWidth,
|
|
value_str.c_str());
|
|
} else {
|
|
StringAppendF(output, " %-16s %21.16G ", name.c_str(), value);
|
|
}
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendMpsLineHeader(const std::string& id,
|
|
const std::string& name,
|
|
std::string* output) const {
|
|
StringAppendF(output, use_fixed_mps_format_ ? " %-2s %-8s" : " %-2s %-16s",
|
|
id.c_str(), name.c_str());
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendMpsLineHeaderWithNewLine(
|
|
const std::string& id, const std::string& name, std::string* output) const {
|
|
AppendMpsLineHeader(id, name, output);
|
|
*output += "\n";
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendMpsTermWithContext(const std::string& head_name,
|
|
const std::string& name,
|
|
double value,
|
|
std::string* output) {
|
|
if (current_mps_column_ == 0) {
|
|
AppendMpsLineHeader("", head_name, output);
|
|
}
|
|
AppendMpsPair(name, value, output);
|
|
AppendNewLineIfTwoColumns(output);
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendMpsBound(const std::string& bound_type,
|
|
const std::string& name, double value,
|
|
std::string* output) const {
|
|
AppendMpsLineHeader(bound_type, "BOUND", output);
|
|
AppendMpsPair(name, value, output);
|
|
*output += "\n";
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendNewLineIfTwoColumns(std::string* output) {
|
|
++current_mps_column_;
|
|
if (current_mps_column_ == 2) {
|
|
*output += "\n";
|
|
current_mps_column_ = 0;
|
|
}
|
|
}
|
|
|
|
void MPModelProtoExporter::AppendMpsColumns(
|
|
bool integrality,
|
|
const std::vector<std::vector<std::pair<int, double>>>& transpose,
|
|
std::string* output) {
|
|
current_mps_column_ = 0;
|
|
for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
|
|
const MPVariableProto& var_proto = proto_.variable(var_index);
|
|
if (var_proto.is_integer() != integrality) continue;
|
|
const std::string& var_name = exported_variable_names_[var_index];
|
|
current_mps_column_ = 0;
|
|
if (var_proto.objective_coefficient() != 0.0) {
|
|
AppendMpsTermWithContext(var_name, "COST",
|
|
var_proto.objective_coefficient(),
|
|
output);
|
|
}
|
|
for (const std::pair<int, double> cst_index_and_coeff :
|
|
transpose[var_index]) {
|
|
const std::string& cst_name =
|
|
exported_constraint_names_[cst_index_and_coeff.first];
|
|
AppendMpsTermWithContext(var_name, cst_name, cst_index_and_coeff.second,
|
|
output);
|
|
}
|
|
AppendNewLineIfTwoColumns(output);
|
|
}
|
|
}
|
|
|
|
bool MPModelProtoExporter::ExportModelAsMpsFormat(bool fixed_format,
|
|
bool obfuscated,
|
|
std::string* output) {
|
|
output->clear();
|
|
Setup();
|
|
use_fixed_mps_format_ = fixed_format;
|
|
exported_constraint_names_ =
|
|
ExtractAndProcessNames(proto_.constraint(), "C", obfuscated);
|
|
exported_variable_names_ =
|
|
ExtractAndProcessNames(proto_.variable(), "V", obfuscated);
|
|
|
|
// use_fixed_mps_format_ was possibly modified by ExtractAndProcessNames().
|
|
LOG_IF(WARNING, fixed_format && !use_fixed_mps_format_)
|
|
<< "Cannot use fixed format. Falling back to free format";
|
|
// Comments.
|
|
AppendComments("*", output);
|
|
|
|
// NAME section.
|
|
StringAppendF(output, "%-14s%s\n", "NAME", proto_.name().c_str());
|
|
|
|
// ROWS section.
|
|
current_mps_column_ = 0;
|
|
std::string rows_section;
|
|
AppendMpsLineHeaderWithNewLine("N", "COST", &rows_section);
|
|
for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
|
|
const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
|
|
const double lb = ct_proto.lower_bound();
|
|
const double ub = ct_proto.upper_bound();
|
|
const std::string& cst_name = exported_constraint_names_[cst_index];
|
|
if (lb == ub) {
|
|
AppendMpsLineHeaderWithNewLine("E", cst_name, &rows_section);
|
|
} else if (lb == -std::numeric_limits<double>::infinity()) {
|
|
DCHECK_NE(std::numeric_limits<double>::infinity(), ub);
|
|
AppendMpsLineHeaderWithNewLine("L", cst_name, &rows_section);
|
|
} else {
|
|
DCHECK_NE(-std::numeric_limits<double>::infinity(), lb);
|
|
AppendMpsLineHeaderWithNewLine("G", cst_name, &rows_section);
|
|
}
|
|
}
|
|
if (!rows_section.empty()) {
|
|
*output += "ROWS\n" + rows_section;
|
|
}
|
|
|
|
// As the information regarding a column needs to be contiguous, we create
|
|
// a vector associating a variable index to a vector containing the indices
|
|
// of the constraints where this variable appears.
|
|
std::vector<std::vector<std::pair<int, double>>> transpose(
|
|
proto_.variable_size());
|
|
for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
|
|
const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
|
|
for (int k = 0; k < ct_proto.var_index_size(); ++k) {
|
|
const int var_index = ct_proto.var_index(k);
|
|
if (var_index < 0 || var_index >= proto_.variable_size()) {
|
|
LOG(DFATAL) << "In constraint #" << cst_index << ", var_index #" << k
|
|
<< " is " << var_index << ", which is out of bounds.";
|
|
return false;
|
|
}
|
|
const double coeff = ct_proto.coefficient(k);
|
|
if (coeff != 0.0) {
|
|
transpose[var_index].push_back(
|
|
std::pair<int, double>(cst_index, coeff));
|
|
}
|
|
}
|
|
}
|
|
|
|
// COLUMNS section.
|
|
std::string columns_section;
|
|
AppendMpsColumns(/*integrality=*/true, transpose, &columns_section);
|
|
if (!columns_section.empty()) {
|
|
const char* const kIntMarkerFormat = " %-10s%-36s%-10s\n";
|
|
columns_section = StringPrintf(kIntMarkerFormat, "INTSTART",
|
|
"'MARKER'", "'INTORG'") + columns_section;
|
|
StringAppendF(&columns_section, kIntMarkerFormat,
|
|
"INTEND", "'MARKER'", "'INTEND'");
|
|
}
|
|
AppendMpsColumns(/*integrality=*/false, transpose, &columns_section);
|
|
if (!columns_section.empty()) {
|
|
*output += "COLUMNS\n" + columns_section;
|
|
}
|
|
|
|
// RHS (right-hand-side) section.
|
|
current_mps_column_ = 0;
|
|
std::string rhs_section;
|
|
for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
|
|
const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
|
|
const double lb = ct_proto.lower_bound();
|
|
const double ub = ct_proto.upper_bound();
|
|
const std::string& cst_name = exported_constraint_names_[cst_index];
|
|
if (lb != -std::numeric_limits<double>::infinity()) {
|
|
AppendMpsTermWithContext("RHS", cst_name, lb, &rhs_section);
|
|
} else if (ub != +std::numeric_limits<double>::infinity()) {
|
|
AppendMpsTermWithContext("RHS", cst_name, ub, &rhs_section);
|
|
}
|
|
}
|
|
AppendNewLineIfTwoColumns(&rhs_section);
|
|
if (!rhs_section.empty()) {
|
|
*output += "RHS\n" + rhs_section;
|
|
}
|
|
|
|
// RANGES section.
|
|
current_mps_column_ = 0;
|
|
std::string ranges_section;
|
|
for (int cst_index = 0; cst_index < proto_.constraint_size(); ++cst_index) {
|
|
const MPConstraintProto& ct_proto = proto_.constraint(cst_index);
|
|
const double range = fabs(ct_proto.upper_bound() - ct_proto.lower_bound());
|
|
if (range != 0.0 && range != +std::numeric_limits<double>::infinity()) {
|
|
const std::string& cst_name = exported_constraint_names_[cst_index];
|
|
AppendMpsTermWithContext("RANGE", cst_name, range, &ranges_section);
|
|
}
|
|
}
|
|
AppendNewLineIfTwoColumns(&ranges_section);
|
|
if (!ranges_section.empty()) {
|
|
*output += "RANGES\n" + ranges_section;
|
|
}
|
|
|
|
// BOUNDS section.
|
|
current_mps_column_ = 0;
|
|
std::string bounds_section;
|
|
for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) {
|
|
const MPVariableProto& var_proto = proto_.variable(var_index);
|
|
const double lb = var_proto.lower_bound();
|
|
const double ub = var_proto.upper_bound();
|
|
const std::string& var_name = exported_variable_names_[var_index];
|
|
if (var_proto.is_integer()) {
|
|
if (IsBoolean(var_proto)) {
|
|
AppendMpsLineHeader("BV", "BOUND", &bounds_section);
|
|
StringAppendF(&bounds_section, " %s\n", var_name.c_str());
|
|
} else {
|
|
if (lb != 0.0) {
|
|
AppendMpsBound("LI", var_name, lb, &bounds_section);
|
|
}
|
|
if (ub != +std::numeric_limits<double>::infinity()) {
|
|
AppendMpsBound("UI", var_name, ub, &bounds_section);
|
|
}
|
|
}
|
|
} else {
|
|
if (lb == -std::numeric_limits<double>::infinity() &&
|
|
ub == +std::numeric_limits<double>::infinity()) {
|
|
AppendMpsLineHeader("FR", "BOUND", &bounds_section);
|
|
StringAppendF(&bounds_section, " %s\n", var_name.c_str());
|
|
} else if (lb == ub) {
|
|
AppendMpsBound("FX", var_name, lb, &bounds_section);
|
|
} else {
|
|
if (lb != 0.0) {
|
|
AppendMpsBound("LO", var_name, lb, &bounds_section);
|
|
} else if (ub == +std::numeric_limits<double>::infinity()) {
|
|
AppendMpsLineHeader("PL", "BOUND", &bounds_section);
|
|
StringAppendF(&bounds_section, " %s\n", var_name.c_str());
|
|
}
|
|
if (ub != +std::numeric_limits<double>::infinity()) {
|
|
AppendMpsBound("UP", var_name, ub, &bounds_section);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!bounds_section.empty()) {
|
|
*output += "BOUNDS\n" + bounds_section;
|
|
}
|
|
|
|
*output += "ENDATA\n";
|
|
return true;
|
|
}
|
|
|
|
} // namespace operations_research
|