Multiple sparse linear algebra backends.
1. Added support for CXSparse - SparseNormalCholesky and
SchurComplementSolver support SuiteSparse and CXSparse now.
I am not sure I will add suport for visibility based
preconditioning using CXSparse. Its not a high priority.
2. New enum SparseLinearAlgebraLibraryType which allows the user
to indicate which sparse linear algebra library should be used.
3. Updated tests for SolverImpl and system_test.
4. Build system changes to automatically detect CXSparse and
link to it by default -- just like SuiteSparse.
5. Minor bug fixes dealing in the cmake files and VBP.
6. Changed the order of the system test.
7. Deduped the unsymmetric linear solver test.
Change-Id: I33252a103c87b722ecb7ed7b5f0ae7fd91249244
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 2793ee5..1b8b0d0 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -130,6 +130,10 @@
ENDIF (EXISTS ${BLAS_LIB})
ENDIF (SUITESPARSE)
+IF (CXSPARSE)
+ LIST(APPEND CERES_LIBRARY_DEPENDENCIES ${CXSPARSE_LIB})
+ENDIF (CXSPARSE)
+
IF (OPENMP_FOUND)
LIST(APPEND CERES_LIBRARY_DEPENDENCIES gomp)
ENDIF (OPENMP_FOUND)
@@ -191,13 +195,15 @@
CERES_TEST(schur_ordering)
CERES_TEST(solver_impl)
CERES_TEST(symmetric_linear_solver)
- IF (GFLAGS)
- CERES_TEST(system)
- ENDIF (GFLAGS)
CERES_TEST(triplet_sparse_matrix)
CERES_TEST(unsymmetric_linear_solver)
CERES_TEST(visibility)
IF (GFLAGS)
CERES_TEST(visibility_based_preconditioner)
ENDIF (GFLAGS)
+
+ # Large end to end test for the entire solver.
+ IF (GFLAGS)
+ CERES_TEST(system)
+ ENDIF (GFLAGS)
ENDIF (BUILD_TESTING)
diff --git a/internal/ceres/linear_solver.cc b/internal/ceres/linear_solver.cc
index 7221a74..c08d1f2 100644
--- a/internal/ceres/linear_solver.cc
+++ b/internal/ceres/linear_solver.cc
@@ -50,22 +50,24 @@
return new CgnrSolver(options);
case SPARSE_NORMAL_CHOLESKY:
-#ifndef CERES_NO_SUITESPARSE
- return new SparseNormalCholeskySolver(options);
-#else
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
LOG(WARNING) << "SPARSE_NORMAL_CHOLESKY is not available. Please "
- << "build Ceres with SuiteSparse. Returning NULL.";
+ << "build Ceres with SuiteSparse or CXSparse. "
+ << "Returning NULL.";
return NULL;
-#endif // CERES_NO_SUITESPARSE
+#else
+ return new SparseNormalCholeskySolver(options);
+#endif
case SPARSE_SCHUR:
-#ifndef CERES_NO_SUITESPARSE
- return new SparseSchurComplementSolver(options);
-#else
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
LOG(WARNING) << "SPARSE_SCHUR is not available. Please "
- << "build Ceres with SuiteSparse. Returning NULL.";
+ << "build Ceres with SuiteSparse or CXSparse. "
+ << "Returning NULL.";
return NULL;
-#endif // CERES_NO_SUITESPARSE
+#else
+ return new SparseSchurComplementSolver(options);
+#endif
case DENSE_SCHUR:
return new DenseSchurComplementSolver(options);
diff --git a/internal/ceres/linear_solver.h b/internal/ceres/linear_solver.h
index 299a541..853abcb 100644
--- a/internal/ceres/linear_solver.h
+++ b/internal/ceres/linear_solver.h
@@ -71,6 +71,7 @@
Options()
: type(SPARSE_NORMAL_CHOLESKY),
preconditioner_type(JACOBI),
+ sparse_linear_algebra_library(SUITE_SPARSE),
min_num_iterations(1),
max_num_iterations(1),
num_threads(1),
@@ -85,6 +86,8 @@
PreconditionerType preconditioner_type;
+ SparseLinearAlgebraLibraryType sparse_linear_algebra_library;
+
// Number of internal iterations that the solver uses. This
// parameter only makes sense for iterative solvers like CG.
int min_num_iterations;
diff --git a/internal/ceres/schur_complement_solver.cc b/internal/ceres/schur_complement_solver.cc
index 4db7c31..b30d6ed 100644
--- a/internal/ceres/schur_complement_solver.cc
+++ b/internal/ceres/schur_complement_solver.cc
@@ -32,6 +32,11 @@
#include <ctime>
#include <set>
#include <vector>
+
+#ifndef CERES_NO_CXSPARSE
+#include "cs.h"
+#endif // CERES_NO_CXSPARSE
+
#include "Eigen/Dense"
#include "ceres/block_random_access_dense_matrix.h"
#include "ceres/block_random_access_matrix.h"
@@ -48,6 +53,7 @@
#include "ceres/internal/scoped_ptr.h"
#include "ceres/types.h"
+
namespace ceres {
namespace internal {
@@ -139,18 +145,22 @@
return true;
}
-#ifndef CERES_NO_SUITESPARSE
+
SparseSchurComplementSolver::SparseSchurComplementSolver(
const LinearSolver::Options& options)
- : SchurComplementSolver(options),
- symbolic_factor_(NULL) {
+ : SchurComplementSolver(options) {
+#ifndef CERES_NO_SUITESPARSE
+ symbolic_factor_ = NULL;
+#endif // CERES_NO_SUITESPARSE
}
SparseSchurComplementSolver::~SparseSchurComplementSolver() {
+#ifndef CERES_NO_SUITESPARSE
if (symbolic_factor_ != NULL) {
ss_.Free(symbolic_factor_);
symbolic_factor_ = NULL;
}
+#endif // CERES_NO_SUITESPARSE
}
// Determine the non-zero blocks in the Schur Complement matrix, and
@@ -224,10 +234,28 @@
set_rhs(new double[lhs()->num_rows()]);
}
+bool SparseSchurComplementSolver::SolveReducedLinearSystem(double* solution) {
+ switch (options().sparse_linear_algebra_library) {
+ case SUITE_SPARSE:
+ return SolveReducedLinearSystemUsingSuiteSparse(solution);
+ case CX_SPARSE:
+ return SolveReducedLinearSystemUsingCXSparse(solution);
+ default:
+ LOG(FATAL) << "Unknown sparse linear algebra library : "
+ << options().sparse_linear_algebra_library;
+ }
+
+ LOG(FATAL) << "Unknown sparse linear algebra library : "
+ << options().sparse_linear_algebra_library;
+ return false;
+}
+
+#ifndef CERES_NO_SUITESPARSE
// Solve the system Sx = r, assuming that the matrix S is stored in a
// BlockRandomAccessSparseMatrix. The linear system is solved using
// CHOLMOD's sparse cholesky factorization routines.
-bool SparseSchurComplementSolver::SolveReducedLinearSystem(double* solution) {
+bool SparseSchurComplementSolver::SolveReducedLinearSystemUsingSuiteSparse(
+ double* solution) {
// Extract the TripletSparseMatrix that is used for actually storing S.
TripletSparseMatrix* tsm =
const_cast<TripletSparseMatrix*>(
@@ -272,7 +300,58 @@
ss_.Free(cholmod_solution);
return true;
}
+#else
+bool SparseSchurComplementSolver::SolveReducedLinearSystemUsingSuiteSparse(
+ double* solution) {
+ LOG(FATAL) << "No SuiteSparse support in Ceres.";
+ return false;
+}
#endif // CERES_NO_SUITESPARSE
+#ifndef CERES_NO_CXSPARSE
+// Solve the system Sx = r, assuming that the matrix S is stored in a
+// BlockRandomAccessSparseMatrix. The linear system is solved using
+// CXSparse's sparse cholesky factorization routines.
+bool SparseSchurComplementSolver::SolveReducedLinearSystemUsingCXSparse(
+ double* solution) {
+ // Extract the TripletSparseMatrix that is used for actually storing S.
+ TripletSparseMatrix* tsm =
+ const_cast<TripletSparseMatrix*>(
+ down_cast<const BlockRandomAccessSparseMatrix*>(lhs())->matrix());
+
+ const int num_rows = tsm->num_rows();
+
+ // The case where there are no f blocks, and the system is block
+ // diagonal.
+ if (num_rows == 0) {
+ return true;
+ }
+
+ cs_di_sparse tsm_wrapper;
+ tsm_wrapper.nzmax = tsm->num_nonzeros();
+ tsm_wrapper.m = num_rows;
+ tsm_wrapper.n = num_rows;
+ tsm_wrapper.p = tsm->mutable_cols();
+ tsm_wrapper.i = tsm->mutable_rows();
+ tsm_wrapper.x = tsm->mutable_values();
+ tsm_wrapper.nz = tsm->num_nonzeros();
+
+ cs_di_sparse* lhs = cs_compress(&tsm_wrapper);
+ VectorRef(solution, num_rows) = ConstVectorRef(rhs(), num_rows);
+
+ // It maybe worth caching the ordering here, but for now we are
+ // going to go with the simple cholsol based implementation.
+ int ok = cs_di_cholsol(1, lhs, solution);
+ cs_free(lhs);
+ return ok;
+}
+#else
+bool SparseSchurComplementSolver::SolveReducedLinearSystemUsingCXSparse(
+ double* solution) {
+ LOG(FATAL) << "No CXSparse support in Ceres.";
+ return false;
+}
+#endif // CERES_NO_CXPARSE
+
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/schur_complement_solver.h b/internal/ceres/schur_complement_solver.h
index 7cd019a..b788ce0 100644
--- a/internal/ceres/schur_complement_solver.h
+++ b/internal/ceres/schur_complement_solver.h
@@ -145,7 +145,7 @@
DISALLOW_COPY_AND_ASSIGN(DenseSchurComplementSolver);
};
-#ifndef CERES_NO_SUITESPARSE
+
// Sparse Cholesky factorization based solver.
class SparseSchurComplementSolver : public SchurComplementSolver {
public:
@@ -155,26 +155,17 @@
private:
virtual void InitStorage(const CompressedRowBlockStructure* bs);
virtual bool SolveReducedLinearSystem(double* solution);
+ bool SolveReducedLinearSystemUsingSuiteSparse(double* solution);
+ bool SolveReducedLinearSystemUsingCXSparse(double* solution);
-
+#ifndef CERES_NO_SUITESPARSE
SuiteSparse ss_;
// Symbolic factorization of the reduced linear system. Precomputed
// once and reused in subsequent calls.
cholmod_factor* symbolic_factor_;
+#endif // CERES_NO_SUITESPARSE
DISALLOW_COPY_AND_ASSIGN(SparseSchurComplementSolver);
};
-#else // CERES_NO_SUITESPARSE
-class SparseSchurComplementSolver : public SchurComplementSolver {
- public:
- explicit SparseSchurComplementSolver(const LinearSolver::Options& options)
- : SchurComplementSolver(options) {
- LOG(FATAL) << "SPARSE_SCHUR is not available. Please "
- "build Ceres with SuiteSparse.";
- }
-
- virtual ~SparseSchurComplementSolver() {}
-};
-#endif // CERES_NO_SUITESPARSE
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/schur_complement_solver_test.cc b/internal/ceres/schur_complement_solver_test.cc
index 5e0cc40..ea5c346 100644
--- a/internal/ceres/schur_complement_solver_test.cc
+++ b/internal/ceres/schur_complement_solver_test.cc
@@ -92,13 +92,17 @@
sol_d.get());
}
- void ComputeAndCompareSolutions(int problem_id,
- bool regularization,
- ceres::LinearSolverType linear_solver_type) {
+ void ComputeAndCompareSolutions(
+ int problem_id,
+ bool regularization,
+ ceres::LinearSolverType linear_solver_type,
+ ceres::SparseLinearAlgebraLibraryType sparse_linear_algebra_library) {
SetUpFromProblemId(problem_id);
LinearSolver::Options options;
options.num_eliminate_blocks = num_eliminate_blocks;
options.type = linear_solver_type;
+ options.sparse_linear_algebra_library = sparse_linear_algebra_library;
+
scoped_ptr<LinearSolver> solver(LinearSolver::Create(options));
LinearSolver::PerSolveOptions per_solve_options;
@@ -133,21 +137,30 @@
};
#ifndef CERES_NO_SUITESPARSE
-
-TEST_F(SchurComplementSolverTest, SparseSchur) {
- ComputeAndCompareSolutions(2, false, SPARSE_SCHUR);
- ComputeAndCompareSolutions(3, false, SPARSE_SCHUR);
- ComputeAndCompareSolutions(2, true, SPARSE_SCHUR);
- ComputeAndCompareSolutions(3, true, SPARSE_SCHUR);
+TEST_F(SchurComplementSolverTest, SparseSchurWithSuiteSparse) {
+ ComputeAndCompareSolutions(2, false, SPARSE_SCHUR, SUITE_SPARSE);
+ ComputeAndCompareSolutions(3, false, SPARSE_SCHUR, SUITE_SPARSE);
+ ComputeAndCompareSolutions(2, true, SPARSE_SCHUR, SUITE_SPARSE);
+ ComputeAndCompareSolutions(3, true, SPARSE_SCHUR, SUITE_SPARSE);
}
-
#endif // CERES_NO_SUITESPARSE
+#ifndef CERES_NO_CXSPARSE
+TEST_F(SchurComplementSolverTest, SparseSchurWithCXSparse) {
+ ComputeAndCompareSolutions(2, false, SPARSE_SCHUR, CX_SPARSE);
+ ComputeAndCompareSolutions(3, false, SPARSE_SCHUR, CX_SPARSE);
+ ComputeAndCompareSolutions(2, true, SPARSE_SCHUR, CX_SPARSE);
+ ComputeAndCompareSolutions(3, true, SPARSE_SCHUR, CX_SPARSE);
+}
+#endif // CERES_NO_CXSPARSE
+
TEST_F(SchurComplementSolverTest, DenseSchur) {
- ComputeAndCompareSolutions(2, false, DENSE_SCHUR);
- ComputeAndCompareSolutions(3, false, DENSE_SCHUR);
- ComputeAndCompareSolutions(2, true, DENSE_SCHUR);
- ComputeAndCompareSolutions(3, true, DENSE_SCHUR);
+ // The sparse linear algebra library type is ignored for
+ // DENSE_SCHUR.
+ ComputeAndCompareSolutions(2, false, DENSE_SCHUR, SUITE_SPARSE);
+ ComputeAndCompareSolutions(3, false, DENSE_SCHUR, SUITE_SPARSE);
+ ComputeAndCompareSolutions(2, true, DENSE_SCHUR, SUITE_SPARSE);
+ ComputeAndCompareSolutions(3, true, DENSE_SCHUR, SUITE_SPARSE);
}
} // namespace internal
diff --git a/internal/ceres/solver_impl.cc b/internal/ceres/solver_impl.cc
index 2ec3dee..6f8fa17 100644
--- a/internal/ceres/solver_impl.cc
+++ b/internal/ceres/solver_impl.cc
@@ -432,12 +432,23 @@
LinearSolver* SolverImpl::CreateLinearSolver(Solver::Options* options,
string* error) {
#ifdef CERES_NO_SUITESPARSE
- if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY) {
- *error = "Can't use SPARSE_NORMAL_CHOLESKY because SuiteSparse was not "
- "enabled when Ceres was built.";
+ if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY &&
+ options->sparse_linear_algebra_library == SUITE_SPARSE) {
+ *error = "Can't use SPARSE_NORMAL_CHOLESKY with SUITESPARSE because "
+ "SuiteSparse was not enabled when Ceres was built.";
return NULL;
}
-#endif // CERES_NO_SUITESPARSE
+#endif
+
+#ifdef CERES_NO_CXSPARSE
+ if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY &&
+ options->sparse_linear_algebra_library == CX_SPARSE) {
+ *error = "Can't use SPARSE_NORMAL_CHOLESKY with CXSPARSE because "
+ "CXSparse was not enabled when Ceres was built.";
+ return NULL;
+ }
+#endif
+
if (options->linear_solver_max_num_iterations <= 0) {
*error = "Solver::Options::linear_solver_max_num_iterations is 0.";
@@ -461,23 +472,25 @@
options->linear_solver_max_num_iterations;
linear_solver_options.type = options->linear_solver_type;
linear_solver_options.preconditioner_type = options->preconditioner_type;
+ linear_solver_options.sparse_linear_algebra_library =
+ options->sparse_linear_algebra_library;
#ifdef CERES_NO_SUITESPARSE
if (linear_solver_options.preconditioner_type == SCHUR_JACOBI) {
*error = "SCHUR_JACOBI preconditioner not suppored. Please build Ceres "
- "with SuiteSparse support";
+ "with SuiteSparse support.";
return NULL;
}
if (linear_solver_options.preconditioner_type == CLUSTER_JACOBI) {
*error = "CLUSTER_JACOBI preconditioner not suppored. Please build Ceres "
- "with SuiteSparse support";
+ "with SuiteSparse support.";
return NULL;
}
if (linear_solver_options.preconditioner_type == CLUSTER_TRIDIAGONAL) {
*error = "CLUSTER_TRIDIAGONAL preconditioner not suppored. Please build "
- "Ceres with SuiteSparse support";
+ "Ceres with SuiteSparse support.";
return NULL;
}
#endif
@@ -488,23 +501,23 @@
if ((linear_solver_options.num_eliminate_blocks == 0) &&
IsSchurType(linear_solver_options.type)) {
-#ifndef CERES_NO_SUITESPARSE
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
+ LOG(INFO) << "No elimination block remaining switching to DENSE_QR.";
+ linear_solver_options.type = DENSE_QR;
+#else
LOG(INFO) << "No elimination block remaining "
<< "switching to SPARSE_NORMAL_CHOLESKY.";
linear_solver_options.type = SPARSE_NORMAL_CHOLESKY;
-#else
- LOG(INFO) << "No elimination block remaining switching to DENSE_QR.";
- linear_solver_options.type = DENSE_QR;
-#endif // CERES_NO_SUITESPARSE
+#endif
}
-#ifdef CERES_NO_SUITESPARSE
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
if (linear_solver_options.type == SPARSE_SCHUR) {
- *error = "Can't use SPARSE_SCHUR because SuiteSparse was not "
- "enabled when Ceres was built.";
+ *error = "Can't use SPARSE_SCHUR because neither SuiteSparse nor"
+ "CXSparse was enabled when Ceres was compiled.";
return NULL;
}
-#endif // CERES_NO_SUITESPARSE
+#endif
// The matrix used for storing the dense Schur complement has a
// single lock guarding the whole matrix. Running the
diff --git a/internal/ceres/solver_impl_test.cc b/internal/ceres/solver_impl_test.cc
index 2abca63..20190f1 100644
--- a/internal/ceres/solver_impl_test.cc
+++ b/internal/ceres/solver_impl_test.cc
@@ -432,14 +432,14 @@
EXPECT_EQ(parameter_blocks[2]->user_state(), &y);
}
-#ifdef CERES_NO_SUITESPARSE
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
TEST(SolverImpl, CreateLinearSolverNoSuiteSparse) {
Solver::Options options;
options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
string error;
EXPECT_FALSE(SolverImpl::CreateLinearSolver(&options, &error));
}
-#endif // CERES_NO_SUITESPARSE
+#endif
TEST(SolverImpl, CreateLinearSolverNegativeMaxNumIterations) {
Solver::Options options;
@@ -477,11 +477,12 @@
scoped_ptr<LinearSolver> solver(
SolverImpl::CreateLinearSolver(&options, &error));
EXPECT_TRUE(solver != NULL);
-#ifndef CERES_NO_SUITESPARSE
- EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
-#else
+
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
EXPECT_EQ(options.linear_solver_type, DENSE_QR);
-#endif // CERES_NO_SUITESPARSE
+#else
+ EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
+#endif
}
TEST(SolverImpl, CreateLinearSolverDenseSchurMultipleThreads) {
@@ -508,10 +509,19 @@
#ifndef CERES_NO_SUITESPARSE
options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+ options.sparse_linear_algebra_library = SUITE_SPARSE;
solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
EXPECT_TRUE(solver.get() != NULL);
-#endif // CERES_NO_SUITESPARSE
+#endif
+
+#ifndef CERES_NO_CXSPARSE
+ options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+ options.sparse_linear_algebra_library = CX_SPARSE;
+ solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
+ EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
+ EXPECT_TRUE(solver.get() != NULL);
+#endif
options.linear_solver_type = DENSE_SCHUR;
options.num_eliminate_blocks = 2;
@@ -521,13 +531,14 @@
options.linear_solver_type = SPARSE_SCHUR;
options.num_eliminate_blocks = 2;
-#ifndef CERES_NO_SUITESPARSE
solver.reset(SolverImpl::CreateLinearSolver(&options, &error));
+
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE)
+ EXPECT_TRUE(SolverImpl::CreateLinearSolver(&options, &error) == NULL);
+#else
EXPECT_TRUE(solver.get() != NULL);
EXPECT_EQ(options.linear_solver_type, SPARSE_SCHUR);
-#else // CERES_NO_SUITESPARSE
- EXPECT_TRUE(SolverImpl::CreateLinearSolver(&options, &error) == NULL);
-#endif // CERES_NO_SUITESPARSE
+#endif
options.linear_solver_type = ITERATIVE_SCHUR;
options.num_eliminate_blocks = 2;
diff --git a/internal/ceres/sparse_normal_cholesky_solver.cc b/internal/ceres/sparse_normal_cholesky_solver.cc
index d4c2f3d..5a082de 100644
--- a/internal/ceres/sparse_normal_cholesky_solver.cc
+++ b/internal/ceres/sparse_normal_cholesky_solver.cc
@@ -28,13 +28,16 @@
//
// Author: sameeragarwal@google.com (Sameer Agarwal)
-#ifndef CERES_NO_SUITESPARSE
-
#include "ceres/sparse_normal_cholesky_solver.h"
#include <algorithm>
#include <cstring>
#include <ctime>
+
+#ifndef CERES_NO_CXSPARSE
+#include "cs.h"
+#endif
+
#include "ceres/compressed_row_sparse_matrix.h"
#include "ceres/linear_solver.h"
#include "ceres/suitesparse.h"
@@ -43,18 +46,26 @@
#include "ceres/internal/scoped_ptr.h"
#include "ceres/types.h"
+
+
namespace ceres {
namespace internal {
SparseNormalCholeskySolver::SparseNormalCholeskySolver(
const LinearSolver::Options& options)
- : options_(options), symbolic_factor_(NULL) {}
+ : options_(options) {
+#ifndef CERES_NO_SUITESPARSE
+ symbolic_factor_ = NULL;
+#endif
+}
SparseNormalCholeskySolver::~SparseNormalCholeskySolver() {
+#ifndef CERES_NO_SUITESPARSE
if (symbolic_factor_ != NULL) {
ss_.Free(symbolic_factor_);
symbolic_factor_ = NULL;
}
+#endif
}
LinearSolver::Summary SparseNormalCholeskySolver::SolveImpl(
@@ -62,6 +73,94 @@
const double* b,
const LinearSolver::PerSolveOptions& per_solve_options,
double * x) {
+ switch (options_.sparse_linear_algebra_library) {
+ case SUITE_SPARSE:
+ return SolveImplUsingSuiteSparse(A, b, per_solve_options, x);
+ case CX_SPARSE:
+ return SolveImplUsingCXSparse(A, b, per_solve_options, x);
+ default:
+ LOG(FATAL) << "Unknown sparse linear algebra library : "
+ << options_.sparse_linear_algebra_library;
+ }
+
+ LOG(FATAL) << "Unknown sparse linear algebra library : "
+ << options_.sparse_linear_algebra_library;
+ return LinearSolver::Summary();
+}
+
+#ifndef CERES_NO_CXSPARSE
+LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingCXSparse(
+ CompressedRowSparseMatrix* A,
+ const double* b,
+ const LinearSolver::PerSolveOptions& per_solve_options,
+ double * x) {
+ LinearSolver::Summary summary;
+ summary.num_iterations = 1;
+ const int num_cols = A->num_cols();
+ Vector Atb = Vector::Zero(num_cols);
+ A->LeftMultiply(b, Atb.data());
+
+ if (per_solve_options.D != NULL) {
+ // Temporarily append a diagonal block to the A matrix, but undo
+ // it before returning the matrix to the user.
+ CompressedRowSparseMatrix D(per_solve_options.D, num_cols);
+ A->AppendRows(D);
+ }
+
+ VectorRef(x, num_cols).setZero();
+
+ // Wrap the augmented Jacobian in a compressed sparse column matrix.
+ cs_di At;
+ At.m = A->num_cols();
+ At.n = A->num_rows();
+ At.nz = -1;
+ At.nzmax = A->num_nonzeros();
+ At.p = A->mutable_rows();
+ At.i = A->mutable_cols();
+ At.x = A->mutable_values();
+
+ // Compute the normal equations. J'J delta = J'f and solve them
+ // using a sparse Cholesky factorization. Notice that when compared
+ // to SuiteSparse we have to explicitly compute the transpose of Jt,
+ // and then the normal equations before they can be
+ // factorized. CHOLMOD/SuiteSparse on the other hand can just work
+ // off of Jt to compute the Cholesky factorization of the normal
+ // equations.
+ cs_di* A2 = cs_transpose(&At, 1);
+ cs_di* AtA = cs_multiply(&At,A2);
+
+ cs_free(A2);
+ if (per_solve_options.D != NULL) {
+ A->DeleteRows(num_cols);
+ }
+
+ // This recomputes the symbolic factorization every time it is
+ // invoked. It will perhaps be worth it to cache the symbolic
+ // factorization the way we do for SuiteSparse.
+ if (cs_cholsol(1, AtA, Atb.data())) {
+ VectorRef(x, Atb.rows()) = Atb;
+ summary.termination_type = TOLERANCE;
+ }
+
+ cs_free(AtA);
+ return summary;
+}
+#else
+LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingCXSparse(
+ CompressedRowSparseMatrix* A,
+ const double* b,
+ const LinearSolver::PerSolveOptions& per_solve_options,
+ double * x) {
+ LOG(FATAL) << "No CXSparse support in Ceres.";
+}
+#endif
+
+#ifndef CERES_NO_SUITESPARSE
+LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingSuiteSparse(
+ CompressedRowSparseMatrix* A,
+ const double* b,
+ const LinearSolver::PerSolveOptions& per_solve_options,
+ double * x) {
const time_t start_time = time(NULL);
const int num_cols = A->num_cols();
@@ -117,8 +216,15 @@
<< " cleanup: " << cleanup_time - solve_time;
return summary;
}
+#else
+LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingSuiteSparse(
+ CompressedRowSparseMatrix* A,
+ const double* b,
+ const LinearSolver::PerSolveOptions& per_solve_options,
+ double * x) {
+ LOG(FATAL) << "No SuiteSparse support in Ceres.";
+}
+#endif
} // namespace internal
} // namespace ceres
-
-#endif // CERES_NO_SUITESPARSE
diff --git a/internal/ceres/sparse_normal_cholesky_solver.h b/internal/ceres/sparse_normal_cholesky_solver.h
index ce1d6d2..5445559 100644
--- a/internal/ceres/sparse_normal_cholesky_solver.h
+++ b/internal/ceres/sparse_normal_cholesky_solver.h
@@ -35,11 +35,11 @@
#define CERES_INTERNAL_SPARSE_NORMAL_CHOLESKY_SOLVER_H_
#ifndef CERES_NO_SUITESPARSE
-
#include "cholmod.h"
-#include "cholmod_core.h"
-#include "ceres/linear_solver.h"
#include "ceres/suitesparse.h"
+#endif // CERES_NO_SUITESPARSE
+
+#include "ceres/linear_solver.h"
#include "ceres/internal/macros.h"
namespace ceres {
@@ -61,17 +61,31 @@
const LinearSolver::PerSolveOptions& options,
double* x);
- const LinearSolver::Options options_;
- SuiteSparse ss_;
+ LinearSolver::Summary SolveImplUsingSuiteSparse(
+ CompressedRowSparseMatrix* A,
+ const double* b,
+ const LinearSolver::PerSolveOptions& options,
+ double* x);
+ // Crashes if CSparse is not installed.
+ LinearSolver::Summary SolveImplUsingCXSparse(
+ CompressedRowSparseMatrix* A,
+ const double* b,
+ const LinearSolver::PerSolveOptions& options,
+ double* x);
+
+#ifndef CERES_NO_SUITESPARSE
+ SuiteSparse ss_;
// Cached factorization
cholmod_factor* symbolic_factor_;
+#endif // CERES_NO_SUITESPARSE
+
+
+ const LinearSolver::Options options_;
DISALLOW_COPY_AND_ASSIGN(SparseNormalCholeskySolver);
};
} // namespace internal
} // namespace ceres
-#endif // CERES_NO_SUITESPARSE
-
#endif // CERES_INTERNAL_SPARSE_NORMAL_CHOLESKY_SOLVER_H_
diff --git a/internal/ceres/system_test.cc b/internal/ceres/system_test.cc
index a9a6747..8e2f5eb 100644
--- a/internal/ceres/system_test.cc
+++ b/internal/ceres/system_test.cc
@@ -62,32 +62,39 @@
// Struct used for configuring the solver.
struct SolverConfig {
SolverConfig(LinearSolverType linear_solver_type,
+ SparseLinearAlgebraLibraryType sparse_linear_algebra_library,
OrderingType ordering_type)
: linear_solver_type(linear_solver_type),
+ sparse_linear_algebra_library(sparse_linear_algebra_library),
ordering_type(ordering_type),
preconditioner_type(IDENTITY),
num_threads(1) {
}
SolverConfig(LinearSolverType linear_solver_type,
+ SparseLinearAlgebraLibraryType sparse_linear_algebra_library,
OrderingType ordering_type,
PreconditionerType preconditioner_type,
int num_threads)
: linear_solver_type(linear_solver_type),
+ sparse_linear_algebra_library(sparse_linear_algebra_library),
ordering_type(ordering_type),
preconditioner_type(preconditioner_type),
num_threads(num_threads) {
}
string ToString() const {
- return StringPrintf("(%s, %s, %s, %d)",
- LinearSolverTypeToString(linear_solver_type),
- OrderingTypeToString(ordering_type),
- PreconditionerTypeToString(preconditioner_type),
- num_threads);
+ return StringPrintf(
+ "(%s, %s, %s, %s, %d)",
+ LinearSolverTypeToString(linear_solver_type),
+ SparseLinearAlgebraLibraryTypeToString(sparse_linear_algebra_library),
+ OrderingTypeToString(ordering_type),
+ PreconditionerTypeToString(preconditioner_type),
+ num_threads);
}
LinearSolverType linear_solver_type;
+ SparseLinearAlgebraLibraryType sparse_linear_algebra_library;
OrderingType ordering_type;
PreconditionerType preconditioner_type;
int num_threads;
@@ -122,6 +129,8 @@
Solver::Options& options = *(system_test_problem->mutable_solver_options());
options.linear_solver_type = config.linear_solver_type;
+ options.sparse_linear_algebra_library =
+ config.sparse_linear_algebra_library;
options.ordering_type = config.ordering_type;
options.preconditioner_type = config.preconditioner_type;
options.num_threads = config.num_threads;
@@ -266,19 +275,31 @@
};
TEST(SystemTest, PowellsFunction) {
- vector<SolverConfig> configurations;
- configurations.push_back(SolverConfig(DENSE_QR, NATURAL));
- configurations.push_back(SolverConfig(DENSE_SCHUR, SCHUR));
+ vector<SolverConfig> configs;
+#define CONFIGURE(linear_solver, sparse_linear_algebra_library, ordering) \
+ configs.push_back(SolverConfig(linear_solver, \
+ sparse_linear_algebra_library, \
+ ordering))
+
+ CONFIGURE(DENSE_QR, SUITE_SPARSE, NATURAL);
+ CONFIGURE(DENSE_SCHUR, SUITE_SPARSE, SCHUR);
#ifndef CERES_NO_SUITESPARSE
- configurations.push_back(SolverConfig(SPARSE_NORMAL_CHOLESKY, NATURAL));
- configurations.push_back(SolverConfig(SPARSE_SCHUR, SCHUR));
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, NATURAL);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, SCHUR);
#endif // CERES_NO_SUITESPARSE
- configurations.push_back(SolverConfig(ITERATIVE_SCHUR, SCHUR));
+#ifndef CERES_NO_CXSPARSE
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, NATURAL);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, SCHUR);
+#endif // CERES_NO_CXSPARSE
+
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, SCHUR);
+
+#undef CONFIGURE
+
const double kMaxAbsoluteDifference = 1e-8;
- RunSolversAndCheckTheyMatch<PowellsFunction>(configurations,
- kMaxAbsoluteDifference);
+ RunSolversAndCheckTheyMatch<PowellsFunction>(configs, kMaxAbsoluteDifference);
}
// This class implements the SystemTestProblem interface and provides
@@ -462,36 +483,50 @@
TEST(SystemTest, BundleAdjustmentProblem) {
vector<SolverConfig> configs;
-#define CONFIGURE(a, b, c, d) configs.push_back(SolverConfig(a, b, c, d))
+#define CONFIGURE(linear_solver, sparse_linear_algebra_library, ordering, preconditioner, threads) \
+ configs.push_back(SolverConfig(linear_solver, \
+ sparse_linear_algebra_library, \
+ ordering, \
+ preconditioner, \
+ threads))
#ifndef CERES_NO_SUITESPARSE
- CONFIGURE(SPARSE_NORMAL_CHOLESKY, NATURAL, IDENTITY, 1);
- CONFIGURE(SPARSE_NORMAL_CHOLESKY, USER, IDENTITY, 1);
- CONFIGURE(SPARSE_NORMAL_CHOLESKY, SCHUR, IDENTITY, 1);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, NATURAL, IDENTITY, 1);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, USER, IDENTITY, 1);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE, SCHUR, IDENTITY, 1);
- CONFIGURE(SPARSE_SCHUR, USER, IDENTITY, 1);
- CONFIGURE(SPARSE_SCHUR, SCHUR, IDENTITY, 1);
+ CONFIGURE(SPARSE_SCHUR, SUITE_SPARSE, USER, IDENTITY, 1);
+ CONFIGURE(SPARSE_SCHUR, SUITE_SPARSE, SCHUR, IDENTITY, 1);
#endif // CERES_NO_SUITESPARSE
- CONFIGURE(DENSE_SCHUR, USER, IDENTITY, 1);
- CONFIGURE(DENSE_SCHUR, SCHUR, IDENTITY, 1);
+#ifndef CERES_NO_CXSPARSE
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, NATURAL, IDENTITY, 1);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, USER, IDENTITY, 1);
+ CONFIGURE(SPARSE_NORMAL_CHOLESKY, CX_SPARSE, SCHUR, IDENTITY, 1);
- CONFIGURE(CGNR, USER, JACOBI, 1);
+ CONFIGURE(SPARSE_SCHUR, CX_SPARSE, USER, IDENTITY, 1);
+ CONFIGURE(SPARSE_SCHUR, CX_SPARSE, SCHUR, IDENTITY, 1);
+#endif // CERES_NO_CXSPARSE
- CONFIGURE(ITERATIVE_SCHUR, USER, JACOBI, 1);
+ CONFIGURE(DENSE_SCHUR, SUITE_SPARSE, USER, IDENTITY, 1);
+ CONFIGURE(DENSE_SCHUR, SUITE_SPARSE, SCHUR, IDENTITY, 1);
+
+ CONFIGURE(CGNR, SUITE_SPARSE, USER, JACOBI, 1);
+
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, USER, JACOBI, 1);
#ifndef CERES_NO_SUITESPARSE
- CONFIGURE(ITERATIVE_SCHUR, USER, SCHUR_JACOBI, 1);
- CONFIGURE(ITERATIVE_SCHUR, USER, CLUSTER_JACOBI, 1);
- CONFIGURE(ITERATIVE_SCHUR, USER, CLUSTER_TRIDIAGONAL, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, USER, SCHUR_JACOBI, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, USER, CLUSTER_JACOBI, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, USER, CLUSTER_TRIDIAGONAL, 1);
#endif // CERES_NO_SUITESPARSE
- CONFIGURE(ITERATIVE_SCHUR, SCHUR, JACOBI, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, SCHUR, JACOBI, 1);
#ifndef CERES_NO_SUITESPARSE
- CONFIGURE(ITERATIVE_SCHUR, SCHUR, SCHUR_JACOBI, 1);
- CONFIGURE(ITERATIVE_SCHUR, SCHUR, CLUSTER_JACOBI, 1);
- CONFIGURE(ITERATIVE_SCHUR, SCHUR, CLUSTER_TRIDIAGONAL, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, SCHUR, SCHUR_JACOBI, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, SCHUR, CLUSTER_JACOBI, 1);
+ CONFIGURE(ITERATIVE_SCHUR, SUITE_SPARSE, SCHUR, CLUSTER_TRIDIAGONAL, 1);
#endif // CERES_NO_SUITESPARSE
#undef CONFIGURE
diff --git a/internal/ceres/types.cc b/internal/ceres/types.cc
index 860f8a4..c6c4dea 100644
--- a/internal/ceres/types.cc
+++ b/internal/ceres/types.cc
@@ -61,6 +61,16 @@
}
}
+const char* SparseLinearAlgebraLibraryTypeToString(
+ SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type) {
+ switch (sparse_linear_algebra_library_type) {
+ CASESTR(SUITE_SPARSE);
+ CASESTR(CX_SPARSE);
+ default:
+ return "UNKNOWN";
+ }
+}
+
const char* OrderingTypeToString(OrderingType ordering_type) {
switch (ordering_type) {
CASESTR(NATURAL);
diff --git a/internal/ceres/unsymmetric_linear_solver_test.cc b/internal/ceres/unsymmetric_linear_solver_test.cc
index be91056..a7ee23e 100644
--- a/internal/ceres/unsymmetric_linear_solver_test.cc
+++ b/internal/ceres/unsymmetric_linear_solver_test.cc
@@ -52,109 +52,82 @@
A_.reset(down_cast<TripletSparseMatrix*>(problem->A.release()));
b_.reset(problem->b.release());
D_.reset(problem->D.release());
- sol1_.reset(problem->x.release());
- sol2_.reset(problem->x_D.release());
- x_.reset(new double[A_->num_cols()]);
+ sol_unregularized_.reset(problem->x.release());
+ sol_regularized_.reset(problem->x_D.release());
}
- void TestSolver(LinearSolverType linear_solver_type) {
+ void TestSolver(
+ LinearSolverType linear_solver_type,
+ SparseLinearAlgebraLibraryType sparse_linear_algebra_library) {
LinearSolver::Options options;
options.type = linear_solver_type;
+ options.sparse_linear_algebra_library = sparse_linear_algebra_library;
scoped_ptr<LinearSolver> solver(LinearSolver::Create(options));
LinearSolver::PerSolveOptions per_solve_options;
+ LinearSolver::Summary unregularized_solve_summary;
+ LinearSolver::Summary regularized_solve_summary;
+ Vector x_unregularized(A_->num_cols());
+ Vector x_regularized(A_->num_cols());
- // Unregularized
- LinearSolver::Summary summary =
- solver->Solve(A_.get(), b_.get(), per_solve_options, x_.get());
+ scoped_ptr<SparseMatrix> transformed_A;
- EXPECT_EQ(summary.termination_type, TOLERANCE);
-
- for (int i = 0; i < A_->num_cols(); ++i) {
- EXPECT_NEAR(sol1_[i], x_[i], 1e-8);
+ if (linear_solver_type == DENSE_QR) {
+ transformed_A.reset(new DenseSparseMatrix(*A_));
+ } else if (linear_solver_type == SPARSE_NORMAL_CHOLESKY) {
+ transformed_A.reset(new CompressedRowSparseMatrix(*A_));
+ } else {
+ LOG(FATAL) << "Unknown linear solver : " << linear_solver_type;
}
+ // Unregularized
+ unregularized_solve_summary =
+ solver->Solve(transformed_A.get(),
+ b_.get(),
+ per_solve_options,
+ x_unregularized.data());
// Regularized solution
per_solve_options.D = D_.get();
- summary = solver->Solve(A_.get(), b_.get(), per_solve_options, x_.get());
+ regularized_solve_summary =
+ solver->Solve(transformed_A.get(),
+ b_.get(),
+ per_solve_options,
+ x_regularized.data());
- EXPECT_EQ(summary.termination_type, TOLERANCE);
+ EXPECT_EQ(unregularized_solve_summary.termination_type, TOLERANCE);
for (int i = 0; i < A_->num_cols(); ++i) {
- EXPECT_NEAR(sol2_[i], x_[i], 1e-8);
+ EXPECT_NEAR(sol_unregularized_[i], x_unregularized[i], 1e-8);
+ }
+
+ EXPECT_EQ(regularized_solve_summary.termination_type, TOLERANCE);
+ for (int i = 0; i < A_->num_cols(); ++i) {
+ EXPECT_NEAR(sol_regularized_[i], x_regularized[i], 1e-8);
}
}
scoped_ptr<TripletSparseMatrix> A_;
scoped_array<double> b_;
scoped_array<double> D_;
- scoped_array<double> sol1_;
- scoped_array<double> sol2_;
-
- scoped_array<double> x_;
+ scoped_array<double> sol_unregularized_;
+ scoped_array<double> sol_regularized_;
};
-// TODO(keir): Reduce duplication.
TEST_F(UnsymmetricLinearSolverTest, DenseQR) {
- LinearSolver::Options options;
- options.type = DENSE_QR;
- scoped_ptr<LinearSolver> solver(LinearSolver::Create(options));
-
- LinearSolver::PerSolveOptions per_solve_options;
- DenseSparseMatrix A(*A_);
-
- // Unregularized
- LinearSolver::Summary summary =
- solver->Solve(&A, b_.get(), per_solve_options, x_.get());
-
- EXPECT_EQ(summary.termination_type, TOLERANCE);
- for (int i = 0; i < A_->num_cols(); ++i) {
- EXPECT_NEAR(sol1_[i], x_[i], 1e-8);
- }
-
- VectorRef x(x_.get(), A_->num_cols());
- VectorRef b(b_.get(), A_->num_rows());
- Vector r = A.matrix()*x - b;
- LOG(INFO) << "r = A*x - b: \n" << r;
-
- // Regularized solution
- per_solve_options.D = D_.get();
- summary = solver->Solve(&A, b_.get(), per_solve_options, x_.get());
-
- EXPECT_EQ(summary.termination_type, TOLERANCE);
- for (int i = 0; i < A_->num_cols(); ++i) {
- EXPECT_NEAR(sol2_[i], x_[i], 1e-8);
- }
+ TestSolver(DENSE_QR, SUITE_SPARSE);
}
#ifndef CERES_NO_SUITESPARSE
-TEST_F(UnsymmetricLinearSolverTest, SparseNormalCholesky) {
- LinearSolver::Options options;
- options.type = SPARSE_NORMAL_CHOLESKY;
- scoped_ptr<LinearSolver>solver(LinearSolver::Create(options));
-
- LinearSolver::PerSolveOptions per_solve_options;
- CompressedRowSparseMatrix A(*A_);
-
- // Unregularized
- LinearSolver::Summary summary =
- solver->Solve(&A, b_.get(), per_solve_options, x_.get());
-
- EXPECT_EQ(summary.termination_type, TOLERANCE);
- for (int i = 0; i < A_->num_cols(); ++i) {
- EXPECT_NEAR(sol1_[i], x_[i], 1e-8);
- }
-
- // Regularized solution
- per_solve_options.D = D_.get();
- summary = solver->Solve(&A, b_.get(), per_solve_options, x_.get());
-
- EXPECT_EQ(summary.termination_type, TOLERANCE);
- for (int i = 0; i < A_->num_cols(); ++i) {
- EXPECT_NEAR(sol2_[i], x_[i], 1e-8);
- }
+TEST_F(UnsymmetricLinearSolverTest, SparseNormalCholeskyUsingSuiteSparse) {
+ TestSolver(SPARSE_NORMAL_CHOLESKY, SUITE_SPARSE);
}
-#endif // CERES_NO_SUITESPARSE
+#endif
+
+#ifndef CERES_NO_CXSPARSE
+TEST_F(UnsymmetricLinearSolverTest, SparseNormalCholeskyUsingCXSparse) {
+ TestSolver(SPARSE_NORMAL_CHOLESKY, CX_SPARSE);
+}
+#endif
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/visibility_based_preconditioner.h b/internal/ceres/visibility_based_preconditioner.h
index 5f14842..c265a0e 100644
--- a/internal/ceres/visibility_based_preconditioner.h
+++ b/internal/ceres/visibility_based_preconditioner.h
@@ -261,7 +261,7 @@
virtual void LeftMultiply(const double* x, double* y) const {}
virtual int num_rows() const { return -1; }
virtual int num_cols() const { return -1; }
- bool Compute(const BlockSparseMatrixBase& A, const double* D) {
+ bool Update(const BlockSparseMatrixBase& A, const double* D) {
return false;
}
};