Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 1860 #1920

Merged
merged 6 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions check/TestFilereader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,17 @@ TEST_CASE("filereader-dD2e", "[highs_filereader]") {
objective_value = highs.getInfo().objective_function_value;
REQUIRE(objective_value == optimal_objective_value);
}

TEST_CASE("filereader-comment", "[highs_filereader]") {
// Check that comments - either whole line with * in first column,
// or rest of line following */$ are handled correctly
const double optimal_objective_value = -4;
std::string model_file =
std::string(HIGHS_DIR) + "/check/instances/comment.mps";
Highs highs;
highs.setOptionValue("output_flag", dev_run);
REQUIRE(highs.readModel(model_file) == HighsStatus::kOk);
REQUIRE(highs.run() == HighsStatus::kOk);
double objective_value = highs.getInfo().objective_function_value;
REQUIRE(objective_value == optimal_objective_value);
}
23 changes: 23 additions & 0 deletions check/instances/comment.mps
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
* Optimal objective is -4
NAME Comment
* Comment
ROWS
N COST $Comment
G R0
G R1 *Comment
G R2 $Comment
COLUMNS
C0 R0 1.0 COST -1.0 $Comment
C0 R1 -1.0 $Comment
C0 R2 -1.0 $Comment
C1 R0 1.0 COST -2.0 *Comment
C1 R1 -1.0 *Comment
C1 R2 -1.0 *Comment
RHS
DEMANDS R0 1.0 R1 -2.0 $Comment
DEMANDS R2 -2.0 $Comment
RANGES
test R0 1 $Comment
BOUNDS
UP SERVINGS C0 1.0 $Comment
ENDATA
212 changes: 75 additions & 137 deletions src/io/HMpsFF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,31 @@ HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) {
return 0;
}

bool HMpsFF::timeout() {
return time_limit > 0 && getWallTime() - start_time > time_limit;
}

bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) {
skip = false;
if (!getline(file, strline)) return false;
if (is_empty(strline) || strline[0] == '*') {
skip = true;
} else {
// Remove any trailing comment
const size_t p = strline.find_first_of(mps_comment_chars);
if (p <= strline.length()) {
// A comment character has been found, so erase from it to the end
// of the line and check whether the line is now empty
strline.erase(p);
skip = is_empty(strline);
if (skip) return true;
}
strline = trim(strline);
skip = is_empty(strline);
}
return true;
}

