Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Spectra to v1.1.0 to fix C++20 compatibility issues on GCC/Clang #1871

Merged
merged 5 commits into from
Jan 10, 2025

Conversation

valgur
Copy link
Contributor

@valgur valgur commented Oct 11, 2024

GTSAM currently unfortunately fails to build with C++20 or newer on GCC and Clang with the following errors in ShonanAveraging.cpp:

  [ 70%] Building CXX object gtsam/CMakeFiles/gtsam.dir/sfm/ShonanAveraging.cpp.o
  In file included from /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/SymEigsBase.h:20,
                   from /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/SymEigsSolver.h:12,
                   from /home/martin/libs/gtsam/gtsam/sfm/ShonanAveraging.cpp:19:
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/MatOp/internal/ArnoldiOp.h:108:50: error: expected ‘)’ before ‘*’ token
    108 |     ArnoldiOp<Scalar, OpType, IdentityBOp>(OpType* op, IdentityBOp* /*Bop*/) :
        |                                           ~      ^
        |                                                  )
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/SymEigsBase.h: In instantiation of ‘Spectra::SymEigsBase<Scalar, SelectionRule, OpType, BOpType>::SymEigsBase(OpType*, BOpType*, Index, Index) [with Scalar = double; int SelectionRule = 0; OpType = gtsam::MatrixProdFunctor; BOpType = Spectra::IdentityBOp; Index = long int]’:
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/SymEigsSolver.h:165:83:   required from ‘Spectra::SymEigsSolver<Scalar, SelectionRule, OpType>::SymEigsSolver(OpType*, Index, Index) [with Scalar = double; int SelectionRule = 0; OpType = gtsam::MatrixProdFunctor; Index = long int]’
  /home/martin/libs/gtsam/gtsam/sfm/ShonanAveraging.cpp:641:79:   required from here
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/SymEigsBase.h:252:15: error: no matching function for call to ‘Spectra::ArnoldiOp<double, gtsam::MatrixProdFunctor, Spectra::IdentityBOp>::ArnoldiOp(gtsam::MatrixProdFunctor*&, Spectra::IdentityBOp*&)’
    252 |         m_fac(ArnoldiOpType(op, Bop), m_ncv),
        |               ^~~~~~~~~~~~~~~~~~~~~~
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/MatOp/internal/ArnoldiOp.h:99:7: note: candidate: ‘constexpr Spectra::ArnoldiOp<double, gtsam::MatrixProdFunctor, Spectra::IdentityBOp>::ArnoldiOp(const Spectra::ArnoldiOp<double, gtsam::MatrixProdFunctor, Spectra::IdentityBOp>&)’
     99 | class ArnoldiOp<Scalar, OpType, IdentityBOp>
        |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/MatOp/internal/ArnoldiOp.h:99:7: note:   candidate expects 1 argument, 2 provided
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/MatOp/internal/ArnoldiOp.h:99:7: note: candidate: ‘constexpr Spectra::ArnoldiOp<double, gtsam::MatrixProdFunctor, Spectra::IdentityBOp>::ArnoldiOp(Spectra::ArnoldiOp<double, gtsam::MatrixProdFunctor, Spectra::IdentityBOp>&&)’
  /home/martin/libs/gtsam/gtsam/3rdparty/Spectra/MatOp/internal/ArnoldiOp.h:99:7: note:   candidate expects 1 argument, 2 provided
  gmake[2]: *** [gtsam/CMakeFiles/gtsam.dir/build.make:2050: gtsam/CMakeFiles/gtsam.dir/sfm/ShonanAveraging.cpp.o] Error 1
  gmake[1]: *** [CMakeFiles/Makefile2:1397: gtsam/CMakeFiles/gtsam.dir/all] Error 2
  gmake: *** [Makefile:166: all] Error 2

This PR updates the vendored Spectra from v0.9.0 to the latest v1.0.1 as a simple fix for these C++20 compatibility issues. I also moved the Spectra headers under a Spectra/... prefix to match the installed header paths since v0.7.0.

Change log: https://github.com/yixuan/spectra/blob/master/CHANGELOG.md

Since this is a major version bump, there are some breaking changes in the Spectra API, which are listed in the migration guide: https://spectralib.org/upgrade

Corresponding changes to ShonanAveraging.cpp:

diff --git a/gtsam/sfm/ShonanAveraging.cpp b/gtsam/sfm/ShonanAveraging.cpp
index 7c8b07f37c..151c816b21 100644
--- a/gtsam/sfm/ShonanAveraging.cpp
+++ b/gtsam/sfm/ShonanAveraging.cpp
@@ -16,7 +16,7 @@
  * @brief  Shonan Averaging algorithm
  */
 
-#include <SymEigsSolver.h>
+#include <Spectra/SymEigsSolver.h>
 #include <cmath>
 #include <gtsam/linear/PCGSolver.h>
 #include <gtsam/linear/SubgraphPreconditioner.h>
