Fix bundle adjuster perturbation assertions

Avoid debug-mode assertions from zero width normal distributions when
optional perturbations are disabled.

Change-Id: Ic078482a5f845ba3095ba4389a1bcdadf426944e
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index e3ba5c4..355fc25 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -102,6 +102,19 @@
   bal_problem.cc)
 target_link_libraries(bundle_adjuster PRIVATE absl::log_initialize absl::flags absl::flags_parse Ceres::ceres)
 
+if (BUILD_TESTING)
+  add_executable(bal_problem_test
+    bal_problem_test.cc
+    bal_problem.cc)
+  target_compile_definitions(bal_problem_test PRIVATE
+    CERES_TEST_DATA_DIR="${Ceres_SOURCE_DIR}/data")
+  target_link_libraries(bal_problem_test PRIVATE
+    absl::log_initialize
+    Ceres::ceres
+    GTest::gmock_main)
+  add_test(NAME bal_problem_test COMMAND bal_problem_test)
+endif (BUILD_TESTING)
+
 add_executable(libmv_bundle_adjuster
   libmv_bundle_adjuster.cc)
 target_link_libraries(libmv_bundle_adjuster PRIVATE absl::log_initialize absl::flags absl::flags_parse Ceres::ceres)
diff --git a/examples/bal_problem.cc b/examples/bal_problem.cc
index d6cb2b7..28b3bb9 100644
--- a/examples/bal_problem.cc
+++ b/examples/bal_problem.cc
@@ -298,34 +298,36 @@
   CHECK_GE(rotation_sigma, 0.0);
   CHECK_GE(translation_sigma, 0.0);
   std::mt19937 prng;
-  std::normal_distribution<double> point_noise_distribution(0.0, point_sigma);
   double* points = mutable_points();
   if (point_sigma > 0) {
+    std::normal_distribution<double> point_noise_distribution(0.0, point_sigma);
     for (int i = 0; i < num_points_; ++i) {
       PerturbPoint3(std::bind(point_noise_distribution, std::ref(prng)),
                     points + 3 * i);
     }
   }
 
-  std::normal_distribution<double> rotation_noise_distribution(0.0,
-                                                               point_sigma);
-  std::normal_distribution<double> translation_noise_distribution(
-      0.0, translation_sigma);
-  for (int i = 0; i < num_cameras_; ++i) {
-    double* camera = mutable_cameras() + camera_block_size() * i;
-
-    double angle_axis[3];
-    double center[3];
-    // Perturb in the rotation of the camera in the angle-axis
-    // representation.
-    CameraToAngleAxisAndCenter(camera, angle_axis, center);
-    if (rotation_sigma > 0.0) {
+  if (rotation_sigma > 0.0) {
+    std::normal_distribution<double> rotation_noise_distribution(
+        0.0, rotation_sigma);
+    for (int i = 0; i < num_cameras_; ++i) {
+      double* camera = mutable_cameras() + camera_block_size() * i;
+      double angle_axis[3];
+      double center[3];
+      // Perturb in the rotation of the camera in the angle-axis
+      // representation.
+      CameraToAngleAxisAndCenter(camera, angle_axis, center);
       PerturbPoint3(std::bind(rotation_noise_distribution, std::ref(prng)),
                     angle_axis);
+      AngleAxisAndCenterToCamera(angle_axis, center, camera);
     }
-    AngleAxisAndCenterToCamera(angle_axis, center, camera);
+  }
 
-    if (translation_sigma > 0.0) {
+  if (translation_sigma > 0.0) {
+    std::normal_distribution<double> translation_noise_distribution(
+        0.0, translation_sigma);
+    for (int i = 0; i < num_cameras_; ++i) {
+      double* camera = mutable_cameras() + camera_block_size() * i;
       PerturbPoint3(std::bind(translation_noise_distribution, std::ref(prng)),
                     camera + camera_block_size() - 6);
     }
diff --git a/examples/bal_problem_test.cc b/examples/bal_problem_test.cc
new file mode 100644
index 0000000..5be8965
--- /dev/null
+++ b/examples/bal_problem_test.cc
@@ -0,0 +1,66 @@
+// 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: sergiu.deitsch@gmail.com (Sergiu Deitsch)
+
+#include "bal_problem.h"
+
+#include <algorithm>
+#include <cmath>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace ceres::examples {
+namespace {
+
+TEST(BALProblem, PerturbsRotationWhenPointPerturbationIsDisabled) {
+  constexpr const char* kProblemFile =
+      CERES_TEST_DATA_DIR "/problem-16-22106-pre.txt";
+  BALProblem baseline(kProblemFile, false);
+  BALProblem problem(kProblemFile, false);
+  const int num_camera_parameters =
+      problem.num_cameras() * problem.camera_block_size();
+
+  constexpr double kRotationSigma = 1.0;
+  constexpr double kNoRotationSigma = 0.0;
+  constexpr double kNoTranslationSigma = 0.0;
+  constexpr double kNoPointSigma = 0.0;
+  baseline.Perturb(kNoRotationSigma, kNoTranslationSigma, kNoPointSigma);
+  problem.Perturb(kRotationSigma, kNoTranslationSigma, kNoPointSigma);
+
+  double maximum_change = 0.0;
+  for (int i = 0; i < num_camera_parameters; ++i) {
+    maximum_change = std::max(
+        maximum_change, std::abs(baseline.cameras()[i] - problem.cameras()[i]));
+  }
+  EXPECT_GT(maximum_change, 0.0);
+}
+
+}  // namespace
+}  // namespace ceres::examples