Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/build-python-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
sys:
- { os: ubuntu-latest, shell: bash, build: "*manylinux*" }
- { os: ubuntu-22.04-arm, shell: bash, build: "*manylinux*" }
- { os: macos-latest, shell: bash, build: "*" }
- { os: macos-15, shell: bash, build: "*" }
- { os: macos-15-intel, shell: bash, build: "*" }
- { os: windows-latest, shell: 'msys2 {0}', build: "*" }
defaults:
Expand Down Expand Up @@ -244,7 +244,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest]
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15, macos-15-intel, windows-latest]
python-version: [ "3.12" ]

steps:
Expand All @@ -270,7 +270,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-15-intel, windows-latest]
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15, macos-15-intel, windows-latest]
python-version: ["3.12"]

steps:
Expand Down
54 changes: 40 additions & 14 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,27 +131,45 @@ endfunction()
# Dependencies
# ======================

# HSL or MA57
find_library(HSL hsl)
if(HSL)
link_to_uno(hsl ${HSL})
# HSL (MA27/MA57)
# IPOPT-style runtime loading: don't link libhsl, dlopen it on first use and
# resolve MA27/MA57 by symbol name (see uno/ingredients/subproblem_solvers/HSL).
option(HSL_RUNTIME_LOADING "Load HSL (MA27/MA57) at runtime via dlopen instead of linking" OFF)
set(_compile_ma57 OFF)
set(_compile_ma27 OFF)
if(HSL_RUNTIME_LOADING)
message(STATUS "HSL: IPOPT-style runtime loading enabled (libhsl resolved via dlopen)")
target_sources(compiled_uno_files PRIVATE uno/ingredients/subproblem_solvers/HSL/HSLLoader.cpp)
target_compile_definitions(uno_dependencies INTERFACE HAS_HSL HSL_RUNTIME_LOADING)
target_link_libraries(uno_dependencies INTERFACE ${CMAKE_DL_LIBS})
set(_compile_ma57 ON)
set(_compile_ma27 ON)
else()
find_library(MA57 ma57)
if(MA57)
link_to_uno(ma57 ${MA57})
endif()
find_library(MA27 ma27)
if(MA27)
link_to_uno(ma27 ${MA27})
find_library(HSL hsl)
if(HSL)
link_to_uno(hsl ${HSL})
set(_compile_ma57 ON)
set(_compile_ma27 ON)
else()
find_library(MA57 ma57)
if(MA57)
link_to_uno(ma57 ${MA57})
set(_compile_ma57 ON)
endif()
find_library(MA27 ma27)
if(MA27)
link_to_uno(ma27 ${MA27})
set(_compile_ma27 ON)
endif()
endif()
endif()
if(HSL OR MA57)
if(_compile_ma57)
target_sources(compiled_uno_files PRIVATE uno/ingredients/subproblem_solvers/MA57/MA57Solver.cpp)
if(ENABLE_TESTS)
target_sources(run_unotest PRIVATE unotest/functional_tests/MA57SolverTests.cpp)
endif()
endif()
if(HSL OR MA27)
if(_compile_ma27)
target_sources(compiled_uno_files PRIVATE uno/ingredients/subproblem_solvers/MA27/MA27Solver.cpp)
if(ENABLE_TESTS)
target_sources(run_unotest PRIVATE unotest/functional_tests/MA27SolverTests.cpp)
Expand Down Expand Up @@ -244,6 +262,14 @@ if(SPRAL)

target_include_directories(uno_dependencies SYSTEM INTERFACE ${SPRAL_INCLUDE_DIR})
target_sources(compiled_uno_files PRIVATE uno/ingredients/subproblem_solvers/SSIDS/SSIDSSolver.cpp)

# static libspral needs METIS after it on the link line (Windows DLLs forbid undefined refs)
if(METIS_LIBRARY)
target_link_libraries(uno_dependencies INTERFACE ${METIS_LIBRARY})
elseif(METIS)
target_link_libraries(uno_dependencies INTERFACE ${METIS})
endif()

