diff --git a/examples/bundle_adjuster.cc b/examples/bundle_adjuster.cc index f5c1ac105..e5f9100a6 100644 --- a/examples/bundle_adjuster.cc +++ b/examples/bundle_adjuster.cc @@ -66,27 +66,30 @@ #include "snavely_reprojection_error.h" DEFINE_string(input, "", "Input File name"); +DEFINE_string(trust_region_strategy, "levenberg_marquardt", + "Options are: levenberg_marquardt, dogleg"); +DEFINE_string(linear_solver, "sparse_schur", "Options are: " + "sparse_schur, dense_schur, iterative_schur, sparse_normal_cholesky, " + "dense_qr, dense_normal_cholesky and cgnr"); +DEFINE_string(preconditioner, "jacobi", "Options are: " + "identity, jacobi, schur_jacobi, cluster_jacobi, " + "cluster_tridiagonal"); +DEFINE_string(sparse_linear_algebra_library, "suite_sparse", + "Options are: suite_sparse and cx_sparse"); +DEFINE_string(ordering, "schur", "Options are: schur, user, natural"); +DEFINE_string(dogleg, "traditional_dogleg", "Options are: traditional_dogleg," + "subspace_dogleg"); + DEFINE_bool(use_quaternions, false, "If true, uses quaternions to represent " "rotations. If false, angle axis is used"); DEFINE_bool(use_local_parameterization, false, "For quaternions, use a local " "parameterization."); DEFINE_bool(robustify, false, "Use a robust loss function"); -DEFINE_string(trust_region_strategy, "lm", "Options are: lm, dogleg"); DEFINE_double(eta, 1e-2, "Default value for eta. Eta determines the " "accuracy of each linear solve of the truncated newton step. " "Changing this parameter can affect solve performance "); -DEFINE_string(solver_type, "sparse_schur", "Options are: " - "sparse_schur, dense_schur, iterative_schur, sparse_cholesky, " - "dense_qr, dense_cholesky and conjugate_gradients"); -DEFINE_string(preconditioner_type, "jacobi", "Options are: " - "identity, jacobi, schur_jacobi, cluster_jacobi, " - "cluster_tridiagonal"); -DEFINE_string(sparse_linear_algebra_library, "suitesparse", - "Options are: suitesparse and cxsparse"); -DEFINE_string(ordering_type, "schur", "Options are: schur, user, natural"); -DEFINE_string(dogleg_type, "traditional", "Options are: traditional, subspace"); DEFINE_bool(use_block_amd, true, "Use a block oriented fill reducing " "ordering."); @@ -111,88 +114,19 @@ namespace ceres { namespace examples { void SetLinearSolver(Solver::Options* options) { - if (FLAGS_solver_type == "sparse_schur") { - options->linear_solver_type = ceres::SPARSE_SCHUR; - } else if (FLAGS_solver_type == "dense_schur") { - options->linear_solver_type = ceres::DENSE_SCHUR; - } else if (FLAGS_solver_type == "iterative_schur") { - options->linear_solver_type = ceres::ITERATIVE_SCHUR; - } else if (FLAGS_solver_type == "sparse_cholesky") { - options->linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; - } else if (FLAGS_solver_type == "cgnr") { - options->linear_solver_type = ceres::CGNR; - } else if (FLAGS_solver_type == "dense_qr") { - // DENSE_QR is included here for completeness, but actually using - // this option is a bad idea due to the amount of memory needed - // to store even the smallest of the bundle adjustment jacobian - // arrays - options->linear_solver_type = ceres::DENSE_QR; - } else if (FLAGS_solver_type == "dense_cholesky") { - // DENSE_NORMAL_CHOLESKY is included here for completeness, but - // actually using this option is a bad idea due to the amount of - // memory needed to store even the smallest of the bundle - // adjustment jacobian arrays - options->linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - } else { - LOG(FATAL) << "Unknown ceres solver type: " - << FLAGS_solver_type; - } - - if (options->linear_solver_type == ceres::CGNR) { - options->linear_solver_min_num_iterations = 5; - if (FLAGS_preconditioner_type == "identity") { - options->preconditioner_type = ceres::IDENTITY; - } else if (FLAGS_preconditioner_type == "jacobi") { - options->preconditioner_type = ceres::JACOBI; - } else { - LOG(FATAL) << "For CGNR, only identity and jacobian " - << "preconditioners are supported. Got: " - << FLAGS_preconditioner_type; - } - } - - if (options->linear_solver_type == ceres::ITERATIVE_SCHUR) { - options->linear_solver_min_num_iterations = 5; - if (FLAGS_preconditioner_type == "identity") { - options->preconditioner_type = ceres::IDENTITY; - } else if (FLAGS_preconditioner_type == "jacobi") { - options->preconditioner_type = ceres::JACOBI; - } else if (FLAGS_preconditioner_type == "schur_jacobi") { - options->preconditioner_type = ceres::SCHUR_JACOBI; - } else if (FLAGS_preconditioner_type == "cluster_jacobi") { - options->preconditioner_type = ceres::CLUSTER_JACOBI; - } else if (FLAGS_preconditioner_type == "cluster_tridiagonal") { - options->preconditioner_type = ceres::CLUSTER_TRIDIAGONAL; - } else { - LOG(FATAL) << "Unknown ceres preconditioner type: " - << FLAGS_preconditioner_type; - } - } - - if (FLAGS_sparse_linear_algebra_library == "suitesparse") { - options->sparse_linear_algebra_library = SUITE_SPARSE; - } else if (FLAGS_sparse_linear_algebra_library == "cxsparse") { - options->sparse_linear_algebra_library = CX_SPARSE; - } else { - LOG(FATAL) << "Unknown sparse linear algebra library type."; - } - + CHECK(StringToLinearSolverType(FLAGS_linear_solver, + &options->linear_solver_type)); + CHECK(StringToPreconditionerType(FLAGS_preconditioner, + &options->preconditioner_type)); + CHECK(StringToSparseLinearAlgebraLibraryType( + FLAGS_sparse_linear_algebra_library, + &options->sparse_linear_algebra_library)); options->num_linear_solver_threads = FLAGS_num_threads; } void SetOrdering(BALProblem* bal_problem, Solver::Options* options) { options->use_block_amd = FLAGS_use_block_amd; - - // Only non-Schur solvers support the natural ordering for this - // problem. - if (FLAGS_ordering_type == "natural") { - if (options->linear_solver_type == SPARSE_SCHUR || - options->linear_solver_type == DENSE_SCHUR || - options->linear_solver_type == ITERATIVE_SCHUR) { - LOG(FATAL) << "Natural ordering with Schur type solver does not work."; - } - return; - } + CHECK(StringToOrderingType(FLAGS_ordering, &options->ordering_type)); // Bundle adjustment problems have a sparsity structure that makes // them amenable to more specialized and much more efficient @@ -208,8 +142,7 @@ void SetOrdering(BALProblem* bal_problem, Solver::Options* options) { // the right ParameterBlock ordering, or by manually specifying a // suitable ordering vector and defining // Options::num_eliminate_blocks. - if (FLAGS_ordering_type == "schur") { - options->ordering_type = ceres::SCHUR; + if (options->ordering_type == ceres::SCHUR) { return; } @@ -248,22 +181,9 @@ void SetMinimizerOptions(Solver::Options* options) { options->eta = FLAGS_eta; options->max_solver_time_in_seconds = FLAGS_max_solver_time; options->use_nonmonotonic_steps = FLAGS_nonmonotonic_steps; - if (FLAGS_trust_region_strategy == "lm") { - options->trust_region_strategy_type = LEVENBERG_MARQUARDT; - } else if (FLAGS_trust_region_strategy == "dogleg") { - options->trust_region_strategy_type = DOGLEG; - } else { - LOG(FATAL) << "Unknown trust region strategy: " - << FLAGS_trust_region_strategy; - } - if (FLAGS_dogleg_type == "traditional") { - options->dogleg_type = TRADITIONAL_DOGLEG; - } else if (FLAGS_dogleg_type == "subspace") { - options->dogleg_type = SUBSPACE_DOGLEG; - } else { - LOG(FATAL) << "Unknown dogleg type: " - << FLAGS_dogleg_type; - } + CHECK(StringToTrustRegionStrategyType(FLAGS_trust_region_strategy, + &options->trust_region_strategy_type)); + CHECK(StringToDoglegType(FLAGS_dogleg, &options->dogleg_type)); } void SetSolverOptionsFromFlags(BALProblem* bal_problem, diff --git a/examples/nist.cc b/examples/nist.cc index 2c1a4c5ea..0f5951f2b 100644 --- a/examples/nist.cc +++ b/examples/nist.cc @@ -51,6 +51,18 @@ DEFINE_string(nist_data_dir, "", "Directory containing the NIST non-linear" "regression examples"); +DEFINE_string(trust_region_strategy, "levenberg_marquardt", + "Options are: levenberg_marquardt, dogleg"); +DEFINE_string(dogleg, "traditional_dogleg", + "Options are: traditional_dogleg, subspace_dogleg"); +DEFINE_string(linear_solver, "dense_qr", "Options are: " + "sparse_cholesky, dense_qr, dense_normal_cholesky and" + "cgnr"); +DEFINE_string(preconditioner, "jacobi", "Options are: " + "identity, jacobi"); +DEFINE_int32(num_iterations, 10000, "Number of iterations"); +DEFINE_bool(nonmonotonic_steps, false, "Trust region algorithm can use" + " nonmonotic steps"); using Eigen::Dynamic; using Eigen::RowMajor; @@ -120,8 +132,13 @@ class NISTProblem { final_parameters_(0, parameter_id) = std::atof(pieces[2 + kNumTries].c_str()); } + // Certfied cost + SkipLines(ifs, 1); + GetAndSplitLine(ifs, &pieces); + certified_cost_ = std::atof(pieces[4].c_str()) / 2.0; + // Read the observations. - SkipLines(ifs, 20 - kNumParameters); + SkipLines(ifs, 18 - kNumParameters); for (int i = 0; i < kNumObservations; ++i) { GetAndSplitLine(ifs, &pieces); // Response. @@ -145,12 +162,14 @@ class NISTProblem { int response_size() const { return response_.cols(); } int num_parameters() const { return initial_parameters_.cols(); } int num_starts() const { return initial_parameters_.rows(); } + double certified_cost() const { return certified_cost_; } private: Matrix predictor_; Matrix response_; Matrix initial_parameters_; Matrix final_parameters_; + double certified_cost_; }; #define NIST_BEGIN(CostFunctionName) \ @@ -340,27 +359,7 @@ int RegressionDriver(const std::string& filename, Solve(options, &problem, &summaries[start]); } - // Ugly hack to get the objective function value at the certified - // optimal parameter values. So we build the problem and call Ceres - // with zero iterations to get the initial_cost. - { - Matrix initial_parameters = nist_problem.final_parameters(); - ceres::Problem problem; - for (int i = 0; i < nist_problem.num_observations(); ++i) { - problem.AddResidualBlock( - new ceres::AutoDiffCostFunction( - new Model(predictor.data() + nist_problem.predictor_size() * i, - response.data() + nist_problem.response_size() * i)), - NULL, - initial_parameters.data()); - } - - ceres::Solver::Options options; - options.max_num_iterations = 0; - Solve(options, &problem, &summaries[nist_problem.num_starts()]); - } - - double certified_cost = summaries[nist_problem.num_starts()].initial_cost; + const double certified_cost = nist_problem.certified_cost(); int num_success = 0; const int kMinNumMatchingDigits = 4; @@ -380,6 +379,7 @@ int RegressionDriver(const std::string& filename, std::cerr << "FAILURE"; std::cerr << " summary: " << summary.BriefReport() + << " Certified cost: " << certified_cost << std::endl; } else { ++num_success; @@ -389,7 +389,31 @@ int RegressionDriver(const std::string& filename, return num_success; } -void SolveNISTProblems(const ceres::Solver::Options& options) { +void SetMinimizerOptions(ceres::Solver::Options* options) { + CHECK(ceres::StringToLinearSolverType(FLAGS_linear_solver, + &options->linear_solver_type)); + CHECK(ceres::StringToPreconditionerType(FLAGS_preconditioner, + &options->preconditioner_type)); + CHECK(ceres::StringToTrustRegionStrategyType( + FLAGS_trust_region_strategy, + &options->trust_region_strategy_type)); + CHECK(ceres::StringToDoglegType(FLAGS_dogleg, &options->dogleg_type)); + + options->max_num_iterations = FLAGS_num_iterations; + options->use_nonmonotonic_steps = FLAGS_nonmonotonic_steps; + options->function_tolerance = 1e-18; + options->gradient_tolerance = 1e-18; + options->parameter_tolerance = 1e-18; +} + +void SolveNISTProblems() { + if (FLAGS_nist_data_dir.empty()) { + LOG(FATAL) << "Must specify the directory containing the NIST problems"; + } + + ceres::Solver::Options options; + SetMinimizerOptions(&options); + std::cerr << "Lower Difficulty\n"; int easy_success = 0; easy_success += RegressionDriver("Misra1a.dat", options); @@ -437,50 +461,6 @@ void SolveNISTProblems(const ceres::Solver::Options& options) { int main(int argc, char** argv) { google::ParseCommandLineFlags(&argc, &argv, true); google::InitGoogleLogging(argv[0]); - - // TODO(sameeragarwal): Test more combinations of non-linear and - // linear solvers. - ceres::Solver::Options options; - options.max_num_iterations = 10000; - options.function_tolerance *= 1e-10; - options.gradient_tolerance *= 1e-10; - options.parameter_tolerance *= 1e-10; - - options.linear_solver_type = ceres::DENSE_QR; - options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; - std::cerr << "Levenberg-Marquardt - DENSE_QR\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::TRADITIONAL_DOGLEG; - std::cerr << "\n\nTraditional Dogleg - DENSE_QR\n\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::SUBSPACE_DOGLEG; - std::cerr << "\n\nSubspace Dogleg - DENSE_QR\n\n"; - SolveNISTProblems(options); - - options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; - std::cerr << "Levenberg-Marquardt - DENSE_NORMAL_CHOLESKY\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::TRADITIONAL_DOGLEG; - std::cerr << "\n\nTraditional Dogleg - DENSE_NORMAL_CHOLESKY\n\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::SUBSPACE_DOGLEG; - std::cerr << "\n\nSubspace Dogleg - DENSE_NORMAL_CHOLESKY\n\n"; - SolveNISTProblems(options); - - options.linear_solver_type = ceres::CGNR; - options.preconditioner_type = ceres::JACOBI; - options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; - std::cerr << "Levenberg-Marquardt - CGNR + JACOBI\n"; - SolveNISTProblems(options); - + SolveNISTProblems(); return 0; }; diff --git a/include/ceres/types.h b/include/ceres/types.h index 3980885b5..2c0b7bddf 100644 --- a/include/ceres/types.h +++ b/include/ceres/types.h @@ -37,6 +37,8 @@ #ifndef CERES_PUBLIC_TYPES_H_ #define CERES_PUBLIC_TYPES_H_ +#include "ceres/internal/port.h" + namespace ceres { // Basic integer types. These typedefs are in the Ceres namespace to avoid @@ -297,16 +299,31 @@ enum DimensionType { }; const char* LinearSolverTypeToString(LinearSolverType type); +bool StringToLinearSolverType(string value, LinearSolverType* type); + const char* PreconditionerTypeToString(PreconditionerType type); +bool StringToPreconditionerType(string value, PreconditionerType* type); + const char* SparseLinearAlgebraLibraryTypeToString( SparseLinearAlgebraLibraryType type); +bool StringToSparseLinearAlgebraLibraryType( + string value, + SparseLinearAlgebraLibraryType* type); + +const char* OrderingTypeToString(OrderingType type); +bool StringToOrderingType(string value, OrderingType* type); + +const char* TrustRegionStrategyTypeToString(TrustRegionStrategyType type); +bool StringToTrustRegionStrategyType(string value, + TrustRegionStrategyType* type); + +const char* DoglegTypeToString(DoglegType type); +bool StringToDoglegType(string value, DoglegType* type); + const char* LinearSolverTerminationTypeToString( LinearSolverTerminationType type); -const char* OrderingTypeToString(OrderingType type); + const char* SolverTerminationTypeToString(SolverTerminationType type); -const char* SparseLinearAlgebraLibraryTypeToString( - SparseLinearAlgebraLibraryType type); -const char* TrustRegionStrategyTypeToString( TrustRegionStrategyType type); bool IsSchurType(LinearSolverType type); } // namespace ceres diff --git a/internal/ceres/types.cc b/internal/ceres/types.cc index 2e950c51d..9339bc4f5 100644 --- a/internal/ceres/types.cc +++ b/internal/ceres/types.cc @@ -28,13 +28,21 @@ // // Author: sameeragarwal@google.com (Sameer Agarwal) +#include #include #include "ceres/types.h" +#include "glog/logging.h" namespace ceres { #define CASESTR(x) case x: return #x +#define STRENUM(x) if (value == #x) { *type = x; return true;} + +void UpperCase(string* input) { + std::transform(input->begin(), input->end(), input->begin(), ::toupper); +} + const char* LinearSolverTypeToString(LinearSolverType solver_type) { switch (solver_type) { CASESTR(DENSE_NORMAL_CHOLESKY); @@ -49,6 +57,18 @@ const char* LinearSolverTypeToString(LinearSolverType solver_type) { } } +bool StringToLinearSolverType(string value, LinearSolverType* type) { + UpperCase(&value); + STRENUM(DENSE_NORMAL_CHOLESKY); + STRENUM(DENSE_QR); + STRENUM(SPARSE_NORMAL_CHOLESKY); + STRENUM(DENSE_SCHUR); + STRENUM(SPARSE_SCHUR); + STRENUM(ITERATIVE_SCHUR); + STRENUM(CGNR); + return false; +} + const char* PreconditionerTypeToString( PreconditionerType preconditioner_type) { switch (preconditioner_type) { @@ -62,6 +82,16 @@ const char* PreconditionerTypeToString( } } +bool StringToPreconditionerType(string value, PreconditionerType* type) { + UpperCase(&value); + STRENUM(IDENTITY); + STRENUM(JACOBI); + STRENUM(SCHUR_JACOBI); + STRENUM(CLUSTER_JACOBI); + STRENUM(CLUSTER_TRIDIAGONAL); + return false; +} + const char* SparseLinearAlgebraLibraryTypeToString( SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type) { switch (sparse_linear_algebra_library_type) { @@ -72,6 +102,16 @@ const char* SparseLinearAlgebraLibraryTypeToString( } } + +bool StringToSparseLinearAlgebraLibraryType( + string value, + SparseLinearAlgebraLibraryType* type) { + UpperCase(&value); + STRENUM(SUITE_SPARSE); + STRENUM(CX_SPARSE); + return false; +} + const char* OrderingTypeToString(OrderingType ordering_type) { switch (ordering_type) { CASESTR(NATURAL); @@ -82,6 +122,48 @@ const char* OrderingTypeToString(OrderingType ordering_type) { } } +bool StringToOrderingType(string value, OrderingType* type) { + UpperCase(&value); + STRENUM(NATURAL); + STRENUM(USER); + STRENUM(SCHUR); + return false; +} + +const char* TrustRegionStrategyTypeToString( + TrustRegionStrategyType trust_region_strategy_type) { + switch (trust_region_strategy_type) { + CASESTR(LEVENBERG_MARQUARDT); + CASESTR(DOGLEG); + default: + return "UNKNOWN"; + } +} + +bool StringToTrustRegionStrategyType(string value, + TrustRegionStrategyType* type) { + UpperCase(&value); + STRENUM(LEVENBERG_MARQUARDT); + STRENUM(DOGLEG); + return false; +} + +const char* DoglegTypeToString(DoglegType dogleg_type) { + switch (dogleg_type) { + CASESTR(TRADITIONAL_DOGLEG); + CASESTR(SUBSPACE_DOGLEG); + default: + return "UNKNOWN"; + } +} + +bool StringToDoglegType(string value, DoglegType* type) { + UpperCase(&value); + STRENUM(TRADITIONAL_DOGLEG); + STRENUM(SUBSPACE_DOGLEG); + return false; +} + const char* SolverTerminationTypeToString( SolverTerminationType termination_type) { switch (termination_type) { @@ -98,27 +180,20 @@ const char* SolverTerminationTypeToString( } } -const char* SparseLinearAlgebraTypeToString( - SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type) { - switch (sparse_linear_algebra_library_type) { - CASESTR(CX_SPARSE); - CASESTR(SUITE_SPARSE); - default: - return "UNKNOWN"; - } -} - -const char* TrustRegionStrategyTypeToString( - TrustRegionStrategyType trust_region_strategy_type) { - switch (trust_region_strategy_type) { - CASESTR(LEVENBERG_MARQUARDT); - CASESTR(DOGLEG); +const char* LinearSolverTerminationTypeToString( + LinearSolverTerminationType termination_type) { + switch (termination_type) { + CASESTR(TOLERANCE); + CASESTR(MAX_ITERATIONS); + CASESTR(STAGNATION); + CASESTR(FAILURE); default: return "UNKNOWN"; } } #undef CASESTR +#undef STRENUM bool IsSchurType(LinearSolverType type) { return ((type == SPARSE_SCHUR) ||