Add cuDSS as sparse Cholesky solver

cuDSS could be used as an alternative for SuiteSparse and EigenSparse
in case if CUDA capable GPU is available.

Change-Id: I7a567093ce91363478118153e181134ed5804573
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index c7e2cfa..eb1463b 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -82,6 +82,13 @@
           apt-get install -y nvidia-cuda-toolkit
           echo "CUDACXX=/usr/lib/nvidia-cuda-toolkit/bin/nvcc" >> $GITHUB_ENV
 
+      - name: Setup cuDSS
+        if: matrix.gpu == 'cuda'
+        run: |
+          wget https://developer.download.nvidia.com/compute/cudss/redist/libcudss/linux-x86_64/libcudss-linux-x86_64-0.3.0.9_cuda12-archive.tar.xz
+          tar -xf libcudss-linux-x86_64-0.3.0.9_cuda12-archive.tar.xz
+          echo "cudss_DIR=${{github.workspace}}/libcudss-linux-x86_64-0.3.0.9_cuda12-archive/lib/cmake/cudss" >> $GITHUB_ENV
+
       - name: Cache Build
         id: cache-build
         uses: actions/cache@v4
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index eaffa79..182ed30 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -133,6 +133,17 @@
           echo "CUDA_PATH_V12_5=$CUDA_PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
           echo "$CUDA_PATH/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
 
+      - name: Setup cuDSS
+        if: matrix.gpu == 'cuda'
+        run: |
+          Invoke-WebRequest https://developer.download.nvidia.com/compute/cudss/redist/libcudss/windows-x86_64/libcudss-windows-x86_64-0.3.0.9_cuda12-archive.zip -OutFile libcudss-windows-x86_64-0.3.0.9_cuda12-archive.zip
+          Expand-Archive -Path .\libcudss-windows-x86_64-0.3.0.9_cuda12-archive.zip -DestinationPath "C:/"
+          Remove-Item .\libcudss-windows-x86_64-0.3.0.9_cuda12-archive.zip
+          $CUDSS_PATH = "C:/libcudss-windows-x86_64-0.3.0.9_cuda12-archive/"
+          $CUDSS_CMAKE_CONFIG = "$CUDSS_PATH/lib/cmake/cudss/cudss-config.cmake"
+          echo "cudss_DIR=$CUDSS_PATH/lib/cmake/cudss" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
+          echo "$CUDSS_PATH/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+
       - name: Cache gflags
         id: cache-gflags
         uses: actions/cache@v4
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6cbc942..b92437c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -278,10 +278,21 @@
     CUDA::cudart${CERES_CUDA_TARGET_SUFFIX}
     CUDA::cusolver${CERES_CUDA_TARGET_SUFFIX}
     CUDA::cusparse${CERES_CUDA_TARGET_SUFFIX})
+
+  find_package(cudss CONFIG)
+  if (cudss_FOUND)
+    set(cudss_DEPENDENCY "find_dependency(cudss ${cudss_VERSION})")
+    list(APPEND CERES_CUDA_LIBRARIES cudss${CERES_CUDA_TARGET_SUFFIX})
+  else (cudss_FOUND)
+    list(APPEND CERES_COMPILE_OPTIONS CERES_NO_CUDSS)
+    message("-- Did not find cuDSS, SPARSE_SCHUR and SPARSE_NORMAL_CHOLESKY with CUDA_SPARSE will not be available.")
+  endif (cudss_FOUND)
+
   unset (CERES_CUDA_TARGET_SUFFIX)
   set(CMAKE_CUDA_RUNTIME_LIBRARY NONE)
 else (USE_CUDA)
   message("-- Building without CUDA.")
+  list(APPEND CERES_COMPILE_OPTIONS CERES_NO_CUDSS)
   list(APPEND CERES_COMPILE_OPTIONS CERES_NO_CUDA)
 endif (USE_CUDA)
 
@@ -380,10 +391,10 @@
 endif()
 
 # Ensure that the user understands they have disabled all sparse libraries.
-if (NOT SUITESPARSE AND NOT EIGENSPARSE AND NOT ACCELERATESPARSE)
+if (NOT SUITESPARSE AND NOT EIGENSPARSE AND NOT ACCELERATESPARSE AND NOT cudss_FOUND)
   message("   ===============================================================")
-  message("   Compiling without any sparse library: SuiteSparse, ")
-  message("   EigenSparse & Apple's Accelerate are all disabled or unavailable.  ")
+  message("   Compiling without any sparse library: SuiteSparse, EigenSparse")
+  message("   Apple's Accelerate & cuDSS are all disabled or unavailable.  ")
   message("   No sparse linear solvers (SPARSE_NORMAL_CHOLESKY & SPARSE_SCHUR)")
   message("   will be available when Ceres is used.")
   message("   ===============================================================")
diff --git a/cmake/CeresCompileOptionsToComponents.cmake b/cmake/CeresCompileOptionsToComponents.cmake
index 64634d5..f4ed9ee 100644
--- a/cmake/CeresCompileOptionsToComponents.cmake
+++ b/cmake/CeresCompileOptionsToComponents.cmake
@@ -79,6 +79,8 @@
   add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR}
     CERES_NO_ACCELERATE_SPARSE "AccelerateSparse;SparseLinearAlgebraLibrary")
   add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR}
+    CERES_NO_CUDSS "cuDSS;SparseLinearAlgebraLibrary")
+  add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR}
     CERES_RESTRICT_SCHUR_SPECIALIZATION "SchurSpecializations")
   # Remove duplicates of SparseLinearAlgebraLibrary if multiple sparse backends
   # are present.
diff --git a/cmake/CeresConfig.cmake.in b/cmake/CeresConfig.cmake.in
index ceb7e26..b929c82 100644
--- a/cmake/CeresConfig.cmake.in
+++ b/cmake/CeresConfig.cmake.in
@@ -180,6 +180,7 @@
 @METIS_DEPENDENCY@
 @SuiteSparse_DEPENDENCY@
 @CUDAToolkit_DEPENDENCY@
+@cudss_DEPENDENCY@
 @Threads_DEPENDENCY@
 
 # As imported CMake targets are not re-exported when a dependent target is
diff --git a/cmake/config.h.in b/cmake/config.h.in
index 1566795..51db4a0 100644
--- a/cmake/config.h.in
+++ b/cmake/config.h.in
@@ -53,11 +53,15 @@
 // If defined, Ceres was compiled without CUDA.
 @CERES_NO_CUDA@
 
+// If defined, Ceres was compiled without cuDSS.
+@CERES_NO_CUDSS@
+
 // If defined, Ceres was compiled without Apple's Accelerate framework solvers.
 @CERES_NO_ACCELERATE_SPARSE@
 
 #if defined(CERES_NO_SUITESPARSE) &&              \
     defined(CERES_NO_ACCELERATE_SPARSE) &&        \
+    defined(CERES_NO_CUDSS) &&                    \
     !defined(CERES_USE_EIGEN_SPARSE)  // NOLINT
 // If defined Ceres was compiled without any sparse linear algebra support.
 #define CERES_NO_SPARSE
@@ -90,6 +94,9 @@
 #if defined(CERES_USE_EIGEN_SPARSE)
 #error CERES_NO_SPARSE requires !CERES_USE_EIGEN_SPARSE
 #endif
+#if !defined(CERES_NO_CUDSS)
+#error CERES_NO_SPARSE required CERES_NO_CUDSS
+#endif
 #endif
 
 #endif  // CERES_PUBLIC_INTERNAL_CONFIG_H_
diff --git a/examples/bundle_adjuster.cc b/examples/bundle_adjuster.cc
index 582ae2e..8df0d8a 100644
--- a/examples/bundle_adjuster.cc
+++ b/examples/bundle_adjuster.cc
@@ -96,8 +96,7 @@
             "Use power series expansion to initialize the solution in ITERATIVE_SCHUR linear solver.");
 
 DEFINE_string(sparse_linear_algebra_library, "suite_sparse",
-              "Options are: suite_sparse, accelerate_sparse, eigen_sparse, and "
-              "cuda_sparse.");
+              "Options are: suite_sparse, accelerate_sparse, eigen_sparse and cuda_sparse");
 DEFINE_string(dense_linear_algebra_library, "eigen",
               "Options are: eigen, lapack, and cuda");
 DEFINE_string(ordering_type, "amd", "Options are: amd, nesdis");
