Provide optional METIS support
* Split `CERES_NO_METIS` into two defines: `CERES_NO_PARTITION` and
`CERES_NO_METIS`. The former refers to METIS support in SuiteSparse,
the latter to the Eigen's MetisSupport module. This enables the use of
sparse matrix reordering independent from SuiteSparse.
* Run Linux, macOS, and macOS Github workflows with METIS enabled
SuiteSparse.
Fixes #808
Change-Id: I5076b7e1268d32cc3e7e56650edcbaf7fb3b59ce
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 1214fd0..4f9d29b 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -46,6 +46,7 @@
libgflags-dev \
libgoogle-glog-dev \
liblapack-dev \
+ libmetis-dev \
libsuitesparse-dev \
ninja-build
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 431ad6c..03e5f8d 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -48,6 +48,7 @@
gflags \
glog \
google-benchmark \
+ metis \
ninja \
suite-sparse
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 3c4a5a5..b7e5a2f 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -92,12 +92,12 @@
uses: actions/cache@v2
with:
path: suitesparse/
- key: ${{matrix.msvc}}-suitesparse-5.11.0-cmake.2-${{matrix.arch}}-${{matrix.build_type}}-${{matrix.lib}}
+ key: ${{matrix.msvc}}-suitesparse-5.12.0-cmake.3-${{matrix.arch}}-${{matrix.build_type}}-${{matrix.lib}}
- name: Download SuiteSparse
if: steps.cache-suitesparse.outputs.cache-hit != 'true'
run: |
- (New-Object System.Net.WebClient).DownloadFile("https://github.com/sergiud/SuiteSparse/releases/download/5.11.0-cmake.2/SuiteSparse-5.11.0-cmake.2-${{matrix.marker}}-Win64-${{matrix.build_type}}-${{matrix.lib}}-gpl.zip", "suitesparse.zip");
+ (New-Object System.Net.WebClient).DownloadFile("https://github.com/sergiud/SuiteSparse/releases/download/5.12.0-cmake.3/SuiteSparse-5.12.0-cmake.3-${{matrix.marker}}-Win64-${{matrix.build_type}}-${{matrix.lib}}-gpl-metis.zip", "suitesparse.zip");
Expand-Archive -Path suitesparse.zip -DestinationPath ${{github.workspace}}/suitesparse;
- name: Cache Eigen
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 21eaefb..f0d86f5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -119,7 +119,9 @@
enable_testing()
include(CeresThreadingModels)
+include(CMakeDependentOption)
include(PrettyPrintCMakeList)
+
find_available_ceres_threading_models(CERES_THREADING_MODELS_AVAILABLE)
pretty_print_cmake_list(PRETTY_CERES_THREADING_MODELS_AVAILABLE
${CERES_THREADING_MODELS_AVAILABLE})
@@ -155,6 +157,7 @@
# Enable the use of Eigen as a sparse linear algebra library for
# solving the nonlinear least squares problems.
option(EIGENSPARSE "Enable Eigen as a sparse linear algebra library." ON)
+cmake_dependent_option(EIGENMETIS "Enable Eigen METIS support." ON EIGENSPARSE OFF)
option(EXPORT_BUILD_DIR
"Export build directory using CMake (enables external use without install)." OFF)
option(BUILD_TESTING "Enable tests" ON)
@@ -265,7 +268,8 @@
# built with SuiteSparse support.
# Check for SuiteSparse and dependencies.
- find_package(SuiteSparse 4.5.6 COMPONENTS CHOLMOD SPQR)
+ find_package(SuiteSparse 4.5.6 COMPONENTS CHOLMOD SPQR
+ OPTIONAL_COMPONENTS Partition)
if (SuiteSparse_FOUND)
set(SuiteSparse_DEPENDENCY "find_dependency(SuiteSparse ${SuiteSparse_VERSION})")
# By default, if all of SuiteSparse's dependencies are found, Ceres is
@@ -290,6 +294,30 @@
list(APPEND CERES_COMPILE_OPTIONS CERES_NO_SUITESPARSE)
endif (SUITESPARSE)
+if (NOT SuiteSparse_Partition_FOUND)
+ list (APPEND CERES_COMPILE_OPTIONS CERES_NO_CHOLMOD_PARTITION)
+endif (NOT SuiteSparse_Partition_FOUND)
+
+if (EIGENMETIS)
+ find_package (METIS)
+ if (METIS_FOUND)
+ # Since METIS is a private dependency of Ceres, it requires access to the
+ # link-only METIS::METIS target to avoid undefined linker errors in projects
+ # relying on Ceres. We do not actually need to propagate anything besides
+ # the link libraries (such as include directories.)
+ set(METIS_DEPENDENCY "find_dependency(METIS ${METIS_VERSION})")
+ # METIS find module must be installed unless a package config is being used.
+ if (NOT METIS_DIR)
+ install(FILES ${Ceres_SOURCE_DIR}/cmake/FindMETIS.cmake
+ DESTINATION ${RELATIVE_CMAKECONFIG_INSTALL_DIR})
+ endif (NOT METIS_DIR)
+ endif (METIS_FOUND)
+endif (EIGENMETIS)
+
+if (NOT EIGENMETIS OR NOT METIS_FOUND)
+ list (APPEND CERES_COMPILE_OPTIONS CERES_NO_EIGEN_METIS)
+endif (NOT EIGENMETIS OR NOT METIS_FOUND)
+
if (ACCELERATESPARSE)
find_package(AccelerateSparse)
if (AccelerateSparse_FOUND)
diff --git a/bazel/ceres.bzl b/bazel/ceres.bzl
index 87dca32..f293424 100644
--- a/bazel/ceres.bzl
+++ b/bazel/ceres.bzl
@@ -203,15 +203,16 @@
# part of a Skylark Ceres target macro.
# https://github.com/ceres-solver/ceres-solver/issues/396
defines = [
- "CERES_NO_SUITESPARSE",
- "CERES_NO_METIS",
- "CERES_NO_ACCELERATE_SPARSE",
- "CERES_NO_LAPACK",
- "CERES_USE_EIGEN_SPARSE",
- "CERES_USE_CXX_THREADS",
- "CERES_NO_CUDA",
"CERES_EXPORT=",
+ "CERES_NO_ACCELERATE_SPARSE",
+ "CERES_NO_CHOLMOD_PARTITION",
+ "CERES_NO_CUDA",
+ "CERES_NO_EIGEN_METIS",
"CERES_NO_EXPORT=",
+ "CERES_NO_LAPACK",
+ "CERES_NO_SUITESPARSE",
+ "CERES_USE_CXX_THREADS",
+ "CERES_USE_EIGEN_SPARSE",
],
includes = [
"config",
diff --git a/cmake/CeresConfig.cmake.in b/cmake/CeresConfig.cmake.in
index 32108e4..33d8078 100644
--- a/cmake/CeresConfig.cmake.in
+++ b/cmake/CeresConfig.cmake.in
@@ -177,6 +177,7 @@
include(CMakeFindDependencyMacro)
# Optional dependencies
+@METIS_DEPENDENCY@
@OpenMP_DEPENDENCY@
@SuiteSparse_DEPENDENCY@
@Threads_DEPENDENCY@
diff --git a/cmake/FindSuiteSparse.cmake b/cmake/FindSuiteSparse.cmake
index 768b5df..4e05930 100644
--- a/cmake/FindSuiteSparse.cmake
+++ b/cmake/FindSuiteSparse.cmake
@@ -78,6 +78,9 @@
``SuiteSparse::CHOLMOD``
Sparse Supernodal Cholesky Factorization and Update/Downdate (CHOLMOD)
+``SuiteSparse::Partition``
+ CHOLMOD with METIS support
+
``SuiteSparse::SPQR``
Multifrontal Sparse QR (SuiteSparseQR)
@@ -124,6 +127,8 @@
set (SuiteSparse_FOUND TRUE)
include (CheckLibraryExists)
+include (CheckSymbolExists)
+include (CMakePushCheckState)
# Config is a base component and thus always required
set (SuiteSparse_IMPLICIT_COMPONENTS Config)
@@ -299,6 +304,11 @@
endif (NOT LAPACK_FOUND)
foreach (component IN LISTS SuiteSparse_FIND_COMPONENTS)
+ if (component STREQUAL Partition)
+ # Partition is a meta component that neither provides additional headers nor
+ # a separate library. It is strictly part of CHOLMOD.
+ continue ()
+ endif (component STREQUAL Partition)
string (TOLOWER ${component} component_library)
if (component STREQUAL "Config")
@@ -417,17 +427,8 @@
endif (NOT EXISTS ${SuiteSparse_VERSION_FILE})
endif (TARGET SuiteSparse::Config)
-# METIS (Optional dependency).
-find_package (METIS)
-
# CHOLMOD requires AMD CAMD CCOLAMD COLAMD
if (TARGET SuiteSparse::CHOLMOD)
- # METIS is optional
- if (TARGET METIS::METIS)
- set_property (TARGET SuiteSparse::CHOLMOD APPEND PROPERTY
- INTERFACE_LINK_LIBRARIES METIS::METIS)
- endif (TARGET METIS::METIS)
-
foreach (component IN ITEMS AMD CAMD CCOLAMD COLAMD)
if (TARGET SuiteSparse::${component})
set_property (TARGET SuiteSparse::CHOLMOD APPEND PROPERTY
@@ -464,6 +465,44 @@
endforeach (component IN LISTS SuiteSparse_FIND_COMPONENTS)
endif (TARGET SuiteSparse::Config)
+# Check whether CHOLMOD was compiled with METIS support. The check can be
+# performed only after the main components have been set up.
+if (TARGET SuiteSparse::CHOLMOD)
+ # NOTE If SuiteSparse was compiled as a static library we'll need to link
+ # against METIS already during the check. Otherwise, the check can fail due to
+ # undefined references even though SuiteSparse was compiled with METIS.
+ find_package (METIS)
+
+ if (TARGET METIS::METIS)
+ cmake_push_check_state (RESET)
+ set (CMAKE_REQUIRED_LIBRARIES SuiteSparse::CHOLMOD METIS::METIS)
+ check_symbol_exists (cholmod_metis cholmod.h SuiteSparse_CHOLMOD_USES_METIS)
+ cmake_pop_check_state ()
+
+ if (SuiteSparse_CHOLMOD_USES_METIS)
+ set_property (TARGET SuiteSparse::CHOLMOD APPEND PROPERTY
+ INTERFACE_LINK_LIBRARIES $<LINK_ONLY:METIS::METIS>)
+
+ # Provide the SuiteSparse::Partition component whose availability indicates
+ # that CHOLMOD was compiled with the Partition module.
+ if (NOT TARGET SuiteSparse::Partition)
+ add_library (SuiteSparse::Partition IMPORTED INTERFACE)
+ endif (NOT TARGET SuiteSparse::Partition)
+
+ set_property (TARGET SuiteSparse::Partition APPEND PROPERTY
+ INTERFACE_LINK_LIBRARIES SuiteSparse::CHOLMOD)
+ endif (SuiteSparse_CHOLMOD_USES_METIS)
+ endif (TARGET METIS::METIS)
+endif (TARGET SuiteSparse::CHOLMOD)
+
+# We do not use suitesparse_find_component to find Partition and therefore must
+# handle the availability in an extra step.
+if (TARGET SuiteSparse::Partition)
+ set (SuiteSparse_Partition_FOUND TRUE)
+else (TARGET SuiteSparse::Partition)
+ set (SuiteSparse_Partition_FOUND FALSE)
+endif (TARGET SuiteSparse::Partition)
+
suitesparse_reset_find_library_prefix()
# Handle REQUIRED and QUIET arguments to FIND_PACKAGE
diff --git a/cmake/config.h.in b/cmake/config.h.in
index 66a195a..4007955 100644
--- a/cmake/config.h.in
+++ b/cmake/config.h.in
@@ -76,6 +76,11 @@
@CERES_USE_OPENMP@
// If defined Ceres was compiled with modern C++ multithreading.
@CERES_USE_CXX_THREADS@
+// If defined, Ceres was compiled with a version of SuiteSparse/CHOLMOD without
+// the Partition module (requires METIS).
+@CERES_NO_CHOLMOD_PARTITION@
+// If defined Ceres was compiled without support for METIS via Eigen.
+@CERES_NO_EIGEN_METIS@
// If defined, Ceres was compiled with a version MSVC >= 2005 which
// deprecated the standard POSIX names for bessel functions, replacing them
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 4c9bfe7..4e5008b 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -124,8 +124,18 @@
add_definitions(-DCERES_SUITESPARSE_VERSION="${SuiteSparse_VERSION}")
list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES SuiteSparse::CHOLMOD
SuiteSparse::SPQR)
+
+ if (SuiteSparse_Partition_FOUND)
+ list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES SuiteSparse::Partition)
+ endif (SuiteSparse_Partition_FOUND)
endif (SUITESPARSE AND SuiteSparse_FOUND)
+if (EIGENMETIS AND METIS_FOUND)
+ # Define version information for use in Solver::FullReport.
+ add_definitions(-DCERES_METIS_VERSION="${METIS_VERSION}")
+ list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES METIS::METIS)
+endif (EIGENMETIS AND METIS_FOUND)
+
if (ACCELERATESPARSE AND AccelerateSparse_FOUND)
list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${AccelerateSparse_LIBRARIES})
endif()
diff --git a/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc b/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc
index b0c218d..f9ff443 100644
--- a/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc
+++ b/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc
@@ -117,7 +117,7 @@
TestSolver(SUITE_SPARSE, OrderingType::AMD);
}
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_CHOLMOD_PARTITION
TEST_F(DynamicSparseNormalCholeskySolverTest, SuiteSparseNESDIS) {
TestSolver(SUITE_SPARSE, OrderingType::NESDIS);
}
@@ -129,7 +129,7 @@
TestSolver(EIGEN_SPARSE, OrderingType::AMD);
}
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
TEST_F(DynamicSparseNormalCholeskySolverTest, EigenNESDIS) {
TestSolver(EIGEN_SPARSE, OrderingType::NESDIS);
}
diff --git a/internal/ceres/eigensparse.cc b/internal/ceres/eigensparse.cc
index 3be222e..982608a 100644
--- a/internal/ceres/eigensparse.cc
+++ b/internal/ceres/eigensparse.cc
@@ -36,7 +36,7 @@
#include <sstream>
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
#include <iostream> // This is needed because MetisSupport depends on iostream.
#include "Eigen/MetisSupport"
@@ -157,7 +157,7 @@
if (ordering_type == OrderingType::AMD) {
return std::make_unique<EigenSparseCholeskyTemplate<WithAMDOrdering>>();
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
} else if (ordering_type == OrderingType::NESDIS) {
using WithMetisOrdering = Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>,
Eigen::Upper,
@@ -190,7 +190,7 @@
Eigen::NaturalOrdering<int>>;
if (ordering_type == OrderingType::AMD) {
return std::make_unique<EigenSparseCholeskyTemplate<WithAMDOrdering>>();
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
} else if (ordering_type == OrderingType::NESDIS) {
using WithMetisOrdering = Eigen::SimplicialLDLT<Eigen::SparseMatrix<float>,
Eigen::Upper,
diff --git a/internal/ceres/eigensparse.h b/internal/ceres/eigensparse.h
index 58d15eb..04cdbad 100644
--- a/internal/ceres/eigensparse.h
+++ b/internal/ceres/eigensparse.h
@@ -51,7 +51,7 @@
class EigenSparse {
public:
static constexpr bool IsNestedDissectionAvailable() noexcept {
-#ifdef CERES_NO_METIS
+#ifdef CERES_NO_EIGEN_METIS
return false;
#else
return true;
diff --git a/internal/ceres/reorder_program.cc b/internal/ceres/reorder_program.cc
index 9f89c4b..eb37dc3 100644
--- a/internal/ceres/reorder_program.cc
+++ b/internal/ceres/reorder_program.cc
@@ -51,7 +51,7 @@
#ifdef CERES_USE_EIGEN_SPARSE
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
#include <iostream> // Need this because MetisSupport refers to std::cerr.
#include "Eigen/MetisSupport"
@@ -191,7 +191,7 @@
Eigen::AMDOrdering<int> amd_ordering;
amd_ordering(block_hessian, perm);
} else {
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
Eigen::MetisOrdering<int> metis_ordering;
metis_ordering(block_hessian, perm);
#else
@@ -400,7 +400,7 @@
Eigen::AMDOrdering<int> amd_ordering;
amd_ordering(block_schur_complement, perm);
} else {
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
Eigen::MetisOrdering<int> metis_ordering;
metis_ordering(block_schur_complement, perm);
#else
diff --git a/internal/ceres/schur_complement_solver_test.cc b/internal/ceres/schur_complement_solver_test.cc
index 233a599..9ec9ffe 100644
--- a/internal/ceres/schur_complement_solver_test.cc
+++ b/internal/ceres/schur_complement_solver_test.cc
@@ -215,7 +215,7 @@
3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::AMD);
}
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
TEST_F(SchurComplementSolverTest,
SparseSchurWithSuiteSparseSmallProblemNESDIS) {
ComputeAndCompareSolutions(
@@ -230,7 +230,7 @@
ComputeAndCompareSolutions(
3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NESDIS);
}
-#endif // CERES_NO_METIS
+#endif // CERES_NO_EIGEN_METIS
#endif // CERES_NO_SUITESPARSE
#ifndef CERES_NO_ACCELERATE_SPARSE
@@ -275,7 +275,7 @@
2, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::AMD);
}
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
TEST_F(SchurComplementSolverTest,
SparseSchurWithEigenSparseSmallProblemNESDIS) {
ComputeAndCompareSolutions(
@@ -300,7 +300,7 @@
3, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::AMD);
}
-#ifndef CERES_NO_METIS
+#ifndef CERES_NO_EIGEN_METIS
TEST_F(SchurComplementSolverTest,
SparseSchurWithEigenSparseLargeProblemNESDIS) {
ComputeAndCompareSolutions(
diff --git a/internal/ceres/solver_utils.cc b/internal/ceres/solver_utils.cc
index 968b958..7cf1f32 100644
--- a/internal/ceres/solver_utils.cc
+++ b/internal/ceres/solver_utils.cc
@@ -62,10 +62,9 @@
"-suitesparse-(" CERES_SUITESPARSE_VERSION ")"
#endif
-// TODO(sergiud) Capture METIS version
-// #ifndef CERES_NO_METIS
-// "-metis-(" CERES_METIS_VERSION ")"
-// #endif
+#ifndef CERES_NO_EIGEN_METIS
+ "-metis-(" CERES_METIS_VERSION ")"
+#endif
#ifndef CERES_NO_ACCELERATE_SPARSE
"-acceleratesparse"
diff --git a/internal/ceres/suitesparse.cc b/internal/ceres/suitesparse.cc
index 88ef935..85fc5b2 100644
--- a/internal/ceres/suitesparse.cc
+++ b/internal/ceres/suitesparse.cc
@@ -345,7 +345,7 @@
return cholmod_amd(matrix, nullptr, 0, ordering, &cc_);
}
-#ifdef CERES_NO_METIS
+#ifdef CERES_NO_CHOLMOD_PARTITION
return false;
#else
std::vector<int> CParent(matrix->nrow, 0);
@@ -361,7 +361,7 @@
}
bool SuiteSparse::IsNestedDissectionAvailable() {
-#ifdef CERES_NO_METIS
+#ifdef CERES_NO_CHOLMOD_PARTITION
return false;
#else
return true;