@@ -574,6 +574,8 @@ static bool PowerMinimumEigenValue(
  * nontrivial function, perform_op(x,y), that computes and returns the product
  * y = (A + sigma*I) x */
 struct MatrixProdFunctor {
+  using Scalar = double;
+
   // Const reference to an externally-held matrix whose minimum-eigenvalue we
   // want to compute
   const Sparse &A_;
@@ -634,13 +636,13 @@ static bool SparseMinimumEigenValue(
     Eigen::Index numLanczosVectors = 20) {
   // a. Estimate the largest-magnitude eigenvalue of this matrix using Lanczos
   MatrixProdFunctor lmOperator(A);
-  Spectra::SymEigsSolver<double, Spectra::SELECT_EIGENVALUE::LARGEST_MAGN,
-                         MatrixProdFunctor>
-      lmEigenValueSolver(&lmOperator, 1, std::min(numLanczosVectors, A.rows()));
+  Spectra::SymEigsSolver lmEigenValueSolver(
+      lmOperator, 1, std::min(numLanczosVectors, A.rows()));
   lmEigenValueSolver.init();
 
-  const int lmConverged = lmEigenValueSolver.compute(
-      maxIterations, 1e-4, Spectra::SELECT_EIGENVALUE::LARGEST_MAGN);
+  const int lmConverged =
+      lmEigenValueSolver.compute(Spectra::SortRule::LargestMagn, maxIterations,
+                                 1e-4, Spectra::SortRule::LargestMagn);
 
   // Check convergence and bail out if necessary
   if (lmConverged != 1) return false;
@@ -668,10 +670,8 @@ static bool SparseMinimumEigenValue(
 
   MatrixProdFunctor minShiftedOperator(A, -2 * lmEigenValue);
 
-  Spectra::SymEigsSolver<double, Spectra::SELECT_EIGENVALUE::LARGEST_MAGN,
-                         MatrixProdFunctor>
-      minEigenValueSolver(&minShiftedOperator, 1,
-                          std::min(numLanczosVectors, A.rows()));
+  Spectra::SymEigsSolver minEigenValueSolver(
+      minShiftedOperator, 1, std::min(numLanczosVectors, A.rows()));
 
   // If S is a critical point of F, then S^T is also in the null space of S -
   // Lambda(S) (cf. Lemma 6 of the tech report), and therefore its rows are
@@ -699,8 +699,9 @@ static bool SparseMinimumEigenValue(
   // order to be able to estimate the smallest eigenvalue within an *absolute*
   // tolerance of 'minEigenvalueNonnegativityTolerance'
   const int minConverged = minEigenValueSolver.compute(
-      maxIterations, minEigenvalueNonnegativityTolerance / lmEigenValue,
-      Spectra::SELECT_EIGENVALUE::LARGEST_MAGN);
+      Spectra::SortRule::LargestMagn, maxIterations,
+      minEigenvalueNonnegativityTolerance / lmEigenValue,
+      Spectra::SortRule::LargestMagn);
 
   if (minConverged != 1) return false;

To be honest, I would personally be hesitant to merge such an unwieldy PR, so you might want to commit these changes yourself or at least diff them against the official Spectra sources.

@dellaert
Copy link
Member

@valgur sorry for the delay in reviewing. Partly the reason is it seems such a big change. Is there compelling reason to change the location of the headers? Or: can you make this PR much smaller/reviewable by using the old location? Note I have not tried to figure out the answer myself.

PS FWIW at Verdant we compiled with C++20 without any issue - so slightly confused about that - but I think upgrading Spectra is a good thing nonetheless.

@valgur valgur force-pushed the feature/spectra-1.0.1 branch 2 times, most recently from a440f0e to e383b0e Compare January 10, 2025 18:53
@valgur valgur changed the title Update Spectra to v1.0.1 to fix C++20 compatibility issues on GCC/Clang Update Spectra to v1.1.0 to fix C++20 compatibility issues on GCC/Clang Jan 10, 2025
@valgur
Copy link
Contributor Author

valgur commented Jan 10, 2025

Is there compelling reason to change the location of the headers?

Sorry, I should have indeed included my reasoning behind this. The official Spectra package uses a Spectra/ prefix for its includes since v0.7.0, e.g. #include <Spectra/SymEigsSolver.h> instead of #include <SymEigsSolver.h>, and the change in file path was to align with that. It does not really make much sense in isolation, but it's relevant for external package managers like Conan and Vcpkg, which generally try to avoid vendored libraries as much as possible and replace them with versions from the package repository. Following the conventions of the official Spectra package makes it easier to swap out the vendored version without additional patching.

That being said, it's not something that is strictly required and I can revert the move. The necessary patching is limited to just a single line anyway.

The PR will still be quite large regardless, since the update touches more than 50 files.

PS FWIW at Verdant we compiled with C++20 without any issue - so slightly confused about that - but I think upgrading Spectra is a good thing nonetheless.

The change is motivated by a GTSAM build failure on GCC 14 (if I recall correctly) with C++20 enabled, which was reported on ConanCenter Slack a couple of months ago.

I also now noticed that Spectra v1.1.0 was released just last week after a 2.5 year hiatus. 🙂 I applied the update to the PR.

@dellaert
Copy link
Member

OK, let me take a deeper look. I think it'll be ok.

Copy link
Member

@dellaert dellaert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks !!!

I have a slight preference if the files were in the same place as before, but if this is easier for you I don't really care that much. I have some question about the mapping that I'd like to know the answer to before merging, but otherwise looks great!

@valgur valgur force-pushed the feature/spectra-1.0.1 branch from e383b0e to 8583185 Compare January 10, 2025 20:59
@valgur
Copy link
Contributor Author

valgur commented Jan 10, 2025

I have a slight preference if the files were in the same place as before, but if this is easier for you I don't really care that much.

Ok, I moved the headers back to their original location.

@dellaert
Copy link
Member

Awesome! Many thanks for contributing!
If you are using gcc14 with c++20, consider adding a special case CI flow for GTSAM. It'd be good to check for other issues as new features are developed....

@dellaert dellaert merged commit 60365b7 into borglab:develop Jan 10, 2025
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants