Add mixed precision solves for SUITE_SPARSE

Starting with SuiteSparse version 7.4.0 CHOLMOD has support for single
precision matrices. This allows us to have single precision and mixed
precision solves when using the SUITE_SPARSE backend.

This CL also fixes sparse_cholesky_test which was completely broken for
single precision testing.

Sample performance on my Mac.
/usr/bin/time -l ./bin/bundle_adjuster --input=../../Downloads/problem-3068-310854-pre.txt
<SNIP>

Cost:
Initial                          9.099334e+07
Final                            4.161838e+06
Change                           8.683150e+07

Minimizer iterations                        6
Successful steps                            4
Unsuccessful steps                          2

Time (in seconds):
Preprocessor                         2.528222

  Residual only evaluation           0.142804 (5)
  Jacobian & residual evaluation     0.424014 (4)
  Linear solver                     54.083396 (5)
Minimizer                           54.895752

Postprocessor                        0.024564
Total                               57.448539

Termination:                   NO_CONVERGENCE (Maximum number of iterations reached. Number of iterations: 5.)

       59.04 real       341.24 user         5.49 sys
          5776375808  maximum resident set size
<SNIP>
        616329634071  instructions retired
        929475980510  cycles elapsed
          5375034560  peak memory footprint

/usr/bin/time -l ./bin/bundle_adjuster --input=../../Downloads/problem-3068-310854-pre.txt  -mixed_precision_solves
<SNIP>

Cost:
Initial                          9.099334e+07
Final                            4.148930e+06
Change                           8.684441e+07

Minimizer iterations                        6
Successful steps                            4
Unsuccessful steps                          2

Time (in seconds):
Preprocessor                         2.580217

  Residual only evaluation           0.144098 (5)
  Jacobian & residual evaluation     0.396723 (4)
  Linear solver                     23.636074 (5)
Minimizer                           24.427163

Postprocessor                        0.023790
Total                               27.031170

Termination:                   NO_CONVERGENCE (Maximum number of iterations reached. Number of iterations: 5.)

       28.58 real       128.53 user         2.37 sys
          4818386944  maximum resident set size
<SNIP>
        395186936091  instructions retired
        368802808856  cycles elapsed
          4327029824  peak memory footprint

Change-Id: I1f137b0dd12da8da7f9ced338dd8f20f4bbdf99d
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 529c99b..f266e9c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -374,6 +374,11 @@
     message("-- Found SuiteSparse ${SuiteSparse_VERSION}, "
             "building with SuiteSparse.")
 
