Add CUDA based bundle adjustment tests.

Also refactor the logic for generating ba tests a bit
breaking it into dense, sparse and iterative tests.

Change-Id: I6b33e3b047ef900b12907bd150febe4744779b7a
diff --git a/internal/ceres/generate_bundle_adjustment_tests.py b/internal/ceres/generate_bundle_adjustment_tests.py
index 86b4fcb..29a9297 100644
--- a/internal/ceres/generate_bundle_adjustment_tests.py
+++ b/internal/ceres/generate_bundle_adjustment_tests.py
@@ -41,27 +41,37 @@
 MULTI_THREADED = "4"
 THREAD_CONFIGS = [SINGLE_THREADED, MULTI_THREADED]
 
-SOLVER_CONFIGS = [
-  # Linear solver            Sparse backend      Preconditioner
-  ('DENSE_SCHUR',            'NO_SPARSE',        'IDENTITY'),
-  ('ITERATIVE_SCHUR',        'NO_SPARSE',        'JACOBI'),
-  ('ITERATIVE_SCHUR',        'NO_SPARSE',        'SCHUR_JACOBI'),
-  ('ITERATIVE_SCHUR',        'SUITE_SPARSE',     'CLUSTER_JACOBI'),
-  ('ITERATIVE_SCHUR',        'EIGEN_SPARSE',     'CLUSTER_JACOBI'),
-  ('ITERATIVE_SCHUR',        'CX_SPARSE',        'CLUSTER_JACOBI'),
-  ('ITERATIVE_SCHUR',        'ACCELERATE_SPARSE','CLUSTER_JACOBI'),
-  ('ITERATIVE_SCHUR',        'SUITE_SPARSE',     'CLUSTER_TRIDIAGONAL'),
-  ('ITERATIVE_SCHUR',        'EIGEN_SPARSE',     'CLUSTER_TRIDIAGONAL'),
-  ('ITERATIVE_SCHUR',        'CX_SPARSE',        'CLUSTER_TRIDIAGONAL'),
-  ('ITERATIVE_SCHUR',        'ACCELERATE_SPARSE','CLUSTER_TRIDIAGONAL'),
-  ('SPARSE_NORMAL_CHOLESKY', 'SUITE_SPARSE',     'IDENTITY'),
-  ('SPARSE_NORMAL_CHOLESKY', 'EIGEN_SPARSE',     'IDENTITY'),
-  ('SPARSE_NORMAL_CHOLESKY', 'CX_SPARSE',        'IDENTITY'),
-  ('SPARSE_NORMAL_CHOLESKY', 'ACCELERATE_SPARSE','IDENTITY'),
-  ('SPARSE_SCHUR',           'SUITE_SPARSE',     'IDENTITY'),
-  ('SPARSE_SCHUR',           'EIGEN_SPARSE',     'IDENTITY'),
-  ('SPARSE_SCHUR',           'CX_SPARSE',        'IDENTITY'),
-  ('SPARSE_SCHUR',           'ACCELERATE_SPARSE','IDENTITY'),
+DENSE_SOLVER_CONFIGS = [
+    # Linear solver  Dense backend
+    ('DENSE_SCHUR',  'EIGEN'),
+    ('DENSE_SCHUR',  'LAPACK'),
+    ('DENSE_SCHUR',  'CUDA'),
+]
+
+SPARSE_SOLVER_CONFIGS = [
+    # Linear solver            Sparse backend
+    ('SPARSE_NORMAL_CHOLESKY', 'SUITE_SPARSE'),
+    ('SPARSE_NORMAL_CHOLESKY', 'EIGEN_SPARSE'),
+    ('SPARSE_NORMAL_CHOLESKY', 'CX_SPARSE'),
+    ('SPARSE_NORMAL_CHOLESKY', 'ACCELERATE_SPARSE'),
+    ('SPARSE_SCHUR',           'SUITE_SPARSE'),
+    ('SPARSE_SCHUR',           'EIGEN_SPARSE'),
+    ('SPARSE_SCHUR',           'CX_SPARSE'),
+    ('SPARSE_SCHUR',           'ACCELERATE_SPARSE'),
+]
+
+ITERATIVE_SOLVER_CONFIGS = [
+    # Linear solver            Sparse backend      Preconditioner
+    ('ITERATIVE_SCHUR',        'NO_SPARSE',        'JACOBI'),
+    ('ITERATIVE_SCHUR',        'NO_SPARSE',        'SCHUR_JACOBI'),
+    ('ITERATIVE_SCHUR',        'SUITE_SPARSE',     'CLUSTER_JACOBI'),
+    ('ITERATIVE_SCHUR',        'EIGEN_SPARSE',     'CLUSTER_JACOBI'),
+    ('ITERATIVE_SCHUR',        'CX_SPARSE',        'CLUSTER_JACOBI'),
+    ('ITERATIVE_SCHUR',        'ACCELERATE_SPARSE','CLUSTER_JACOBI'),
+    ('ITERATIVE_SCHUR',        'SUITE_SPARSE',     'CLUSTER_TRIDIAGONAL'),
+    ('ITERATIVE_SCHUR',        'EIGEN_SPARSE',     'CLUSTER_TRIDIAGONAL'),
+    ('ITERATIVE_SCHUR',        'CX_SPARSE',        'CLUSTER_TRIDIAGONAL'),
+    ('ITERATIVE_SCHUR',        'ACCELERATE_SPARSE','CLUSTER_TRIDIAGONAL'),
 ]
 
 FILENAME_SHORTENING_MAP = dict(
@@ -69,6 +79,9 @@
   ITERATIVE_SCHUR='iterschur',
   SPARSE_NORMAL_CHOLESKY='sparsecholesky',
   SPARSE_SCHUR='sparseschur',
+  EIGEN='eigen',
+  LAPACK='lapack',
+  CUDA='cuda',
   NO_SPARSE='',  # Omit sparse reference entirely for dense tests.
   SUITE_SPARSE='suitesparse',
   EIGEN_SPARSE='eigensparse',
@@ -85,7 +98,7 @@
 
 COPYRIGHT_HEADER = (
 """// Ceres Solver - A fast non-linear least squares minimizer
-// Copyright 2018 Google Inc. All rights reserved.
+// Copyright 2022 Google Inc. All rights reserved.
 // http://ceres-solver.org/
 //
 // Redistribution and use in source and binary forms, with or without
@@ -134,6 +147,7 @@
   Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
   options->num_threads = %(num_threads)s;
   options->linear_solver_type = %(linear_solver)s;
+  options->dense_linear_algebra_library_type = %(dense_backend)s;
   options->sparse_linear_algebra_library_type = %(sparse_backend)s;
   options->preconditioner_type = %(preconditioner)s;
   if (%(ordering)s) {
@@ -153,6 +167,7 @@
 
 
 def generate_bundle_test(linear_solver,
+                         dense_backend,
                          sparse_backend,
                          preconditioner,
                          ordering,
@@ -164,6 +179,10 @@
   if linear_solver != 'ITERATIVE_SCHUR':
     preconditioner_tag = ''
 
+  dense_backend_tag = dense_backend
+  if linear_solver != 'DENSE_SCHUR':
+    dense_backend_tag=''
+
   # Omit references to the sparse backend when one is not in use.
   sparse_backend_tag = sparse_backend
   if sparse_backend == 'NO_SPARSE':
@@ -172,6 +191,7 @@
   # Use a double underscore; otherwise the names are harder to understand.
   test_class_name = '_'.join(filter(lambda x: x, [
       camelcasify(linear_solver),
+      camelcasify(dense_backend_tag),
       camelcasify(sparse_backend_tag),
       camelcasify(preconditioner_tag),
       ordering[1:],  # Strip 'k'
@@ -180,6 +200,7 @@
   # Initial template parameters (augmented more below).
   template_parameters = dict(
           linear_solver=linear_solver,
+          dense_backend=dense_backend,
           sparse_backend=sparse_backend,
           preconditioner=preconditioner,
           ordering=ordering,
@@ -202,6 +223,13 @@
     preprocessor_conditions_begin.append('#ifdef CERES_USE_EIGEN_SPARSE')
     preprocessor_conditions_end.insert(0, '#endif  // CERES_USE_EIGEN_SPARSE')
 
+  if dense_backend == "LAPACK":
+    preprocessor_conditions_begin.append('#ifndef CERES_NO_LAPACK')
+    preprocessor_conditions_end.insert(0, '#endif  // CERES_NO_LAPACK')
+  elif dense_backend == "CUDA":
+    preprocessor_conditions_begin.append('#ifndef CERES_NO_CUDA')
+    preprocessor_conditions_end.insert(0, '#endif  // CERES_NO_CUDA')
+
   # Accumulate appropriate #ifdef/#ifndefs for threading conditions.
   if thread_config == MULTI_THREADED:
     preprocessor_conditions_begin.append('#ifndef CERES_NO_THREADS')
@@ -223,10 +251,12 @@
   # Substitute variables into the test template, and write the result to a file.
   filename_tag = '_'.join(FILENAME_SHORTENING_MAP.get(x) for x in [
       linear_solver,
+      dense_backend_tag,
       sparse_backend_tag,
       preconditioner_tag,
       ordering]
       if FILENAME_SHORTENING_MAP.get(x))
+
   if (thread_config == MULTI_THREADED):
     filename_tag += '_threads'
 
@@ -244,16 +274,37 @@
 if __name__ == '__main__':
   # Iterate over all the possible configurations and generate the tests.
   generated_files = []
-  for linear_solver, sparse_backend, preconditioner in SOLVER_CONFIGS:
-    for ordering in ORDERINGS:
-      for thread_config in THREAD_CONFIGS:
+
+  for ordering in ORDERINGS:
+    for thread_config in THREAD_CONFIGS:
+      for linear_solver, dense_backend in DENSE_SOLVER_CONFIGS:
         generated_files.append(
             generate_bundle_test(linear_solver,
+                                 dense_backend,
+                                 'NO_SPARSE',
+                                 'IDENTITY',
+                                 ordering,
+                                 thread_config))
+
+      for linear_solver, sparse_backend, in SPARSE_SOLVER_CONFIGS:
+        generated_files.append(
+            generate_bundle_test(linear_solver,
+                                 'EIGEN',
+                                 sparse_backend,
+                                 'IDENTITY',
+                                 ordering,
+                                 thread_config))
+
+      for linear_solver, sparse_backend, preconditioner, in ITERATIVE_SOLVER_CONFIGS:
+        generated_files.append(
+            generate_bundle_test(linear_solver,
+                                 'EIGEN',
                                  sparse_backend,
                                  preconditioner,
                                  ordering,
                                  thread_config))
 
+
   # Generate the CMakeLists.txt as well.
   with open('generated_bundle_adjustment_tests/CMakeLists.txt', 'w') as fd:
     fd.write(COPYRIGHT_HEADER.replace('//', '#').replace('http:#', 'http://'))