@@ -381,6 +380,7 @@
 int main(int argc, char** argv) {
   GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
   google::InitGoogleLogging(argv[0]);
+
   if (CERES_GET_FLAG(FLAGS_input).empty()) {
     LOG(ERROR) << "Usage: bundle_adjuster --input=bal_problem";
     return 1;
diff --git a/include/ceres/types.h b/include/ceres/types.h
index 6e19c51..f5b66a9 100644
--- a/include/ceres/types.h
+++ b/include/ceres/types.h
@@ -175,7 +175,7 @@
   // Apple's Accelerate framework sparse linear algebra routines.
   ACCELERATE_SPARSE,
 
-  // Nvidia's cuSPARSE library.
+  // Nvidia's cuDSS and cuSPARSE libraries.
   CUDA_SPARSE,
 
   // No sparse linear solver should be used.  This does not necessarily
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index f4e40fb..6c1fe93 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -170,6 +170,7 @@
     cuda_partitioned_block_sparse_crs_view.cc
     cuda_block_structure.cc
     cuda_sparse_matrix.cc
+    cuda_sparse_cholesky.cc
     cuda_vector.cc
     dense_cholesky.cc
     dense_normal_cholesky_solver.cc
diff --git a/internal/ceres/context_impl.cc b/internal/ceres/context_impl.cc
index 2b9d9cc..073dc7a 100644
--- a/internal/ceres/context_impl.cc
+++ b/internal/ceres/context_impl.cc
@@ -60,6 +60,12 @@
     cusparseDestroy(cusparse_handle_);
     cusparse_handle_ = nullptr;
   }
+#ifndef CERES_NO_CUDSS
+  if (cudss_handle_ != nullptr) {
+    cudssDestroy(cudss_handle_);
+    cudss_handle_ = nullptr;
+  }
+#endif  // CERES_NO_CUDSS
   for (auto& s : streams_) {
     if (s != nullptr) {
       cudaStreamDestroy(s);
@@ -157,6 +163,13 @@
     return false;
   }
   event_logger.AddEvent("cusparseCreate");
+#ifndef CERES_NO_CUDSS
+  if (cudssCreate(&cudss_handle_) != CUDSS_STATUS_SUCCESS) {
+    *message = "CUDA initialization failed because cudssCreate() failed.";
+    TearDown();
+    return false;
+  }
+#endif  // CERES_NO_CUDSS
   for (auto& s : streams_) {
     if (cudaStreamCreateWithFlags(&s, cudaStreamNonBlocking) != cudaSuccess) {
       *message =
@@ -172,7 +185,11 @@
       cublasSetStream(cublas_handle_, DefaultStream()) !=
           CUBLAS_STATUS_SUCCESS ||
       cusparseSetStream(cusparse_handle_, DefaultStream()) !=
-          CUSPARSE_STATUS_SUCCESS) {
+          CUSPARSE_STATUS_SUCCESS
+#ifndef CERES_NO_CUDSS
+      || cudssSetStream(cudss_handle_, DefaultStream()) != CUDSS_STATUS_SUCCESS
+#endif  // CERES_NO_CUDSS
+  ) {
     *message = "CUDA initialization failed because SetStream failed.";
     TearDown();
     return false;
diff --git a/internal/ceres/context_impl.h b/internal/ceres/context_impl.h
index 46692e6..cc301ee 100644
--- a/internal/ceres/context_impl.h
+++ b/internal/ceres/context_impl.h
@@ -47,6 +47,9 @@
 #include "cuda_runtime.h"
 #include "cusolverDn.h"
 #include "cusparse.h"
+#ifndef CERES_NO_CUDSS
+#include "cudss.h"
+#endif  // CERES_NO_CUDSS
 #endif  // CERES_NO_CUDA
 
 #include "ceres/thread_pool.h"
@@ -122,6 +125,9 @@
 
   cusolverDnHandle_t cusolver_handle_ = nullptr;
   cublasHandle_t cublas_handle_ = nullptr;
+#ifndef CERES_NO_CUDSS
+  cudssHandle_t cudss_handle_ = nullptr;
+#endif  // CERES_NO_CUDSS
 
   // Default stream.
   // Kernel invocations and memory copies on this stream can be left without
diff --git a/internal/ceres/cuda_buffer.h b/internal/ceres/cuda_buffer.h
index 40048fd..95504ca 100644
--- a/internal/ceres/cuda_buffer.h
+++ b/internal/ceres/cuda_buffer.h
@@ -36,6 +36,8 @@
 
 #ifndef CERES_NO_CUDA
 
+#include <cstddef>
+#include <utility>
 #include <vector>
 
 #include "cuda_runtime.h"
@@ -165,6 +167,55 @@
   size_t size_ = 0;
   ContextImpl* context_ = nullptr;
 };
+
+// This class wraps host memory region allocated via cudaMallocHost. Such memory
+// region is page-locked, hence enabling direct transfer to/from device,
+// avoiding implicit buffering under the hood of CUDA API.
+template <typename T>
+class CudaPinnedHostBuffer {
+ public:
+  CudaPinnedHostBuffer() noexcept = default;
+  CudaPinnedHostBuffer(int size) { Reserve(size); }
+  CudaPinnedHostBuffer(CudaPinnedHostBuffer&& other) noexcept
+      : data_(std::exchange(other.data_, nullptr)),
+        size_(std::exchange(other.size_, 0)) {}
+  CudaPinnedHostBuffer(const CudaPinnedHostBuffer&) = delete;
+  CudaPinnedHostBuffer& operator=(const CudaPinnedHostBuffer&) = delete;
+  CudaPinnedHostBuffer& operator=(CudaPinnedHostBuffer&& other) noexcept {
+    Free();
+    data_ = std::exchange(other.data_, nullptr);
+    size_ = std::exchange(other.size_, 0);
+    return *this;
+  }
+  ~CudaPinnedHostBuffer() { Free(); }
+
+  void Reserve(const std::size_t size) {
+    if (size > size_) {
+      Free();
+      CHECK_EQ(cudaMallocHost(&data_, size * sizeof(T)), cudaSuccess)
+          << "Failed to allocate " << size * sizeof(T)
+          << " bytes of pinned host memory";
+      size_ = size;
+    }
+  }
+
+  T* data() noexcept { return data_; }
+  const T* data() const noexcept { return data_; }
+  std::size_t size() const noexcept { return size_; }
+
+ private:
+  void Free() {
+    if (data_ != nullptr) {
+      CHECK_EQ(cudaFreeHost(data_), cudaSuccess);
+      data_ = nullptr;
+      size_ = 0;
+    }
+  }
+
+  T* data_ = nullptr;
+  std::size_t size_ = 0;
+};
+
 }  // namespace ceres::internal
 
 #endif  // CERES_NO_CUDA
diff --git a/internal/ceres/cuda_sparse_cholesky.cc b/internal/ceres/cuda_sparse_cholesky.cc
new file mode 100644
index 0000000..d4a285b
--- /dev/null
+++ b/internal/ceres/cuda_sparse_cholesky.cc
@@ -0,0 +1,485 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2024 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: markshachkov@gmail.com (Mark Shachkov)
+
+#include "ceres/cuda_sparse_cholesky.h"
+
+#ifndef CERES_NO_CUDSS
+
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <type_traits>
+
+#include "Eigen/Core"
+#include "ceres/compressed_row_sparse_matrix.h"
+#include "ceres/cuda_buffer.h"
+#include "ceres/linear_solver.h"
+#include "cudss.h"
+
+namespace ceres::internal {
+
+inline std::string cuDSSStatusToString(cudssStatus_t status) {
+  switch (status) {
+    case CUDSS_STATUS_SUCCESS:
+      return "CUDSS_STATUS_SUCCESS";
+    case CUDSS_STATUS_NOT_INITIALIZED:
+      return "CUDSS_STATUS_NOT_INITIALIZED";
+    case CUDSS_STATUS_ALLOC_FAILED:
+      return "CUDSS_STATUS_ALLOC_FAILED";
+    case CUDSS_STATUS_INVALID_VALUE:
+      return "CUDSS_STATUS_INVALID_VALUE";
+    case CUDSS_STATUS_NOT_SUPPORTED:
+      return "CUDSS_STATUS_NOT_SUPPORTED";
+    case CUDSS_STATUS_EXECUTION_FAILED:
+      return "CUDSS_STATUS_EXECUTION_FAILED";
+    case CUDSS_STATUS_INTERNAL_ERROR:
+      return "CUDSS_STATUS_INTERNAL_ERROR";
+    default:
+      return "unknown cuDSS status: " + std::to_string(status);
+  }
+}
+
+#define CUDSS_STATUS_CHECK(IN)                                     \
+  if (cudssStatus_t status = IN; status != CUDSS_STATUS_SUCCESS) { \
+    CHECK(false) << "Got error: " << cuDSSStatusToString(status);  \
+  }
+
+#define CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(IN, additional_message)    \
+  if (cudssStatus_t status = IN; status != CUDSS_STATUS_SUCCESS) {       \
+    *message = std::string(additional_message) +                         \
+               " Got error: " + cuDSSStatusToString(status);             \
+    return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR; \
+  }
+
+#define CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(IN)                 \
+  if (cudssStatus_t status = IN; status != CUDSS_STATUS_SUCCESS) { \
+    return status;                                                 \
+  }
+
+class CERES_NO_EXPORT CuDSSMatrixBase {
+ public:
+  CuDSSMatrixBase() = default;
+  CuDSSMatrixBase(const CuDSSMatrixBase&) = delete;
+  CuDSSMatrixBase(CuDSSMatrixBase&&) = delete;
+  CuDSSMatrixBase& operator=(const CuDSSMatrixBase&) = delete;
+  CuDSSMatrixBase& operator=(CuDSSMatrixBase&&) = delete;
+  ~CuDSSMatrixBase() { CUDSS_STATUS_CHECK(Free()); }
+
+  cudssStatus_t Free() noexcept {
+    if (matrix_) {
+      const auto status = cudssMatrixDestroy(matrix_);
+      matrix_ = nullptr;
+      return status;
+    }
+
+    return CUDSS_STATUS_SUCCESS;
+  }
+
+  cudssMatrix_t Get() const noexcept { return matrix_; }
+
+ protected:
+  cudssMatrix_t matrix_{nullptr};
+};
+
+class CERES_NO_EXPORT CuDSSMatrixCSR : public CuDSSMatrixBase {
+ public:
+  cudssStatus_t Reset(int64_t num_rows,
+                      int64_t num_cols,
+                      int64_t num_nonzeros,
+                      void* rows_start,
+                      void* rows_end,
+                      void* cols,
+                      void* values,
+                      cudaDataType_t index_type,
+                      cudaDataType_t value_type,
+                      cudssMatrixType_t matrix_type,
+                      cudssMatrixViewType_t matrix_storage_type,
+                      cudssIndexBase_t index_base) {
+    CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(Free());
+
+    return cudssMatrixCreateCsr(&matrix_,
+                                num_rows,
+                                num_cols,
+                                num_nonzeros,
+                                rows_start,
+                                rows_end,
+                                cols,
+                                values,
+                                index_type,
+                                value_type,
+                                matrix_type,
+                                matrix_storage_type,
+                                index_base);
+  }
+};
+
+class CERES_NO_EXPORT CuDSSMatrixDense : public CuDSSMatrixBase {
+ public:
+  cudssStatus_t Reset(int64_t num_rows,
+                      int64_t num_cols,
+                      int64_t leading_dimension_size,
+                      void* values,
+                      cudaDataType_t value_type,
+                      cudssLayout_t layout) {
+    CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(Free());
+
+    return cudssMatrixCreateDn(&matrix_,
+                               num_rows,
+                               num_cols,
+                               leading_dimension_size,
+                               values,
+                               value_type,
+                               layout);
+  }
+};
+
+struct CudssContext {
+  CudssContext(cudssHandle_t cudss_handle) : cudss_handle_(cudss_handle) {
+    CUDSS_STATUS_CHECK(cudssConfigCreate(&solver_config_));
+    CUDSS_STATUS_CHECK(cudssDataCreate(cudss_handle_, &solver_data_));
+  }
+  CudssContext(const CudssContext&) = delete;
+  CudssContext(CudssContext&&) = delete;
+  CudssContext& operator=(const CudssContext&) = delete;
+  CudssContext& operator=(CudssContext&&) = delete;
+  ~CudssContext() {
+    CUDSS_STATUS_CHECK(cudssDataDestroy(cudss_handle_, solver_data_));
+    CUDSS_STATUS_CHECK(cudssConfigDestroy(solver_config_));
+  }
+
+  cudssHandle_t cudss_handle_{nullptr};
+  cudssConfig_t solver_config_{nullptr};
+  cudssData_t solver_data_{nullptr};
+};
+
+template <typename Scalar>
+class CERES_NO_EXPORT CudaSparseCholeskyImpl final : public SparseCholesky {
+ public:
+  static_assert(std::is_same_v<Scalar, float> || std::is_same_v<Scalar, double>,
+                "Scalar type is unsuported by cuDSS");
+  static constexpr cudaDataType_t kCuDSSScalar =
+      std::is_same_v<Scalar, float> ? CUDA_R_32F : CUDA_R_64F;
+
+  CudaSparseCholeskyImpl(ContextImpl* context)
+      : context_(context),
+        lhs_cols_d_(context_),
+        lhs_rows_d_(context_),
+        lhs_values_d_(context_),
+        rhs_d_(context_),
+        x_d_(context_) {}
+  CudaSparseCholeskyImpl(const CudaSparseCholeskyImpl&) = delete;
+  CudaSparseCholeskyImpl(CudaSparseCholeskyImpl&&) = delete;
+  CudaSparseCholeskyImpl& operator=(const CudaSparseCholeskyImpl&) = delete;
+  CudaSparseCholeskyImpl& operator=(CudaSparseCholeskyImpl&&) = delete;
+  ~CudaSparseCholeskyImpl() = default;
+
+  CompressedRowSparseMatrix::StorageType StorageType() const {
+    return CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR;
+  }
+
+  LinearSolverTerminationType Factorize(CompressedRowSparseMatrix* lhs,
+                                        std::string* message) {
+    if (lhs->num_rows() != lhs->num_cols()) {
+      *message = "lhs matrix must be square";
+      return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR;
+    }
+    if (lhs->storage_type() != StorageType()) {
+      *message = "lhs matrix must be lower triangular";
+      return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR;
+    }
+
+    // If, after previous attempt to factorize, cudssDataGet(CUDSS_DATA_INFO)
+    // returned a numerical error, such error will be preserved by cuDSS 0.3.0
+    // and returned by cudssDataGet(CUDSS_DATA_INFO) even after correctly
+    // factorizeable matrix is provided. Such behaviour forces us to reset a
+    // cudssData_t object (managed by CudssContext) and to loose a result of
+    // anylyze stage, thus we have to perform analyze one more time.
+    // TODO: do not re-perform analyze in case of failed factorization numerics
+    if (analyze_result_ != LinearSolverTerminationType::SUCCESS ||
+        factorize_result_ != LinearSolverTerminationType::SUCCESS) {
+      analyze_result_ = Analyze(lhs, message);
+      if (analyze_result_ != LinearSolverTerminationType::SUCCESS) {
+        return analyze_result_;
+      }
+    }
+    CHECK_NE(cudss_context_.get(), nullptr);
+
+    ConvertAndCopyToDevice(lhs->values(), lhs_values_h_.data(), lhs_values_d_);
+
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        cudssExecute(context_->cudss_handle_,
+                     CUDSS_PHASE_FACTORIZATION,
+                     cudss_context_->solver_config_,
+                     cudss_context_->solver_data_,
+                     cudss_lhs_.Get(),
+                     cudss_x_.Get(),
+                     cudss_rhs_.Get()),
+        "cudssExecute with CUDSS_PHASE_FACTORIZATION failed");
+
+    int cudss_data_info;
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        GetCudssDataInfo(cudss_data_info),
+        "cudssDataGet with CUDSS_DATA_INFO failed");
+    const auto factorization_status =
+        static_cast<cudssStatus_t>(cudss_data_info);
+
+    if (factorization_status == CUDSS_STATUS_SUCCESS) {
+      return factorize_result_ = LinearSolverTerminationType::SUCCESS;
+    }
+
+    if (cudss_data_info > 0) {
+      return factorize_result_ = LinearSolverTerminationType::FAILURE;
+    }
+
+    return factorize_result_ = LinearSolverTerminationType::FATAL_ERROR;
+  }
+
+  LinearSolverTerminationType Solve(const double* rhs,
+                                    double* solution,
+                                    std::string* message) {
+    CHECK_NE(cudss_context_.get(), nullptr);
+
+    if (factorize_result_ != LinearSolverTerminationType::SUCCESS) {
+      *message = "Factorize did not complete successfully previously.";
+      return factorize_result_;
+    }
+
+    ConvertAndCopyToDevice(rhs, rhs_h_.data(), rhs_d_);
+
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        cudssExecute(context_->cudss_handle_,
+                     CUDSS_PHASE_SOLVE,
+                     cudss_context_->solver_config_,
+                     cudss_context_->solver_data_,
+                     cudss_lhs_.Get(),
+                     cudss_x_.Get(),
+                     cudss_rhs_.Get()),
+        "cudssExecute with CUDSS_PHASE_SOLVE failed");
+
+    ConvertAndCopyToHost(x_d_, x_h_.data(), solution);
+
+    int cudss_data_info;
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        GetCudssDataInfo(cudss_data_info),
+        "cudssDataGet with CUDSS_DATA_INFO failed");
+    const auto solve_status = static_cast<cudssStatus_t>(cudss_data_info);
+
+    if (solve_status != CUDSS_STATUS_SUCCESS) {
+      return LinearSolverTerminationType::FAILURE;
+    }
+
+    return LinearSolverTerminationType::SUCCESS;
+  }
+
+ private:
+  cudssStatus_t GetCudssDataInfo(int& cudss_data_info) {
+    CHECK_NE(cudss_context_.get(), nullptr);
+    std::size_t size_written = 0;
+    CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS(
+        cudssDataGet(context_->cudss_handle_,
+                     cudss_context_->solver_data_,
+                     CUDSS_DATA_INFO,
+                     &cudss_data_info,
+                     sizeof(cudss_data_info),
+                     &size_written));
+    // TODO: enable following check after cudssDataGet will be fixed
+    // CHECK_EQ(size_written, sizeof(cudss_data_info));
+
+    return CUDSS_STATUS_SUCCESS;
+  }
+
+  LinearSolverTerminationType Analyze(const CompressedRowSparseMatrix* lhs,
+                                      std::string* message) {
+    if (auto status = SetupCudssMatrices(lhs, message);
+        status != LinearSolverTerminationType::SUCCESS) {
+      return status;
+    }
+
+    lhs_rows_d_.CopyFromCpu(lhs->rows(), lhs->num_rows() + 1);
+    lhs_cols_d_.CopyFromCpu(lhs->cols(), lhs->num_nonzeros());
+
+    // Analyze and factorization results are stored in cudssData_t (managed by
+    // CudssContext). Given that cuDSS 0.3.0 does not reset it's error state in
+    // case of failed numerics at factorization stage, we have to reset
+    // cudssData_t and to recompute an analyze stage while trying to factorize a
+    // rescaled matrix with the same structure.
+    // TODO: move creation of CudssContext to ctor of CudaSparseCholeskyImpl
+    cudss_context_ = std::make_unique<CudssContext>(context_->cudss_handle_);
+
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        cudssExecute(context_->cudss_handle_,
+                     CUDSS_PHASE_ANALYSIS,
+                     cudss_context_->solver_config_,
+                     cudss_context_->solver_data_,
+                     cudss_lhs_.Get(),
+                     cudss_x_.Get(),
+                     cudss_rhs_.Get()),
+        "cudssExecute with CUDSS_PHASE_ANALYSIS failed");
+
+    return LinearSolverTerminationType::SUCCESS;
+  }
+
+  // Resize buffers and setup cuDSS structs that describe the type and storage
+  // configuration of linear system operands.
+  LinearSolverTerminationType SetupCudssMatrices(
+      const CompressedRowSparseMatrix* lhs, std::string* message) {
+    const auto num_rows = lhs->num_rows();
+    const auto num_nonzeros = lhs->num_nonzeros();
+
+    if constexpr (std::is_same_v<Scalar, float>) {
+      lhs_values_h_.Reserve(num_nonzeros);
+      rhs_h_.Reserve(num_rows);
+      x_h_.Reserve(num_rows);
+    }
+
+    lhs_rows_d_.Reserve(num_rows + 1);
+    lhs_cols_d_.Reserve(num_nonzeros);
+    lhs_values_d_.Reserve(num_nonzeros);
+    rhs_d_.Reserve(num_rows);
+    x_d_.Reserve(num_rows);
+
+    static constexpr auto kFailedToCreateCuDSSMatrix =
+        "cudssMatrixCreate() call failed";
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(cudss_lhs_.Reset(num_rows,
+                                                           num_rows,
+                                                           num_nonzeros,
+                                                           lhs_rows_d_.data(),
+                                                           nullptr,
+                                                           lhs_cols_d_.data(),
+                                                           lhs_values_d_.data(),
+                                                           CUDA_R_32I,
+                                                           kCuDSSScalar,
+                                                           CUDSS_MTYPE_SPD,
+                                                           CUDSS_MVIEW_LOWER,
+                                                           CUDSS_BASE_ZERO),
+                                          kFailedToCreateCuDSSMatrix);
+
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        cudss_rhs_.Reset(num_rows,
+                         1,
+                         num_rows,
+                         rhs_d_.data(),
+                         kCuDSSScalar,
+                         CUDSS_LAYOUT_COL_MAJOR),
+        kFailedToCreateCuDSSMatrix);
+
+    CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR(
+        cudss_x_.Reset(num_rows,
+                       1,
+                       num_rows,
+                       x_d_.data(),
+                       kCuDSSScalar,
+                       CUDSS_LAYOUT_COL_MAJOR),
+        kFailedToCreateCuDSSMatrix);
+
+    return LinearSolverTerminationType::SUCCESS;
+  }
+
+  template <typename S, typename D>
+  void Convert(const S* source, D* destination, size_t size) {
+    Eigen::Map<Eigen::Matrix<D, Eigen::Dynamic, 1>>(destination, size) =
+        Eigen::Map<const Eigen::Matrix<S, Eigen::Dynamic, 1>>(source, size)
+            .template cast<D>();
+  }
+
+  void ConvertAndCopyToDevice(const double* source,
+                              Scalar* intermediate,
+                              CudaBuffer<Scalar>& destination) {
+    const auto size = destination.size();
+    if constexpr (std::is_same_v<Scalar, double>) {
+      destination.CopyFromCpu(source, size);
+    } else {
+      Convert(source, intermediate, size);
+      destination.CopyFromCpu(intermediate, size);
+    }
+  }
+
+  void ConvertAndCopyToHost(const CudaBuffer<Scalar>& source,
+                            Scalar* intermediate,
+                            double* destination) {
+    const auto size = source.size();
+    if constexpr (std::is_same_v<Scalar, double>) {
+      source.CopyToCpu(destination, source.size());
+    } else {
+      source.CopyToCpu(intermediate, source.size());
+      Convert(intermediate, destination, size);
+    }
+  }
+
+  ContextImpl* context_{nullptr};
+  std::unique_ptr<CudssContext> cudss_context_;
+  CuDSSMatrixCSR cudss_lhs_;
+  CuDSSMatrixDense cudss_rhs_;
+  CuDSSMatrixDense cudss_x_;
+
+  CudaPinnedHostBuffer<Scalar> lhs_values_h_;
+  CudaPinnedHostBuffer<Scalar> rhs_h_;
+  CudaPinnedHostBuffer<Scalar> x_h_;
+  CudaBuffer<int> lhs_rows_d_;
+  CudaBuffer<int> lhs_cols_d_;
+  CudaBuffer<Scalar> lhs_values_d_;
+  CudaBuffer<Scalar> rhs_d_;
+  CudaBuffer<Scalar> x_d_;
+
+  LinearSolverTerminationType analyze_result_ =
+      LinearSolverTerminationType::FATAL_ERROR;
+  LinearSolverTerminationType factorize_result_ =
+      LinearSolverTerminationType::FATAL_ERROR;
+};
+
+template <typename Scalar>
+std::unique_ptr<SparseCholesky> CudaSparseCholesky<Scalar>::Create(
+    ContextImpl* context, const OrderingType ordering_type) {
+  if (ordering_type == OrderingType::NESDIS) {
+    LOG(FATAL)
+        << "Congratulations you have found a bug in Ceres Solver. Please "
+           "report it to the Ceres Solver developers.";
+    return nullptr;
+  }
+
+  if (context == nullptr || !context->IsCudaInitialized()) {
+    LOG(FATAL) << "CudaSparseCholesky requires CUDA context to be initialized";
+    return nullptr;
+  }
+
+  return std::make_unique<CudaSparseCholeskyImpl<Scalar>>(context);
+}
+
+template class CudaSparseCholesky<float>;
+template class CudaSparseCholesky<double>;
+
+}  // namespace ceres::internal
+
+#undef CUDSS_STATUS_CHECK
+#undef CUDSS_STATUS_OK_OR_RETURN_FATAL_ERROR
+#undef CUDSS_STATUS_OK_OR_RETURN_CUDSS_STATUS
+
+#endif  // CERES_NO_CUDSS
diff --git a/internal/ceres/cuda_sparse_cholesky.h b/internal/ceres/cuda_sparse_cholesky.h
new file mode 100644
index 0000000..e147633
--- /dev/null
+++ b/internal/ceres/cuda_sparse_cholesky.h
@@ -0,0 +1,66 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2024 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: markshachkov@gmail.com (Mark Shachkov)
+//
+// A C++ interface to cuDSS.
+
+#ifndef CERES_INTERNAL_CUDA_SPARSE_CHOLESKY_H_
+#define CERES_INTERNAL_CUDA_SPARSE_CHOLESKY_H_
+
+// This include must come before any #ifndef check on Ceres compile options.
+#include "ceres/internal/config.h"
+
+#ifndef CERES_NO_CUDSS
+
+#include <memory>
+
+#include "ceres/internal/export.h"
+#include "ceres/linear_solver.h"
+#include "ceres/sparse_cholesky.h"
+
+namespace ceres::internal {
+
+// This class is a factory for implementation of sparse cholesky that uses cuDSS
+// on CUDA capable GPU's to solve sparse linear systems. Scalar controls the
+// precision used during computations, currently float and double are supported.
+// Details of implementation are incapsulated into cuda_sparse_cholesky.cc
+template <typename Scalar = double>
+class CERES_NO_EXPORT CudaSparseCholesky : public SparseCholesky {
+ public:
+  static constexpr bool IsNestedDissectionAvailable() noexcept { return false; }
+
+  static std::unique_ptr<SparseCholesky> Create(
+      ContextImpl* context, const OrderingType ordering_type);
+};
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+
+#endif  // CERES_INTERNAL_CUDA_SPARSE_CHOLESKY_H_
diff --git a/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc b/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc
index d77d7f7..6dd0adf 100644
--- a/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc
+++ b/internal/ceres/dynamic_sparse_normal_cholesky_solver.cc
@@ -46,6 +46,7 @@
 #include "ceres/triplet_sparse_matrix.h"
 #include "ceres/types.h"
 #include "ceres/wall_time.h"
+#include "cuda_sparse_cholesky.h"
 
 #ifdef CERES_USE_EIGEN_SPARSE
 #include "Eigen/SparseCholesky"
@@ -88,6 +89,9 @@
     case EIGEN_SPARSE:
       summary = SolveImplUsingEigen(A, x);
       break;
+    case CUDA_SPARSE:
+      summary = SolveImplUsingCuda(A, x);
+      break;
     default:
       LOG(FATAL) << "Unsupported sparse linear algebra library for "
                  << "dynamic sparsity: "
@@ -231,4 +235,63 @@
 #endif
 }
 
