Add manifold and first-order diff benchmarks

- Add internal/ceres/autodiff_manifold_benchmark.cc to benchmark
  AutoDiffManifold PlusJacobian and MinusJacobian on Quaternion and
  Pose3 manifolds.
- Add internal/ceres/first_order_function_differentiation_benchmark.cc
  to benchmark AutoDiffFirstOrderFunction and static/dynamic
  NumericDiffFirstOrderFunction on Rosenbrock.

Change-Id: Ie58ae9f67952961ebefc1957d7ad9c1b56a18203
diff --git a/BUILD b/BUILD
index ac8ff91..e82cdfb 100644
--- a/BUILD
+++ b/BUILD
@@ -199,9 +199,11 @@
     copts = ["-mllvm -inlinehint-threshold=1000000"],
     deps = TEST_DEPS + ["@google_benchmark//:benchmark"],
 ) for benchmark_name in [
+    "autodiff_manifold_benchmark",
     "block_jacobi_preconditioner_benchmark",
     "dense_linear_solver_benchmark",
     "evaluation_benchmark",
+    "first_order_function_differentiation_benchmark",
     "invert_psd_matrix_benchmark",
     "jet_operator_benchmark",
     "parallel_for_benchmark",
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index f85d0d6..ff2b267 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -561,7 +561,14 @@
     block_jacobi_preconditioner_benchmark.cc)
   add_dependencies_to_benchmark(block_jacobi_preconditioner_benchmark)
 
+  add_executable(autodiff_manifold_benchmark autodiff_manifold_benchmark.cc)
+  add_dependencies_to_benchmark(autodiff_manifold_benchmark)
+
   add_executable(cost_function_differentiation_benchmark
     cost_function_differentiation_benchmark.cc)
   add_dependencies_to_benchmark(cost_function_differentiation_benchmark)
+
+  add_executable(first_order_function_differentiation_benchmark
+    first_order_function_differentiation_benchmark.cc)
+  add_dependencies_to_benchmark(first_order_function_differentiation_benchmark)
 endif (_Ceres_FEATURE_BENCHMARKS)