+    if (SuiteSparse_VERSION VERSION_LESS 7.4.0)
+      list(APPEND CERES_COMPILE_OPTIONS CERES_NO_CHOLMOD_FLOAT)
+    else()
+      message("-- CHOLMOD supports single precision factorization")
+    endif()
     if (SuiteSparse_NO_CMAKE OR NOT SuiteSparse_DIR)
       install(FILES ${Ceres_SOURCE_DIR}/cmake/FindSuiteSparse.cmake
                     ${Ceres_SOURCE_DIR}/cmake/FindMETIS.cmake
diff --git a/cmake/config.h.in b/cmake/config.h.in
index 51db4a0..94120ae 100644
--- a/cmake/config.h.in
+++ b/cmake/config.h.in
@@ -77,6 +77,11 @@
 // If defined, Ceres was compiled with a version of SuiteSparse/CHOLMOD without
 // the Partition module (requires METIS).
 @CERES_NO_CHOLMOD_PARTITION@
+
+// If defined Ceres was compiled with a version of SuiteSparse that does not
+// support single precision solves in CHOLMOD.
+@CERES_NO_CHOLMOD_FLOAT@
+
 // If defined Ceres was compiled without support for METIS via Eigen.
 @CERES_NO_EIGEN_METIS@
 
diff --git a/docs/source/nnls_solving.rst b/docs/source/nnls_solving.rst
index 184b94b..bc0e16a 100644
--- a/docs/source/nnls_solving.rst
+++ b/docs/source/nnls_solving.rst
@@ -905,20 +905,12 @@
 :member:`Solver::Options::max_num_refinement_iterations`. The default
 value of this parameter is zero, which means if
 :member:`Solver::Options::use_mixed_precision_solves` is ``true``,
-then no iterative refinement is performed. Usually 2-3 refinement
-iterations are enough.
+then no iterative refinement is performed. Usually 1-3 refinement
+iterations are enough, depending upon the conditioning of your
+problem.
 
-Mixed precision solves are available in the following linear solver
-configurations:
-
-1. ``DENSE_NORMAL_CHOLESKY`` + ``EIGEN``/ ``LAPACK`` / ``CUDA``.
-2. ``DENSE_SCHUR`` + ``EIGEN``/ ``LAPACK`` / ``CUDA``.
-3. ``SPARSE_NORMAL_CHOLESKY`` + ``EIGEN_SPARSE`` / ``ACCELERATE_SPARSE``
-4. ``SPARSE_SCHUR`` + ``EIGEN_SPARSE`` / ``ACCELERATE_SPARSE``
-
-Mixed precision solves area not available when using ``SUITE_SPARSE``
-as the sparse linear algebra backend because SuiteSparse/CHOLMOD does
-not support single precision solves.
+If :member:`Solver::Options::max_num_refinement_iterations = 0`, then
+the Gauss-Newton step is computed in single precision.
 
 .. _section-preconditioner:
 
diff --git a/include/ceres/solver.h b/include/ceres/solver.h
index 68438a1..b31880f 100644
--- a/include/ceres/solver.h
+++ b/include/ceres/solver.h
@@ -569,23 +569,24 @@
     // This settings only affects the SPARSE_NORMAL_CHOLESKY solver.
     bool dynamic_sparsity = false;
 
-    // If use_mixed_precision_solves is true, the Gauss-Newton matrix
-    // is computed in double precision, but its factorization is
-    // computed in single precision. This can result in significant
-    // time and memory savings at the cost of some accuracy in the
-    // Gauss-Newton step. Iterative refinement is used to recover some
-    // of this accuracy back.
+    // If use_mixed_precision_solves is true, the Gauss-Newton matrix is
+    // computed in double precision, but its factorization is computed in single
+    // precision. This can result in significant time and memory savings at the
+    // cost of some accuracy in the Gauss-Newton step. Iterative refinement is
+    // used to recover some of this accuracy back.
     //
     // If use_mixed_precision_solves is true, we recommend setting
-    // max_num_refinement_iterations to 2-3.
+    // max_num_refinement_iterations to 1-3, depending on your problem. If
+    // max_num_refinement_iterations = 0, then the Gauss-Newton step is computed
+    // in single precision.
     //
     // This options is available when linear solver uses sparse or dense
-    // cholesky factorization, except when sparse_linear_algebra_library_type =
-    // SUITE_SPARSE.
+    // cholesky factorization.
     bool use_mixed_precision_solves = false;
 
-    // Number steps of the iterative refinement process to run when
-    // computing the Gauss-Newton step.
+    // Number steps of the iterative refinement process to run when computing
+    // the Gauss-Newton step. This is most useful when used in conjunction with
+    // use_mixed_precision = true.
     int max_num_refinement_iterations = 0;
 
     // Minimum number of iterations for which the linear solver should
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 5331843..ed30c7d 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -184,7 +184,6 @@
     fake_bundle_adjustment_jacobian.cc
     file.cc
     first_order_function.cc
-    float_suitesparse.cc
     function_sample.cc
     gradient_checking_cost_function.cc
     gradient_problem_solver.cc
diff --git a/internal/ceres/float_suitesparse.cc b/internal/ceres/float_suitesparse.cc
deleted file mode 100644
index cea5c4a..0000000
--- a/internal/ceres/float_suitesparse.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Ceres Solver - A fast non-linear least squares minimizer
-// Copyright 2023 Google Inc. All rights reserved.
-// http://ceres-solver.org/
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright notice,
-//   this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright notice,
-//   this list of conditions and the following disclaimer in the documentation
-//   and/or other materials provided with the distribution.
-// * Neither the name of Google Inc. nor the names of its contributors may be
-//   used to endorse or promote products derived from this software without
-//   specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-//
-// Author: sameeragarwal@google.com (Sameer Agarwal)
-
-#include "ceres/float_suitesparse.h"
-
-#include <memory>
-
-#include "absl/log/log.h"
-
-#if !defined(CERES_NO_SUITESPARSE)
-
-namespace ceres::internal {
-
-std::unique_ptr<SparseCholesky> FloatSuiteSparseCholesky::Create(
-    OrderingType ordering_type) {
-  LOG(FATAL) << "FloatSuiteSparseCholesky is not available.";
-  return {};
-}
-
-}  // namespace ceres::internal
-
-#endif  // !defined(CERES_NO_SUITESPARSE)
diff --git a/internal/ceres/float_suitesparse.h b/internal/ceres/float_suitesparse.h
deleted file mode 100644
index b9d298e..0000000
--- a/internal/ceres/float_suitesparse.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Ceres Solver - A fast non-linear least squares minimizer
-// Copyright 2023 Google Inc. All rights reserved.
-// http://ceres-solver.org/
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// * Redistributions of source code must retain the above copyright notice,
-//   this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright notice,
-//   this list of conditions and the following disclaimer in the documentation
-//   and/or other materials provided with the distribution.
-// * Neither the name of Google Inc. nor the names of its contributors may be
-//   used to endorse or promote products derived from this software without
-//   specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
-//
-// Author: sameeragarwal@google.com (Sameer Agarwal)
-
-#ifndef CERES_INTERNAL_FLOAT_SUITESPARSE_H_
-#define CERES_INTERNAL_FLOAT_SUITESPARSE_H_
-
-// This include must come before any #ifndef check on Ceres compile options.
-// clang-format off
-#include "ceres/internal/config.h"
-// clang-format on
-
-#include <memory>
-
-#include "ceres/internal/export.h"
-#include "ceres/sparse_cholesky.h"
-
-#if !defined(CERES_NO_SUITESPARSE)
-
-namespace ceres::internal {
-
-// Fake implementation of a single precision Sparse Cholesky using
-// SuiteSparse.
-class CERES_NO_EXPORT FloatSuiteSparseCholesky : public SparseCholesky {
- public:
-  static std::unique_ptr<SparseCholesky> Create(OrderingType ordering_type);
-};
-
-}  // namespace ceres::internal
-
-#endif  // !defined(CERES_NO_SUITESPARSE)
-
-#endif  // CERES_INTERNAL_FLOAT_SUITESPARSE_H_
diff --git a/internal/ceres/solver.cc b/internal/ceres/solver.cc
index bb82e6d..010ad26 100644
--- a/internal/ceres/solver.cc
+++ b/internal/ceres/solver.cc
@@ -195,11 +195,16 @@
     return false;
   }
 