if(ENABLE_TESTS)
target_sources(run_unotest PRIVATE unotest/functional_tests/SSIDSSolverTests.cpp)
endif()
Expand Down Expand Up @@ -441,4 +467,4 @@ install(TARGETS ${INSTALL_TARGETS}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # static libraries
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # shared libraries
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/uno # headers
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace uno {
PrimalDualInertiaCorrection::PrimalDualInertiaCorrection(const Options& options):
InertiaCorrectionStrategy(),
optional_linear_solver_name(options.get_string("linear_solver")),
hsllib(options.get_string_optional("hsllib").value_or("")),
regularization_failure_threshold(options.get_double("regularization_failure_threshold")),
primal_regularization_initial_factor(options.get_double("primal_regularization_initial_factor")),
dual_regularization_fraction(options.get_double("dual_regularization_fraction")),
Expand All @@ -34,7 +35,7 @@ namespace uno {
const Inertia& expected_inertia, double* hessian_values) {
// pick the member linear solver
if (this->optional_linear_solver == nullptr) {
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name);
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name, this->hsllib);
this->optional_linear_solver->get_linear_system().initialize_augmented_system(subproblem);
this->optional_linear_solver->initialize_memory();
this->optional_linear_solver->do_symbolic_analysis();
Expand All @@ -55,7 +56,7 @@ namespace uno {
double dual_regularization_parameter, const Inertia& expected_inertia, double* primal_regularization_values,
double* dual_regularization_values) {
if (this->optional_linear_solver == nullptr) {
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name);
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name, this->hsllib);
this->optional_linear_solver->get_linear_system().initialize_augmented_system(subproblem);
this->optional_linear_solver->initialize_memory();
this->optional_linear_solver->do_symbolic_analysis();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace uno {

protected:
const std::string& optional_linear_solver_name;
const std::string hsllib;
std::unique_ptr<DirectSymmetricIndefiniteLinearSolver<double>> optional_linear_solver{};
double primal_regularization{0.};
double dual_regularization{0.};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace uno {
PrimalInertiaCorrection::PrimalInertiaCorrection(const Options& options):
InertiaCorrectionStrategy(),
optional_linear_solver_name(options.get_string("linear_solver")),
hsllib(options.get_string_optional("hsllib").value_or("")),
regularization_initial_factor(options.get_double("primal_regularization_initial_factor")),
regularization_increase_factor(options.get_double("regularization_increase_factor")),
regularization_failure_threshold(options.get_double("regularization_failure_threshold")) {
Expand All @@ -29,7 +30,7 @@ namespace uno {
const Inertia& expected_inertia, double* hessian_values) {
// pick the member linear solver
if (this->optional_linear_solver == nullptr) {
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name);
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name, this->hsllib);
this->optional_linear_solver->get_linear_system().initialize_hessian(subproblem);
this->optional_linear_solver->initialize_memory();
this->optional_linear_solver->do_symbolic_analysis();
Expand Down Expand Up @@ -90,7 +91,7 @@ namespace uno {
double* dual_regularization_values) {
// pick the member linear solver
if (this->optional_linear_solver == nullptr) {
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name);
this->optional_linear_solver = SymmetricIndefiniteLinearSolverFactory::create(this->optional_linear_solver_name, this->hsllib);
this->optional_linear_solver->get_linear_system().initialize_hessian(subproblem);
this->optional_linear_solver->initialize_memory();
this->optional_linear_solver->do_symbolic_analysis();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace uno {

protected:
const std::string& optional_linear_solver_name;
const std::string hsllib;
std::unique_ptr<DirectSymmetricIndefiniteLinearSolver<double>> optional_linear_solver{};
double regularization_factor{0.};
const double regularization_initial_factor{};
Expand Down
3 changes: 2 additions & 1 deletion uno/ingredients/subproblem_solvers/EQPSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
namespace uno {
EQPSolver::EQPSolver(const Options& options):
SubproblemSolver(),
linear_solver(SymmetricIndefiniteLinearSolverFactory::create(options.get_string("linear_solver"))) {
linear_solver(SymmetricIndefiniteLinearSolverFactory::create(options.get_string("linear_solver"),
options.get_string_optional("hsllib").value_or(""))) {
}

void EQPSolver::initialize_memory(const Subproblem& subproblem) {
Expand Down
127 changes: 127 additions & 0 deletions uno/ingredients/subproblem_solvers/HSL/HSLLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright (c) 2018-2024 Charlie Vanaret
// Licensed under the MIT license. See LICENSE file in the project directory for details.

#include "HSLLoader.hpp"
#include <cctype>
#include <cstdlib>
#include <string>
#include "tools/Logger.hpp"

#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif

// default library name: libhsl.<platform shared-lib extension> (matches IPOPT's hsllib)
#if defined(_WIN32)
#define UNO_HSL_DEFAULT_LIBRARY "libhsl.dll"
#elif defined(__APPLE__)
#define UNO_HSL_DEFAULT_LIBRARY "libhsl.dylib"
#else
#define UNO_HSL_DEFAULT_LIBRARY "libhsl.so"
#endif

namespace uno {
ma57id_fp hsl_ma57id = nullptr;
ma57ad_fp hsl_ma57ad = nullptr;
ma57bd_fp hsl_ma57bd = nullptr;
ma57cd_fp hsl_ma57cd = nullptr;
ma57dd_fp hsl_ma57dd = nullptr;
ma57ed_fp hsl_ma57ed = nullptr;
ma27id_fp hsl_ma27id = nullptr;
ma27ad_fp hsl_ma27ad = nullptr;
ma27bd_fp hsl_ma27bd = nullptr;
ma27cd_fp hsl_ma27cd = nullptr;

namespace {
#ifdef _WIN32
using LibraryHandle = HMODULE;
LibraryHandle open_library(const char* name) { return LoadLibraryA(name); }
void* raw_symbol(LibraryHandle handle, const char* symbol) {
return reinterpret_cast<void*>(GetProcAddress(handle, symbol));
}
#else
using LibraryHandle = void*;
// match upstream IPOPT: resolve now, do not export the HSL symbols globally
LibraryHandle open_library(const char* name) { return dlopen(name, RTLD_NOW); }
void* raw_symbol(LibraryHandle handle, const char* symbol) { return dlsym(handle, symbol); }
#endif

LibraryHandle hsl_handle = nullptr;

// Resolve a Fortran symbol trying the manglings IPOPT tries, so the runtime
// libhsl can have been built by any compiler regardless of how Uno was:
// base, base_, lower_, lower, UPPER_, UPPER.
void* resolve_symbol(LibraryHandle handle, const std::string& base) {
std::string lower = base, upper = base;
for (char& c: lower) { c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }
for (char& c: upper) { c = static_cast<char>(std::toupper(static_cast<unsigned char>(c))); }
const std::string candidates[] = {base, base + "_", lower + "_", lower, upper + "_", upper};
for (const std::string& candidate: candidates) {
if (void* symbol = raw_symbol(handle, candidate.c_str())) {
return symbol;
}
}
return nullptr;
}

template <typename FunctionPointer>
void resolve(LibraryHandle handle, FunctionPointer& function_pointer, const std::string& base) {
function_pointer = reinterpret_cast<FunctionPointer>(resolve_symbol(handle, base));
}
} // anonymous namespace

bool load_hsl_library(const std::string& library_name) {
// cache success only: a failed probe (e.g. the early available_solvers() check
// with no name) must not block a later load with an explicit hsllib path.
if (hsl_handle != nullptr) {
return true;
}

std::string name = library_name;
if (name.empty()) {
if (const char* env = std::getenv("UNO_HSL_LIBRARY")) {
name = env;
}
}
if (name.empty()) {
name = UNO_HSL_DEFAULT_LIBRARY;
}

hsl_handle = open_library(name.c_str());
if (hsl_handle == nullptr) {
DEBUG << "Uno: could not load the HSL library '" << name << "' at runtime\n";
return false;
}
DEBUG << "Uno: loaded the HSL library '" << name << "' at runtime\n";

resolve(hsl_handle, hsl_ma57id, "ma57id");
resolve(hsl_handle, hsl_ma57ad, "ma57ad");
resolve(hsl_handle, hsl_ma57bd, "ma57bd");
resolve(hsl_handle, hsl_ma57cd, "ma57cd");
resolve(hsl_handle, hsl_ma57dd, "ma57dd");
resolve(hsl_handle, hsl_ma57ed, "ma57ed");
resolve(hsl_handle, hsl_ma27id, "ma27id");
resolve(hsl_handle, hsl_ma27ad, "ma27ad");
resolve(hsl_handle, hsl_ma27bd, "ma27bd");
resolve(hsl_handle, hsl_ma27cd, "ma27cd");
return true;
}

bool ma57_symbols_available() {
load_hsl_library();
return hsl_ma57id && hsl_ma57ad && hsl_ma57bd && hsl_ma57cd && hsl_ma57dd && hsl_ma57ed;
}

bool ma27_symbols_available() {
load_hsl_library();
return hsl_ma27id && hsl_ma27ad && hsl_ma27bd && hsl_ma27cd;
}
} // namespace

// Mirrors the symbol the linked coinhsl meta-library exports; the
// SymmetricIndefiniteLinearSolverFactory uses it to gate MA27/MA57.
extern "C" bool LIBHSL_isfunctional() {
return uno::ma57_symbols_available() || uno::ma27_symbols_available();
}
59 changes: 59 additions & 0 deletions uno/ingredients/subproblem_solvers/HSL/HSLLoader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2018-2024 Charlie Vanaret
// Licensed under the MIT license. See LICENSE file in the project directory for details.

#ifndef UNO_HSLLOADER_H
#define UNO_HSLLOADER_H

// IPOPT-style runtime loading of the HSL routines: instead of linking libhsl at
// build time, dlopen it on first use and resolve MA27/MA57 by symbol name. This
// keeps Uno build- and ship-able without HSL; the user supplies libhsl at runtime.

#include <string>

namespace uno {
extern "C" {
// MA57
using ma57id_fp = void (*)(double cntl[], int icntl[]);
using ma57ad_fp = void (*)(const int* n, const int* ne, const int irn[], const int jcn[], const int* lkeep,
int keep[], int iwork[], int icntl[], int info[], double rinfo[]);
using ma57bd_fp = void (*)(const int* n, int* ne, const double a[], double fact[], const int* lfact,
int ifact[], const int* lifact, const int* lkeep, const int keep[], int iwork[], int icntl[], double cntl[],
int info[], double rinfo[]);
using ma57cd_fp = void (*)(const int* job, const int* n, double fact[], int* lfact, int ifact[], int* lifact,
const int* nrhs, double rhs[], const int* lrhs, double work[], int* lwork, int iwork[], int icntl[], int info[]);
using ma57dd_fp = void (*)(const int* job, const int* n, int* ne, const double a[], const int irn[],
const int jcn[], double fact[], int* lfact, int ifact[], int* lifact, const double rhs[], double x[], double resid[],
double work[], int iwork[], int icntl[], double cntl[], int info[], double rinfo[]);
using ma57ed_fp = void (*)(const int* n, const int* ic, int keep[], const double fact[], const int* lfact,
double newfac[], const int* lnew, const int ifact[], const int* lifact, int newifc[], const int* linew, int info[]);

// MA27
using ma27id_fp = void (*)(int ICNTL[], double CNTL[]);
using ma27ad_fp = void (*)(int* N, int* NZ, int IRN[], int ICN[], int IW[], int* LIW, int IKEEP[], int IW1[],
int* NSTEPS, int* IFLAG, int ICNTL[], double CNTL[], int INFO[], double* OPS);
using ma27bd_fp = void (*)(int* N, int* NZ, int IRN[], int ICN[], double A[], int* LA, int IW[], int* LIW,
int IKEEP[], int* NSTEPS, int* MAXFRT, int IW1[], int ICNTL[], double CNTL[], int INFO[]);
using ma27cd_fp = void (*)(int* N, double A[], int* LA, int IW[], int* LIW, double W[], int* MAXFRT, double RHS[],
int IW1[], int* NSTEPS, int ICNTL[], int INFO[]);
}

// Resolved by load_hsl_library(); nullptr until loaded or if the symbol is absent.
extern ma57id_fp hsl_ma57id;
extern ma57ad_fp hsl_ma57ad;
extern ma57bd_fp hsl_ma57bd;
extern ma57cd_fp hsl_ma57cd;
extern ma57dd_fp hsl_ma57dd;
extern ma57ed_fp hsl_ma57ed;
extern ma27id_fp hsl_ma27id;
extern ma27ad_fp hsl_ma27ad;
extern ma27bd_fp hsl_ma27bd;
extern ma27cd_fp hsl_ma27cd;

// dlopen the HSL library (default name per platform, overridable via the
// UNO_HSL_LIBRARY environment variable) and resolve the symbols. Idempotent.
bool load_hsl_library(const std::string& library_name = "");
bool ma57_symbols_available();
bool ma27_symbols_available();
} // namespace

#endif // UNO_HSLLOADER_H
Loading
Loading