From b42d3140a50051efd4e097df0e605943502148e0 Mon Sep 17 00:00:00 2001 From: jajhall Date: Mon, 9 Sep 2024 00:39:38 +0100 Subject: [PATCH 1/6] Now reading rvb-sub, but need to add the comment trim to other MPS read methods to pass unit test --- check/TestFilereader.cpp | 15 ++++++++++++++- src/io/HMpsFF.cpp | 5 +++++ src/util/stringutil.cpp | 9 +++++++++ src/util/stringutil.h | 17 ++++++++++------- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 14ad114b6e..98c0ecae26 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -10,7 +10,7 @@ #include "lp_data/HighsLp.h" #include "lp_data/HighsLpUtils.h" -const bool dev_run = false; +const bool dev_run = true; TEST_CASE("filereader-edge-cases", "[highs_filereader]") { std::string model = ""; @@ -343,3 +343,16 @@ 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); +} diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 14f0bf5dc0..823109a6e5 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -534,6 +534,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, assert(row_upper.size() == 0); while (getline(file, strline)) { if (is_empty(strline) || strline[0] == '*') continue; + printf("HMpsFF::parseRows before rtrim: strline = \"%s\"\n", strline.c_str()); + mpsCommentTrim(strline); + printf("HMpsFF::parseRows after rtrim: strline = \"%s\"\n", strline.c_str()); double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; @@ -588,7 +591,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, } std::string rowname = first_word(strline, start + 1); + printf("HMpsFF::parseRows rowname = \"%s\"; strline = \"%s\"\n", rowname.c_str(), strline.c_str()); size_t rowname_end = first_word_end(strline, start + 1); + printf("HMpsFF::parseRows rowname_end = %d\n", int(rowname_end)); // Detect if file is in fixed format. if (!is_end(strline, rowname_end)) { diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 5b0de58e73..5a4fef80a5 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -126,3 +126,12 @@ std::string first_word(std::string& str, size_t start) { assert(next_word_start != std::string::npos); return str.substr(next_word_start, next_word_end - next_word_start); } + +std::string& mpsCommentTrim(std::string& str) { + const size_t p = str.find_first_of(mps_comment_chars); + printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), str.c_str(), p); + if (p > str.length()) return str; + str.erase(p); + return str; +} + diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 4fa072485b..95b71e17e1 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -27,18 +27,21 @@ 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); +const std::string default_non_chars = "\t\n\v\f\r "; +const std::string mps_comment_chars = "*$"; +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 = 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); +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. std::string first_word(std::string& str, size_t start); size_t first_word_end(std::string& str, size_t start); +std::string& mpsCommentTrim(std::string& str); + #endif From 1556a6ed2161ef4d98b6f9b7a6650f1fc300fd3c Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 11 Sep 2024 23:17:43 +0100 Subject: [PATCH 2/6] Added comment.mps --- check/instances/comment.mps | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 check/instances/comment.mps diff --git a/check/instances/comment.mps b/check/instances/comment.mps new file mode 100644 index 0000000000..96d7c80985 --- /dev/null +++ b/check/instances/comment.mps @@ -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 From 1ec433b3caee9dd65fd4534eff88a067c0c0e8c4 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 09:03:44 +0100 Subject: [PATCH 3/6] Introduced HMpsFF::getMpsLine, eliminating mpsCommentTrim --- src/io/HMpsFF.cpp | 25 ++++++++++++++++++++----- src/io/HMpsFF.h | 4 ++++ src/util/stringutil.cpp | 9 --------- src/util/stringutil.h | 3 --- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 823109a6e5..e7bd94e1d9 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -210,6 +210,22 @@ HighsInt HMpsFF::fillHessian(const HighsLogOptions& log_options) { return 0; } +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); + printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), strline.c_str(), p); + // If a comment character has been found, then erase from it to + // the end of the line + if (p <= strline.length()) strline.erase(p); + } + return true; +} + FreeFormatParserReturnCode HMpsFF::parse(const HighsLogOptions& log_options, const std::string& filename) { HMpsFF::Parsekey keyword = HMpsFF::Parsekey::kNone; @@ -525,6 +541,7 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, std::istream& file) { std::string strline, word; + bool skip; bool hasobj = false; // Assign a default objective name objective_name = "Objective"; @@ -532,11 +549,9 @@ 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; - printf("HMpsFF::parseRows before rtrim: strline = \"%s\"\n", strline.c_str()); - mpsCommentTrim(strline); - printf("HMpsFF::parseRows after rtrim: strline = \"%s\"\n", strline.c_str()); + while (getMpsLine(file, strline, skip)) { + if (skip) continue; + printf("HMpsFF::parseRows: strline = \"%s\"\n", strline.c_str()); double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index 6c17e0c7ef..cd5dc5c22b 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -38,6 +38,8 @@ using Triplet = std::tuple; +const std::string mps_comment_chars = "*$"; + enum class FreeFormatParserReturnCode { kSuccess, kParserError, @@ -189,6 +191,8 @@ class HMpsFF { mutable std::string section_args; + 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 diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 5a4fef80a5..5b0de58e73 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -126,12 +126,3 @@ std::string first_word(std::string& str, size_t start) { assert(next_word_start != std::string::npos); return str.substr(next_word_start, next_word_end - next_word_start); } - -std::string& mpsCommentTrim(std::string& str) { - const size_t p = str.find_first_of(mps_comment_chars); - printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), str.c_str(), p); - if (p > str.length()) return str; - str.erase(p); - return str; -} - diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 95b71e17e1..3c0a9c819d 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -28,7 +28,6 @@ void strTrim(char* str); void tolower(std::string& str); const std::string default_non_chars = "\t\n\v\f\r "; -const std::string mps_comment_chars = "*$"; 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); @@ -42,6 +41,4 @@ bool is_end(std::string& str, size_t end, const std::string& chars = default_non std::string first_word(std::string& str, size_t start); size_t first_word_end(std::string& str, size_t start); -std::string& mpsCommentTrim(std::string& str); - #endif From 49c6aa4b1ff62cc0b6d9aa8ba04f49ef50cb2d70 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 09:34:00 +0100 Subject: [PATCH 4/6] Start replacing use of getline by getMpsLine --- src/io/HMpsFF.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index e7bd94e1d9..9aa9bbe952 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -218,10 +218,12 @@ bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { } else { // Remove any trailing comment const size_t p = strline.find_first_of(mps_comment_chars); - printf("In rTrimAt, First of \"%s\" in \"%s\" is %zu\n", mps_comment_chars.c_str(), strline.c_str(), p); - // If a comment character has been found, then erase from it to - // the end of the line - if (p <= strline.length()) strline.erase(p); + 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); + } } return true; } @@ -541,7 +543,6 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, std::istream& file) { std::string strline, word; - bool skip; bool hasobj = false; // Assign a default objective name objective_name = "Objective"; @@ -549,9 +550,9 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, assert(num_row == 0); assert(row_lower.size() == 0); assert(row_upper.size() == 0); + bool skip; while (getMpsLine(file, strline, skip)) { if (skip) continue; - printf("HMpsFF::parseRows: strline = \"%s\"\n", strline.c_str()); double current = getWallTime(); if (time_limit > 0 && current - start_time > time_limit) return HMpsFF::Parsekey::kTimeout; From aeb80d5d53ca9cb593ac94efa141ad44cff901c6 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 09:51:53 +0100 Subject: [PATCH 5/6] Now using getMpsLine rather than getline --- check/TestFilereader.cpp | 7 +- src/io/HMpsFF.cpp | 163 ++++++++++++++++----------------------- src/io/HMpsFF.h | 1 - src/util/stringutil.h | 12 ++- 4 files changed, 79 insertions(+), 104 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index 98c0ecae26..bd6c709c1e 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -10,7 +10,7 @@ #include "lp_data/HighsLp.h" #include "lp_data/HighsLpUtils.h" -const bool dev_run = true; +const bool dev_run = false; TEST_CASE("filereader-edge-cases", "[highs_filereader]") { std::string model = ""; @@ -348,9 +348,10 @@ 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"; + std::string model_file = + std::string(HIGHS_DIR) + "/check/instances/comment.mps"; Highs highs; - highs.setOptionValue("output_flag", dev_run); + // 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; diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 9aa9bbe952..9f9b3fbb55 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -219,8 +219,8 @@ bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { // 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 + // 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); } @@ -466,7 +466,8 @@ 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)) { + bool skip; + if (getMpsLine(file, strline, skip)) { strline = trim(strline); if (strline.empty()) return HMpsFF::Parsekey::kComment; size_t s, e; @@ -513,8 +514,9 @@ 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; size_t start = 0; size_t end = 0; @@ -607,9 +609,7 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, } std::string rowname = first_word(strline, start + 1); - printf("HMpsFF::parseRows rowname = \"%s\"; strline = \"%s\"\n", rowname.c_str(), strline.c_str()); size_t rowname_end = first_word_end(strline, start + 1); - printf("HMpsFF::parseRows rowname_end = %d\n", int(rowname_end)); // Detect if file is in fixed format. if (!is_end(strline, rowname_end)) { @@ -691,22 +691,18 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, assert(-1 == rowidx || -2 == rowidx); }; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word); @@ -998,22 +994,18 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, has_obj_entry_ = false; bool has_entry = false; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1164,22 +1156,18 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, has_lower.assign(num_col, false); has_upper.assign(num_col, false); - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1443,22 +1431,18 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, // Initialise tracking for duplicate entries has_row_entry_.assign(num_row, false); - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin, end; std::string word; @@ -1596,21 +1580,18 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( size_t end_coeff_name; HighsInt colidx, rowidx; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1723,7 +1704,8 @@ 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)) { size_t begin = 0; size_t end = 0; HMpsFF::Parsekey key = checkFirstWord(strline, begin, end, col_name); @@ -1746,21 +1728,18 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( auto& qentries = (rowidx == -1 ? q_entries : qrows_entries[rowidx]); - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin = 0; size_t end = 0; @@ -1899,22 +1878,18 @@ 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)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin; std::string colname; @@ -1942,22 +1917,18 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, const HMpsFF::Parsekey keyword) { std::string strline, word; - while (getline(file, strline)) { + bool skip; + while (getMpsLine(file, strline, skip)) { 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; + if (strline.size() > 0) { + // Just look for comment character in column 1 + if (strline[0] == '*') continue; } + trim(strline); + if (strline.size() == 0) continue; size_t begin, end; std::string word; diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index cd5dc5c22b..b5c86ae177 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -126,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 diff --git a/src/util/stringutil.h b/src/util/stringutil.h index 3c0a9c819d..25ac72c0ef 100644 --- a/src/util/stringutil.h +++ b/src/util/stringutil.h @@ -28,13 +28,17 @@ void strTrim(char* str); void tolower(std::string& str); 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); +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); +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. From cd69aea9efb7339636003b24c5aee905b56bf3ff Mon Sep 17 00:00:00 2001 From: JAJHall Date: Thu, 12 Sep 2024 10:51:41 +0100 Subject: [PATCH 6/6] getMpsLine now trims lines and the checks is_empty --- check/TestFilereader.cpp | 2 +- src/io/HMpsFF.cpp | 116 +++++++++++---------------------------- src/io/HMpsFF.h | 1 + 3 files changed, 33 insertions(+), 86 deletions(-) diff --git a/check/TestFilereader.cpp b/check/TestFilereader.cpp index bd6c709c1e..a1b420d316 100644 --- a/check/TestFilereader.cpp +++ b/check/TestFilereader.cpp @@ -351,7 +351,7 @@ TEST_CASE("filereader-comment", "[highs_filereader]") { std::string model_file = std::string(HIGHS_DIR) + "/check/instances/comment.mps"; Highs highs; - // highs.setOptionValue("output_flag", dev_run); + 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; diff --git a/src/io/HMpsFF.cpp b/src/io/HMpsFF.cpp index 9f9b3fbb55..414e1fcbbb 100644 --- a/src/io/HMpsFF.cpp +++ b/src/io/HMpsFF.cpp @@ -210,6 +210,10 @@ 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; @@ -223,7 +227,10 @@ bool HMpsFF::getMpsLine(std::istream& file, std::string& strline, bool& skip) { // 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; } @@ -468,8 +475,9 @@ HMpsFF::Parsekey HMpsFF::parseDefault(const HighsLogOptions& log_options, std::string strline, word; bool skip; if (getMpsLine(file, strline, skip)) { - strline = trim(strline); - if (strline.empty()) return HMpsFF::Parsekey::kComment; + 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) { @@ -517,6 +525,7 @@ HMpsFF::Parsekey HMpsFF::parseObjsense(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t start = 0; size_t end = 0; @@ -555,9 +564,7 @@ HMpsFF::Parsekey HMpsFF::parseRows(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { if (skip) continue; - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; + if (timeout()) return HMpsFF::Parsekey::kTimeout; bool isobj = false; bool isFreeRow = false; @@ -693,16 +700,8 @@ typename HMpsFF::Parsekey HMpsFF::parseCols(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; HMpsFF::Parsekey key = checkFirstWord(strline, start, end, word); @@ -996,16 +995,8 @@ HMpsFF::Parsekey HMpsFF::parseRhs(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1158,16 +1149,8 @@ HMpsFF::Parsekey HMpsFF::parseBounds(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1433,16 +1416,8 @@ HMpsFF::Parsekey HMpsFF::parseRanges(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin, end; std::string word; @@ -1582,16 +1557,8 @@ typename HMpsFF::Parsekey HMpsFF::parseHessian( bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1706,6 +1673,9 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( // read lines until start of new section 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); @@ -1730,16 +1700,8 @@ typename HMpsFF::Parsekey HMpsFF::parseQuadRows( bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin = 0; size_t end = 0; @@ -1880,16 +1842,8 @@ typename HMpsFF::Parsekey HMpsFF::parseCones(const HighsLogOptions& log_options, std::string strline; bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin; std::string colname; @@ -1919,16 +1873,8 @@ typename HMpsFF::Parsekey HMpsFF::parseSos(const HighsLogOptions& log_options, bool skip; while (getMpsLine(file, strline, skip)) { - double current = getWallTime(); - if (time_limit > 0 && current - start_time > time_limit) - return HMpsFF::Parsekey::kTimeout; - - if (strline.size() > 0) { - // Just look for comment character in column 1 - if (strline[0] == '*') continue; - } - trim(strline); - if (strline.size() == 0) continue; + if (skip) continue; + if (timeout()) return HMpsFF::Parsekey::kTimeout; size_t begin, end; std::string word; diff --git a/src/io/HMpsFF.h b/src/io/HMpsFF.h index b5c86ae177..2426273ec2 100644 --- a/src/io/HMpsFF.h +++ b/src/io/HMpsFF.h @@ -190,6 +190,7 @@ 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,