Refactor options checking for linear solvers

The code that verifies that the linear solver is configuration
specified by the user has grown into a rat's nest. This CL
attempts to bring some order to this madness.

Fixes https://github.com/ceres-solver/ceres-solver/issues/852

Change-Id: I3f34c0e27da13a6412117dee43ef2d9ec3835b64
diff --git a/internal/ceres/solver.cc b/internal/ceres/solver.cc
index 8ad0862..b07d4f0 100644
--- a/internal/ceres/solver.cc
+++ b/internal/ceres/solver.cc
@@ -118,37 +118,242 @@
            internal::EigenSparse::IsNestedDissectionAvailable()));
 }
 
-bool MixedPrecisionOptionIsValid(const Solver::Options& options,
-                                 string* error) {
-  if (!options.use_mixed_precision_solves) {
-    return true;
+bool IsIterativeSolver(LinearSolverType type) {
+  return (type == CGNR || type == ITERATIVE_SCHUR);
+}
+
+bool OptionsAreValidForDenseSolver(const Solver::Options& options,
+                                   string* error) {
+  const char* library_name = DenseLinearAlgebraLibraryTypeToString(
+      options.dense_linear_algebra_library_type);
+  const char* solver_name =
+      LinearSolverTypeToString(options.linear_solver_type);
+  constexpr char kFormat[] =
+      "Can't use %s with dense_linear_algebra_library_type = %s "
+      "because support not enabled when Ceres was built.";
+
+  if (!IsDenseLinearAlgebraLibraryTypeAvailable(
+          options.dense_linear_algebra_library_type)) {
+    *error = StringPrintf(kFormat, solver_name, library_name);
+    return false;
+  }
+  return true;
+}
+
+bool OptionsAreValidForSparseCholeskyBasedSolver(const Solver::Options& options,
+                                                 string* error) {
+  const char* library_name = SparseLinearAlgebraLibraryTypeToString(
+      options.sparse_linear_algebra_library_type);
+  // Sparse factorization based solvers and some preconditioners require a
+  // sparse Cholesky factorization.
+  const char* solver_name =
+      IsIterativeSolver(options.linear_solver_type)
+          ? PreconditionerTypeToString(options.preconditioner_type)
+          : LinearSolverTypeToString(options.linear_solver_type);
+
+  constexpr char kNoSparseFormat[] =
+      "Can't use %s with sparse_linear_algebra_library_type = %s.";
+  constexpr char kNoLibraryFormat[] =
+      "Can't use %s sparse_linear_algebra_library_type = %s, because support "
+      "was not enabled when Ceres Solver was built.";
+  constexpr char kNoNesdisFormat[] =
+      "NESDIS is not available with sparse_linear_algebra_library_type = %s.";
+  constexpr char kMixedFormat[] =
+      "use_mixed_precision_solves with %s is not supported with "
+      "sparse_linear_algebra_library_type = %s";
+  constexpr char kDynamicSparsityFormat[] =
+      "dynamic sparsity is not supported with "
+      "sparse_linear_algebra_library_type = %s";
+
+  if (options.sparse_linear_algebra_library_type == NO_SPARSE) {
+    *error = StringPrintf(kNoSparseFormat, solver_name, library_name);
+    return false;
   }
 
-  // All dense linear algebra backends support mixed precision solves now with
-  // Cholesky factorization.
-  if ((options.linear_solver_type == DENSE_NORMAL_CHOLESKY ||
-       options.linear_solver_type == DENSE_SCHUR)) {
-    return true;
+  if (!IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    *error = StringPrintf(kNoLibraryFormat, solver_name, library_name);
+    return false;
   }
 
-  if ((options.linear_solver_type == SPARSE_NORMAL_CHOLESKY ||
-       options.linear_solver_type == SPARSE_SCHUR)) {
-    if (options.sparse_linear_algebra_library_type == EIGEN_SPARSE ||
-        options.sparse_linear_algebra_library_type == ACCELERATE_SPARSE) {
-      // Mixed precision with any Eigen or Accelerate Cholesky variant: okay.
-      return true;
+  if (options.linear_solver_ordering_type == ceres::NESDIS &&
+      !IsNestedDissectionAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    *error = StringPrintf(kNoNesdisFormat, library_name);
+    return false;
+  }
+
+  if (options.use_mixed_precision_solves &&
+      options.sparse_linear_algebra_library_type == SUITE_SPARSE) {
+    *error = StringPrintf(kMixedFormat, solver_name, library_name);
+    return false;
+  }
+
+  if (options.dynamic_sparsity &&
+      options.sparse_linear_algebra_library_type == ACCELERATE_SPARSE) {
+    *error = StringPrintf(kDynamicSparsityFormat, library_name);
+    return false;
+  }
+
+  return true;
+}
+
+bool OptionsAreValidForDenseNormalCholesky(const Solver::Options& options,
+                                           string* error) {
+  CHECK_EQ(options.linear_solver_type, DENSE_NORMAL_CHOLESKY);
+  return OptionsAreValidForDenseSolver(options, error);
+}
+
+bool OptionsAreValidForDenseQr(const Solver::Options& options, string* error) {
+  CHECK_EQ(options.linear_solver_type, DENSE_QR);
+
+  if (!OptionsAreValidForDenseSolver(options, error)) {
+    return false;
+  }
+
+  if (options.use_mixed_precision_solves) {
+    *error = "Can't use use_mixed_precision_solves with DENSE_QR.";
+    return false;
+  }
+
+  return true;
+}
+
+bool OptionsAreValidForSparseNormalCholesky(const Solver::Options& options,
+                                            string* error) {
+  CHECK_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
+  return OptionsAreValidForSparseCholeskyBasedSolver(options, error);
+}
+
+bool OptionsAreValidForDenseSchur(const Solver::Options& options,
+                                  string* error) {
+  CHECK_EQ(options.linear_solver_type, DENSE_SCHUR);
+
+  if (options.dynamic_sparsity) {
+    *error = "dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY";
+    return false;
+  }
+
+  if (!OptionsAreValidForDenseSolver(options, error)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool OptionsAreValidForSparseSchur(const Solver::Options& options,
+                                   string* error) {
+  CHECK_EQ(options.linear_solver_type, SPARSE_SCHUR);
+  if (options.dynamic_sparsity) {
+    *error = "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY.";
+    return false;
+  }
+  return OptionsAreValidForSparseCholeskyBasedSolver(options, error);
+}
+
+bool OptionsAreValidForIterativeSchur(const Solver::Options& options,
+                                      string* error) {
+  CHECK_EQ(options.linear_solver_type, ITERATIVE_SCHUR);
+  if (options.dynamic_sparsity) {
+    *error = "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY.";
+    return false;
+  }
+
+  if (options.use_explicit_schur_complement &&
+      options.preconditioner_type != SCHUR_JACOBI) {
+    *error =
+        "use_explicit_schur_complement only supports "
+        "SCHUR_JACOBI as the preconditioner.";
+    return false;
+  }
+
+  if (options.use_mixed_precision_solves) {
+    *error = "Can't use use_mixed_precision_solves with ITERATIVE_SCHUR";
+    return false;
+  }
+
+  if (options.dynamic_sparsity) {
+    *error = "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY.";
+    return false;
+  }
+
+  if (options.preconditioner_type == SUBSET) {
+    *error = "Can't use SUBSET preconditioner with ITERATIVE_SCHUR";
+    return false;
+  }
+
+  // CLUSTER_JACOBI and CLUSTER_TRIDIAGONAL require sparse Cholesky
+  // factorization.
+  if (options.preconditioner_type == CLUSTER_JACOBI ||
+      options.preconditioner_type == CLUSTER_TRIDIAGONAL) {
+    return OptionsAreValidForSparseCholeskyBasedSolver(options, error);
+  }
+
+  return true;
+}
+
+bool OptionsAreValidForCgnr(const Solver::Options& options, string* error) {
+  CHECK_EQ(options.linear_solver_type, CGNR);
+
+  if (options.preconditioner_type != IDENTITY &&
+      options.preconditioner_type != JACOBI &&
+      options.preconditioner_type != SUBSET) {
+    *error =
+        StringPrintf("Can't use CGNR with preconditioner_type = %s.",
+                     PreconditionerTypeToString(options.preconditioner_type));
+    return false;
+  }
+
+  if (options.use_mixed_precision_solves) {
+    *error = "use_mixed_precision_solves cannot be used with CGNR";
+    return false;
+  }
+
+  if (options.dynamic_sparsity) {
+    *error = "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY.";
+    return false;
+  }
+
+  if (options.preconditioner_type == SUBSET) {
+    if (options.residual_blocks_for_subset_preconditioner.empty()) {
+      *error =
+          "When using SUBSET preconditioner, "
+          "residual_blocks_for_subset_preconditioner cannot be empty";
+      return false;
     }
-    if (options.sparse_linear_algebra_library_type == SUITE_SPARSE) {
-      *error = StringPrintf(
-          "use_mixed_precision_solves with %s is not supported with "
-          "SUITE_SPARSE as the sparse_linear_algebra_library_type.",
-          LinearSolverTypeToString(options.linear_solver_type));
+
+    // SUBSET preconditioner requires sparse Cholesky factorization.
+    if (!OptionsAreValidForSparseCholeskyBasedSolver(options, error)) {
       return false;
     }
   }
 
-  *error = StringPrintf("use_mixed_precision_solves with %s is not supported.",
-                        LinearSolverTypeToString(options.linear_solver_type));
+  return true;
+}
+
+bool OptionsAreValidForLinearSolver(const Solver::Options& options,
+                                    string* error) {
+  switch (options.linear_solver_type) {
+    case DENSE_NORMAL_CHOLESKY:
+      return OptionsAreValidForDenseNormalCholesky(options, error);
+    case DENSE_QR:
+      return OptionsAreValidForDenseQr(options, error);
+    case SPARSE_NORMAL_CHOLESKY:
+      return OptionsAreValidForSparseNormalCholesky(options, error);
+    case DENSE_SCHUR:
+      return OptionsAreValidForDenseSchur(options, error);
+    case SPARSE_SCHUR:
+      return OptionsAreValidForSparseSchur(options, error);
+    case ITERATIVE_SCHUR:
+      return OptionsAreValidForIterativeSchur(options, error);
+    case CGNR:
+      return OptionsAreValidForCgnr(options, error);
+    default:
+      LOG(FATAL) << "Congratulations you have found a bug. Please report "
+                    "this to the "
+                    "Ceres Solver developers. Unknown linear solver type: "
+                 << LinearSolverTypeToString(options.linear_solver_type);
+  }
   return false;
 }
 
@@ -177,106 +382,19 @@
     OPTION_GT(max_consecutive_nonmonotonic_steps, 0);
   }
 
-  if (options.linear_solver_type == ITERATIVE_SCHUR &&
-      options.use_explicit_schur_complement &&
-      options.preconditioner_type != SCHUR_JACOBI) {
+  if ((options.trust_region_strategy_type == DOGLEG) &&
+      IsIterativeSolver(options.linear_solver_type)) {
     *error =
-        "use_explicit_schur_complement only supports "
-        "SCHUR_JACOBI as the preconditioner.";
+        "DOGLEG only supports exact factorization based linear "
+        "solvers. If you want to use an iterative solver please "
+        "use LEVENBERG_MARQUARDT as the trust_region_strategy_type";
     return false;
   }
 
-  if (!IsDenseLinearAlgebraLibraryTypeAvailable(
-          options.dense_linear_algebra_library_type) &&
-      (options.linear_solver_type == DENSE_NORMAL_CHOLESKY ||
-       options.linear_solver_type == DENSE_QR ||
-       options.linear_solver_type == DENSE_SCHUR)) {
-    *error = StringPrintf(
-        "Can't use %s with "
-        "Solver::Options::dense_linear_algebra_library_type = %s "
-        "because %s was not enabled when Ceres was built.",
-        LinearSolverTypeToString(options.linear_solver_type),
-        DenseLinearAlgebraLibraryTypeToString(
-            options.dense_linear_algebra_library_type),
-        DenseLinearAlgebraLibraryTypeToString(
-            options.dense_linear_algebra_library_type));
+  if (!OptionsAreValidForLinearSolver(options, error)) {
     return false;
   }
 
-  {
-    const char* sparse_linear_algebra_library_name =
-        SparseLinearAlgebraLibraryTypeToString(
-            options.sparse_linear_algebra_library_type);
-    const char* name = nullptr;
-    if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY ||
-        options.linear_solver_type == SPARSE_SCHUR) {
-      name = LinearSolverTypeToString(options.linear_solver_type);
-    } else if ((options.linear_solver_type == ITERATIVE_SCHUR &&
-                (options.preconditioner_type == CLUSTER_JACOBI ||
-                 options.preconditioner_type == CLUSTER_TRIDIAGONAL)) ||
-               (options.linear_solver_type == CGNR &&
-                options.preconditioner_type == SUBSET)) {
-      name = PreconditionerTypeToString(options.preconditioner_type);
-    }
-
-    if (name) {
-      if (options.sparse_linear_algebra_library_type == NO_SPARSE) {
-        *error = StringPrintf(
-            "Can't use %s with "
-            "Solver::Options::sparse_linear_algebra_library_type = %s.",
-            name,
-            sparse_linear_algebra_library_name);
-        return false;
-      }
-
-      if (!IsSparseLinearAlgebraLibraryTypeAvailable(
-              options.sparse_linear_algebra_library_type)) {
-        *error = StringPrintf(
-            "Can't use %s with "
-            "Solver::Options::sparse_linear_algebra_library_type = %s, "
-            "because support was not enabled when Ceres Solver was built.",
-            name,
-            sparse_linear_algebra_library_name);
-        return false;
-      }
-
-      if (options.linear_solver_ordering_type == ceres::NESDIS &&
-          !IsNestedDissectionAvailable(
-              options.sparse_linear_algebra_library_type)) {
-        if (options.sparse_linear_algebra_library_type == SUITE_SPARSE) {
-          *error = StringPrintf(
-              "Can't use NESDIS with SUITE_SPARSE because SuiteSparse was "
-              "compiled without support for Metis.");
-        } else if (options.sparse_linear_algebra_library_type == EIGEN_SPARSE) {
-          *error = StringPrintf(
-              "Can't use NESDIS with EIGEN_SPARSE because Ceres was "
-              "compiled without support for Metis.");
-        } else {
-          *error = StringPrintf(
-              "Can't use NESDIS with "
-              "Solver::Options::sparse_linear_algebra_library_type = %s.",
-              sparse_linear_algebra_library_name);
-        }
-        return false;
-      }
-    }
-  }
-
-  if (!MixedPrecisionOptionIsValid(options, error)) {
-    return false;
-  }
-
-  if (options.trust_region_strategy_type == DOGLEG) {
-    if (options.linear_solver_type == ITERATIVE_SCHUR ||
-        options.linear_solver_type == CGNR) {
-      *error =
-          "DOGLEG only supports exact factorization based linear "
-          "solvers. If you want to use an iterative solver please "
-          "use LEVENBERG_MARQUARDT as the trust_region_strategy_type";
-      return false;
-    }
-  }
-
   if (!options.trust_region_minimizer_iterations_to_dump.empty() &&
       options.trust_region_problem_dump_format_type != CONSOLE &&
       options.trust_region_problem_dump_directory.empty()) {
@@ -284,31 +402,6 @@
     return false;
   }
 
-  if (options.dynamic_sparsity) {
-    if (options.linear_solver_type != SPARSE_NORMAL_CHOLESKY) {
-      *error =
-          "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY.";
-      return false;
-    }
-    if (options.sparse_linear_algebra_library_type == ACCELERATE_SPARSE) {
-      *error =
-          "ACCELERATE_SPARSE is not currently supported with dynamic "
-          "sparsity.";
-      return false;
-    }
-  }
-
-  if (options.linear_solver_type == CGNR &&
-      options.preconditioner_type == SUBSET &&
-      options.residual_blocks_for_subset_preconditioner.empty()) {
-    *error =
-        "When using SUBSET preconditioner, "
-        "Solver::Options::residual_blocks_for_subset_preconditioner cannot "
-        "be "
-        "empty";
-    return false;
-  }
-
   return true;
 }
 
@@ -785,8 +878,7 @@
                   LinearSolverTypeToString(linear_solver_type_given),
                   LinearSolverTypeToString(linear_solver_type_used));
 
-    if (linear_solver_type_given == CGNR ||
-        linear_solver_type_given == ITERATIVE_SCHUR) {
+    if (IsIterativeSolver(linear_solver_type_given)) {
       StringAppendF(&report,
                     "Preconditioner      %25s%25s\n",
                     PreconditionerTypeToString(preconditioner_type_given),
diff --git a/internal/ceres/solver_test.cc b/internal/ceres/solver_test.cc
index 5abd723..1b61b18 100644
--- a/internal/ceres/solver_test.cc
+++ b/internal/ceres/solver_test.cc
@@ -312,160 +312,6 @@
   EXPECT_EQ(summary.final_cost, 1.0 / 2.0);
 }
 
-#if defined(CERES_NO_SUITESPARSE)
-TEST(Solver, SparseNormalCholeskyNoSuiteSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, SparseSchurNoSuiteSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
-  options.linear_solver_type = SPARSE_SCHUR;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-#endif
-
-#if defined(CERES_NO_CXSPARSE)
-TEST(Solver, SparseNormalCholeskyNoCXSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = CX_SPARSE;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, SparseSchurNoCXSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = CX_SPARSE;
-  options.linear_solver_type = SPARSE_SCHUR;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-#endif
-
-#if defined(CERES_NO_ACCELERATE_SPARSE)
-TEST(Solver, SparseNormalCholeskyNoAccelerateSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, SparseSchurNoAccelerateSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
-  options.linear_solver_type = SPARSE_SCHUR;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-#else
-TEST(Solver, DynamicSparseNormalCholeskyUnsupportedWithAccelerateSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  options.dynamic_sparsity = true;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-#endif
-
-#if !defined(CERES_USE_EIGEN_SPARSE)
-TEST(Solver, SparseNormalCholeskyNoEigenSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, SparseSchurNoEigenSparse) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
-  options.linear_solver_type = SPARSE_SCHUR;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-#endif
-
-TEST(Solver, SparseNormalCholeskyNoSparseLibrary) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = NO_SPARSE;
-  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, SparseSchurNoSparseLibrary) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = NO_SPARSE;
-  options.linear_solver_type = SPARSE_SCHUR;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, IterativeSchurWithClusterJacobiPerconditionerNoSparseLibrary) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = NO_SPARSE;
-  options.linear_solver_type = ITERATIVE_SCHUR;
-  // Requires SuiteSparse.
-  options.preconditioner_type = CLUSTER_JACOBI;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver,
-     IterativeSchurWithClusterTridiagonalPerconditionerNoSparseLibrary) {
-  Solver::Options options;
-  options.sparse_linear_algebra_library_type = NO_SPARSE;
-  options.linear_solver_type = ITERATIVE_SCHUR;
-  // Requires SuiteSparse.
-  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
-  string message;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, IterativeLinearSolverForDogleg) {
-  Solver::Options options;
-  options.trust_region_strategy_type = DOGLEG;
-  string message;
-  options.linear_solver_type = ITERATIVE_SCHUR;
-  EXPECT_FALSE(options.IsValid(&message));
-
-  options.linear_solver_type = CGNR;
-  EXPECT_FALSE(options.IsValid(&message));
-}
-
-TEST(Solver, LinearSolverTypeNormalOperation) {
-  Solver::Options options;
-  options.linear_solver_type = DENSE_QR;
-
-  string message;
-  EXPECT_TRUE(options.IsValid(&message));
-
-  options.linear_solver_type = DENSE_NORMAL_CHOLESKY;
-  EXPECT_TRUE(options.IsValid(&message));
-
-  options.linear_solver_type = DENSE_SCHUR;
-  EXPECT_TRUE(options.IsValid(&message));
-
-  options.linear_solver_type = SPARSE_SCHUR;
-#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE) && \
-    !defined(CERES_USE_EIGEN_SPARSE)
-  EXPECT_FALSE(options.IsValid(&message));
-#else
-  EXPECT_TRUE(options.IsValid(&message));
-#endif
-
-  options.linear_solver_type = ITERATIVE_SCHUR;
-  EXPECT_TRUE(options.IsValid(&message));
-}
-
 template <int kNumResiduals, int... Ns>
 class DummyCostFunction : public SizedCostFunction<kNumResiduals, Ns...> {
  public:
@@ -529,4 +375,793 @@
   EXPECT_EQ(y, 1.0);
 }
 
+TEST(Solver, DenseNormalCholeskyOptions) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = DENSE_NORMAL_CHOLESKY;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dense_linear_algebra_library_type = EIGEN;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.use_mixed_precision_solves = true;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  if (IsDenseLinearAlgebraLibraryTypeAvailable(LAPACK)) {
+    options.use_mixed_precision_solves = false;
+    options.dense_linear_algebra_library_type = LAPACK;
+
+    EXPECT_TRUE(options.IsValid(&message));
+    options.use_mixed_precision_solves = true;
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    options.use_mixed_precision_solves = false;
+    options.dense_linear_algebra_library_type = LAPACK;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+}
+
+TEST(Solver, DenseQrOptions) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = DENSE_QR;
+
+  options.use_mixed_precision_solves = false;
+  options.dense_linear_algebra_library_type = EIGEN;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  if (IsDenseLinearAlgebraLibraryTypeAvailable(LAPACK)) {
+    options.use_mixed_precision_solves = false;
+    options.dense_linear_algebra_library_type = LAPACK;
+    EXPECT_TRUE(options.IsValid(&message));
+    options.use_mixed_precision_solves = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  } else {
+    options.use_mixed_precision_solves = false;
+    options.dense_linear_algebra_library_type = LAPACK;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+}
+
+TEST(Solver, SparseNormalCholeskyOptionsNoSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options.sparse_linear_algebra_library_type = NO_SPARSE;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, SparseNormalCholeskyOptionsEigenSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
+  options.linear_solver_ordering_type = AMD;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(EIGEN_SPARSE)) {
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(EIGEN_SPARSE)) {
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_TRUE(options.IsValid(&message));
+  }
+
+#ifndef CERES_NO_EIGEN_METIS
+  options.linear_solver_ordering_type = NESDIS;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(EIGEN_SPARSE)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_TRUE(options.IsValid(&message));
+  }
+#else
+  options.linear_solver_ordering_type = NESDIS;
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  EXPECT_FALSE(options.IsValid(&message));
+#endif
+}
+
+TEST(Solver, SparseNormalCholeskyOptionsSuiteSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
+  options.linear_solver_ordering_type = AMD;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+#ifndef CERES_NO_CHOLMOD_PARTITION
+  options.linear_solver_ordering_type = NESDIS;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+#else
+  options.linear_solver_ordering_type = NESDIS;
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  EXPECT_FALSE(options.IsValid(&message));
+#endif
+}
+
+TEST(Solver, SparseNormalCholeskyOptionsAccelerateSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
+  options.linear_solver_ordering_type = AMD;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  options.linear_solver_ordering_type = NESDIS;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+}
+
+TEST(Solver, DenseSchurOptions) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = DENSE_SCHUR;
+  options.dense_linear_algebra_library_type = EIGEN;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.use_mixed_precision_solves = true;
+  options.dynamic_sparsity = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.use_mixed_precision_solves = true;
+  options.dynamic_sparsity = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dense_linear_algebra_library_type = LAPACK;
+  if (IsDenseLinearAlgebraLibraryTypeAvailable(
+          options.dense_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+}
+
+TEST(Solver, SparseSchurOptionsNoSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_SCHUR;
+  options.sparse_linear_algebra_library_type = NO_SPARSE;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, SparseSchurOptionsEigenSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_SCHUR;
+  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
+  options.linear_solver_ordering_type = AMD;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(EIGEN_SPARSE)) {
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(EIGEN_SPARSE)) {
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+#ifndef CERES_NO_EIGEN_METIS
+  options.linear_solver_ordering_type = NESDIS;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(EIGEN_SPARSE)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+#else
+  options.linear_solver_ordering_type = NESDIS;
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  EXPECT_FALSE(options.IsValid(&message));
+#endif
+}
+
+TEST(Solver, SparseSchurOptionsSuiteSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_SCHUR;
+  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
+  options.linear_solver_ordering_type = AMD;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+#ifndef CERES_NO_CHOLMOD_PARTITION
+  options.linear_solver_ordering_type = NESDIS;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+#else
+  options.linear_solver_ordering_type = NESDIS;
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  EXPECT_FALSE(options.IsValid(&message));
+#endif
+}
+
+TEST(Solver, SparseSchurOptionsAccelerateSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = SPARSE_SCHUR;
+  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
+  options.linear_solver_ordering_type = AMD;
+
+  options.use_mixed_precision_solves = false;
+  options.dynamic_sparsity = false;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    EXPECT_TRUE(options.IsValid(&message));
+  } else {
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  options.linear_solver_ordering_type = NESDIS;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = false;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.use_mixed_precision_solves = true;
+    options.dynamic_sparsity = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+}
+
+TEST(Solver, CgnrOptionsIdentityPreconditioner) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = CGNR;
+  options.preconditioner_type = IDENTITY;
+  options.sparse_linear_algebra_library_type = NO_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.dynamic_sparsity = false;
+    options.use_mixed_precision_solves = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.dynamic_sparsity = true;
+    options.use_mixed_precision_solves = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.dynamic_sparsity = false;
+    options.use_mixed_precision_solves = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.dynamic_sparsity = false;
+    options.use_mixed_precision_solves = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.dynamic_sparsity = true;
+    options.use_mixed_precision_solves = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.dynamic_sparsity = false;
+    options.use_mixed_precision_solves = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+
+  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
+  if (IsSparseLinearAlgebraLibraryTypeAvailable(
+          options.sparse_linear_algebra_library_type)) {
+    options.dynamic_sparsity = false;
+    options.use_mixed_precision_solves = false;
+    EXPECT_TRUE(options.IsValid(&message));
+
+    options.dynamic_sparsity = true;
+    options.use_mixed_precision_solves = false;
+    EXPECT_FALSE(options.IsValid(&message));
+
+    options.dynamic_sparsity = false;
+    options.use_mixed_precision_solves = true;
+    EXPECT_FALSE(options.IsValid(&message));
+  }
+}
+
+TEST(Solver, CgnrOptionsJacobiPreconditioner) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = CGNR;
+  options.preconditioner_type = JACOBI;
+  options.sparse_linear_algebra_library_type = NO_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, CgnrOptionsSubsetPreconditioner) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = CGNR;
+  options.preconditioner_type = SUBSET;
+
+  options.sparse_linear_algebra_library_type = NO_SPARSE;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.residual_blocks_for_subset_preconditioner.insert(nullptr);
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = false;
+  EXPECT_TRUE(options.IsValid(&message));
+
+  options.dynamic_sparsity = true;
+  options.use_mixed_precision_solves = false;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.dynamic_sparsity = false;
+  options.use_mixed_precision_solves = true;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, CgnrOptionsSchurPreconditioners) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = CGNR;
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, IterativeSchurOptionsNoSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = ITERATIVE_SCHUR;
+  options.sparse_linear_algebra_library_type = NO_SPARSE;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = SUBSET;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.use_explicit_schur_complement = true;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, IterativeSchurOptionsEigenSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = ITERATIVE_SCHUR;
+  options.sparse_linear_algebra_library_type = EIGEN_SPARSE;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_EQ(options.IsValid(&message),
+            IsSparseLinearAlgebraLibraryTypeAvailable(
+                options.sparse_linear_algebra_library_type));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_EQ(options.IsValid(&message),
+            IsSparseLinearAlgebraLibraryTypeAvailable(
+                options.sparse_linear_algebra_library_type));
+  options.preconditioner_type = SUBSET;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.use_explicit_schur_complement = true;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, IterativeSchurOptionsSuiteSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = ITERATIVE_SCHUR;
+  options.sparse_linear_algebra_library_type = SUITE_SPARSE;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_EQ(options.IsValid(&message),
+            IsSparseLinearAlgebraLibraryTypeAvailable(
+                options.sparse_linear_algebra_library_type));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_EQ(options.IsValid(&message),
+            IsSparseLinearAlgebraLibraryTypeAvailable(
+                options.sparse_linear_algebra_library_type));
+  options.preconditioner_type = SUBSET;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.use_explicit_schur_complement = true;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
+TEST(Solver, IterativeSchurOptionsAccelerateSparse) {
+  std::string message;
+  Solver::Options options;
+  options.linear_solver_type = ITERATIVE_SCHUR;
+  options.sparse_linear_algebra_library_type = ACCELERATE_SPARSE;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_EQ(options.IsValid(&message),
+            IsSparseLinearAlgebraLibraryTypeAvailable(
+                options.sparse_linear_algebra_library_type));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_EQ(options.IsValid(&message),
+            IsSparseLinearAlgebraLibraryTypeAvailable(
+                options.sparse_linear_algebra_library_type));
+  options.preconditioner_type = SUBSET;
+  EXPECT_FALSE(options.IsValid(&message));
+
+  options.use_explicit_schur_complement = true;
+  options.preconditioner_type = IDENTITY;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = SCHUR_JACOBI;
+  EXPECT_TRUE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_JACOBI;
+  EXPECT_FALSE(options.IsValid(&message));
+  options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+  EXPECT_FALSE(options.IsValid(&message));
+}
+
 }  // namespace ceres::internal
diff --git a/internal/ceres/types.cc b/internal/ceres/types.cc
index 5d1bcea..c0e3355 100644
--- a/internal/ceres/types.cc
+++ b/internal/ceres/types.cc
@@ -418,6 +418,10 @@
 #endif
   }
 
+  if (type == NO_SPARSE) {
+    return true;
+  }
+
   LOG(WARNING) << "Unknown sparse linear algebra library " << type;
   return false;
 }