From c1b85ec74d80b673b22d729122445e5eb08bdbe3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 24 Apr 2024 08:57:27 -0600 Subject: [PATCH 01/20] highspy: release python GIL when calling run --- highspy/highs_bindings.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/highspy/highs_bindings.cpp b/highspy/highs_bindings.cpp index 5c64109adb..1f4e9c0423 100644 --- a/highspy/highs_bindings.cpp +++ b/highspy/highs_bindings.cpp @@ -551,6 +551,15 @@ std::tuple highs_getRowByName(Highs* h, } +HighsStatus highs_run(Highs* h) +{ + py::gil_scoped_release release; + HighsStatus status = h->run(); + py::gil_scoped_acquire(); + return status; +} + + PYBIND11_MODULE(highspy, m) { // enum classes @@ -835,7 +844,7 @@ PYBIND11_MODULE(highspy, m) { .def("writeBasis", &Highs::writeBasis) .def("postsolve", &highs_postsolve) .def("postsolve", &highs_mipPostsolve) - .def("run", &Highs::run) + .def("run", &highs_run) .def("presolve", &Highs::presolve) .def("writeSolution", &highs_writeSolution) .def("readSolution", &Highs::readSolution) From e9f423506dc14a7a6c2b2eecf6c889533e3f95d2 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 19 Aug 2024 13:25:18 -0600 Subject: [PATCH 02/20] run solve in background thread so that keyboard interrupt works --- src/highspy/highs.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/highspy/highs.py b/src/highspy/highs.py index 9abf20ea26..456708e3c2 100644 --- a/src/highspy/highs.py +++ b/src/highspy/highs.py @@ -36,6 +36,13 @@ from itertools import groupby from operator import itemgetter from decimal import Decimal +from threading import Thread + + +class _ThreadingResult: + def __init__(self): + self.out = None + class Highs(_Highs): """HiGHS solver interface""" @@ -51,10 +58,20 @@ def __init__(self): # Silence logging def silent(self): super().setOptionValue("output_flag", False) + + def _run(self, res): + res.out = super().run() + + def run(self): + return self.solve() # solve def solve(self): - return super().run() + res = _ThreadingResult() + t = Thread(target=self._run, args=(res,)) + t.start() + t.join() + return res.out # reset the objective and sense, then solve def minimize(self, obj=None): From fb8998673c4958bd05f3ef6cda4221098f9d56dd Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 28 Aug 2024 16:19:42 +0200 Subject: [PATCH 03/20] use std::array instead of C arrays if possible --- src/io/HMPSIO.cpp | 32 +++++++++++++++++++------------- src/io/HighsIO.cpp | 32 ++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/io/HMPSIO.cpp b/src/io/HMPSIO.cpp index b6037d3624..014f5716b1 100644 --- a/src/io/HMPSIO.cpp +++ b/src/io/HMPSIO.cpp @@ -78,21 +78,21 @@ FilereaderRetcode readMps( highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Opened file OK\n"); // Input buffer const HighsInt lmax = 128; - char line[lmax]; - char flag[2] = {0, 0}; - double data[3]; + std::array line; + std::array flag = {0, 0}; + std::array data; HighsInt num_alien_entries = 0; HighsVarType integerCol = HighsVarType::kContinuous; // Load NAME - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read NAME OK\n"); // Load OBJSENSE or ROWS - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); if (flag[0] == 'O') { // Found OBJSENSE - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); std::string sense(&line[2], &line[2] + 3); // the sense must be "MAX" or "MIN" if (sense.compare("MAX") == 0) { @@ -105,7 +105,7 @@ FilereaderRetcode readMps( highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read OBJSENSE OK\n"); // Load ROWS - load_mpsLine(file, integerCol, lmax, line, flag, data); + load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), data.data()); } row_names.clear(); @@ -113,7 +113,8 @@ FilereaderRetcode readMps( vector rowType; map rowIndex; double objName = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { if (flag[0] == 'N' && (objName == 0 || keep_n_rows == kKeepNRowsDeleteRows)) { // N-row: take the first as the objective and possibly ignore any others @@ -149,7 +150,8 @@ FilereaderRetcode readMps( // line - field 5 non-empty. save_flag1 is used to deduce whether // the row name and value are from fields 5 and 6, or 3 and 4 HighsInt save_flag1 = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { HighsInt iRow = rowIndex[data[2]] - 1; std::string name = ""; if (iRow >= 0) name = row_names[iRow]; @@ -218,7 +220,8 @@ FilereaderRetcode readMps( num_alien_entries = 0; vector RHS(numRow, 0); save_flag1 = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { if (data[2] != objName) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { @@ -268,7 +271,8 @@ FilereaderRetcode readMps( rowUpper.resize(numRow); if (flag[0] == 'R') { save_flag1 = 0; - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { if (rowType[iRow] == 'L' || (rowType[iRow] == 'E' && data[0] < 0)) { @@ -338,7 +342,8 @@ FilereaderRetcode readMps( colLower.assign(numCol, 0); colUpper.assign(numCol, kHighsInf); if (flag[0] == 'B') { - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { // Find the column index associated with the name "data[2]". If // the name is in colIndex then the value stored is the true // column index plus one. Otherwise 0 will be returned. @@ -389,7 +394,8 @@ FilereaderRetcode readMps( HighsInt previous_col = -1; bool has_diagonal = false; Qstart.clear(); - while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { + while (load_mpsLine(file, integerCol, lmax, line.data(), flag.data(), + data.data())) { HighsInt iCol0 = colIndex[data[1]] - 1; std::string name0 = ""; if (iCol0 >= 0) name0 = col_names[iCol0]; diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index b32b5758a5..22763a09aa 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -131,27 +131,35 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, if (flush_streams) fflush(stdout); } } else { - int len = 0; - char msgbuffer[kIoBufferSize] = {}; - if (prefix) - len = snprintf(msgbuffer, sizeof(msgbuffer), "%-9s", - HighsLogTypeTag[(int)type]); - if (len < (int)sizeof(msgbuffer)) - len += - vsnprintf(msgbuffer + len, sizeof(msgbuffer) - len, format, argptr); - if (len >= (int)sizeof(msgbuffer)) { + size_t len = 0; + std::array msgbuffer = {}; + if (prefix) { + int l = snprintf(msgbuffer.data(), msgbuffer.size(), "%-9s", + HighsLogTypeTag[(int)type]); + // assert that there are no encoding errors + assert(l >= 0); + len += static_cast(l); + } + if (len < msgbuffer.size()) { + int l = vsnprintf(msgbuffer.data() + len, msgbuffer.size() - len, format, + argptr); + // assert that there are no encoding errors + assert(l >= 0); + len += static_cast(l); + } + if (len >= msgbuffer.size()) { // Output was truncated: for now just ensure string is null-terminated - msgbuffer[sizeof(msgbuffer) - 1] = '\0'; + msgbuffer[msgbuffer.size() - 1] = '\0'; } if (log_options_.user_log_callback) { - log_options_.user_log_callback(type, msgbuffer, + log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); } if (log_options_.user_callback_active) { assert(log_options_.user_callback); HighsCallbackDataOut data_out; data_out.log_type = int(type); - log_options_.user_callback(kCallbackLogging, msgbuffer, &data_out, + log_options_.user_callback(kCallbackLogging, msgbuffer.data(), &data_out, nullptr, log_options_.user_callback_data); } } From b1862c8900c697d575a46b7e9d3b531737fd7130 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 28 Aug 2024 16:34:43 +0200 Subject: [PATCH 04/20] Chaser --- src/io/HighsIO.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 22763a09aa..936b2db5c0 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -207,21 +207,22 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, if (flush_streams) fflush(stdout); } } else { - int len; - char msgbuffer[kIoBufferSize] = {}; - len = vsnprintf(msgbuffer, sizeof(msgbuffer), format, argptr); - if (len >= (int)sizeof(msgbuffer)) { + std::array msgbuffer = {}; + int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); + // assert that there are no encoding errors + assert(len >= 0); + if (static_cast(len) >= msgbuffer.size()) { // Output was truncated: for now just ensure string is null-terminated - msgbuffer[sizeof(msgbuffer) - 1] = '\0'; + msgbuffer[msgbuffer.size() - 1] = '\0'; } if (log_options_.user_log_callback) { - log_options_.user_log_callback(type, msgbuffer, + log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); } else if (log_options_.user_callback_active) { assert(log_options_.user_callback); HighsCallbackDataOut data_out; data_out.log_type = int(type); - log_options_.user_callback(kCallbackLogging, msgbuffer, &data_out, + log_options_.user_callback(kCallbackLogging, msgbuffer.data(), &data_out, nullptr, log_options_.user_callback_data); } } @@ -269,16 +270,17 @@ void highsReportLogOptions(const HighsLogOptions& log_options_) { std::string highsFormatToString(const char* format, ...) { va_list argptr; va_start(argptr, format); - int len; - char msgbuffer[kIoBufferSize] = {}; - len = vsnprintf(msgbuffer, sizeof(msgbuffer), format, argptr); + std::array msgbuffer = {}; + int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); + // assert that there are no encoding errors + assert(len >= 0); - if (len >= (int)sizeof(msgbuffer)) { + if (static_cast(len) >= msgbuffer.size()) { // Output was truncated: for now just ensure string is null-terminated - msgbuffer[sizeof(msgbuffer) - 1] = '\0'; + msgbuffer[msgbuffer.size() - 1] = '\0'; } va_end(argptr); - return std::string(msgbuffer); + return std::string(msgbuffer.data()); } const std::string highsBoolToString(const bool b, const HighsInt field_width) { From 2788fea3603d57947a62a15e4865ab9f3bb7f1b9 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 28 Aug 2024 20:00:56 +0200 Subject: [PATCH 05/20] Use std::array in other places --- src/mip/HighsDomain.cpp | 4 ++-- src/mip/HighsGFkSolve.h | 6 +++--- src/mip/HighsImplications.cpp | 18 ++++++++++-------- src/mip/HighsPathSeparator.cpp | 2 +- src/util/HighsHashTree.h | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index eb38717812..e20ff5a19a 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -180,7 +180,7 @@ void HighsDomain::ConflictPoolPropagation::conflictAdded(HighsInt conflict) { } switch (numWatched) { case 0: { - std::pair latestActive[2]; + std::array, 2> latestActive; HighsInt numActive = 0; for (HighsInt i = start; i != end; ++i) { HighsInt pos = conflictEntries[i].boundtype == HighsBoundType::kLower @@ -306,7 +306,7 @@ void HighsDomain::ConflictPoolPropagation::propagateConflict( WatchedLiteral* watched = watchedLiterals_.data() + 2 * conflict; - HighsInt inactive[2]; + std::array inactive; HighsInt numInactive = 0; for (HighsInt i = start; i != end; ++i) { if (domain->isActive(entries[i])) continue; diff --git a/src/mip/HighsGFkSolve.h b/src/mip/HighsGFkSolve.h index 75519459de..d483d3bfda 100644 --- a/src/mip/HighsGFkSolve.h +++ b/src/mip/HighsGFkSolve.h @@ -317,7 +317,7 @@ class HighsGFkSolve { // check if a solution exists by scanning the linearly dependent rows for // nonzero right hand sides - bool hasSolution[kNumRhs]; + std::array hasSolution; HighsInt numRhsWithSolution = 0; for (int rhsIndex = 0; rhsIndex < kNumRhs; ++rhsIndex) { hasSolution[rhsIndex] = true; @@ -341,7 +341,7 @@ class HighsGFkSolve { // now iterate a subset of the basic solutions. // When a column leaves the basis we do not allow it to enter again so that // we iterate at most one solution for each nonbasic column - std::vector solution[kNumRhs]; + std::array, kNumRhs> solution; for (int rhsIndex = 0; rhsIndex < kNumRhs; ++rhsIndex) if (hasSolution[rhsIndex]) solution[rhsIndex].reserve(numCol); @@ -384,7 +384,7 @@ class HighsGFkSolve { for (HighsInt i = numFactorRows - 1; i >= 0; --i) { HighsInt row = factorRowPerm[i]; - unsigned int solval[kNumRhs]; + std::array solval; for (int rhsIndex = 0; rhsIndex < kNumRhs; ++rhsIndex) { if (!hasSolution[rhsIndex]) continue; diff --git a/src/mip/HighsImplications.cpp b/src/mip/HighsImplications.cpp index 2c909d9f0d..e625669c22 100644 --- a/src/mip/HighsImplications.cpp +++ b/src/mip/HighsImplications.cpp @@ -79,7 +79,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { pdqsort(implics.begin(), binstart); - HighsCliqueTable::CliqueVar clique[2]; + std::array clique; clique[0] = HighsCliqueTable::CliqueVar(col, val); for (auto i = binstart; i != implics.end(); ++i) { @@ -88,7 +88,7 @@ bool HighsImplications::computeImplications(HighsInt col, bool val) { else clique[1] = HighsCliqueTable::CliqueVar(i->column, 1); - cliquetable.addClique(mipsolver, clique, 2); + cliquetable.addClique(mipsolver, clique.data(), 2); if (globaldomain.infeasible() || globaldomain.isFixed(col)) return true; } @@ -519,8 +519,8 @@ void HighsImplications::separateImpliedBounds( HighsCutPool& cutpool, double feastol) { HighsDomain& globaldomain = mipsolver.mipdata_->domain; - HighsInt inds[2]; - double vals[2]; + std::array inds; + std::array vals; double rhs; HighsInt numboundchgs = 0; @@ -586,7 +586,8 @@ void HighsImplications::separateImpliedBounds( if (infeas) { vals[0] = 1.0; inds[0] = col; - cutpool.addCut(mipsolver, inds, vals, 1, 0.0, false, true, false); + cutpool.addCut(mipsolver, inds.data(), vals.data(), 1, 0.0, false, true, + false); continue; } @@ -621,7 +622,7 @@ void HighsImplications::separateImpliedBounds( if (viol > feastol) { // printf("added implied bound cut to pool\n"); - cutpool.addCut(mipsolver, inds, vals, 2, rhs, + cutpool.addCut(mipsolver, inds.data(), vals.data(), 2, rhs, mipsolver.variableType(implics[i].column) != HighsVarType::kContinuous, false, false, false); @@ -637,7 +638,8 @@ void HighsImplications::separateImpliedBounds( if (infeas) { vals[0] = -1.0; inds[0] = col; - cutpool.addCut(mipsolver, inds, vals, 1, -1.0, false, true, false); + cutpool.addCut(mipsolver, inds.data(), vals.data(), 1, -1.0, false, + true, false); continue; } @@ -671,7 +673,7 @@ void HighsImplications::separateImpliedBounds( if (viol > feastol) { // printf("added implied bound cut to pool\n"); - cutpool.addCut(mipsolver, inds, vals, 2, rhs, + cutpool.addCut(mipsolver, inds.data(), vals.data(), 2, rhs, mipsolver.variableType(implics[i].column) != HighsVarType::kContinuous, false, false, false); diff --git a/src/mip/HighsPathSeparator.cpp b/src/mip/HighsPathSeparator.cpp index 83ab962a3d..0989bd098c 100644 --- a/src/mip/HighsPathSeparator.cpp +++ b/src/mip/HighsPathSeparator.cpp @@ -174,7 +174,7 @@ void HighsPathSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, HighsInt currentPath[maxPathLen]; std::vector, std::vector>> aggregatedPath; - double scales[2]; + std::array scales; for (HighsInt i = 0; i != lp.num_row_; ++i) { switch (rowtype[i]) { case RowType::kUnusuable: diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index c42bc271e9..b33e470e43 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -113,8 +113,8 @@ class HighsHashTree { // to do a linear scan and key comparisons at all Occupation occupation; int size; - uint64_t hashes[capacity() + 1]; - Entry entries[capacity()]; + std::array hashes; + std::array entries; InnerLeaf() : occupation(0), size(0) { hashes[0] = 0; } From 455c007defe951b6b0d39aa122ffc4e552c94993 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Thu, 29 Aug 2024 10:37:22 +0200 Subject: [PATCH 06/20] Use std::next a little bit --- src/util/HighsHashTree.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index b33e470e43..46557b8119 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -123,7 +123,8 @@ class HighsHashTree { assert(other.size <= capacity()); memcpy((void*)this, (void*)&other, (char*)&other.hashes[other.size + 1] - (char*)&other); - std::move(&other.entries[0], &other.entries[size], &entries[0]); + std::move(other.entries.begin(), std::next(other.entries.begin(), size), + entries.begin()); } int get_num_entries() const { return size; } @@ -190,7 +191,9 @@ class HighsHashTree { --size; if (pos < size) { - std::move(&entries[pos + 1], &entries[size + 1], &entries[pos]); + std::move(std::next(entries.begin(), pos + 1), + std::next(entries.begin(), size + 1), + std::next(entries.begin(), pos)); memmove(&hashes[pos], &hashes[pos + 1], sizeof(hashes[0]) * (size - pos)); if (get_first_chunk16(hashes[startPos]) != hashChunk) @@ -254,7 +257,9 @@ class HighsHashTree { void move_backward(const int& first, const int& last) { // move elements backwards - std::move_backward(&entries[first], &entries[last], &entries[last + 1]); + std::move_backward(std::next(entries.begin(), first), + std::next(entries.begin(), last), + std::next(entries.begin(), last + 1)); memmove(&hashes[first + 1], &hashes[first], sizeof(hashes[0]) * (last - first)); } From d69e295d35aa595c53bce4dd4814cae541ecfb39 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Fri, 30 Aug 2024 16:44:32 +0200 Subject: [PATCH 07/20] Make hashes an std::array --- src/util/HighsHashTree.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index c42bc271e9..026f26195b 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -113,7 +113,7 @@ class HighsHashTree { // to do a linear scan and key comparisons at all Occupation occupation; int size; - uint64_t hashes[capacity() + 1]; + std::array hashes; Entry entries[capacity()]; InnerLeaf() : occupation(0), size(0) { hashes[0] = 0; } @@ -121,8 +121,10 @@ class HighsHashTree { template InnerLeaf(InnerLeaf&& other) { assert(other.size <= capacity()); - memcpy((void*)this, (void*)&other, - (char*)&other.hashes[other.size + 1] - (char*)&other); + occupation = other.occupation; + size = other.size; + std::copy(other.hashes.cbegin(), + std::next(other.hashes.cbegin(), size + 1), hashes.begin()); std::move(&other.entries[0], &other.entries[size], &entries[0]); } From 584069fb19f2f08050d038d49fea13b2a8274084 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 2 Sep 2024 08:45:45 +0200 Subject: [PATCH 08/20] Make entries an std::array --- src/util/HighsHashTree.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index 026f26195b..c06bb96758 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -114,7 +114,7 @@ class HighsHashTree { Occupation occupation; int size; std::array hashes; - Entry entries[capacity()]; + std::array entries; InnerLeaf() : occupation(0), size(0) { hashes[0] = 0; } @@ -125,7 +125,8 @@ class HighsHashTree { size = other.size; std::copy(other.hashes.cbegin(), std::next(other.hashes.cbegin(), size + 1), hashes.begin()); - std::move(&other.entries[0], &other.entries[size], &entries[0]); + std::move(other.entries.begin(), std::next(other.entries.begin(), size), + entries.begin()); } int get_num_entries() const { return size; } @@ -192,7 +193,9 @@ class HighsHashTree { --size; if (pos < size) { - std::move(&entries[pos + 1], &entries[size + 1], &entries[pos]); + std::move(std::next(entries.begin(), pos + 1), + std::next(entries.begin(), size + 1), + std::next(entries.begin(), pos)); memmove(&hashes[pos], &hashes[pos + 1], sizeof(hashes[0]) * (size - pos)); if (get_first_chunk16(hashes[startPos]) != hashChunk) @@ -256,7 +259,9 @@ class HighsHashTree { void move_backward(const int& first, const int& last) { // move elements backwards - std::move_backward(&entries[first], &entries[last], &entries[last + 1]); + std::move_backward(std::next(entries.begin(), first), + std::next(entries.begin(), last), + std::next(entries.begin(), last + 1)); memmove(&hashes[first + 1], &hashes[first], sizeof(hashes[0]) * (last - first)); } From 6e32cb553e70a7bb05083585eef50602aff164c5 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Mon, 2 Sep 2024 10:09:34 +0200 Subject: [PATCH 09/20] Fix warning along the way --- src/mip/HighsDomain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mip/HighsDomain.cpp b/src/mip/HighsDomain.cpp index e20ff5a19a..3fb1950e79 100644 --- a/src/mip/HighsDomain.cpp +++ b/src/mip/HighsDomain.cpp @@ -276,7 +276,7 @@ void HighsDomain::ConflictPoolPropagation::updateActivityUbChange( HighsInt conflict = i >> 1; const HighsDomainChange& domchg = watchedLiterals_[i].domchg; - HighsInt numInactiveDelta = + uint8_t numInactiveDelta = (domchg.boundval < newbound) - (domchg.boundval < oldbound); if (numInactiveDelta != 0) { conflictFlag_[conflict] += numInactiveDelta; From ff778c65a780bfd67994aeb2c9764cef75a838d8 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 3 Sep 2024 11:39:00 +0200 Subject: [PATCH 10/20] Two minor changes --- src/io/FilereaderLp.cpp | 11 ++++++----- src/util/HighsHashTree.h | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index 5de2163359..3e6ab1cad5 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -216,16 +216,17 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options, void FilereaderLp::writeToFile(FILE* file, const char* format, ...) { va_list argptr; va_start(argptr, format); - char stringbuffer[LP_MAX_LINE_LENGTH + 1] = {}; + std::array stringbuffer = {}; HighsInt tokenlength = - vsnprintf(stringbuffer, sizeof stringbuffer, format, argptr); + vsnprintf(stringbuffer.data(), stringbuffer.size(), format, argptr); va_end(argptr); - if (this->linelength + tokenlength >= LP_MAX_LINE_LENGTH) { + if (static_cast(this->linelength + tokenlength) + 1 >= + stringbuffer.size()) { fprintf(file, "\n"); - fprintf(file, "%s", stringbuffer); + fprintf(file, "%s", stringbuffer.data()); this->linelength = tokenlength; } else { - fprintf(file, "%s", stringbuffer); + fprintf(file, "%s", stringbuffer.data()); this->linelength += tokenlength; } } diff --git a/src/util/HighsHashTree.h b/src/util/HighsHashTree.h index c06bb96758..abd9012bde 100644 --- a/src/util/HighsHashTree.h +++ b/src/util/HighsHashTree.h @@ -843,7 +843,7 @@ class HighsHashTree { hash, hashPos + 1, entry); } else { // there are many collisions, determine the exact sizes first - uint8_t sizes[InnerLeaf<4>::capacity() + 1] = {}; + std::array::capacity() + 1> sizes = {}; sizes[occupation.num_set_until(hashChunk) - 1] += 1; for (int i = 0; i < leaf->size; ++i) { int pos = From d772edf6dd42ed84832b38c868155620b1df6701 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 3 Sep 2024 12:43:53 +0200 Subject: [PATCH 11/20] No need to check for null termination --- src/io/FilereaderLp.cpp | 3 +-- src/io/HighsIO.cpp | 15 +-------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/io/FilereaderLp.cpp b/src/io/FilereaderLp.cpp index 3e6ab1cad5..8a9e0ba988 100644 --- a/src/io/FilereaderLp.cpp +++ b/src/io/FilereaderLp.cpp @@ -220,8 +220,7 @@ void FilereaderLp::writeToFile(FILE* file, const char* format, ...) { HighsInt tokenlength = vsnprintf(stringbuffer.data(), stringbuffer.size(), format, argptr); va_end(argptr); - if (static_cast(this->linelength + tokenlength) + 1 >= - stringbuffer.size()) { + if (this->linelength + tokenlength >= LP_MAX_LINE_LENGTH) { fprintf(file, "\n"); fprintf(file, "%s", stringbuffer.data()); this->linelength = tokenlength; diff --git a/src/io/HighsIO.cpp b/src/io/HighsIO.cpp index 936b2db5c0..c1e8ad4195 100644 --- a/src/io/HighsIO.cpp +++ b/src/io/HighsIO.cpp @@ -138,7 +138,7 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, HighsLogTypeTag[(int)type]); // assert that there are no encoding errors assert(l >= 0); - len += static_cast(l); + len = static_cast(l); } if (len < msgbuffer.size()) { int l = vsnprintf(msgbuffer.data() + len, msgbuffer.size() - len, format, @@ -147,10 +147,6 @@ void highsLogUser(const HighsLogOptions& log_options_, const HighsLogType type, assert(l >= 0); len += static_cast(l); } - if (len >= msgbuffer.size()) { - // Output was truncated: for now just ensure string is null-terminated - msgbuffer[msgbuffer.size() - 1] = '\0'; - } if (log_options_.user_log_callback) { log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); @@ -211,10 +207,6 @@ void highsLogDev(const HighsLogOptions& log_options_, const HighsLogType type, int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); // assert that there are no encoding errors assert(len >= 0); - if (static_cast(len) >= msgbuffer.size()) { - // Output was truncated: for now just ensure string is null-terminated - msgbuffer[msgbuffer.size() - 1] = '\0'; - } if (log_options_.user_log_callback) { log_options_.user_log_callback(type, msgbuffer.data(), log_options_.user_log_callback_data); @@ -274,11 +266,6 @@ std::string highsFormatToString(const char* format, ...) { int len = vsnprintf(msgbuffer.data(), msgbuffer.size(), format, argptr); // assert that there are no encoding errors assert(len >= 0); - - if (static_cast(len) >= msgbuffer.size()) { - // Output was truncated: for now just ensure string is null-terminated - msgbuffer[msgbuffer.size() - 1] = '\0'; - } va_end(argptr); return std::string(msgbuffer.data()); } From cc3e870a05892bc7205f36a18cd31a5950e9d315 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 3 Sep 2024 15:15:11 +0200 Subject: [PATCH 12/20] Fix typo --- src/parallel/HighsSplitDeque.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parallel/HighsSplitDeque.h b/src/parallel/HighsSplitDeque.h index c4981f14ae..cdf3831953 100644 --- a/src/parallel/HighsSplitDeque.h +++ b/src/parallel/HighsSplitDeque.h @@ -176,7 +176,7 @@ class HighsSplitDeque { static_assert(sizeof(StealerData) <= 64, "sizeof(StealerData) exceeds cache line size"); static_assert(sizeof(WorkerBunkData) <= 64, - "sizeof(GlobalQueueData) exceeds cache line size"); + "sizeof(WorkerBunkData) exceeds cache line size"); alignas(64) OwnerData ownerData; alignas(64) std::atomic splitRequest; From b8a716f0e3f84fbdd8b9a7268aaed8c4a3fbfce0 Mon Sep 17 00:00:00 2001 From: jajhall Date: Wed, 4 Sep 2024 13:30:52 +0100 Subject: [PATCH 13/20] Formatted --- src/io/HighsIO.h | 2 +- src/lp_data/HighsSolve.cpp | 2 +- src/test/DevKkt.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/HighsIO.h b/src/io/HighsIO.h index b63d47141f..52bf156ff6 100644 --- a/src/io/HighsIO.h +++ b/src/io/HighsIO.h @@ -18,7 +18,7 @@ #include #include "lp_data/HighsCallback.h" -//#include "util/HighsInt.h" +// #include "util/HighsInt.h" class HighsOptions; diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index d839bfb88d..b1a33476fb 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -127,7 +127,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return HighsStatus::kError; } } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } // unwelcome_ipx_status } else { // PDLP has been used, so check whether claim of optimality // satisfies the HiGHS criteria diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index de393f7bf1..47941d9fd6 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -261,8 +261,8 @@ void checkComplementarySlackness(const State& state, if (fabs(state.colDual[i]) > tol && fabs(state.colValue[i] - state.colUpper[i]) > tol) { if (dev_print) - std::cout << "Comp. slackness fail: " - << "l[" << i << "]=" << state.colLower[i] << ", x[" << i + std::cout << "Comp. slackness fail: " << "l[" << i + << "]=" << state.colLower[i] << ", x[" << i << "]=" << state.colValue[i] << ", z[" << i << "]=" << state.colDual[i] << std::endl; infeas = fabs(state.colDual[i]); From cd80bd5dc7495b1d51ef0e7338701465011f7d0a Mon Sep 17 00:00:00 2001 From: JAJHall Date: Mon, 9 Sep 2024 17:19:58 +0100 Subject: [PATCH 14/20] Formatted with Ubuntu clang-format version 14.0.0-1ubuntu1.1 --- src/lp_data/HighsSolve.cpp | 2 +- src/test/DevKkt.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index b1a33476fb..d839bfb88d 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -127,7 +127,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return HighsStatus::kError; } } // options.run_crossover == kHighsOnString - } // unwelcome_ipx_status + } // unwelcome_ipx_status } else { // PDLP has been used, so check whether claim of optimality // satisfies the HiGHS criteria diff --git a/src/test/DevKkt.cpp b/src/test/DevKkt.cpp index 47941d9fd6..de393f7bf1 100644 --- a/src/test/DevKkt.cpp +++ b/src/test/DevKkt.cpp @@ -261,8 +261,8 @@ void checkComplementarySlackness(const State& state, if (fabs(state.colDual[i]) > tol && fabs(state.colValue[i] - state.colUpper[i]) > tol) { if (dev_print) - std::cout << "Comp. slackness fail: " << "l[" << i - << "]=" << state.colLower[i] << ", x[" << i + std::cout << "Comp. slackness fail: " + << "l[" << i << "]=" << state.colLower[i] << ", x[" << i << "]=" << state.colValue[i] << ", z[" << i << "]=" << state.colDual[i] << std::endl; infeas = fabs(state.colDual[i]); From c706fe9fb36d3c7ddbf39d7c4dbeaa724d85952c Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 10 Sep 2024 08:07:13 +0200 Subject: [PATCH 15/20] Fix some typos --- src/interfaces/highs_c_api.h | 2 +- src/lp_data/HighsInterface.cpp | 6 +++--- src/lp_data/HighsSolve.cpp | 2 +- src/util/HVectorBase.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/interfaces/highs_c_api.h b/src/interfaces/highs_c_api.h index 3a0c9c05c9..43833a9c75 100644 --- a/src/interfaces/highs_c_api.h +++ b/src/interfaces/highs_c_api.h @@ -18,7 +18,7 @@ // Highs_mipCall or Highs_qpCall, and these methods return the // appropriate solution information // -// For sophisticated applications, where esoteric solutiuon +// For sophisticated applications, where esoteric solution // information is needed, or if a sequence of modified models need to // be solved, use the Highs_create method to generate a pointer to an // instance of the C++ Highs class, and then use any of a large number diff --git a/src/lp_data/HighsInterface.cpp b/src/lp_data/HighsInterface.cpp index 6176c39bf1..f3150c64e8 100644 --- a/src/lp_data/HighsInterface.cpp +++ b/src/lp_data/HighsInterface.cpp @@ -1712,7 +1712,7 @@ HighsStatus Highs::elasticityFilterReturn( if (return_status == HighsStatus::kOk) { // Solution is invalidated by deleting rows and columns, but - // primal values are correct. Have to recompute row acivities, + // primal values are correct. Have to recompute row activities, // though this->model_.lp_.a_matrix_.productQuad(this->solution_.row_value, this->solution_.col_value); @@ -1842,7 +1842,7 @@ HighsStatus Highs::elasticityFilter( const double lower_penalty = has_local_lower_penalty ? local_lower_penalty[iCol] : global_lower_penalty; - // Negative lower penalty and infininte upper bound implies that the + // Negative lower penalty and infinite upper bound implies that the // bounds cannot be violated if (lower_penalty < 0 && upper >= kHighsInf) continue; @@ -1850,7 +1850,7 @@ HighsStatus Highs::elasticityFilter( const double upper_penalty = has_local_upper_penalty ? local_upper_penalty[iCol] : global_upper_penalty; - // Infininte upper bound and negative lower penalty implies that the + // Infinite upper bound and negative lower penalty implies that the // bounds cannot be violated if (lower <= -kHighsInf && upper_penalty < 0) continue; erow_lower.push_back(lower); diff --git a/src/lp_data/HighsSolve.cpp b/src/lp_data/HighsSolve.cpp index d839bfb88d..233b16adcd 100644 --- a/src/lp_data/HighsSolve.cpp +++ b/src/lp_data/HighsSolve.cpp @@ -136,7 +136,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { // and duality gap that are within the tolerances supplied by // HiGHS, the HiGHS primal and dual feasibility tolerances may // not be satisfied since they are absolute, and in PDLP they - // are relative. Note that, even when only one PDLP row activit + // are relative. Note that, even when only one PDLP row activity // fails to satisfy the absolute tolerance, the absolute norm // measure reported by PDLP will not necessarily be the same as // with HiGHS, since PDLP uses the 2-norm, and HiGHS the diff --git a/src/util/HVectorBase.h b/src/util/HVectorBase.h index 9db206d809..5b086f3f55 100644 --- a/src/util/HVectorBase.h +++ b/src/util/HVectorBase.h @@ -77,7 +77,7 @@ class HVectorBase { vector packValue; //!< Packed values /** - * @brief Copy from another HVector structure to this instanc + * @brief Copy from another HVector structure to this instance */ template void copy(const HVectorBase* From 0b49a9e71dd064794258f3b0ccc94cbb0f208737 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Tue, 10 Sep 2024 09:15:40 +0200 Subject: [PATCH 16/20] Add utility for computing fractional part --- src/lp_data/Highs.cpp | 5 ++-- src/lp_data/HighsLpUtils.cpp | 3 +- src/mip/HighsLpRelaxation.cpp | 3 +- src/mip/HighsMipSolver.cpp | 3 +- src/mip/HighsMipSolverData.cpp | 15 ++++------ src/mip/HighsSearch.cpp | 3 +- src/mip/HighsTableauSeparator.cpp | 12 ++++---- src/presolve/HPresolve.cpp | 42 +++++++++++----------------- src/presolve/HighsPostsolveStack.cpp | 11 +++----- src/util/HighsUtils.h | 10 +++++++ 10 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/lp_data/Highs.cpp b/src/lp_data/Highs.cpp index 76a4dbc2ac..43cd3094c6 100644 --- a/src/lp_data/Highs.cpp +++ b/src/lp_data/Highs.cpp @@ -3924,10 +3924,9 @@ HighsStatus Highs::callRunPostsolve(const HighsSolution& solution, max_integrality_violation = 0; for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) { if (lp.integrality_[iCol] == HighsVarType::kInteger) { - const double value = this->solution_.col_value[iCol]; - double intval = std::floor(value + 0.5); max_integrality_violation = - std::max(fabs(intval - value), max_integrality_violation); + std::max(fractionality(this->solution_.col_value[iCol]), + max_integrality_violation); } } highsLogUser( diff --git a/src/lp_data/HighsLpUtils.cpp b/src/lp_data/HighsLpUtils.cpp index ebe803bab0..465ee64aab 100644 --- a/src/lp_data/HighsLpUtils.cpp +++ b/src/lp_data/HighsLpUtils.cpp @@ -2305,8 +2305,7 @@ void assessColPrimalSolution(const HighsOptions& options, const double primal, } integer_infeasibility = 0; if (type == HighsVarType::kInteger || type == HighsVarType::kSemiInteger) { - double nearest_integer = std::floor(primal + 0.5); - integer_infeasibility = std::fabs(primal - nearest_integer); + integer_infeasibility = fractionality(primal); } if (col_infeasibility > 0 && (type == HighsVarType::kSemiContinuous || type == HighsVarType::kSemiInteger)) { diff --git a/src/mip/HighsLpRelaxation.cpp b/src/mip/HighsLpRelaxation.cpp index 0d50457416..95133d79be 100644 --- a/src/mip/HighsLpRelaxation.cpp +++ b/src/mip/HighsLpRelaxation.cpp @@ -1224,9 +1224,8 @@ HighsLpRelaxation::Status HighsLpRelaxation::resolveLp(HighsDomain* domain) { double val = std::max( std::min(sol.col_value[i], lpsolver.getLp().col_upper_[i]), lpsolver.getLp().col_lower_[i]); - double intval = std::floor(val + 0.5); - if (std::abs(val - intval) > mipsolver.mipdata_->feastol) { + if (fractionality(val) > mipsolver.mipdata_->feastol) { HighsInt col = i; if (roundable && mipsolver.mipdata_->uplocks[col] != 0 && mipsolver.mipdata_->downlocks[col] != 0) diff --git a/src/mip/HighsMipSolver.cpp b/src/mip/HighsMipSolver.cpp index 1cf6e34c51..ba0faed6bc 100644 --- a/src/mip/HighsMipSolver.cpp +++ b/src/mip/HighsMipSolver.cpp @@ -63,9 +63,8 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback, obj += orig_model_->col_cost_[i] * value; if (orig_model_->integrality_[i] == HighsVarType::kInteger) { - double intval = std::floor(value + 0.5); integrality_violation_ = - std::max(fabs(intval - value), integrality_violation_); + std::max(fractionality(value), integrality_violation_); } const double lower = orig_model_->col_lower_[i]; diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 181b6adb1e..6a571a0010 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -27,7 +27,7 @@ bool HighsMipSolverData::checkSolution( if (solution[i] < mipsolver.model_->col_lower_[i] - feastol) return false; if (solution[i] > mipsolver.model_->col_upper_[i] + feastol) return false; if (mipsolver.variableType(i) == HighsVarType::kInteger && - std::abs(solution[i] - std::floor(solution[i] + 0.5)) > feastol) + fractionality(solution[i]) > feastol) return false; } @@ -57,7 +57,7 @@ bool HighsMipSolverData::trySolution(const std::vector& solution, if (solution[i] < mipsolver.model_->col_lower_[i] - feastol) return false; if (solution[i] > mipsolver.model_->col_upper_[i] + feastol) return false; if (mipsolver.variableType(i) == HighsVarType::kInteger && - std::abs(solution[i] - std::floor(solution[i] + 0.5)) > feastol) + fractionality(solution[i]) > feastol) return false; obj += mipsolver.colCost(i) * solution[i]; @@ -526,9 +526,8 @@ void HighsMipSolverData::runSetup() { if (integral) { if (mipsolver.variableType(ARindex_[j]) == HighsVarType::kContinuous) integral = false; - else { - double intval = std::floor(ARvalue_[j] + 0.5); - if (std::abs(ARvalue_[j] - intval) > epsilon) integral = false; + else if (fractionality(ARvalue_[j]) > epsilon) { + integral = false; } } @@ -597,8 +596,7 @@ void HighsMipSolverData::runSetup() { break; case HighsVarType::kInteger: if (domain.isFixed(i)) { - if (std::abs(domain.col_lower_[i] - - std::floor(domain.col_lower_[i] + 0.5)) > feastol) { + if (fractionality(domain.col_lower_[i]) > feastol) { // integer variable is fixed to a fractional value -> infeasible mipsolver.modelstatus_ = HighsModelStatus::kInfeasible; lower_bound = kHighsInf; @@ -729,8 +727,7 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution( mipsolver.orig_model_->col_cost_[i] * value; if (mipsolver.orig_model_->integrality_[i] == HighsVarType::kInteger) { - double intval = std::floor(value + 0.5); - double integrality_infeasibility = std::fabs(intval - value); + double integrality_infeasibility = fractionality(value); if (integrality_infeasibility > mipsolver.options_mip_->mip_feasibility_tolerance) { if (debug_report) diff --git a/src/mip/HighsSearch.cpp b/src/mip/HighsSearch.cpp index 4cab953b03..348b9ab468 100644 --- a/src/mip/HighsSearch.cpp +++ b/src/mip/HighsSearch.cpp @@ -48,8 +48,7 @@ double HighsSearch::checkSol(const std::vector& sol, if (!integerfeasible || mipsolver.variableType(i) != HighsVarType::kInteger) continue; - double intval = std::floor(sol[i] + 0.5); - if (std::abs(sol[i] - intval) > mipsolver.mipdata_->feastol) { + if (fractionality(sol[i]) > mipsolver.mipdata_->feastol) { integerfeasible = false; } } diff --git a/src/mip/HighsTableauSeparator.cpp b/src/mip/HighsTableauSeparator.cpp index 5e9ab01bc6..4937f0148b 100644 --- a/src/mip/HighsTableauSeparator.cpp +++ b/src/mip/HighsTableauSeparator.cpp @@ -65,25 +65,23 @@ void HighsTableauSeparator::separateLpSolution(HighsLpRelaxation& lpRelaxation, std::vector fractionalBasisvars; fractionalBasisvars.reserve(numRow); for (HighsInt i = 0; i < numRow; ++i) { - double fractionality; + double my_fractionality; if (basisinds[i] >= numCol) { HighsInt row = basisinds[i] - numCol; if (!lpRelaxation.isRowIntegral(row)) continue; - double solval = lpSolution.row_value[row]; - fractionality = std::fabs(std::round(solval) - solval); + my_fractionality = fractionality(lpSolution.row_value[row]); } else { HighsInt col = basisinds[i]; if (mip.variableType(col) == HighsVarType::kContinuous) continue; - double solval = lpSolution.col_value[col]; - fractionality = std::fabs(std::round(solval) - solval); + my_fractionality = fractionality(lpSolution.col_value[col]); } - if (fractionality < 1000 * mip.mipdata_->feastol) continue; + if (my_fractionality < 1000 * mip.mipdata_->feastol) continue; - fractionalBasisvars.emplace_back(i, fractionality); + fractionalBasisvars.emplace_back(i, my_fractionality); } if (fractionalBasisvars.empty()) return; diff --git a/src/presolve/HPresolve.cpp b/src/presolve/HPresolve.cpp index 9229c8c264..10e17ab95f 100644 --- a/src/presolve/HPresolve.cpp +++ b/src/presolve/HPresolve.cpp @@ -173,8 +173,7 @@ bool HPresolve::okSetInput(HighsMipSolver& mipsolver, bool HPresolve::rowCoefficientsIntegral(HighsInt row, double scale) const { for (const HighsSliceNonzero& nz : getRowVector(row)) { - double val = nz.value() * scale; - if (std::abs(val - std::round(val)) > options->small_matrix_value) + if (fractionality(nz.value() * scale) > options->small_matrix_value) return false; } @@ -252,9 +251,8 @@ bool HPresolve::isImpliedIntegral(HighsInt col) { double scale = 1.0 / nz.value(); if (!rowCoefficientsIntegral(nz.index(), scale)) continue; - double rhs = model->row_lower_[nz.index()] * scale; - - if (std::abs(rhs - std::round(rhs)) > primal_feastol) { + if (fractionality(model->row_lower_[nz.index()] * scale) > + primal_feastol) { // todo infeasible } @@ -323,9 +321,9 @@ bool HPresolve::isImpliedInteger(HighsInt col) { // if there is an equation the dual detection does not need to be tried runDualDetection = false; double scale = 1.0 / nz.value(); - double rhs = model->row_lower_[nz.index()] * scale; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) { + if (fractionality(model->row_lower_[nz.index()] * scale) > + primal_feastol) { continue; } @@ -338,24 +336,20 @@ bool HPresolve::isImpliedInteger(HighsInt col) { if (!runDualDetection) return false; if ((model->col_lower_[col] != -kHighsInf && - std::abs(std::round(model->col_lower_[col]) - model->col_lower_[col]) > - options->small_matrix_value) || + fractionality(model->col_lower_[col]) > options->small_matrix_value) || (model->col_upper_[col] != -kHighsInf && - std::abs(std::round(model->col_upper_[col]) - model->col_upper_[col]) > - options->small_matrix_value)) + fractionality(model->col_upper_[col]) > options->small_matrix_value)) return false; for (const HighsSliceNonzero& nz : getColumnVector(col)) { double scale = 1.0 / nz.value(); - if (model->row_upper_[nz.index()] != kHighsInf) { - double rhs = model->row_upper_[nz.index()]; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) return false; - } + if (model->row_upper_[nz.index()] != kHighsInf && + fractionality(model->row_upper_[nz.index()]) > primal_feastol) + return false; - if (model->row_lower_[nz.index()] != -kHighsInf) { - double rhs = model->row_lower_[nz.index()]; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) return false; - } + if (model->row_lower_[nz.index()] != -kHighsInf && + fractionality(model->row_lower_[nz.index()]) > primal_feastol) + return false; if (!rowCoefficientsIntegral(nz.index(), scale)) return false; } @@ -3305,7 +3299,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, if (intScale != 0.0 && intScale <= 1e3) { double rhs = rowUpper * intScale; - if (std::abs(rhs - std::round(rhs)) > primal_feastol) + if (fractionality(rhs) > primal_feastol) return Result::kPrimalInfeasible; rhs = std::round(rhs); @@ -3341,8 +3335,7 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack, HighsInt x1Pos = rowpositions[x1Cand]; HighsInt x1 = Acol[x1Pos]; double rhs2 = rhs / static_cast(d); - if (std::abs(std::round(rhs2) - rhs2) <= - mipsolver->mipdata_->epsilon) { + if (fractionality(rhs2) <= mipsolver->mipdata_->epsilon) { // the right hand side is integral, so we can substitute // x1 = d * z @@ -5723,10 +5716,9 @@ HPresolve::Result HPresolve::detectParallelRowsAndCols( } double scaleCand = colMax[duplicateCol].first / colMax[col].first; - colScale = std::round(scaleCand); - assert(std::abs(colScale) >= 1.0); - if (std::abs(colScale - scaleCand) > options->small_matrix_value) + if (fractionality(scaleCand, &colScale) > options->small_matrix_value) continue; + assert(std::abs(colScale) >= 1.0); // if the scale is larger than 1, duplicate column cannot compensate for // all values of scaled col due to integrality as the scaled column diff --git a/src/presolve/HighsPostsolveStack.cpp b/src/presolve/HighsPostsolveStack.cpp index bd73f128e4..bec3e4945f 100644 --- a/src/presolve/HighsPostsolveStack.cpp +++ b/src/presolve/HighsPostsolveStack.cpp @@ -15,6 +15,7 @@ #include "lp_data/HConst.h" #include "lp_data/HighsOptions.h" #include "util/HighsCDouble.h" +#include "util/HighsUtils.h" namespace presolve { @@ -728,8 +729,7 @@ void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options, } else if (duplicateColIntegral) { // Doesn't set basis.col_status[duplicateCol], so assume no basis assert(!basis.valid); - double roundVal = std::round(solution.col_value[duplicateCol]); - if (std::abs(roundVal - solution.col_value[duplicateCol]) > + if (fractionality(solution.col_value[duplicateCol]) > options.mip_feasibility_tolerance) { solution.col_value[duplicateCol] = std::floor(solution.col_value[duplicateCol]); @@ -927,9 +927,7 @@ bool HighsPostsolveStack::DuplicateColumn::okMerge( if (x_int) { if (y_int) { // Scale must be integer and not exceed (x_u-x_l)+1 in magnitude - double int_scale = std::floor(scale + 0.5); - bool scale_is_int = std::fabs(int_scale - scale) <= tolerance; - if (!scale_is_int) { + if (fractionality(scale) > tolerance) { if (debug_report) printf( "%sDuplicateColumn::checkMerge: scale must be integer, but is " @@ -1012,8 +1010,7 @@ void HighsPostsolveStack::DuplicateColumn::undoFix( //============================================================================================= auto isInteger = [&](const double v) { - double int_v = std::floor(v + 0.5); - return std::fabs(int_v - v) <= mip_feasibility_tolerance; + return (fractionality(v) <= mip_feasibility_tolerance); }; auto isFeasible = [&](const double l, const double v, const double u) { diff --git a/src/util/HighsUtils.h b/src/util/HighsUtils.h index 27791ff2be..447219a8f9 100644 --- a/src/util/HighsUtils.h +++ b/src/util/HighsUtils.h @@ -207,4 +207,14 @@ void highsAssert(const bool assert_condition, const std::string message = ""); // If pause_condition is true, then keyboard input is required. Allows // breakpoints in VScode where optimization might prevent them. bool highsPause(const bool pause_condition, const std::string message = ""); + +// Utility for computing fractional part +template +inline T fractionality(T input, T* intval = nullptr) { + using std::abs; + using std::round; + T val = round(input); + if (intval != nullptr) *intval = val; + return abs(input - val); +} #endif // UTIL_HIGHSUTILS_H_ From 54b575ea9b5716f451a64269179186c83836f982 Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 10 Sep 2024 15:23:40 +0100 Subject: [PATCH 17/20] Added src/solvers.md --- docs/make.jl | 4 +++- docs/src/index.md | 21 +++++---------------- docs/src/solvers.md | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 docs/src/solvers.md diff --git a/docs/make.jl b/docs/make.jl index 0c65c9d2b4..c1f6bc70b9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,8 +2,9 @@ * * * This file is part of the HiGHS linear optimization suite * * * -* Written and engineered 2008-2023 by Julian Hall, Ivet Galabova, * +* Written and engineered 2008-2024 by Julian Hall, Ivet Galabova, * * Leona Gottwald and Michael Feldmeier * +* * * Available as open-source under the MIT License * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *=# @@ -90,6 +91,7 @@ Documenter.makedocs( "options/definitions.md" ], "Parallel" => "parallel.md", + "Solvers" => "solvers.md", "Terminology" => "terminology.md", ], ) diff --git a/docs/src/index.md b/docs/src/index.md index 1af6ab7ee6..54af09ff19 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -54,23 +54,12 @@ calls. These can be studied via the [C++ header file](https://github.com/ERGO-Co The C interface cannot make use of the C++ structures and enums, and its methods are documented [explicitly](@ref c-api). -## Solution algorithms - -For LPs, HiGHS has implementations of both the revised simplex and interior -point methods. MIPs are solved by branch-and-cut, and QPs by active set. - -For LP, the novel features of the dual simplex solver are described in -_Parallelizing the dual revised simplex method_, Q. Huangfu and -J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, -2018 [DOI: -10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). For -the interior point solver, the reference is _Implementation of an -interior point method with basis preconditioning_, Mathematical -Programming Computation, 12, 603-635, 2020. [DOI: -10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). There -are no specific references to the techniques used in the MIP or QP -solvers. +## Solvers +For LPs, HiGHS has implementations of the revised simplex method, +interior point method, and PDLP first order method. MIPs are solved by +branch-and-cut, and QPs by active set. More information on the HiGHS +solvers is [available](@ref solvers). ## Citing HiGHS diff --git a/docs/src/solvers.md b/docs/src/solvers.md new file mode 100644 index 0000000000..1aff0a1555 --- /dev/null +++ b/docs/src/solvers.md @@ -0,0 +1,46 @@ +# [Solvers](@id solvers) + +## LP + +HiGHS has implementations of the three main solution techniques for +LP. HiGHS will choose the most appropriate technique for a given +problem, but this can be over-ridden by setting the option +[__solver__](solver). + +### Simplex + +HiGHS has efficient implementations of both the primal and dual +simplex methods, although the dual simplex solver is likely to be +faster and is more robust, so is used by default. The novel features +of the dual simplex solver are described in + +_Parallelizing the dual revised simplex method_, Q. Huangfu and +J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, +2018 [DOI: +10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). + +The option [__simplex\_strategy__](simplex\_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. + +### Interior point + +HiGHS has one interior point solver based on the preconditioned conjugate gradient method, as discussed in + +_Implementation of an interior point method with basis +preconditioning_, Mathematical Programming Computation, 12, 603-635, +2020. [DOI: +10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). + +This solver is serial. An interior point solver based on direct factorization is being developed. + +### MIP + +The HiGHS MIP solver uses established branch-and-cut techniques + +### QP + +The HiGHS solver for convex QP problems uses an established primal +active set method. The new interior point solver will also be able to +solve convex QP problems. + + + From b506d7aa054402a70ffcd8b2b246dad6b7ce024a Mon Sep 17 00:00:00 2001 From: jajhall Date: Tue, 10 Sep 2024 15:34:29 +0100 Subject: [PATCH 18/20] Fixed internal links --- docs/src/executable.md | 2 +- docs/src/index.md | 2 +- docs/src/options/definitions.md | 4 ++-- docs/src/options/intro.md | 4 ++-- docs/src/parallel.md | 2 +- docs/src/solvers.md | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/executable.md b/docs/src/executable.md index d9f1777555..b82003127c 100644 --- a/docs/src/executable.md +++ b/docs/src/executable.md @@ -1,4 +1,4 @@ -# Executable +# [Executable](@id executable) For convenience, the executable is assumed to be `bin/highs`. diff --git a/docs/src/index.md b/docs/src/index.md index 54af09ff19..2e0e65a03b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -39,7 +39,7 @@ Get started by following [Install HiGHS](@ref). ## Overview -The standalone [Executable](@ref) allows models to be solved from +The standalone [executable](@ref executable) allows models to be solved from [MPS](https://en.wikipedia.org/wiki/MPS_(format)) or (CPLEX) [LP](https://web.mit.edu/lpsolve/doc/CPLEX-format.htm) files, with full control of the HiGHS run-time options, and the solution can be written to files in human diff --git a/docs/src/options/definitions.md b/docs/src/options/definitions.md index 2505c4d3c3..31a0eb128d 100644 --- a/docs/src/options/definitions.md +++ b/docs/src/options/definitions.md @@ -5,7 +5,7 @@ - Type: string - Default: "choose" -## solver +## [solver](@id option-solver) - Solver option: "simplex", "choose", "ipm" or "pdlp". If "simplex"/"ipm"/"pdlp" is chosen then, for a MIP (QP) the integrality constraint (quadratic term) will be ignored - Type: string - Default: "choose" @@ -109,7 +109,7 @@ - Range: {-2147483647, 2147483647} - Default: 0 -## simplex\_strategy +## [simplex\_strategy](@id option-simplex_strategy) - Strategy for simplex solver 0 => Choose; 1 => Dual (serial); 2 => Dual (PAMI); 3 => Dual (SIP); 4 => Primal - Type: integer - Range: {0, 4} diff --git a/docs/src/options/intro.md b/docs/src/options/intro.md index ff33cae234..81f0e64246 100644 --- a/docs/src/options/intro.md +++ b/docs/src/options/intro.md @@ -3,12 +3,12 @@ The options that control HiGHS are of four types: `boolean`, `integer`, `double` and `string`. Their values can be specified: - * via the command line when running the [Executable](@ref) + * via the command line when running the [Executable](@ref executable) * via method calls when running HiGHS in an application. ## Options file -When running the [Executable](@ref) via the command line, some options values +When running the [Executable](@ref executable) via the command line, some options values can be set explicitly in the command, and all options can be set by means of an options file. diff --git a/docs/src/parallel.md b/docs/src/parallel.md index 3a857629e3..016834ab98 100644 --- a/docs/src/parallel.md +++ b/docs/src/parallel.md @@ -20,7 +20,7 @@ has a variant allowing concurrent processing. This variant is used when the [parallel](@ref) option is set "on", by specifying `--parallel` when running the -[executable](@ref Executable) via +[executable](@ref executable) via the command line, or by setting it via a library call in an application. diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 1aff0a1555..15a49f9225 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -5,7 +5,7 @@ HiGHS has implementations of the three main solution techniques for LP. HiGHS will choose the most appropriate technique for a given problem, but this can be over-ridden by setting the option -[__solver__](solver). +[__solver__](@ref option-solver). ### Simplex @@ -19,7 +19,7 @@ J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, 2018 [DOI: 10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). -The option [__simplex\_strategy__](simplex\_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. +The option [__simplex\_strategy__](@ref option-simplex_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. ### Interior point From cdf2cf4d95737f48a692322a4184f8306ee847b1 Mon Sep 17 00:00:00 2001 From: JAJHall Date: Tue, 10 Sep 2024 19:05:06 +0100 Subject: [PATCH 19/20] Added PDLP! --- docs/src/solvers.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/src/solvers.md b/docs/src/solvers.md index 15a49f9225..0b881c14c0 100644 --- a/docs/src/solvers.md +++ b/docs/src/solvers.md @@ -7,7 +7,7 @@ LP. HiGHS will choose the most appropriate technique for a given problem, but this can be over-ridden by setting the option [__solver__](@ref option-solver). -### Simplex +#### Simplex HiGHS has efficient implementations of both the primal and dual simplex methods, although the dual simplex solver is likely to be @@ -19,24 +19,38 @@ J. A. J. Hall, Mathematical Programming Computation, 10 (1), 119-142, 2018 [DOI: 10.1007/s12532-017-0130-5](https://link.springer.com/article/10.1007/s12532-017-0130-5). -The option [__simplex\_strategy__](@ref option-simplex_strategy) determines whether the primal solver or one of hte parallel solvers is to be used. +* Setting the option [__solver__](@ref option-solver) to "simplex" forces the simplex solver to be used +* The option [__simplex\_strategy__](@ref option-simplex_strategy) +determines whether the primal solver or one of the parallel solvers is +to be used. -### Interior point +#### Interior point -HiGHS has one interior point solver based on the preconditioned conjugate gradient method, as discussed in +HiGHS has one interior point (IPM) solver based on the preconditioned conjugate gradient method, as discussed in _Implementation of an interior point method with basis -preconditioning_, Mathematical Programming Computation, 12, 603-635, -2020. [DOI: +preconditioning_, Mathematical Programming Computation, 12, 603-635, 2020. [DOI: 10.1007/s12532-020-00181-8](https://link.springer.com/article/10.1007/s12532-020-00181-8). This solver is serial. An interior point solver based on direct factorization is being developed. -### MIP +Setting the option [__solver__](@ref option-solver) to "ipm" forces the IPM solver to be used + +#### Primal-dual hybrid gradient method + +HiGHS includes the [cuPDLP-C](https://github.com/COPT-Public/cuPDLP-C) +primal-dual hybrid gradient method for LP (PDLP). Currently this only +runs on CPU, so it is unlikely to be competitive with the HiGHS +interior point or simplex solvers. Enabling HiGHS to run PDLP on a GPU +is work in progress. + +Setting the option [__solver__](@ref option-solver) to "pdlp" forces the PDLP solver to be used + +## MIP The HiGHS MIP solver uses established branch-and-cut techniques -### QP +## QP The HiGHS solver for convex QP problems uses an established primal active set method. The new interior point solver will also be able to From 450c026b18c3cca60d05311aa6a9c42c70037361 Mon Sep 17 00:00:00 2001 From: fwesselm Date: Wed, 11 Sep 2024 09:25:47 +0200 Subject: [PATCH 20/20] Tiny simplification --- src/mip/HighsMipSolverData.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/mip/HighsMipSolverData.cpp b/src/mip/HighsMipSolverData.cpp index 6a571a0010..884f7a4713 100644 --- a/src/mip/HighsMipSolverData.cpp +++ b/src/mip/HighsMipSolverData.cpp @@ -523,13 +523,10 @@ void HighsMipSolverData::runSetup() { HighsInt end = ARstart_[i + 1]; bool integral = true; for (HighsInt j = start; j != end; ++j) { - if (integral) { - if (mipsolver.variableType(ARindex_[j]) == HighsVarType::kContinuous) - integral = false; - else if (fractionality(ARvalue_[j]) > epsilon) { - integral = false; - } - } + integral = + integral && + mipsolver.variableType(ARindex_[j]) != HighsVarType::kContinuous && + fractionality(ARvalue_[j]) <= epsilon; maxabsval = std::max(maxabsval, std::abs(ARvalue_[j])); }