|  | # Ceres Solver - A fast non-linear least squares minimizer | 
|  | # Copyright 2023 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: keir@google.com (Keir Mierle) | 
|  | # | 
|  | # Generate bundle adjustment tests as separate binaries. Since the bundle | 
|  | # adjustment tests are fairly processing intensive, serializing them makes the | 
|  | # tests take forever to run. Splitting them into separate binaries makes it | 
|  | # easier to parallelize in continuous integration systems, and makes local | 
|  | # processing on multi-core workstations much faster. | 
|  |  | 
|  | # Product of ORDERINGS, THREAD_CONFIGS, and SOLVER_CONFIGS is the full set of | 
|  | # tests to generate. | 
|  | ORDERINGS = ["kAutomaticOrdering", "kUserOrdering"] | 
|  | SINGLE_THREADED = "1" | 
|  | MULTI_THREADED = "4" | 
|  | THREAD_CONFIGS = [SINGLE_THREADED, MULTI_THREADED] | 
|  |  | 
|  | 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', 'ACCELERATE_SPARSE'), | 
|  | ('SPARSE_NORMAL_CHOLESKY', 'CUDA_SPARSE'), | 
|  | ('SPARSE_SCHUR',           'SUITE_SPARSE'), | 
|  | ('SPARSE_SCHUR',           'EIGEN_SPARSE'), | 
|  | ('SPARSE_SCHUR',           'ACCELERATE_SPARSE'), | 
|  | ('SPARSE_SCHUR',           'CUDA_SPARSE') | 
|  | ] | 
|  |  | 
|  | ITERATIVE_SOLVER_CONFIGS = [ | 
|  | # Linear solver            Sparse backend      Preconditioner | 
|  | ('ITERATIVE_SCHUR',        'NO_SPARSE',        'JACOBI'), | 
|  | ('ITERATIVE_SCHUR',        'NO_SPARSE',        'SCHUR_JACOBI'), | 
|  | ('ITERATIVE_SCHUR',        'NO_SPARSE',        'SCHUR_POWER_SERIES_EXPANSION'), | 
|  | ('ITERATIVE_SCHUR',        'SUITE_SPARSE',     'CLUSTER_JACOBI'), | 
|  | ('ITERATIVE_SCHUR',        'EIGEN_SPARSE',     'CLUSTER_JACOBI'), | 
|  | ('ITERATIVE_SCHUR',        'ACCELERATE_SPARSE','CLUSTER_JACOBI'), | 
|  | ('ITERATIVE_SCHUR',        'CUDA_SPARSE',      'CLUSTER_JACOBI'), | 
|  | ('ITERATIVE_SCHUR',        'SUITE_SPARSE',     'CLUSTER_TRIDIAGONAL'), | 
|  | ('ITERATIVE_SCHUR',        'EIGEN_SPARSE',     'CLUSTER_TRIDIAGONAL'), | 
|  | ('ITERATIVE_SCHUR',        'ACCELERATE_SPARSE','CLUSTER_TRIDIAGONAL'), | 
|  | ('ITERATIVE_SCHUR',        'CUDA_SPARSE',      'CLUSTER_TRIDIAGONAL'), | 
|  | ] | 
|  |  | 
|  | FILENAME_SHORTENING_MAP = dict( | 
|  | DENSE_SCHUR='denseschur', | 
|  | 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', | 
|  | ACCELERATE_SPARSE='acceleratesparse', | 
|  | CUDA_SPARSE='cudasparse', | 
|  | IDENTITY='identity', | 
|  | JACOBI='jacobi', | 
|  | SCHUR_JACOBI='schurjacobi', | 
|  | CLUSTER_JACOBI='clustjacobi', | 
|  | CLUSTER_TRIDIAGONAL='clusttri', | 
|  | SCHUR_POWER_SERIES_EXPANSION='spse', | 
|  | kAutomaticOrdering='auto', | 
|  | kUserOrdering='user', | 
|  | ) | 
|  |  | 
|  | COPYRIGHT_HEADER = ( | 
|  | """// Ceres Solver - A fast non-linear least squares minimizer | 
|  | // Copyright 2023 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. | 
|  | // | 
|  | // ======================================== | 
|  | // THIS FILE IS AUTOGENERATED. DO NOT EDIT. | 
|  | // THIS FILE IS AUTOGENERATED. DO NOT EDIT. | 
|  | // THIS FILE IS AUTOGENERATED. DO NOT EDIT. | 
|  | // THIS FILE IS AUTOGENERATED. DO NOT EDIT. | 
|  | // ======================================== | 
|  | // | 
|  | // This file is generated using generate_bundle_adjustment_tests.py.""") | 
|  |  | 
|  | BUNDLE_ADJUSTMENT_TEST_TEMPLATE = (COPYRIGHT_HEADER + """ | 
|  |  | 
|  | #include "ceres/bundle_adjustment_test_util.h" | 
|  | #include "ceres/internal/config.h" | 
|  | #include "ceres/problem.h" | 
|  | #include "ceres/solver.h" | 
|  | #include "ceres/types.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | %(preprocessor_conditions_begin)s | 
|  | namespace ceres::internal { | 
|  |  | 
|  | TEST_F(BundleAdjustmentTest, | 
|  | %(test_class_name)s) {  // NOLINT | 
|  | BundleAdjustmentProblem bundle_adjustment_problem; | 
|  | Solver::Options* options = bundle_adjustment_problem.mutable_solver_options(); | 
|  | options->eta = 0.01; | 
|  | 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) { | 
|  | options->linear_solver_ordering = nullptr; | 
|  | } | 
|  | Problem* problem = bundle_adjustment_problem.mutable_problem(); | 
|  | RunSolverForConfigAndExpectResidualsMatch(*options, problem); | 
|  | } | 
|  |  | 
|  | }  // namespace ceres::internal | 
|  | %(preprocessor_conditions_end)s""") | 
|  |  | 
|  | def camelcasify(token): | 
|  | """Convert capitalized underscore tokens to camel case""" | 
|  | return ''.join([x.lower().capitalize() for x in token.split('_')]) | 
|  |  | 
|  |  | 
|  | def generate_bundle_test(linear_solver, | 
|  | dense_backend, | 
|  | sparse_backend, | 
|  | preconditioner, | 
|  | ordering, | 
|  | thread_config): | 
|  | """Generate a bundle adjustment test executable configured appropriately""" | 
|  |  | 
|  | # Preconditioner only makes sense for iterative schur; drop it otherwise. | 
|  | preconditioner_tag = preconditioner | 
|  | 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': | 
|  | sparse_backend_tag = '' | 
|  |  | 
|  | # 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' | 
|  | 'Threads' if thread_config == MULTI_THREADED else ''])) | 
|  |  | 
|  | # 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, | 
|  | num_threads=thread_config, | 
|  | test_class_name=test_class_name) | 
|  |  | 
|  | # Accumulate appropriate #ifdef/#ifndefs for the solver's sparse backend. | 
|  | preprocessor_conditions_begin = [] | 
|  | preprocessor_conditions_end = [] | 
|  | if sparse_backend == 'SUITE_SPARSE': | 
|  | preprocessor_conditions_begin.append('#ifndef CERES_NO_SUITESPARSE') | 
|  | preprocessor_conditions_end.insert(0, '#endif  // CERES_NO_SUITESPARSE') | 
|  | elif sparse_backend == 'ACCELERATE_SPARSE': | 
|  | preprocessor_conditions_begin.append('#ifndef CERES_NO_ACCELERATE_SPARSE') | 
|  | preprocessor_conditions_end.insert(0, '#endif  // CERES_NO_ACCELERATE_SPARSE') | 
|  | elif sparse_backend == 'EIGEN_SPARSE': | 
|  | preprocessor_conditions_begin.append('#ifdef CERES_USE_EIGEN_SPARSE') | 
|  | preprocessor_conditions_end.insert(0, '#endif  // CERES_USE_EIGEN_SPARSE') | 
|  | elif sparse_backend == 'CUDA_SPARSE': | 
|  | preprocessor_conditions_begin.append('#ifndef CERES_NO_CUDA') | 
|  | preprocessor_conditions_end.insert(0, '#endif  // CERES_NO_CUDA') | 
|  | if linear_solver == 'SPARSE_SCHUR' or linear_solver == 'SPARSE_NORMAL_CHOLESKY' or ( | 
|  | linear_solver == 'ITERATIVE_SCHUR' and ( | 
|  | preconditioner == 'CLUSTER_JACOBI' or preconditioner == 'CLUSTER_TRIDIAGONAL')): | 
|  | preprocessor_conditions_begin.append('#ifndef CERES_NO_CUDSS') | 
|  | preprocessor_conditions_end.insert(0, '#endif  // CERES_NO_CUDSS') | 
|  |  | 
|  | 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') | 
|  |  | 
|  | # If there are #ifdefs, put newlines around them. | 
|  | if preprocessor_conditions_begin: | 
|  | preprocessor_conditions_begin.insert(0, '') | 
|  | preprocessor_conditions_begin.append('') | 
|  | preprocessor_conditions_end.insert(0, '') | 
|  | preprocessor_conditions_end.append('') | 
|  |  | 
|  | # Put #ifdef/#ifndef stacks into the template parameters. | 
|  | template_parameters['preprocessor_conditions_begin'] = '\n'.join( | 
|  | preprocessor_conditions_begin) | 
|  | template_parameters['preprocessor_conditions_end'] = '\n'.join( | 
|  | preprocessor_conditions_end) | 
|  |  | 
|  | # 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' | 
|  |  | 
|  | filename = ('generated_bundle_adjustment_tests/ba_%s_test.cc' % | 
|  | filename_tag.lower()) | 
|  | with open(filename, 'w') as fd: | 
|  | fd.write(BUNDLE_ADJUSTMENT_TEST_TEMPLATE % template_parameters) | 
|  |  | 
|  | # All done. | 
|  | print('Generated', filename) | 
|  |  | 
|  | return filename | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | # Iterate over all the possible configurations and generate the tests. | 
|  | generated_files = [] | 
|  |  | 
|  | 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://')) | 
|  | fd.write('\n') | 
|  | fd.write('\n') | 
|  | for generated_file in generated_files: | 
|  | fd.write('ceres_test(%s)\n' % | 
|  | generated_file.split('/')[1].replace('_test.cc', '')) |