diff --git a/.gitignore b/.gitignore index 3fbea51..3e9ea99 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ TAGS *.m~ python/*.egg-info/ /doc/_build/ +/build/ + diff --git a/CMakeLists.txt b/CMakeLists.txt index eea1557..0843998 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.18) project(robin - VERSION 0.1 + VERSION 1.2.0 DESCRIPTION "Robust outlier rejection based on measurement compatibility graphs" LANGUAGES CXX ) @@ -32,9 +32,9 @@ include(DownloadExternal) # option(BUILD_DOCS "Build documentation." OFF) option(BUILD_TESTS "Enable testing with ctest." ON) -option(BUILD_PYTHON_BINDINGS "Build python bindings." ON) option(BUILD_MATLAB_BINDINGS "Build MATLAB bindings" OFF) option(USE_ASAN "Enable address sanitizer" OFF) +option(USE_SYSTEM_EIGEN3 "Use system pre-installed Eigen" ON) option(ENABLE_DIAGNOSTIC_PRINT "Enable printing of diagnostic messages" OFF) if (ENABLE_DIAGNOSTIC_PRINT) @@ -46,7 +46,7 @@ endif () # Dependencies # set(CMAKE_POSITION_INDEPENDENT_CODE ON) -find_package(Eigen3 3.3 QUIET REQUIRED NO_MODULE) +find_external_dependency("Eigen3" "Eigen3::Eigen" "${CMAKE_CURRENT_LIST_DIR}/cmake/DownloadEigen.cmake") find_package(OpenMP QUIET REQUIRED) robin_download_pmc() robin_download_xenium() @@ -89,6 +89,7 @@ include(CMakePackageConfigHelpers) install( TARGETS robin EXPORT robinTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) @@ -113,6 +114,7 @@ export( install( TARGETS xenium pmc EXPORT robinTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) @@ -146,13 +148,6 @@ if (BUILD_TESTS) add_subdirectory(tests) endif () -# Python bindings -if (BUILD_PYTHON_BINDINGS) - robin_download_pybind11() - add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR}) - add_subdirectory(python) -endif () - # MATLAB bindings if (BUILD_MATLAB_BINDINGS) if (USE_ASAN) @@ -177,7 +172,6 @@ message(STATUS "===============================================================" message(STATUS "============= Robin Configuration Options =====================") message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}") message(STATUS "BUILD_DOCS: ${BUILD_DOCS}") -message(STATUS "BUILD_PYTHON_BINDINGS: ${BUILD_PYTHON_BINDINGS}") message(STATUS "BUILD_MATLAB_BINDINGS: ${BUILD_MATLAB_BINDINGS}") message(STATUS "USE_ASAN: ${USE_ASAN}") message(STATUS "ENABLE_DIAGNOSTIC_PRINT: ${ENABLE_DIAGNOSTIC_PRINT}") diff --git a/README.md b/README.md index 8373342..61d4d38 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,24 @@ and If you are interested in more works from us, please visit our lab page [here](http://web.mit.edu/sparklab/). -# Build and Install +--- + +# :gear: Build & Installation + +## :package: Dependency + ROBIN has the following dependencies: 1. OpenMP 2. Eigen3 +Thus, follow the below commandline: + +```bash +sudo apt-get install gcc g++ build-essential libeigen3-dev cmake python3-pip python3-dev git ninja-build -y +``` + +## C++ Installation + Run the following to build the library using CMake (inside the repo root directory): ```bash mkdir build && cd build @@ -41,16 +54,21 @@ The following CMake options are provided: ``` BUILD_DOCS: Build documentation. Default: OFF BUILD_TESTS: Enable testing with ctest. Default: ON -BUILD_PYTHON_BINDINGS: Build python bindings. Default: ON BUILD_MATLAB_BINDINGS: Build MATLAB bindings. Default: OFF USE_ASAN: Enable address sanitizer. Default: OFF ENABLE_DIAGNOSTIC_PRINT: Enable printing of diagnostic messages. Default: OFF ``` -To install Python bindings, after building the library (with `BUILD_PYTHON_BINDINGS=ON`), run: + +## Python Installation + +It's simple! To install Python bindings, just run: + ```bash -cd build/python && pip install . +pip3 install -e python/ ``` +--- + # Third-party Data Some of the testing data are from the [Network Repository](http://networkrepository.com/index.php). For more information, please refer to: diff --git a/cmake/DownloadEigen.cmake b/cmake/DownloadEigen.cmake new file mode 100644 index 0000000..3603d4a --- /dev/null +++ b/cmake/DownloadEigen.cmake @@ -0,0 +1,17 @@ +include(FetchContent) +FetchContent_Declare(eigen URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz + PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_LIST_DIR}/eigen.patch UPDATE_DISCONNECTED 1) +FetchContent_GetProperties(eigen) +if(NOT eigen_POPULATED) + FetchContent_Populate(eigen) + if(${CMAKE_VERSION} GREATER_EQUAL 3.25) + add_subdirectory(${eigen_SOURCE_DIR} ${eigen_BINARY_DIR} SYSTEM EXCLUDE_FROM_ALL) + else() + # Emulate the SYSTEM flag introduced in CMake 3.25. Withouth this flag the compiler will + # consider this 3rdparty headers as source code and fail due the -Werror flag. + add_subdirectory(${eigen_SOURCE_DIR} ${eigen_BINARY_DIR} EXCLUDE_FROM_ALL) + get_target_property(eigen_include_dirs eigen INTERFACE_INCLUDE_DIRECTORIES) + set_target_properties(eigen PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${eigen_include_dirs}") + endif() +endif() + diff --git a/cmake/DownloadExternal.cmake b/cmake/DownloadExternal.cmake index 09142c3..9b2e587 100644 --- a/cmake/DownloadExternal.cmake +++ b/cmake/DownloadExternal.cmake @@ -5,7 +5,7 @@ include(GNUInstallDirs) function(robin_download_pybind11) download_project(PROJ pybind11 GIT_REPOSITORY https://github.com/pybind/pybind11.git - GIT_TAG v2.5.0 + GIT_TAG v2.13.6 QUIET ) set(pybind11_SOURCE_DIR "${pybind11_SOURCE_DIR}" PARENT_SCOPE) @@ -89,3 +89,17 @@ function(robin_download_xenium) ) install(DIRECTORY ${xenium_SOURCE_DIR}/xenium DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endfunction() + +# For handling Eigen3 library +function(find_external_dependency PACKAGE_NAME TARGET_NAME INCLUDED_CMAKE_PATH) + string(TOUPPER ${PACKAGE_NAME} PACKAGE_NAME_UP) + set(USE_FROM_SYSTEM_OPTION "USE_SYSTEM_${PACKAGE_NAME_UP}") + if(${${USE_FROM_SYSTEM_OPTION}}) + find_package(${PACKAGE_NAME} QUIET NO_MODULE) + endif() + if(NOT ${${USE_FROM_SYSTEM_OPTION}} OR NOT TARGET ${TARGET_NAME}) + set(${USE_FROM_SYSTEM_OPTION} OFF PARENT_SCOPE) + include(${INCLUDED_CMAKE_PATH}) + endif() +endfunction() + diff --git a/cmake/eigen.patch b/cmake/eigen.patch new file mode 100644 index 0000000..6eb341c --- /dev/null +++ b/cmake/eigen.patch @@ -0,0 +1,74 @@ +commit cf82186416d04ea5df2a397d8fe09dc78d40ca65 +Author: Antonio Sánchez +Date: Sat Mar 5 05:49:45 2022 +0000 + + Adds new CMake Options for controlling build components. + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index de1c23e91..0af36a53a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -477,6 +477,9 @@ if(EIGEN_BUILD_TESTING) + add_subdirectory(failtest) + endif() + ++include(CMakeDetermineFortranCompiler) ++option(EIGEN_BUILD_BLAS "Toggles the building of the Eigen Blas library" ${CMAKE_Fortran_COMPILER}) ++option(EIGEN_BUILD_LAPACK "Toggles the building of the included Eigen LAPACK library" ${CMAKE_Fortran_COMPILER}) + if(EIGEN_LEAVE_TEST_IN_ALL_TARGET) + add_subdirectory(blas) + add_subdirectory(lapack) +@@ -611,6 +614,8 @@ set_target_properties (eigen PROPERTIES EXPORT_NAME Eigen) + + install (TARGETS eigen EXPORT Eigen3Targets) + ++option(EIGEN_BUILD_CMAKE_PACKAGE "Enables the creation of EigenConfig.cmake and related files" ON) ++if(EIGEN_BUILD_CMAKE_PACKAGE) + configure_package_config_file ( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Eigen3Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/Eigen3Config.cmake +@@ -655,6 +660,7 @@ install (FILES ${CMAKE_CURRENT_BINARY_DIR}/Eigen3Config.cmake + # Add uninstall target + add_custom_target ( uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/EigenUninstall.cmake) ++endif() + + if (EIGEN_SPLIT_TESTSUITE) + ei_split_testsuite("${EIGEN_SPLIT_TESTSUITE}") +diff --git a/blas/CMakeLists.txt b/blas/CMakeLists.txt +index 8d3cb86dc..c530957fb 100644 +--- a/blas/CMakeLists.txt ++++ b/blas/CMakeLists.txt +@@ -1,6 +1,7 @@ + + project(EigenBlas CXX) + ++if(EIGEN_BUILD_BLAS) + include(CheckLanguage) + check_language(Fortran) + if(CMAKE_Fortran_COMPILER) +@@ -59,4 +60,4 @@ if(EIGEN_BUILD_TESTING) + endif() + + endif() +- ++endif() +diff --git a/lapack/CMakeLists.txt b/lapack/CMakeLists.txt +index c8ca64001..8d6d75401 100644 +--- a/lapack/CMakeLists.txt ++++ b/lapack/CMakeLists.txt +@@ -1,5 +1,7 @@ + project(EigenLapack CXX) + ++if(EIGEN_BUILD_LAPACK AND EIGEN_BUILD_BLAS) ++ + include(CheckLanguage) + check_language(Fortran) + if(CMAKE_Fortran_COMPILER) +@@ -457,3 +459,6 @@ if(EXISTS ${eigen_full_path_to_testing_lapack}) + + endif() + ++elseif(EIGEN_BUILD_LAPACK AND NOT EIGEN_BUILD_BLAS) ++ message(FATAL_ERROR "EIGEN_BUILD_LAPACK requires EIGEN_BUILD_BLAS") ++endif() #EIGEN_BUILD_LAPACK diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index c896b78..aecc849 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,37 +1,40 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.18) project(robin_python_bindings) -pybind11_add_module(robin_py robin_py/robin_py.cpp) +# Set build type +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) -message(STATUS "Python Interpreter Version: ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}") +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +find_package(pybind11 CONFIG REQUIRED) -target_link_libraries(robin_py PUBLIC robin) +message(STATUS "Python Interpreter Version: ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}") # fix for clang # see: https://github.com/pybind/pybind11/issues/1818 if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - target_compile_options(robin_py PUBLIC -fsized-deallocation) + target_compile_options(spark_robin PUBLIC -fsized-deallocation) +endif () + +if (DEFINED SKBUILD) + message(STATUS "Building with Scikit-Build") endif () -# make sure to output the build file to robin_py folder -SET_TARGET_PROPERTIES(robin_py - PROPERTIES - PREFIX "" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/robin_py" - ) - -# copy package __init__.py file -configure_file(robin_py/__init__.py - ${CMAKE_CURRENT_BINARY_DIR}/robin_py/__init__.py - ) - -# copy setup.py file -configure_file(setup.py.in - ${CMAKE_CURRENT_BINARY_DIR}/setup.py - ) - -file(COPY . - DESTINATION . - FILES_MATCHING - PATTERN *.py) \ No newline at end of file +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../) + # 'robin_cpp': user-defined build directory + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../ ${CMAKE_CURRENT_BINARY_DIR}/robin_cpp) +else() + message(STATUS "Performing out-of-tree build, fetching ROBIN v${CMAKE_PROJECT_VERSION} Release from Github") + include(FetchContent) + FetchContent_Declare( + ext_robin PREFIX robin + URL https://github.com/MIT-SPARK/ROBIN/archive/refs/tags/v${CMAKE_PROJECT_VERSION}.tar.gz) + FetchContent_MakeAvailable(ext_robin) +endif() + +pybind11_add_module(spark_robin robin_py/robin_py.cpp) + +target_link_libraries(spark_robin PUBLIC robin::robin pmc) + +install(TARGETS spark_robin DESTINATION .) diff --git a/python/example.py b/python/example.py index feef609..166d8ec 100644 --- a/python/example.py +++ b/python/example.py @@ -1,10 +1,10 @@ -import robin_py +import spark_robin if __name__ == "__main__": print("Examples showing usage of robin_py") # creating a Graph in robin - g = robin_py.AdjListGraph() + g = spark_robin.AdjListGraph() for i in range(10): g.AddVertex(i) @@ -12,11 +12,11 @@ g.AddEdge(i, i+10) # find the corresponding inlier structures - max_core_indices = robin_py.FindInlierStructure( - g, robin_py.InlierGraphStructure.MAX_CORE + max_core_indices = spark_robin.FindInlierStructure( + g, spark_robin.InlierGraphStructure.MAX_CORE ) - max_clique_indices = robin_py.FindInlierStructure( - g, robin_py.InlierGraphStructure.MAX_CLIQUE + max_clique_indices = spark_robin.FindInlierStructure( + g, spark_robin.InlierGraphStructure.MAX_CLIQUE ) diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..7a946d9 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["scikit_build_core", "pybind11"] +build-backend = "scikit_build_core.build" + +[project] +name = "spark_robin" +version = "1.2.0" +requires-python = ">=3.8" +description ='Python binding for Robin' +authors = [ + { name = "Jingnan Shi", email = "jnshi@mit.edu" }, +] +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Other Audience", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: Unix", + "Programming Language :: C++", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "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", +] + +[tool.scikit-build] + diff --git a/python/robin_py/__init__.py b/python/robin_py/__init__.py index 9672fb3..2f4e7ab 100644 --- a/python/robin_py/__init__.py +++ b/python/robin_py/__init__.py @@ -1 +1 @@ -from .robin_py import * \ No newline at end of file +from .spark_robin import * diff --git a/python/robin_py/robin_py.cpp b/python/robin_py/robin_py.cpp index ccbeedd..c4e2578 100644 --- a/python/robin_py/robin_py.cpp +++ b/python/robin_py/robin_py.cpp @@ -15,7 +15,7 @@ namespace py = pybind11; /** * Python interface with pybind11 */ -PYBIND11_MODULE(robin_py, m) { +PYBIND11_MODULE(spark_robin, m) { m.doc() = "Python binding for Robin"; // GraphStorageType diff --git a/python/setup.py.in b/python/setup.py.in deleted file mode 100644 index 61df235..0000000 --- a/python/setup.py.in +++ /dev/null @@ -1,12 +0,0 @@ -from setuptools import setup - -setup( - name='robin_py', - version='1.1.0', - author='Jingnan Shi', - author_email='jnshi@mit.edu', - description='Python binding for Robin', - package_dir={'': '${CMAKE_CURRENT_BINARY_DIR}'}, - packages=['robin_py'], - package_data={'': ['*.so']} -) \ No newline at end of file