+#ifdef CERES_NO_CHOLMOD_FLOAT
   if (options.use_mixed_precision_solves &&
       options.sparse_linear_algebra_library_type == SUITE_SPARSE) {
-    *error = absl::StrFormat(kMixedFormat, solver_name, library_name);
+    *error =
+        "This version of SuiteSparse does not support single precision "
+        "Cholesky factorization. So use_mixed_precision_solves is not "
+        "supported with SUITE_SPARSE";
     return false;
   }
+#endif
 
   if (options.dynamic_sparsity &&
       options.sparse_linear_algebra_library_type == ACCELERATE_SPARSE) {
diff --git a/internal/ceres/solver_test.cc b/internal/ceres/solver_test.cc
index 0a83a34..f6fd9c7 100644
--- a/internal/ceres/solver_test.cc
+++ b/internal/ceres/solver_test.cc
@@ -511,7 +511,11 @@
           options.sparse_linear_algebra_library_type)) {
     options.use_mixed_precision_solves = true;
     options.dynamic_sparsity = false;
+#ifdef CERES_NO_CHOLMOD_FLOAT
     EXPECT_FALSE(options.IsValid(&message));
+#else
+    EXPECT_TRUE(options.IsValid(&message));
+#endif
 
     options.use_mixed_precision_solves = false;
     options.dynamic_sparsity = true;
@@ -519,7 +523,11 @@
 
     options.use_mixed_precision_solves = true;
     options.dynamic_sparsity = true;
+#ifdef CERES_NO_CHOLMOD_FLOAT
     EXPECT_FALSE(options.IsValid(&message));
+#else
+    EXPECT_TRUE(options.IsValid(&message));
+#endif
   }
 
 #ifndef CERES_NO_CHOLMOD_PARTITION
