Refactor SolverImpl::CreateReducedProgram.
Break up CreateReducedProgram into smaller functions in
preparation for more sophisticated ordering strategies.
Change-Id: Ic3897522574fde770646d747fe383f5dbd7a6619
diff --git a/internal/ceres/solver_impl.cc b/internal/ceres/solver_impl.cc
index ffc347e..add28ae 100644
--- a/internal/ceres/solver_impl.cc
+++ b/internal/ceres/solver_impl.cc
@@ -38,8 +38,8 @@
#include "ceres/gradient_checking_cost_function.h"
#include "ceres/iteration_callback.h"
#include "ceres/levenberg_marquardt_strategy.h"
-#include "ceres/linear_solver.h"
#include "ceres/line_search_minimizer.h"
+#include "ceres/linear_solver.h"
#include "ceres/map_util.h"
#include "ceres/minimizer.h"
#include "ceres/ordered_groups.h"
@@ -505,19 +505,6 @@
summary->trust_region_strategy_type = options.trust_region_strategy_type;
summary->dogleg_type = options.dogleg_type;
- // Only Schur types require the lexicographic reordering.
- if (IsSchurType(options.linear_solver_type)) {
- const int num_eliminate_blocks =
- options.linear_solver_ordering
- ->group_to_elements().begin()
- ->second.size();
- if (!LexicographicallyOrderResidualBlocks(num_eliminate_blocks,
- reduced_program.get(),
- &summary->error)) {
- return;
- }
- }
-
scoped_ptr<Evaluator> evaluator(CreateEvaluator(options,
problem_impl->parameter_map(),
reduced_program.get(),
@@ -953,11 +940,14 @@
parameter_blocks->resize(j);
}
- CHECK(((program->NumResidualBlocks() == 0) &&
+ if (!(((program->NumResidualBlocks() == 0) &&
(program->NumParameterBlocks() == 0)) ||
((program->NumResidualBlocks() != 0) &&
- (program->NumParameterBlocks() != 0)))
- << "Congratulations, you found a bug in Ceres. Please report it.";
+ (program->NumParameterBlocks() != 0)))) {
+ *error = "Congratulations, you found a bug in Ceres. Please report it.";
+ return false;
+ }
+
return true;
}
@@ -965,19 +955,14 @@
ProblemImpl* problem_impl,
double* fixed_cost,
string* error) {
- EventLogger event_logger("CreateReducedProgram");
-
CHECK_NOTNULL(options->linear_solver_ordering);
Program* original_program = problem_impl->mutable_program();
scoped_ptr<Program> transformed_program(new Program(*original_program));
- event_logger.AddEvent("TransformedProgram");
ParameterBlockOrdering* linear_solver_ordering =
options->linear_solver_ordering;
-
const int min_group_id =
linear_solver_ordering->group_to_elements().begin()->first;
- const int original_num_groups = linear_solver_ordering->NumGroups();
if (!RemoveFixedBlocksFromProgram(transformed_program.get(),
linear_solver_ordering,
@@ -986,97 +971,39 @@
return NULL;
}
- event_logger.AddEvent("RemoveFixedBlocks");
-
if (transformed_program->NumParameterBlocks() == 0) {
- if (transformed_program->NumResidualBlocks() > 0) {
- *error = "Zero parameter blocks but non-zero residual blocks"
- " in the reduced program. Congratulations, you found a "
- "Ceres bug! Please report this error to the developers.";
- return NULL;
- }
-
LOG(WARNING) << "No varying parameter blocks to optimize; "
<< "bailing early.";
return transformed_program.release();
}
- // If the user supplied an linear_solver_ordering with just one
- // group, it is equivalent to the user supplying NULL as
- // ordering. Ceres is completely free to choose the parameter block
- // ordering as it sees fit. For Schur type solvers, this means that
- // the user wishes for Ceres to identify the e_blocks, which we do
- // by computing a maximal independent set.
- if (original_num_groups == 1 && IsSchurType(options->linear_solver_type)) {
- vector<ParameterBlock*> schur_ordering;
- const int num_eliminate_blocks = ComputeSchurOrdering(*transformed_program,
- &schur_ordering);
- CHECK_EQ(schur_ordering.size(), transformed_program->NumParameterBlocks())
- << "Congratulations, you found a Ceres bug! Please report this error "
- << "to the developers.";
-
- for (int i = 0; i < schur_ordering.size(); ++i) {
- linear_solver_ordering->AddElementToGroup(
- schur_ordering[i]->mutable_user_state(),
- (i < num_eliminate_blocks) ? 0 : 1);
- }
- }
- event_logger.AddEvent("SchurOrdering");
-
- if (!ApplyUserOrdering(problem_impl->parameter_map(),
- linear_solver_ordering,
- transformed_program.get(),
- error)) {
- return NULL;
- }
- event_logger.AddEvent("ApplyOrdering");
-
- // If the user requested the use of a Schur type solver, and
- // supplied a non-NULL linear_solver_ordering object with more than
- // one elimination group, then it can happen that after all the
- // parameter blocks which are fixed or unused have been removed from
- // the program and the ordering, there are no more parameter blocks
- // in the first elimination group.
- //
- // In such a case, the use of a Schur type solver is not possible,
- // as they assume there is at least one e_block. Thus, we
- // automatically switch to one of the other solvers, depending on
- // the user's indicated preferences.
if (IsSchurType(options->linear_solver_type) &&
- original_num_groups > 1 &&
linear_solver_ordering->GroupSize(min_group_id) == 0) {
- string msg = "No e_blocks remaining. Switching from ";
- if (options->linear_solver_type == SPARSE_SCHUR) {
- options->linear_solver_type = SPARSE_NORMAL_CHOLESKY;
- msg += "SPARSE_SCHUR to SPARSE_NORMAL_CHOLESKY.";
- } else if (options->linear_solver_type == DENSE_SCHUR) {
- // TODO(sameeragarwal): This is probably not a great choice.
- // Ideally, we should have a DENSE_NORMAL_CHOLESKY, that can
- // take a BlockSparseMatrix as input.
- options->linear_solver_type = DENSE_QR;
- msg += "DENSE_SCHUR to DENSE_QR.";
- } else if (options->linear_solver_type == ITERATIVE_SCHUR) {
- msg += StringPrintf("ITERATIVE_SCHUR with %s preconditioner "
- "to CGNR with JACOBI preconditioner.",
- PreconditionerTypeToString(
- options->preconditioner_type));
- options->linear_solver_type = CGNR;
- if (options->preconditioner_type != IDENTITY) {
- // CGNR currently only supports the JACOBI preconditioner.
- options->preconditioner_type = JACOBI;
- }
- }
-
- LOG(WARNING) << msg;
+ // If the user requested the use of a Schur type solver, and
+ // supplied a non-NULL linear_solver_ordering object with more than
+ // one elimination group, then it can happen that after all the
+ // parameter blocks which are fixed or unused have been removed from
+ // the program and the ordering, there are no more parameter blocks
+ // in the first elimination group.
+ //
+ // In such a case, the use of a Schur type solver is not possible,
+ // as they assume there is at least one e_block. Thus, we
+ // automatically switch to the closest solver to the one indicated
+ // by the user.
+ AlternateLinearSolverForSchurTypeLinearSolver(options);
}
- event_logger.AddEvent("AlternateSolver");
+ if (IsSchurType(options->linear_solver_type)) {
+ if (!ReorderProgramForSchurTypeLinearSolver(problem_impl->parameter_map(),
+ linear_solver_ordering,
+ transformed_program.get(),
+ error)) {
+ return NULL;
+ }
+ return transformed_program.release();
+ }
- // Since the transformed program is the "active" program, and it is
- // mutated, update the parameter offsets and indices.
transformed_program->SetParameterOffsetsAndIndex();
-
- event_logger.AddEvent("SetOffsets");
return transformed_program.release();
}
@@ -1398,5 +1325,96 @@
return inner_iteration_minimizer.release();
}
+void SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(
+ Solver::Options* options) {
+ if (!IsSchurType(options->linear_solver_type)) {
+ return;
+ }
+
+ string msg = "No e_blocks remaining. Switching from ";
+ if (options->linear_solver_type == SPARSE_SCHUR) {
+ options->linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+ msg += "SPARSE_SCHUR to SPARSE_NORMAL_CHOLESKY.";
+ } else if (options->linear_solver_type == DENSE_SCHUR) {
+ // TODO(sameeragarwal): This is probably not a great choice.
+ // Ideally, we should have a DENSE_NORMAL_CHOLESKY, that can
+ // take a BlockSparseMatrix as input.
+ options->linear_solver_type = DENSE_QR;
+ msg += "DENSE_SCHUR to DENSE_QR.";
+ } else if (options->linear_solver_type == ITERATIVE_SCHUR) {
+ options->linear_solver_type = CGNR;
+ if (options->preconditioner_type != IDENTITY) {
+ msg += StringPrintf("ITERATIVE_SCHUR with %s preconditioner "
+ "to CGNR with JACOBI preconditioner.",
+ PreconditionerTypeToString(
+ options->preconditioner_type));
+ // CGNR currently only supports the JACOBI preconditioner.
+ options->preconditioner_type = JACOBI;
+ } else {
+ msg += StringPrintf("ITERATIVE_SCHUR with IDENTITY preconditioner "
+ "to CGNR with IDENTITY preconditioner.");
+ }
+ }
+ LOG(WARNING) << msg;
+}
+
+bool SolverImpl::ReorderProgramForSchurTypeLinearSolver(
+ const ProblemImpl::ParameterMap& parameter_map,
+ ParameterBlockOrdering* ordering,
+ Program* program,
+ string* error) {
+ // At this point one of two things is true.
+ //
+ // 1. The user did not specify an ordering - ordering has one
+ // group containined all the parameter blocks.
+
+ // 2. The user specified an ordering, and the first group has
+ // non-zero elements.
+ //
+ // We handle these two cases in turn.
+ if (ordering->NumGroups() == 1) {
+ // If the user supplied an ordering with just one
+ // group, it is equivalent to the user supplying NULL as an
+ // ordering. Ceres is completely free to choose the parameter
+ // block ordering as it sees fit. For Schur type solvers, this
+ // means that the user wishes for Ceres to identify the e_blocks,
+ // which we do by computing a maximal independent set.
+ vector<ParameterBlock*> schur_ordering;
+ const int num_eliminate_blocks = ComputeSchurOrdering(*program,
+ &schur_ordering);
+
+ CHECK_EQ(schur_ordering.size(), program->NumParameterBlocks())
+ << "Congratulations, you found a Ceres bug! Please report this error "
+ << "to the developers.";
+
+ // Update the ordering object.
+ for (int i = 0; i < schur_ordering.size(); ++i) {
+ double* parameter_block = schur_ordering[i]->mutable_user_state();
+ const int group_id = (i < num_eliminate_blocks) ? 0 : 1;
+ ordering->AddElementToGroup(parameter_block, group_id);
+ }
+
+ // Apply the parameter block re-ordering. Technically we could
+ // call ApplyUserOrdering, but this is cheaper and simpler.
+ swap(*program->mutable_parameter_blocks(), schur_ordering);
+ } else {
+ // The user supplied an ordering.
+ if (!ApplyUserOrdering(parameter_map, ordering, program, error)) {
+ return false;
+ }
+ }
+
+ program->SetParameterOffsetsAndIndex();
+
+ const int num_eliminate_blocks =
+ ordering->group_to_elements().begin()->second.size();
+
+ // Schur type solvers also require that their residual blocks be
+ // lexicographically ordered.
+ return LexicographicallyOrderResidualBlocks(num_eliminate_blocks,
+ program,
+ error);
+}
+
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/solver_impl.h b/internal/ceres/solver_impl.h
index c054830..6377e84 100644
--- a/internal/ceres/solver_impl.h
+++ b/internal/ceres/solver_impl.h
@@ -151,6 +151,32 @@
const Program& program,
const ProblemImpl::ParameterMap& parameter_map,
Solver::Summary* summary);
+
+ // If the linear solver is of Schur type, then replace it with the
+ // closest equivalent linear solver. This is done when the user
+ // requested a Schur type solver but the problem structure makes it
+ // impossible to use one.
+ //
+ // If the linear solver is not of Schur type, the function is a
+ // no-op.
+ static void AlternateLinearSolverForSchurTypeLinearSolver(
+ Solver::Options* options);
+
+ // Schur type solvers require that all parameter blocks eliminated
+ // by the Schur eliminator occur before others and the residuals be
+ // sorted in lexicographic order of their parameter blocks.
+ //
+ // If ordering has atleast two groups, then apply the ordering,
+ // otherwise compute a new ordering using a Maximal Independent Set
+ // algorithm and apply it.
+ //
+ // Upon return, ordering contains the parameter block ordering that
+ // was used to order the program.
+ static bool ReorderProgramForSchurTypeLinearSolver(
+ const ProblemImpl::ParameterMap& parameter_map,
+ ParameterBlockOrdering* ordering,
+ Program* program,
+ string* error);
};
} // namespace internal
diff --git a/internal/ceres/solver_impl_test.cc b/internal/ceres/solver_impl_test.cc
index 24860d2..a752eff 100644
--- a/internal/ceres/solver_impl_test.cc
+++ b/internal/ceres/solver_impl_test.cc
@@ -378,11 +378,6 @@
expected_residual_blocks.push_back(residual_blocks[3]);
expected_residual_blocks.push_back(residual_blocks[2]);
- EXPECT_TRUE(SolverImpl::LexicographicallyOrderResidualBlocks(
- 2,
- reduced_program.get(),
- &error));
-
EXPECT_EQ(reduced_program->residual_blocks().size(),
expected_residual_blocks.size());
for (int i = 0; i < expected_residual_blocks.size(); ++i) {
@@ -794,5 +789,62 @@
EXPECT_EQ(summary.final_cost, 1.0 / 2.0);
}
+TEST(SolverImpl, AlternateLinearSolverForSchurTypeLinearSolver) {
+ Solver::Options options;
+
+ options.linear_solver_type = DENSE_QR;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, DENSE_QR);
+
+ options.linear_solver_type = DENSE_NORMAL_CHOLESKY;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, DENSE_NORMAL_CHOLESKY);
+
+ options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
+
+ options.linear_solver_type = CGNR;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, CGNR);
+
+ options.linear_solver_type = DENSE_SCHUR;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, DENSE_QR);
+
+ options.linear_solver_type = SPARSE_SCHUR;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, SPARSE_NORMAL_CHOLESKY);
+
+ options.linear_solver_type = ITERATIVE_SCHUR;
+ options.preconditioner_type = IDENTITY;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, CGNR);
+ EXPECT_EQ(options.preconditioner_type, IDENTITY);
+
+ options.linear_solver_type = ITERATIVE_SCHUR;
+ options.preconditioner_type = JACOBI;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, CGNR);
+ EXPECT_EQ(options.preconditioner_type, JACOBI);
+
+ options.linear_solver_type = ITERATIVE_SCHUR;
+ options.preconditioner_type = SCHUR_JACOBI;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, CGNR);
+ EXPECT_EQ(options.preconditioner_type, JACOBI);
+
+ options.linear_solver_type = ITERATIVE_SCHUR;
+ options.preconditioner_type = CLUSTER_JACOBI;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, CGNR);
+ EXPECT_EQ(options.preconditioner_type, JACOBI);
+
+ options.linear_solver_type = ITERATIVE_SCHUR;
+ options.preconditioner_type = CLUSTER_TRIDIAGONAL;
+ SolverImpl::AlternateLinearSolverForSchurTypeLinearSolver(&options);
+ EXPECT_EQ(options.linear_solver_type, CGNR);
+ EXPECT_EQ(options.preconditioner_type, JACOBI);
+}
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/sparse_normal_cholesky_solver.cc b/internal/ceres/sparse_normal_cholesky_solver.cc
index 7ece965..0cd064b 100644
--- a/internal/ceres/sparse_normal_cholesky_solver.cc
+++ b/internal/ceres/sparse_normal_cholesky_solver.cc
@@ -205,11 +205,11 @@
if (factor_ == NULL) {
if (options_.use_block_amd) {
- factor_ = ss_.BlockAnalyzeCholesky(lhs.get(),
+ factor_ = ss_.BlockAnalyzeCholesky(&lhs,
A->col_blocks(),
A->row_blocks());
} else {
- factor_ = ss_.AnalyzeCholesky(lhs.get());
+ factor_ = ss_.AnalyzeCholesky(&lhs);
}
}
event_logger.AddEvent("Analysis");