From 860e6b24ae9d0898d1d4aa4caaf5d4d7e30bb983 Mon Sep 17 00:00:00 2001 From: Luke Marshall <52978038+mathgeekcoder@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:57:54 -0800 Subject: [PATCH] Initial proof-of-concept for dynamic HiPO loading in highspy --- CMakeLists.txt | 12 +- cmake/hipo-python.cmake | 132 +++++++++++++++++ cmake/sources-python.cmake | 157 +++++++++++++++++++- cmake/sources.cmake | 10 +- highs/highspy/highs.py | 15 ++ highs/ipm/hipo/HipoCApi.cpp | 49 +++++++ highs/ipm/hipo/HipoCApi.h | 104 ++++++++++++++ highs/lp_data/DynamicHipoLoader.cpp | 198 ++++++++++++++++++++++++++ highs/lp_data/DynamicHipoLoader.h | 162 +++++++++++++++++++++ highs/lp_data/HighsOptions.cpp | 64 +++------ highs/lp_data/HighsSolve.cpp | 41 ++++-- highspy-hipo/CMakeLists.txt | 19 +++ highspy-hipo/README.md | 49 +++++++ highspy-hipo/highspy_hipo/__init__.py | 88 ++++++++++++ highspy-hipo/pyproject.toml | 76 ++++++++++ pyproject.toml | 1 + 16 files changed, 1120 insertions(+), 57 deletions(-) create mode 100644 cmake/hipo-python.cmake create mode 100644 highs/ipm/hipo/HipoCApi.cpp create mode 100644 highs/ipm/hipo/HipoCApi.h create mode 100644 highs/lp_data/DynamicHipoLoader.cpp create mode 100644 highs/lp_data/DynamicHipoLoader.h create mode 100644 highspy-hipo/CMakeLists.txt create mode 100644 highspy-hipo/README.md create mode 100644 highspy-hipo/highspy_hipo/__init__.py create mode 100644 highspy-hipo/pyproject.toml diff --git a/CMakeLists.txt b/CMakeLists.txt index 19ee58fd8d..9e6309ddcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,15 @@ if (PYTHON_BUILD_SETUP) set(BUILD_TESTING OFF) endif() +option(HIPO "Build HIPO" OFF) +option(HIPO_PYTHON_BUILD "Build HiPO Python shared library" OFF) +message(STATUS "Build HiPO Python: ${HIPO_PYTHON_BUILD}") +if (HIPO_PYTHON_BUILD) + set(BUILD_CXX OFF) + set(BUILD_TESTING OFF) + set(HIPO ON) +endif() + include(CMakeDependentOption) CMAKE_DEPENDENT_OPTION(ALL_TESTS "Build all tests" OFF "BUILD_TESTING;BUILD_CXX" OFF) @@ -146,7 +155,7 @@ endif() # message(STATUS "CUPLDP with Nvidia is only supported on Linux at the moment. Using CPU version.") # endif() -option(HIPO "Build HIPO" OFF) + message(STATUS "Build HIPO: ${HIPO}") option(BUILD_OPENBLAS "Build OpenBLAS" OFF) message(STATUS "Build OpenBLAS: ${BUILD_OPENBLAS}") @@ -850,6 +859,7 @@ else(FAST_BUILD) endif() include(python-highs) + include(hipo-python) option(USE_DOTNET_STD_21 "Use .Net Standard 2.1 support" ON) message(STATUS ".Net: Use .Net Framework 2.1 support: ${USE_DOTNET_STD_21}") diff --git a/cmake/hipo-python.cmake b/cmake/hipo-python.cmake new file mode 100644 index 0000000000..7bf4c12036 --- /dev/null +++ b/cmake/hipo-python.cmake @@ -0,0 +1,132 @@ +# HiPO Python build module +# Similar to python-highs.cmake but builds the HiPO shared library + +if (NOT HIPO_PYTHON_BUILD) + return() +endif() + +include(sources-python) + +# could use subset of sources, but this is easier to maintain +set(sources_python ${highs_sources_python} + ${cupdlp_sources_python} + ${ipx_sources_python} + ${basiclu_sources_python}) + +set(headers_python ${highs_headers_python} + ${cupdlp_headers_python} + ${ipx_headers_python} + ${basiclu_headers_python}) + +# Create shared library +add_library(highs_hipo SHARED + ${sources_python} + ${hipo_sources} + ${factor_highs_sources} + ${hipo_util_sources} + ${hipo_orderings_sources} + + ${headers_python} + ${hipo_headers} + ${factor_highs_headers} + ${hipo_util_headers} + ${hipo_orderings_headers} +) + +target_include_directories(highs_hipo PRIVATE + ${include_dirs_python} +) + +target_compile_definitions(highs_hipo PRIVATE HIPO_LIBRARY_BUILD) + +# Dependencies +find_package(ZLIB) +if(ZLIB_FOUND) + target_link_libraries(highs_hipo PRIVATE ZLIB::ZLIB) +endif() + + +if (NOT USE_CMAKE_FIND_BLAS) + if(APPLE) + target_link_libraries(highs_hipo PRIVATE "-framework Accelerate") + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_APPLE_BLAS) + elseif(WIN32) + if(TARGET OpenBLAS::OpenBLAS) + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + target_link_libraries(highs_hipo PRIVATE OpenBLAS::OpenBLAS) + elseif(OPENBLAS_LIB) + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + message(STATUS "Linking against OpenBLAS via raw library: ${OPENBLAS_LIB}") + target_link_libraries(highs_hipo PRIVATE ${OPENBLAS_LIB}) + target_include_directories(highs_hipo PRIVATE ${OPENBLAS_INCLUDE_DIR}) + elseif(BUILD_OPENBLAS) + target_link_libraries(highs_hipo PRIVATE openblas) + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + else() + message(FATAL_ERROR "OpenBLAS not found on Windows.") + endif() + + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + else() + # LINUX + if(BLAS_LIB) + target_link_libraries(highs_hipo PRIVATE "${BLAS_LIB}" cblas) + elseif(OPENBLAS_LIB) + target_link_libraries(highs_hipo PRIVATE "${OPENBLAS_LIB}") + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + elseif(BUILD_OPENBLAS) + target_link_libraries(highs_hipo PRIVATE openblas) + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + else() + message(FATAL_ERROR "No BLAS library available") + endif(BLAS_LIB) + endif(APPLE) +else() + + if (WIN32 AND TARGET OpenBLAS::OpenBLAS) + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + target_link_libraries(highs_hipo PRIVATE OpenBLAS::OpenBLAS) + else() + target_link_libraries(highs_hipo PRIVATE BLAS::BLAS) + + string(TOLOWER "${BLAS_LIBRARIES}" blas_lower) + if(blas_lower MATCHES "openblas") + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_OPENBLAS) + elseif(blas_lower MATCHES "accelerate") + target_compile_definitions(highs_hipo PRIVATE HIPO_USES_APPLE_BLAS) + endif() + endif() +endif() + +if(MSVC) + target_compile_options(highs_hipo PRIVATE "/bigobj") +endif() + +if (NOT MSVC) + target_compile_options(highs_hipo PRIVATE "-ftemplate-depth=2048") +endif() + + +# Set library properties +set_target_properties(highs_hipo PROPERTIES + OUTPUT_NAME "highs_hipo" + POSITION_INDEPENDENT_CODE ON + CXX_VISIBILITY_PRESET hidden + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON +) + +if(WIN32) + set_target_properties(highs_hipo PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF) +elseif(APPLE) + set_target_properties(highs_hipo PROPERTIES INSTALL_RPATH "@loader_path") +else() + set_target_properties(highs_hipo PROPERTIES INSTALL_RPATH "$ORIGIN") +endif() + +# Install to Python package directory +install(TARGETS highs_hipo + LIBRARY DESTINATION highspy_hipo + RUNTIME DESTINATION highspy_hipo + ARCHIVE DESTINATION highspy_hipo +) diff --git a/cmake/sources-python.cmake b/cmake/sources-python.cmake index c60b376130..bd27dc56a7 100644 --- a/cmake/sources-python.cmake +++ b/cmake/sources-python.cmake @@ -283,7 +283,8 @@ set(highs_sources_python highs/util/HighsUtils.cpp highs/util/HSet.cpp highs/util/HVectorBase.cpp - highs/util/stringutil.cpp) + highs/util/stringutil.cpp + highs/lp_data/DynamicHipoLoader.cpp) set(highs_headers_python extern/pdqsort/pdqsort.h @@ -321,6 +322,7 @@ set(highs_headers_python highs/lp_data/HighsSolve.h highs/lp_data/HighsStatus.h highs/lp_data/HStruct.h + highs/lp_data/DynamicHipoLoader.h highs/mip/feasibilityjump.hh highs/mip/HighsCliqueTable.h highs/mip/HighsConflictPool.h @@ -444,4 +446,155 @@ set(highs_headers_python highs/util/HVectorBase.h highs/util/stringutil.h highs/Highs.h - ) \ No newline at end of file + ) + + + set(hipo_sources + highs/ipm/hipo/ipm/CurtisReidScaling.cpp + highs/ipm/hipo/ipm/IpmData.cpp + highs/ipm/hipo/ipm/FactorHiGHSSolver.cpp + highs/ipm/hipo/ipm/Control.cpp + highs/ipm/hipo/ipm/Iterate.cpp + highs/ipm/hipo/ipm/LogHighs.cpp + highs/ipm/hipo/ipm/Model.cpp + highs/ipm/hipo/ipm/Refine.cpp + highs/ipm/hipo/ipm/Solver.cpp + highs/ipm/hipo/HipoCApi.cpp) + +set(hipo_headers + highs/ipm/hipo/ipm/CurtisReidScaling.h + highs/ipm/hipo/ipm/IpmData.h + highs/ipm/hipo/ipm/FactorHiGHSSolver.h + highs/ipm/hipo/ipm/Parameters.h + highs/ipm/hipo/ipm/Control.h + highs/ipm/hipo/ipm/Info.h + highs/ipm/hipo/ipm/Iterate.h + highs/ipm/hipo/ipm/LinearSolver.h + highs/ipm/hipo/ipm/LogHighs.h + highs/ipm/hipo/ipm/Model.h + highs/ipm/hipo/ipm/Options.h + highs/ipm/hipo/ipm/Solver.h + highs/ipm/hipo/ipm/Status.h + highs/ipm/hipo/HipoCApi.h) + +set(factor_highs_sources + highs/ipm/hipo/factorhighs/Analyse.cpp + highs/ipm/hipo/factorhighs/CallAndTimeBlas.cpp + highs/ipm/hipo/factorhighs/CliqueStack.cpp + highs/ipm/hipo/factorhighs/DataCollector.cpp + highs/ipm/hipo/factorhighs/DenseFactHybrid.cpp + highs/ipm/hipo/factorhighs/DenseFactKernel.cpp + highs/ipm/hipo/factorhighs/DgemmParallel.cpp + highs/ipm/hipo/factorhighs/FactorHiGHS.cpp + highs/ipm/hipo/factorhighs/Factorise.cpp + highs/ipm/hipo/factorhighs/FormatHandler.cpp + highs/ipm/hipo/factorhighs/HybridHybridFormatHandler.cpp + highs/ipm/hipo/factorhighs/HybridSolveHandler.cpp + highs/ipm/hipo/factorhighs/KrylovMethodsIpm.cpp + highs/ipm/hipo/factorhighs/Numeric.cpp + highs/ipm/hipo/factorhighs/SolveHandler.cpp + highs/ipm/hipo/factorhighs/Swaps.cpp + highs/ipm/hipo/factorhighs/Symbolic.cpp) + +set(factor_highs_headers + highs/ipm/hipo/factorhighs/Analyse.h + highs/ipm/hipo/factorhighs/CallAndTimeBlas.h + highs/ipm/hipo/factorhighs/CliqueStack.h + highs/ipm/hipo/factorhighs/DataCollector.h + highs/ipm/hipo/factorhighs/DenseFact.h + highs/ipm/hipo/factorhighs/DgemmParallel.h + highs/ipm/hipo/factorhighs/FactorHiGHS.h + highs/ipm/hipo/factorhighs/FactorHiGHSSettings.h + highs/ipm/hipo/factorhighs/Factorise.h + highs/ipm/hipo/factorhighs/FormatHandler.h + highs/ipm/hipo/factorhighs/HybridHybridFormatHandler.h + highs/ipm/hipo/factorhighs/HybridSolveHandler.h + highs/ipm/hipo/factorhighs/KrylovMethodsIpm.h + highs/ipm/hipo/factorhighs/Numeric.h + highs/ipm/hipo/factorhighs/ReturnValues.h + highs/ipm/hipo/factorhighs/SolveHandler.h + highs/ipm/hipo/factorhighs/Swaps.h + highs/ipm/hipo/factorhighs/Symbolic.h + highs/ipm/hipo/factorhighs/Timing.h) + +set(hipo_util_sources + highs/ipm/hipo/auxiliary/Auxiliary.cpp + highs/ipm/hipo/auxiliary/KrylovMethods.cpp + highs/ipm/hipo/auxiliary/Log.cpp + highs/ipm/hipo/auxiliary/VectorOperations.cpp) + +set(hipo_util_headers + highs/ipm/hipo/auxiliary/Auxiliary.h + highs/ipm/hipo/auxiliary/IntConfig.h + highs/ipm/hipo/auxiliary/KrylovMethods.h + highs/ipm/hipo/auxiliary/Log.h + highs/ipm/hipo/auxiliary/mycblas.h + highs/ipm/hipo/auxiliary/VectorOperations.h) + +set(hipo_orderings_sources + extern/amd/amd_1.c + extern/amd/amd_2.c + extern/amd/amd_aat.c + extern/amd/amd_control.c + extern/amd/amd_defaults.c + extern/amd/amd_info.c + extern/amd/amd_order.c + extern/amd/amd_post_tree.c + extern/amd/amd_postorder.c + extern/amd/amd_preprocess.c + extern/amd/amd_valid.c + extern/amd/SuiteSparse_config.c + extern/metis/GKlib/error.c + extern/metis/GKlib/mcore.c + extern/metis/GKlib/memory.c + extern/metis/GKlib/random.c + extern/metis/libmetis/auxapi.c + extern/metis/libmetis/balance.c + extern/metis/libmetis/bucketsort.c + extern/metis/libmetis/coarsen.c + extern/metis/libmetis/compress.c + extern/metis/libmetis/contig.c + extern/metis/libmetis/fm.c + extern/metis/libmetis/gklib.c + extern/metis/libmetis/graph.c + extern/metis/libmetis/initpart.c + extern/metis/libmetis/mcutil.c + extern/metis/libmetis/mmd.c + extern/metis/libmetis/ometis.c + extern/metis/libmetis/options.c + extern/metis/libmetis/refine.c + extern/metis/libmetis/separator.c + extern/metis/libmetis/sfm.c + extern/metis/libmetis/srefine.c + extern/metis/libmetis/util.c + extern/metis/libmetis/wspace.c + extern/rcm/rcm.cpp) + +set(hipo_orderings_headers + extern/amd/amd_internal.h + extern/amd/amd.h + extern/amd/SuiteSparse_config.h + extern/metis/GKlib/gk_arch.h + extern/metis/GKlib/gk_defs.h + extern/metis/GKlib/gk_macros.h + extern/metis/GKlib/gk_mkblas.h + extern/metis/GKlib/gk_mkmemory.h + extern/metis/GKlib/gk_mkpqueue.h + extern/metis/GKlib/gk_mkrandom.h + extern/metis/GKlib/gk_mksort.h + extern/metis/GKlib/gk_ms_inttypes.h + extern/metis/GKlib/gk_ms_stat.h + extern/metis/GKlib/gk_ms_stdint.h + extern/metis/GKlib/gk_proto.h + extern/metis/GKlib/gk_struct.h + extern/metis/GKlib/gk_types.h + extern/metis/GKlib/GKlib.h + extern/metis/libmetis/defs.h + extern/metis/libmetis/gklib_defs.h + extern/metis/libmetis/macros.h + extern/metis/libmetis/metislib.h + extern/metis/libmetis/proto.h + extern/metis/libmetis/stdheaders.h + extern/metis/libmetis/struct.h + extern/metis/metis.h + extern/rcm/rcm.h) \ No newline at end of file diff --git a/cmake/sources.cmake b/cmake/sources.cmake index 4bedd7531e..b2e0228b7d 100644 --- a/cmake/sources.cmake +++ b/cmake/sources.cmake @@ -185,7 +185,8 @@ set(hipo_sources ipm/hipo/ipm/LogHighs.cpp ipm/hipo/ipm/Model.cpp ipm/hipo/ipm/Refine.cpp - ipm/hipo/ipm/Solver.cpp) + ipm/hipo/ipm/Solver.cpp + ipm/hipo/HipoCApi.cpp) set(hipo_headers ipm/hipo/ipm/CurtisReidScaling.h @@ -200,7 +201,8 @@ set(hipo_headers ipm/hipo/ipm/Model.h ipm/hipo/ipm/Options.h ipm/hipo/ipm/Solver.h - ipm/hipo/ipm/Status.h) + ipm/hipo/ipm/Status.h + ipm/hipo/HipoCApi.h) set(factor_highs_sources ipm/hipo/factorhighs/Analyse.cpp @@ -436,7 +438,8 @@ set(highs_sources util/HighsUtils.cpp util/HSet.cpp util/HVectorBase.cpp - util/stringutil.cpp) + util/stringutil.cpp + lp_data/DynamicHipoLoader.cpp) # add catch header? set(highs_headers @@ -477,6 +480,7 @@ set(highs_headers lp_data/HighsSolve.h lp_data/HighsStatus.h lp_data/HStruct.h + lp_data/DynamicHipoLoader.h mip/feasibilityjump.hh mip/HighsCliqueTable.h mip/HighsConflictPool.h diff --git a/highs/highspy/highs.py b/highs/highspy/highs.py index 84f60c9639..77d3b1ea0c 100644 --- a/highs/highspy/highs.py +++ b/highs/highspy/highs.py @@ -29,6 +29,20 @@ class Highs(_Highs): """ HiGHS solver interface """ + @staticmethod + def is_hipo_available() -> bool: + """ + Check if the HiPO solver extension is available. + + Returns: + True if highspy-hipo is installed and the library is available. + """ + try: + import highspy_hipo + return highspy_hipo.is_available() + except ImportError: + return False + __handle_keyboard_interrupt: bool = False __handle_user_interrupt: bool = False @@ -36,6 +50,7 @@ class Highs(_Highs): __solver_stopped: RLock = RLock() __solver_started: Lock = Lock() __solver_status: Optional[HighsStatus] = None + IsHipoAvailable: bool = is_hipo_available() def __init__(self): super().__init__() diff --git a/highs/ipm/hipo/HipoCApi.cpp b/highs/ipm/hipo/HipoCApi.cpp new file mode 100644 index 0000000000..48f0b9fc78 --- /dev/null +++ b/highs/ipm/hipo/HipoCApi.cpp @@ -0,0 +1,49 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file ipm/hipo/HipoCApi.cpp + * @brief C-style API implementation for the HiPO library + */ + +#include "ipm/hipo/HipoCApi.h" + +#include "ipm/IpxWrapper.h" +#include "lp_data/HighsLpSolverObject.h" +#include "lp_data/HighsOptions.h" +#include "lp_data/HighsSolution.h" +#include "lp_data/HighsStatus.h" +#include "util/HighsTimer.h" + +extern "C" { + +HIPO_API int hipo_get_abi_version(void) { return HIPO_ABI_VERSION; } + +HIPO_API const char* hipo_get_version(void) { return HIPO_VERSION; } + +} // extern "C" + +// Note: We use extern "C" here to disable C++ name mangling, allowing +// GetProcAddress/dlsym to find the function by its simple name "hipo_solve_lp". +// While extern "C" is typically for C-compatible interfaces, using C++ types +// (references, classes) works here because: +// 1. Both highspy and highspy-hipo are built with the same C++ compiler/ABI +// 2. The ABI version check ensures struct layouts match between builds +// 3. We only need C linkage for symbol lookup, not C type compatibility +extern "C" HIPO_API HighsStatus hipo_solve_lp( + const HighsOptions& options, + HighsTimer& timer, + const HighsLp& lp, + HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsModelStatus& model_status, + HighsInfo& highs_info, + HighsCallback& callback) { + // Call the actual HiPO solver implementation + // Note: solveLpHipo is defined in IpxWrapper.cpp when HIPO is defined + return solveLpHipo(options, timer, lp, highs_basis, highs_solution, + model_status, highs_info, callback); +} diff --git a/highs/ipm/hipo/HipoCApi.h b/highs/ipm/hipo/HipoCApi.h new file mode 100644 index 0000000000..76f2917c60 --- /dev/null +++ b/highs/ipm/hipo/HipoCApi.h @@ -0,0 +1,104 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file ipm/hipo/HipoCApi.h + * @brief C-style API for the HiPO library for dynamic loading + */ +#ifndef IPM_HIPO_C_API_H_ +#define IPM_HIPO_C_API_H_ + +#include "HConfig.h" + +// Export macro for shared library +#if defined(_WIN32) || defined(_WIN64) +#ifdef HIPO_LIBRARY_BUILD +#define HIPO_API __declspec(dllexport) +#else +#define HIPO_API __declspec(dllimport) +#endif +#else +#define HIPO_API __attribute__((visibility("default"))) +#endif + +// ABI version - increment when the C API signature changes +#define HIPO_ABI_VERSION 1 + +// Helper macros for stringification +#define HIPO_STRINGIFY(x) #x +#define HIPO_TOSTRING(x) HIPO_STRINGIFY(x) + +// Version string derived from HConfig.h +#define HIPO_VERSION \ + HIPO_TOSTRING(HIGHS_VERSION_MAJOR) "." \ + HIPO_TOSTRING(HIGHS_VERSION_MINOR) "." \ + HIPO_TOSTRING(HIGHS_VERSION_PATCH) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get the ABI version of the HiPO library. + * Used for compatibility checking at runtime. + * + * @return ABI version number + */ +HIPO_API int hipo_get_abi_version(void); + +/** + * Get the version string of the HiPO library. + * + * @return Version string (e.g., "1.12.0") + */ +HIPO_API const char* hipo_get_version(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +// C++ API with actual types (outside extern "C" block) +// These use C++ references and HiGHS types directly +#ifdef __cplusplus + +#include "lp_data/HighsCallback.h" +#include "lp_data/HighsInfo.h" +#include "lp_data/HighsLp.h" +#include "lp_data/HighsOptions.h" +#include "lp_data/HighsSolution.h" +#include "lp_data/HighsStatus.h" +#include "util/HighsTimer.h" + +/** + * Solve an LP using the HiPO solver. + * + * This is the main entry point for the dynamically loaded HiPO library. + * Uses actual HiGHS types for type safety. + * + * @param options Reference to HighsOptions + * @param timer Reference to HighsTimer + * @param lp Reference to HighsLp + * @param highs_basis Reference to HighsBasis + * @param highs_solution Reference to HighsSolution + * @param model_status Reference to HighsModelStatus + * @param highs_info Reference to HighsInfo + * @param callback Reference to HighsCallback + * @return HighsStatus indicating success or failure + */ +extern "C" HIPO_API HighsStatus hipo_solve_lp( + const HighsOptions& options, + HighsTimer& timer, + const HighsLp& lp, + HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsModelStatus& model_status, + HighsInfo& highs_info, + HighsCallback& callback +); + +#endif // __cplusplus + +#endif // IPM_HIPO_C_API_H_ diff --git a/highs/lp_data/DynamicHipoLoader.cpp b/highs/lp_data/DynamicHipoLoader.cpp new file mode 100644 index 0000000000..0b914c5c35 --- /dev/null +++ b/highs/lp_data/DynamicHipoLoader.cpp @@ -0,0 +1,198 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file lp_data/DynamicHipoLoader.cpp + * @brief Dynamic loader for the optional HiPO library + */ + +#include "lp_data/DynamicHipoLoader.h" + +#include +#include + +#include "lp_data/HighsLpSolverObject.h" + +// Platform-specific includes for dynamic loading +#if defined(_WIN32) || defined(_WIN64) +#define WIN32_LEAN_AND_MEAN +#include +#define PATH_SEPARATOR "\\" +#else +#include +#define PATH_SEPARATOR "/" +#endif + +DynamicHipoLoader& DynamicHipoLoader::instance() { + static DynamicHipoLoader loader; + return loader; +} + +DynamicHipoLoader::DynamicHipoLoader() = default; + +DynamicHipoLoader::~DynamicHipoLoader() { unloadLibrary(); } + +bool DynamicHipoLoader::isAvailable() { + if (!initialized_) { + initialized_ = true; + available_ = tryLoad(); + } + return available_; +} + +std::string DynamicHipoLoader::getVersion() const { return version_; } + +std::string DynamicHipoLoader::getLibraryFilename() const { +#if defined(_WIN32) || defined(_WIN64) + return "highs_hipo.dll"; +#elif defined(__APPLE__) + return "libhighs_hipo.dylib"; +#else + return "libhighs_hipo.so"; +#endif +} + +std::vector DynamicHipoLoader::getSearchPaths() const { + std::vector paths; + const std::string lib_name = getLibraryFilename(); + + // 1. Explicit path via environment variable (for testing/advanced users) + const char* explicit_path = std::getenv("HIGHS_HIPO_LIBRARY"); + if (explicit_path && std::strlen(explicit_path) > 0) { + paths.push_back(std::string(explicit_path)); + } + + // 2. Python highspy_hipo package location + // Set by highspy_hipo.__init__ when the package is imported + const char* pkg_path = std::getenv("HIGHSPY_HIPO_LIBRARY_PATH"); + if (pkg_path && std::strlen(pkg_path) > 0) { + paths.push_back(std::string(pkg_path) + PATH_SEPARATOR + lib_name); + } + + return paths; +} + +bool DynamicHipoLoader::loadLibrary(const std::string& path) { +#if defined(_WIN32) || defined(_WIN64) + lib_handle_ = static_cast(LoadLibraryA(path.c_str())); + if (!lib_handle_) { + DWORD error = GetLastError(); + char* msg = nullptr; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, error, 0, reinterpret_cast(&msg), 0, + nullptr); + last_error_ = "Failed to load " + path + ": " + (msg ? msg : "Unknown error"); + if (msg) LocalFree(msg); + return false; + } +#else + lib_handle_ = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!lib_handle_) { + const char* err = dlerror(); + last_error_ = "Failed to load " + path + ": " + (err ? err : "Unknown error"); + return false; + } +#endif + return true; +} + +void DynamicHipoLoader::unloadLibrary() { + if (lib_handle_) { +#if defined(_WIN32) || defined(_WIN64) + FreeLibrary(static_cast(lib_handle_)); +#else + dlclose(lib_handle_); +#endif + lib_handle_ = nullptr; + } + fn_get_abi_version_ = nullptr; + fn_get_version_ = nullptr; + fn_solve_lp_ = nullptr; +} + +void* DynamicHipoLoader::resolveSymbol(const char* name) { +#if defined(_WIN32) || defined(_WIN64) + return reinterpret_cast( + GetProcAddress(static_cast(lib_handle_), name)); +#else + return dlsym(lib_handle_, name); +#endif +} + +bool DynamicHipoLoader::resolveFunctions() { + if (!lib_handle_) return false; + + fn_get_abi_version_ = + reinterpret_cast(resolveSymbol("hipo_get_abi_version")); + fn_get_version_ = + reinterpret_cast(resolveSymbol("hipo_get_version")); + fn_solve_lp_ = + reinterpret_cast(resolveSymbol("hipo_solve_lp")); + + if (!fn_get_abi_version_ || !fn_get_version_ || !fn_solve_lp_) { + last_error_ = "Failed to resolve required HiPO functions"; + return false; + } + + return true; +} + +bool DynamicHipoLoader::tryLoad() { + const auto paths = getSearchPaths(); + + if (paths.empty()) { + last_error_ = "HiPO not available. Install with: pip install highspy[hipo]"; + return false; + } + + for (const auto& path : paths) { + if (loadLibrary(path)) { + if (resolveFunctions()) { + // Check ABI compatibility + int loaded_abi_version = fn_get_abi_version_(); + if (loaded_abi_version != kHipoAbiVersion) { + last_error_ = "HiPO ABI version mismatch: expected " + + std::to_string(kHipoAbiVersion) + ", got " + + std::to_string(loaded_abi_version) + + ". Please reinstall: pip install --force-reinstall highspy[hipo]"; + unloadLibrary(); + continue; + } + + // Get version string + const char* ver = fn_get_version_(); + version_ = ver ? ver : ""; + + return true; + } + unloadLibrary(); + } + } + + if (last_error_.empty()) { + last_error_ = "HiPO not available. Install with: pip install highspy[hipo]"; + } + return false; +} + +HighsStatus DynamicHipoLoader::solveLp(HighsLpSolverObject& solver_object) { + std::cout << "Using HiPO version: " << getVersion() << std::endl; + + if (!isAvailable()) { + return HighsStatus::kError; + } + + // Call the dynamically loaded solve function with actual types + return fn_solve_lp_( + solver_object.options_, + solver_object.timer_, + solver_object.lp_, + solver_object.basis_, + solver_object.solution_, + solver_object.model_status_, + solver_object.highs_info_, + solver_object.callback_); +} diff --git a/highs/lp_data/DynamicHipoLoader.h b/highs/lp_data/DynamicHipoLoader.h new file mode 100644 index 0000000000..4cd247694f --- /dev/null +++ b/highs/lp_data/DynamicHipoLoader.h @@ -0,0 +1,162 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* */ +/* This file is part of the HiGHS linear optimization suite */ +/* */ +/* Available as open-source under the MIT License */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/**@file lp_data/DynamicHipoLoader.h + * @brief Dynamic loader for the optional HiPO library + */ +#ifndef LP_DATA_DYNAMIC_HIPO_LOADER_H_ +#define LP_DATA_DYNAMIC_HIPO_LOADER_H_ + +#include +#include + +#include "lp_data/HighsCallback.h" +#include "lp_data/HighsInfo.h" +#include "lp_data/HighsLp.h" +#include "lp_data/HighsOptions.h" +#include "lp_data/HighsSolution.h" +#include "lp_data/HighsStatus.h" +#include "util/HighsTimer.h" + +// Forward declaration +class HighsLpSolverObject; + +// ABI version for compatibility checking +// Increment this when the C API signature changes +constexpr int kHipoAbiVersion = 1; + +// C-style function pointer types for dynamic loading +extern "C" { + +// Get the ABI version of the loaded HiPO library +typedef int (*hipo_get_abi_version_t)(); + +// Get the version string of the loaded HiPO library +typedef const char* (*hipo_get_version_t)(); + +// Solve LP using HiPO - uses actual HiGHS types for type safety +// Note: ABI version check ensures struct layouts match between builds +typedef HighsStatus (*hipo_solve_lp_t)( + const HighsOptions& options, + HighsTimer& timer, + const HighsLp& lp, + HighsBasis& highs_basis, + HighsSolution& highs_solution, + HighsModelStatus& model_status, + HighsInfo& highs_info, + HighsCallback& callback +); + +} // extern "C" + +/** + * Dynamic loader for the optional HiPO library. + * + * This class handles runtime loading of the HiPO shared library, + * allowing HiGHS to optionally use HiPO when it's installed via + * the highspy-hipo package, without requiring HiPO at compile time. + */ +class DynamicHipoLoader { + public: + /** + * Get the singleton instance of the loader. + */ + static DynamicHipoLoader& instance(); + + /** + * Check if the HiPO library is available and compatible. + * This performs lazy initialization on first call. + * + * @return true if HiPO is loaded and ABI-compatible + */ + bool isAvailable(); + + /** + * Get the version string of the loaded HiPO library. + * + * @return Version string, or empty string if not loaded + */ + std::string getVersion() const; + + /** + * Solve LP using the dynamically loaded HiPO solver. + * + * @param solver_object The LP solver object containing problem data + * @return HighsStatus indicating success or failure + */ + HighsStatus solveLp(HighsLpSolverObject& solver_object); + + /** + * Get the last error message if loading failed. + */ + const std::string& getLastError() const { return last_error_; } + + private: + DynamicHipoLoader(); + ~DynamicHipoLoader(); + + // Prevent copying + DynamicHipoLoader(const DynamicHipoLoader&) = delete; + DynamicHipoLoader& operator=(const DynamicHipoLoader&) = delete; + + /** + * Attempt to load the HiPO library from various locations. + */ + bool tryLoad(); + + /** + * Load a library by path. + * @return true if successful + */ + bool loadLibrary(const std::string& path); + + /** + * Resolve a symbol from the loaded library. + */ + void* resolveSymbol(const char* name); + + /** + * Resolve all required function pointers. + * @return true if all functions were resolved + */ + bool resolveFunctions(); + + /** + * Unload the library if loaded. + */ + void unloadLibrary(); + + /** + * Get platform-specific library search paths. + */ + std::vector getSearchPaths() const; + + /** + * Get platform-specific library filename. + */ + std::string getLibraryFilename() const; + + // Library handle (platform-specific) +#if defined(_WIN32) || defined(_WIN64) + void* lib_handle_ = nullptr; // HMODULE +#else + void* lib_handle_ = nullptr; +#endif + + // State + bool initialized_ = false; + bool available_ = false; + std::string last_error_; + std::string version_; + + // Function pointers + hipo_get_abi_version_t fn_get_abi_version_ = nullptr; + hipo_get_version_t fn_get_version_ = nullptr; + hipo_solve_lp_t fn_solve_lp_ = nullptr; +}; + +#endif // LP_DATA_DYNAMIC_HIPO_LOADER_H_ diff --git a/highs/lp_data/HighsOptions.cpp b/highs/lp_data/HighsOptions.cpp index 445b246c91..5e3851432d 100644 --- a/highs/lp_data/HighsOptions.cpp +++ b/highs/lp_data/HighsOptions.cpp @@ -9,15 +9,23 @@ * @brief */ #include "lp_data/HighsOptions.h" +#include "lp_data/DynamicHipoLoader.h" #include #include #include #include "util/stringutil.h" - // void setLogOptions(); +bool hipoAvailable() { +#ifndef HIPO + return DynamicHipoLoader::instance().isAvailable(); +#else + return true; +#endif +} + void highsOpenLogFile(HighsLogOptions& log_options, std::vector& option_records, const std::string log_file) { @@ -82,8 +90,7 @@ bool optionOffOnOk(const HighsLogOptions& report_log_options, bool optionSolverOk(const HighsLogOptions& report_log_options, const string& value) { -#ifndef HIPO - if (value == kHipoString) { + if (value == kHipoString && !hipoAvailable()) { highsLogUser( report_log_options, HighsLogType::kError, "The HiPO solver was requested via the \"%s\" option, but this build " @@ -92,33 +99,25 @@ bool optionSolverOk(const HighsLogOptions& report_log_options, kSolverString.c_str()); return false; } -#endif if (value == kHighsChooseString || value == kSimplexString || value == kIpmString || -#ifdef HIPO - value == kHipoString || -#endif + (value == kHipoString && hipoAvailable()) || value == kIpxString || value == kPdlpString) return true; highsLogUser(report_log_options, HighsLogType::kError, "Value \"%s\" for LP solver option (\"%s\") is not one of " -#ifdef HIPO - "\"%s\", " -#endif - "\"%s\", \"%s\", \"%s\", \"%s\" or \"%s\"\n", - value.c_str(), kSolverString.c_str(), kHighsChooseString.c_str(), + "%s\"%s\", \"%s\", \"%s\", \"%s\" or \"%s\"\n", + value.c_str(), kSolverString.c_str(), + hipoAvailable() ? (kHipoString + "\", \"").c_str() : "", + kHighsChooseString.c_str(), kSimplexString.c_str(), kIpmString.c_str(), -#ifdef HIPO - kHipoString.c_str(), -#endif kIpxString.c_str(), kPdlpString.c_str()); return false; } bool optionMipLpSolverOk(const HighsLogOptions& report_log_options, const string& value) { -#ifndef HIPO - if (value == kHipoString) { + if (value == kHipoString && !hipoAvailable()) { highsLogUser( report_log_options, HighsLogType::kError, "The HiPO solver was requested via the \"%s\" option, but this build " @@ -127,34 +126,25 @@ bool optionMipLpSolverOk(const HighsLogOptions& report_log_options, kMipLpSolverString.c_str()); return false; } -#endif if (value == kHighsChooseString || value == kSimplexString || value == kIpmString || -#ifdef HIPO - value == kHipoString || -#endif + (value == kHipoString && hipoAvailable()) || value == kIpxString) return true; highsLogUser(report_log_options, HighsLogType::kError, "Value \"%s\" for MIP LP solver option (\"%s\") is not one of " -#ifdef HIPO - "\"%s\", " -#endif - "\"%s\", \"%s\", \"%s\" or \"%s\"\n", + "%s\"%s\", \"%s\", \"%s\" or \"%s\"\n", value.c_str(), kMipLpSolverString.c_str(), + hipoAvailable() ? (kHipoString + "\", \"").c_str() : "", kHighsChooseString.c_str(), kSimplexString.c_str(), kIpmString.c_str(), -#ifdef HIPO - kHipoString.c_str(), -#endif kIpxString.c_str()); return false; } bool optionMipIpmSolverOk(const HighsLogOptions& report_log_options, const string& value) { -#ifndef HIPO - if (value == kHipoString) { + if (value == kHipoString && !hipoAvailable()) { highsLogUser( report_log_options, HighsLogType::kError, "The HiPO solver was requested via the \"%s\" option, but this build " @@ -163,24 +153,16 @@ bool optionMipIpmSolverOk(const HighsLogOptions& report_log_options, kMipIpmSolverString.c_str()); return false; } -#endif if (value == kHighsChooseString || value == kIpmString || -#ifdef HIPO - value == kHipoString || -#endif + (value == kHipoString && hipoAvailable()) || value == kIpxString) return true; highsLogUser(report_log_options, HighsLogType::kError, "Value \"%s\" for MIP IPM solver (\"%s\") option is not one of " -#ifdef HIPO - "\"%s\", " -#endif - "\"%s\", \"%s\" or \"%s\"\n", + "%s\"%s\", \"%s\" or \"%s\"\n", value.c_str(), kMipIpmSolverString.c_str(), + hipoAvailable() ? (kHipoString + "\", \"").c_str() : "", kHighsChooseString.c_str(), kIpmString.c_str(), -#ifdef HIPO - kHipoString.c_str(), -#endif kIpxString.c_str()); return false; } diff --git a/highs/lp_data/HighsSolve.cpp b/highs/lp_data/HighsSolve.cpp index a40c6ea7e7..7a14dae766 100644 --- a/highs/lp_data/HighsSolve.cpp +++ b/highs/lp_data/HighsSolve.cpp @@ -10,6 +10,7 @@ */ #include "ipm/IpxWrapper.h" +#include "lp_data/DynamicHipoLoader.h" #include "lp_data/HighsSolutionDebug.h" #include "pdlp/CupdlpWrapper.h" #include "simplex/HApp.h" @@ -39,11 +40,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { } const bool use_only_ipm = useIpm(options.solver) || options.run_centring; bool use_hipo = useHipo(options, kSolverString, solver_object.lp_); -#ifndef HIPO - // Shouldn't be possible to choose HiPO if it's not in the build - assert(!use_hipo); - use_hipo = false; -#endif + const bool use_ipx = use_only_ipm && !use_hipo; // Now actually solve LPs! // @@ -74,7 +71,7 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { // Use IPM to solve the LP if (use_hipo) { #ifdef HIPO - // Use HIPO to solve the LP + // Use HIPO to solve the LP (compiled in) sub_solver_call_time.num_call[kSubSolverHipo]++; sub_solver_call_time.run_time[kSubSolverHipo] = -solver_object.timer_.read(); @@ -90,9 +87,31 @@ HighsStatus solveLp(HighsLpSolverObject& solver_object, const string message) { return_status = interpretCallStatus(options.log_options, call_status, return_status, "solveLpHipo"); #else - highsLogUser(options.log_options, HighsLogType::kError, - "HiPO is not available in this build.\n"); - return HighsStatus::kError; + // Use HIPO via dynamic loading + DynamicHipoLoader& hipo_loader = DynamicHipoLoader::instance(); + if (hipo_loader.isAvailable()) { + sub_solver_call_time.num_call[kSubSolverHipo]++; + sub_solver_call_time.run_time[kSubSolverHipo] = + -solver_object.timer_.read(); + try { + call_status = hipo_loader.solveLp(solver_object); + } catch (const std::exception& exception) { + highsLogDev(options.log_options, HighsLogType::kError, + "Exception %s in DynamicHipoLoader::solveLp\n", + exception.what()); + call_status = HighsStatus::kError; + } + sub_solver_call_time.run_time[kSubSolverHipo] += + solver_object.timer_.read(); + return_status = interpretCallStatus(options.log_options, call_status, + return_status, "solveLpHipo"); + } else { + highsLogUser(options.log_options, HighsLogType::kError, + "HiPO is not available. Install with: pip install " + "highspy[hipo]\nError: %s\n", + hipo_loader.getLastError().c_str()); + return HighsStatus::kError; + } #endif } else if (use_ipx) { sub_solver_call_time.num_call[kSubSolverIpx]++; @@ -719,9 +738,11 @@ bool useHipo(const HighsOptions& options, } else if (specific_solver_option_value == kIpmString || specific_solver_option_value == kHipoString || force_ipm) { #ifdef HIPO + // HiPO is compiled in - use it directly use_hipo = true; #else - use_hipo = false; + // HiPO not compiled in - check if dynamically loaded library is available + use_hipo = DynamicHipoLoader::instance().isAvailable(); #endif } if (options.run_centring) use_hipo = false; diff --git a/highspy-hipo/CMakeLists.txt b/highspy-hipo/CMakeLists.txt new file mode 100644 index 0000000000..78e322bf1a --- /dev/null +++ b/highspy-hipo/CMakeLists.txt @@ -0,0 +1,19 @@ +# CMakeLists.txt for highspy-hipo wheel +# Builds the HiPO shared library using the main HiGHS CMake infrastructure + +cmake_minimum_required(VERSION 3.15...3.27) + +# Use parent directory as source - forward to main CMakeLists with HIPO_PYTHON_BUILD enabled +set(HIGHS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..") + +# Use VCPKG_ROOT environment variable for dependencies +if(DEFINED ENV{VCPKG_ROOT}) + set(VCPKG_INSTALLED "$ENV{VCPKG_ROOT}/installed/x64-windows-static") + set(ZLIB_LIBRARY "${VCPKG_INSTALLED}/lib/zlib.lib" CACHE STRING "Root directory of zlib") + set(ZLIB_INCLUDE_DIR "${VCPKG_INSTALLED}/include" CACHE STRING "Include directory for zlib") + set(BLAS_ROOT "${VCPKG_INSTALLED}/share/openblas" CACHE STRING "Root directory of BLAS/LAPACK") + set(BLAS_LIBRARIES "${VCPKG_INSTALLED}/lib/openblas.lib" CACHE STRING "BLAS/LAPACK libraries to link against") +endif() + +set(HIPO_PYTHON_BUILD ON CACHE BOOL "" FORCE) +add_subdirectory(${HIGHS_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/highs) diff --git a/highspy-hipo/README.md b/highspy-hipo/README.md new file mode 100644 index 0000000000..d1516f5d90 --- /dev/null +++ b/highspy-hipo/README.md @@ -0,0 +1,49 @@ +# highspy-hipo + +HiPO IPM solver extension for [highspy](https://pypi.org/project/highspy/). + +HiPO is an advanced Interior Point Method (IPM) solver that provides enhanced performance for linear programming problems. + +## Installation + +Install directly: + +```bash +pip install highspy-hipo +``` + +Or install via the highspy optional dependency: + +```bash +pip install highspy[hipo] +``` + +## Usage + +When `highspy-hipo` is installed, HiGHS will automatically detect and use the HiPO solver when appropriate. You can explicitly select the HiPO solver: + +```python +import highspy + +# Create a HiGHS instance +h = highspy.Highs() + +# Load your model +h.readModel("model.mps") + +# Set solver to use HiPO +h.setOptionValue("solver", "hipo") + +# Solve +h.run() +``` + +## Requirements + +- Python >= 3.8 +- highspy >= 1.12.0 +- BLAS library (bundled or system) + +## License + +Apache - see the [HiGHS repository](https://github.com/ERGO-Code/HiGHS) for details. diff --git a/highspy-hipo/highspy_hipo/__init__.py b/highspy-hipo/highspy_hipo/__init__.py new file mode 100644 index 0000000000..98edc41fe0 --- /dev/null +++ b/highspy-hipo/highspy_hipo/__init__.py @@ -0,0 +1,88 @@ +"""HiPO IPM solver extension for highspy.""" + +import os +import sys +from pathlib import Path + +# Version is kept in sync with HiGHS version in Version.txt +# This is updated during the build/release process +__version__ = "1.12.0" + + +def _get_library_name() -> str: + """Get the platform-specific library filename.""" + if sys.platform == "win32": + return "highs_hipo.dll" + elif sys.platform == "darwin": + return "libhighs_hipo.dylib" + else: + return "libhighs_hipo.so" + + +def get_library_path() -> Path: + """ + Get the path to the highs_hipo shared library. + + Returns: + Path to the shared library file. + + Raises: + FileNotFoundError: If the library is not found. + """ + package_dir = Path(__file__).parent + lib_name = _get_library_name() + lib_path = package_dir / lib_name + + if not lib_path.exists(): + raise FileNotFoundError(f"HiPO library not found at {lib_path}") + + return lib_path + + +def is_available() -> bool: + """ + Check if the HiPO library is available. + + Returns: + True if the library file exists. + """ + try: + get_library_path() + return True + except FileNotFoundError: + return False + + +def _setup_environment(): + """ + Set up environment variables for the HiGHS loader to find the library. + + This is called when the package is imported to ensure the dynamic + loader in HiGHS can find the HiPO library. + """ + try: + lib_path = get_library_path() + lib_dir = str(lib_path.parent) + + # Set environment variable for HiGHS dynamic loader + os.environ["HIGHSPY_HIPO_LIBRARY_PATH"] = lib_dir + + # Also add to system library path for completeness + if sys.platform == "win32": + path = os.environ.get("PATH", "") + if lib_dir not in path: + os.environ["PATH"] = lib_dir + os.pathsep + path + elif sys.platform == "darwin": + path = os.environ.get("DYLD_LIBRARY_PATH", "") + if lib_dir not in path: + os.environ["DYLD_LIBRARY_PATH"] = lib_dir + (os.pathsep + path if path else "") + else: + path = os.environ.get("LD_LIBRARY_PATH", "") + if lib_dir not in path: + os.environ["LD_LIBRARY_PATH"] = lib_dir + (os.pathsep + path if path else "") + except FileNotFoundError: + pass # Library not found, will be handled later + + +# Set up environment on import +_setup_environment() diff --git a/highspy-hipo/pyproject.toml b/highspy-hipo/pyproject.toml new file mode 100644 index 0000000000..382c577ad7 --- /dev/null +++ b/highspy-hipo/pyproject.toml @@ -0,0 +1,76 @@ +[build-system] +requires = ["scikit-build-core>=0.3.3"] +build-backend = "scikit_build_core.build" + +[project] +name = "highspy-hipo" +version = "1.12.0" +description = "HiPO IPM solver extension for highspy" +authors = [{ name = "HiGHS developers", email = "highsopt@gmail.com" }] +readme = "README.md" + +requires-python = ">=3.8" +dependencies = ["highspy>=1.12.0"] + +classifiers = [ + "License :: OSI Approved :: Apache License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.urls] +"Source Code" = "https://github.com/ERGO-Code/HiGHS" +"Bug Tracker" = "https://github.com/ERGO-Code/HiGHS/issues" + +[tool.scikit-build] +cmake.args = ["-DHIPO_LIBRARY_BUILD=ON"] +cmake.verbose = true +wheel.expand-macos-universal-tags = true +wheel.packages = ["highspy_hipo"] + + +# Files to include in the SDist even if they are skipped by default. Supports +# gitignore syntax. +sdist.include = [ + "highspy-hipo/*", + "Version.txt", + "LICENSE", + "README.md", + "highs/HConfig.h.in", + "highs", + "external", + "cmake", +] + +sdist.exclude = [ + ".github", + ".gitattributes", + ".gitignore", + ".github", + "app", + "build", + "check", + "docs", + "subprojects", + ".coin-or", + "build_webdemo.sh", + ".clang-format", + "__setup.py", + "BUILD.bazel", + "**meson**", + "MODS.md", + "WORKSPACE", + "nuget/", + "nuget/README.md", + "highs/*.bazel*", + "highs/*.meson*", + "interfaces/*csharp*", + "interfaces/*fortran*", + "flake.*", + "highs.pc.in" +] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4c2b1d88f5..ffc1957da7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ [project.optional-dependencies] test = ["pytest", "numpy"] +hipo = ["highspy-hipo>=1.12.0"] [tool.scikit-build] cmake.args = ["-DPYTHON_BUILD_SETUP=ON"]