Pay attention to condition number in covariance estimation.

1. Sparse covariance estimation now uses cholmod_rcond to
detect singular Jacobians.

2. Dense covariance estimation now uses relative magnitude
of singular/eigen values to compute the pseudoinverse.

3. Truncation logic is now unified with Solver::Options::null_space_rank.

Change-Id: I095bd737510c836b4251255926190a7f31d64bce
diff --git a/internal/ceres/covariance_impl.cc b/internal/ceres/covariance_impl.cc
index 1a96193..966ba35 100644
--- a/internal/ceres/covariance_impl.cc
+++ b/internal/ceres/covariance_impl.cc
@@ -397,9 +397,23 @@
 
   cholmod_factor* factor = ss_.AnalyzeCholesky(&cholmod_jacobian_view);
   event_logger.AddEvent("Symbolic Factorization");
-  bool status = ss_.Cholesky(&cholmod_jacobian_view, factor);
+  bool factorization_succeeded = ss_.Cholesky(&cholmod_jacobian_view, factor);
+  if (factorization_succeeded) {
+    const double reciprocal_condition_number =
+        cholmod_rcond(factor, ss_.mutable_cc());
+    if (reciprocal_condition_number <
+        options_.min_reciprocal_condition_number) {
+      LOG(WARNING) << "Cholesky factorization of J'J is not reliable. "
+                   << "Reciprocal condition number: "
+                   << reciprocal_condition_number << " "
+                   << "min_reciprocal_condition_number : "
+                   << options_.min_reciprocal_condition_number;
+      factorization_succeeded = false;
+    }
+  }
+
   event_logger.AddEvent("Numeric Factorization");
-  if (!status) {
+  if (!factorization_succeeded) {
     ss_.Free(factor);
     LOG(WARNING) << "Cholesky factorization failed.";
     return false;
@@ -520,22 +534,39 @@
 
   Eigen::JacobiSVD<Matrix> svd(dense_jacobian,
                                Eigen::ComputeThinU | Eigen::ComputeThinV);
-  Vector inverse_singular_values = svd.singularValues();
-  event_logger.AddEvent("SVD");
+  event_logger.AddEvent("SingularValueDecomposition");
 
-  for (int i = 0; i < inverse_singular_values.rows(); ++i) {
-    if (inverse_singular_values[i] > options_.min_singular_value_threshold &&
-        i < (inverse_singular_values.rows() - options_.null_space_rank)) {
-      inverse_singular_values[i] =
-          1.0 / (inverse_singular_values[i] * inverse_singular_values[i]);
-    } else {
-      inverse_singular_values[i] = 0.0;
+  const Vector singular_values = svd.singularValues();
+  const int num_singular_values = singular_values.rows();
+  Vector inverse_squared_singular_values(num_singular_values);
+  inverse_squared_singular_values.setZero();
+
+  const double max_singular_value = singular_values[0];
+  const double min_singular_value_ratio =
+      sqrt(options_.min_reciprocal_condition_number);
+
+  const bool automatic_truncation = (options_.null_space_rank < 0);
+  const int max_rank = min(num_singular_values,
+                           num_singular_values - options_.null_space_rank);
+
+  for (int i = 0; i < max_rank; ++i) {
+    const double singular_value_ratio = singular_values[i] / max_singular_value;
+    if (singular_value_ratio >= min_singular_value_ratio) {
+      inverse_squared_singular_values[i] =
+          1.0 / (singular_values[i] * singular_values[i]);
+    } else if (!automatic_truncation) {
+      LOG(WARNING) << "Cholesky factorization of J'J is not reliable. "
+                   << "Reciprocal condition number: "
+                   << singular_value_ratio * singular_value_ratio << " "
+                   << "min_reciprocal_condition_number : "
+                   << options_.min_reciprocal_condition_number;
+      return false;
     }
   }
 
   Matrix dense_covariance =
       svd.matrixV() *
-      inverse_singular_values.asDiagonal() *
+      inverse_squared_singular_values.asDiagonal() *
       svd.matrixV().transpose();
   event_logger.AddEvent("PseudoInverse");
 
diff --git a/internal/ceres/covariance_test.cc b/internal/ceres/covariance_test.cc
index b74efda..05fb234 100644
--- a/internal/ceres/covariance_test.cc
+++ b/internal/ceres/covariance_test.cc
@@ -532,15 +532,24 @@
     -1.6076e-02,   1.2549e-02,  -3.3329e-04,  -6.6659e-04,  -9.9988e-04,   3.9539e-02
   };
 
-  Covariance::Options options;
-  options.use_dense_linear_algebra = true;
-  options.null_space_rank = 1;
-  ComputeAndCompareCovarianceBlocks(options, expected_covariance);
 
-  options.use_dense_linear_algebra = true;
-  options.null_space_rank = 0;
-  options.min_singular_value_threshold = sqrt(3.5);
-  ComputeAndCompareCovarianceBlocks(options, expected_covariance);
+  {
+    Covariance::Options options;
+    options.use_dense_linear_algebra = true;
+    // Force dropping of the smallest eigenvector.
+    options.null_space_rank = 1;
+    ComputeAndCompareCovarianceBlocks(options, expected_covariance);
+  }
+
+  {
+    Covariance::Options options;
+    options.use_dense_linear_algebra = true;
+    // Force dropping of the smallest eigenvector via the ratio but
+    // automatic truncation.
+    options.min_reciprocal_condition_number = 0.044494;
+    options.null_space_rank = -1;
+    ComputeAndCompareCovarianceBlocks(options, expected_covariance);
+  }
 }
 
 class RankDeficientCovarianceTest : public CovarianceTest {
@@ -598,7 +607,7 @@
   }
 };
 
-TEST_F(RankDeficientCovarianceTest, MinSingularValueTolerance) {
+TEST_F(RankDeficientCovarianceTest, AutomaticTruncation) {
   // J
   //
   //   1  0  0  0  0  0
@@ -631,15 +640,7 @@
 
   Covariance::Options options;
   options.use_dense_linear_algebra = true;
-  ComputeAndCompareCovarianceBlocks(options, expected_covariance);
-
-  options.null_space_rank = 1;
-  ComputeAndCompareCovarianceBlocks(options, expected_covariance);
-
-  options.null_space_rank = 2;
-  ComputeAndCompareCovarianceBlocks(options, expected_covariance);
-
-  options.null_space_rank = 3;
+  options.null_space_rank = -1;
   ComputeAndCompareCovarianceBlocks(options, expected_covariance);
 }