Add BiCubic interpolation.

This bi-cubic interpolation implementation is based
on the cubic convolution algorithm of keys, which allows
us to implement a bi-cubic spline like interpolation scheme
using five one dimensional cubic spline operations.

Change-Id: I116aa8036191c3e654af788323fc8298ae8252a6
diff --git a/internal/ceres/cubic_interpolation_test.cc b/internal/ceres/cubic_interpolation_test.cc
index 04aacc0..d3b5b49 100644
--- a/internal/ceres/cubic_interpolation_test.cc
+++ b/internal/ceres/cubic_interpolation_test.cc
@@ -47,7 +47,6 @@
 
 class CubicInterpolatorTest : public ::testing::Test {
  public:
-
   void RunPolynomialInterpolationTest(const double a,
                                       const double b,
                                       const double c,
@@ -83,6 +82,7 @@
     }
   }
 
+ private:
   static const int kNumSamples = 10;
   static const int kNumTestSamples = 100;
   double values_[kNumSamples];
@@ -109,22 +109,188 @@
 
   // Create a Jet with the same scalar part as x, so that the output
   // Jet will be evaluate at x.
-  Jet<double, 4> input_jet;
-  input_jet.a = x;
-  input_jet.v(0) = 1.0;
-  input_jet.v(1) = 1.1;
-  input_jet.v(2) = 1.2;
-  input_jet.v(3) = 1.3;
+  Jet<double, 4> x_jet;
+  x_jet.a = x;
+  x_jet.v(0) = 1.0;
+  x_jet.v(1) = 1.1;
+  x_jet.v(2) = 1.2;
+  x_jet.v(3) = 1.3;
 
-  Jet<double, 4> output_jet;
-  EXPECT_TRUE(interpolator.Evaluate(input_jet, &output_jet));
+  Jet<double, 4> f_jet;
+  EXPECT_TRUE(interpolator.Evaluate(x_jet, &f_jet));
 
   // Check that the scalar part of the Jet is f(x).
-  EXPECT_EQ(output_jet.a, f);
+  EXPECT_EQ(f_jet.a, f);
 
-  // Check that the derivative part of the Jet is dfdx * input_jet.v
+  // Check that the derivative part of the Jet is dfdx * x_jet.v
   // by the chain rule.
-  EXPECT_EQ((output_jet.v - dfdx * input_jet.v).norm(), 0.0);
+  EXPECT_EQ((f_jet.v - dfdx * x_jet.v).norm(), 0.0);
+}
+
+class BiCubicInterpolatorTest : public ::testing::Test {
+ public:
+  void RunPolynomialInterpolationTest(const Eigen::Matrix3d& coeff) {
+    coeff_ = coeff;
+    double* v = values_;
+    for (int r = 0; r < kNumRows; ++r) {
+      for (int c = 0; c < kNumCols; ++c) {
+        *v++ = EvaluateF(r, c);
+      }
+    }
+    BiCubicInterpolator interpolator(values_, kNumRows, kNumCols);
+
+    for (int j = 0; j < kNumRowSamples; ++j) {
+      const double r = 1.0 + 7.0 / (kNumRowSamples - 1) * j;
+      for (int k = 0; k < kNumColSamples; ++k) {
+        const double c = 1.0 + 7.0 / (kNumColSamples - 1) * k;
+        const double expected_f = EvaluateF(r, c);
+        const double expected_dfdr = EvaluatedFdr(r, c);
+        const double expected_dfdc = EvaluatedFdc(r, c);
+        double f, dfdr, dfdc;
+
+        EXPECT_TRUE(interpolator.Evaluate(r, c, &f, &dfdr, &dfdc));
+        EXPECT_NEAR(f, expected_f, kTolerance);
+        EXPECT_NEAR(dfdr, expected_dfdr, kTolerance);
+        EXPECT_NEAR(dfdc, expected_dfdc, kTolerance);
+      }
+    }
+  }
+
+ private:
+  double EvaluateF(double r, double c) {
+    Eigen::Vector3d x;
+    x(0) = r;
+    x(1) = c;
+    x(2) = 1;
+    return x.transpose() * coeff_ * x;
+  }
+
+  double EvaluatedFdr(double r, double c) {
+    Eigen::Vector3d x;
+    x(0) = r;
+    x(1) = c;
+    x(2) = 1;
+    return (coeff_.row(0) + coeff_.col(0).transpose()) * x;
+  }
+
+  double EvaluatedFdc(double r, double c) {
+    Eigen::Vector3d x;
+    x(0) = r;
+    x(1) = c;
+    x(2) = 1;
+    return (coeff_.row(1) + coeff_.col(1).transpose()) * x;
+  }
+
+
+  Eigen::Matrix3d coeff_;
+  static const int kNumRows = 10;
+  static const int kNumCols = 10;
+  static const int kNumRowSamples = 100;
+  static const int kNumColSamples = 100;
+  double values_[kNumRows * kNumCols];
+};
+
+TEST_F(BiCubicInterpolatorTest, ZeroFunction) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree00Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree01Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 2) = 0.1;
+  coeff(2, 0) = 0.1;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree10Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree11Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree12Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  coeff(1, 1) = 0.3;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree21Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  coeff(0, 0) = 0.3;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST_F(BiCubicInterpolatorTest, Degree22Function) {
+  Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
+  coeff(2, 2) = 1.0;
+  coeff(0, 1) = 0.1;
+  coeff(1, 0) = 0.1;
+  coeff(0, 2) = 0.2;
+  coeff(2, 0) = 0.2;
+  coeff(0, 0) = 0.3;
+  coeff(0, 1) = -0.4;
+  coeff(1, 0) = -0.4;
+  RunPolynomialInterpolationTest(coeff);
+}
+
+TEST(BiCubicInterpolator, JetEvaluation) {
+  const double values[] = {1.0, 2.0, 2.0, 3.0,
+                           1.0, 2.0, 2.0, 3.0};
+  BiCubicInterpolator interpolator(values, 2, 4);
+  double f, dfdr, dfdc;
+  const double r = 0.5;
+  const double c = 2.5;
+  EXPECT_TRUE(interpolator.Evaluate(r, c, &f, &dfdr, &dfdc));
+
+  // Create a Jet with the same scalar part as x, so that the output
+  // Jet will be evaluate at x.
+  Jet<double, 4> r_jet;
+  r_jet.a = r;
+  r_jet.v(0) = 1.0;
+  r_jet.v(1) = 1.1;
+  r_jet.v(2) = 1.2;
+  r_jet.v(3) = 1.3;
+
+  Jet<double, 4> c_jet;
+  c_jet.a = c;
+  c_jet.v(0) = 2.0;
+  c_jet.v(1) = 3.1;
+  c_jet.v(2) = 4.2;
+  c_jet.v(3) = 5.3;
+
+  Jet<double, 4> f_jet;
+  EXPECT_TRUE(interpolator.Evaluate(r_jet, c_jet, &f_jet));
+  EXPECT_EQ(f_jet.a, f);
+  EXPECT_EQ((f_jet.v - dfdr * r_jet.v - dfdc * c_jet.v).norm(), 0.0);
 }
 
 }  // namespace internal