diff --git a/ci/build_wheel_cuopt.sh b/ci/build_wheel_cuopt.sh index 2e38167c0a..2cc81e3f27 100755 --- a/ci/build_wheel_cuopt.sh +++ b/ci/build_wheel_cuopt.sh @@ -41,6 +41,7 @@ EXCLUDE_ARGS=( --exclude "libcusolver.so.*" --exclude "libcusparse.so.*" --exclude "libcuopt.so" + --exclude "libcuopt_client.so" --exclude "libnvJitLink.so*" --exclude "librapids_logger.so" --exclude "librmm.so" diff --git a/conda/recipes/libcuopt/recipe.yaml b/conda/recipes/libcuopt/recipe.yaml index b6172827d1..1c11cdab73 100644 --- a/conda/recipes/libcuopt/recipe.yaml +++ b/conda/recipes/libcuopt/recipe.yaml @@ -167,6 +167,7 @@ outputs: - package_contents: files: - lib/libcuopt.so + - lib/libcuopt_client.so - bin/cuopt_cli - bin/cuopt_grpc_server about: diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index b375cc4c56..63f80b7514 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -550,11 +550,12 @@ if (BUILD_TESTS) endif () set(CUOPT_SRC_FILES) +set(CUOPT_CLIENT_SRC_FILES) set(MPS_FAST_SRC_FILES) add_subdirectory(src) if (HOST_LINEINFO) - set_source_files_properties(${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1") + set_source_files_properties(${CUOPT_SRC_FILES} ${CUOPT_CLIENT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1") endif () # Needed for the fast MPS parser, available on all x86-64-v3 compliant x86 CPUs (essentially since Haswell ~2013) @@ -567,11 +568,11 @@ endif () # TODO: figure out a set of flags for ARM that fits the range of CPUs we wish to support (neoverse?) # NEON should be universal on aarch64 and enough for our purposes (parsing) though -# Apply -UNDEBUG only to solver source files (not gRPC infrastructure). -# Must happen before gRPC files are appended to CUOPT_SRC_FILES. +# Apply -UNDEBUG only to solver and parser source files (not gRPC infrastructure). +# Must happen before gRPC files are appended to CUOPT_CLIENT_SRC_FILES. # Uses APPEND to preserve any existing per-file options (e.g. -g1 from HOST_LINEINFO). if (DEFINE_ASSERT) - set_property(SOURCE ${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + set_property(SOURCE ${CUOPT_SRC_FILES} ${CUOPT_CLIENT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} APPEND PROPERTY COMPILE_OPTIONS "-UNDEBUG") endif () @@ -598,9 +599,7 @@ if (NOT SKIP_GRPC_BUILD) src/grpc/client/grpc_client.cpp src/grpc/client/grpc_client_env.cpp src/grpc/client/cython_grpc_client.cpp - src/grpc/client/solve_remote.cpp ) - # Routing (VRP) arm: everything that depends on the routing engine. Kept as # its own list so a routing-only gRPC client can be split out of the # cuopt_grpc component without moving code around again. @@ -616,7 +615,24 @@ if (NOT SKIP_GRPC_BUILD) if (CUOPT_ENABLE_GRPC_ROUTING) list(APPEND GRPC_INFRA_FILES ${GRPC_ROUTING_FILES}) endif () - list(APPEND CUOPT_SRC_FILES ${GRPC_INFRA_FILES}) + + # The protocol and the LP/MIP arm build into cuopt_client. The routing arm does NOT, + # despite also being free of raft/rmm/thrust: its mappers call routing::solver_settings_t + # and routing::assignment_t accessors that live in CUDA translation units inside libcuopt. + # Putting them in cuopt_client creates a cycle (libcuopt -> cuopt_client -> libcuopt) that + # ldd -r does not flag, because both libraries are loaded together -- it surfaces only when + # the call happens, as "undefined symbol: routing::solver_settings_t::get_time_limit". + # Moving the routing arm down needs those host-only accessors split out first, exactly as + # was done for the LP/MIP settings. + list(APPEND CUOPT_CLIENT_SRC_FILES ${GRPC_PROTO_GENERATED_FILES} ${GRPC_MATHOPT_FILES}) + if (CUOPT_ENABLE_GRPC_ROUTING) + list(APPEND CUOPT_SRC_FILES ${GRPC_ROUTING_FILES}) + endif () + + # solve_remote.cpp is the local-vs-remote dispatcher: it calls into the GPU + # solver, so it stays in cuopt_objs rather than moving down to cuopt_client. + list(APPEND CUOPT_SRC_FILES src/grpc/client/solve_remote.cpp) + list(APPEND GRPC_INFRA_FILES src/grpc/client/solve_remote.cpp) # Always keep NDEBUG defined for gRPC infrastructure files so that abseil # headers inline Mutex::Dtor() instead of emitting an external call. @@ -631,6 +647,126 @@ if (NOT SKIP_GRPC_BUILD) APPEND PROPERTY COMPILE_OPTIONS "$<$:-fvisibility=default>") endif (NOT SKIP_GRPC_BUILD) +# ################################################################################################## +# - cuopt_client - CPU-only support library ---------------------------------------------------------- +# +# Holds the host-side problem representation (parsers, data_model_view, mps_data_model, +# writers), the gRPC wire protocol (generated protos + mappers), and the gRPC client. +# None of it touches CUDA, so this library links no CUDA runtime. +# +# It exists so the Python extension modules that never call into the GPU -- data_model, +# solver_settings, io, and the gRPC client -- can link something other than libcuopt.so, +# which is what makes a GPU-free client install possible. libcuopt and cuopt_grpc_server +# both link it, so there is exactly one implementation of the mappers, not a client fork. +# +# LANGUAGES is deliberately not CUDA here: adding a .cu file to CUOPT_CLIENT_SRC_FILES +# should fail loudly rather than quietly reintroduce a CUDA dependency. +# Built as an OBJECT library first, mirroring cuopt_objs/cuopt, so that cuopt_static (for +# internal tests) can link the objects directly. The tests reach parser internals such as +# the fast MPS parser's mps_phase_registry_t, which linking the shared library would not +# give them. See the NOTE below for why this library's visibility differs from cuopt_objs. +add_library(cuopt_client_objs OBJECT ${CUOPT_CLIENT_SRC_FILES}) +# NOTE: default visibility, deliberately unlike cuopt_objs. +# +# cuopt_objs can hide everything not marked CUOPT_EXPORT because libcuopt has a curated +# public C++ API. cuopt_client is different: it was carved out of the *internals*, so +# libcuopt itself depends on ~214 of its symbols (the whole cpu_optimization_problem_t / +# data_model_view_t / mps_data_model_t / grpc_client_t surface). Those are internal +# cross-library references, not a public API, and hiding them makes libcuopt.so fail to +# load with e.g. "undefined symbol: grpc_client_t::solve_mip". +# +# Curating them behind CUOPT_EXPORT would mean annotating essentially every host-side +# method, so default visibility is the right trade here. +set_target_properties(cuopt_client_objs + PROPERTIES POSITION_INDEPENDENT_CODE ON + CXX_SCAN_FOR_MODULES OFF +) + +add_library(cuopt_client SHARED $) +add_library(cuopt::cuopt_client ALIAS cuopt_client) + +target_include_directories(cuopt_client + PUBLIC + "$" + "$" + INTERFACE + "$" +) + +target_compile_definitions(cuopt_client + PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" +) + +set_target_properties(cuopt_client + PROPERTIES POSITION_INDEPENDENT_CODE ON + CXX_SCAN_FOR_MODULES OFF + BUILD_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN" + LINKER_LANGUAGE CXX +) + +target_compile_definitions(cuopt_client_objs + PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}" +) + +target_compile_options(cuopt_client_objs + PRIVATE "$<$:${CUOPT_CXX_FLAGS}>" +) + +target_include_directories(cuopt_client_objs + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty" + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}/src/io" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/client" + "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated" + "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}/include" + $<$:${BZIP2_INCLUDE_DIRS}> + $<$:${ZLIB_INCLUDE_DIRS}> + PUBLIC + "$" + "$" + INTERFACE + "$" +) + +# CCCL is a compile-time (header-only) dependency here: the fast MPS parser uses +# host helpers from (ceil_div, round_up). It pulls in no CUDA runtime. +# bzip2 / zlib / lz4 are dlopen'd at runtime by file_to_string.cpp, so they are +# header-only here too and deliberately absent from the link line. +# The OBJECT library needs these for their INTERFACE include dirs / defines at compile time. +target_link_libraries(cuopt_client_objs + PUBLIC + rapids_logger::rapids_logger + CCCL::CCCL + # Header-only here, exactly like CCCL: the client sources transitively include + # and via pdlp/solver_settings.hpp. + # No CUDA runtime is linked -- the device getters those headers declare only ever + # throw -- but without these the build relies on conda happening to put the headers on + # the default include path, and a CPM/fetched-rmm build fails to find them. + rmm::rmm + raft::raft + PRIVATE + simde::simde + OpenMP::OpenMP_CXX + $<$:protobuf::libprotobuf> + $<$:gRPC::grpc++> +) + +target_link_libraries(cuopt_client + PUBLIC + rapids_logger::rapids_logger + CCCL::CCCL + PRIVATE + simde::simde + OpenMP::OpenMP_CXX + ${CMAKE_DL_LIBS} + $<$:protobuf::libprotobuf> + $<$:gRPC::grpc++> +) + add_library(cuopt_objs OBJECT ${CUOPT_SRC_FILES} ) @@ -757,6 +893,7 @@ target_compile_definitions(cuopt_objs PUBLIC target_link_libraries(cuopt_objs PUBLIC + cuopt::cuopt_client CUDA::cublas CUDA::cusparse rmm::rmm @@ -777,7 +914,10 @@ target_link_libraries(cuopt_objs # - generate tests -------------------------------------------------------------------------------- if (BUILD_TESTS) include(CTest) - add_library(cuopt_static STATIC $) + # Embeds cuopt_client_objs directly rather than linking libcuopt_client.so: the internal + # test binaries reach parser internals that the shared library deliberately does not + # export. Do not also link cuopt::cuopt_client here -- that would duplicate every symbol. + add_library(cuopt_static STATIC $ $) target_link_libraries(cuopt_static PUBLIC CUDA::cublas @@ -839,6 +979,7 @@ target_include_directories(cuopt ) target_link_libraries(cuopt PUBLIC + cuopt::cuopt_client CUDA::cublas CUDA::cusparse rmm::rmm @@ -904,14 +1045,14 @@ else () endif () # adds the .so files to the runtime deb package -install(TARGETS cuopt +install(TARGETS cuopt cuopt_client DESTINATION ${_LIB_DEST} COMPONENT runtime EXPORT cuopt-exports ) # adds the .so files to the development deb package -install(TARGETS cuopt +install(TARGETS cuopt cuopt_client DESTINATION ${_LIB_DEST} COMPONENT dev ) @@ -939,7 +1080,7 @@ cuOpt library is a collection of GPU accelerated combinatorial optimization algo rapids_export(INSTALL cuopt EXPORT_SET cuopt-exports - GLOBAL_TARGETS cuopt + GLOBAL_TARGETS cuopt cuopt_client NAMESPACE cuopt:: DOCUMENTATION doc_string ) @@ -948,7 +1089,7 @@ rapids_export(INSTALL cuopt # - build export ------------------------------------------------------------------------------- rapids_export(BUILD cuopt EXPORT_SET cuopt-exports - GLOBAL_TARGETS cuopt + GLOBAL_TARGETS cuopt cuopt_client NAMESPACE cuopt:: DOCUMENTATION doc_string ) diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index e8737cf6da..e697f7aebc 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -4,11 +4,15 @@ # cmake-format: on set(UTIL_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/utilities/seed_generator.cu - ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logger.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/version_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/timestamp_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/work_unit_scheduler.cpp) +# logger.cpp backs , which both the parsers and the gRPC +# sources include, so it belongs to the CPU library. Without this, cuopt_client would +# have an undefined reference to the logger. +set(UTIL_CLIENT_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/utilities/logger.cpp) + add_subdirectory(linear_algebra) add_subdirectory(pdlp) add_subdirectory(math_optimization) @@ -26,4 +30,5 @@ add_subdirectory(branch_and_bound) add_subdirectory(cuts) set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${UTIL_SRC_FILES} PARENT_SCOPE) +set(CUOPT_CLIENT_SRC_FILES ${CUOPT_CLIENT_SRC_FILES} ${UTIL_CLIENT_SRC_FILES} PARENT_SCOPE) set(MPS_FAST_SRC_FILES ${MPS_FAST_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/io/CMakeLists.txt b/cpp/src/io/CMakeLists.txt index cafcffb23f..c234a2ff43 100644 --- a/cpp/src/io/CMakeLists.txt +++ b/cpp/src/io/CMakeLists.txt @@ -23,5 +23,10 @@ set(PARSERS_SRC_FILES ${MPS_FAST_SRC_FILES} ) -set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${PARSERS_SRC_FILES} PARENT_SCOPE) +# The parsers and the host-side problem representation are CUDA-free (the only +# `cuda::` uses are host integer helpers from header-only libcu++), so they build +# into the CPU-only cuopt_client library rather than into cuopt_objs. That is what +# lets the Python data_model / solver_settings / io extension modules link a +# library with no CUDA runtime dependency. +set(CUOPT_CLIENT_SRC_FILES ${CUOPT_CLIENT_SRC_FILES} ${PARSERS_SRC_FILES} PARENT_SCOPE) set(MPS_FAST_SRC_FILES ${MPS_FAST_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/math_optimization/CMakeLists.txt b/cpp/src/math_optimization/CMakeLists.txt index e6be4079d7..d68063e323 100644 --- a/cpp/src/math_optimization/CMakeLists.txt +++ b/cpp/src/math_optimization/CMakeLists.txt @@ -5,12 +5,20 @@ list(PREPEND MATH_OPT_SRC_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings_gpu.cu ${CMAKE_CURRENT_SOURCE_DIR}/solution_reader.cu ${CMAKE_CURRENT_SOURCE_DIR}/solution_writer.cu ${CMAKE_CURRENT_SOURCE_DIR}/tic_toc.cpp ) +# solver_settings_t is host-only apart from the device members in solver_settings_gpu.cu, +# so the bulk of it builds into cuopt_client. The gRPC client takes a solver_settings_t +# in its public API, so this is required for the client library to resolve standalone. +set(MATH_OPT_CLIENT_SRC_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cpp + ) + set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${MATH_OPT_SRC_FILES} PARENT_SCOPE) +set(CUOPT_CLIENT_SRC_FILES ${CUOPT_CLIENT_SRC_FILES} + ${MATH_OPT_CLIENT_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/math_optimization/solver_settings.cpp b/cpp/src/math_optimization/solver_settings.cpp index 34f1124313..d4e3586a57 100644 --- a/cpp/src/math_optimization/solver_settings.cpp +++ b/cpp/src/math_optimization/solver_settings.cpp @@ -79,160 +79,6 @@ bool string_to_bool(const std::string& value, bool& result) } // namespace -template -solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings() -{ - // clang-format off - // Float parameters - float_parameters = { - {CUOPT_TIME_LIMIT, &mip_settings.time_limit, f_t(0.0), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, - {CUOPT_TIME_LIMIT, &pdlp_settings.time_limit, f_t(0.0), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, - {CUOPT_WORK_LIMIT, &mip_settings.work_limit, f_t(0.0), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, - {CUOPT_ABSOLUTE_DUAL_TOLERANCE, &pdlp_settings.tolerances.absolute_dual_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_RELATIVE_DUAL_TOLERANCE, &pdlp_settings.tolerances.relative_dual_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_ABSOLUTE_PRIMAL_TOLERANCE, &pdlp_settings.tolerances.absolute_primal_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_RELATIVE_PRIMAL_TOLERANCE, &pdlp_settings.tolerances.relative_primal_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_ABSOLUTE_GAP_TOLERANCE, &pdlp_settings.tolerances.absolute_gap_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_RELATIVE_GAP_TOLERANCE, &pdlp_settings.tolerances.relative_gap_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_MIP_ABSOLUTE_TOLERANCE, &mip_settings.tolerances.absolute_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-6)}, - {CUOPT_MIP_RELATIVE_TOLERANCE, &mip_settings.tolerances.relative_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-12)}, - {CUOPT_MIP_INTEGRALITY_TOLERANCE, &mip_settings.tolerances.integrality_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-5)}, - {CUOPT_MIP_ABSOLUTE_GAP, &mip_settings.tolerances.absolute_mip_gap, f_t(0.0), std::numeric_limits::infinity(), std::max(f_t(1e-10), std::numeric_limits::epsilon())}, - {CUOPT_MIP_RELATIVE_GAP, &mip_settings.tolerances.relative_mip_gap, f_t(0.0), f_t(1e-1), f_t(1e-4)}, - {CUOPT_PRIMAL_INFEASIBLE_TOLERANCE, &pdlp_settings.tolerances.primal_infeasible_tolerance, f_t(0.0), f_t(1e-1), std::max(f_t(1e-10), std::numeric_limits::epsilon())}, - {CUOPT_DUAL_INFEASIBLE_TOLERANCE, &pdlp_settings.tolerances.dual_infeasible_tolerance, f_t(0.0), f_t(1e-1), std::max(f_t(1e-10), std::numeric_limits::epsilon())}, - {CUOPT_MIP_CUT_CHANGE_THRESHOLD, &mip_settings.cut_change_threshold, f_t(-1.0), std::numeric_limits::infinity(), f_t(-1.0)}, - {CUOPT_MIP_CUT_MIN_ORTHOGONALITY, &mip_settings.cut_min_orthogonality, f_t(0.0), f_t(1.0), f_t(0.5)}, - {CUOPT_BARRIER_STEP_SCALE, &pdlp_settings.barrier_step_scale, f_t(0.5), f_t(0.9999), f_t(0.9)}, - // MIP heuristic hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_TIME_RATIO, &mip_settings.heuristic_params.root_lp_time_ratio, f_t(0.0), f_t(1.0), f_t(0.1), "fraction of total time for root LP"}, - {CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_MAX_TIME, &mip_settings.heuristic_params.root_lp_max_time, f_t(0.0), std::numeric_limits::infinity(), f_t(15.0), "hard cap on root LP seconds"}, - {CUOPT_MIP_HYPER_HEURISTIC_RINS_TIME_LIMIT, &mip_settings.heuristic_params.rins_time_limit, f_t(0.0), std::numeric_limits::infinity(), f_t(3.0), "per-call RINS sub-MIP time"}, - {CUOPT_MIP_HYPER_HEURISTIC_RINS_MAX_TIME_LIMIT, &mip_settings.heuristic_params.rins_max_time_limit, f_t(0.0), std::numeric_limits::infinity(), f_t(20.0), "ceiling for RINS adaptive time budget"}, - {CUOPT_MIP_HYPER_HEURISTIC_RINS_FIX_RATE, &mip_settings.heuristic_params.rins_fix_rate, f_t(0.0), f_t(1.0), f_t(0.5), "RINS variable fix rate"}, - {CUOPT_MIP_HYPER_HEURISTIC_INITIAL_INFEASIBILITY_WEIGHT, &mip_settings.heuristic_params.initial_infeasibility_weight, f_t(1e-9), std::numeric_limits::infinity(), f_t(1000.0), "constraint violation penalty seed"}, - {CUOPT_MIP_HYPER_HEURISTIC_RELAXED_LP_TIME_LIMIT, &mip_settings.heuristic_params.relaxed_lp_time_limit, f_t(1e-9), std::numeric_limits::infinity(), f_t(1.0), "base relaxed LP time cap in heuristics"}, - {CUOPT_MIP_HYPER_HEURISTIC_RELATED_VARS_TIME_LIMIT, &mip_settings.heuristic_params.related_vars_time_limit, f_t(1e-9), std::numeric_limits::infinity(), f_t(30.0), "time for related-variable structure build"}, - {CUOPT_MIP_SEMICONTINUOUS_BIG_M, &mip_settings.semi_continuous_big_m, f_t(1.0), std::numeric_limits::infinity(), f_t(1e10), "big-M value for semi-continuous variables with no finite upper bound"}, - // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR, &mip_settings.diving_params.iteration_limit_factor, f_t(0.0), f_t(1.0), f_t(0.05), "fraction of best-first iterations allowed per dive"}, - // Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_SUBMIP_BASE_TARGET_FIXRATE, &mip_settings.submip_params.base_target_fixrate, f_t(0.0), f_t(1.0), f_t(0.6), "base target fix rate for the RINS neighbourhood"}, - {CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE, &mip_settings.submip_params.min_fixrate, f_t(0.0), f_t(1.0), f_t(0.25), "minimum fix rate for accepting the RINS neighbourhood"}, - {CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE_CAP, &mip_settings.submip_params.min_fixrate_cap, f_t(0.0), f_t(1.0), f_t(0.1), "hard cap on the minimum fix rate for solving a sub-MIP"}, - {CUOPT_MIP_HYPER_SUBMIP_TARGET_MIP_GAP, &mip_settings.submip_params.target_mip_gap, f_t(0.0), f_t(1.0), f_t(0.01), "MIP gap target for the sub-MIP"}, - {CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_RATIO, &mip_settings.submip_params.iteration_limit_ratio, f_t(0.0), f_t(1.0), f_t(0.8), "sub-MIP simplex-iteration limit as a factor of parent B&B iterations"}, - {CUOPT_MIP_HYPER_SUBMIP_ROUND_CLOSE_RATIO, &mip_settings.submip_params.round_close_ratio, f_t(0.0), f_t(1.0), f_t(0.8), "share of the still-unfixed integers left for later neighbourhood rounds (0 reaches the target fix rate in a single round)"}, - }; - - // Int parameters - // TODO should we have Stable2 and Methodolical1 here? - int_parameters = { - {CUOPT_ITERATION_LIMIT, &pdlp_settings.iteration_limit, 0, std::numeric_limits::max(), std::numeric_limits::max()}, - {CUOPT_NODE_LIMIT, &mip_settings.node_limit, 0, std::numeric_limits::max(), std::numeric_limits::max()}, - {CUOPT_PDLP_SOLVER_MODE, reinterpret_cast(&pdlp_settings.pdlp_solver_mode), CUOPT_PDLP_SOLVER_MODE_STABLE1, CUOPT_PDLP_SOLVER_MODE_STABLE3, CUOPT_PDLP_SOLVER_MODE_STABLE3}, - {CUOPT_METHOD, reinterpret_cast(&pdlp_settings.method), CUOPT_METHOD_CONCURRENT, CUOPT_METHOD_BARRIER, CUOPT_METHOD_CONCURRENT}, - {CUOPT_NUM_CPU_THREADS, &mip_settings.num_cpu_threads, -1, std::numeric_limits::max(), -1}, - {CUOPT_AUGMENTED, &pdlp_settings.augmented, -1, 1, -1}, - {CUOPT_FOLDING, &pdlp_settings.folding, -1, 1, -1}, - {CUOPT_DUALIZE, &pdlp_settings.dualize, -1, 1, -1}, - {CUOPT_ORDERING, &pdlp_settings.ordering, -1, 1, -1}, - {CUOPT_BARRIER_DUAL_INITIAL_POINT, &pdlp_settings.barrier_dual_initial_point, -1, 1, -1}, - {CUOPT_POSTSOLVE_INFO, &pdlp_settings.postsolve_info, -1, 1, -1}, - {CUOPT_MIP_CUT_PASSES, &mip_settings.max_cut_passes, -1, std::numeric_limits::max(), 10}, - {CUOPT_MIP_MIXED_INTEGER_ROUNDING_CUTS, &mip_settings.mir_cuts, -1, 1, -1}, - {CUOPT_MIP_MIXED_INTEGER_GOMORY_CUTS, &mip_settings.mixed_integer_gomory_cuts, -1, 1, -1}, - {CUOPT_MIP_KNAPSACK_CUTS, &mip_settings.knapsack_cuts, -1, 1, -1}, - {CUOPT_MIP_FLOW_COVER_CUTS, &mip_settings.flow_cover_cuts, -1, 1, -1}, - {CUOPT_MIP_CLIQUE_CUTS, &mip_settings.clique_cuts, -1, 1, -1}, - {CUOPT_MIP_ZERO_HALF_CUTS, &mip_settings.zero_half_cuts, -1, 1, -1}, - {CUOPT_MIP_IMPLIED_BOUND_CUTS, &mip_settings.implied_bound_cuts, -1, 1, -1}, - {CUOPT_MIP_STRONG_CHVATAL_GOMORY_CUTS, &mip_settings.strong_chvatal_gomory_cuts, -1, 1, -1}, - {CUOPT_MIP_REDUCED_COST_STRENGTHENING, &mip_settings.reduced_cost_strengthening, -1, std::numeric_limits::max(), -1}, - {CUOPT_MIP_RINS, &mip_settings.submip_params.rins, -1, 1, -1}, - {CUOPT_MIP_RENS, &mip_settings.submip_params.rens, -1, 1, -1}, - {CUOPT_MIP_OBJECTIVE_STEP, &mip_settings.objective_step, 0, 1, 1}, - {CUOPT_NUM_GPUS, &pdlp_settings.num_gpus, -1, 72, 1}, - {CUOPT_NUM_GPUS, &mip_settings.num_gpus, -1, 72, 1}, - {CUOPT_MIP_BATCH_PDLP_STRONG_BRANCHING, &mip_settings.mip_batch_pdlp_strong_branching, 0, 2, 0}, - {CUOPT_MIP_BATCH_PDLP_RELIABILITY_BRANCHING, &mip_settings.mip_batch_pdlp_reliability_branching, 0, 2, 0}, - {CUOPT_MIP_STRONG_BRANCHING_SIMPLEX_ITERATION_LIMIT, &mip_settings.strong_branching_simplex_iteration_limit, -1,std::numeric_limits::max(), -1}, - {CUOPT_PRESOLVE, reinterpret_cast(&pdlp_settings.presolver), CUOPT_PRESOLVE_DEFAULT, CUOPT_PRESOLVE_PSLP, CUOPT_PRESOLVE_DEFAULT}, - {CUOPT_PRESOLVE, reinterpret_cast(&mip_settings.presolver), CUOPT_PRESOLVE_DEFAULT, CUOPT_PRESOLVE_PSLP, CUOPT_PRESOLVE_DEFAULT}, - {CUOPT_DISTRIBUTED_PDLP_PARTITIONER, reinterpret_cast(&pdlp_settings.distributed_pdlp_partitioner), CUOPT_DISTRIBUTED_PDLP_PARTITIONER_AUTO, CUOPT_DISTRIBUTED_PDLP_PARTITIONER_ROUND_ROBIN, CUOPT_DISTRIBUTED_PDLP_PARTITIONER_AUTO}, - {CUOPT_MIP_DETERMINISM_MODE, &mip_settings.determinism_mode, CUOPT_MODE_OPPORTUNISTIC, CUOPT_MODE_DETERMINISTIC, CUOPT_MODE_OPPORTUNISTIC}, - {CUOPT_RANDOM_SEED, &mip_settings.seed, -1, std::numeric_limits::max(), -1}, - {CUOPT_MIP_RELIABILITY_BRANCHING, &mip_settings.reliability_branching, -1, std::numeric_limits::max(), -1}, - {CUOPT_PDLP_PRECISION, reinterpret_cast(&pdlp_settings.pdlp_precision), CUOPT_PDLP_DEFAULT_PRECISION, CUOPT_PDLP_MIXED_PRECISION, CUOPT_PDLP_DEFAULT_PRECISION}, - {CUOPT_MIP_SYMMETRY, &mip_settings.symmetry, -1, 2, -1}, - {CUOPT_MIP_SCALING, &mip_settings.mip_scaling, CUOPT_MIP_SCALING_OFF, CUOPT_MIP_SCALING_NO_OBJECTIVE, CUOPT_MIP_SCALING_NO_OBJECTIVE}, - // MIP heuristic hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_HEURISTIC_POPULATION_SIZE, &mip_settings.heuristic_params.population_size, 1, std::numeric_limits::max(), 32, "max solutions in pool"}, - {CUOPT_MIP_HYPER_HEURISTIC_NUM_CPUFJ_THREADS, &mip_settings.heuristic_params.num_cpufj_threads, 0, std::numeric_limits::max(), 8, "parallel CPU FJ climbers"}, - {CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_MAX_ROUNDS, &mip_settings.heuristic_params.presolve_max_rounds, -1, std::numeric_limits::max(), -1, "Papilo presolve rounds cap (<0 derives it from the problem, 0 keeps Papilo default)"}, - {CUOPT_MIP_HYPER_HEURISTIC_PAPILO_PROBING_MAX_BADGESIZE, &mip_settings.heuristic_params.papilo_probing_max_badgesize, -1, std::numeric_limits::max(), -1, "ceiling on Papilo probing.minbadgesize (<0 derives it from the problem, 0 leaves it uncapped)"}, - {CUOPT_MIP_HYPER_HEURISTIC_STAGNATION_TRIGGER, &mip_settings.heuristic_params.stagnation_trigger, 1, std::numeric_limits::max(), 3, "FP loops w/o improvement before recombination"}, - {CUOPT_MIP_HYPER_HEURISTIC_MAX_ITERS_WITHOUT_IMPROVEMENT, &mip_settings.heuristic_params.max_iterations_without_improvement, 1, std::numeric_limits::max(), 8, "diversity step depth after stagnation"}, - {CUOPT_MIP_HYPER_HEURISTIC_N_OF_MINIMUMS_FOR_EXIT, &mip_settings.heuristic_params.n_of_minimums_for_exit, 1, std::numeric_limits::max(), 7000, "FJ baseline local-minima exit threshold"}, - {CUOPT_MIP_HYPER_HEURISTIC_ENABLED_RECOMBINERS, &mip_settings.heuristic_params.enabled_recombiners, 0, 15, 15, "bitmask: 1=BP 2=FP 4=LS 8=SubMIP"}, - {CUOPT_MIP_HYPER_HEURISTIC_CYCLE_DETECTION_LENGTH, &mip_settings.heuristic_params.cycle_detection_length, 1, std::numeric_limits::max(), 30, "FP assignment cycle ring buffer length"}, - // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_DIVING_LINE_SEARCH, &mip_settings.diving_params.line_search_diving, -1, 1, -1, "line-search diving toggle: -1 automatic, 0 disabled, 1 enabled"}, - {CUOPT_MIP_HYPER_DIVING_PSEUDOCOST, &mip_settings.diving_params.pseudocost_diving, -1, 1, -1, "pseudocost diving toggle: -1 automatic, 0 disabled, 1 enabled"}, - {CUOPT_MIP_HYPER_DIVING_GUIDED, &mip_settings.diving_params.guided_diving, -1, 1, -1, "guided diving toggle: -1 automatic, 0 disabled, 1 enabled"}, - {CUOPT_MIP_HYPER_DIVING_COEFFICIENT, &mip_settings.diving_params.coefficient_diving, -1, 1, -1, "coefficient diving toggle: -1 automatic, 0 disabled, 1 enabled"}, - {CUOPT_MIP_HYPER_DIVING_FARKAS, &mip_settings.diving_params.farkas_diving, -1, 1, -1, "Farkas diving toggle: -1 automatic, 0 disabled, 1 enabled"}, - {CUOPT_MIP_HYPER_DIVING_VECTOR_LENGTH, &mip_settings.diving_params.vector_length_diving, -1, 1, -1, "vector-length diving toggle: -1 automatic, 0 disabled, 1 enabled"}, - {CUOPT_MIP_HYPER_DIVING_MIN_NODE_DEPTH, &mip_settings.diving_params.min_node_depth, 0, std::numeric_limits::max(), 10, "minimum depth at which to start diving"}, - {CUOPT_MIP_HYPER_DIVING_NODE_LIMIT, &mip_settings.diving_params.node_limit, 0, std::numeric_limits::max(), 500, "maximum nodes explored per dive"}, - {CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT, &mip_settings.diving_params.backtrack_limit, 0, std::numeric_limits::max(), 5, "maximum backtracking allowed per dive"}, - // Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_OFFSET, &mip_settings.submip_params.node_limit_offset, 0, std::numeric_limits::max(), 200, "base node limit for the sub-MIP"}, - {CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_OFFSET, &mip_settings.submip_params.iteration_limit_offset, 0, std::numeric_limits::max(), 10000, "base sub-MIP simplex-iteration limit for root heuristics"}, - {CUOPT_MIP_HYPER_SUBMIP_MAX_LEVEL, &mip_settings.submip_params.max_level, 0, std::numeric_limits::max(), 10, "maximum sub-MIP recursion level"}, - {CUOPT_BARRIER_PRESOLVE_BOUND_FREE_VARIABLES, &pdlp_settings.barrier_presolve_bound_free_variables, -1, 1, -1, "Bound free variables during barrier presolve: -1 automatic (default behavior), 0 disabled, 1 enabled"}, - {CUOPT_BARRIER_ADAPTIVE_REGULARIZATION, &pdlp_settings.barrier_adaptive_regularization, -1, 1, -1, "Adaptive regularization for barrier method: -1 automatic (default behavior), 0 disabled, 1 enabled"}, - // QCQP (barrier) scaling hyper-parameter - {CUOPT_QCQP_HYPER_RUIZ_EQUILIBRATION, &pdlp_settings.qcqp_ruiz_equilibration, -1, 1, -1, "Ruiz equilibration for QCQP barrier scaling: -1 automatic (row/column imbalance heuristic), 0 disabled, 1 enabled"}, - }; - - // Bool parameters - bool_parameters = { - {CUOPT_INFEASIBILITY_DETECTION, &pdlp_settings.detect_infeasibility, false}, - {CUOPT_STRICT_INFEASIBILITY, &pdlp_settings.strict_infeasibility, false}, - {CUOPT_PER_CONSTRAINT_RESIDUAL, &pdlp_settings.per_constraint_residual, false}, - {CUOPT_SAVE_BEST_PRIMAL_SO_FAR, &pdlp_settings.save_best_primal_so_far, false}, - {CUOPT_FIRST_PRIMAL_FEASIBLE, &pdlp_settings.first_primal_feasible, false}, - {CUOPT_MIP_HEURISTICS_ONLY, &mip_settings.heuristics_only, false}, - {CUOPT_LOG_TO_CONSOLE, &pdlp_settings.log_to_console, true}, - {CUOPT_LOG_TO_CONSOLE, &mip_settings.log_to_console, true}, - {CUOPT_CROSSOVER, &pdlp_settings.crossover, false}, - {CUOPT_ELIMINATE_DENSE_COLUMNS, &pdlp_settings.eliminate_dense_columns, true}, - {CUOPT_CUDSS_DETERMINISTIC, &pdlp_settings.cudss_deterministic, false}, - {CUOPT_DUAL_POSTSOLVE, &pdlp_settings.dual_postsolve, true}, - {CUOPT_BARRIER_ITERATIVE_REFINEMENT, &pdlp_settings.barrier_iterative_refinement, true}, - {CUOPT_MIP_PROBING, &mip_settings.probing, true}, - {CUOPT_USE_DISTRIBUTED_PDLP, &pdlp_settings.use_distributed_pdlp, false}, - // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_DIVING_SHOW_TYPE, &mip_settings.diving_params.show_type, false, "log diving heuristic type when it finds a new incumbent"}, - // Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_") - {CUOPT_MIP_HYPER_SUBMIP_ENABLE_CPUFJ, &mip_settings.submip_params.enable_cpufj, true, "run CPU FJ over the sub-MIP"}, - {CUOPT_MIP_HYPER_BLOCK_BVE, &mip_settings.block_bve, true, "eliminate blocks of binaries in cuOpt's MIP presolve (needs " CUOPT_MIP_PROBING ")"}, - }; - // String parameters - string_parameters = { - {CUOPT_LOG_FILE, &mip_settings.log_file, ""}, - {CUOPT_LOG_FILE, &pdlp_settings.log_file, ""}, - {CUOPT_SOLUTION_FILE, &mip_settings.sol_file, ""}, - {CUOPT_SOLUTION_FILE, &pdlp_settings.sol_file, ""}, - {CUOPT_USER_PROBLEM_FILE, &mip_settings.user_problem_file, ""}, - {CUOPT_USER_PROBLEM_FILE, &pdlp_settings.user_problem_file, ""}, - {CUOPT_PRESOLVE_FILE, &mip_settings.presolve_file, ""}, - {CUOPT_PRESOLVE_FILE, &pdlp_settings.presolve_file, ""}, - }; - // clang-format on -} - template void solver_settings_t::set_parameter_from_string(const std::string& name, const std::string& value) @@ -595,8 +441,45 @@ bool solver_settings_t::dump_parameters_to_file(const std::string& pat return true; } +// NOTE: deliberately no `template class solver_settings_t<...>` here. +// +// That would instantiate every member, including the implicitly-defined constructor and +// copy constructor. Those construct a pdlp_solver_settings_t, which holds a +// pdlp_warm_start_data_t by value, whose default ctor lives in a CUDA translation unit -- +// so the whole class instantiation drags a CUDA dependency into this CUDA-free library and +// leaves libcuopt_client.so with an undefined symbol. Members are therefore instantiated +// individually below; libcuopt emits the constructors via its own `template class` +// in solver_settings_gpu.cu. + #if MIP_INSTANTIATE_FLOAT -template class CUOPT_EXPORT solver_settings_t; +template CUOPT_EXPORT void solver_settings_t::set_parameter_from_string( + const std::string&, const std::string&); +template CUOPT_EXPORT std::string solver_settings_t::get_parameter_as_string( + const std::string&) const; +template CUOPT_EXPORT void solver_settings_t::set_mip_callback( + internals::base_solution_callback_t*, void*); +template CUOPT_EXPORT const std::vector +solver_settings_t::get_mip_callbacks() const; +template CUOPT_EXPORT pdlp_solver_settings_t& +solver_settings_t::get_pdlp_settings(); +template CUOPT_EXPORT mip_solver_settings_t& +solver_settings_t::get_mip_settings(); +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_float_parameters() const; +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_int_parameters() const; +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_bool_parameters() const; +template CUOPT_EXPORT const std::vector +solver_settings_t::get_parameter_names() const; +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_string_parameters() const; +template CUOPT_EXPORT const pdlp_warm_start_data_view_t& +solver_settings_t::get_pdlp_warm_start_data_view() const noexcept; +template CUOPT_EXPORT void solver_settings_t::load_parameters_from_file( + const std::string&); +template CUOPT_EXPORT bool solver_settings_t::dump_parameters_to_file( + const std::string&, bool) const; template CUOPT_EXPORT void solver_settings_t::set_parameter(const std::string& name, int value); template CUOPT_EXPORT void solver_settings_t::set_parameter(const std::string& name, @@ -614,7 +497,34 @@ template CUOPT_EXPORT std::string solver_settings_t::get_parameter( #endif #if MIP_INSTANTIATE_DOUBLE -template class CUOPT_EXPORT solver_settings_t; +template CUOPT_EXPORT void solver_settings_t::set_parameter_from_string( + const std::string&, const std::string&); +template CUOPT_EXPORT std::string solver_settings_t::get_parameter_as_string( + const std::string&) const; +template CUOPT_EXPORT void solver_settings_t::set_mip_callback( + internals::base_solution_callback_t*, void*); +template CUOPT_EXPORT const std::vector +solver_settings_t::get_mip_callbacks() const; +template CUOPT_EXPORT pdlp_solver_settings_t& +solver_settings_t::get_pdlp_settings(); +template CUOPT_EXPORT mip_solver_settings_t& +solver_settings_t::get_mip_settings(); +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_float_parameters() const; +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_int_parameters() const; +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_bool_parameters() const; +template CUOPT_EXPORT const std::vector +solver_settings_t::get_parameter_names() const; +template CUOPT_EXPORT const std::vector>& +solver_settings_t::get_string_parameters() const; +template CUOPT_EXPORT const pdlp_warm_start_data_view_t& +solver_settings_t::get_pdlp_warm_start_data_view() const noexcept; +template CUOPT_EXPORT void solver_settings_t::load_parameters_from_file( + const std::string&); +template CUOPT_EXPORT bool solver_settings_t::dump_parameters_to_file( + const std::string&, bool) const; template CUOPT_EXPORT void solver_settings_t::set_parameter(const std::string& name, int value); template CUOPT_EXPORT void solver_settings_t::set_parameter(const std::string& name, diff --git a/cpp/src/math_optimization/solver_settings_gpu.cu b/cpp/src/math_optimization/solver_settings_gpu.cu index a23fbf104a..39c3ba1103 100644 --- a/cpp/src/math_optimization/solver_settings_gpu.cu +++ b/cpp/src/math_optimization/solver_settings_gpu.cu @@ -12,8 +12,10 @@ // members take an rmm::cuda_stream_view or hand back a device_uvector, so they are the // only ones that must stay in a CUDA TU inside libcuopt. // -// The `template class` instantiation in solver_settings.cpp cannot emit these members -// (their definitions are not visible there), so they are instantiated explicitly below. +// solver_settings.cpp deliberately has no `template class` at all -- that would instantiate +// the constructor, which needs CUDA (see the note there) -- so it cannot emit these members +// either. The `template class` below covers the class as a whole for libcuopt; members +// defined in the CUDA-free TU are instantiated individually there. #include @@ -105,76 +107,175 @@ void solver_settings_t::add_initial_mip_solution(const f_t* solution, mip_settings.add_initial_solution(solution, size, stream); } +// The constructor lives here, not in solver_settings.cpp, even though its body is pure +// host code building the parameter tables. It default-constructs pdlp_settings, which holds +// a pdlp_warm_start_data_t by value whose ctor is CUDA-side -- so defining it in the +// CUDA-free library would leave libcuopt_client.so with an undefined symbol that only +// surfaces at call time. Moving it here is what lets that library resolve standalone. +template +solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings() +{ + // clang-format off + // Float parameters + float_parameters = { + {CUOPT_TIME_LIMIT, &mip_settings.time_limit, f_t(0.0), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, + {CUOPT_TIME_LIMIT, &pdlp_settings.time_limit, f_t(0.0), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, + {CUOPT_WORK_LIMIT, &mip_settings.work_limit, f_t(0.0), std::numeric_limits::infinity(), std::numeric_limits::infinity()}, + {CUOPT_ABSOLUTE_DUAL_TOLERANCE, &pdlp_settings.tolerances.absolute_dual_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_RELATIVE_DUAL_TOLERANCE, &pdlp_settings.tolerances.relative_dual_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_ABSOLUTE_PRIMAL_TOLERANCE, &pdlp_settings.tolerances.absolute_primal_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_RELATIVE_PRIMAL_TOLERANCE, &pdlp_settings.tolerances.relative_primal_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_ABSOLUTE_GAP_TOLERANCE, &pdlp_settings.tolerances.absolute_gap_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_RELATIVE_GAP_TOLERANCE, &pdlp_settings.tolerances.relative_gap_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_MIP_ABSOLUTE_TOLERANCE, &mip_settings.tolerances.absolute_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-6)}, + {CUOPT_MIP_RELATIVE_TOLERANCE, &mip_settings.tolerances.relative_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-12)}, + {CUOPT_MIP_INTEGRALITY_TOLERANCE, &mip_settings.tolerances.integrality_tolerance, f_t(0.0), f_t(1e-1), f_t(1e-5)}, + {CUOPT_MIP_ABSOLUTE_GAP, &mip_settings.tolerances.absolute_mip_gap, f_t(0.0), std::numeric_limits::infinity(), std::max(f_t(1e-10), std::numeric_limits::epsilon())}, + {CUOPT_MIP_RELATIVE_GAP, &mip_settings.tolerances.relative_mip_gap, f_t(0.0), f_t(1e-1), f_t(1e-4)}, + {CUOPT_PRIMAL_INFEASIBLE_TOLERANCE, &pdlp_settings.tolerances.primal_infeasible_tolerance, f_t(0.0), f_t(1e-1), std::max(f_t(1e-10), std::numeric_limits::epsilon())}, + {CUOPT_DUAL_INFEASIBLE_TOLERANCE, &pdlp_settings.tolerances.dual_infeasible_tolerance, f_t(0.0), f_t(1e-1), std::max(f_t(1e-10), std::numeric_limits::epsilon())}, + {CUOPT_MIP_CUT_CHANGE_THRESHOLD, &mip_settings.cut_change_threshold, f_t(-1.0), std::numeric_limits::infinity(), f_t(-1.0)}, + {CUOPT_MIP_CUT_MIN_ORTHOGONALITY, &mip_settings.cut_min_orthogonality, f_t(0.0), f_t(1.0), f_t(0.5)}, + {CUOPT_BARRIER_STEP_SCALE, &pdlp_settings.barrier_step_scale, f_t(0.5), f_t(0.9999), f_t(0.9)}, + // MIP heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_TIME_RATIO, &mip_settings.heuristic_params.root_lp_time_ratio, f_t(0.0), f_t(1.0), f_t(0.1), "fraction of total time for root LP"}, + {CUOPT_MIP_HYPER_HEURISTIC_ROOT_LP_MAX_TIME, &mip_settings.heuristic_params.root_lp_max_time, f_t(0.0), std::numeric_limits::infinity(), f_t(15.0), "hard cap on root LP seconds"}, + {CUOPT_MIP_HYPER_HEURISTIC_RINS_TIME_LIMIT, &mip_settings.heuristic_params.rins_time_limit, f_t(0.0), std::numeric_limits::infinity(), f_t(3.0), "per-call RINS sub-MIP time"}, + {CUOPT_MIP_HYPER_HEURISTIC_RINS_MAX_TIME_LIMIT, &mip_settings.heuristic_params.rins_max_time_limit, f_t(0.0), std::numeric_limits::infinity(), f_t(20.0), "ceiling for RINS adaptive time budget"}, + {CUOPT_MIP_HYPER_HEURISTIC_RINS_FIX_RATE, &mip_settings.heuristic_params.rins_fix_rate, f_t(0.0), f_t(1.0), f_t(0.5), "RINS variable fix rate"}, + {CUOPT_MIP_HYPER_HEURISTIC_INITIAL_INFEASIBILITY_WEIGHT, &mip_settings.heuristic_params.initial_infeasibility_weight, f_t(1e-9), std::numeric_limits::infinity(), f_t(1000.0), "constraint violation penalty seed"}, + {CUOPT_MIP_HYPER_HEURISTIC_RELAXED_LP_TIME_LIMIT, &mip_settings.heuristic_params.relaxed_lp_time_limit, f_t(1e-9), std::numeric_limits::infinity(), f_t(1.0), "base relaxed LP time cap in heuristics"}, + {CUOPT_MIP_HYPER_HEURISTIC_RELATED_VARS_TIME_LIMIT, &mip_settings.heuristic_params.related_vars_time_limit, f_t(1e-9), std::numeric_limits::infinity(), f_t(30.0), "time for related-variable structure build"}, + {CUOPT_MIP_SEMICONTINUOUS_BIG_M, &mip_settings.semi_continuous_big_m, f_t(1.0), std::numeric_limits::infinity(), f_t(1e10), "big-M value for semi-continuous variables with no finite upper bound"}, + // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_DIVING_ITERATION_LIMIT_FACTOR, &mip_settings.diving_params.iteration_limit_factor, f_t(0.0), f_t(1.0), f_t(0.05), "fraction of best-first iterations allowed per dive"}, + // Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_SUBMIP_BASE_TARGET_FIXRATE, &mip_settings.submip_params.base_target_fixrate, f_t(0.0), f_t(1.0), f_t(0.6), "base target fix rate for the RINS neighbourhood"}, + {CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE, &mip_settings.submip_params.min_fixrate, f_t(0.0), f_t(1.0), f_t(0.25), "minimum fix rate for accepting the RINS neighbourhood"}, + {CUOPT_MIP_HYPER_SUBMIP_MIN_FIXRATE_CAP, &mip_settings.submip_params.min_fixrate_cap, f_t(0.0), f_t(1.0), f_t(0.1), "hard cap on the minimum fix rate for solving a sub-MIP"}, + {CUOPT_MIP_HYPER_SUBMIP_TARGET_MIP_GAP, &mip_settings.submip_params.target_mip_gap, f_t(0.0), f_t(1.0), f_t(0.01), "MIP gap target for the sub-MIP"}, + {CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_RATIO, &mip_settings.submip_params.iteration_limit_ratio, f_t(0.0), f_t(1.0), f_t(0.8), "sub-MIP simplex-iteration limit as a factor of parent B&B iterations"}, + {CUOPT_MIP_HYPER_SUBMIP_ROUND_CLOSE_RATIO, &mip_settings.submip_params.round_close_ratio, f_t(0.0), f_t(1.0), f_t(0.8), "share of the still-unfixed integers left for later neighbourhood rounds (0 reaches the target fix rate in a single round)"}, + }; + + // Int parameters + // TODO should we have Stable2 and Methodolical1 here? + int_parameters = { + {CUOPT_ITERATION_LIMIT, &pdlp_settings.iteration_limit, 0, std::numeric_limits::max(), std::numeric_limits::max()}, + {CUOPT_NODE_LIMIT, &mip_settings.node_limit, 0, std::numeric_limits::max(), std::numeric_limits::max()}, + {CUOPT_PDLP_SOLVER_MODE, reinterpret_cast(&pdlp_settings.pdlp_solver_mode), CUOPT_PDLP_SOLVER_MODE_STABLE1, CUOPT_PDLP_SOLVER_MODE_STABLE3, CUOPT_PDLP_SOLVER_MODE_STABLE3}, + {CUOPT_METHOD, reinterpret_cast(&pdlp_settings.method), CUOPT_METHOD_CONCURRENT, CUOPT_METHOD_BARRIER, CUOPT_METHOD_CONCURRENT}, + {CUOPT_NUM_CPU_THREADS, &mip_settings.num_cpu_threads, -1, std::numeric_limits::max(), -1}, + {CUOPT_AUGMENTED, &pdlp_settings.augmented, -1, 1, -1}, + {CUOPT_FOLDING, &pdlp_settings.folding, -1, 1, -1}, + {CUOPT_DUALIZE, &pdlp_settings.dualize, -1, 1, -1}, + {CUOPT_ORDERING, &pdlp_settings.ordering, -1, 1, -1}, + {CUOPT_BARRIER_DUAL_INITIAL_POINT, &pdlp_settings.barrier_dual_initial_point, -1, 1, -1}, + {CUOPT_POSTSOLVE_INFO, &pdlp_settings.postsolve_info, -1, 1, -1}, + {CUOPT_MIP_CUT_PASSES, &mip_settings.max_cut_passes, -1, std::numeric_limits::max(), 10}, + {CUOPT_MIP_MIXED_INTEGER_ROUNDING_CUTS, &mip_settings.mir_cuts, -1, 1, -1}, + {CUOPT_MIP_MIXED_INTEGER_GOMORY_CUTS, &mip_settings.mixed_integer_gomory_cuts, -1, 1, -1}, + {CUOPT_MIP_KNAPSACK_CUTS, &mip_settings.knapsack_cuts, -1, 1, -1}, + {CUOPT_MIP_FLOW_COVER_CUTS, &mip_settings.flow_cover_cuts, -1, 1, -1}, + {CUOPT_MIP_CLIQUE_CUTS, &mip_settings.clique_cuts, -1, 1, -1}, + {CUOPT_MIP_ZERO_HALF_CUTS, &mip_settings.zero_half_cuts, -1, 1, -1}, + {CUOPT_MIP_IMPLIED_BOUND_CUTS, &mip_settings.implied_bound_cuts, -1, 1, -1}, + {CUOPT_MIP_STRONG_CHVATAL_GOMORY_CUTS, &mip_settings.strong_chvatal_gomory_cuts, -1, 1, -1}, + {CUOPT_MIP_REDUCED_COST_STRENGTHENING, &mip_settings.reduced_cost_strengthening, -1, std::numeric_limits::max(), -1}, + {CUOPT_MIP_RINS, &mip_settings.submip_params.rins, -1, 1, -1}, + {CUOPT_MIP_RENS, &mip_settings.submip_params.rens, -1, 1, -1}, + {CUOPT_MIP_OBJECTIVE_STEP, &mip_settings.objective_step, 0, 1, 1}, + {CUOPT_NUM_GPUS, &pdlp_settings.num_gpus, -1, 72, 1}, + {CUOPT_NUM_GPUS, &mip_settings.num_gpus, -1, 72, 1}, + {CUOPT_MIP_BATCH_PDLP_STRONG_BRANCHING, &mip_settings.mip_batch_pdlp_strong_branching, 0, 2, 0}, + {CUOPT_MIP_BATCH_PDLP_RELIABILITY_BRANCHING, &mip_settings.mip_batch_pdlp_reliability_branching, 0, 2, 0}, + {CUOPT_MIP_STRONG_BRANCHING_SIMPLEX_ITERATION_LIMIT, &mip_settings.strong_branching_simplex_iteration_limit, -1,std::numeric_limits::max(), -1}, + {CUOPT_PRESOLVE, reinterpret_cast(&pdlp_settings.presolver), CUOPT_PRESOLVE_DEFAULT, CUOPT_PRESOLVE_PSLP, CUOPT_PRESOLVE_DEFAULT}, + {CUOPT_PRESOLVE, reinterpret_cast(&mip_settings.presolver), CUOPT_PRESOLVE_DEFAULT, CUOPT_PRESOLVE_PSLP, CUOPT_PRESOLVE_DEFAULT}, + {CUOPT_DISTRIBUTED_PDLP_PARTITIONER, reinterpret_cast(&pdlp_settings.distributed_pdlp_partitioner), CUOPT_DISTRIBUTED_PDLP_PARTITIONER_AUTO, CUOPT_DISTRIBUTED_PDLP_PARTITIONER_ROUND_ROBIN, CUOPT_DISTRIBUTED_PDLP_PARTITIONER_AUTO}, + {CUOPT_MIP_DETERMINISM_MODE, &mip_settings.determinism_mode, CUOPT_MODE_OPPORTUNISTIC, CUOPT_MODE_DETERMINISTIC, CUOPT_MODE_OPPORTUNISTIC}, + {CUOPT_RANDOM_SEED, &mip_settings.seed, -1, std::numeric_limits::max(), -1}, + {CUOPT_MIP_RELIABILITY_BRANCHING, &mip_settings.reliability_branching, -1, std::numeric_limits::max(), -1}, + {CUOPT_PDLP_PRECISION, reinterpret_cast(&pdlp_settings.pdlp_precision), CUOPT_PDLP_DEFAULT_PRECISION, CUOPT_PDLP_MIXED_PRECISION, CUOPT_PDLP_DEFAULT_PRECISION}, + {CUOPT_MIP_SYMMETRY, &mip_settings.symmetry, -1, 2, -1}, + {CUOPT_MIP_SCALING, &mip_settings.mip_scaling, CUOPT_MIP_SCALING_OFF, CUOPT_MIP_SCALING_NO_OBJECTIVE, CUOPT_MIP_SCALING_NO_OBJECTIVE}, + // MIP heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_HEURISTIC_POPULATION_SIZE, &mip_settings.heuristic_params.population_size, 1, std::numeric_limits::max(), 32, "max solutions in pool"}, + {CUOPT_MIP_HYPER_HEURISTIC_NUM_CPUFJ_THREADS, &mip_settings.heuristic_params.num_cpufj_threads, 0, std::numeric_limits::max(), 8, "parallel CPU FJ climbers"}, + {CUOPT_MIP_HYPER_HEURISTIC_PRESOLVE_MAX_ROUNDS, &mip_settings.heuristic_params.presolve_max_rounds, -1, std::numeric_limits::max(), -1, "Papilo presolve rounds cap (<0 derives it from the problem, 0 keeps Papilo default)"}, + {CUOPT_MIP_HYPER_HEURISTIC_PAPILO_PROBING_MAX_BADGESIZE, &mip_settings.heuristic_params.papilo_probing_max_badgesize, -1, std::numeric_limits::max(), -1, "ceiling on Papilo probing.minbadgesize (<0 derives it from the problem, 0 leaves it uncapped)"}, + {CUOPT_MIP_HYPER_HEURISTIC_STAGNATION_TRIGGER, &mip_settings.heuristic_params.stagnation_trigger, 1, std::numeric_limits::max(), 3, "FP loops w/o improvement before recombination"}, + {CUOPT_MIP_HYPER_HEURISTIC_MAX_ITERS_WITHOUT_IMPROVEMENT, &mip_settings.heuristic_params.max_iterations_without_improvement, 1, std::numeric_limits::max(), 8, "diversity step depth after stagnation"}, + {CUOPT_MIP_HYPER_HEURISTIC_N_OF_MINIMUMS_FOR_EXIT, &mip_settings.heuristic_params.n_of_minimums_for_exit, 1, std::numeric_limits::max(), 7000, "FJ baseline local-minima exit threshold"}, + {CUOPT_MIP_HYPER_HEURISTIC_ENABLED_RECOMBINERS, &mip_settings.heuristic_params.enabled_recombiners, 0, 15, 15, "bitmask: 1=BP 2=FP 4=LS 8=SubMIP"}, + {CUOPT_MIP_HYPER_HEURISTIC_CYCLE_DETECTION_LENGTH, &mip_settings.heuristic_params.cycle_detection_length, 1, std::numeric_limits::max(), 30, "FP assignment cycle ring buffer length"}, + // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_DIVING_LINE_SEARCH, &mip_settings.diving_params.line_search_diving, -1, 1, -1, "line-search diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_PSEUDOCOST, &mip_settings.diving_params.pseudocost_diving, -1, 1, -1, "pseudocost diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_GUIDED, &mip_settings.diving_params.guided_diving, -1, 1, -1, "guided diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_COEFFICIENT, &mip_settings.diving_params.coefficient_diving, -1, 1, -1, "coefficient diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_FARKAS, &mip_settings.diving_params.farkas_diving, -1, 1, -1, "Farkas diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_VECTOR_LENGTH, &mip_settings.diving_params.vector_length_diving, -1, 1, -1, "vector-length diving toggle: -1 automatic, 0 disabled, 1 enabled"}, + {CUOPT_MIP_HYPER_DIVING_MIN_NODE_DEPTH, &mip_settings.diving_params.min_node_depth, 0, std::numeric_limits::max(), 10, "minimum depth at which to start diving"}, + {CUOPT_MIP_HYPER_DIVING_NODE_LIMIT, &mip_settings.diving_params.node_limit, 0, std::numeric_limits::max(), 500, "maximum nodes explored per dive"}, + {CUOPT_MIP_HYPER_DIVING_BACKTRACK_LIMIT, &mip_settings.diving_params.backtrack_limit, 0, std::numeric_limits::max(), 5, "maximum backtracking allowed per dive"}, + // Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_SUBMIP_NODE_LIMIT_OFFSET, &mip_settings.submip_params.node_limit_offset, 0, std::numeric_limits::max(), 200, "base node limit for the sub-MIP"}, + {CUOPT_MIP_HYPER_SUBMIP_ITERATION_LIMIT_OFFSET, &mip_settings.submip_params.iteration_limit_offset, 0, std::numeric_limits::max(), 10000, "base sub-MIP simplex-iteration limit for root heuristics"}, + {CUOPT_MIP_HYPER_SUBMIP_MAX_LEVEL, &mip_settings.submip_params.max_level, 0, std::numeric_limits::max(), 10, "maximum sub-MIP recursion level"}, + {CUOPT_BARRIER_PRESOLVE_BOUND_FREE_VARIABLES, &pdlp_settings.barrier_presolve_bound_free_variables, -1, 1, -1, "Bound free variables during barrier presolve: -1 automatic (default behavior), 0 disabled, 1 enabled"}, + {CUOPT_BARRIER_ADAPTIVE_REGULARIZATION, &pdlp_settings.barrier_adaptive_regularization, -1, 1, -1, "Adaptive regularization for barrier method: -1 automatic (default behavior), 0 disabled, 1 enabled"}, + // QCQP (barrier) scaling hyper-parameter + {CUOPT_QCQP_HYPER_RUIZ_EQUILIBRATION, &pdlp_settings.qcqp_ruiz_equilibration, -1, 1, -1, "Ruiz equilibration for QCQP barrier scaling: -1 automatic (row/column imbalance heuristic), 0 disabled, 1 enabled"}, + }; + + // Bool parameters + bool_parameters = { + {CUOPT_INFEASIBILITY_DETECTION, &pdlp_settings.detect_infeasibility, false}, + {CUOPT_STRICT_INFEASIBILITY, &pdlp_settings.strict_infeasibility, false}, + {CUOPT_PER_CONSTRAINT_RESIDUAL, &pdlp_settings.per_constraint_residual, false}, + {CUOPT_SAVE_BEST_PRIMAL_SO_FAR, &pdlp_settings.save_best_primal_so_far, false}, + {CUOPT_FIRST_PRIMAL_FEASIBLE, &pdlp_settings.first_primal_feasible, false}, + {CUOPT_MIP_HEURISTICS_ONLY, &mip_settings.heuristics_only, false}, + {CUOPT_LOG_TO_CONSOLE, &pdlp_settings.log_to_console, true}, + {CUOPT_LOG_TO_CONSOLE, &mip_settings.log_to_console, true}, + {CUOPT_CROSSOVER, &pdlp_settings.crossover, false}, + {CUOPT_ELIMINATE_DENSE_COLUMNS, &pdlp_settings.eliminate_dense_columns, true}, + {CUOPT_CUDSS_DETERMINISTIC, &pdlp_settings.cudss_deterministic, false}, + {CUOPT_DUAL_POSTSOLVE, &pdlp_settings.dual_postsolve, true}, + {CUOPT_BARRIER_ITERATIVE_REFINEMENT, &pdlp_settings.barrier_iterative_refinement, true}, + {CUOPT_MIP_PROBING, &mip_settings.probing, true}, + {CUOPT_USE_DISTRIBUTED_PDLP, &pdlp_settings.use_distributed_pdlp, false}, + // Diving heuristic hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_DIVING_SHOW_TYPE, &mip_settings.diving_params.show_type, false, "log diving heuristic type when it finds a new incumbent"}, + // Recursive sub-MIP (RINS) hyper-parameters (hidden from default --help: name contains "hyper_") + {CUOPT_MIP_HYPER_SUBMIP_ENABLE_CPUFJ, &mip_settings.submip_params.enable_cpufj, true, "run CPU FJ over the sub-MIP"}, + {CUOPT_MIP_HYPER_BLOCK_BVE, &mip_settings.block_bve, true, "eliminate blocks of binaries in cuOpt's MIP presolve (needs " CUOPT_MIP_PROBING ")"}, + }; + // String parameters + string_parameters = { + {CUOPT_LOG_FILE, &mip_settings.log_file, ""}, + {CUOPT_LOG_FILE, &pdlp_settings.log_file, ""}, + {CUOPT_SOLUTION_FILE, &mip_settings.sol_file, ""}, + {CUOPT_SOLUTION_FILE, &pdlp_settings.sol_file, ""}, + {CUOPT_USER_PROBLEM_FILE, &mip_settings.user_problem_file, ""}, + {CUOPT_USER_PROBLEM_FILE, &pdlp_settings.user_problem_file, ""}, + {CUOPT_PRESOLVE_FILE, &mip_settings.presolve_file, ""}, + {CUOPT_PRESOLVE_FILE, &pdlp_settings.presolve_file, ""}, + }; + // clang-format on +} + #if MIP_INSTANTIATE_FLOAT -template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_primal_solution( - const float*, int, rmm::cuda_stream_view); -template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_dual_solution( - const float*, int, rmm::cuda_stream_view); -template CUOPT_EXPORT const rmm::device_uvector& -solver_settings_t::get_initial_pdlp_primal_solution() const; -template CUOPT_EXPORT const rmm::device_uvector& -solver_settings_t::get_initial_pdlp_dual_solution() const; -template CUOPT_EXPORT void solver_settings_t::add_initial_mip_solution( - const float*, int, rmm::cuda_stream_view); -// The 19-argument host overload. It was moved into this TU with the rest of the block, but -// `template class` in solver_settings.cpp cannot emit it (definition not visible there), so -// without this line the symbol disappears -- and it is the one the Cython layer binds to, -// which takes down every Python test, docs-build and wheel-test job. -template CUOPT_EXPORT void solver_settings_t::set_pdlp_warm_start_data(const float*, - const float*, - const float*, - const float*, - const float*, - const float*, - const float*, - const float*, - const float*, - int, - int, - float, - float, - int, - int, - float, - float, - float, - int); +// Emits the ctor/dtor/copy for the whole class; solver_settings.cpp deliberately does not, +// because those need CUDA (see the note there). +template class CUOPT_EXPORT solver_settings_t; #endif #if MIP_INSTANTIATE_DOUBLE -template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_primal_solution( - const double*, int, rmm::cuda_stream_view); -template CUOPT_EXPORT void solver_settings_t::set_initial_pdlp_dual_solution( - const double*, int, rmm::cuda_stream_view); -template CUOPT_EXPORT const rmm::device_uvector& -solver_settings_t::get_initial_pdlp_primal_solution() const; -template CUOPT_EXPORT const rmm::device_uvector& -solver_settings_t::get_initial_pdlp_dual_solution() const; -template CUOPT_EXPORT void solver_settings_t::add_initial_mip_solution( - const double*, int, rmm::cuda_stream_view); -// The 19-argument host overload. It was moved into this TU with the rest of the block, but -// `template class` in solver_settings.cpp cannot emit it (definition not visible there), so -// without this line the symbol disappears -- and it is the one the Cython layer binds to, -// which takes down every Python test, docs-build and wheel-test job. -template CUOPT_EXPORT void solver_settings_t::set_pdlp_warm_start_data(const double*, - const double*, - const double*, - const double*, - const double*, - const double*, - const double*, - const double*, - const double*, - int, - int, - double, - double, - int, - int, - double, - double, - double, - int); +// Emits the ctor/dtor/copy for the whole class; solver_settings.cpp deliberately does not, +// because those need CUDA (see the note there). +template class CUOPT_EXPORT solver_settings_t; #endif } // namespace CUOPT_EXPORT mathematical_optimization diff --git a/cpp/src/mip_heuristics/CMakeLists.txt b/cpp/src/mip_heuristics/CMakeLists.txt index f56c558a90..2acac7eb80 100644 --- a/cpp/src/mip_heuristics/CMakeLists.txt +++ b/cpp/src/mip_heuristics/CMakeLists.txt @@ -9,7 +9,6 @@ set(MIP_LP_NECESSARY_FILES ${CMAKE_CURRENT_SOURCE_DIR}/problem/problem.cu ${CMAKE_CURRENT_SOURCE_DIR}/problem/presolve_data.cu ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu - ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/solver_solution.cu ${CMAKE_CURRENT_SOURCE_DIR}/local_search/rounding/simple_rounding.cu ${CMAKE_CURRENT_SOURCE_DIR}/presolve/third_party_presolve.cpp @@ -56,5 +55,13 @@ else() set(MIP_SRC_FILES ${MIP_LP_NECESSARY_FILES} ${MIP_NON_LP_FILES}) endif() +# Host-only members of mip_solver_settings_t (callbacks, tolerances). The gRPC client +# needs them, so they build into cuopt_client; add_initial_solution stays in the .cu. +set(MIP_CLIENT_SRC_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cpp +) + set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${MIP_SRC_FILES} PARENT_SCOPE) +set(CUOPT_CLIENT_SRC_FILES ${CUOPT_CLIENT_SRC_FILES} + ${MIP_CLIENT_SRC_FILES} PARENT_SCOPE) diff --git a/cpp/src/pdlp/CMakeLists.txt b/cpp/src/pdlp/CMakeLists.txt index 1b1439b350..677f9cd8e0 100644 --- a/cpp/src/pdlp/CMakeLists.txt +++ b/cpp/src/pdlp/CMakeLists.txt @@ -6,9 +6,7 @@ # Core LP files always included set(LP_CORE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings.cu - ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings_accessors.cpp ${CMAKE_CURRENT_SOURCE_DIR}/optimization_problem.cu - ${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem_to_gpu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/backend_selection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utilities/problem_checking.cu @@ -17,7 +15,6 @@ set(LP_CORE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/pdhg.cu ${CMAKE_CURRENT_SOURCE_DIR}/solver_solution.cu ${CMAKE_CURRENT_SOURCE_DIR}/solution_conversion.cu - ${CMAKE_CURRENT_SOURCE_DIR}/solution_conversion_cpu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/saddle_point.cu ${CMAKE_CURRENT_SOURCE_DIR}/cusparse_view.cu ${CMAKE_CURRENT_SOURCE_DIR}/pdlp_warm_start_data.cu @@ -52,4 +49,14 @@ else() set(LP_SRC_FILES ${LP_CORE_FILES} ${LP_ADAPTER_FILES}) endif() +# Host-only LP sources that the gRPC client needs. These build into cuopt_client so the +# client library resolves standalone, without libcuopt.so. Their GPU-facing members were +# split into separate CUDA translation units above (e.g. cpu_optimization_problem_to_gpu.cpp). +set(LP_CLIENT_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/cpu_optimization_problem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/solution_conversion_cpu.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/solver_settings_accessors.cpp +) + set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${LP_SRC_FILES} PARENT_SCOPE) +set(CUOPT_CLIENT_SRC_FILES ${CUOPT_CLIENT_SRC_FILES} ${LP_CLIENT_FILES} PARENT_SCOPE) diff --git a/python/libcuopt/CMakeLists.txt b/python/libcuopt/CMakeLists.txt index 4d24169645..adc1b8e171 100644 --- a/python/libcuopt/CMakeLists.txt +++ b/python/libcuopt/CMakeLists.txt @@ -96,5 +96,9 @@ endif() message(STATUS "libcuopt: Final RPATH = ${rpaths}") set_property(TARGET cuopt PROPERTY INSTALL_RPATH ${rpaths} APPEND) +# cuopt_client needs these too: it PUBLIC-links rapids_logger, which +# build_wheel_libcuopt.sh excludes from vendoring, so the only way to find it is +# $ORIGIN/../../rapids_logger/lib64 from this list. +set_property(TARGET cuopt_client PROPERTY INSTALL_RPATH ${rpaths} APPEND) set_property(TARGET cuopt_cli PROPERTY INSTALL_RPATH ${rpaths} APPEND) set_property(TARGET cuopt_grpc_server PROPERTY INSTALL_RPATH ${rpaths} APPEND)