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.