+LinearSolver::Summary DynamicSparseNormalCholeskySolver::SolveImplUsingCuda(
+    CompressedRowSparseMatrix* A, double* rhs_and_solution) {
+#ifdef CERES_NO_CUDSS
+  (void)A;
+  (void)rhs_and_solution;
+
+  LinearSolver::Summary summary;
+  summary.num_iterations = 0;
+  summary.termination_type = LinearSolverTerminationType::FATAL_ERROR;
+  summary.message =
+      "SPARSE_NORMAL_CHOLESKY cannot be used with CUDA_SPARSE "
+      "because Ceres was not built with support for cuDSS. "
+      "This requires enabling building with -DUSE_CUDA=ON and ensuring that "
+      "cuDSS is found.";
+  return summary;
+#else
+
+  EventLogger event_logger("DynamicSparseNormalCholeskySolver::cuDSS::Solve");
+
+  // TODO: Consider computing A^T*A on device via cuSPARSE
+  // https://github.com/ceres-solver/ceres-solver/issues/1066
+  Eigen::Map<Eigen::SparseMatrix<double, Eigen::RowMajor>> a(
+      A->num_rows(),
+      A->num_cols(),
+      A->num_nonzeros(),
+      A->mutable_rows(),
+      A->mutable_cols(),
+      A->mutable_values());
+  Eigen::SparseMatrix<double, Eigen::RowMajor> ata =
+      (a.transpose() * a).triangularView<Eigen::Lower>();
+
+  CompressedRowSparseMatrix lhs(ata.rows(), ata.cols(), ata.nonZeros());
+  std::copy_n(ata.outerIndexPtr(), lhs.num_rows() + 1, lhs.mutable_rows());
+  std::copy_n(ata.innerIndexPtr(), lhs.num_nonzeros(), lhs.mutable_cols());
+  std::copy_n(ata.valuePtr(), lhs.num_nonzeros(), lhs.mutable_values());
+  lhs.set_storage_type(
+      CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR);
+  event_logger.AddEvent("Compute A^T * A");
+
+  auto sparse_cholesky = CudaSparseCholesky<double>::Create(
+      options_.context, options_.ordering_type);
+
+  LinearSolver::Summary summary;
+  summary.num_iterations = 1;
+  summary.termination_type = sparse_cholesky->Factorize(&lhs, &summary.message);
+  if (summary.termination_type != LinearSolverTerminationType::SUCCESS) {
+    return summary;
+  }
+  event_logger.AddEvent("Analyze");
+
+  const Vector rhs = ConstVectorRef(rhs_and_solution, A->num_cols());
+  summary.termination_type =
+      sparse_cholesky->Solve(rhs.data(), rhs_and_solution, &summary.message);
+  event_logger.AddEvent("Solve");
+
+  return summary;
+#endif
+}
+
 }  // namespace ceres::internal