@@ -532,7 +540,11 @@
 
     options.use_mixed_precision_solves = true;
     options.dynamic_sparsity = false;
+#ifdef CERES_NO_CHOLMOD_FLOAT
     EXPECT_FALSE(options.IsValid(&message));
+#else
+    EXPECT_TRUE(options.IsValid(&message));
+#endif
 
     options.use_mixed_precision_solves = false;
     options.dynamic_sparsity = true;
@@ -540,7 +552,11 @@
 
     options.use_mixed_precision_solves = true;
     options.dynamic_sparsity = true;
+#ifdef CERES_NO_CHOLMOD_FLOAT
     EXPECT_FALSE(options.IsValid(&message));
+#else
+    EXPECT_TRUE(options.IsValid(&message));
+#endif
   }
 #else
   options.linear_solver_ordering_type = NESDIS;
@@ -729,7 +745,11 @@
           options.sparse_linear_algebra_library_type)) {
     options.use_mixed_precision_solves = true;
     options.dynamic_sparsity = false;
+#ifdef CERES_NO_CHOLMOD_FLOAT
     EXPECT_FALSE(options.IsValid(&message));
+#else
+    EXPECT_TRUE(options.IsValid(&message));
+#endif
 
     options.use_mixed_precision_solves = false;
     options.dynamic_sparsity = true;
@@ -750,7 +770,11 @@
 
     options.use_mixed_precision_solves = true;
     options.dynamic_sparsity = false;
+#ifdef CERES_NO_CHOLMOD_FLOAT
     EXPECT_FALSE(options.IsValid(&message));
+#else
+    EXPECT_TRUE(options.IsValid(&message));
+#endif
 
     options.use_mixed_precision_solves = false;
     options.dynamic_sparsity = true;
diff --git a/internal/ceres/sparse_cholesky.cc b/internal/ceres/sparse_cholesky.cc
index f1b2c88..23396a9 100644
--- a/internal/ceres/sparse_cholesky.cc
+++ b/internal/ceres/sparse_cholesky.cc
@@ -38,7 +38,6 @@
 #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"
 #include "ceres/suitesparse.h"
 
diff --git a/internal/ceres/sparse_cholesky_test.cc b/internal/ceres/sparse_cholesky_test.cc
index 2b8e852..b2499ff 100644
--- a/internal/ceres/sparse_cholesky_test.cc
+++ b/internal/ceres/sparse_cholesky_test.cc
@@ -113,6 +113,7 @@
 
 void SparseCholeskySolverUnitTest(
     const SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type,
+    const bool use_single_precision,
     const OrderingType ordering_type,
     const bool use_block_structure,
     const int num_blocks,
@@ -120,16 +121,20 @@
     const int max_block_size,
     const double block_density,
     std::mt19937& prng) {
-  LinearSolver::Options sparse_cholesky_options;
-  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
+
+  LinearSolver::Options sparse_cholesky_options;
+  sparse_cholesky_options.sparse_linear_algebra_library_type =
+      sparse_linear_algebra_library_type;
+  sparse_cholesky_options.ordering_type = ordering_type;
+  sparse_cholesky_options.max_num_refinement_iterations = 0;
+  sparse_cholesky_options.use_mixed_precision_solves = use_single_precision;
+
   auto sparse_cholesky = SparseCholesky::Create(sparse_cholesky_options);
   const CompressedRowSparseMatrix::StorageType storage_type =
       sparse_cholesky->StorageType();
@@ -156,22 +161,30 @@
       LinearSolverTerminationType::SUCCESS);
   Matrix eigen_lhs;
   lhs->ToDenseMatrix(&eigen_lhs);
-  EXPECT_NEAR((actual - expected).norm() / actual.norm(),
-              0.0,
-              std::numeric_limits<double>::epsilon() * 20)
+  const double kTolerance =
+      (use_single_precision ? std::numeric_limits<float>::epsilon()
+                            : std::numeric_limits<double>::epsilon()) *
+      20;
+
+  EXPECT_NEAR((actual - expected).norm() / actual.norm(), 0.0, kTolerance)
       << "\n"
       << eigen_lhs;
 }
 