FreeFormatParserReturnCode HMpsFF::parse(const HighsLogOptions& log_options,
const std::string& filename) {
HMpsFF::Parsekey keyword = HMpsFF::Parsekey::kNone;
Expand Down Expand Up @@ -448,9 +473,11 @@ HighsInt HMpsFF::getColIdx(const std::string& colname, const bool add_if_new) {
HMpsFF::Parsekey HMpsFF::parseDefault(const HighsLogOptions& log_options,
std::istream& file) {
std::string strline, word;
if (getline(file, strline)) {
strline = trim(strline);
if (strline.empty()) return HMpsFF::Parsekey::kComment;
bool skip;
if (getMpsLine(file, strline, skip)) {
if (skip) return HMpsFF::Parsekey::kComment;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t s, e;
HMpsFF::Parsekey key = checkFirstWord(strline, s, e, word);
if (key == HMpsFF::Parsekey::kName) {
Expand Down Expand Up @@ -495,8 +522,10 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options,
std::istream& file) {
std::string strline, word;

while (getline(file, strline)) {
if (is_empty(strline) || strline[0] == '*') continue;
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t start = 0;
size_t end = 0;
Expand Down Expand Up @@ -532,11 +561,10 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options,
assert(num_row == 0);
assert(row_lower.size() == 0);
assert(row_upper.size() == 0);
while (getline(file, strline)) {
if (is_empty(strline) || strline[0] == '*') continue;
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

bool isobj = false;
bool isFreeRow = false;
Expand Down Expand Up @@ -670,22 +698,10 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options,
assert(-1 == rowidx || -2 == rowidx);
};

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;

if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word);

Expand Down Expand Up @@ -977,22 +993,10 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options,
has_obj_entry_ = false;
bool has_entry = false;

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;

if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin = 0;
size_t end = 0;
Expand Down Expand Up @@ -1143,22 +1147,10 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options,
has_lower.assign(num_col, false);
has_upper.assign(num_col, false);

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;

if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin = 0;
size_t end = 0;
Expand Down Expand Up @@ -1422,22 +1414,10 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options,
// Initialise tracking for duplicate entries
has_row_entry_.assign(num_row, false);

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;

if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin, end;
std::string word;
Expand Down Expand Up @@ -1575,21 +1555,10 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian(
size_t end_coeff_name;
HighsInt colidx, rowidx;

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;
if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin = 0;
size_t end = 0;
Expand Down Expand Up @@ -1702,7 +1671,11 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows(
"Row name \"%s\" in %s section is not defined: ignored\n",
rowname.c_str(), section_name.c_str());
// read lines until start of new section
while (getline(file, strline)) {
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin = 0;
size_t end = 0;
HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name);
Expand All @@ -1725,21 +1698,10 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows(

auto& qentries = (rowidx == -1 ? q_entries : qrows_entries[rowidx]);

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;
if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin = 0;
size_t end = 0;
Expand Down Expand Up @@ -1878,22 +1840,10 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options,

// now parse the cone entries: one column per line
std::string strline;
while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;

if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin;
std::string colname;
Expand Down Expand Up @@ -1921,22 +1871,10 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options,
const HMpsFF::Parsekey keyword) {
std::string strline, word;

while (getline(file, strline)) {
double current = getWallTime();
if (time_limit > 0 && current - start_time > time_limit)
return HMpsFF::Parsekey::kTimeout;

if (kAnyFirstNonBlankAsStarImpliesComment) {
trim(strline);
if (strline.size() == 0 || strline[0] == '*') continue;
} else {
if (strline.size() > 0) {
// Just look for comment character in column 1
if (strline[0] == '*') continue;
}
trim(strline);
if (strline.size() == 0) continue;
}
bool skip;
while (getMpsLine(file, strline, skip)) {
if (skip) continue;
if (timeout()) return HMpsFF::Parsekey::kTimeout;

size_t begin, end;
std::string word;
Expand Down
6 changes: 5 additions & 1 deletion src/io/HMpsFF.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

using Triplet = std::tuple<HighsInt, HighsInt, double>;

const std::string mps_comment_chars = "*$";

enum class FreeFormatParserReturnCode {
kSuccess,
kParserError,
Expand Down Expand Up @@ -124,7 +126,6 @@ class HMpsFF {
HighsInt fillMatrix(const HighsLogOptions& log_options);
HighsInt fillHessian(const HighsLogOptions& log_options);

const bool kAnyFirstNonBlankAsStarImpliesComment = false;
/// how to treat variables that appear in COLUMNS section first
/// assume them to be binary as in the original IBM interpretation
/// or integer with default bounds
Expand Down Expand Up @@ -189,6 +190,9 @@ class HMpsFF {

mutable std::string section_args;

bool timeout();
bool getMpsLine(std::istream& file, std::string& strline, bool& skip);

FreeFormatParserReturnCode parse(const HighsLogOptions& log_options,
const std::string& filename);
// Checks first word of strline and wraps it by it_begin and it_end
Expand Down
20 changes: 12 additions & 8 deletions src/util/stringutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ void strTrim(char* str);

void tolower(std::string& str);

const std::string non_chars = "\t\n\v\f\r ";
std::string& ltrim(std::string& str, const std::string& chars = non_chars);
std::string& rtrim(std::string& str, const std::string& chars = non_chars);
std::string& trim(std::string& str, const std::string& chars = non_chars);

bool is_empty(std::string& str, const std::string& chars = non_chars);
bool is_empty(char c, const std::string& chars = non_chars);
bool is_end(std::string& str, size_t end, const std::string& chars = non_chars);
const std::string default_non_chars = "\t\n\v\f\r ";
std::string& ltrim(std::string& str,
const std::string& chars = default_non_chars);
std::string& rtrim(std::string& str,
const std::string& chars = default_non_chars);
std::string& trim(std::string& str,
const std::string& chars = default_non_chars);

bool is_empty(std::string& str, const std::string& chars = default_non_chars);
bool is_empty(char c, const std::string& chars = default_non_chars);
bool is_end(std::string& str, size_t end,
const std::string& chars = default_non_chars);

// todo: replace with pair of references rather than string ret value to avoid
// copy and also using function below. or do it properly with iterators.
Expand Down
Loading