Add the ability to order the Program using AMD.

This will allow CHOLMOD to compute the sparse
Cholesky factorization of J'J without making
a permuted copy of it.

Change-Id: I25d0e18f5957ab7fdce15c543234bb2f09db482e
diff --git a/internal/ceres/solver_impl.cc b/internal/ceres/solver_impl.cc
index add28ae..0d7b83c 100644
--- a/internal/ceres/solver_impl.cc
+++ b/internal/ceres/solver_impl.cc
@@ -50,6 +50,7 @@
 #include "ceres/program.h"
 #include "ceres/residual_block.h"
 #include "ceres/stringprintf.h"
+#include "ceres/suitesparse.h"
 #include "ceres/trust_region_minimizer.h"
 #include "ceres/wall_time.h"
 
@@ -1003,6 +1004,11 @@
     return transformed_program.release();
   }
 
+  if (options->linear_solver_type == SPARSE_NORMAL_CHOLESKY) {
+    ReorderProgramForSparseNormalCholesky(transformed_program.get());
+    return transformed_program.release();
+  }
+
   transformed_program->SetParameterOffsetsAndIndex();
   return transformed_program.release();
 }
@@ -1416,5 +1422,81 @@
                                               error);
 }
 
+TripletSparseMatrix* SolverImpl::CreateJacobianBlockSparsityTranspose(
+    const Program* program) {
+
+  // Matrix to store the block sparsity structure of
+  TripletSparseMatrix* tsm =
+      new TripletSparseMatrix(program->NumParameterBlocks(),
+                              program->NumResidualBlocks(),
+                              10 * program->NumResidualBlocks());
+  int num_nonzeros = 0;
+  int* rows = tsm->mutable_rows();
+  int* cols = tsm->mutable_cols();
+  double* values = tsm->mutable_values();
+
+  const vector<ResidualBlock*>& residual_blocks = program->residual_blocks();
+  for (int c = 0; c < residual_blocks.size(); ++c) {
+    const ResidualBlock* residual_block = residual_blocks[c];
+    const int num_parameter_blocks = residual_block->NumParameterBlocks();
+    ParameterBlock* const* parameter_blocks =
+        residual_block->parameter_blocks();
+
+    for (int j = 0; j < num_parameter_blocks; ++j) {
+      if (parameter_blocks[j]->IsConstant()) {
+        continue;
+      }
+
+      // Re-size the matrix if needed.
+      if (num_nonzeros >= tsm->max_num_nonzeros()) {
+        tsm->Reserve(2 * num_nonzeros);
+        rows = tsm->mutable_rows();
+        cols = tsm->mutable_cols();
+        values = tsm->mutable_values();
+      }
+      CHECK_LT(num_nonzeros,  tsm->max_num_nonzeros());
+
+      const int r = parameter_blocks[j]->index();
+      rows[num_nonzeros] = r;
+      cols[num_nonzeros] = c;
+      values[num_nonzeros] = 1.0;
+      ++num_nonzeros;
+    }
+  }
+
+  tsm->set_num_nonzeros(num_nonzeros);
+  return tsm;
+}
+
+void SolverImpl::ReorderProgramForSparseNormalCholesky(Program* program) {
+#ifndef CERES_NO_SUITESPARSE
+  // Set the offsets and index for CreateJacobianSparsityTranspose.
+  program->SetParameterOffsetsAndIndex();
+
+  // Compute a block sparse presentation of J'.
+  scoped_ptr<TripletSparseMatrix> tsm_block_jacobian_transpose(
+      SolverImpl::CreateJacobianBlockSparsityTranspose(program));
+
+  // Order rows using AMD.
+  SuiteSparse ss;
+  cholmod_sparse* block_jacobian_transpose =
+      ss.CreateSparseMatrix(tsm_block_jacobian_transpose.get());
+
+  vector<int> ordering(program->NumResidualBlocks(), -1);
+  ss.ApproximateMinimumDegreeOrdering(block_jacobian_transpose, &ordering[0]);
+  ss.Free(block_jacobian_transpose);
+
+  // Apply ordering.
+  vector<ParameterBlock*>& parameter_blocks =
+      *(program->mutable_parameter_blocks());
+  const vector<ParameterBlock*> parameter_blocks_copy(parameter_blocks);
+  for (int i = 0; i < program->NumParameterBlocks(); ++i) {
+    parameter_blocks[i] = parameter_blocks_copy[ordering[i]];
+  }
+#endif
+
+  program->SetParameterOffsetsAndIndex();
+}
+
 }  // namespace internal
 }  // namespace ceres
diff --git a/internal/ceres/solver_impl.h b/internal/ceres/solver_impl.h
index 6377e84..22ca622 100644
--- a/internal/ceres/solver_impl.h
+++ b/internal/ceres/solver_impl.h
@@ -46,6 +46,7 @@
 class Evaluator;
 class LinearSolver;
 class Program;
+class TripletSparseMatrix;
 
 class SolverImpl {
  public:
@@ -177,6 +178,21 @@
       ParameterBlockOrdering* ordering,
       Program* program,
       string* error);
+
+  // CHOLMOD when doing the sparse cholesky factorization of the
+  // Jacobian matrix, reorders its columns to reduce the
+  // fill-in. Compute this permutation and re-order the parameter
+  // blocks.
+  //
+  static void ReorderProgramForSparseNormalCholesky(Program* program);
+
+  // Create a TripletSparseMatrix which contains the zero-one
+  // structure corresponding to the block sparsity of the transpose of
+  // the Jacobian matrix.
+  //
+  // Caller owns the result.
+  static TripletSparseMatrix* CreateJacobianBlockSparsityTranspose(
+      const Program* program);
 };
 
 }  // namespace internal
diff --git a/internal/ceres/suitesparse.cc b/internal/ceres/suitesparse.cc
index 9063fa0..5138b52 100644
--- a/internal/ceres/suitesparse.cc
+++ b/internal/ceres/suitesparse.cc
@@ -397,6 +397,11 @@
   return NULL;
 }
 
+void SuiteSparse::ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix,
+                                                   int* ordering) {
+  cholmod_amd(matrix, NULL, 0, ordering, &cc_);
+}
+
 }  // namespace internal
 }  // namespace ceres
 
diff --git a/internal/ceres/suitesparse.h b/internal/ceres/suitesparse.h
index 131716d..a1a4f35 100644
--- a/internal/ceres/suitesparse.h
+++ b/internal/ceres/suitesparse.h
@@ -207,6 +207,11 @@
                                         vector<int>* block_rows,
                                         vector<int>* block_cols);
 
+  // Find a fill reducing approximate minimum degree
+  // ordering. ordering is expected to be large enough to hold the
+  // ordering.
+  void ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix, int* ordering);
+
   void Free(cholmod_sparse* m) { cholmod_free_sparse(&m, &cc_); }
   void Free(cholmod_dense* m)  { cholmod_free_dense(&m, &cc_);  }
   void Free(cholmod_factor* m) { cholmod_free_factor(&m, &cc_); }