diff --git a/internal/ceres/autodiff_manifold_benchmark.cc b/internal/ceres/autodiff_manifold_benchmark.cc
new file mode 100644
index 0000000..5013e86
--- /dev/null
+++ b/internal/ceres/autodiff_manifold_benchmark.cc
@@ -0,0 +1,171 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2026 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+//   this list of conditions and the following disclaimer in the documentation
+//   and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+//   used to endorse or promote products derived from this software without
+//   specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: sameeragarwal@google.com (Sameer Agarwal)
+
+#include <array>
+#include <cmath>
+#include <memory>
+
+#include "absl/log/check.h"
+#include "benchmark/benchmark.h"
+#include "ceres/autodiff_manifold.h"
+#include "ceres/manifold.h"
+#include "ceres/rotation.h"
+
+namespace ceres::internal {
+namespace {
+
+struct QuaternionFunctor {
+  static constexpr int kAmbientSize = 4;
+  static constexpr int kTangentSize = 3;
+
+  static std::array<double, kAmbientSize> MakeParameter() {
+    return {0.5, 0.5, 0.5, 0.5};
+  }
+
+  template <typename T>
+  bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
+    const T squared_norm_delta =
+        delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
+
+    T q_delta[4];
+    if (squared_norm_delta > T(0.0)) {
+      const T norm_delta = sqrt(squared_norm_delta);
+      const T sin_delta_by_delta = sin(norm_delta) / norm_delta;
+      q_delta[0] = cos(norm_delta);
+      q_delta[1] = sin_delta_by_delta * delta[0];
+      q_delta[2] = sin_delta_by_delta * delta[1];
+      q_delta[3] = sin_delta_by_delta * delta[2];
+    } else {
+      q_delta[0] = T(1.0);
+      q_delta[1] = delta[0];
+      q_delta[2] = delta[1];
+      q_delta[3] = delta[2];
+    }
+
+    QuaternionProduct(q_delta, x, x_plus_delta);
+    return true;
+  }
+
+  template <typename T>
+  bool Minus(const T* y, const T* x, T* y_minus_x) const {
+    const T minus_x[4] = {x[0], -x[1], -x[2], -x[3]};
+    T ambient_y_minus_x[4];
+    QuaternionProduct(y, minus_x, ambient_y_minus_x);
+    const T u_sq = ambient_y_minus_x[1] * ambient_y_minus_x[1] +
+                   ambient_y_minus_x[2] * ambient_y_minus_x[2] +
+                   ambient_y_minus_x[3] * ambient_y_minus_x[3];
+    if (u_sq > T(0.0)) {
+      const T u_norm = sqrt(u_sq);
+      const T theta = atan2(u_norm, ambient_y_minus_x[0]);
+      y_minus_x[0] = theta * ambient_y_minus_x[1] / u_norm;
+      y_minus_x[1] = theta * ambient_y_minus_x[2] / u_norm;
+      y_minus_x[2] = theta * ambient_y_minus_x[3] / u_norm;
+    } else {
+      y_minus_x[0] = ambient_y_minus_x[1];
+      y_minus_x[1] = ambient_y_minus_x[2];
+      y_minus_x[2] = ambient_y_minus_x[3];
+    }
+    return true;
+  }
+};
+
+struct Pose3Functor {
+  static constexpr int kAmbientSize = 7;
+  static constexpr int kTangentSize = 6;
+
+  static std::array<double, kAmbientSize> MakeParameter() {
+    return {0.5, 0.5, 0.5, 0.5, 1.0, 2.0, 3.0};
+  }
+
+  template <typename T>
+  bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
+    QuaternionFunctor q;
+    q.Plus(x, delta, x_plus_delta);
+    x_plus_delta[4] = x[4] + delta[3];
+    x_plus_delta[5] = x[5] + delta[4];
+    x_plus_delta[6] = x[6] + delta[5];
+    return true;
+  }
+
+  template <typename T>
+  bool Minus(const T* y, const T* x, T* y_minus_x) const {
+    QuaternionFunctor q;
+    q.Minus(y, x, y_minus_x);
+    y_minus_x[3] = y[4] - x[4];
+    y_minus_x[4] = y[5] - x[5];
+    y_minus_x[5] = y[6] - x[6];
+    return true;
+  }
+};
+
+template <typename Functor>
+void BM_AutoDiffManifoldPlusJacobian(benchmark::State& state) {
+  constexpr int kAmbientSize = Functor::kAmbientSize;
+  constexpr int kTangentSize = Functor::kTangentSize;
+  const std::unique_ptr<Manifold> manifold =
+      std::make_unique<AutoDiffManifold<Functor, kAmbientSize, kTangentSize>>();
+  const auto x = Functor::MakeParameter();
+  std::array<double, kAmbientSize * kTangentSize> jacobian{};
+
+  CHECK(manifold->PlusJacobian(x.data(), jacobian.data()));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(manifold->PlusJacobian(x.data(), jacobian.data()));
+    benchmark::DoNotOptimize(jacobian);
+  }
+}
+
+template <typename Functor>
+void BM_AutoDiffManifoldMinusJacobian(benchmark::State& state) {
+  constexpr int kAmbientSize = Functor::kAmbientSize;
+  constexpr int kTangentSize = Functor::kTangentSize;
+  const std::unique_ptr<Manifold> manifold =
+      std::make_unique<AutoDiffManifold<Functor, kAmbientSize, kTangentSize>>();
+  const auto x = Functor::MakeParameter();
+  std::array<double, kTangentSize * kAmbientSize> jacobian{};
+
+  CHECK(manifold->MinusJacobian(x.data(), jacobian.data()));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(
+        manifold->MinusJacobian(x.data(), jacobian.data()));
+    benchmark::DoNotOptimize(jacobian);
+  }
+}
+
+BENCHMARK_TEMPLATE(BM_AutoDiffManifoldPlusJacobian, QuaternionFunctor);
+BENCHMARK_TEMPLATE(BM_AutoDiffManifoldMinusJacobian, QuaternionFunctor);
+BENCHMARK_TEMPLATE(BM_AutoDiffManifoldPlusJacobian, Pose3Functor);
+BENCHMARK_TEMPLATE(BM_AutoDiffManifoldMinusJacobian, Pose3Functor);
+
+}  // namespace
+}  // namespace ceres::internal
+
+BENCHMARK_MAIN();
diff --git a/internal/ceres/first_order_function_differentiation_benchmark.cc b/internal/ceres/first_order_function_differentiation_benchmark.cc
new file mode 100644
index 0000000..720e85e
--- /dev/null
+++ b/internal/ceres/first_order_function_differentiation_benchmark.cc
@@ -0,0 +1,176 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2026 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+//   this list of conditions and the following disclaimer in the documentation
+//   and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+//   used to endorse or promote products derived from this software without
+//   specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: sameeragarwal@google.com (Sameer Agarwal)
+
+#include <array>
+#include <memory>
+
+#include "absl/log/check.h"
+#include "benchmark/benchmark.h"
+#include "ceres/autodiff_first_order_function.h"
+#include "ceres/first_order_function.h"
+#include "ceres/numeric_diff_first_order_function.h"
+
+namespace ceres::internal {
+namespace {
+
+enum DiffType {
+  kAutoDiff,
+  kNumericForward,
+  kDynamicNumericForward,
+  kNumericCentral,
+  kDynamicNumericCentral,
+  kNumericRidders,
+  kDynamicNumericRidders,
+};
+
+// Controls whether FirstOrderFunction::Evaluate is called to compute only the
+// objective value (gradient = nullptr) or both the objective value and the
+// gradient vector (gradient != nullptr).
+enum EvaluationType {
+  kCostOnly,
+  kCostAndGradient,
+};
+
+template <DiffType kDiffType>
+constexpr NumericDiffMethodType NumericDiffMethod() {
+  if constexpr (kDiffType == kNumericForward ||
+                kDiffType == kDynamicNumericForward) {
+    return FORWARD;
+  } else if constexpr (kDiffType == kNumericCentral ||
+                       kDiffType == kDynamicNumericCentral) {
+    return CENTRAL;
+  } else if constexpr (kDiffType == kNumericRidders ||
+                       kDiffType == kDynamicNumericRidders) {
+    return RIDDERS;
+  }
+}
+
+template <DiffType kDiffType>
+constexpr bool IsDynamicNumericDiff() {
+  return kDiffType == kDynamicNumericForward ||
+         kDiffType == kDynamicNumericCentral ||
+         kDiffType == kDynamicNumericRidders;
+}
+
+template <int kParameters>
+struct RosenbrockFirstOrderFunctor {
+  static constexpr int kNumParameters = kParameters;
+
+  static std::array<double, kNumParameters> MakeParameters() {
+    std::array<double, kNumParameters> x{};
+    for (int i = 0; i < kNumParameters; ++i) {
+      x[i] = 0.5 + 0.01 * i;
+    }
+    return x;
+  }
+
+  template <typename T>
+  bool operator()(const T* const x, T* cost) const {
+    T sum = T(0.0);
+    for (int i = 0; i < kNumParameters - 1; ++i) {
+      const T t1 = T(1.0) - x[i];
+      const T t2 = x[i + 1] - x[i] * x[i];
+      sum += t1 * t1 + T(100.0) * t2 * t2;
+    }
+    *cost = sum;
+    return true;
+  }
+};
+
+template <DiffType kDiffType, typename Functor>
+std::unique_ptr<FirstOrderFunction> CreateFirstOrderFunction() {
+  constexpr int kNumParameters = Functor::kNumParameters;
+  if constexpr (kDiffType == kAutoDiff) {
+    return std::make_unique<AutoDiffFirstOrderFunction<Functor, kNumParameters>>(
+        std::make_unique<Functor>());
+  } else if constexpr (IsDynamicNumericDiff<kDiffType>()) {
+    constexpr NumericDiffMethodType kMethod = NumericDiffMethod<kDiffType>();
+    return std::make_unique<
+        NumericDiffFirstOrderFunction<Functor, kMethod, DYNAMIC>>(
+        std::make_unique<Functor>(), kNumParameters);
+  } else {
+    constexpr NumericDiffMethodType kMethod = NumericDiffMethod<kDiffType>();
+    return std::make_unique<
+        NumericDiffFirstOrderFunction<Functor, kMethod, kNumParameters>>(
+        std::make_unique<Functor>());
+  }
+}
+
+template <typename Functor, DiffType kDiffType, EvaluationType kEvalType>
+void BM_FirstOrderFunction(benchmark::State& state) {
+  const std::unique_ptr<FirstOrderFunction> function =
+      CreateFirstOrderFunction<kDiffType, Functor>();
+  const auto x = Functor::MakeParameters();
+
+  double cost = 0.0;
+  std::array<double, Functor::kNumParameters> gradient{};
+  double* gradient_ptr =
+      (kEvalType == kCostAndGradient) ? gradient.data() : nullptr;
+
+  CHECK(function->Evaluate(x.data(), &cost, gradient_ptr));
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(function->Evaluate(x.data(), &cost, gradient_ptr));
+    benchmark::DoNotOptimize(cost);
+    benchmark::DoNotOptimize(gradient);
+  }
+}
+
+#define REGISTER_ROSENBROCK_FOR_DIFF_TYPE(Diff, N)   \
+  BENCHMARK_TEMPLATE(BM_FirstOrderFunction,          \
+                     RosenbrockFirstOrderFunctor<N>, \
+                     Diff,                           \
+                     kCostOnly);                     \
+  BENCHMARK_TEMPLATE(BM_FirstOrderFunction,          \
+                     RosenbrockFirstOrderFunctor<N>, \
+                     Diff,                           \
+                     kCostAndGradient)
+
+#define REGISTER_ROSENBROCK_BENCHMARKS(N)                       \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kAutoDiff, N);              \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kNumericForward, N);        \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kDynamicNumericForward, N); \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kNumericCentral, N);        \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kDynamicNumericCentral, N); \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kNumericRidders, N);        \
+  REGISTER_ROSENBROCK_FOR_DIFF_TYPE(kDynamicNumericRidders, N)
+
+REGISTER_ROSENBROCK_BENCHMARKS(4);
+REGISTER_ROSENBROCK_BENCHMARKS(10);
+REGISTER_ROSENBROCK_BENCHMARKS(16);
+REGISTER_ROSENBROCK_BENCHMARKS(24);
+
+#undef REGISTER_ROSENBROCK_BENCHMARKS
+#undef REGISTER_ROSENBROCK_FOR_DIFF_TYPE
+
+}  // namespace
+}  // namespace ceres::internal
+
+BENCHMARK_MAIN();