Refactor suitesparse.h/cc
1. Generalize SuiteSparse::AnalyzeCholesky and
SuiteSparse::BlockAnalyzeCholesky from just doing AMD to taking
OrderingType as an argument and using that to determine whether
AMD & Nested Dissection algorithms are used for computing the
fill-reducing ordering or a natural ordering when computing
the symbolic factorization.
2. Remove AnalyzeCholeskyWithNaturalOrdering.
3. Replace and generalize SuiteSparse::BlockAMDOrdering with
SuiteSparse::BlockOrdering which also takes OrderingType as an
argument. Same for SuiteSparse::ApproximateMinimumDegreeOrdering
and SuiteSparse::NestedDissectionOrdering by
SuiteSparse::Ordering.
4. Remove LinearSolver::Options::use_postordering and replace it
with LinearSolver::Options::ordering_type.
5. Replace Preconditioner::Options::use_postordering and replace it
with Preconditioner::Options::ordering_type.
6. Add NESDIS to OrderingType. With the above changes, the linear
solvers can now use Nested Dissection once this information
is piped through the nonlinear solver.
Change-Id: Ib8e93fbf34ae2981bf2ac54dcda9e25c7c213790
diff --git a/internal/ceres/cgnr_solver.cc b/internal/ceres/cgnr_solver.cc
index c7cf4f8..12e2ef9 100644
--- a/internal/ceres/cgnr_solver.cc
+++ b/internal/ceres/cgnr_solver.cc
@@ -80,7 +80,7 @@
options_.subset_preconditioner_start_row_block;
preconditioner_options.sparse_linear_algebra_library_type =
options_.sparse_linear_algebra_library_type;
- preconditioner_options.use_postordering = options_.use_postordering;
+ preconditioner_options.ordering_type = options_.ordering_type;
preconditioner_options.num_threads = options_.num_threads;
preconditioner_options.context = options_.context;
preconditioner_ =
diff --git a/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc b/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc
index f31004b..3abfb29 100644
--- a/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc
+++ b/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc
@@ -254,7 +254,8 @@
const int num_cols = A->num_cols();
cholmod_sparse lhs = ss.CreateSparseMatrixTransposeView(A);
event_logger.AddEvent("Setup");
- cholmod_factor* factor = ss.AnalyzeCholesky(&lhs, &summary.message);
+ cholmod_factor* factor =
+ ss.AnalyzeCholesky(&lhs, options_.ordering_type, &summary.message);
event_logger.AddEvent("Analysis");
if (factor == nullptr) {
diff --git a/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc b/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc
index f3bf650..2621690 100644
--- a/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc
+++ b/internal/ceres/dynamic_sparse_normal_cholesky_solver_test.cc
@@ -93,12 +93,14 @@
}
void TestSolver(
- const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type) {
+ const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
+ const OrderingType ordering_type) {
LinearSolver::Options options;
options.type = SPARSE_NORMAL_CHOLESKY;
options.dynamic_sparsity = true;
options.sparse_linear_algebra_library_type =
sparse_linear_algebra_library_type;
+ options.ordering_type = ordering_type;
ContextImpl context;
options.context = &context;
TestSolver(options, nullptr);
@@ -111,20 +113,25 @@
};
#ifndef CERES_NO_SUITESPARSE
-TEST_F(DynamicSparseNormalCholeskySolverTest, SuiteSparse) {
- TestSolver(SUITE_SPARSE);
+TEST_F(DynamicSparseNormalCholeskySolverTest, SuiteSparseAMD) {
+ TestSolver(SUITE_SPARSE, OrderingType::AMD);
}
+#ifndef CERES_NO_METIS
+TEST_F(DynamicSparseNormalCholeskySolverTest, SuiteSparseNESDIS) {
+ TestSolver(SUITE_SPARSE, OrderingType::NESDIS);
+}
+#endif
#endif
#ifndef CERES_NO_CXSPARSE
TEST_F(DynamicSparseNormalCholeskySolverTest, CXSparse) {
- TestSolver(CX_SPARSE);
+ TestSolver(CX_SPARSE, OrderingType::AMD);
}
#endif
#ifdef CERES_USE_EIGEN_SPARSE
TEST_F(DynamicSparseNormalCholeskySolverTest, Eigen) {
- TestSolver(EIGEN_SPARSE);
+ TestSolver(EIGEN_SPARSE, OrderingType::AMD);
}
#endif // CERES_USE_EIGEN_SPARSE
diff --git a/internal/ceres/linear_solver.h b/internal/ceres/linear_solver.h
index 57c1a79..4916e41 100644
--- a/internal/ceres/linear_solver.h
+++ b/internal/ceres/linear_solver.h
@@ -72,6 +72,7 @@
// the wrong arguments.
FATAL_ERROR
};
+
inline std::ostream& operator<<(std::ostream& s,
LinearSolverTerminationType type) {
switch (type) {
@@ -105,8 +106,27 @@
AMD, // Use the Approximate Minimum Degree algorithm to re-order
// the matrix.
+
+ NESDIS, // Use the Nested Dissection algorithm to re-order the matrix.
};
+inline std::ostream& operator<<(std::ostream& s, OrderingType type) {
+ switch (type) {
+ case OrderingType::NATURAL:
+ s << "NATURAL";
+ break;
+ case OrderingType::AMD:
+ s << "AMD";
+ break;
+ case OrderingType::NESDIS:
+ s << "NESDIS";
+ break;
+ default:
+ s << "UNKNOWN OrderingType";
+ }
+ return s;
+}
+
class LinearOperator;
// Abstract base class for objects that implement algorithms for
@@ -134,9 +154,9 @@
DenseLinearAlgebraLibraryType dense_linear_algebra_library_type = EIGEN;
SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type =
SUITE_SPARSE;
+ OrderingType ordering_type = OrderingType::NATURAL;
// See solver.h for information about these flags.
- bool use_postordering = false;
bool dynamic_sparsity = false;
bool use_explicit_schur_complement = false;
diff --git a/internal/ceres/preconditioner.h b/internal/ceres/preconditioner.h
index fdc4d86..68b575f 100644
--- a/internal/ceres/preconditioner.h
+++ b/internal/ceres/preconditioner.h
@@ -39,6 +39,7 @@
#include "ceres/internal/disable_warnings.h"
#include "ceres/internal/export.h"
#include "ceres/linear_operator.h"
+#include "ceres/linear_solver.h"
#include "ceres/sparse_matrix.h"
#include "ceres/types.h"
@@ -54,6 +55,7 @@
VisibilityClusteringType visibility_clustering_type = CANONICAL_VIEWS;
SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type =
SUITE_SPARSE;
+ OrderingType ordering_type = OrderingType::NATURAL;
// When using the subset preconditioner, all row blocks starting
// from this row block are used to construct the preconditioner.
@@ -67,9 +69,6 @@
// and the preconditioner is the inverse of the matrix Q'Q.
int subset_preconditioner_start_row_block = -1;
- // See solver.h for information about these flags.
- bool use_postordering = false;
-
// If possible, how many threads the preconditioner can use.
int num_threads = 1;
diff --git a/internal/ceres/reorder_program.cc b/internal/ceres/reorder_program.cc
index bda4545..c6765fe 100644
--- a/internal/ceres/reorder_program.cc
+++ b/internal/ceres/reorder_program.cc
@@ -123,7 +123,7 @@
// If the user did not supply a useful ordering, then just use
// regular AMD.
if (parameter_block_ordering.NumGroups() <= 1) {
- ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, &ordering[0]);
+ ss.Ordering(block_jacobian_transpose, OrderingType::AMD, &ordering[0]);
} else {
vector<int> constraints;
for (auto* parameter_block : parameter_blocks) {
diff --git a/internal/ceres/schur_complement_solver_test.cc b/internal/ceres/schur_complement_solver_test.cc
index 697d54a..362a1f6 100644
--- a/internal/ceres/schur_complement_solver_test.cc
+++ b/internal/ceres/schur_complement_solver_test.cc
@@ -93,7 +93,7 @@
ceres::LinearSolverType linear_solver_type,
ceres::DenseLinearAlgebraLibraryType dense_linear_algebra_library_type,
ceres::SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
- bool use_postordering) {
+ ceres::internal::OrderingType ordering_type) {
SetUpFromProblemId(problem_id);
LinearSolver::Options options;
options.elimination_groups.push_back(num_eliminate_blocks);
@@ -104,7 +104,7 @@
dense_linear_algebra_library_type;
options.sparse_linear_algebra_library_type =
sparse_linear_algebra_library_type;
- options.use_postordering = use_postordering;
+ options.ordering_type = ordering_type;
ContextImpl context;
options.context = &context;
DetectStructure(*A->block_structure(),
@@ -150,96 +150,154 @@
// TODO(sameeragarwal): Refactor these using value parameterized tests.
// TODO(sameeragarwal): More extensive tests using random matrices.
TEST_F(SchurComplementSolverTest, DenseSchurWithEigenSmallProblem) {
- ComputeAndCompareSolutions(2, false, DENSE_SCHUR, EIGEN, SUITE_SPARSE, true);
- ComputeAndCompareSolutions(2, true, DENSE_SCHUR, EIGEN, SUITE_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 2, false, DENSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 2, true, DENSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
}
TEST_F(SchurComplementSolverTest, DenseSchurWithEigenLargeProblem) {
- ComputeAndCompareSolutions(3, false, DENSE_SCHUR, EIGEN, SUITE_SPARSE, true);
- ComputeAndCompareSolutions(3, true, DENSE_SCHUR, EIGEN, SUITE_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 3, false, DENSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 3, true, DENSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
}
TEST_F(SchurComplementSolverTest, DenseSchurWithEigenVaryingFBlockSize) {
- ComputeAndCompareSolutions(4, true, DENSE_SCHUR, EIGEN, SUITE_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 4, true, DENSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
}
#ifndef CERES_NO_LAPACK
TEST_F(SchurComplementSolverTest, DenseSchurWithLAPACKSmallProblem) {
- ComputeAndCompareSolutions(2, false, DENSE_SCHUR, LAPACK, SUITE_SPARSE, true);
- ComputeAndCompareSolutions(2, true, DENSE_SCHUR, LAPACK, SUITE_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 2, false, DENSE_SCHUR, LAPACK, SUITE_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 2, true, DENSE_SCHUR, LAPACK, SUITE_SPARSE, OrderingType::NATURAL);
}
TEST_F(SchurComplementSolverTest, DenseSchurWithLAPACKLargeProblem) {
- ComputeAndCompareSolutions(3, false, DENSE_SCHUR, LAPACK, SUITE_SPARSE, true);
- ComputeAndCompareSolutions(3, true, DENSE_SCHUR, LAPACK, SUITE_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 3, false, DENSE_SCHUR, LAPACK, SUITE_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 3, true, DENSE_SCHUR, LAPACK, SUITE_SPARSE, OrderingType::NATURAL);
}
#endif
#ifndef CERES_NO_SUITESPARSE
TEST_F(SchurComplementSolverTest,
- SparseSchurWithSuiteSparseSmallProblemNoPostOrdering) {
+ SparseSchurWithSuiteSparseSmallProblemNATURAL) {
ComputeAndCompareSolutions(
- 2, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, false);
- ComputeAndCompareSolutions(2, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, false);
-}
-
-TEST_F(SchurComplementSolverTest,
- SparseSchurWithSuiteSparseSmallProblemPostOrdering) {
- ComputeAndCompareSolutions(2, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, true);
- ComputeAndCompareSolutions(2, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, true);
-}
-
-TEST_F(SchurComplementSolverTest,
- SparseSchurWithSuiteSparseLargeProblemNoPostOrdering) {
+ 2, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
ComputeAndCompareSolutions(
- 3, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, false);
- ComputeAndCompareSolutions(3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, false);
+ 2, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
}
TEST_F(SchurComplementSolverTest,
- SparseSchurWithSuiteSparseLargeProblemPostOrdering) {
- ComputeAndCompareSolutions(3, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, true);
- ComputeAndCompareSolutions(3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, true);
+ SparseSchurWithSuiteSparseLargeProblemNATURAL) {
+ ComputeAndCompareSolutions(
+ 3, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NATURAL);
}
+
+TEST_F(SchurComplementSolverTest, SparseSchurWithSuiteSparseSmallProblemAMD) {
+ ComputeAndCompareSolutions(
+ 2, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::AMD);
+ ComputeAndCompareSolutions(
+ 2, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::AMD);
+}
+
+TEST_F(SchurComplementSolverTest, SparseSchurWithSuiteSparseLargeProblemAMD) {
+ ComputeAndCompareSolutions(
+ 3, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::AMD);
+ ComputeAndCompareSolutions(
+ 3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::AMD);
+}
+
+#ifndef CERES_NO_METIS
+TEST_F(SchurComplementSolverTest,
+ SparseSchurWithSuiteSparseSmallProblemNESDIS) {
+ ComputeAndCompareSolutions(
+ 2, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NESDIS);
+ ComputeAndCompareSolutions(
+ 2, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NESDIS);
+}
+TEST_F(SchurComplementSolverTest,
+ SparseSchurWithSuiteSparseLargeProblemNESDIS) {
+ ComputeAndCompareSolutions(
+ 3, false, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NESDIS);
+ ComputeAndCompareSolutions(
+ 3, true, SPARSE_SCHUR, EIGEN, SUITE_SPARSE, OrderingType::NESDIS);
+}
+#endif // CERES_NO_METIS
#endif // CERES_NO_SUITESPARSE
#ifndef CERES_NO_CXSPARSE
+// TODO(sameeragarwal): Extend these tests for NATURAL & NESDIS, once the linear
+// solver supports it.
TEST_F(SchurComplementSolverTest, SparseSchurWithCXSparseSmallProblem) {
- ComputeAndCompareSolutions(2, false, SPARSE_SCHUR, EIGEN, CX_SPARSE, true);
- ComputeAndCompareSolutions(2, true, SPARSE_SCHUR, EIGEN, CX_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 2, false, SPARSE_SCHUR, EIGEN, CX_SPARSE, OrderingType::AMD);
+ ComputeAndCompareSolutions(
+ 2, true, SPARSE_SCHUR, EIGEN, CX_SPARSE, OrderingType::AMD);
}
TEST_F(SchurComplementSolverTest, SparseSchurWithCXSparseLargeProblem) {
- ComputeAndCompareSolutions(3, false, SPARSE_SCHUR, EIGEN, CX_SPARSE, true);
- ComputeAndCompareSolutions(3, true, SPARSE_SCHUR, EIGEN, CX_SPARSE, true);
+ ComputeAndCompareSolutions(
+ 3, false, SPARSE_SCHUR, EIGEN, CX_SPARSE, OrderingType::AMD);
+ ComputeAndCompareSolutions(
+ 3, true, SPARSE_SCHUR, EIGEN, CX_SPARSE, OrderingType::AMD);
}
#endif // CERES_NO_CXSPARSE
#ifndef CERES_NO_ACCELERATE_SPARSE
+// TODO(sameeragarwal): Extend these tests for NATURAL & NESDIS, once the linear
+// solver supports it.
TEST_F(SchurComplementSolverTest, SparseSchurWithAccelerateSparseSmallProblem) {
ComputeAndCompareSolutions(
- 2, false, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, true);
+ 2, false, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, OrderingType::AMD);
ComputeAndCompareSolutions(
- 2, true, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, true);
+ 2, true, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, OrderingType::AMD);
}
TEST_F(SchurComplementSolverTest, SparseSchurWithAccelerateSparseLargeProblem) {
ComputeAndCompareSolutions(
- 3, false, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, true);
+ 3, false, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, OrderingType::AMD);
ComputeAndCompareSolutions(
- 3, true, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, true);
+ 3, true, SPARSE_SCHUR, EIGEN, ACCELERATE_SPARSE, OrderingType::AMD);
}
#endif // CERES_NO_ACCELERATE_SPARSE
#ifdef CERES_USE_EIGEN_SPARSE
-TEST_F(SchurComplementSolverTest, SparseSchurWithEigenSparseSmallProblem) {
- ComputeAndCompareSolutions(2, false, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, true);
- ComputeAndCompareSolutions(2, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, true);
+TEST_F(SchurComplementSolverTest, SparseSchurWithEigenSparseSmallProblemAMD) {
+ ComputeAndCompareSolutions(
+ 2, false, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::AMD);
+ ComputeAndCompareSolutions(
+ 2, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::AMD);
}
-TEST_F(SchurComplementSolverTest, SparseSchurWithEigenSparseLargeProblem) {
- ComputeAndCompareSolutions(3, false, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, true);
- ComputeAndCompareSolutions(3, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, true);
+TEST_F(SchurComplementSolverTest,
+ SparseSchurWithEigenSparseSmallProblemNATURAL) {
+ ComputeAndCompareSolutions(
+ 2, false, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 2, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::NATURAL);
+}
+
+TEST_F(SchurComplementSolverTest, SparseSchurWithEigenSparseLargeProblemAMD) {
+ ComputeAndCompareSolutions(
+ 3, false, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::AMD);
+ ComputeAndCompareSolutions(
+ 3, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::AMD);
+}
+
+TEST_F(SchurComplementSolverTest,
+ SparseSchurWithEigenSparseLargeProblemNATURAL) {
+ ComputeAndCompareSolutions(
+ 3, false, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::NATURAL);
+ ComputeAndCompareSolutions(
+ 3, true, SPARSE_SCHUR, EIGEN, EIGEN_SPARSE, OrderingType::NATURAL);
}
#endif // CERES_USE_EIGEN_SPARSE
diff --git a/internal/ceres/sparse_cholesky.cc b/internal/ceres/sparse_cholesky.cc
index ac7c9b2..c68b833 100644
--- a/internal/ceres/sparse_cholesky.cc
+++ b/internal/ceres/sparse_cholesky.cc
@@ -44,17 +44,16 @@
std::unique_ptr<SparseCholesky> SparseCholesky::Create(
const LinearSolver::Options& options) {
- const OrderingType ordering_type =
- options.use_postordering ? OrderingType::AMD : OrderingType::NATURAL;
std::unique_ptr<SparseCholesky> sparse_cholesky;
switch (options.sparse_linear_algebra_library_type) {
case SUITE_SPARSE:
#ifndef CERES_NO_SUITESPARSE
if (options.use_mixed_precision_solves) {
- sparse_cholesky = FloatSuiteSparseCholesky::Create(ordering_type);
+ sparse_cholesky =
+ FloatSuiteSparseCholesky::Create(options.ordering_type);
} else {
- sparse_cholesky = SuiteSparseCholesky::Create(ordering_type);
+ sparse_cholesky = SuiteSparseCholesky::Create(options.ordering_type);
}
break;
#else
@@ -64,9 +63,10 @@
case EIGEN_SPARSE:
#ifdef CERES_USE_EIGEN_SPARSE
if (options.use_mixed_precision_solves) {
- sparse_cholesky = FloatEigenSparseCholesky::Create(ordering_type);
+ sparse_cholesky =
+ FloatEigenSparseCholesky::Create(options.ordering_type);
} else {
- sparse_cholesky = EigenSparseCholesky::Create(ordering_type);
+ sparse_cholesky = EigenSparseCholesky::Create(options.ordering_type);
}
break;
#else
@@ -77,9 +77,9 @@
case CX_SPARSE:
#ifndef CERES_NO_CXSPARSE
if (options.use_mixed_precision_solves) {
- sparse_cholesky = FloatCXSparseCholesky::Create(ordering_type);
+ sparse_cholesky = FloatCXSparseCholesky::Create(options.ordering_type);
} else {
- sparse_cholesky = CXSparseCholesky::Create(ordering_type);
+ sparse_cholesky = CXSparseCholesky::Create(options.ordering_type);
}
break;
#else
@@ -89,10 +89,11 @@
case ACCELERATE_SPARSE:
#ifndef CERES_NO_ACCELERATE_SPARSE
if (options.use_mixed_precision_solves) {
- sparse_cholesky = AppleAccelerateCholesky<float>::Create(ordering_type);
+ sparse_cholesky =
+ AppleAccelerateCholesky<float>::Create(options.ordering_type);
} else {
sparse_cholesky =
- AppleAccelerateCholesky<double>::Create(ordering_type);
+ AppleAccelerateCholesky<double>::Create(options.ordering_type);
}
break;
#else
diff --git a/internal/ceres/sparse_cholesky.h b/internal/ceres/sparse_cholesky.h
index f4f8996..feea7aa 100644
--- a/internal/ceres/sparse_cholesky.h
+++ b/internal/ceres/sparse_cholesky.h
@@ -63,7 +63,7 @@
// CompressedRowSparseMatrix lhs = ...;
// std::string message;
// CHECK_EQ(sparse_cholesky->Factorize(&lhs, &message),
-// LinearSolverTerminationType::SUCCESS);
+// LinearSolverTerminationType::SUCCESS);
// Vector rhs = ...;
// Vector solution = ...;
// CHECK_EQ(sparse_cholesky->Solve(rhs.data(), solution.data(), &message),
diff --git a/internal/ceres/sparse_cholesky_test.cc b/internal/ceres/sparse_cholesky_test.cc
index c9c8363..21b26a4 100644
--- a/internal/ceres/sparse_cholesky_test.cc
+++ b/internal/ceres/sparse_cholesky_test.cc
@@ -114,8 +114,7 @@
LinearSolver::Options sparse_cholesky_options;
sparse_cholesky_options.sparse_linear_algebra_library_type =
sparse_linear_algebra_library_type;
- sparse_cholesky_options.use_postordering =
- (ordering_type == OrderingType::AMD);
+ sparse_cholesky_options.ordering_type = ordering_type;
auto sparse_cholesky = SparseCholesky::Create(sparse_cholesky_options);
const CompressedRowSparseMatrix::StorageType storage_type =
sparse_cholesky->StorageType();
@@ -156,8 +155,7 @@
Param param = info.param;
std::stringstream ss;
ss << SparseLinearAlgebraLibraryTypeToString(::testing::get<0>(param)) << "_"
- << (::testing::get<1>(param) == OrderingType::AMD ? "AMD" : "NATURAL")
- << "_"
+ << ::testing::get<1>(param) << "_"
<< (::testing::get<2>(param) ? "UseBlockStructure" : "NoBlockStructure");
return ss.str();
}
@@ -197,8 +195,9 @@
SuiteSparseCholesky,
SparseCholeskyTest,
::testing::Combine(::testing::Values(SUITE_SPARSE),
- ::testing::Values(OrderingType::AMD,
- OrderingType::NATURAL),
+ ::testing::Values(OrderingType::NATURAL,
+ OrderingType::AMD,
+ OrderingType::NESDIS),
::testing::Values(true, false)),
ParamInfoToString);
#endif
diff --git a/internal/ceres/sparse_normal_cholesky_solver_test.cc b/internal/ceres/sparse_normal_cholesky_solver_test.cc
index 6b3316c..39d9d82 100644
--- a/internal/ceres/sparse_normal_cholesky_solver_test.cc
+++ b/internal/ceres/sparse_normal_cholesky_solver_test.cc
@@ -111,7 +111,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = SUITE_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = false;
+ options.ordering_type = OrderingType::NATURAL;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -122,7 +122,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = SUITE_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = true;
+ options.ordering_type = OrderingType::AMD;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -135,7 +135,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = CX_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = false;
+ options.ordering_type = OrderingType::NATURAL;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -146,7 +146,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = CX_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = true;
+ options.ordering_type = OrderingType::AMD;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -159,7 +159,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = false;
+ options.ordering_type = OrderingType::NATURAL;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -170,7 +170,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = true;
+ options.ordering_type = OrderingType::AMD;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -183,7 +183,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = false;
+ options.ordering_type = OrderingType::NATURAL;
ContextImpl context;
options.context = &context;
TestSolver(options);
@@ -194,7 +194,7 @@
LinearSolver::Options options;
options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
options.type = SPARSE_NORMAL_CHOLESKY;
- options.use_postordering = true;
+ options.ordering_type = OrderingType::AMD;
ContextImpl context;
options.context = &context;
TestSolver(options);
diff --git a/internal/ceres/subset_preconditioner.cc b/internal/ceres/subset_preconditioner.cc
index c804274..a228545 100644
--- a/internal/ceres/subset_preconditioner.cc
+++ b/internal/ceres/subset_preconditioner.cc
@@ -51,7 +51,7 @@
LinearSolver::Options sparse_cholesky_options;
sparse_cholesky_options.sparse_linear_algebra_library_type =
options_.sparse_linear_algebra_library_type;
- sparse_cholesky_options.use_postordering = options_.use_postordering;
+ sparse_cholesky_options.ordering_type = options_.ordering_type;
sparse_cholesky_ = SparseCholesky::Create(sparse_cholesky_options);
}
diff --git a/internal/ceres/suitesparse.cc b/internal/ceres/suitesparse.cc
index a7d28e6..82d20d4 100644
--- a/internal/ceres/suitesparse.cc
+++ b/internal/ceres/suitesparse.cc
@@ -43,6 +43,23 @@
#include "cholmod.h"
namespace ceres::internal {
+namespace {
+int OrderingTypeToCHOLMODEnum(OrderingType ordering_type) {
+ if (ordering_type == OrderingType::AMD) {
+ return CHOLMOD_AMD;
+ }
+ if (ordering_type == OrderingType::NESDIS) {
+ return CHOLMOD_NESDIS;
+ }
+
+ if (ordering_type == OrderingType::NATURAL) {
+ return CHOLMOD_NATURAL;
+ }
+ LOG(FATAL) << "Congratulations you have discovered a bug in Ceres Solver."
+ << "Please report it to the developers. " << ordering_type;
+ return -1;
+}
+} // namespace
using std::string;
using std::vector;
@@ -145,42 +162,38 @@
}
cholmod_factor* SuiteSparse::AnalyzeCholesky(cholmod_sparse* A,
+ OrderingType ordering_type,
string* message) {
- // Cholmod can try multiple re-ordering strategies to find a fill
- // reducing ordering. Here we just tell it use AMD with automatic
- // matrix dependence choice of supernodal versus simplicial
- // factorization.
cc_.nmethods = 1;
- cc_.method[0].ordering = CHOLMOD_AMD;
+ cc_.method[0].ordering = OrderingTypeToCHOLMODEnum(ordering_type);
cc_.supernodal = CHOLMOD_AUTO;
- cholmod_factor* factor = cholmod_analyze(A, &cc_);
- if (VLOG_IS_ON(2)) {
- cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
+ // TODO(sameeragarwal): The following modification exists entirely
+ // to preserve existing behaviour. It is not clear if this is
+ // actually the optimal thing to do. Once I understand the use of
+ // postordering better, I will come back and document this/remove it
+ // as the case maybe.
+ if (ordering_type == OrderingType::NATURAL) {
+ cc_.postorder = 0;
}
+ cholmod_factor* factor = cholmod_analyze(A, &cc_);
if (cc_.status != CHOLMOD_OK) {
*message =
StringPrintf("cholmod_analyze failed. error code: %d", cc_.status);
return nullptr;
}
-
CHECK(factor != nullptr);
- return factor;
-}
-cholmod_factor* SuiteSparse::BlockAnalyzeCholesky(cholmod_sparse* A,
- const vector<int>& row_blocks,
- const vector<int>& col_blocks,
- string* message) {
- vector<int> ordering;
- if (!BlockAMDOrdering(A, row_blocks, col_blocks, &ordering)) {
- return nullptr;
+ if (VLOG_IS_ON(2)) {
+ cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
}
- return AnalyzeCholeskyWithUserOrdering(A, ordering, message);
}
-cholmod_factor* SuiteSparse::AnalyzeCholeskyWithUserOrdering(
+return factor;
+}
+
+cholmod_factor* SuiteSparse::AnalyzeCholeskyWithGivenOrdering(
cholmod_sparse* A, const vector<int>& ordering, string* message) {
CHECK_EQ(ordering.size(), A->nrow);
@@ -189,9 +202,6 @@
cholmod_factor* factor =
cholmod_analyze_p(A, const_cast<int*>(&ordering[0]), nullptr, 0, &cc_);
- if (VLOG_IS_ON(2)) {
- cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
- }
if (cc_.status != CHOLMOD_OK) {
*message =
StringPrintf("cholmod_analyze failed. error code: %d", cc_.status);
@@ -199,33 +209,26 @@
}
CHECK(factor != nullptr);
- return factor;
-}
-
-cholmod_factor* SuiteSparse::AnalyzeCholeskyWithNaturalOrdering(
- cholmod_sparse* A, string* message) {
- cc_.nmethods = 1;
- cc_.method[0].ordering = CHOLMOD_NATURAL;
- cc_.postorder = 0;
-
- cholmod_factor* factor = cholmod_analyze(A, &cc_);
if (VLOG_IS_ON(2)) {
cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
}
- if (cc_.status != CHOLMOD_OK) {
- *message =
- StringPrintf("cholmod_analyze failed. error code: %d", cc_.status);
- return nullptr;
- }
- CHECK(factor != nullptr);
return factor;
}
-bool SuiteSparse::BlockAMDOrdering(const cholmod_sparse* A,
- const vector<int>& row_blocks,
- const vector<int>& col_blocks,
- vector<int>* ordering) {
+bool SuiteSparse::BlockOrdering(const cholmod_sparse* A,
+ OrderingType ordering_type,
+ const vector<int>& row_blocks,
+ const vector<int>& col_blocks,
+ vector<int>* ordering) {
+ if (ordering_type == OrderingType::NATURAL) {
+ ordering->resize(A->nrow);
+ for (int i = 0; i < A->nrow; ++i) {
+ (*ordering)[i] = i;
+ }
+ return true;
+ }
+
const int num_row_blocks = row_blocks.size();
const int num_col_blocks = col_blocks.size();
@@ -255,7 +258,7 @@
block_matrix.packed = 1;
vector<int> block_ordering(num_row_blocks);
- if (!cholmod_amd(&block_matrix, nullptr, 0, &block_ordering[0], &cc_)) {
+ if (!Ordering(&block_matrix, ordering_type, block_ordering.data())) {
return false;
}
@@ -263,6 +266,18 @@
return true;
}
+cholmod_factor* SuiteSparse::BlockAnalyzeCholesky(cholmod_sparse* A,
+ OrderingType ordering_type,
+ const vector<int>& row_blocks,
+ const vector<int>& col_blocks,
+ string* message) {
+ vector<int> ordering;
+ if (!BlockOrdering(A, ordering_type, row_blocks, col_blocks, &ordering)) {
+ return nullptr;
+ }
+ return AnalyzeCholeskyWithGivenOrdering(A, ordering, message);
+}
+
LinearSolverTerminationType SuiteSparse::Cholesky(cholmod_sparse* A,
cholmod_factor* L,
string* message) {
@@ -333,13 +348,14 @@
return cholmod_solve(CHOLMOD_A, L, b, &cc_);
}
-bool SuiteSparse::ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix,
- int* ordering) {
- return cholmod_amd(matrix, nullptr, 0, ordering, &cc_);
-}
+bool SuiteSparse::Ordering(cholmod_sparse* matrix,
+ OrderingType ordering_type,
+ int* ordering) {
+ CHECK_NE(ordering_type, OrderingType::NATURAL);
+ if (ordering_type == OrderingType::AMD) {
+ return cholmod_amd(matrix, nullptr, 0, ordering, &cc_);
+ }
-bool SuiteSparse::NestedDissectionOrdering(cholmod_sparse* matrix,
- int* ordering) {
#ifdef CERES_NO_METIS
return false;
#else
@@ -355,6 +371,14 @@
return cholmod_camd(matrix, nullptr, 0, constraints, ordering, &cc_);
}
+bool SuiteSparse::IsNestedDissectionAvailable() {
+#ifdef CERES_NO_METIS
+ return false;
+#else
+ return true;
+#endif
+}
+
std::unique_ptr<SparseCholesky> SuiteSparseCholesky::Create(
const OrderingType ordering_type) {
return std::unique_ptr<SparseCholesky>(
@@ -379,23 +403,36 @@
cholmod_sparse cholmod_lhs = ss_.CreateSparseMatrixTransposeView(lhs);
+ // If a factorization does not exist, compute the symbolic
+ // factorization first.
+ //
+ // If the ordering type is NATURAL, then there is no fill reducing
+ // ordering to be computed, regardless of block structure, so we can
+ // just call the scalar version of symbolic factorization. For
+ // SuiteSparse this is the common case since we have already
+ // pre-ordered the columns of the Jacobian.
+ //
+ // Similarly regardless of ordering type, if there is no block
+ // structure in the matrix we call the scalar version of symbolic
+ // factorization.
if (factor_ == nullptr) {
- if (ordering_type_ == OrderingType::NATURAL) {
- factor_ = ss_.AnalyzeCholeskyWithNaturalOrdering(&cholmod_lhs, message);
+ if (ordering_type_ == OrderingType::NATURAL ||
+ (lhs->col_blocks().empty() || lhs->row_blocks().empty())) {
+ factor_ = ss_.AnalyzeCholesky(&cholmod_lhs, ordering_type_, message);
} else {
- if (!lhs->col_blocks().empty() && !(lhs->row_blocks().empty())) {
- factor_ = ss_.BlockAnalyzeCholesky(
- &cholmod_lhs, lhs->col_blocks(), lhs->row_blocks(), message);
- } else {
- factor_ = ss_.AnalyzeCholesky(&cholmod_lhs, message);
- }
- }
-
- if (factor_ == nullptr) {
- return LinearSolverTerminationType::FATAL_ERROR;
+ factor_ = ss_.BlockAnalyzeCholesky(&cholmod_lhs,
+ ordering_type_,
+ lhs->col_blocks(),
+ lhs->row_blocks(),
+ message);
}
}
+ if (factor_ == nullptr) {
+ return LinearSolverTerminationType::FATAL_ERROR;
+ }
+
+ // Compute and return the numeric factorization.
return ss_.Cholesky(&cholmod_lhs, factor_, message);
}
diff --git a/internal/ceres/suitesparse.h b/internal/ceres/suitesparse.h
index b8dc666..e0c485d 100644
--- a/internal/ceres/suitesparse.h
+++ b/internal/ceres/suitesparse.h
@@ -119,12 +119,11 @@
cholmod_sdmult(A, 0, alpha_, beta_, x, y, &cc_);
}
- // Find an ordering of A or AA' (if A is unsymmetric) that minimizes
- // the fill-in in the Cholesky factorization of the corresponding
- // matrix. This is done by using the AMD algorithm.
- //
- // Using this ordering, the symbolic Cholesky factorization of A (or
- // AA') is computed and returned.
+ // Compute a symbolic factorization for A or AA' (if A is
+ // unsymmetric). If ordering_type is NATURAL, then no fill reducing
+ // ordering is computed, otherwise depending on the value of
+ // ordering_type AMD or Nested Dissection is used to compute a fill
+ // reducing ordering before the symbolic factorization is computed.
//
// A is not modified, only the pattern of non-zeros of A is used,
// the actual numerical values in A are of no consequence.
@@ -132,9 +131,13 @@
// message contains an explanation of the failures if any.
//
// Caller owns the result.
- cholmod_factor* AnalyzeCholesky(cholmod_sparse* A, std::string* message);
+ cholmod_factor* AnalyzeCholesky(cholmod_sparse* A,
+ OrderingType ordering_type,
+ std::string* message);
+ // Block oriented version of AnalyzeCholesky.
cholmod_factor* BlockAnalyzeCholesky(cholmod_sparse* A,
+ OrderingType ordering_type,
const std::vector<int>& row_blocks,
const std::vector<int>& col_blocks,
std::string* message);
@@ -150,20 +153,11 @@
// message contains an explanation of the failures if any.
//
// Caller owns the result.
- cholmod_factor* AnalyzeCholeskyWithUserOrdering(
+ cholmod_factor* AnalyzeCholeskyWithGivenOrdering(
cholmod_sparse* A,
const std::vector<int>& ordering,
std::string* message);
- // Perform a symbolic factorization of A without re-ordering A. No
- // postordering of the elimination tree is performed. This ensures
- // that the symbolic factor does not introduce an extra permutation
- // on the matrix. See the documentation for CHOLMOD for more details.
- //
- // message contains an explanation of the failures if any.
- cholmod_factor* AnalyzeCholeskyWithNaturalOrdering(cholmod_sparse* A,
- std::string* message);
-
// Use the symbolic factorization in L, to find the numerical
// factorization for the matrix A or AA^T. Return true if
// successful, false otherwise. L contains the numeric factorization
@@ -183,50 +177,39 @@
cholmod_dense* b,
std::string* message);
+ // Find a fill reducing ordering. ordering is expected to be large
+ // enough to hold the ordering. ordering_type must be AMD or NESDIS.
+ bool Ordering(cholmod_sparse* matrix,
+ OrderingType ordering_type,
+ int* ordering);
+
+ // Find the block oriented fill reducing ordering of a matrix A,
+ // whose row and column blocks are given by row_blocks, and
+ // col_blocks respectively. The matrix may or may not be
+ // symmetric. The entries of col_blocks do not need to sum to the
+ // number of columns in A. If this is the case, only the first
+ // sum(col_blocks) are used to compute the ordering.
+ //
// By virtue of the modeling layer in Ceres being block oriented,
// all the matrices used by Ceres are also block oriented. When
// doing sparse direct factorization of these matrices the
- // fill-reducing ordering algorithms (in particular AMD) can either
- // be run on the block or the scalar form of these matrices. The two
- // SuiteSparse::AnalyzeCholesky methods allows the client to
- // compute the symbolic factorization of a matrix by either using
- // AMD on the matrix or a user provided ordering of the rows.
- //
- // But since the underlying matrices are block oriented, it is worth
- // running AMD on just the block structure of these matrices and then
- // lifting these block orderings to a full scalar ordering. This
- // preserves the block structure of the permuted matrix, and exposes
- // more of the super-nodal structure of the matrix to the numerical
- // factorization routines.
- //
- // Find the block oriented AMD ordering of a matrix A, whose row and
- // column blocks are given by row_blocks, and col_blocks
- // respectively. The matrix may or may not be symmetric. The entries
- // of col_blocks do not need to sum to the number of columns in
- // A. If this is the case, only the first sum(col_blocks) are used
- // to compute the ordering.
- bool BlockAMDOrdering(const cholmod_sparse* A,
- const std::vector<int>& row_blocks,
- const std::vector<int>& col_blocks,
- std::vector<int>* ordering);
-
- // Find a fill reducing approximate minimum degree
- // ordering. ordering is expected to be large enough to hold the
- // ordering.
- bool ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix, int* ordering);
-
- // Find a fill reducing ordering using nested dissection.
- bool NestedDissectionOrdering(cholmod_sparse* matrix, int* ordering);
+ // fill-reducing ordering algorithms can either be run on the block
+ // or the scalar form of these matrices. But since the underlying
+ // matrices are block oriented, it is worth running the fill
+ // reducing ordering on just the block structure of these matrices
+ // and then lifting these block orderings to a full scalar
+ // ordering. This preserves the block structure of the permuted
+ // matrix, and exposes more of the super-nodal structure of the
+ // matrix to the numerical factorization routines.
+ bool BlockOrdering(const cholmod_sparse* A,
+ OrderingType ordering_type,
+ const std::vector<int>& row_blocks,
+ const std::vector<int>& col_blocks,
+ std::vector<int>* ordering);
// Nested dissection is only available if SuiteSparse is compiled
// with Metis support.
- static bool IsNestedDissectionAvailable() {
-#ifdef CERES_NO_METIS
- return false;
-#else
- return true;
-#endif
- }
+ static bool IsNestedDissectionAvailable();
// Find a fill reducing approximate minimum degree
// ordering. constraints is an array which associates with each
@@ -300,6 +283,9 @@
class CERES_NO_EXPORT SuiteSparse {
public:
+ // Nested dissection is only available if SuiteSparse is compiled
+ // with Metis support.
+ static bool IsNestedDissectionAvailable() { return false; }
void Free(void* /*arg*/) {}
};
diff --git a/internal/ceres/trust_region_preprocessor.cc b/internal/ceres/trust_region_preprocessor.cc
index daf6601..9467b5a 100644
--- a/internal/ceres/trust_region_preprocessor.cc
+++ b/internal/ceres/trust_region_preprocessor.cc
@@ -210,7 +210,10 @@
pp->linear_solver_options.max_num_refinement_iterations =
options.max_num_refinement_iterations;
pp->linear_solver_options.num_threads = options.num_threads;
- pp->linear_solver_options.use_postordering = options.use_postordering;
+ pp->linear_solver_options.ordering_type = OrderingType::NATURAL;
+ if (options.use_postordering) {
+ pp->linear_solver_options.ordering_type = OrderingType::AMD;
+ }
pp->linear_solver_options.context = pp->problem->context();
if (IsSchurType(pp->linear_solver_options.type)) {
@@ -236,7 +239,7 @@
// TODO(sameeragarwal): Implement the reordering of parameter
// blocks for CX_SPARSE.
if (options.sparse_linear_algebra_library_type == CX_SPARSE) {
- pp->linear_solver_options.use_postordering = true;
+ pp->linear_solver_options.ordering_type = OrderingType::AMD;
}
}
}
diff --git a/internal/ceres/visibility_based_preconditioner.cc b/internal/ceres/visibility_based_preconditioner.cc
index f04442d..bde2814 100644
--- a/internal/ceres/visibility_based_preconditioner.cc
+++ b/internal/ceres/visibility_based_preconditioner.cc
@@ -106,14 +106,7 @@
LinearSolver::Options sparse_cholesky_options;
sparse_cholesky_options.sparse_linear_algebra_library_type =
options_.sparse_linear_algebra_library_type;
-
- // The preconditioner's sparsity is not available in the
- // preprocessor, so the columns of the Jacobian have not been
- // reordered to minimize fill in when computing its sparse Cholesky
- // factorization. So we must tell the SparseCholesky object to
- // perform approximate minimum-degree reordering, which is done by
- // setting use_postordering to true.
- sparse_cholesky_options.use_postordering = true;
+ sparse_cholesky_options.ordering_type = options.ordering_type;
sparse_cholesky_ = SparseCholesky::Create(sparse_cholesky_options);
const time_t init_time = time(nullptr);