+// SparseLinearAlgebraLibraryType
+// FLOAT/DOUBLE
+// OrderingType
+// BlockStructure
 using Param =
-    ::testing::tuple<SparseLinearAlgebraLibraryType, OrderingType, bool>;
+    ::testing::tuple<SparseLinearAlgebraLibraryType, bool, OrderingType, bool>;
 
 std::string ParamInfoToString(testing::TestParamInfo<Param> info) {
   Param param = info.param;
   std::stringstream ss;
   ss << SparseLinearAlgebraLibraryTypeToString(::testing::get<0>(param)) << "_"
-     << ::testing::get<1>(param) << "_"
-     << (::testing::get<2>(param) ? "UseBlockStructure" : "NoBlockStructure");
+     << (::testing::get<1>(param) ? "FLOAT" : "DOUBLE") << "_"
+     << ::testing::get<2>(param) << "_"
+     << (::testing::get<3>(param) ? "UseBlockStructure" : "NoBlockStructure");
   return ss.str();
 }
 
@@ -198,6 +211,7 @@
       SparseCholeskySolverUnitTest(::testing::get<0>(param),
                                    ::testing::get<1>(param),
                                    ::testing::get<2>(param),
+                                   ::testing::get<3>(param),
                                    num_blocks,
                                    kMinBlockSize,
                                    kMaxBlockSize,
@@ -214,38 +228,29 @@
     SuiteSparseCholesky,
     SparseCholeskyTest,
     ::testing::Combine(::testing::Values(SUITE_SPARSE),
+#if defined(CERES_NO_CHOLMOD_FLOAT)
+                       ::testing::Values(false),
+#else
+                       ::testing::Values(false, true),
+#endif  // defined(CERES_NO_CHOLMOD_FLOAT)
+#if defined(CERES_NO_CHOLMOD_PARTITION)
                        ::testing::Values(OrderingType::AMD,
                                          OrderingType::NATURAL),
+#else
+                       ::testing::Values(OrderingType::AMD,
+                                         OrderingType::NESDIS,
+                                         OrderingType::NATURAL),
+#endif  // defined(CERES_NO_CHOLMOD_PARTITION)
                        ::testing::Values(true, false)),
     ParamInfoToString);