diff --git a/internal/ceres/dynamic_sparse_normal_cholesky_solver.h b/internal/ceres/dynamic_sparse_normal_cholesky_solver.h
index 394ba2a..e5e8a28 100644
--- a/internal/ceres/dynamic_sparse_normal_cholesky_solver.h
+++ b/internal/ceres/dynamic_sparse_normal_cholesky_solver.h
@@ -70,6 +70,9 @@
   LinearSolver::Summary SolveImplUsingEigen(CompressedRowSparseMatrix* A,
                                             double* rhs_and_solution);
 
+  LinearSolver::Summary SolveImplUsingCuda(CompressedRowSparseMatrix* A,
+                                            double* rhs_and_solution);
+
   const LinearSolver::Options options_;
 };
 
diff --git a/internal/ceres/dynamic_sparsity_test.cc b/internal/ceres/dynamic_sparsity_test.cc
index 0c29595..ae56037 100644
--- a/internal/ceres/dynamic_sparsity_test.cc
+++ b/internal/ceres/dynamic_sparsity_test.cc
@@ -426,38 +426,41 @@
 
   Solver::Options options;
   options.max_num_iterations = 100;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  // Only SuiteSparse & EigenSparse currently support dynamic sparsity.
-  options.sparse_linear_algebra_library_type =
+  SparseLinearAlgebraLibraryType sparse_library_types[] = {
 #if !defined(CERES_NO_SUITESPARSE)
-      ceres::SUITE_SPARSE;
-#elif defined(CERES_USE_EIGEN_SPARSE)
-      ceres::EIGEN_SPARSE;
+      ceres::SUITE_SPARSE,
 #endif
-
-  // First, solve `X` and `t` jointly with dynamic_sparsity = true.
-  Matrix X0 = X;
-  Vector t0 = t;
-  options.dynamic_sparsity = false;
-  Solver::Summary static_summary;
-  Solve(options, &problem, &static_summary);
-  EXPECT_EQ(static_summary.termination_type, CONVERGENCE)
-      << static_summary.FullReport();
-
-  X = X0;
-  t = t0;
-  options.dynamic_sparsity = true;
-  Solver::Summary dynamic_summary;
-  Solve(options, &problem, &dynamic_summary);
-  EXPECT_EQ(dynamic_summary.termination_type, CONVERGENCE)
-      << dynamic_summary.FullReport();
-
-  EXPECT_NEAR(static_summary.final_cost,
-              dynamic_summary.final_cost,
-              std::numeric_limits<double>::epsilon())
-      << "Static: \n"
-      << static_summary.FullReport() << "\nDynamic: \n"
-      << dynamic_summary.FullReport();
+#if defined(CERES_USE_EIGEN_SPARSE)
+      ceres::EIGEN_SPARSE,
+#endif
+#if !defined(CERES_NO_CUDSS)
+      ceres::CUDA_SPARSE,
+#endif
+  };
+  for (auto type : sparse_library_types) {
+    options.sparse_linear_algebra_library_type = type;
+    // First, solve `X` and `t` jointly with dynamic_sparsity = true.
+    Matrix X0 = X;
+    Vector t0 = t;
+    options.dynamic_sparsity = false;
+    Solver::Summary static_summary;
+    Solve(options, &problem, &static_summary);
+    EXPECT_EQ(static_summary.termination_type, CONVERGENCE)
+        << static_summary.FullReport();
+    X = X0;
+    t = t0;
+    options.dynamic_sparsity = true;
+    Solver::Summary dynamic_summary;
+    Solve(options, &problem, &dynamic_summary);
+    EXPECT_EQ(dynamic_summary.termination_type, CONVERGENCE)
+        << dynamic_summary.FullReport();
+    EXPECT_NEAR(static_summary.final_cost,
+                dynamic_summary.final_cost,
+                std::numeric_limits<double>::epsilon())
+        << "Static: \n"
+        << static_summary.FullReport() << "\nDynamic: \n"
+        << dynamic_summary.FullReport();
+  }
 }
 
 }  // namespace ceres::internal
