Move EvaluationCallback from Solver::Options to Problem::Options. Adding it to Solver::Options was a mistake, as it prevents it from being used in covariance estimation. Also updated associated docs. https://github.com/ceres-solver/ceres-solver/issues/380 https://github.com/ceres-solver/ceres-solver/issues/401 https://github.com/ceres-solver/ceres-solver/issues/484 Change-Id: I63809a47a58e84c04a58bf8e59ace92f45fc2873
diff --git a/docs/source/nnls_modeling.rst b/docs/source/nnls_modeling.rst index c35fac8..7a86640 100644 --- a/docs/source/nnls_modeling.rst +++ b/docs/source/nnls_modeling.rst
@@ -1536,6 +1536,116 @@ delete on each ``cost_function`` or ``loss_function`` pointer only once, regardless of how many residual blocks refer to them. +.. class:: Problem::Options + + Options struct that is used to control :class:`Problem::`. + +.. member:: Ownership Problem::Options::cost_function_ownership + + Default: ``TAKE_OWNERSHIP`` + + This option controls whether the Problem object owns the cost + functions. + + If set to TAKE_OWNERSHIP, then the problem object will delete the + cost functions on destruction. The destructor is careful to delete + the pointers only once, since sharing cost functions is allowed. + +.. member:: Ownership Problem::Options::loss_function_ownership + + Default: ``TAKE_OWNERSHIP`` + + This option controls whether the Problem object owns the loss + functions. + + If set to TAKE_OWNERSHIP, then the problem object will delete the + loss functions on destruction. The destructor is careful to delete + the pointers only once, since sharing loss functions is allowed. + +.. member:: Ownership Problem::Options::local_parameterization_ownership + + Default: ``TAKE_OWNERSHIP`` + + This option controls whether the Problem object owns the local + parameterizations. + + If set to TAKE_OWNERSHIP, then the problem object will delete the + local parameterizations on destruction. The destructor is careful + to delete the pointers only once, since sharing local + parameterizations is allowed. + +.. member:: bool Problem::Options::enable_fast_removal + + Default: ``false`` + + If true, trades memory for faster + :member:`Problem::RemoveResidualBlock` and + :member:`Problem::RemoveParameterBlock` operations. + + By default, :member:`Problem::RemoveParameterBlock` and + :member:`Problem::RemoveResidualBlock` take time proportional to + the size of the entire problem. If you only ever remove + parameters or residuals from the problem occasionally, this might + be acceptable. However, if you have memory to spare, enable this + option to make :member:`Problem::RemoveParameterBlock` take time + proportional to the number of residual blocks that depend on it, + and :member:`Problem::RemoveResidualBlock` take (on average) + constant time. + + The increase in memory usage is twofold: an additional hash set + per parameter block containing all the residuals that depend on + the parameter block; and a hash set in the problem containing all + residuals. + +.. member:: bool Problem::Options::disable_all_safety_checks + + Default: `false` + + By default, Ceres performs a variety of safety checks when + constructing the problem. There is a small but measurable + performance penalty to these checks, typically around 5% of + construction time. If you are sure your problem construction is + correct, and 5% of the problem construction time is truly an + overhead you want to avoid, then you can set + disable_all_safety_checks to true. + + **WARNING** Do not set this to true, unless you are absolutely + sure of what you are doing. + +.. member:: Context* Problem::Options::context + + Default: `nullptr` + + A Ceres global context to use for solving this problem. This may + help to reduce computation time as Ceres can reuse expensive + objects to create. The context object can be `nullptr`, in which + case Ceres may create one. + + Ceres does NOT take ownership of the pointer. + +.. member:: EvaluationCallback* Problem::Options::evaluation_callback + + Default: `nullptr` + + Using this callback interface, Ceres can notify you when it is + about to evaluate the residuals or Jacobians. With the callback, + you can share computation between residual blocks by doing the + shared computation in + :member:`EvaluationCallback::PrepareForEvaluation` before Ceres + calls :member:`CostFunction::Evaluate`. It also enables caching + results between a pure residual evaluation and a residual & + Jacobian evaluation. + + Problem does NOT take ownership of the callback. + + .. NOTE:: + + Evaluation callbacks are incompatible with inner iterations. So + calling Solve with + :member:`Solver::Options::use_inner_iterations` set to `true` + on a :class:`Problem` with a non-null evaluation callback is an + error. + .. function:: ResidualBlockId Problem::AddResidualBlock(CostFunction* cost_function, LossFunction* loss_function, const vector<double*> parameter_blocks) .. function:: ResidualBlockId Problem::AddResidualBlock(CostFunction* cost_function, LossFunction* loss_function, double *x0, double *x1, ...) @@ -1745,15 +1855,15 @@ blocks for a parameter block will incur a scan of the entire :class:`Problem` object. -.. function:: const CostFunction* GetCostFunctionForResidualBlock(const ResidualBlockId residual_block) const +.. function:: const CostFunction* Problem::GetCostFunctionForResidualBlock(const ResidualBlockId residual_block) const Get the :class:`CostFunction` for the given residual block. -.. function:: const LossFunction* GetLossFunctionForResidualBlock(const ResidualBlockId residual_block) const +.. function:: const LossFunction* Problem::GetLossFunctionForResidualBlock(const ResidualBlockId residual_block) const Get the :class:`LossFunction` for the given residual block. -.. function:: bool EvaluateResidualBlock(ResidualBlockId residual_block_id, bool apply_loss_function, double* cost,double* residuals, double** jacobians) const; +.. function:: bool EvaluateResidualBlock(ResidualBlockId residual_block_id, bool apply_loss_function, double* cost,double* residuals, double** jacobians) const Evaluates the residual block, storing the scalar cost in ``cost``, the residual components in ``residuals``, and the jacobians between the @@ -1783,8 +1893,21 @@ .. NOTE:: - TODO(sameeragarwal): Clarify interaction with IterationCallback - once that cleanup is done. + If an :class:`EvaluationCallback` is associated with the problem + then it is the user's responsibility to call + :func:`EvaluationCalback::PrepareForEvaluation` it before + calling this method. + + This is because, if the user calls this method multiple times, + we cannot tell if the underlying parameter blocks have changed + between calls or not. So if ``EvaluateResidualBlock`` was + responsible for calling the + :func:`EvaluationCalback::PrepareForEvaluation`, it will have to + do it everytime it is called. Which makes the common case where + the parameter blocks do not change, inefficient. So we leave it + to the user to call the + :func:`EvaluationCalback::PrepareForEvaluation` as needed. + .. function:: bool Problem::Evaluate(const Problem::EvaluateOptions& options, double* cost, vector<double>* residuals, vector<double>* gradient, CRSMatrix* jacobian) @@ -1832,6 +1955,12 @@ :class:`IterationCallback` at the end of an iteration during a solve. + .. NOTE:: + + If an EvaluationCallback is associated with the problem, then + its PrepareForEvaluation method will be called everytime this + method is called with ``new_point = true``. + .. class:: Problem::EvaluateOptions Options struct that is used to control :func:`Problem::Evaluate`. @@ -1873,7 +2002,64 @@ Number of threads to use. (Requires OpenMP). -.. + +:class:`EvaluationCallback` +=========================== + +.. class:: EvaluationCallback + + Interface for receiving callbacks before Ceres evaluates residuals or + Jacobians: + + .. code-block:: c++ + + class EvaluationCallback { + public: + virtual ~EvaluationCallback() {} + virtual void PrepareForEvaluation()(bool evaluate_jacobians + bool new_evaluation_point) = 0; + }; + + Ceres will call ``PrepareForEvaluation()`` every time, and once + before it computes the residuals and/or the Jacobians. + + User parameters (the double* values provided by the us) + are fixed until the next call to ``PrepareForEvaluation()``. If + ``new_evaluation_point == true``, then this is a new point that is + different from the last evaluated point. Otherwise, it is the same + point that was evaluated previously (either Jacobian or residual) + and the user can use cached results from previous evaluations. If + ``evaluate_jacobians`` is true, then Ceres will request Jacobians + in the upcoming cost evaluation. + + Using this callback interface, Ceres can notify you when it is about + to evaluate the residuals or Jacobians. With the callback, you can + share computation between residual blocks by doing the shared + computation in PrepareForEvaluation() before Ceres calls + CostFunction::Evaluate() on all the residuals. It also enables + caching results between a pure residual evaluation and a residual & + Jacobian evaluation, via the new_evaluation_point argument. + + One use case for this callback is if the cost function compute is + moved to the GPU. In that case, the prepare call does the actual cost + function evaluation, and subsequent calls from Ceres to the actual + cost functions merely copy the results from the GPU onto the + corresponding blocks for Ceres to plug into the solver. + + **Note**: Ceres provides no mechanism to share data other than the + notification from the callback. Users must provide access to + pre-computed shared data to their cost functions behind the scenes; + this all happens without Ceres knowing. One approach is to put a + pointer to the shared data in each cost function (recommended) or to + use a global shared variable (discouraged; bug-prone). As far as + Ceres is concerned, it is evaluating cost functions like any other; + it just so happens that behind the scenes the cost functions reuse + pre-computed data to execute faster. + + See ``evaluation_callback_test.cc`` for code that explicitly verifies + the preconditions between ``PrepareForEvaluation()`` and + ``CostFunction::Evaluate()``. + ``rotation.h`` ==============
diff --git a/docs/source/nnls_solving.rst b/docs/source/nnls_solving.rst index fde62b9..3a2ac01 100644 --- a/docs/source/nnls_solving.rst +++ b/docs/source/nnls_solving.rst
@@ -1413,6 +1413,10 @@ on each Newton/Trust region step using a coordinate descent algorithm. For more details, see :ref:`section-inner-iterations`. + **Note** Inner iterations cannot be used with :class:`Problem` + objects that have an :class:`EvaluationCallback` associated with + them. + .. member:: double Solver::Options::inner_iteration_tolerance Default: ``1e-3`` @@ -1603,63 +1607,40 @@ which break this finite difference heuristic, but they do not come up often in practice. -.. member:: vector<IterationCallback> Solver::Options::callbacks - - Callbacks that are executed at the end of each iteration of the - :class:`Minimizer`. They are executed in the order that they are - specified in this vector. By default, parameter blocks are updated - only at the end of the optimization, i.e., when the - :class:`Minimizer` terminates. This behavior is controlled by - :member:`Solver::Options::update_state_every_iteration`. If the user - wishes to have access to the updated parameter blocks when his/her - callbacks are executed, then set - :member:`Solver::Options::update_state_every_iteration` to true. - - The solver does NOT take ownership of these pointers. - .. member:: bool Solver::Options::update_state_every_iteration Default: ``false`` - If true, the user's parameter blocks are updated at the end of - every Minimizer iteration, otherwise they are updated when the - Minimizer terminates. This is useful if, for example, the user - wishes to visualize the state of the optimization every iteration - (in combination with an IterationCallback). + If ``update_state_every_iteration`` is ``true``, then Ceres Solver + will guarantee that at the end of every iteration and before any + user :class:`IterationCallback` is called, the parameter blocks are + updated to the current best solution found by the solver. Thus the + IterationCallback can inspect the values of the parameter blocks + for purposes of computation, visualization or termination. - **Note**: If :member:`Solver::Options::evaluation_callback` is set, - then the behaviour of this flag is slightly different in each case: + If ``update_state_every_iteration`` is ``false`` then there is no + such guarantee, and user provided :class:`IterationCallback` s should + not expect to look at the parameter blocks and interpret their + values. - 1. If :member:`Solver::Options::update_state_every_iteration` is - false, then the user's state is changed at every residual and/or - jacobian evaluation. Any user provided IterationCallbacks should - **not** inspect and depend on the user visible state while the - solver is running, since they it have undefined contents. +.. member:: vector<IterationCallback> Solver::Options::callbacks - 2. If :member:`Solver::Options::update_state_every_iteration` is - false, then the user's state is changed at every residual and/or - jacobian evaluation, BUT the solver will ensure that before the - user provided `IterationCallbacks` are called, the user visible - state will be updated to the current best point found by the - solver. + Callbacks that are executed at the end of each iteration of the + :class:`Minimizer`. They are executed in the order that they are + specified in this vector. -.. member:: bool Solver::Options::evaluation_callback + By default, parameter blocks are updated only at the end of the + optimization, i.e., when the :class:`Minimizer` terminates. This + means that by default, if an :class:`IterationCallback` inspects + the parameter blocks, they will not see them changing in the course + of the optimization. - Default: ``NULL`` + To tell Ceres to update the parameter blocks at the end of each + iteration and before calling the user's callback, set + :member:`Solver::Options::update_state_every_iteration` to + ``true``. - If non-``NULL``, gets notified when Ceres is about to evaluate the - residuals and/or Jacobians. This enables sharing computation between - residuals, which in some cases is important for efficient cost - evaluation. See :class:`EvaluationCallback` for details. - - **Note**: Evaluation callbacks are incompatible with inner - iterations. - - **Warning**: This interacts with - :member:`Solver::Options::update_state_every_iteration`. See the - documentation for that option for more details. - - The solver does `not` take ownership of the pointer. + The solver does NOT take ownership of these pointers. :class:`ParameterBlockOrdering` =============================== @@ -1720,62 +1701,6 @@ Number of groups with one or more elements. -:class:`EvaluationCallback` -=========================== - -.. class:: EvaluationCallback - - Interface for receiving callbacks before Ceres evaluates residuals or - Jacobians: - - .. code-block:: c++ - - class EvaluationCallback { - public: - virtual ~EvaluationCallback() {} - virtual void PrepareForEvaluation()(bool evaluate_jacobians - bool new_evaluation_point) = 0; - }; - - ``PrepareForEvaluation()`` is called before Ceres requests residuals - or jacobians for a given setting of the parameters. User parameters - (the double* values provided to the cost functions) are fixed until - the next call to ``PrepareForEvaluation()``. If - ``new_evaluation_point == true``, then this is a new point that is - different from the last evaluated point. Otherwise, it is the same - point that was evaluated previously (either jacobian or residual) and - the user can use cached results from previous evaluations. If - ``evaluate_jacobians`` is true, then Ceres will request jacobians in - the upcoming cost evaluation. - - Using this callback interface, Ceres can notify you when it is about - to evaluate the residuals or jacobians. With the callback, you can - share computation between residual blocks by doing the shared - computation in PrepareForEvaluation() before Ceres calls - CostFunction::Evaluate() on all the residuals. It also enables - caching results between a pure residual evaluation and a residual & - jacobian evaluation, via the new_evaluation_point argument. - - One use case for this callback is if the cost function compute is - moved to the GPU. In that case, the prepare call does the actual cost - function evaluation, and subsequent calls from Ceres to the actual - cost functions merely copy the results from the GPU onto the - corresponding blocks for Ceres to plug into the solver. - - **Note**: Ceres provides no mechanism to share data other than the - notification from the callback. Users must provide access to - pre-computed shared data to their cost functions behind the scenes; - this all happens without Ceres knowing. One approach is to put a - pointer to the shared data in each cost function (recommended) or to - use a global shared variable (discouraged; bug-prone). As far as - Ceres is concerned, it is evaluating cost functions like any other; - it just so happens that behind the scenes the cost functions reuse - pre-computed data to execute faster. - - See ``evaluation_callback_test.cc`` for code that explicitly verifies - the preconditions between ``PrepareForEvaluation()`` and - ``CostFunction::Evaluate()``. - :class:`IterationCallback` ==========================
diff --git a/include/ceres/ceres.h b/include/ceres/ceres.h index 6e077cc..88875a0 100644 --- a/include/ceres/ceres.h +++ b/include/ceres/ceres.h
@@ -46,6 +46,7 @@ #include "ceres/dynamic_cost_function.h" #include "ceres/dynamic_cost_function_to_functor.h" #include "ceres/dynamic_numeric_diff_cost_function.h" +#include "ceres/evaluation_callback.h" #include "ceres/gradient_checker.h" #include "ceres/gradient_problem.h" #include "ceres/gradient_problem_solver.h"
diff --git a/include/ceres/evaluation_callback.h b/include/ceres/evaluation_callback.h index a0a8601..24122a8 100644 --- a/include/ceres/evaluation_callback.h +++ b/include/ceres/evaluation_callback.h
@@ -35,28 +35,31 @@ namespace ceres { -// Using this callback interface, Ceres can notify you when it is about to -// evaluate the residuals or jacobians. With the callback, you can share -// computation between residual blocks by doing the shared computation in -// PrepareForEvaluation() before Ceres calls CostFunction::Evaluate() on all -// the residuals. It also enables caching results between a pure residual -// evaluation and a residual & jacobian evaluation, via the -// new_evaluation_point argument. +// Using this callback interface, Ceres can notify you when it is +// about to evaluate the residuals or jacobians. With the callback, +// you can share computation between residual blocks by doing the +// shared computation in PrepareForEvaluation() before Ceres calls +// CostFunction::Evaluate(). It also enables caching results between a +// pure residual evaluation and a residual & jacobian evaluation, via +// the new_evaluation_point argument. // -// One use case for this callback is if the cost function compute is moved to -// the GPU. In that case, the prepare call does the actual cost function -// evaluation, and subsequent calls from Ceres to the actual cost functions -// merely copy the results from the GPU onto the corresponding blocks for Ceres -// to plug into the solver. +// One use case for this callback is if the cost function compute is +// moved to the GPU. In that case, the prepare call does the actual +// cost function evaluation, and subsequent calls from Ceres to the +// actual cost functions merely copy the results from the GPU onto the +// corresponding blocks for Ceres to plug into the solver. // -// NOTE: Ceres provides no mechanism to share data other than the notification -// from the callback. Users must provide access to pre-computed shared data to -// their cost functions behind the scenes; this all happens without Ceres -// knowing. One approach is to put a pointer to the shared data in each cost -// function (recommended) or to use a global shared variable (discouraged; -// bug-prone). As far as Ceres is concerned, it is evaluating cost functions -// like any other; it just so happens that behind the scenes the cost functions -// reuse pre-computed data to execute faster. +// NOTE: Ceres provides no mechanism to share data other than the +// notification from the callback. Users must provide access to +// pre-computed shared data to their cost functions behind the scenes; +// this all happens without Ceres knowing. +// +// One approach is to put a pointer to the shared data in each cost +// function (recommended) or to use a global shared variable +// (discouraged; bug-prone). As far as Ceres is concerned, it is +// evaluating cost functions like any other; it just so happens that +// behind the scenes the cost functions reuse pre-computed data to +// execute faster. class CERES_EXPORT EvaluationCallback { public: virtual ~EvaluationCallback() {}
diff --git a/include/ceres/problem.h b/include/ceres/problem.h index 366d6ac..1b57eec 100644 --- a/include/ceres/problem.h +++ b/include/ceres/problem.h
@@ -50,6 +50,7 @@ namespace ceres { class CostFunction; +class EvaluationCallback; class LossFunction; class LocalParameterization; class Solver; @@ -165,6 +166,23 @@ // // Ceres does NOT take ownership of the pointer. Context* context = nullptr; + + // Using this callback interface, Ceres can notify you when it is + // about to evaluate the residuals or jacobians. With the + // callback, you can share computation between residual blocks by + // doing the shared computation in + // EvaluationCallback::PrepareForEvaluation() before Ceres calls + // CostFunction::Evaluate(). It also enables caching results + // between a pure residual evaluation and a residual & jacobian + // evaluation. + // + // Problem DOES NOT take ownership of the callback. + // + // NOTE: Evaluation callbacks are incompatible with inner + // iterations. So calling Solve with + // Solver::Options::use_inner_iterations = true on a Problem with + // a non-null evaluation callback is an error. + EvaluationCallback* evaluation_callback = nullptr; }; // The default constructor is equivalent to the @@ -448,6 +466,10 @@ // Note 3: This function cannot be called while the problem is being // solved, for example it cannot be called from an IterationCallback // at the end of an iteration during a solve. + // + // Note 4: If an EvaluationCallback is associated with the problem, + // then its PrepareForEvaluation method will be called everytime + // this method is called with new_point = true. bool Evaluate(const EvaluateOptions& options, double* cost, std::vector<double>* residuals, @@ -480,8 +502,17 @@ // apply_loss_function as the name implies allows the user to switch // the application of the loss function on and off. // - // TODO(sameeragarwal): Clarify interaction with IterationCallback - // once that cleanup is done. + // WARNING: If an EvaluationCallback is associated with the problem + // then it is the user's responsibility to call it before calling + // this method. + // + // This is because, if the user calls this method multiple times, we + // cannot tell if the underlying parameter blocks have changed + // between calls or not. So if EvaluateResidualBlock was responsible + // for calling the EvaluationCallback, it will have to do it + // everytime it is called. Which makes the common case where the + // parameter blocks do not change, inefficient. So we leave it to + // the user to call the EvaluationCallback as needed. bool EvaluateResidualBlock(ResidualBlockId residual_block_id, bool apply_loss_function, double* cost,
diff --git a/include/ceres/solver.h b/include/ceres/solver.h index 10c2683..1e479ae 100644 --- a/include/ceres/solver.h +++ b/include/ceres/solver.h
@@ -36,7 +36,6 @@ #include <string> #include <vector> #include "ceres/crs_matrix.h" -#include "ceres/evaluation_callback.h" #include "ceres/internal/disable_warnings.h" #include "ceres/internal/port.h" #include "ceres/iteration_callback.h" @@ -697,26 +696,18 @@ // optimistic. This number should be exposed for users to change. double gradient_check_numeric_derivative_relative_step_size = 1e-6; - // If true, the user's parameter blocks are updated at the end of - // every Minimizer iteration, otherwise they are updated when the - // Minimizer terminates. This is useful if, for example, the user - // wishes to visualize the state of the optimization every iteration - // (in combination with an IterationCallback). - // - // NOTE: If an evaluation_callback is provided, then the behaviour - // of this flag is slightly different in each case: - // - // (1) If update_state_every_iteration = false, then the user's - // state is changed at every residual and/or jacobian evaluation. - // Any user provided IterationCallbacks should NOT inspect and - // depend on the user visible state while the solver is running, - // since there will be undefined contents. - // - // (2) If update_state_every_iteration is true, then the user's - // state is changed at every residual and/or jacobian evaluation, - // BUT the solver will ensure that before the user provided - // IterationCallbacks are called, the user visible state will be - // updated to the current best point found by the solver. + // If update_state_every_iteration is true, then Ceres Solver will + // guarantee that at the end of every iteration and before any + // user provided IterationCallback is called, the parameter blocks + // are updated to the current best solution found by the + // solver. Thus the IterationCallback can inspect the values of + // the parameter blocks for purposes of computation, visualization + // or termination. + + // If update_state_every_iteration is false then there is no such + // guarantee, and user provided IterationCallbacks should not + // expect to look at the parameter blocks and interpret their + // values. bool update_state_every_iteration = false; // Callbacks that are executed at the end of each iteration of the @@ -735,19 +726,6 @@ // // The solver does NOT take ownership of these pointers. std::vector<IterationCallback*> callbacks; - - // If non-NULL, gets notified when Ceres is about to evaluate the - // residuals and/or Jacobians. This enables sharing computation - // between residuals, which in some cases is important for efficient - // cost evaluation. See evaluation_callback.h for details. - // - // NOTE: Evaluation callbacks are incompatible with inner iterations. - // - // WARNING: This interacts with update_state_every_iteration. See - // the documentation for that option for more details. - // - // The solver does NOT take ownership of the pointer. - EvaluationCallback* evaluation_callback = nullptr; }; struct CERES_EXPORT Summary {
diff --git a/internal/ceres/evaluation_callback_test.cc b/internal/ceres/evaluation_callback_test.cc index 9ca500f..dff0943 100644 --- a/internal/ceres/evaluation_callback_test.cc +++ b/internal/ceres/evaluation_callback_test.cc
@@ -35,6 +35,7 @@ #include <vector> #include "gtest/gtest.h" +#include "ceres/evaluation_callback.h" #include "ceres/sized_cost_function.h" #include "ceres/problem.h" #include "ceres/problem_impl.h" @@ -193,6 +194,7 @@ WigglyBowlCostFunctionAndEvaluationCallback cost_function(parameters); Problem::Options problem_options; + problem_options.evaluation_callback = &cost_function; problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP; Problem problem(problem_options); problem.AddResidualBlock(&cost_function, NULL, parameters); @@ -200,7 +202,6 @@ Solver::Options options; options.linear_solver_type = DENSE_QR; options.max_num_iterations = 300; // Cost function is hard. - options.evaluation_callback = &cost_function; // Run the solve. Checking is done inside the cost function / callback. Solver::Summary summary; @@ -240,6 +241,7 @@ WigglyBowlCostFunctionAndEvaluationCallback cost_function(parameters); Problem::Options problem_options; + problem_options.evaluation_callback = &cost_function; problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP; Problem problem(problem_options); problem.AddResidualBlock(&cost_function, NULL, parameters); @@ -248,7 +250,7 @@ options.linear_solver_type = DENSE_QR; options.max_num_iterations = 300; // Cost function is hard. options.minimizer_type = ceres::LINE_SEARCH; - options.evaluation_callback = &cost_function; + options.line_search_type = line_search; options.line_search_direction_type = line_search_direction; options.line_search_interpolation_type = line_search_interpolation;
diff --git a/internal/ceres/line_search_preprocessor.cc b/internal/ceres/line_search_preprocessor.cc index 17226ad..5a21809 100644 --- a/internal/ceres/line_search_preprocessor.cc +++ b/internal/ceres/line_search_preprocessor.cc
@@ -60,7 +60,8 @@ pp->evaluator_options.num_eliminate_blocks = 0; pp->evaluator_options.num_threads = pp->options.num_threads; pp->evaluator_options.context = pp->problem->context(); - pp->evaluator_options.evaluation_callback = pp->options.evaluation_callback; + pp->evaluator_options.evaluation_callback = + pp->reduced_program->mutable_evaluation_callback(); pp->evaluator.reset(Evaluator::Create(pp->evaluator_options, pp->reduced_program.get(), &pp->error));
diff --git a/internal/ceres/problem_impl.cc b/internal/ceres/problem_impl.cc index 523d5bd..63dad12 100644 --- a/internal/ceres/problem_impl.cc +++ b/internal/ceres/problem_impl.cc
@@ -47,6 +47,7 @@ #include "ceres/context_impl.h" #include "ceres/cost_function.h" #include "ceres/crs_matrix.h" +#include "ceres/evaluation_callback.h" #include "ceres/evaluator.h" #include "ceres/internal/fixed_array.h" #include "ceres/internal/port.h" @@ -244,6 +245,7 @@ ProblemImpl::ProblemImpl(const Problem::Options& options) : options_(options), program_(new internal::Program) { + program_->evaluation_callback_ = options.evaluation_callback; InitializeContext(options_.context, &context_impl_, &context_impl_owned_); } @@ -688,7 +690,8 @@ // The main thread also does work so we only need to launch num_threads - 1. context_impl_->EnsureMinimumThreads(evaluator_options.num_threads - 1); evaluator_options.context = context_impl_; - + evaluator_options.evaluation_callback = + program_->mutable_evaluation_callback(); std::unique_ptr<Evaluator> evaluator( new ProgramEvaluator<ScratchEvaluatePreparer, CompressedRowJacobianWriter>(evaluator_options,
diff --git a/internal/ceres/problem_impl.h b/internal/ceres/problem_impl.h index 106992d..f366b7d 100644 --- a/internal/ceres/problem_impl.h +++ b/internal/ceres/problem_impl.h
@@ -53,6 +53,7 @@ namespace ceres { class CostFunction; +class EvaluationCallback; class LossFunction; class LocalParameterization; struct CRSMatrix;
diff --git a/internal/ceres/program.cc b/internal/ceres/program.cc index f1cd1bb..fa3fbf1 100644 --- a/internal/ceres/program.cc +++ b/internal/ceres/program.cc
@@ -62,7 +62,8 @@ Program::Program(const Program& program) : parameter_blocks_(program.parameter_blocks_), - residual_blocks_(program.residual_blocks_) { + residual_blocks_(program.residual_blocks_), + evaluation_callback_(program.evaluation_callback_){ } const vector<ParameterBlock*>& Program::parameter_blocks() const { @@ -81,6 +82,10 @@ return &residual_blocks_; } +EvaluationCallback* Program::mutable_evaluation_callback() { + return evaluation_callback_; +} + bool Program::StateVectorToParameterBlocks(const double *state) { for (int i = 0; i < parameter_blocks_.size(); ++i) { if (!parameter_blocks_[i]->IsConstant() &&
diff --git a/internal/ceres/program.h b/internal/ceres/program.h index 38c958f..297be65 100644 --- a/internal/ceres/program.h +++ b/internal/ceres/program.h
@@ -37,6 +37,9 @@ #include "ceres/internal/port.h" namespace ceres { + +class EvaluationCallback; + namespace internal { class ParameterBlock; @@ -64,6 +67,7 @@ const std::vector<ResidualBlock*>& residual_blocks() const; std::vector<ParameterBlock*>* mutable_parameter_blocks(); std::vector<ResidualBlock*>* mutable_residual_blocks(); + EvaluationCallback* mutable_evaluation_callback(); // Serialize to/from the program and update states. // @@ -182,6 +186,7 @@ // The Program does not own the ParameterBlock or ResidualBlock objects. std::vector<ParameterBlock*> parameter_blocks_; std::vector<ResidualBlock*> residual_blocks_; + EvaluationCallback* evaluation_callback_ = nullptr; friend class ProblemImpl; };
diff --git a/internal/ceres/solver.cc b/internal/ceres/solver.cc index 14e50f3..84a5e5a 100644 --- a/internal/ceres/solver.cc +++ b/internal/ceres/solver.cc
@@ -128,14 +128,6 @@ OPTION_GE(inner_iteration_tolerance, 0.0); } - if (options.use_inner_iterations && - options.evaluation_callback != NULL) { - *error = "Inner iterations (use_inner_iterations = true) can't be " - "combined with an evaluation callback " - "(options.evaluation_callback != NULL)."; - return false; - } - if (options.use_nonmonotonic_steps) { OPTION_GT(max_consecutive_nonmonotonic_steps, 0); }
diff --git a/internal/ceres/solver_test.cc b/internal/ceres/solver_test.cc index 6fb5f74..02494de 100644 --- a/internal/ceres/solver_test.cc +++ b/internal/ceres/solver_test.cc
@@ -94,15 +94,13 @@ } }; -TEST(Solver, UpdateStateEveryIterationOption) { +TEST(Solver, UpdateStateEveryIterationOptionNoEvaluationCallback) { double x = 50.0; const double original_x = x; - std::unique_ptr<CostFunction> cost_function(QuadraticCostFunctor::Create()); Problem::Options problem_options; - problem_options.cost_function_ownership = DO_NOT_TAKE_OWNERSHIP; Problem problem(problem_options); - problem.AddResidualBlock(cost_function.get(), NULL, &x); + problem.AddResidualBlock(QuadraticCostFunctor::Create(), nullptr, &x); Solver::Options options; options.linear_solver_type = DENSE_QR; @@ -114,14 +112,7 @@ int num_iterations; - // There are four cases that need to be checked: - // - // (update_state_every_iteration = true|false) X - // (evaluation_callback = NULL|provided) - // - // These need to get checked since there is some interaction between them. - - // First: update_state_every_iteration=false, evaluation_callback=NULL. + // First: update_state_every_iteration=false, evaluation_callback=nullptr. Solve(options, &problem, &summary); num_iterations = summary.num_successful_steps + summary.num_unsuccessful_steps; @@ -130,7 +121,7 @@ EXPECT_EQ(50.0, callback.x_values[i]); } - // Second: update_state_every_iteration=true, evaluation_callback=NULL. + // Second: update_state_every_iteration=true, evaluation_callback=nullptr. x = 50.0; options.update_state_every_iteration = true; callback.x_values.clear(); @@ -140,34 +131,75 @@ EXPECT_GT(num_iterations, 1); EXPECT_EQ(original_x, callback.x_values[0]); EXPECT_NE(original_x, callback.x_values[1]); - - NoOpEvaluationCallback evaluation_callback; - - // Third: update_state_every_iteration=true, evaluation_callback=!NULL. - x = 50.0; - options.update_state_every_iteration = true; - options.evaluation_callback = &evaluation_callback; - callback.x_values.clear(); - Solve(options, &problem, &summary); - num_iterations = summary.num_successful_steps + - summary.num_unsuccessful_steps; - EXPECT_GT(num_iterations, 1); - EXPECT_EQ(original_x, callback.x_values[0]); - EXPECT_NE(original_x, callback.x_values[1]); - - // Fourth: update_state_every_iteration=false, evaluation_callback=!NULL. - x = 50.0; - options.update_state_every_iteration = false; - options.evaluation_callback = &evaluation_callback; - callback.x_values.clear(); - Solve(options, &problem, &summary); - num_iterations = summary.num_successful_steps + - summary.num_unsuccessful_steps; - EXPECT_GT(num_iterations, 1); - EXPECT_EQ(original_x, callback.x_values[0]); - EXPECT_NE(original_x, callback.x_values[1]); } +TEST(Solver, UpdateStateEveryIterationOptionWithEvaluationCallback) { + double x = 50.0; + const double original_x = x; + + Problem::Options problem_options; + NoOpEvaluationCallback evaluation_callback; + problem_options.evaluation_callback = &evaluation_callback; + + Problem problem(problem_options); + problem.AddResidualBlock(QuadraticCostFunctor::Create(), nullptr, &x); + + Solver::Options options; + options.linear_solver_type = DENSE_QR; + RememberingCallback callback(&x); + options.callbacks.push_back(&callback); + + Solver::Summary summary; + + int num_iterations; + + // First: update_state_every_iteration=true, evaluation_callback=!nullptr. + x = 50.0; + options.update_state_every_iteration = true; + callback.x_values.clear(); + Solve(options, &problem, &summary); + num_iterations = summary.num_successful_steps + + summary.num_unsuccessful_steps; + EXPECT_GT(num_iterations, 1); + EXPECT_EQ(original_x, callback.x_values[0]); + EXPECT_NE(original_x, callback.x_values[1]); + + // Second: update_state_every_iteration=false, evaluation_callback=!nullptr. + x = 50.0; + options.update_state_every_iteration = false; + callback.x_values.clear(); + Solve(options, &problem, &summary); + num_iterations = summary.num_successful_steps + + summary.num_unsuccessful_steps; + EXPECT_GT(num_iterations, 1); + EXPECT_EQ(original_x, callback.x_values[0]); + EXPECT_NE(original_x, callback.x_values[1]); +} + +TEST(Solver, CantMixEvaluationCallbackWithInnerIterations) { + double x = 50.0; + double y = 60.0; + + Problem::Options problem_options; + NoOpEvaluationCallback evaluation_callback; + problem_options.evaluation_callback = &evaluation_callback; + + Problem problem(problem_options); + problem.AddResidualBlock(QuadraticCostFunctor::Create(), nullptr, &x); + problem.AddResidualBlock(QuadraticCostFunctor::Create(), nullptr, &y); + + Solver::Options options; + options.use_inner_iterations = true; + Solver::Summary summary; + Solve(options, &problem, &summary); + EXPECT_EQ(summary.termination_type, FAILURE); + + options.use_inner_iterations = false; + Solve(options, &problem, &summary); + EXPECT_EQ(summary.termination_type, CONVERGENCE); +} + + // The parameters must be in separate blocks so that they can be individually // set constant or not. struct Quadratic4DCostFunction { @@ -197,7 +229,7 @@ double* residuals, double** jacobians) const { residuals[0] = parameters[0][0]; - if (jacobians != NULL && jacobians[0] != NULL) { + if (jacobians != nullptr && jacobians[0] != nullptr) { jacobians[0][0] = 1.0; } return true; @@ -259,7 +291,7 @@ TEST(Solver, TrustRegionProblemIsConstant) { Problem problem; double x = 1; - problem.AddResidualBlock(new UnaryIdentityCostFunction, NULL, &x); + problem.AddResidualBlock(new UnaryIdentityCostFunction, nullptr, &x); problem.SetParameterBlockConstant(&x); Solver::Options options; options.minimizer_type = TRUST_REGION; @@ -273,7 +305,7 @@ TEST(Solver, LineSearchProblemIsConstant) { Problem problem; double x = 1; - problem.AddResidualBlock(new UnaryIdentityCostFunction, NULL, &x); + problem.AddResidualBlock(new UnaryIdentityCostFunction, nullptr, &x); problem.SetParameterBlockConstant(&x); Solver::Options options; options.minimizer_type = LINE_SEARCH; @@ -438,30 +470,6 @@ EXPECT_TRUE(options.IsValid(&message)); } -TEST(Solver, CantMixEvaluationCallbackWithInnerIterations) { - Solver::Options options; - NoOpEvaluationCallback evaluation_callback; - string message; - - // Can't combine them. - options.use_inner_iterations = true; - options.evaluation_callback = &evaluation_callback; - EXPECT_FALSE(options.IsValid(&message)); - - // Either or none is OK. - options.use_inner_iterations = false; - options.evaluation_callback = &evaluation_callback; - EXPECT_TRUE(options.IsValid(&message)); - - options.use_inner_iterations = true; - options.evaluation_callback = NULL; - EXPECT_TRUE(options.IsValid(&message)); - - options.use_inner_iterations = false; - options.evaluation_callback = NULL; - EXPECT_TRUE(options.IsValid(&message)); -} - template <int kNumResiduals, int... Ns> class DummyCostFunction : public SizedCostFunction<kNumResiduals, Ns...> { public: @@ -479,7 +487,7 @@ TEST(Solver, FixedCostForConstantProblem) { double x = 1.0; Problem problem; - problem.AddResidualBlock(new DummyCostFunction<2, 1>(), NULL, &x); + problem.AddResidualBlock(new DummyCostFunction<2, 1>(), nullptr, &x); problem.SetParameterBlockConstant(&x); const double expected_cost = 41.0 / 2.0; // 1/2 * ((4 + 0)^2 + (4 + 1)^2) Solver::Options options;
diff --git a/internal/ceres/trust_region_preprocessor.cc b/internal/ceres/trust_region_preprocessor.cc index aa7f095..dbeb3e5 100644 --- a/internal/ceres/trust_region_preprocessor.cc +++ b/internal/ceres/trust_region_preprocessor.cc
@@ -256,7 +256,8 @@ pp->evaluator_options.num_threads = options.num_threads; pp->evaluator_options.dynamic_sparsity = options.dynamic_sparsity; pp->evaluator_options.context = pp->problem->context(); - pp->evaluator_options.evaluation_callback = options.evaluation_callback; + pp->evaluator_options.evaluation_callback = + pp->reduced_program->mutable_evaluation_callback(); pp->evaluator.reset(Evaluator::Create(pp->evaluator_options, pp->reduced_program.get(), &pp->error)); @@ -273,6 +274,11 @@ return true; } + if (pp->reduced_program->mutable_evaluation_callback()) { + pp->error = "Inner iterations cannot be used with EvaluationCallbacks"; + return false; + } + // With just one parameter block, the outer iteration of the trust // region method and inner iterations are doing exactly the same // thing, and thus inner iterations are not needed.