Back-substitute on NO_CONVERGENCE in SchurComplementSolver When ITERATIVE_SCHUR is used with use_explicit_schur_complement = true, ExplicitSchurComplementSolver inherits from SchurComplementSolver. Previously, SchurComplementSolver::SolveImpl only performed back-substitution when SolveReducedLinearSystem returned SUCCESS. When ConjugateGradientsSolver hit max_num_iterations and returned NO_CONVERGENCE, the eliminated parameter blocks were left at zero instead of being back-substituted from the approximate reduced solution, unlike IterativeSchurComplementSolver. Thanks to @1784174022-ops for reporting this. Fixes https://github.com/ceres-solver/ceres-solver/issues/1215 Change-Id: Ic879f1d6ad429d0ee054f8101c13a858d53b02c4
diff --git a/internal/ceres/schur_complement_solver.cc b/internal/ceres/schur_complement_solver.cc index 4a2fbba..7683633 100644 --- a/internal/ceres/schur_complement_solver.cc +++ b/internal/ceres/schur_complement_solver.cc
@@ -149,7 +149,8 @@ SolveReducedLinearSystem(per_solve_options, reduced_solution); event_logger.AddEvent("ReducedSolve"); - if (summary.termination_type == LinearSolverTerminationType::SUCCESS) { + if (summary.termination_type == LinearSolverTerminationType::SUCCESS || + summary.termination_type == LinearSolverTerminationType::NO_CONVERGENCE) { eliminator_->BackSubstitute( BlockSparseMatrixData(*A), b, per_solve_options.D, reduced_solution, x); event_logger.AddEvent("BackSubstitute");
diff --git a/internal/ceres/schur_complement_solver_test.cc b/internal/ceres/schur_complement_solver_test.cc index 18b79f3..349f6b1 100644 --- a/internal/ceres/schur_complement_solver_test.cc +++ b/internal/ceres/schur_complement_solver_test.cc
@@ -193,6 +193,48 @@ 4, true, ITERATIVE_SCHUR, EIGEN, NO_SPARSE, OrderingType::NATURAL); } +TEST_F(SchurComplementSolverTest, + ExplicitIterativeSchurBackSubstitutesOnNoConvergence) { + SetUpFromProblemId(2); + LinearSolver::Options options; + options.elimination_groups.push_back(num_eliminate_blocks); + options.elimination_groups.push_back(A->block_structure()->cols.size() - + num_eliminate_blocks); + options.type = ITERATIVE_SCHUR; + options.preconditioner_type = SCHUR_JACOBI; + options.max_num_iterations = 1; + ContextImpl context; + options.context = &context; + DetectStructure(*A->block_structure(), + num_eliminate_blocks, + &options.row_block_size, + &options.e_block_size, + &options.f_block_size); + + LinearSolver::PerSolveOptions per_solve_options; + per_solve_options.r_tolerance = 0.0; + per_solve_options.q_tolerance = 0.0; + per_solve_options.D = D.get(); + + options.use_explicit_schur_complement = false; + std::unique_ptr<LinearSolver> implicit_solver(LinearSolver::Create(options)); + Vector x_implicit(num_cols); + LinearSolver::Summary implicit_summary = implicit_solver->Solve( + A.get(), b.get(), per_solve_options, x_implicit.data()); + ASSERT_EQ(implicit_summary.termination_type, + LinearSolverTerminationType::NO_CONVERGENCE); + + options.use_explicit_schur_complement = true; + std::unique_ptr<LinearSolver> explicit_solver(LinearSolver::Create(options)); + Vector x_explicit(num_cols); + LinearSolver::Summary explicit_summary = explicit_solver->Solve( + A.get(), b.get(), per_solve_options, x_explicit.data()); + ASSERT_EQ(explicit_summary.termination_type, + LinearSolverTerminationType::NO_CONVERGENCE); + + EXPECT_NEAR((x_implicit - x_explicit).norm() / num_cols, 0.0, 1e-10); +} + #ifndef CERES_NO_LAPACK TEST_F(SchurComplementSolverTest, DenseSchurWithLAPACKSmallProblem) { ComputeAndCompareSolutions(