diff --git a/internal/ceres/evaluator.cc b/internal/ceres/evaluator.cc
index 64eb4c5..9226dc5 100644
--- a/internal/ceres/evaluator.cc
+++ b/internal/ceres/evaluator.cc
@@ -63,7 +63,11 @@
           options, program);
     case DENSE_SCHUR:
     case SPARSE_SCHUR:
-    case ITERATIVE_SCHUR:
+    case ITERATIVE_SCHUR: {
+      return std::make_unique<
+            ProgramEvaluator<BlockEvaluatePreparer, BlockJacobianWriter>>(
+            options, program);
+    }
     case CGNR: {
       if (options.sparse_linear_algebra_library_type == CUDA_SPARSE) {
         return std::make_unique<ProgramEvaluator<ScratchEvaluatePreparer,
diff --git a/internal/ceres/generate_bundle_adjustment_tests.py b/internal/ceres/generate_bundle_adjustment_tests.py
index ac83bc3..0359b40 100644
--- a/internal/ceres/generate_bundle_adjustment_tests.py
+++ b/internal/ceres/generate_bundle_adjustment_tests.py
@@ -53,9 +53,11 @@
     ('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 = [
@@ -66,9 +68,11 @@
     ('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(
@@ -83,6 +87,7 @@
   SUITE_SPARSE='suitesparse',
   EIGEN_SPARSE='eigensparse',
   ACCELERATE_SPARSE='acceleratesparse',
+  CUDA_SPARSE='cudasparse',
   IDENTITY='identity',
   JACOBI='jacobi',
   SCHUR_JACOBI='schurjacobi',
@@ -217,6 +222,14 @@
   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')
diff --git a/internal/ceres/generated_bundle_adjustment_tests/CMakeLists.txt b/internal/ceres/generated_bundle_adjustment_tests/CMakeLists.txt
index 5f4f65e..8da71b4 100644
--- a/internal/ceres/generated_bundle_adjustment_tests/CMakeLists.txt
+++ b/internal/ceres/generated_bundle_adjustment_tests/CMakeLists.txt
@@ -41,69 +41,85 @@
 ceres_test(ba_sparsecholesky_suitesparse_auto)
 ceres_test(ba_sparsecholesky_eigensparse_auto)
 ceres_test(ba_sparsecholesky_acceleratesparse_auto)
+ceres_test(ba_sparsecholesky_cudasparse_auto)
 ceres_test(ba_sparseschur_suitesparse_auto)
 ceres_test(ba_sparseschur_eigensparse_auto)
 ceres_test(ba_sparseschur_acceleratesparse_auto)
+ceres_test(ba_sparseschur_cudasparse_auto)
 ceres_test(ba_iterschur_jacobi_auto)
 ceres_test(ba_iterschur_schurjacobi_auto)
 ceres_test(ba_iterschur_spse_auto)
 ceres_test(ba_iterschur_suitesparse_clustjacobi_auto)
 ceres_test(ba_iterschur_eigensparse_clustjacobi_auto)
 ceres_test(ba_iterschur_acceleratesparse_clustjacobi_auto)
+ceres_test(ba_iterschur_cudasparse_clustjacobi_auto)
 ceres_test(ba_iterschur_suitesparse_clusttri_auto)
 ceres_test(ba_iterschur_eigensparse_clusttri_auto)
 ceres_test(ba_iterschur_acceleratesparse_clusttri_auto)
+ceres_test(ba_iterschur_cudasparse_clusttri_auto)
 ceres_test(ba_denseschur_eigen_auto_threads)
 ceres_test(ba_denseschur_lapack_auto_threads)
 ceres_test(ba_denseschur_cuda_auto_threads)
 ceres_test(ba_sparsecholesky_suitesparse_auto_threads)
 ceres_test(ba_sparsecholesky_eigensparse_auto_threads)
 ceres_test(ba_sparsecholesky_acceleratesparse_auto_threads)
+ceres_test(ba_sparsecholesky_cudasparse_auto_threads)
 ceres_test(ba_sparseschur_suitesparse_auto_threads)
 ceres_test(ba_sparseschur_eigensparse_auto_threads)
 ceres_test(ba_sparseschur_acceleratesparse_auto_threads)
+ceres_test(ba_sparseschur_cudasparse_auto_threads)
 ceres_test(ba_iterschur_jacobi_auto_threads)
 ceres_test(ba_iterschur_schurjacobi_auto_threads)
 ceres_test(ba_iterschur_spse_auto_threads)
 ceres_test(ba_iterschur_suitesparse_clustjacobi_auto_threads)
 ceres_test(ba_iterschur_eigensparse_clustjacobi_auto_threads)
 ceres_test(ba_iterschur_acceleratesparse_clustjacobi_auto_threads)
+ceres_test(ba_iterschur_cudasparse_clustjacobi_auto_threads)
 ceres_test(ba_iterschur_suitesparse_clusttri_auto_threads)
 ceres_test(ba_iterschur_eigensparse_clusttri_auto_threads)
 ceres_test(ba_iterschur_acceleratesparse_clusttri_auto_threads)
+ceres_test(ba_iterschur_cudasparse_clusttri_auto_threads)
 ceres_test(ba_denseschur_eigen_user)
 ceres_test(ba_denseschur_lapack_user)
 ceres_test(ba_denseschur_cuda_user)
 ceres_test(ba_sparsecholesky_suitesparse_user)
 ceres_test(ba_sparsecholesky_eigensparse_user)
 ceres_test(ba_sparsecholesky_acceleratesparse_user)
+ceres_test(ba_sparsecholesky_cudasparse_user)
 ceres_test(ba_sparseschur_suitesparse_user)
 ceres_test(ba_sparseschur_eigensparse_user)
 ceres_test(ba_sparseschur_acceleratesparse_user)
+ceres_test(ba_sparseschur_cudasparse_user)
 ceres_test(ba_iterschur_jacobi_user)
 ceres_test(ba_iterschur_schurjacobi_user)
 ceres_test(ba_iterschur_spse_user)
 ceres_test(ba_iterschur_suitesparse_clustjacobi_user)
 ceres_test(ba_iterschur_eigensparse_clustjacobi_user)
 ceres_test(ba_iterschur_acceleratesparse_clustjacobi_user)
+ceres_test(ba_iterschur_cudasparse_clustjacobi_user)
 ceres_test(ba_iterschur_suitesparse_clusttri_user)
 ceres_test(ba_iterschur_eigensparse_clusttri_user)
 ceres_test(ba_iterschur_acceleratesparse_clusttri_user)
+ceres_test(ba_iterschur_cudasparse_clusttri_user)
 ceres_test(ba_denseschur_eigen_user_threads)
 ceres_test(ba_denseschur_lapack_user_threads)
 ceres_test(ba_denseschur_cuda_user_threads)
 ceres_test(ba_sparsecholesky_suitesparse_user_threads)
 ceres_test(ba_sparsecholesky_eigensparse_user_threads)
 ceres_test(ba_sparsecholesky_acceleratesparse_user_threads)
+ceres_test(ba_sparsecholesky_cudasparse_user_threads)
 ceres_test(ba_sparseschur_suitesparse_user_threads)
 ceres_test(ba_sparseschur_eigensparse_user_threads)
 ceres_test(ba_sparseschur_acceleratesparse_user_threads)
+ceres_test(ba_sparseschur_cudasparse_user_threads)
 ceres_test(ba_iterschur_jacobi_user_threads)
 ceres_test(ba_iterschur_schurjacobi_user_threads)
 ceres_test(ba_iterschur_spse_user_threads)
 ceres_test(ba_iterschur_suitesparse_clustjacobi_user_threads)
 ceres_test(ba_iterschur_eigensparse_clustjacobi_user_threads)
 ceres_test(ba_iterschur_acceleratesparse_clustjacobi_user_threads)
+ceres_test(ba_iterschur_cudasparse_clustjacobi_user_threads)
 ceres_test(ba_iterschur_suitesparse_clusttri_user_threads)
 ceres_test(ba_iterschur_eigensparse_clusttri_user_threads)
 ceres_test(ba_iterschur_acceleratesparse_clusttri_user_threads)
+ceres_test(ba_iterschur_cudasparse_clusttri_user_threads)
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_auto_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_auto_test.cc
new file mode 100644
index 0000000..1719aee
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_auto_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterJacobi_AutomaticOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_JACOBI;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_auto_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_auto_threads_test.cc
new file mode 100644
index 0000000..c0c3489
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_auto_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterJacobi_AutomaticOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_JACOBI;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_user_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_user_test.cc
new file mode 100644
index 0000000..554a432
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_user_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterJacobi_UserOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_JACOBI;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_user_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_user_threads_test.cc
new file mode 100644
index 0000000..1e39f65
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clustjacobi_user_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterJacobi_UserOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_JACOBI;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_auto_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_auto_test.cc
new file mode 100644
index 0000000..6addad5
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_auto_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterTridiagonal_AutomaticOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_TRIDIAGONAL;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_auto_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_auto_threads_test.cc
new file mode 100644
index 0000000..d2fefb8
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_auto_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterTridiagonal_AutomaticOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_TRIDIAGONAL;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_user_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_user_test.cc
new file mode 100644
index 0000000..ae04eec
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_user_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterTridiagonal_UserOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_TRIDIAGONAL;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_user_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_user_threads_test.cc
new file mode 100644
index 0000000..cad2051
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_iterschur_cudasparse_clusttri_user_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       IterativeSchur_CudaSparse_ClusterTridiagonal_UserOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = ITERATIVE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = CLUSTER_TRIDIAGONAL;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_auto_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_auto_test.cc
new file mode 100644
index 0000000..77f2fc1
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_auto_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholesky_CudaSparse_AutomaticOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_auto_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_auto_threads_test.cc
new file mode 100644
index 0000000..f436544
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_auto_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholesky_CudaSparse_AutomaticOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_user_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_user_test.cc
new file mode 100644
index 0000000..dea6202
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_user_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholesky_CudaSparse_UserOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_user_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_user_threads_test.cc
new file mode 100644
index 0000000..90a69e8
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparsecholesky_cudasparse_user_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseNormalCholesky_CudaSparse_UserOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_auto_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_auto_test.cc
new file mode 100644
index 0000000..5c45977
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_auto_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseSchur_CudaSparse_AutomaticOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = SPARSE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_auto_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_auto_threads_test.cc
new file mode 100644
index 0000000..b866acb
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_auto_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseSchur_CudaSparse_AutomaticOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = SPARSE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kAutomaticOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_user_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_user_test.cc
new file mode 100644
index 0000000..a7e5e14
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_user_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseSchur_CudaSparse_UserOrdering) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 1;
+  options->linear_solver_type = SPARSE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_user_threads_test.cc b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_user_threads_test.cc
new file mode 100644
index 0000000..7319b28
--- /dev/null
+++ b/internal/ceres/generated_bundle_adjustment_tests/ba_sparseschur_cudasparse_user_threads_test.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "ceres/bundle_adjustment_test_util.h"
+#include "ceres/internal/config.h"
+#include "gtest/gtest.h"
+
+#ifndef CERES_NO_CUDA
+#ifndef CERES_NO_CUDSS
+
+namespace ceres::internal {
+
+TEST_F(BundleAdjustmentTest,
+       SparseSchur_CudaSparse_UserOrdering_Threads) {  // NOLINT
+  BundleAdjustmentProblem bundle_adjustment_problem;
+  Solver::Options* options = bundle_adjustment_problem.mutable_solver_options();
+  options->eta = 0.01;
+  options->num_threads = 4;
+  options->linear_solver_type = SPARSE_SCHUR;
+  options->dense_linear_algebra_library_type = EIGEN;
+  options->sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options->preconditioner_type = IDENTITY;
+  if (kUserOrdering) {
+    options->linear_solver_ordering = nullptr;
+  }
+  Problem* problem = bundle_adjustment_problem.mutable_problem();
+  RunSolverForConfigAndExpectResidualsMatch(*options, problem);
+}
+
+}  // namespace ceres::internal
+
+#endif  // CERES_NO_CUDSS
+#endif  // CERES_NO_CUDA
diff --git a/internal/ceres/reorder_program.cc b/internal/ceres/reorder_program.cc
index 44c4e46..268c477 100644
--- a/internal/ceres/reorder_program.cc
+++ b/internal/ceres/reorder_program.cc
@@ -576,7 +576,9 @@
     // would involve performing two symbolic factorisations instead of one
     // which would have a negative overall impact on performance.
     return true;
-
+  } else if (sparse_linear_algebra_library_type == CUDA_SPARSE) {
+    // cuDSS has the same limitation as Accelerate
+    return true;
   } else if (sparse_linear_algebra_library_type == EIGEN_SPARSE) {
     OrderingForSparseNormalCholeskyUsingEigenSparse(
         linear_solver_ordering_type,
diff --git a/internal/ceres/solver.cc b/internal/ceres/solver.cc
index 611e465..fadb17b 100644
--- a/internal/ceres/solver.cc
+++ b/internal/ceres/solver.cc
@@ -41,6 +41,7 @@
 #include "ceres/casts.h"
 #include "ceres/context.h"
 #include "ceres/context_impl.h"
+#include "ceres/cuda_sparse_cholesky.h"
 #include "ceres/detect_structure.h"
 #include "ceres/eigensparse.h"
 #include "ceres/gradient_checking_cost_function.h"
@@ -113,7 +114,12 @@
            internal::SuiteSparse::IsNestedDissectionAvailable()) ||
           (type == ACCELERATE_SPARSE) ||
           ((type == EIGEN_SPARSE) &&
-           internal::EigenSparse::IsNestedDissectionAvailable()));
+           internal::EigenSparse::IsNestedDissectionAvailable())
+#ifndef CERES_NO_CUDSS
+          || ((type == CUDA_SPARSE) &&
+              internal::CudaSparseCholesky<>::IsNestedDissectionAvailable())
+#endif
+  );
 }
 
 bool IsIterativeSolver(LinearSolverType type) {
@@ -168,8 +174,15 @@
     return false;
   }
 
