Add Problem::EvaluateResidualBlock.
This method gives the user the ability to evaluate a given residual
block.
A couple of minor cleanups.
Problem::problem_impl_ -> Problem::impl_
NULL -> nullptr
https://github.com/ceres-solver/ceres-solver/issues/417
Change-Id: I6dd94762c475fa264c387b8c93d516f6e06fe832
diff --git a/docs/source/nnls_modeling.rst b/docs/source/nnls_modeling.rst
index faa3c26..299f859 100644
--- a/docs/source/nnls_modeling.rst
+++ b/docs/source/nnls_modeling.rst
@@ -1753,6 +1753,39 @@
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;
+
+ Evaluates the residual block, storing the scalar cost in ``cost``, the
+ residual components in ``residuals``, and the jacobians between the
+ parameters and residuals in ``jacobians[i]``, in row-major order.
+
+ If ``residuals`` is ``nullptr``, the residuals are not computed.
+
+ If ``jacobians`` is ``nullptr``, no Jacobians are computed. If
+ ``jacobians[i]`` is ``nullptr``, then the Jacobian for that
+ parameter block is not computed.
+
+ It is not okay to request the Jacobian w.r.t a parameter block
+ that is constant.
+
+ The return value indicates the success or failure. Even if the
+ function returns false, the caller should expect the output
+ memory locations to have been modified.
+
+ The returned cost and jacobians have had robustification and local
+ parameterizations applied already; for example, the jacobian for a
+ 4-dimensional quaternion parameter using the
+ :class:`QuaternionParameterization` is ``num_residuals x 3``
+ instead of ``num_residuals x 4``.
+
+ ``apply_loss_function`` as the name implies allows the user to
+ switch the application of the loss function on and off.
+
+ .. NOTE::
+
+ TODO(sameeragarwal): Clarify interaction with IterationCallback
+ once that cleanup is done.
+
.. function:: bool Problem::Evaluate(const Problem::EvaluateOptions& options, double* cost, vector<double>* residuals, vector<double>* gradient, CRSMatrix* jacobian)
Evaluate a :class:`Problem`. Any of the output pointers can be
@@ -1840,6 +1873,7 @@
Number of threads to use. (Requires OpenMP).
+..
``rotation.h``
==============
diff --git a/include/ceres/problem.h b/include/ceres/problem.h
index 503e0fe..f8f1764 100644
--- a/include/ceres/problem.h
+++ b/include/ceres/problem.h
@@ -114,8 +114,8 @@
//
// Problem problem;
//
-// problem.AddResidualBlock(new MyUnaryCostFunction(...), NULL, x1);
-// problem.AddResidualBlock(new MyBinaryCostFunction(...), NULL, x2, x3);
+// problem.AddResidualBlock(new MyUnaryCostFunction(...), nullptr, x1);
+// problem.AddResidualBlock(new MyBinaryCostFunction(...), nullptr, x2, x3);
//
// Please see cost_function.h for details of the CostFunction object.
class CERES_EXPORT Problem {
@@ -161,7 +161,7 @@
// 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 NULL, in which case Ceres may create one.
+ // The context object can be nullptr, in which case Ceres may create one.
//
// Ceres does NOT take ownership of the pointer.
Context* context = nullptr;
@@ -181,7 +181,7 @@
// parameter blocks it expects. The function checks that these match
// the sizes of the parameter blocks listed in parameter_blocks. The
// program aborts if a mismatch is detected. loss_function can be
- // NULL, in which case the cost of the term is just the squared norm
+ // nullptr, in which case the cost of the term is just the squared norm
// of the residuals.
//
// The user has the option of explicitly adding the parameter blocks
@@ -210,8 +210,8 @@
//
// Problem problem;
//
- // problem.AddResidualBlock(new MyUnaryCostFunction(...), NULL, x1);
- // problem.AddResidualBlock(new MyBinaryCostFunction(...), NULL, x2, x1);
+ // problem.AddResidualBlock(new MyUnaryCostFunction(...), nullptr, x1);
+ // problem.AddResidualBlock(new MyBinaryCostFunction(...), nullptr, x2, x1);
//
// Add a residual block by listing the parameter block pointers
// directly instead of wapping them in a container.
@@ -301,7 +301,7 @@
// Get the local parameterization object associated with this
// parameter block. If there is no parameterization object
- // associated then NULL is returned.
+ // associated then nullptr is returned.
const LocalParameterization* GetParameterization(double* values) const;
// Set the lower/upper bound for the parameter at position "index".
@@ -361,7 +361,7 @@
const CostFunction* GetCostFunctionForResidualBlock(
const ResidualBlockId residual_block) const;
- // Get the LossFunction for the given residual block. Returns NULL
+ // Get the LossFunction for the given residual block. Returns nullptr
// if no loss function is associated with this residual block.
const LossFunction* GetLossFunctionForResidualBlock(
const ResidualBlockId residual_block) const;
@@ -415,7 +415,7 @@
int num_threads = 1;
};
- // Evaluate Problem. Any of the output pointers can be NULL. Which
+ // Evaluate Problem. Any of the output pointers can be nullptr. Which
// residual blocks and parameter blocks are used is controlled by
// the EvaluateOptions struct above.
//
@@ -425,16 +425,16 @@
//
// Problem problem;
// double x = 1;
- // problem.AddResidualBlock(new MyCostFunction, NULL, &x);
+ // problem.AddResidualBlock(new MyCostFunction, nullptr, &x);
//
// double cost = 0.0;
- // problem.Evaluate(Problem::EvaluateOptions(), &cost, NULL, NULL, NULL);
+ // problem.Evaluate(Problem::EvaluateOptions(), &cost, nullptr, nullptr, nullptr);
//
// The cost is evaluated at x = 1. If you wish to evaluate the
// problem at x = 2, then
//
// x = 2;
- // problem.Evaluate(Problem::EvaluateOptions(), &cost, NULL, NULL, NULL);
+ // problem.Evaluate(Problem::EvaluateOptions(), &cost, nullptr, nullptr, nullptr);
//
// is the way to do so.
//
@@ -454,10 +454,43 @@
std::vector<double>* gradient,
CRSMatrix* jacobian);
+ // Evaluates the residual block, storing the scalar cost in *cost,
+ // the residual components in *residuals, and the jacobians between
+ // the parameters and residuals in jacobians[i], in row-major order.
+ //
+ // If residuals is nullptr, the residuals are not computed.
+ //
+ // If jacobians is nullptr, no Jacobians are computed. If
+ // jacobians[i] is nullptr, then the Jacobian for that parameter
+ // block is not computed.
+ //
+ // It is not okay to request the Jacobian w.r.t a parameter block
+ // that is constant.
+ //
+ // The return value indicates the success or failure. Even if the
+ // function returns false, the caller should expect the output
+ // memory locations to have been modified.
+ //
+ // The returned cost and jacobians have had robustification and
+ // local parameterizations applied already; for example, the
+ // jacobian for a 4-dimensional quaternion parameter using the
+ // "QuaternionParameterization" is num_residuals by 3 instead of
+ // num_residuals by 4.
+ //
+ // 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.
+ bool EvaluateResidualBlock(ResidualBlockId residual_block_id,
+ bool apply_loss_function,
+ double* cost,
+ double* residuals,
+ double** jacobians) const;
private:
friend class Solver;
friend class Covariance;
- std::unique_ptr<internal::ProblemImpl> problem_impl_;
+ std::unique_ptr<internal::ProblemImpl> impl_;
};
} // namespace ceres
diff --git a/internal/ceres/covariance.cc b/internal/ceres/covariance.cc
index 068cd9c..ee29595 100644
--- a/internal/ceres/covariance.cc
+++ b/internal/ceres/covariance.cc
@@ -52,13 +52,13 @@
bool Covariance::Compute(
const vector<pair<const double*, const double*>>& covariance_blocks,
Problem* problem) {
- return impl_->Compute(covariance_blocks, problem->problem_impl_.get());
+ return impl_->Compute(covariance_blocks, problem->impl_.get());
}
bool Covariance::Compute(
const vector<const double*>& parameter_blocks,
Problem* problem) {
- return impl_->Compute(parameter_blocks, problem->problem_impl_.get());
+ return impl_->Compute(parameter_blocks, problem->impl_.get());
}
bool Covariance::GetCovarianceBlock(const double* parameter_block1,
diff --git a/internal/ceres/problem.cc b/internal/ceres/problem.cc
index 6939b46..110bb14 100644
--- a/internal/ceres/problem.cc
+++ b/internal/ceres/problem.cc
@@ -32,6 +32,7 @@
#include "ceres/problem.h"
#include <vector>
+
#include "ceres/crs_matrix.h"
#include "ceres/problem_impl.h"
@@ -39,92 +40,87 @@
using std::vector;
-Problem::Problem() : problem_impl_(new internal::ProblemImpl) {}
+Problem::Problem() : impl_(new internal::ProblemImpl) {}
Problem::Problem(const Problem::Options& options)
- : problem_impl_(new internal::ProblemImpl(options)) {}
+ : impl_(new internal::ProblemImpl(options)) {}
Problem::~Problem() {}
ResidualBlockId Problem::AddResidualBlock(
CostFunction* cost_function,
LossFunction* loss_function,
const vector<double*>& parameter_blocks) {
- return problem_impl_->AddResidualBlock(
- cost_function,
- loss_function,
- parameter_blocks.data(),
- static_cast<int>(parameter_blocks.size()));
+ return impl_->AddResidualBlock(cost_function,
+ loss_function,
+ parameter_blocks.data(),
+ static_cast<int>(parameter_blocks.size()));
}
-ResidualBlockId Problem::AddResidualBlock(
- CostFunction* cost_function,
- LossFunction* loss_function,
- double* const* const parameter_blocks,
- int num_parameter_blocks) {
- return problem_impl_->AddResidualBlock(cost_function,
- loss_function,
- parameter_blocks,
- num_parameter_blocks);
+ResidualBlockId Problem::AddResidualBlock(CostFunction* cost_function,
+ LossFunction* loss_function,
+ double* const* const parameter_blocks,
+ int num_parameter_blocks) {
+ return impl_->AddResidualBlock(
+ cost_function, loss_function, parameter_blocks, num_parameter_blocks);
}
void Problem::AddParameterBlock(double* values, int size) {
- problem_impl_->AddParameterBlock(values, size);
+ impl_->AddParameterBlock(values, size);
}
void Problem::AddParameterBlock(double* values,
int size,
LocalParameterization* local_parameterization) {
- problem_impl_->AddParameterBlock(values, size, local_parameterization);
+ impl_->AddParameterBlock(values, size, local_parameterization);
}
void Problem::RemoveResidualBlock(ResidualBlockId residual_block) {
- problem_impl_->RemoveResidualBlock(residual_block);
+ impl_->RemoveResidualBlock(residual_block);
}
void Problem::RemoveParameterBlock(double* values) {
- problem_impl_->RemoveParameterBlock(values);
+ impl_->RemoveParameterBlock(values);
}
void Problem::SetParameterBlockConstant(double* values) {
- problem_impl_->SetParameterBlockConstant(values);
+ impl_->SetParameterBlockConstant(values);
}
void Problem::SetParameterBlockVariable(double* values) {
- problem_impl_->SetParameterBlockVariable(values);
+ impl_->SetParameterBlockVariable(values);
}
bool Problem::IsParameterBlockConstant(double* values) const {
- return problem_impl_->IsParameterBlockConstant(values);
+ return impl_->IsParameterBlockConstant(values);
}
void Problem::SetParameterization(
- double* values,
- LocalParameterization* local_parameterization) {
- problem_impl_->SetParameterization(values, local_parameterization);
+ double* values, LocalParameterization* local_parameterization) {
+ impl_->SetParameterization(values, local_parameterization);
}
const LocalParameterization* Problem::GetParameterization(
double* values) const {
- return problem_impl_->GetParameterization(values);
+ return impl_->GetParameterization(values);
}
void Problem::SetParameterLowerBound(double* values,
int index,
double lower_bound) {
- problem_impl_->SetParameterLowerBound(values, index, lower_bound);
+ impl_->SetParameterLowerBound(values, index, lower_bound);
}
void Problem::SetParameterUpperBound(double* values,
int index,
double upper_bound) {
- problem_impl_->SetParameterUpperBound(values, index, upper_bound);
+ impl_->SetParameterUpperBound(values, index, upper_bound);
}
double Problem::GetParameterUpperBound(double* values, int index) const {
- return problem_impl_->GetParameterUpperBound(values, index);
+ return impl_->GetParameterUpperBound(values, index);
}
double Problem::GetParameterLowerBound(double* values, int index) const {
- return problem_impl_->GetParameterLowerBound(values, index);
+ return impl_->GetParameterLowerBound(values, index);
}
bool Problem::Evaluate(const EvaluateOptions& evaluate_options,
@@ -132,72 +128,66 @@
vector<double>* residuals,
vector<double>* gradient,
CRSMatrix* jacobian) {
- return problem_impl_->Evaluate(evaluate_options,
- cost,
- residuals,
- gradient,
- jacobian);
+ return impl_->Evaluate(evaluate_options, cost, residuals, gradient, jacobian);
}
-int Problem::NumParameterBlocks() const {
- return problem_impl_->NumParameterBlocks();
+bool Problem::EvaluateResidualBlock(ResidualBlockId residual_block_id,
+ bool apply_loss_function,
+ double* cost,
+ double* residuals,
+ double** jacobians) const {
+ return impl_->EvaluateResidualBlock(
+ residual_block_id, apply_loss_function, cost, residuals, jacobians);
}
-int Problem::NumParameters() const {
- return problem_impl_->NumParameters();
-}
+int Problem::NumParameterBlocks() const { return impl_->NumParameterBlocks(); }
-int Problem::NumResidualBlocks() const {
- return problem_impl_->NumResidualBlocks();
-}
+int Problem::NumParameters() const { return impl_->NumParameters(); }
-int Problem::NumResiduals() const {
- return problem_impl_->NumResiduals();
-}
+int Problem::NumResidualBlocks() const { return impl_->NumResidualBlocks(); }
+
+int Problem::NumResiduals() const { return impl_->NumResiduals(); }
int Problem::ParameterBlockSize(const double* parameter_block) const {
- return problem_impl_->ParameterBlockSize(parameter_block);
+ return impl_->ParameterBlockSize(parameter_block);
}
int Problem::ParameterBlockLocalSize(const double* parameter_block) const {
- return problem_impl_->ParameterBlockLocalSize(parameter_block);
+ return impl_->ParameterBlockLocalSize(parameter_block);
}
bool Problem::HasParameterBlock(const double* values) const {
- return problem_impl_->HasParameterBlock(values);
+ return impl_->HasParameterBlock(values);
}
void Problem::GetParameterBlocks(vector<double*>* parameter_blocks) const {
- problem_impl_->GetParameterBlocks(parameter_blocks);
+ impl_->GetParameterBlocks(parameter_blocks);
}
void Problem::GetResidualBlocks(
vector<ResidualBlockId>* residual_blocks) const {
- problem_impl_->GetResidualBlocks(residual_blocks);
+ impl_->GetResidualBlocks(residual_blocks);
}
void Problem::GetParameterBlocksForResidualBlock(
const ResidualBlockId residual_block,
vector<double*>* parameter_blocks) const {
- problem_impl_->GetParameterBlocksForResidualBlock(residual_block,
- parameter_blocks);
+ impl_->GetParameterBlocksForResidualBlock(residual_block, parameter_blocks);
}
const CostFunction* Problem::GetCostFunctionForResidualBlock(
const ResidualBlockId residual_block) const {
- return problem_impl_->GetCostFunctionForResidualBlock(residual_block);
+ return impl_->GetCostFunctionForResidualBlock(residual_block);
}
const LossFunction* Problem::GetLossFunctionForResidualBlock(
const ResidualBlockId residual_block) const {
- return problem_impl_->GetLossFunctionForResidualBlock(residual_block);
+ return impl_->GetLossFunctionForResidualBlock(residual_block);
}
void Problem::GetResidualBlocksForParameterBlock(
- const double* values,
- vector<ResidualBlockId>* residual_blocks) const {
- problem_impl_->GetResidualBlocksForParameterBlock(values,
- residual_blocks);
+ const double* values, vector<ResidualBlockId>* residual_blocks) const {
+ impl_->GetResidualBlocksForParameterBlock(values, residual_blocks);
}
} // namespace ceres
diff --git a/internal/ceres/problem_impl.cc b/internal/ceres/problem_impl.cc
index 40d5aa2..587aef6 100644
--- a/internal/ceres/problem_impl.cc
+++ b/internal/ceres/problem_impl.cc
@@ -41,6 +41,7 @@
#include <utility>
#include <vector>
+#include "ceres/internal/fixed_array.h"
#include "ceres/casts.h"
#include "ceres/compressed_row_jacobian_writer.h"
#include "ceres/compressed_row_sparse_matrix.h"
@@ -771,6 +772,37 @@
return status;
}
+bool ProblemImpl::EvaluateResidualBlock(ResidualBlock* residual_block,
+ bool apply_loss_function,
+ double* cost,
+ double* residuals,
+ double** jacobians) const {
+ ParameterBlock* const* parameter_blocks = residual_block->parameter_blocks();
+ const int num_parameter_blocks = residual_block->NumParameterBlocks();
+ for (int i = 0; i < num_parameter_blocks; ++i) {
+ ParameterBlock* parameter_block = parameter_blocks[i];
+ if (parameter_block->IsConstant()) {
+ if (jacobians != nullptr && jacobians[i] != nullptr) {
+ LOG(ERROR) << "Jacobian requested for parameter block : " << i
+ << ". But the parameter block is marked constant.";
+ return false;
+ }
+ } else {
+ CHECK(parameter_block->SetState(parameter_block->user_state()))
+ << "Congratulations, you found a Ceres bug! Please report this error "
+ << "to the developers.";
+ }
+ }
+
+ double dummy_cost = 0.0;
+ FixedArray<double> scratch(residual_block->NumScratchDoublesForEvaluate());
+ return residual_block->Evaluate(apply_loss_function,
+ cost ? cost : &dummy_cost,
+ residuals,
+ jacobians,
+ scratch.data());
+}
+
int ProblemImpl::NumParameterBlocks() const {
return program_->NumParameterBlocks();
}
diff --git a/internal/ceres/problem_impl.h b/internal/ceres/problem_impl.h
index eabeaed..d0fab3f 100644
--- a/internal/ceres/problem_impl.h
+++ b/internal/ceres/problem_impl.h
@@ -122,6 +122,12 @@
std::vector<double>* gradient,
CRSMatrix* jacobian);
+ bool EvaluateResidualBlock(ResidualBlock* residual_block,
+ bool apply_loss_function,
+ double* cost,
+ double* residuals,
+ double** jacobians) const;
+
int NumParameterBlocks() const;
int NumParameters() const;
int NumResidualBlocks() const;
diff --git a/internal/ceres/problem_test.cc b/internal/ceres/problem_test.cc
index 736e295..03e8da7 100644
--- a/internal/ceres/problem_test.cc
+++ b/internal/ceres/problem_test.cc
@@ -30,9 +30,10 @@
// keir@google.com (Keir Mierle)
#include "ceres/problem.h"
-#include "ceres/problem_impl.h"
#include <memory>
+
+#include "ceres/autodiff_cost_function.h"
#include "ceres/casts.h"
#include "ceres/cost_function.h"
#include "ceres/crs_matrix.h"
@@ -42,6 +43,7 @@
#include "ceres/loss_function.h"
#include "ceres/map_util.h"
#include "ceres/parameter_block.h"
+#include "ceres/problem_impl.h"
#include "ceres/program.h"
#include "ceres/sized_cost_function.h"
#include "ceres/sparse_matrix.h"
@@ -1528,6 +1530,498 @@
CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
}
+struct IdentityFunctor {
+ template <typename T>
+ bool operator()(const T* x, const T* y, T* residuals) const {
+ residuals[0] = x[0];
+ residuals[1] = x[1];
+ residuals[2] = y[0];
+ residuals[3] = y[1];
+ residuals[4] = y[2];
+ return true;
+ }
+
+ static CostFunction* Create() {
+ return new AutoDiffCostFunction<IdentityFunctor, 5, 2, 3>(
+ new IdentityFunctor);
+ }
+};
+
+class ProblemEvaluateResidualBlockTest : public ::testing::Test {
+ public:
+ static constexpr bool kApplyLossFunction = true;
+ static constexpr bool kDoNotApplyLossFunction = false;
+ static double kLossFunctionScale;
+
+ protected:
+ void SetUp() {
+ loss_function_ = new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP);
+ }
+
+ LossFunction* loss_function_;
+ ProblemImpl problem_;
+ double x_[2] = {1, 2};
+ double y_[3] = {1, 2, 3};
+};
+
+double ProblemEvaluateResidualBlockTest::kLossFunctionScale = 2.0;
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockNoLossFunctionFullEval) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Matrix expected_dfdx = Matrix::Zero(5, 2);
+ expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
+ Matrix expected_dfdy = Matrix::Zero(5, 3);
+ expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ Matrix actual_dfdy(5, 3);
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdx;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockNoLossFunctionNullEval) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(
+ residual_block_id, kApplyLossFunction, nullptr, nullptr, nullptr));
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockNoLossFunctionCost) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(
+ residual_block_id, kApplyLossFunction, &actual_cost, nullptr, nullptr));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockNoLossFunctionCostAndResidual) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ nullptr));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockNoLossFunctionCostResidualAndOneJacobian) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Matrix expected_dfdx = Matrix::Zero(5, 2);
+ expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ double* jacobians[2] = {actual_dfdx.data(), nullptr};
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdx;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockNoLossFunctionResidual) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Vector actual_f(5);
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ nullptr,
+ actual_f.data(),
+ nullptr));
+
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockWithLossFunction) {
+ ResidualBlockId residual_block_id = problem_.AddResidualBlock(
+ IdentityFunctor::Create(), loss_function_, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ expected_f *= std::sqrt(kLossFunctionScale);
+ Matrix expected_dfdx = Matrix::Zero(5, 2);
+ expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
+ expected_dfdx *= std::sqrt(kLossFunctionScale);
+ Matrix expected_dfdy = Matrix::Zero(5, 3);
+ expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
+ expected_dfdy *= std::sqrt(kLossFunctionScale);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ Matrix actual_dfdy(5, 3);
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdx;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockWithLossFunctionDisabled) {
+ ResidualBlockId residual_block_id = problem_.AddResidualBlock(
+ IdentityFunctor::Create(), loss_function_, x_, y_);
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Matrix expected_dfdx = Matrix::Zero(5, 2);
+ expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
+ Matrix expected_dfdy = Matrix::Zero(5, 3);
+ expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ Matrix actual_dfdy(5, 3);
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kDoNotApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdx;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockWithOneLocalParameterization) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ problem_.SetParameterization(x_, new SubsetParameterization(2, {1}));
+
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Matrix expected_dfdx = Matrix::Zero(5, 1);
+ expected_dfdx.block(0, 0, 1, 1) = Matrix::Identity(1, 1);
+ Matrix expected_dfdy = Matrix::Zero(5, 3);
+ expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 1);
+ Matrix actual_dfdy(5, 3);
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdx;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockWithTwoLocalParameterizations) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ problem_.SetParameterization(x_, new SubsetParameterization(2, {1}));
+ problem_.SetParameterization(y_, new SubsetParameterization(3, {2}));
+
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Matrix expected_dfdx = Matrix::Zero(5, 1);
+ expected_dfdx.block(0, 0, 1, 1) = Matrix::Identity(1, 1);
+ Matrix expected_dfdy = Matrix::Zero(5, 2);
+ expected_dfdy.block(2, 0, 2, 2) = Matrix::Identity(2, 2);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 1);
+ Matrix actual_dfdy(5, 2);
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdx;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockWithOneConstantParameterBlock) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ problem_.SetParameterBlockConstant(x_);
+
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ Matrix expected_dfdy = Matrix::Zero(5, 3);
+ expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ Matrix actual_dfdy(5, 3);
+
+ // Try evaluating both Jacobians, this should fail.
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ jacobians[0] = nullptr;
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockWithAllConstantParameterBlocks) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ problem_.SetParameterBlockConstant(x_);
+ problem_.SetParameterBlockConstant(y_);
+
+ Vector expected_f(5);
+ expected_f << 1, 2, 1, 2, 3;
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ Matrix actual_dfdy(5, 3);
+
+ // Try evaluating with one or more Jacobians, this should fail.
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ jacobians[0] = nullptr;
+ EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+ jacobians[1] = nullptr;
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+}
+
+TEST_F(ProblemEvaluateResidualBlockTest,
+ OneResidualBlockWithOneParameterBlockConstantAndParameterBlockChanged) {
+ ResidualBlockId residual_block_id =
+ problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
+ problem_.SetParameterBlockConstant(x_);
+
+ x_[0] = 2;
+ y_[2] = 1;
+ Vector expected_f(5);
+ expected_f << 2, 2, 1, 2, 1;
+ Matrix expected_dfdy = Matrix::Zero(5, 3);
+ expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
+ double expected_cost = expected_f.squaredNorm() / 2.0;
+
+ double actual_cost;
+ Vector actual_f(5);
+ Matrix actual_dfdx(5, 2);
+ Matrix actual_dfdy(5, 3);
+
+ // Try evaluating with one or more Jacobians, this should fail.
+ double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
+ EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+
+ jacobians[0] = nullptr;
+ EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
+ kApplyLossFunction,
+ &actual_cost,
+ actual_f.data(),
+ jacobians));
+ EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_cost;
+ EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_f;
+ EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
+ 0,
+ std::numeric_limits<double>::epsilon())
+ << actual_dfdy;
+}
+
TEST(Problem, SetAndGetParameterLowerBound) {
Problem problem;
double x[] = {1.0, 2.0};
diff --git a/internal/ceres/residual_block.cc b/internal/ceres/residual_block.cc
index 7bfcc29..0bf30bc 100644
--- a/internal/ceres/residual_block.cc
+++ b/internal/ceres/residual_block.cc
@@ -80,11 +80,11 @@
// Put pointers into the scratch space into global_jacobians as appropriate.
FixedArray<double*, 8> global_jacobians(num_parameter_blocks);
- if (jacobians != NULL) {
+ if (jacobians != nullptr) {
for (int i = 0; i < num_parameter_blocks; ++i) {
const ParameterBlock* parameter_block = parameter_blocks_[i];
- if (jacobians[i] != NULL &&
- parameter_block->LocalParameterizationJacobian() != NULL) {
+ if (jacobians[i] != nullptr &&
+ parameter_block->LocalParameterizationJacobian() != nullptr) {
global_jacobians[i] = scratch;
scratch += num_residuals * parameter_block->Size();
} else {
@@ -94,7 +94,7 @@
}
// If the caller didn't request residuals, use the scratch space for them.
- bool outputting_residuals = (residuals != NULL);
+ bool outputting_residuals = (residuals != nullptr);
if (!outputting_residuals) {
residuals = scratch;
}
@@ -103,7 +103,7 @@
// the CostFunction::Evaluate call, to see if all the return values
// that were required were written to and that they are finite.
double** eval_jacobians =
- (jacobians != NULL) ? global_jacobians.data() : NULL;
+ (jacobians != nullptr) ? global_jacobians.data() : nullptr;
InvalidateEvaluation(*this, cost, residuals, eval_jacobians);
@@ -134,13 +134,13 @@
double squared_norm = VectorRef(residuals, num_residuals).squaredNorm();
// Update the jacobians with the local parameterizations.
- if (jacobians != NULL) {
+ if (jacobians != nullptr) {
for (int i = 0; i < num_parameter_blocks; ++i) {
- if (jacobians[i] != NULL) {
+ if (jacobians[i] != nullptr) {
const ParameterBlock* parameter_block = parameter_blocks_[i];
// Apply local reparameterization to the jacobians.
- if (parameter_block->LocalParameterizationJacobian() != NULL) {
+ if (parameter_block->LocalParameterizationJacobian() != nullptr) {
// jacobians[i] = global_jacobians[i] * global_to_local_jacobian.
MatrixMatrixMultiply<Dynamic, Dynamic, Dynamic, Dynamic, 0>(
global_jacobians[i],
@@ -155,7 +155,7 @@
}
}
- if (loss_function_ == NULL || !apply_loss_function) {
+ if (loss_function_ == nullptr || !apply_loss_function) {
*cost = 0.5 * squared_norm;
return true;
}
@@ -166,16 +166,16 @@
// No jacobians and not outputting residuals? All done. Doing an early exit
// here avoids constructing the "Corrector" object below in a common case.
- if (jacobians == NULL && !outputting_residuals) {
+ if (jacobians == nullptr && !outputting_residuals) {
return true;
}
// Correct for the effects of the loss function. The jacobians need to be
// corrected before the residuals, since they use the uncorrected residuals.
Corrector correct(squared_norm, rho);
- if (jacobians != NULL) {
+ if (jacobians != nullptr) {
for (int i = 0; i < num_parameter_blocks; ++i) {
- if (jacobians[i] != NULL) {
+ if (jacobians[i] != nullptr) {
const ParameterBlock* parameter_block = parameter_blocks_[i];
// Correct the jacobians for the loss function.
@@ -205,8 +205,7 @@
int scratch_doubles = 1;
for (int i = 0; i < num_parameters; ++i) {
const ParameterBlock* parameter_block = parameter_blocks_[i];
- if (!parameter_block->IsConstant() &&
- parameter_block->LocalParameterizationJacobian() != NULL) {
+ if (parameter_block->LocalParameterizationJacobian() != nullptr) {
scratch_doubles += parameter_block->Size();
}
}
diff --git a/internal/ceres/residual_block.h b/internal/ceres/residual_block.h
index 6964c6d..a2e4425 100644
--- a/internal/ceres/residual_block.h
+++ b/internal/ceres/residual_block.h
@@ -82,6 +82,8 @@
// computed. If jacobians[i] is NULL, then the jacobian for that parameter is
// not computed.
//
+ // cost must not be null.
+ //
// Evaluate needs scratch space which must be supplied by the caller via
// scratch. The array should have at least NumScratchDoublesForEvaluate()
// space available.
diff --git a/internal/ceres/solver.cc b/internal/ceres/solver.cc
index f8ad2c9..65ddf5b 100644
--- a/internal/ceres/solver.cc
+++ b/internal/ceres/solver.cc
@@ -491,7 +491,7 @@
return;
}
- ProblemImpl* problem_impl = problem->problem_impl_.get();
+ ProblemImpl* problem_impl = problem->impl_.get();
Program* program = problem_impl->mutable_program();
PreSolveSummarize(options, problem_impl, summary);
@@ -566,7 +566,7 @@
}
const double postprocessor_start_time = WallTimeInSeconds();
- problem_impl = problem->problem_impl_.get();
+ problem_impl = problem->impl_.get();
program = problem_impl->mutable_program();
// On exit, ensure that the parameter blocks again point at the user
// provided values and the parameter blocks are numbered according