-#endif
-
-#if !defined(CERES_NO_SUITESPARSE) && !defined(CERES_NO_CHOLMOD_PARTITION)
-INSTANTIATE_TEST_SUITE_P(
-    SuiteSparseCholeskyMETIS,
-    SparseCholeskyTest,
-    ::testing::Combine(::testing::Values(SUITE_SPARSE),
-                       ::testing::Values(OrderingType::NESDIS),
-                       ::testing::Values(true, false)),
-    ParamInfoToString);
-#endif  // !defined(CERES_NO_SUITESPARSE) &&
-        // !defined(CERES_NO_CHOLMOD_PARTITION)
+#endif  // !defined(CERES_NO_SUITESPARSE)
 
 #ifndef CERES_NO_ACCELERATE_SPARSE
 INSTANTIATE_TEST_SUITE_P(
     AccelerateSparseCholesky,
     SparseCholeskyTest,
     ::testing::Combine(::testing::Values(ACCELERATE_SPARSE),
-                       ::testing::Values(OrderingType::AMD,
-                                         OrderingType::NESDIS,
-                                         OrderingType::NATURAL),
-                       ::testing::Values(true, false)),
-    ParamInfoToString);
-
-INSTANTIATE_TEST_SUITE_P(
-    AccelerateSparseCholeskySingle,
-    SparseCholeskyTest,
-    ::testing::Combine(::testing::Values(ACCELERATE_SPARSE),
+                       ::testing::Values(false, true),
                        ::testing::Values(OrderingType::AMD,
                                          OrderingType::NESDIS,
                                          OrderingType::NATURAL),
@@ -258,59 +263,30 @@
     EigenSparseCholesky,
     SparseCholeskyTest,
     ::testing::Combine(::testing::Values(EIGEN_SPARSE),
+                       ::testing::Values(false, true),
+#if defined(CERES_NO_EIGEN_METIS)
                        ::testing::Values(OrderingType::AMD,
                                          OrderingType::NATURAL),
-                       ::testing::Values(true, false)),
-    ParamInfoToString);
-
-INSTANTIATE_TEST_SUITE_P(
-    FloatEigenSparseCholesky,
-    SparseCholeskyTest,
-    ::testing::Combine(::testing::Values(EIGEN_SPARSE),
+#else
                        ::testing::Values(OrderingType::AMD,
-                                         OrderingType::NATURAL),
+                                         OrderingType::NATURAL,
+                                         OrderingType::NESDIS),
+#endif  // defined(CERES_NO_EIGEN_METIS)
                        ::testing::Values(true, false)),
     ParamInfoToString);
 #endif  // CERES_USE_EIGEN_SPARSE
 
 #ifndef CERES_NO_CUDSS
-using CuDSSCholeskyDouble = CudaSparseCholesky<double>;
 INSTANTIATE_TEST_SUITE_P(
-    CuDSSCholeskyDouble,
+    CudaCholesky,
     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(false, true),
                        ::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,
-    SparseCholeskyTest,
-    ::testing::Combine(::testing::Values(EIGEN_SPARSE),
-                       ::testing::Values(OrderingType::NESDIS),
-                       ::testing::Values(true, false)),
-    ParamInfoToString);
-
-INSTANTIATE_TEST_SUITE_P(
-    EigenSparseCholeskySingleMETIS,
-    SparseCholeskyTest,
-    ::testing::Combine(::testing::Values(EIGEN_SPARSE),
-                       ::testing::Values(OrderingType::NESDIS),
-                       ::testing::Values(true, false)),
-    ParamInfoToString);
-#endif  // defined(CERES_USE_EIGEN_SPARSE) && !defined(CERES_NO_EIGEN_METIS)
-
 class MockSparseCholesky : public SparseCholesky {
  public:
   MOCK_CONST_METHOD0(StorageType, CompressedRowSparseMatrix::StorageType());
diff --git a/internal/ceres/suitesparse.cc b/internal/ceres/suitesparse.cc
index eeb850c..2cc9ff3 100644
--- a/internal/ceres/suitesparse.cc
+++ b/internal/ceres/suitesparse.cc
@@ -250,9 +250,9 @@
   block_matrix.i = reinterpret_cast<void*>(block_rows.data());
   block_matrix.x = nullptr;
   block_matrix.stype = A->stype;
-  block_matrix.itype = CHOLMOD_INT;
+  block_matrix.itype = A->itype;
   block_matrix.xtype = CHOLMOD_PATTERN;
-  block_matrix.dtype = CHOLMOD_DOUBLE;
+  block_matrix.dtype = A->dtype;
   block_matrix.sorted = 1;
   block_matrix.packed = 1;
 
@@ -466,6 +466,154 @@
   return LinearSolverTerminationType::SUCCESS;
 }
 