-  if (!IsSparseLinearAlgebraLibraryTypeAvailable(
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
           options.sparse_linear_algebra_library_type)) {
+    if (options.sparse_linear_algebra_library_type == CUDA_SPARSE) {
+#if defined(CERES_NO_CUDSS)
+      *error = StringPrintf(kNoLibraryFormat, solver_name, library_name);
+      return false;
+#endif
+    }
+  } else {
     *error = StringPrintf(kNoLibraryFormat, solver_name, library_name);
     return false;
   }
@@ -678,7 +691,12 @@
       options.linear_solver_type == DENSE_QR) {
     return (options.dense_linear_algebra_library_type == CUDA);
   }
-  if (options.linear_solver_type == CGNR) {
+  if (options.linear_solver_type == CGNR ||
+      options.linear_solver_type == SPARSE_SCHUR ||
+      options.linear_solver_type == SPARSE_NORMAL_CHOLESKY ||
+      (options.linear_solver_type == ITERATIVE_SCHUR &&
+       (options.preconditioner_type == CLUSTER_JACOBI ||
+        options.preconditioner_type == CLUSTER_TRIDIAGONAL))) {
     return (options.sparse_linear_algebra_library_type == CUDA_SPARSE);
   }
   return false;
diff --git a/internal/ceres/solver_utils.cc b/internal/ceres/solver_utils.cc
index f5fbf05..ac22210 100644
--- a/internal/ceres/solver_utils.cc
+++ b/internal/ceres/solver_utils.cc
@@ -36,6 +36,9 @@
 #include "ceres/version.h"
 #ifndef CERES_NO_CUDA
 #include "cuda_runtime.h"
+#ifndef CERES_NO_CUDSS
+#include "cudss.h"
+#endif  // CERES_NO_CUDSS
 #endif  // CERES_NO_CUDA
 
 namespace ceres::internal {
@@ -79,6 +82,11 @@
 
 #ifndef CERES_NO_CUDA
   "-cuda-(" CERES_TO_STRING(CUDART_VERSION) ")"
+#ifndef CERES_NO_CUDSS
+  "-cudss-(" CERES_SEMVER_VERSION(CUDSS_VERSION_MAJOR,
+                                  CUDSS_VERSION_MINOR,
+                                  CUDSS_VERSION_PATCH) ")"
+#endif // CERES_NO_CUDSS
 #endif
   ;
 // clang-format on
diff --git a/internal/ceres/sparse_cholesky.cc b/internal/ceres/sparse_cholesky.cc
index 4f1bf87..ecfeb29 100644
--- a/internal/ceres/sparse_cholesky.cc
+++ b/internal/ceres/sparse_cholesky.cc
@@ -34,6 +34,7 @@
 #include <utility>
 
 #include "ceres/accelerate_sparse.h"
+#include "ceres/cuda_sparse_cholesky.h"
 #include "ceres/eigensparse.h"
 #include "ceres/float_suitesparse.h"
 #include "ceres/iterative_refiner.h"
@@ -87,6 +88,19 @@
       LOG(FATAL) << "Ceres was compiled without support for Apple's Accelerate "
                  << "framework solvers.";
 #endif
+    case CUDA_SPARSE:
+#ifndef CERES_NO_CUDSS
+      if (options.use_mixed_precision_solves) {
+        sparse_cholesky = CudaSparseCholesky<float>::Create(
+            options.context, options.ordering_type);
+      } else {
+        sparse_cholesky = CudaSparseCholesky<double>::Create(
+            options.context, options.ordering_type);
+      }
+      break;
+#else   // CERES_NO_CUDSS
+      LOG(FATAL) << "Ceres was compiled without support for cuDSS.";
+#endif  // CERES_NO_CUDSS
 
     default:
       LOG(FATAL) << "Unknown sparse linear algebra library type : "
diff --git a/internal/ceres/sparse_cholesky_test.cc b/internal/ceres/sparse_cholesky_test.cc
index d0d962e..bb5c7c8 100644
--- a/internal/ceres/sparse_cholesky_test.cc
+++ b/internal/ceres/sparse_cholesky_test.cc
@@ -39,6 +39,7 @@
 #include "Eigen/SparseCore"
 #include "ceres/block_sparse_matrix.h"
 #include "ceres/compressed_row_sparse_matrix.h"
+#include "ceres/cuda_sparse_cholesky.h"
 #include "ceres/inner_product_computer.h"
 #include "ceres/internal/config.h"
 #include "ceres/internal/eigen.h"
@@ -117,6 +118,12 @@
   sparse_cholesky_options.sparse_linear_algebra_library_type =
       sparse_linear_algebra_library_type;
   sparse_cholesky_options.ordering_type = ordering_type;
+#ifndef CERES_NO_CUDSS
+  ContextImpl context;
+  sparse_cholesky_options.context = &context;
+  std::string error;
+  CHECK(context.InitCuda(&error)) << error;
+#endif  // CERES_NO_CUDSS
   auto sparse_cholesky = SparseCholesky::Create(sparse_cholesky_options);
   const CompressedRowSparseMatrix::StorageType storage_type =
       sparse_cholesky->StorageType();
@@ -251,7 +258,7 @@
     ParamInfoToString);
 
 INSTANTIATE_TEST_SUITE_P(
-    EigenSparseCholeskySingle,
+    FloatEigenSparseCholesky,
     SparseCholeskyTest,
     ::testing::Combine(::testing::Values(EIGEN_SPARSE),
                        ::testing::Values(OrderingType::AMD,
@@ -260,6 +267,26 @@
     ParamInfoToString);
 #endif  // CERES_USE_EIGEN_SPARSE
 
+#ifndef CERES_NO_CUDSS
+using CuDSSCholeskyDouble = CudaSparseCholesky<double>;
+INSTANTIATE_TEST_SUITE_P(
+    CuDSSCholeskyDouble,
+    SparseCholeskyTest,
+    ::testing::Combine(::testing::Values(CUDA_SPARSE),
+                       ::testing::Values(OrderingType::AMD),
+                       ::testing::Values(true, false)),
+    ParamInfoToString);
+
+using CuDSSCholeskySingle = CudaSparseCholesky<float>;
+INSTANTIATE_TEST_SUITE_P(
+    CuDSSCholeskySingle,
+    SparseCholeskyTest,
+    ::testing::Combine(::testing::Values(CUDA_SPARSE),
+                       ::testing::Values(OrderingType::AMD),
+                       ::testing::Values(true, false)),
+    ParamInfoToString);
+#endif  // CERES_NO_CUDSS
+
 #if defined(CERES_USE_EIGEN_SPARSE) && !defined(CERES_NO_EIGEN_METIS)
 INSTANTIATE_TEST_SUITE_P(
     EigenSparseCholeskyMETIS,
diff --git a/internal/ceres/sparse_normal_cholesky_solver_test.cc b/internal/ceres/sparse_normal_cholesky_solver_test.cc
index 3396e34..2ff3f6e 100644
--- a/internal/ceres/sparse_normal_cholesky_solver_test.cc
+++ b/internal/ceres/sparse_normal_cholesky_solver_test.cc
@@ -88,8 +88,9 @@
 
     EXPECT_EQ(summary.termination_type, LinearSolverTerminationType::SUCCESS);
 
+    const double eps = options.use_mixed_precision_solves ? 2e-6 : 1e-8;
     for (int i = 0; i < A_->num_cols(); ++i) {
-      EXPECT_NEAR(expected_solution(i), actual_solution(i), 1e-8)
+      EXPECT_NEAR(expected_solution(i), actual_solution(i), eps)
           << "\nExpected: " << expected_solution.transpose()
           << "\nActual: " << actual_solution.transpose();
     }
@@ -177,4 +178,31 @@
 }
 #endif  // CERES_USE_EIGEN_SPARSE
 
+#ifndef CERES_NO_CUDSS
+TEST_F(SparseNormalCholeskySolverTest, SparseNormalCholeskyUsingCuDSSSingle) {
+  LinearSolver::Options options;
+  options.sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options.type = SPARSE_NORMAL_CHOLESKY;
+  options.ordering_type = OrderingType::AMD;
+  options.use_mixed_precision_solves = true;
+  ContextImpl context;
+  options.context = &context;
+  std::string error;
+  CHECK(context.InitCuda(&error)) << error;
+  TestSolver(options);
+}
+
+TEST_F(SparseNormalCholeskySolverTest, SparseNormalCholeskyUsingCuDSSDouble) {
+  LinearSolver::Options options;
+  options.sparse_linear_algebra_library_type = CUDA_SPARSE;
+  options.type = SPARSE_NORMAL_CHOLESKY;
+  options.ordering_type = OrderingType::AMD;
+  ContextImpl context;
+  options.context = &context;
+  std::string error;
+  CHECK(context.InitCuda(&error)) << error;
+  TestSolver(options);
+}
+#endif  // CERES_USE_EIGEN_SPARSE
+
 }  // namespace ceres::internal
diff --git a/internal/ceres/visibility_based_preconditioner.cc b/internal/ceres/visibility_based_preconditioner.cc
index 42e8a6e..34d6e0c 100644
--- a/internal/ceres/visibility_based_preconditioner.cc
+++ b/internal/ceres/visibility_based_preconditioner.cc
@@ -100,6 +100,8 @@
   sparse_cholesky_options.sparse_linear_algebra_library_type =
       options_.sparse_linear_algebra_library_type;
   sparse_cholesky_options.ordering_type = options_.ordering_type;
+  sparse_cholesky_options.context = options_.context;
+
   sparse_cholesky_ = SparseCholesky::Create(sparse_cholesky_options);
 
   const time_t init_time = time(nullptr);