Block AMD for SparseNormalCholesky + EIGEN_SPARSE.

This is just the reordering routine. The integration with
SparseNormalCholesky shall happen in a subsequent CL.

Change-Id: I39ddc32aa66b11c368faf75404850fa0ae0d2b3a
diff --git a/internal/ceres/reorder_program.cc b/internal/ceres/reorder_program.cc
index 4caa01f..d05f9f9 100644
--- a/internal/ceres/reorder_program.cc
+++ b/internal/ceres/reorder_program.cc
@@ -46,6 +46,12 @@
 #include "ceres/suitesparse.h"
 #include "ceres/triplet_sparse_matrix.h"
 #include "ceres/types.h"
+#include "Eigen/SparseCore"
+
+#ifdef CERES_USE_EIGEN_SPARSE
+#include "Eigen/OrderingMethods"
+#endif
+
 #include "glog/logging.h"
 
 namespace ceres {
@@ -133,6 +139,53 @@
 #endif  // CERES_NO_CXSPARSE
 }
 
+void OrderingForSparseNormalCholeskyUsingEigenSparse(
+    const TripletSparseMatrix& tsm_block_jacobian_transpose,
+    int* ordering) {
+#ifndef CERES_USE_EIGEN_SPARSE
+  LOG(FATAL) <<
+      "SPARSE_NORMAL_CHOLESKY cannot be used with EIGEN_SPARSE "
+      "because Ceres was not built with support for "
+      "Eigen's SimplicialLDLT decomposition. "
+      "This requires enabling building with -DEIGENSPARSE=ON.";
+#else
+
+  // This conversion from a TripletSparseMatrix to a Eigen::Triplet
+  // matrix is unfortunate, but unavoidable for now. It is not a
+  // significant performance penalty in the grand scheme of
+  // things. The right thing to do here would be to get a compressed
+  // row sparse matrix representation of the jacobian and go from
+  // there. But that is a project for another day.
+
+  const int* rows = tsm_block_jacobian_transpose.rows();
+  const int* cols = tsm_block_jacobian_transpose.cols();
+
+  typedef Eigen::SparseMatrix<int> SparseMatrix;
+  typedef Eigen::Triplet<int> Triplet;
+  std::vector<Triplet> triplets;
+  int num_nonzeros = tsm_block_jacobian_transpose.num_nonzeros();
+  triplets.reserve(num_nonzeros);
+  for (int i = 0; i < num_nonzeros; ++i) {
+    triplets.push_back(Triplet(rows[i], cols[i], 1));
+  }
+
+  SparseMatrix block_jacobian_transpose(
+      tsm_block_jacobian_transpose.num_rows(),
+      tsm_block_jacobian_transpose.num_cols());
+  block_jacobian_transpose.setFromTriplets(triplets.begin(),
+                                           triplets.end());
+  SparseMatrix block_hessian =
+      block_jacobian_transpose * block_jacobian_transpose.transpose();
+
+  Eigen::AMDOrdering<int> amd_ordering;
+  Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic, int> perm;
+  amd_ordering(block_hessian, perm);
+  for (int i = 0; i < tsm_block_jacobian_transpose.num_rows(); ++i) {
+    ordering[i] = perm.indices()[i];
+  }
+#endif  // CERES_USE_EIGEN_SPARSE
+}
+
 }  // namespace
 
 bool ApplyOrdering(const ProblemImpl::ParameterMap& parameter_map,
@@ -423,11 +476,18 @@
         *tsm_block_jacobian_transpose,
         &ordering[0]);
   } else if (sparse_linear_algebra_library_type == EIGEN_SPARSE) {
-    // Starting with v3.2.2 Eigen has support for symbolic analysis on
-    // pre-ordered matrices.
-    //
-    // TODO(sameeragarwal): Apply block amd for eigen.
+#if EIGEN_VERSION_AT_LEAST(3, 2, 2)
+       OrderingForSparseNormalCholeskyUsingEigenSparse(
+        *tsm_block_jacobian_transpose,
+        &ordering[0]);
+#else
+    // For Eigen versions less than 3.2.2, there is nothing to do as
+    // older versions of Eigen do not expose a method for doing
+    // symbolic analysis on pre-ordered matrices, so a block
+    // pre-ordering is a bit pointless.
+
     return true;
+#endif
   }
 
   // Apply ordering.