+#ifndef CERES_NO_CHOLMOD_FLOAT
+
+std::unique_ptr<SparseCholesky> FloatSuiteSparseCholesky::Create(
+    const OrderingType ordering_type) {
+  return std::unique_ptr<SparseCholesky>(
+      new FloatSuiteSparseCholesky(ordering_type));
+}
+
+FloatSuiteSparseCholesky::FloatSuiteSparseCholesky(
+    const OrderingType ordering_type)
+    : ordering_type_(ordering_type), factor_(nullptr) {}
+
+FloatSuiteSparseCholesky::~FloatSuiteSparseCholesky() {
+  if (factor_ != nullptr) {
+    ss_.Free(factor_);
+  }
+}
+
+LinearSolverTerminationType FloatSuiteSparseCholesky::Factorize(
+    CompressedRowSparseMatrix* lhs, std::string* message) {
+  if (lhs == nullptr) {
+    *message = "Failure: Input lhs is nullptr.";
+    return LinearSolverTerminationType::FATAL_ERROR;
+  }
+
+  cholmod_sparse cholmod_lhs = ss_.CreateSparseMatrixTransposeView(lhs);
+  float_lhs_values_ =
+      ConstVectorRef(lhs->values(), lhs->num_nonzeros()).cast<float>();
+  cholmod_lhs.dtype = CHOLMOD_SINGLE;
+  cholmod_lhs.x = reinterpret_cast<void*>(float_lhs_values_.data());
+
+  // If a factorization does not exist, compute the symbolic
+  // factorization first.
+  //
+  // If the ordering type is NATURAL, then there is no fill reducing
+  // ordering to be computed, regardless of block structure, so we can
+  // just call the scalar version of symbolic factorization. For
+  // SuiteSparse this is the common case since we have already
+  // pre-ordered the columns of the Jacobian.
+  //
+  // Similarly regardless of ordering type, if there is no block
+  // structure in the matrix we call the scalar version of symbolic
+  // factorization.
+  if (factor_ == nullptr) {
+    if (ordering_type_ == OrderingType::NATURAL ||
+        (lhs->col_blocks().empty() || lhs->row_blocks().empty())) {
+      factor_ = ss_.AnalyzeCholesky(&cholmod_lhs, ordering_type_, message);
+    } else {
+      factor_ = ss_.BlockAnalyzeCholesky(&cholmod_lhs,
+                                         ordering_type_,
+                                         lhs->col_blocks(),
+                                         lhs->row_blocks(),
+                                         message);
+    }
+  }
+
+  if (factor_ == nullptr) {
+    return LinearSolverTerminationType::FATAL_ERROR;
+  }
+
+  // Compute and return the numeric factorization.
+  return ss_.Cholesky(&cholmod_lhs, factor_, message);
+}
+
+CompressedRowSparseMatrix::StorageType FloatSuiteSparseCholesky::StorageType()
+    const {
+  return ((ordering_type_ == OrderingType::NATURAL)
+              ? CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR
+              : CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR);
+}
+
+LinearSolverTerminationType FloatSuiteSparseCholesky::Solve(
+    const double* rhs, double* solution, std::string* message) {
+  // Error checking
+  if (factor_ == nullptr) {
+    *message = "Solve called without a call to Factorize first.";
+    return LinearSolverTerminationType::FATAL_ERROR;
+  }
+
+  const int num_cols = factor_->n;
+  cholmod_dense cholmod_rhs = ss_.CreateDenseVectorView(rhs, num_cols);
+
+  float_rhs_ = ConstVectorRef(rhs, num_cols).cast<float>();
+  cholmod_rhs.dtype = CHOLMOD_SINGLE;
+  cholmod_rhs.x = reinterpret_cast<void*>(float_rhs_.data());
+
+  cholmod_dense* cholmod_dense_solution =
+      ss_.Solve(factor_, &cholmod_rhs, message);
+
+  if (cholmod_dense_solution == nullptr) {
+    return LinearSolverTerminationType::FAILURE;
+  }
+
+  CHECK_EQ(cholmod_dense_solution->dtype, CHOLMOD_SINGLE);
+  VectorRef(solution, num_cols) =
+      Eigen::Map<Eigen::VectorXf>(
+          reinterpret_cast<float*>(cholmod_dense_solution->x), num_cols)
+          .cast<double>();
+  ss_.Free(cholmod_dense_solution);
+  return LinearSolverTerminationType::SUCCESS;
+}
+
+#else
+
+std::unique_ptr<SparseCholesky> FloatSuiteSparseCholesky::Create(
+    const OrderingType ordering_type) {
+  return nullptr;
+}
+
+FloatSuiteSparseCholesky::FloatSuiteSparseCholesky(
+    const OrderingType ordering_type)
+    : ordering_type_(ordering_type), factor_(nullptr) {}
+
+FloatSuiteSparseCholesky::~FloatSuiteSparseCholesky() {
+  if (factor_ != nullptr) {
+    ss_.Free(factor_);
+  }
+}
+
+LinearSolverTerminationType FloatSuiteSparseCholesky::Factorize(
+    CompressedRowSparseMatrix* lhs, std::string* message) {
+  *message =
+      "Single precision Cholesky factorization is not supported. If "
+      "you are seeing this failure, then you have discovered a Ceres "
+      "Solver bug. Please get in touch with the Ceres Solver team at "
+      "ceres-solver@googlegroups.com";
+  return LinearSolverTerminationType::FATAL_ERROR;
+}
+
+CompressedRowSparseMatrix::StorageType FloatSuiteSparseCholesky::StorageType()
+    const {
+  return ((ordering_type_ == OrderingType::NATURAL)
+              ? CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR
+              : CompressedRowSparseMatrix::StorageType::LOWER_TRIANGULAR);
+}
+
+LinearSolverTerminationType FloatSuiteSparseCholesky::Solve(
+    const double* rhs, double* solution, std::string* message) {
+  *message =
+      "Single precision Cholesky factorization is not supported. If "
+      "you are seeing this failure, then you have discovered a Ceres "
+      "Solver bug. Please get in touch with the Ceres Solver team at "
+      "ceres-solver@googlegroups.com";
+  return LinearSolverTerminationType::FATAL_ERROR;
+}
+
+#endif  // CERES_NO_CHOLMOD_FLOAT
+
 }  // namespace ceres::internal
 
 #endif  // CERES_NO_SUITESPARSE
