cubic_interpolation_test.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2023 Google Inc. All rights reserved.
  3. // http://ceres-solver.org/
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of Google Inc. nor the names of its contributors may be
  14. // used to endorse or promote products derived from this software without
  15. // specific prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. // POSSIBILITY OF SUCH DAMAGE.
  28. //
  29. // Author: sameeragarwal@google.com (Sameer Agarwal)
  30. #include "ceres/cubic_interpolation.h"
  31. #include <memory>
  32. #include "ceres/jet.h"
  33. #include "glog/logging.h"
  34. #include "gtest/gtest.h"
  35. namespace ceres::internal {
  36. static constexpr double kTolerance = 1e-12;
  37. TEST(Grid1D, OneDataDimension) {
  38. int x[] = {1, 2, 3};
  39. Grid1D<int, 1> grid(x, 0, 3);
  40. for (int i = 0; i < 3; ++i) {
  41. double value;
  42. grid.GetValue(i, &value);
  43. EXPECT_EQ(value, static_cast<double>(i + 1));
  44. }
  45. }
  46. TEST(Grid1D, OneDataDimensionOutOfBounds) {
  47. int x[] = {1, 2, 3};
  48. Grid1D<int, 1> grid(x, 0, 3);
  49. double value;
  50. grid.GetValue(-1, &value);
  51. EXPECT_EQ(value, x[0]);
  52. grid.GetValue(-2, &value);
  53. EXPECT_EQ(value, x[0]);
  54. grid.GetValue(3, &value);
  55. EXPECT_EQ(value, x[2]);
  56. grid.GetValue(4, &value);
  57. EXPECT_EQ(value, x[2]);
  58. }
  59. TEST(Grid1D, TwoDataDimensionIntegerDataInterleaved) {
  60. // clang-format off
  61. int x[] = {1, 5,
  62. 2, 6,
  63. 3, 7};
  64. // clang-format on
  65. Grid1D<int, 2, true> grid(x, 0, 3);
  66. for (int i = 0; i < 3; ++i) {
  67. double value[2];
  68. grid.GetValue(i, value);
  69. EXPECT_EQ(value[0], static_cast<double>(i + 1));
  70. EXPECT_EQ(value[1], static_cast<double>(i + 5));
  71. }
  72. }
  73. TEST(Grid1D, TwoDataDimensionIntegerDataStacked) {
  74. // clang-format off
  75. int x[] = {1, 2, 3,
  76. 5, 6, 7};
  77. // clang-format on
  78. Grid1D<int, 2, false> grid(x, 0, 3);
  79. for (int i = 0; i < 3; ++i) {
  80. double value[2];
  81. grid.GetValue(i, value);
  82. EXPECT_EQ(value[0], static_cast<double>(i + 1));
  83. EXPECT_EQ(value[1], static_cast<double>(i + 5));
  84. }
  85. }
  86. TEST(Grid2D, OneDataDimensionRowMajor) {
  87. // clang-format off
  88. int x[] = {1, 2, 3,
  89. 2, 3, 4};
  90. // clang-format on
  91. Grid2D<int, 1, true, true> grid(x, 0, 2, 0, 3);
  92. for (int r = 0; r < 2; ++r) {
  93. for (int c = 0; c < 3; ++c) {
  94. double value;
  95. grid.GetValue(r, c, &value);
  96. EXPECT_EQ(value, static_cast<double>(r + c + 1));
  97. }
  98. }
  99. }
  100. TEST(Grid2D, OneDataDimensionRowMajorOutOfBounds) {
  101. // clang-format off
  102. int x[] = {1, 2, 3,
  103. 2, 3, 4};
  104. // clang-format on
  105. Grid2D<int, 1, true, true> grid(x, 0, 2, 0, 3);
  106. double value;
  107. grid.GetValue(-1, -1, &value);
  108. EXPECT_EQ(value, x[0]);
  109. grid.GetValue(-1, 0, &value);
  110. EXPECT_EQ(value, x[0]);
  111. grid.GetValue(-1, 1, &value);
  112. EXPECT_EQ(value, x[1]);
  113. grid.GetValue(-1, 2, &value);
  114. EXPECT_EQ(value, x[2]);
  115. grid.GetValue(-1, 3, &value);
  116. EXPECT_EQ(value, x[2]);
  117. grid.GetValue(0, 3, &value);
  118. EXPECT_EQ(value, x[2]);
  119. grid.GetValue(1, 3, &value);
  120. EXPECT_EQ(value, x[5]);
  121. grid.GetValue(2, 3, &value);
  122. EXPECT_EQ(value, x[5]);
  123. grid.GetValue(2, 2, &value);
  124. EXPECT_EQ(value, x[5]);
  125. grid.GetValue(2, 1, &value);
  126. EXPECT_EQ(value, x[4]);
  127. grid.GetValue(2, 0, &value);
  128. EXPECT_EQ(value, x[3]);
  129. grid.GetValue(2, -1, &value);
  130. EXPECT_EQ(value, x[3]);
  131. grid.GetValue(1, -1, &value);
  132. EXPECT_EQ(value, x[3]);
  133. grid.GetValue(0, -1, &value);
  134. EXPECT_EQ(value, x[0]);
  135. }
  136. TEST(Grid2D, TwoDataDimensionRowMajorInterleaved) {
  137. // clang-format off
  138. int x[] = {1, 4, 2, 8, 3, 12,
  139. 2, 8, 3, 12, 4, 16};
  140. // clang-format on
  141. Grid2D<int, 2, true, true> grid(x, 0, 2, 0, 3);
  142. for (int r = 0; r < 2; ++r) {
  143. for (int c = 0; c < 3; ++c) {
  144. double value[2];
  145. grid.GetValue(r, c, value);
  146. EXPECT_EQ(value[0], static_cast<double>(r + c + 1));
  147. EXPECT_EQ(value[1], static_cast<double>(4 * (r + c + 1)));
  148. }
  149. }
  150. }
  151. TEST(Grid2D, TwoDataDimensionRowMajorStacked) {
  152. // clang-format off
  153. int x[] = {1, 2, 3,
  154. 2, 3, 4,
  155. 4, 8, 12,
  156. 8, 12, 16};
  157. // clang-format on
  158. Grid2D<int, 2, true, false> grid(x, 0, 2, 0, 3);
  159. for (int r = 0; r < 2; ++r) {
  160. for (int c = 0; c < 3; ++c) {
  161. double value[2];
  162. grid.GetValue(r, c, value);
  163. EXPECT_EQ(value[0], static_cast<double>(r + c + 1));
  164. EXPECT_EQ(value[1], static_cast<double>(4 * (r + c + 1)));
  165. }
  166. }
  167. }
  168. TEST(Grid2D, TwoDataDimensionColMajorInterleaved) {
  169. // clang-format off
  170. int x[] = { 1, 4, 2, 8,
  171. 2, 8, 3, 12,
  172. 3, 12, 4, 16};
  173. // clang-format on
  174. Grid2D<int, 2, false, true> grid(x, 0, 2, 0, 3);
  175. for (int r = 0; r < 2; ++r) {
  176. for (int c = 0; c < 3; ++c) {
  177. double value[2];
  178. grid.GetValue(r, c, value);
  179. EXPECT_EQ(value[0], static_cast<double>(r + c + 1));
  180. EXPECT_EQ(value[1], static_cast<double>(4 * (r + c + 1)));
  181. }
  182. }
  183. }
  184. TEST(Grid2D, TwoDataDimensionColMajorStacked) {
  185. // clang-format off
  186. int x[] = {1, 2,
  187. 2, 3,
  188. 3, 4,
  189. 4, 8,
  190. 8, 12,
  191. 12, 16};
  192. // clang-format on
  193. Grid2D<int, 2, false, false> grid(x, 0, 2, 0, 3);
  194. for (int r = 0; r < 2; ++r) {
  195. for (int c = 0; c < 3; ++c) {
  196. double value[2];
  197. grid.GetValue(r, c, value);
  198. EXPECT_EQ(value[0], static_cast<double>(r + c + 1));
  199. EXPECT_EQ(value[1], static_cast<double>(4 * (r + c + 1)));
  200. }
  201. }
  202. }
  203. class CubicInterpolatorTest : public ::testing::Test {
  204. public:
  205. template <int kDataDimension>
  206. void RunPolynomialInterpolationTest(const double a,
  207. const double b,
  208. const double c,
  209. const double d) {
  210. values_ = std::make_unique<double[]>(kDataDimension * kNumSamples);
  211. for (int x = 0; x < kNumSamples; ++x) {
  212. for (int dim = 0; dim < kDataDimension; ++dim) {
  213. values_[x * kDataDimension + dim] =
  214. (dim * dim + 1) * (a * x * x * x + b * x * x + c * x + d);
  215. }
  216. }
  217. Grid1D<double, kDataDimension> grid(values_.get(), 0, kNumSamples);
  218. CubicInterpolator<Grid1D<double, kDataDimension>> interpolator(grid);
  219. // Check values in the all the cells but the first and the last
  220. // ones. In these cells, the interpolated function values should
  221. // match exactly the values of the function being interpolated.
  222. //
  223. // On the boundary, we extrapolate the values of the function on
  224. // the basis of its first derivative, so we do not expect the
  225. // function values and its derivatives not to match.
  226. for (int j = 0; j < kNumTestSamples; ++j) {
  227. const double x = 1.0 + 7.0 / (kNumTestSamples - 1) * j;
  228. double expected_f[kDataDimension], expected_dfdx[kDataDimension];
  229. double f[kDataDimension], dfdx[kDataDimension];
  230. for (int dim = 0; dim < kDataDimension; ++dim) {
  231. expected_f[dim] =
  232. (dim * dim + 1) * (a * x * x * x + b * x * x + c * x + d);
  233. expected_dfdx[dim] =
  234. (dim * dim + 1) * (3.0 * a * x * x + 2.0 * b * x + c);
  235. }
  236. interpolator.Evaluate(x, f, dfdx);
  237. for (int dim = 0; dim < kDataDimension; ++dim) {
  238. EXPECT_NEAR(f[dim], expected_f[dim], kTolerance)
  239. << "x: " << x << " dim: " << dim
  240. << " actual f(x): " << expected_f[dim]
  241. << " estimated f(x): " << f[dim];
  242. EXPECT_NEAR(dfdx[dim], expected_dfdx[dim], kTolerance)
  243. << "x: " << x << " dim: " << dim
  244. << " actual df(x)/dx: " << expected_dfdx[dim]
  245. << " estimated df(x)/dx: " << dfdx[dim];
  246. }
  247. }
  248. }
  249. private:
  250. static constexpr int kNumSamples = 10;
  251. static constexpr int kNumTestSamples = 100;
  252. std::unique_ptr<double[]> values_;
  253. };
  254. TEST_F(CubicInterpolatorTest, ConstantFunction) {
  255. RunPolynomialInterpolationTest<1>(0.0, 0.0, 0.0, 0.5);
  256. RunPolynomialInterpolationTest<2>(0.0, 0.0, 0.0, 0.5);
  257. RunPolynomialInterpolationTest<3>(0.0, 0.0, 0.0, 0.5);
  258. }
  259. TEST_F(CubicInterpolatorTest, LinearFunction) {
  260. RunPolynomialInterpolationTest<1>(0.0, 0.0, 1.0, 0.5);
  261. RunPolynomialInterpolationTest<2>(0.0, 0.0, 1.0, 0.5);
  262. RunPolynomialInterpolationTest<3>(0.0, 0.0, 1.0, 0.5);
  263. }
  264. TEST_F(CubicInterpolatorTest, QuadraticFunction) {
  265. RunPolynomialInterpolationTest<1>(0.0, 0.4, 1.0, 0.5);
  266. RunPolynomialInterpolationTest<2>(0.0, 0.4, 1.0, 0.5);
  267. RunPolynomialInterpolationTest<3>(0.0, 0.4, 1.0, 0.5);
  268. }
  269. TEST(CubicInterpolator, JetEvaluation) {
  270. const double values[] = {1.0, 2.0, 2.0, 5.0, 3.0, 9.0, 2.0, 7.0};
  271. Grid1D<double, 2, true> grid(values, 0, 4);
  272. CubicInterpolator<Grid1D<double, 2, true>> interpolator(grid);
  273. double f[2], dfdx[2];
  274. const double x = 2.5;
  275. interpolator.Evaluate(x, f, dfdx);
  276. // Create a Jet with the same scalar part as x, so that the output
  277. // Jet will be evaluated at x.
  278. Jet<double, 4> x_jet;
  279. x_jet.a = x;
  280. x_jet.v(0) = 1.0;
  281. x_jet.v(1) = 1.1;
  282. x_jet.v(2) = 1.2;
  283. x_jet.v(3) = 1.3;
  284. Jet<double, 4> f_jets[2];
  285. interpolator.Evaluate(x_jet, f_jets);
  286. // Check that the scalar part of the Jet is f(x).
  287. EXPECT_EQ(f_jets[0].a, f[0]);
  288. EXPECT_EQ(f_jets[1].a, f[1]);
  289. // Check that the derivative part of the Jet is dfdx * x_jet.v
  290. // by the chain rule.
  291. EXPECT_NEAR((f_jets[0].v - dfdx[0] * x_jet.v).norm(), 0.0, kTolerance);
  292. EXPECT_NEAR((f_jets[1].v - dfdx[1] * x_jet.v).norm(), 0.0, kTolerance);
  293. }
  294. class BiCubicInterpolatorTest : public ::testing::Test {
  295. public:
  296. // This class needs to have an Eigen aligned operator new as it contains
  297. // fixed-size Eigen types.
  298. EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  299. template <int kDataDimension>
  300. void RunPolynomialInterpolationTest(const Eigen::Matrix3d& coeff) {
  301. values_ = std::make_unique<double[]>(kNumRows * kNumCols * kDataDimension);
  302. coeff_ = coeff;
  303. double* v = values_.get();
  304. for (int r = 0; r < kNumRows; ++r) {
  305. for (int c = 0; c < kNumCols; ++c) {
  306. for (int dim = 0; dim < kDataDimension; ++dim) {
  307. *v++ = (dim * dim + 1) * EvaluateF(r, c);
  308. }
  309. }
  310. }
  311. Grid2D<double, kDataDimension> grid(
  312. values_.get(), 0, kNumRows, 0, kNumCols);
  313. BiCubicInterpolator<Grid2D<double, kDataDimension>> interpolator(grid);
  314. for (int j = 0; j < kNumRowSamples; ++j) {
  315. const double r = 1.0 + 7.0 / (kNumRowSamples - 1) * j;
  316. for (int k = 0; k < kNumColSamples; ++k) {
  317. const double c = 1.0 + 7.0 / (kNumColSamples - 1) * k;
  318. double f[kDataDimension], dfdr[kDataDimension], dfdc[kDataDimension];
  319. interpolator.Evaluate(r, c, f, dfdr, dfdc);
  320. for (int dim = 0; dim < kDataDimension; ++dim) {
  321. EXPECT_NEAR(f[dim], (dim * dim + 1) * EvaluateF(r, c), kTolerance);
  322. EXPECT_NEAR(
  323. dfdr[dim], (dim * dim + 1) * EvaluatedFdr(r, c), kTolerance);
  324. EXPECT_NEAR(
  325. dfdc[dim], (dim * dim + 1) * EvaluatedFdc(r, c), kTolerance);
  326. }
  327. }
  328. }
  329. }
  330. private:
  331. double EvaluateF(double r, double c) {
  332. Eigen::Vector3d x;
  333. x(0) = r;
  334. x(1) = c;
  335. x(2) = 1;
  336. return x.transpose() * coeff_ * x;
  337. }
  338. double EvaluatedFdr(double r, double c) {
  339. Eigen::Vector3d x;
  340. x(0) = r;
  341. x(1) = c;
  342. x(2) = 1;
  343. return (coeff_.row(0) + coeff_.col(0).transpose()) * x;
  344. }
  345. double EvaluatedFdc(double r, double c) {
  346. Eigen::Vector3d x;
  347. x(0) = r;
  348. x(1) = c;
  349. x(2) = 1;
  350. return (coeff_.row(1) + coeff_.col(1).transpose()) * x;
  351. }
  352. Eigen::Matrix3d coeff_;
  353. static constexpr int kNumRows = 10;
  354. static constexpr int kNumCols = 10;
  355. static constexpr int kNumRowSamples = 100;
  356. static constexpr int kNumColSamples = 100;
  357. std::unique_ptr<double[]> values_;
  358. };
  359. TEST_F(BiCubicInterpolatorTest, ZeroFunction) {
  360. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  361. RunPolynomialInterpolationTest<1>(coeff);
  362. RunPolynomialInterpolationTest<2>(coeff);
  363. RunPolynomialInterpolationTest<3>(coeff);
  364. }
  365. TEST_F(BiCubicInterpolatorTest, Degree00Function) {
  366. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  367. coeff(2, 2) = 1.0;
  368. RunPolynomialInterpolationTest<1>(coeff);
  369. RunPolynomialInterpolationTest<2>(coeff);
  370. RunPolynomialInterpolationTest<3>(coeff);
  371. }
  372. TEST_F(BiCubicInterpolatorTest, Degree01Function) {
  373. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  374. coeff(2, 2) = 1.0;
  375. coeff(0, 2) = 0.1;
  376. coeff(2, 0) = 0.1;
  377. RunPolynomialInterpolationTest<1>(coeff);
  378. RunPolynomialInterpolationTest<2>(coeff);
  379. RunPolynomialInterpolationTest<3>(coeff);
  380. }
  381. TEST_F(BiCubicInterpolatorTest, Degree10Function) {
  382. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  383. coeff(2, 2) = 1.0;
  384. coeff(0, 1) = 0.1;
  385. coeff(1, 0) = 0.1;
  386. RunPolynomialInterpolationTest<1>(coeff);
  387. RunPolynomialInterpolationTest<2>(coeff);
  388. RunPolynomialInterpolationTest<3>(coeff);
  389. }
  390. TEST_F(BiCubicInterpolatorTest, Degree11Function) {
  391. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  392. coeff(2, 2) = 1.0;
  393. coeff(0, 1) = 0.1;
  394. coeff(1, 0) = 0.1;
  395. coeff(0, 2) = 0.2;
  396. coeff(2, 0) = 0.2;
  397. RunPolynomialInterpolationTest<1>(coeff);
  398. RunPolynomialInterpolationTest<2>(coeff);
  399. RunPolynomialInterpolationTest<3>(coeff);
  400. }
  401. TEST_F(BiCubicInterpolatorTest, Degree12Function) {
  402. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  403. coeff(2, 2) = 1.0;
  404. coeff(0, 1) = 0.1;
  405. coeff(1, 0) = 0.1;
  406. coeff(0, 2) = 0.2;
  407. coeff(2, 0) = 0.2;
  408. coeff(1, 1) = 0.3;
  409. RunPolynomialInterpolationTest<1>(coeff);
  410. RunPolynomialInterpolationTest<2>(coeff);
  411. RunPolynomialInterpolationTest<3>(coeff);
  412. }
  413. TEST_F(BiCubicInterpolatorTest, Degree21Function) {
  414. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  415. coeff(2, 2) = 1.0;
  416. coeff(0, 1) = 0.1;
  417. coeff(1, 0) = 0.1;
  418. coeff(0, 2) = 0.2;
  419. coeff(2, 0) = 0.2;
  420. coeff(0, 0) = 0.3;
  421. RunPolynomialInterpolationTest<1>(coeff);
  422. RunPolynomialInterpolationTest<2>(coeff);
  423. RunPolynomialInterpolationTest<3>(coeff);
  424. }
  425. TEST_F(BiCubicInterpolatorTest, Degree22Function) {
  426. Eigen::Matrix3d coeff = Eigen::Matrix3d::Zero();
  427. coeff(2, 2) = 1.0;
  428. coeff(0, 1) = 0.1;
  429. coeff(1, 0) = 0.1;
  430. coeff(0, 2) = 0.2;
  431. coeff(2, 0) = 0.2;
  432. coeff(0, 0) = 0.3;
  433. coeff(0, 1) = -0.4;
  434. coeff(1, 0) = -0.4;
  435. RunPolynomialInterpolationTest<1>(coeff);
  436. RunPolynomialInterpolationTest<2>(coeff);
  437. RunPolynomialInterpolationTest<3>(coeff);
  438. }
  439. TEST(BiCubicInterpolator, JetEvaluation) {
  440. // clang-format off
  441. const double values[] = {1.0, 5.0, 2.0, 10.0, 2.0, 6.0, 3.0, 5.0,
  442. 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 3.0, 1.0};
  443. // clang-format on
  444. Grid2D<double, 2> grid(values, 0, 2, 0, 4);
  445. BiCubicInterpolator<Grid2D<double, 2>> interpolator(grid);
  446. double f[2], dfdr[2], dfdc[2];
  447. const double r = 0.5;
  448. const double c = 2.5;
  449. interpolator.Evaluate(r, c, f, dfdr, dfdc);
  450. // Create a Jet with the same scalar part as x, so that the output
  451. // Jet will be evaluated at x.
  452. Jet<double, 4> r_jet;
  453. r_jet.a = r;
  454. r_jet.v(0) = 1.0;
  455. r_jet.v(1) = 1.1;
  456. r_jet.v(2) = 1.2;
  457. r_jet.v(3) = 1.3;
  458. Jet<double, 4> c_jet;
  459. c_jet.a = c;
  460. c_jet.v(0) = 2.0;
  461. c_jet.v(1) = 3.1;
  462. c_jet.v(2) = 4.2;
  463. c_jet.v(3) = 5.3;
  464. Jet<double, 4> f_jets[2];
  465. interpolator.Evaluate(r_jet, c_jet, f_jets);
  466. EXPECT_EQ(f_jets[0].a, f[0]);
  467. EXPECT_EQ(f_jets[1].a, f[1]);
  468. EXPECT_NEAR((f_jets[0].v - dfdr[0] * r_jet.v - dfdc[0] * c_jet.v).norm(),
  469. 0.0,
  470. kTolerance);
  471. EXPECT_NEAR((f_jets[1].v - dfdr[1] * r_jet.v - dfdc[1] * c_jet.v).norm(),
  472. 0.0,
  473. kTolerance);
  474. }
  475. } // namespace ceres::internal