Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmake/FindHipoDeps.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ endfunction()
function(highs_configure_blas_metadata)
set(HIGHS_BLAS_COMPILE_DEFINITION "" PARENT_SCOPE)

string(TOLOWER "${BLAS_LIBRARIES}" BLAS_LIBRARIES_LOWER)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, can you explain why this is needed?

@filikat filikat Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I use OpenBLAS by passing libopenblas.so to BLAS_LIBRARIES, CMake doesn't know which vendor is being used and so it doesn't set the BLAS metadata (i.e., the provider is unknown, etc). Crucially, this means that HIPO_USES_OPENBLAS is not set, and OpenBLAS runs multi-threaded, with a substantial reduction in performance.
Maybe there are better ways of achieving this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, that makes sense. Previously in my tests I simply passed BLA_VENDOR as well, but probably not the nicest either.

if(BLAS_LIBRARIES_LOWER MATCHES "openblas")
set(BLA_VENDOR OpenBLAS)
endif()

if(OpenBLAS_FOUND OR OPENBLAS_LIB OR BLA_VENDOR MATCHES "OpenBLAS")
set(HIGHS_BLAS_VENDOR OpenBLAS PARENT_SCOPE)
set(HIGHS_BLAS_VERSION "${OpenBLAS_VERSION}" PARENT_SCOPE)
Expand Down
1 change: 0 additions & 1 deletion extern/HighsExtrasApiBinding.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

#include <tuple>


// provide metadata info for each feature
struct HighsExtrasFeatureInfo {
HighsExtrasFeatureInfo(const char* provider_ = nullptr,
Expand Down
4 changes: 2 additions & 2 deletions extern/HighsExtrasExternalDeps.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ struct blas : extras_feature<1> {
lda, B, ldb);
}

static void openblas_set_num_threads(int num_threads) {
impl::template fn<11>()(num_threads);
static int openblas_set_num_threads(int num_threads) {
return impl::template fn<11>()(num_threads);
}
};

Expand Down
5 changes: 4 additions & 1 deletion extern/blas/myblas.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#include "mycblas.h"

void highs_openblas_set_num_threads(int num_threads) {
int highs_openblas_set_num_threads(int num_threads) {
#if defined(HIPO_USES_OPENBLAS)
openblas_set_num_threads(num_threads);
return openblas_get_num_threads();
#else
return -1;
#endif
}
3 changes: 2 additions & 1 deletion extern/blas/mycblas.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ extern "C" {

#if defined(HIPO_USES_OPENBLAS)
void openblas_set_num_threads(int num_threads);
int openblas_get_num_threads(void);
#endif

void highs_openblas_set_num_threads(int num_threads);
int highs_openblas_set_num_threads(int num_threads);
#ifdef __cplusplus
}
#endif
Expand Down
20 changes: 17 additions & 3 deletions highs/ipm/IpxWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,19 @@ HighsStatus solveQpHipo(HighsQpSolverObject& solver_object) {
solver_object.callback_);
}

static HighsInt prepareOpenBLAS(const HighsOptions& options) {
// force openblas to run in serial, for determinism and better performance
// no-op if openblas is not used
const int threads_used = HighsExtras::blas::openblas_set_num_threads(1);
if (is_substring_case(HighsExtras::blas::getInfo()->provider, "openblas") &&
threads_used != 1) {
highsLogUser(options.log_options, HighsLogType::kError,
"OpenBLAS failed to set the number of threads to 1\n");
return 1;
}
return 0;
}

HighsStatus solveHipo(const HighsOptions& options, HighsTimer& timer,
const HighsLp& lp, const HighsHessian& Q,
HighsBasis& highs_basis, HighsSolution& highs_solution,
Expand Down Expand Up @@ -457,9 +470,10 @@ HighsStatus solveHipo(const HighsOptions& options, HighsTimer& timer,
// Indicate that no imprecise solution has (yet) been found
resetModelStatusAndHighsInfo(model_status, highs_info);

// force openblas to run in serial, for determinism and better performance
// no-op if openblas is not used
HighsExtras::blas::openblas_set_num_threads(1);
if (prepareOpenBLAS(options)) {
model_status = HighsModelStatus::kSolveError;
return HighsStatus::kError;
}

// Create solver instance
hipo::Solver hipo{};
Expand Down
7 changes: 1 addition & 6 deletions highs/ipm/hipo/ipm/FactorHighsSolver.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include "FactorHighsSolver.h"

#include <cstring>
#include <limits>

#include "HighsExternalApi.h"
Expand Down Expand Up @@ -592,15 +591,11 @@ void FactorHighsSolver::setParallelBeforeSymbolic() {
parallel_NE_values_default);
}

static bool usingAppleBlas() {
return strstr(HighsExtras::blas::getInfo()->provider, "Apple") != nullptr;
}

void FactorHighsSolver::setParallelAfterSymbolic() {
bool parallel_tree = false;
bool parallel_node = false;

if (usingAppleBlas()) {
if (is_substring_case(HighsExtras::blas::getInfo()->provider, "apple")) {
// Blas on Apple do not work well with parallel_node, but parallel_tree
// seems to always be beneficial.
parallel_node = false;
Expand Down
18 changes: 18 additions & 0 deletions highs/util/stringutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,22 @@ bool is_end(std::string& str, size_t end,
std::string first_word(std::string& str, size_t start);
size_t first_word_end(std::string& str, size_t start);

#if defined(_WIN32) || defined(_WIN64)
#include <string.h> // _strnicmp
#else
#include <strings.h> // strcasestr
#endif

// check if rhs is a substring of lhs, case insensitive
inline bool is_substring_case(const char* lhs, const char* rhs) {
#if defined(_WIN32) || defined(_WIN64)
size_t len = strlen(rhs);
for (; *lhs != '\0'; ++lhs)
if (_strnicmp(lhs, rhs, len) == 0) return true;
return false;
#else
return strcasestr(lhs, rhs) != nullptr;
#endif

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential suggestion?

Note that strcasecmp isn't posix standard and needs _GNU_SOURCE defined. Alternatively we can use strncasecmp as follows. Should be about the same speed, but slightly more robust.

inline bool is_substring_case(const char* str, const char* substring) {
	if (!str || !substring) return false;

	const size_t substring_len = strlen(substring);
	if (substring_len == 0) return true;

	const size_t str_len = strlen(str);
	if (substring_len > str_len) return false;
		
	const char* str_last_possible = str + str_len - substring_len;

	for (; str <= str_last_possible; ++str) {
#if defined(_WIN32) || defined(_WIN64)
		if (_strnicmp(str, substring, substring_len) == 0) return true;
#else
		if (strncasecmp(str, substring, substring_len) == 0) return true;
#endif
	}

	return false;
}

}

#endif
Loading