diff options
Diffstat (limited to 'extern/ceres/internal/ceres/solver.cc')
-rw-r--r-- | extern/ceres/internal/ceres/solver.cc | 398 |
1 files changed, 188 insertions, 210 deletions
diff --git a/extern/ceres/internal/ceres/solver.cc b/extern/ceres/internal/ceres/solver.cc index 8411350986a..861d8d30485 100644 --- a/extern/ceres/internal/ceres/solver.cc +++ b/extern/ceres/internal/ceres/solver.cc @@ -32,8 +32,14 @@ #include "ceres/solver.h" #include <algorithm> -#include <sstream> // NOLINT +#include <memory> +#include <sstream> // NOLINT #include <vector> + +#include "ceres/casts.h" +#include "ceres/context.h" +#include "ceres/context_impl.h" +#include "ceres/detect_structure.h" #include "ceres/gradient_checking_cost_function.h" #include "ceres/internal/port.h" #include "ceres/parameter_block_ordering.h" @@ -41,6 +47,7 @@ #include "ceres/problem.h" #include "ceres/problem_impl.h" #include "ceres/program.h" +#include "ceres/schur_templates.h" #include "ceres/solver_utils.h" #include "ceres/stringprintf.h" #include "ceres/types.h" @@ -52,6 +59,8 @@ namespace { using std::map; using std::string; using std::vector; +using internal::StringAppendF; +using internal::StringPrintf; #define OPTION_OP(x, y, OP) \ if (!(options.x OP y)) { \ @@ -91,7 +100,6 @@ bool CommonOptionsAreValid(const Solver::Options& options, string* error) { OPTION_GE(gradient_tolerance, 0.0); OPTION_GE(parameter_tolerance, 0.0); OPTION_GT(num_threads, 0); - OPTION_GT(num_linear_solver_threads, 0); if (options.check_gradients) { OPTION_GT(gradient_check_relative_precision, 0.0); OPTION_GT(gradient_check_numeric_derivative_relative_step_size, 0.0); @@ -132,101 +140,51 @@ bool TrustRegionOptionsAreValid(const Solver::Options& options, string* error) { return false; } - if (options.preconditioner_type == CLUSTER_JACOBI && - options.sparse_linear_algebra_library_type != SUITE_SPARSE) { - *error = "CLUSTER_JACOBI requires " - "Solver::Options::sparse_linear_algebra_library_type to be " - "SUITE_SPARSE"; - return false; - } - - if (options.preconditioner_type == CLUSTER_TRIDIAGONAL && - options.sparse_linear_algebra_library_type != SUITE_SPARSE) { - *error = "CLUSTER_TRIDIAGONAL requires " - "Solver::Options::sparse_linear_algebra_library_type to be " - "SUITE_SPARSE"; - return false; - } - -#ifdef CERES_NO_LAPACK - if (options.dense_linear_algebra_library_type == LAPACK) { - if (options.linear_solver_type == DENSE_NORMAL_CHOLESKY) { - *error = "Can't use DENSE_NORMAL_CHOLESKY with LAPACK because " - "LAPACK was not enabled when Ceres was built."; - return false; - } else if (options.linear_solver_type == DENSE_QR) { - *error = "Can't use DENSE_QR with LAPACK because " - "LAPACK was not enabled when Ceres was built."; - return false; - } else if (options.linear_solver_type == DENSE_SCHUR) { - *error = "Can't use DENSE_SCHUR with LAPACK because " - "LAPACK was not enabled when Ceres was built."; - return false; - } - } -#endif - -#ifdef CERES_NO_SUITESPARSE - if (options.sparse_linear_algebra_library_type == SUITE_SPARSE) { - if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY) { - *error = "Can't use SPARSE_NORMAL_CHOLESKY with SUITESPARSE because " - "SuiteSparse was not enabled when Ceres was built."; - return false; - } else if (options.linear_solver_type == SPARSE_SCHUR) { - *error = "Can't use SPARSE_SCHUR with SUITESPARSE because " - "SuiteSparse was not enabled when Ceres was built."; - return false; - } else if (options.preconditioner_type == CLUSTER_JACOBI) { - *error = "CLUSTER_JACOBI preconditioner not supported. " - "SuiteSparse was not enabled when Ceres was built."; - return false; - } else if (options.preconditioner_type == CLUSTER_TRIDIAGONAL) { - *error = "CLUSTER_TRIDIAGONAL preconditioner not supported. " - "SuiteSparse was not enabled when Ceres was built."; + if (options.dense_linear_algebra_library_type == LAPACK && + !IsDenseLinearAlgebraLibraryTypeAvailable(LAPACK) && + (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 = LAPACK " + "because LAPACK was not enabled when Ceres was built.", + LinearSolverTypeToString(options.linear_solver_type)); return false; - } } -#endif -#ifdef CERES_NO_CXSPARSE - if (options.sparse_linear_algebra_library_type == CX_SPARSE) { - if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY) { - *error = "Can't use SPARSE_NORMAL_CHOLESKY with CX_SPARSE because " - "CXSparse was not enabled when Ceres was built."; - return false; - } else if (options.linear_solver_type == SPARSE_SCHUR) { - *error = "Can't use SPARSE_SCHUR with CX_SPARSE because " - "CXSparse was not enabled when Ceres was built."; - 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); } - } -#endif -#ifndef CERES_USE_EIGEN_SPARSE - if (options.sparse_linear_algebra_library_type == EIGEN_SPARSE) { - if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY) { - *error = "Can't use SPARSE_NORMAL_CHOLESKY with EIGEN_SPARSE because " - "Eigen's sparse linear algebra was not enabled when Ceres was " - "built."; - return false; - } else if (options.linear_solver_type == SPARSE_SCHUR) { - *error = "Can't use SPARSE_SCHUR with EIGEN_SPARSE because " - "Eigen's sparse linear algebra was not enabled when Ceres was " - "built."; - return false; - } - } -#endif - - if (options.sparse_linear_algebra_library_type == NO_SPARSE) { - if (options.linear_solver_type == SPARSE_NORMAL_CHOLESKY) { - *error = "Can't use SPARSE_NORMAL_CHOLESKY as " - "sparse_linear_algebra_library_type is NO_SPARSE."; - return false; - } else if (options.linear_solver_type == SPARSE_SCHUR) { - *error = "Can't use SPARSE_SCHUR as " - "sparse_linear_algebra_library_type is NO_SPARSE."; - return false; + 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; + } else 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; + } } } @@ -240,16 +198,32 @@ bool TrustRegionOptionsAreValid(const Solver::Options& options, string* error) { } } - if (options.trust_region_minimizer_iterations_to_dump.size() > 0 && + if (!options.trust_region_minimizer_iterations_to_dump.empty() && options.trust_region_problem_dump_format_type != CONSOLE && options.trust_region_problem_dump_directory.empty()) { *error = "Solver::Options::trust_region_problem_dump_directory is empty."; return false; } - if (options.dynamic_sparsity && - options.linear_solver_type != SPARSE_NORMAL_CHOLESKY) { - *error = "Dynamic sparsity is only supported with SPARSE_NORMAL_CHOLESKY."; + 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; } @@ -264,7 +238,8 @@ bool LineSearchOptionsAreValid(const Solver::Options& options, string* error) { OPTION_LT_OPTION(max_line_search_step_contraction, min_line_search_step_contraction); OPTION_LE(min_line_search_step_contraction, 1.0); - OPTION_GT(max_num_line_search_step_size_iterations, 0); + OPTION_GE(max_num_line_search_step_size_iterations, + (options.minimizer_type == ceres::TRUST_REGION ? 0 : 1)); OPTION_GT(line_search_sufficient_function_decrease, 0.0); OPTION_LT_OPTION(line_search_sufficient_function_decrease, line_search_sufficient_curvature_decrease); @@ -310,13 +285,13 @@ bool LineSearchOptionsAreValid(const Solver::Options& options, string* error) { #undef OPTION_LT_OPTION void StringifyOrdering(const vector<int>& ordering, string* report) { - if (ordering.size() == 0) { + if (ordering.empty()) { internal::StringAppendF(report, "AUTOMATIC"); return; } for (int i = 0; i < ordering.size() - 1; ++i) { - internal::StringAppendF(report, "%d, ", ordering[i]); + internal::StringAppendF(report, "%d,", ordering[i]); } internal::StringAppendF(report, "%d", ordering.back()); } @@ -364,7 +339,6 @@ void PreSolveSummarize(const Solver::Options& options, summary->max_lbfgs_rank = options.max_lbfgs_rank; summary->minimizer_type = options.minimizer_type; summary->nonlinear_conjugate_gradient_type = options.nonlinear_conjugate_gradient_type; // NOLINT - summary->num_linear_solver_threads_given = options.num_linear_solver_threads; // NOLINT summary->num_threads_given = options.num_threads; summary->preconditioner_type_given = options.preconditioner_type; summary->sparse_linear_algebra_library_type = options.sparse_linear_algebra_library_type; // NOLINT @@ -380,10 +354,9 @@ void PostSolveSummarize(const internal::PreprocessedProblem& pp, &(summary->inner_iteration_ordering_used)); summary->inner_iterations_used = pp.inner_iteration_minimizer.get() != NULL; // NOLINT - summary->linear_solver_type_used = pp.options.linear_solver_type; - summary->num_linear_solver_threads_used = pp.options.num_linear_solver_threads; // NOLINT + summary->linear_solver_type_used = pp.linear_solver_options.type; summary->num_threads_used = pp.options.num_threads; - summary->preconditioner_type_used = pp.options.preconditioner_type; // NOLINT + summary->preconditioner_type_used = pp.options.preconditioner_type; internal::SetSummaryFinalCost(summary); @@ -391,36 +364,47 @@ void PostSolveSummarize(const internal::PreprocessedProblem& pp, SummarizeReducedProgram(*pp.reduced_program, summary); } + using internal::CallStatistics; + // It is possible that no evaluator was created. This would be the // case if the preprocessor failed, or if the reduced problem did // not contain any parameter blocks. Thus, only extract the // evaluator statistics if one exists. if (pp.evaluator.get() != NULL) { - const map<string, double>& evaluator_time_statistics = - pp.evaluator->TimeStatistics(); - summary->residual_evaluation_time_in_seconds = - FindWithDefault(evaluator_time_statistics, "Evaluator::Residual", 0.0); - summary->jacobian_evaluation_time_in_seconds = - FindWithDefault(evaluator_time_statistics, "Evaluator::Jacobian", 0.0); + const map<string, CallStatistics>& evaluator_statistics = + pp.evaluator->Statistics(); + { + const CallStatistics& call_stats = FindWithDefault( + evaluator_statistics, "Evaluator::Residual", CallStatistics()); + + summary->residual_evaluation_time_in_seconds = call_stats.time; + summary->num_residual_evaluations = call_stats.calls; + } + { + const CallStatistics& call_stats = FindWithDefault( + evaluator_statistics, "Evaluator::Jacobian", CallStatistics()); + + summary->jacobian_evaluation_time_in_seconds = call_stats.time; + summary->num_jacobian_evaluations = call_stats.calls; + } } // Again, like the evaluator, there may or may not be a linear // solver from which we can extract run time statistics. In // particular the line search solver does not use a linear solver. if (pp.linear_solver.get() != NULL) { - const map<string, double>& linear_solver_time_statistics = - pp.linear_solver->TimeStatistics(); - summary->linear_solver_time_in_seconds = - FindWithDefault(linear_solver_time_statistics, - "LinearSolver::Solve", - 0.0); + const map<string, CallStatistics>& linear_solver_statistics = + pp.linear_solver->Statistics(); + const CallStatistics& call_stats = FindWithDefault( + linear_solver_statistics, "LinearSolver::Solve", CallStatistics()); + summary->num_linear_solves = call_stats.calls; + summary->linear_solver_time_in_seconds = call_stats.time; } } void Minimize(internal::PreprocessedProblem* pp, Solver::Summary* summary) { using internal::Program; - using internal::scoped_ptr; using internal::Minimizer; Program* program = pp->reduced_program.get(); @@ -434,16 +418,36 @@ void Minimize(internal::PreprocessedProblem* pp, return; } - scoped_ptr<Minimizer> minimizer( + const Vector original_reduced_parameters = pp->reduced_parameters; + std::unique_ptr<Minimizer> minimizer( Minimizer::Create(pp->options.minimizer_type)); minimizer->Minimize(pp->minimizer_options, pp->reduced_parameters.data(), summary); - if (summary->IsSolutionUsable()) { - program->StateVectorToParameterBlocks(pp->reduced_parameters.data()); - program->CopyParameterBlockStateToUserState(); - } + program->StateVectorToParameterBlocks( + summary->IsSolutionUsable() + ? pp->reduced_parameters.data() + : original_reduced_parameters.data()); + program->CopyParameterBlockStateToUserState(); +} + +std::string SchurStructureToString(const int row_block_size, + const int e_block_size, + const int f_block_size) { + const std::string row = + (row_block_size == Eigen::Dynamic) + ? "d" : internal::StringPrintf("%d", row_block_size); + + const std::string e = + (e_block_size == Eigen::Dynamic) + ? "d" : internal::StringPrintf("%d", e_block_size); + + const std::string f = + (f_block_size == Eigen::Dynamic) + ? "d" : internal::StringPrintf("%d", f_block_size); + + return internal::StringPrintf("%s,%s,%s", row.c_str(), e.c_str(), f.c_str()); } } // namespace @@ -475,11 +479,10 @@ void Solver::Solve(const Solver::Options& options, using internal::Preprocessor; using internal::ProblemImpl; using internal::Program; - using internal::scoped_ptr; using internal::WallTimeInSeconds; - CHECK_NOTNULL(problem); - CHECK_NOTNULL(summary); + CHECK(problem != nullptr); + CHECK(summary != nullptr); double start_time = WallTimeInSeconds(); *summary = Summary(); @@ -488,18 +491,14 @@ void Solver::Solve(const Solver::Options& options, return; } - ProblemImpl* problem_impl = problem->problem_impl_.get(); + ProblemImpl* problem_impl = problem->impl_.get(); Program* program = problem_impl->mutable_program(); PreSolveSummarize(options, problem_impl, summary); - // Make sure that all the parameter blocks states are set to the - // values provided by the user. - program->SetParameterBlockStatePtrsToUserStatePtrs(); - // If gradient_checking is enabled, wrap all cost functions in a // gradient checker and install a callback that terminates if any gradient // error is detected. - scoped_ptr<internal::ProblemImpl> gradient_checking_problem; + std::unique_ptr<internal::ProblemImpl> gradient_checking_problem; internal::GradientCheckingIterationCallback gradient_checking_callback; Solver::Options modified_options = options; if (options.check_gradients) { @@ -514,10 +513,46 @@ void Solver::Solve(const Solver::Options& options, program = problem_impl->mutable_program(); } - scoped_ptr<Preprocessor> preprocessor( + // Make sure that all the parameter blocks states are set to the + // values provided by the user. + program->SetParameterBlockStatePtrsToUserStatePtrs(); + + // The main thread also does work so we only need to launch num_threads - 1. + problem_impl->context()->EnsureMinimumThreads(options.num_threads - 1); + + std::unique_ptr<Preprocessor> preprocessor( Preprocessor::Create(modified_options.minimizer_type)); PreprocessedProblem pp; + const bool status = preprocessor->Preprocess(modified_options, problem_impl, &pp); + + // We check the linear_solver_options.type rather than + // modified_options.linear_solver_type because, depending on the + // lack of a Schur structure, the preprocessor may change the linear + // solver type. + if (IsSchurType(pp.linear_solver_options.type)) { + // TODO(sameeragarwal): We can likely eliminate the duplicate call + // to DetectStructure here and inside the linear solver, by + // calling this in the preprocessor. + int row_block_size; + int e_block_size; + int f_block_size; + DetectStructure(*static_cast<internal::BlockSparseMatrix*>( + pp.minimizer_options.jacobian.get()) + ->block_structure(), + pp.linear_solver_options.elimination_groups[0], + &row_block_size, + &e_block_size, + &f_block_size); + summary->schur_structure_given = + SchurStructureToString(row_block_size, e_block_size, f_block_size); + internal::GetBestSchurTemplateSpecialization(&row_block_size, + &e_block_size, + &f_block_size); + summary->schur_structure_used = + SchurStructureToString(row_block_size, e_block_size, f_block_size); + } + summary->fixed_cost = pp.fixed_cost; summary->preprocessor_time_in_seconds = WallTimeInSeconds() - start_time; @@ -531,7 +566,7 @@ void Solver::Solve(const Solver::Options& options, } const double postprocessor_start_time = WallTimeInSeconds(); - problem_impl = problem->problem_impl_.get(); + problem_impl = problem->impl_.get(); program = problem_impl->mutable_program(); // On exit, ensure that the parameter blocks again point at the user // provided values and the parameter blocks are numbered according @@ -559,66 +594,6 @@ void Solve(const Solver::Options& options, solver.Solve(options, problem, summary); } -Solver::Summary::Summary() - // Invalid values for most fields, to ensure that we are not - // accidentally reporting default values. - : minimizer_type(TRUST_REGION), - termination_type(FAILURE), - message("ceres::Solve was not called."), - initial_cost(-1.0), - final_cost(-1.0), - fixed_cost(-1.0), - num_successful_steps(-1), - num_unsuccessful_steps(-1), - num_inner_iteration_steps(-1), - num_line_search_steps(-1), - preprocessor_time_in_seconds(-1.0), - minimizer_time_in_seconds(-1.0), - postprocessor_time_in_seconds(-1.0), - total_time_in_seconds(-1.0), - linear_solver_time_in_seconds(-1.0), - residual_evaluation_time_in_seconds(-1.0), - jacobian_evaluation_time_in_seconds(-1.0), - inner_iteration_time_in_seconds(-1.0), - line_search_cost_evaluation_time_in_seconds(-1.0), - line_search_gradient_evaluation_time_in_seconds(-1.0), - line_search_polynomial_minimization_time_in_seconds(-1.0), - line_search_total_time_in_seconds(-1.0), - num_parameter_blocks(-1), - num_parameters(-1), - num_effective_parameters(-1), - num_residual_blocks(-1), - num_residuals(-1), - num_parameter_blocks_reduced(-1), - num_parameters_reduced(-1), - num_effective_parameters_reduced(-1), - num_residual_blocks_reduced(-1), - num_residuals_reduced(-1), - is_constrained(false), - num_threads_given(-1), - num_threads_used(-1), - num_linear_solver_threads_given(-1), - num_linear_solver_threads_used(-1), - linear_solver_type_given(SPARSE_NORMAL_CHOLESKY), - linear_solver_type_used(SPARSE_NORMAL_CHOLESKY), - inner_iterations_given(false), - inner_iterations_used(false), - preconditioner_type_given(IDENTITY), - preconditioner_type_used(IDENTITY), - visibility_clustering_type(CANONICAL_VIEWS), - trust_region_strategy_type(LEVENBERG_MARQUARDT), - dense_linear_algebra_library_type(EIGEN), - sparse_linear_algebra_library_type(SUITE_SPARSE), - line_search_direction_type(LBFGS), - line_search_type(ARMIJO), - line_search_interpolation_type(BISECTION), - nonlinear_conjugate_gradient_type(FLETCHER_REEVES), - max_lbfgs_rank(-1) { -} - -using internal::StringAppendF; -using internal::StringPrintf; - string Solver::Summary::BriefReport() const { return StringPrintf("Ceres Solver Report: " "Iterations: %d, " @@ -647,7 +622,7 @@ string Solver::Summary::FullReport() const { } StringAppendF(&report, "Residual blocks % 25d% 25d\n", num_residual_blocks, num_residual_blocks_reduced); - StringAppendF(&report, "Residual % 25d% 25d\n", + StringAppendF(&report, "Residuals % 25d% 25d\n", num_residuals, num_residuals_reduced); if (minimizer_type == TRUST_REGION) { @@ -708,9 +683,6 @@ string Solver::Summary::FullReport() const { } StringAppendF(&report, "Threads % 25d% 25d\n", num_threads_given, num_threads_used); - StringAppendF(&report, "Linear solver threads % 23d% 25d\n", - num_linear_solver_threads_given, - num_linear_solver_threads_used); string given; StringifyOrdering(linear_solver_ordering_given, &given); @@ -720,6 +692,12 @@ string Solver::Summary::FullReport() const { "Linear solver ordering %22s %24s\n", given.c_str(), used.c_str()); + if (IsSchurType(linear_solver_type_used)) { + StringAppendF(&report, + "Schur structure %22s %24s\n", + schur_structure_given.c_str(), + schur_structure_used.c_str()); + } if (inner_iterations_given) { StringAppendF(&report, @@ -808,44 +786,44 @@ string Solver::Summary::FullReport() const { } StringAppendF(&report, "\nTime (in seconds):\n"); - StringAppendF(&report, "Preprocessor %25.4f\n", + StringAppendF(&report, "Preprocessor %25.6f\n", preprocessor_time_in_seconds); - StringAppendF(&report, "\n Residual evaluation %23.4f\n", - residual_evaluation_time_in_seconds); + StringAppendF(&report, "\n Residual only evaluation %18.6f (%d)\n", + residual_evaluation_time_in_seconds, num_residual_evaluations); if (line_search_used) { - StringAppendF(&report, " Line search cost evaluation %10.4f\n", + StringAppendF(&report, " Line search cost evaluation %10.6f\n", line_search_cost_evaluation_time_in_seconds); } - StringAppendF(&report, " Jacobian evaluation %23.4f\n", - jacobian_evaluation_time_in_seconds); + StringAppendF(&report, " Jacobian & residual evaluation %12.6f (%d)\n", + jacobian_evaluation_time_in_seconds, num_jacobian_evaluations); if (line_search_used) { - StringAppendF(&report, " Line search gradient evaluation %6.4f\n", + StringAppendF(&report, " Line search gradient evaluation %6.6f\n", line_search_gradient_evaluation_time_in_seconds); } if (minimizer_type == TRUST_REGION) { - StringAppendF(&report, " Linear solver %23.4f\n", - linear_solver_time_in_seconds); + StringAppendF(&report, " Linear solver %23.6f (%d)\n", + linear_solver_time_in_seconds, num_linear_solves); } if (inner_iterations_used) { - StringAppendF(&report, " Inner iterations %23.4f\n", + StringAppendF(&report, " Inner iterations %23.6f\n", inner_iteration_time_in_seconds); } if (line_search_used) { - StringAppendF(&report, " Line search polynomial minimization %.4f\n", + StringAppendF(&report, " Line search polynomial minimization %.6f\n", line_search_polynomial_minimization_time_in_seconds); } - StringAppendF(&report, "Minimizer %25.4f\n\n", + StringAppendF(&report, "Minimizer %25.6f\n\n", minimizer_time_in_seconds); - StringAppendF(&report, "Postprocessor %24.4f\n", + StringAppendF(&report, "Postprocessor %24.6f\n", postprocessor_time_in_seconds); - StringAppendF(&report, "Total %25.4f\n\n", + StringAppendF(&report, "Total %25.6f\n\n", total_time_in_seconds); StringAppendF(&report, "Termination: %25s (%s)\n", |