diff --git a/internal/ceres/suitesparse.h b/internal/ceres/suitesparse.h
index 57d1d78..11b136d 100644
--- a/internal/ceres/suitesparse.h
+++ b/internal/ceres/suitesparse.h
@@ -264,7 +264,30 @@
 
   const OrderingType ordering_type_;
   SuiteSparse ss_;
-  cholmod_factor* factor_;
+  cholmod_factor* factor_ = nullptr;
+};
+
+class CERES_NO_EXPORT FloatSuiteSparseCholesky final : public SparseCholesky {
+ public:
+  static std::unique_ptr<SparseCholesky> Create(OrderingType ordering_type);
+
+  // SparseCholesky interface.
+  ~FloatSuiteSparseCholesky() override;
+  CompressedRowSparseMatrix::StorageType StorageType() const final;
+  LinearSolverTerminationType Factorize(CompressedRowSparseMatrix* lhs,
+                                        std::string* message) final;
+  LinearSolverTerminationType Solve(const double* rhs,
+                                    double* solution,
+                                    std::string* message) final;
+
+ private:
+  explicit FloatSuiteSparseCholesky(const OrderingType ordering_type);
+
+  const OrderingType ordering_type_;
+  SuiteSparse ss_;
+  cholmod_factor* factor_ = nullptr;
+  Eigen::VectorXf float_lhs_values_;
+  Eigen::VectorXf float_rhs_;
 };
 
 }  // namespace ceres::internal