Compute the gradient if requested in the evaluator
This extends the Evaluator interface to support evaluating the
gradient in addition to the residuals and jacobian, if requested.
bool Evaluate(const double* state,
double* cost,
double* residuals,
double* gradient, <----------- NEW
SparseMatrix* jacobian) = 0;
The ProgramEvaluator is extended to support the new gradient
evaluation. This required some gymnastics around the block
evaluate preparer, which now contains a scratch evaluate preparer
for the case that no jacobian is requested but the gradient is.
Gradient evaluation is a prerequisite for the planned suite of
first order methods, including nonlinear conjugate gradient,
CG_DESCENT, L-BFGS, trust region with line search, and more.
This also considerably refactors the evaluator_test to make it
shorter and check the results for all combinations of the optional
parameters [residuals, gradient, jacobian].
Change-Id: Ic7d0fec028dc5ffebc08ee079ad04eeaf6e02582
diff --git a/internal/ceres/block_evaluate_preparer.cc b/internal/ceres/block_evaluate_preparer.cc
index 05e63eb..9edc4fa 100644
--- a/internal/ceres/block_evaluate_preparer.cc
+++ b/internal/ceres/block_evaluate_preparer.cc
@@ -40,16 +40,26 @@
namespace ceres {
namespace internal {
-void BlockEvaluatePreparer::Init(int** jacobian_layout) {
+void BlockEvaluatePreparer::Init(int const* const* jacobian_layout,
+ int max_derivatives_per_residual_block) {
jacobian_layout_ = jacobian_layout;
+ scratch_evaluate_preparer_.Init(max_derivatives_per_residual_block);
}
// Point the jacobian blocks directly into the block sparse matrix.
void BlockEvaluatePreparer::Prepare(const ResidualBlock* residual_block,
int residual_block_index,
SparseMatrix* jacobian,
- double** jacobians) const {
- CHECK(jacobian != NULL);
+ double** jacobians) {
+ // If the overall jacobian is not available, use the scratch space.
+ if (jacobian == NULL) {
+ scratch_evaluate_preparer_.Prepare(residual_block,
+ residual_block_index,
+ jacobian,
+ jacobians);
+ return;
+ }
+
double* jacobian_values =
down_cast<BlockSparseMatrix*>(jacobian)->mutable_values();
diff --git a/internal/ceres/block_evaluate_preparer.h b/internal/ceres/block_evaluate_preparer.h
index a786931..354acc0 100644
--- a/internal/ceres/block_evaluate_preparer.h
+++ b/internal/ceres/block_evaluate_preparer.h
@@ -36,6 +36,8 @@
#ifndef CERES_INTERNAL_BLOCK_EVALUATE_PREPARER_H_
#define CERES_INTERNAL_BLOCK_EVALUATE_PREPARER_H_
+#include "ceres/scratch_evaluate_preparer.h"
+
namespace ceres {
namespace internal {
@@ -47,18 +49,26 @@
// Using Init() instead of a constructor allows for allocating this structure
// with new[]. This is because C++ doesn't allow passing arguments to objects
// constructed with new[] (as opposed to plain 'new').
- void Init(int** jacobian_layout);
+ void Init(int const* const* jacobian_layout,
+ int max_derivatives_per_residual_block);
// EvaluatePreparer interface
- // Point the jacobian blocks directly into the block sparse matrix.
+ // Point the jacobian blocks directly into the block sparse matrix, if
+ // jacobian is non-null. Otherwise, uses an internal per-thread buffer to
+ // store the jacobians temporarily.
void Prepare(const ResidualBlock* residual_block,
int residual_block_index,
SparseMatrix* jacobian,
- double** jacobians) const;
+ double** jacobians);
private:
int const* const* jacobian_layout_;
+
+ // For the case that the overall jacobian is not available, but the
+ // individual jacobians are requested, use a pass-through scratch evaluate
+ // preparer.
+ ScratchEvaluatePreparer scratch_evaluate_preparer_;
};
} // namespace internal
diff --git a/internal/ceres/block_jacobian_writer.cc b/internal/ceres/block_jacobian_writer.cc
index 52a58bb..f90c350 100644
--- a/internal/ceres/block_jacobian_writer.cc
+++ b/internal/ceres/block_jacobian_writer.cc
@@ -136,9 +136,12 @@
// makes the final Write() a nop.
BlockEvaluatePreparer* BlockJacobianWriter::CreateEvaluatePreparers(
int num_threads) {
+ int max_derivatives_per_residual_block =
+ program_->MaxDerivativesPerResidualBlock();
+
BlockEvaluatePreparer* preparers = new BlockEvaluatePreparer[num_threads];
for (int i = 0; i < num_threads; i++) {
- preparers[i].Init(&jacobian_layout_[0]);
+ preparers[i].Init(&jacobian_layout_[0], max_derivatives_per_residual_block);
}
return preparers;
}
diff --git a/internal/ceres/evaluator.h b/internal/ceres/evaluator.h
index adefdd2..e80efa1 100644
--- a/internal/ceres/evaluator.h
+++ b/internal/ceres/evaluator.h
@@ -95,6 +95,7 @@
virtual bool Evaluate(const double* state,
double* cost,
double* residuals,
+ double* gradient,
SparseMatrix* jacobian) = 0;
// Make a change delta (of size NumEffectiveParameters()) to state (of size
diff --git a/internal/ceres/evaluator_test.cc b/internal/ceres/evaluator_test.cc
index b7381a4..6fbb759 100644
--- a/internal/ceres/evaluator_test.cc
+++ b/internal/ceres/evaluator_test.cc
@@ -88,6 +88,15 @@
}
};
+struct ExpectedEvaluation {
+ int num_rows;
+ int num_cols;
+ double cost;
+ const double residuals[50];
+ const double gradient[50];
+ const double jacobian[200];
+};
+
struct EvaluatorTest
: public ::testing::TestWithParam<pair<LinearSolverType, int> > {
Evaluator* CreateEvaluator(Program* program) {
@@ -103,6 +112,93 @@
string error;
return Evaluator::Create(options, program, &error);
}
+
+ void CheckEvaluation(ProblemImpl *problem,
+ int expected_num_rows,
+ int expected_num_cols,
+ double expected_cost,
+ const double* expected_residuals,
+ const double* expected_gradient,
+ const double* expected_jacobian) {
+ scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem->mutable_program()));
+ int num_residuals = expected_num_rows;
+ int num_parameters = expected_num_cols;
+
+ double cost = -1;
+
+ Vector residuals(num_residuals);
+ residuals.setConstant(-2000);
+
+ Vector gradient(num_parameters);
+ gradient.setConstant(-3000);
+
+ scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
+
+ ASSERT_EQ(expected_num_rows, evaluator->NumResiduals());
+ ASSERT_EQ(expected_num_cols, evaluator->NumEffectiveParameters());
+ ASSERT_EQ(expected_num_rows, jacobian->num_rows());
+ ASSERT_EQ(expected_num_cols, jacobian->num_cols());
+
+ vector<double> state(evaluator->NumParameters());
+
+ ASSERT_TRUE(evaluator->Evaluate(
+ &state[0],
+ &cost,
+ expected_residuals != NULL ? &residuals[0] : NULL,
+ expected_gradient != NULL ? &gradient[0] : NULL,
+ expected_jacobian != NULL ? jacobian.get() : NULL));
+
+ EXPECT_EQ(expected_cost, cost);
+
+ if (expected_residuals != NULL) {
+ for (int i = 0; i < num_residuals; ++i) {
+ EXPECT_EQ(expected_residuals[i], residuals[i]) << i;
+ }
+ }
+
+ if (expected_gradient != NULL) {
+ ConstVectorRef expected_gradient_vector(expected_gradient,
+ expected_num_cols);
+
+ EXPECT_TRUE((gradient.array() ==
+ expected_gradient_vector.array()).all())
+ << "Actual:\n" << gradient.transpose()
+ << "\nExpected:\n" << expected_gradient_vector.transpose();
+ }
+
+ if (expected_jacobian != NULL) {
+ ConstMatrixRef expected_jacobian_matrix(expected_jacobian,
+ expected_num_rows,
+ expected_num_cols);
+ Matrix actual_jacobian;
+ jacobian->ToDenseMatrix(&actual_jacobian);
+
+ EXPECT_TRUE((actual_jacobian.array() ==
+ expected_jacobian_matrix.array()).all())
+ << "Actual:\n" << actual_jacobian
+ << "\nExpected:\n" << expected_jacobian;
+ }
+ }
+
+ // Try all combinations of parameters for the evaluator.
+ void CheckAllEvaluationCombinations(const ExpectedEvaluation &expected) {
+ for (int i = 0; i < 8; ++i) {
+ CheckEvaluation(&problem,
+ expected.num_rows,
+ expected.num_cols,
+ expected.cost,
+ (i & 1) ? expected.residuals : NULL,
+ (i & 2) ? expected.gradient : NULL,
+ (i & 4) ? expected.jacobian : NULL);
+ }
+ }
+
+ // The values are ignored completely by the cost function.
+ double x[2];
+ double y[3];
+ double z[4];
+
+ ProblemImpl problem;
};
void SetSparseMatrixConstant(SparseMatrix* sparse_matrix, double value) {
@@ -111,77 +207,32 @@
}
TEST_P(EvaluatorTest, SingleResidualProblem) {
- ProblemImpl problem;
-
- // The values are ignored completely by the cost function.
- double x[2];
- double y[3];
- double z[4];
- double state[9];
-
problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
NULL,
x, y, z);
- scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
- scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
- ASSERT_EQ(3, jacobian->num_rows());
- ASSERT_EQ(9, jacobian->num_cols());
-
- // Cost only; no residuals and no jacobian.
- {
- double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
- EXPECT_EQ(7.0, cost);
- }
-
- // Cost and residuals, no jacobian.
- {
- double cost = -1;
- double residuals[3] = { -2, -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
- EXPECT_EQ(7.0, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(3.0, residuals[2]);
- }
-
- // Cost, residuals, and jacobian.
- {
- double cost = -1;
- double residuals[3] = { -2, -2, -2 };
- SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
- EXPECT_EQ(7.0, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(3.0, residuals[2]);
-
- Matrix actual_jacobian;
- jacobian->ToDenseMatrix(&actual_jacobian);
-
- Matrix expected_jacobian(3, 9);
- expected_jacobian
- // x y z
- << 1, 2, 1, 2, 3, 1, 2, 3, 4,
- 1, 2, 1, 2, 3, 1, 2, 3, 4,
- 1, 2, 1, 2, 3, 1, 2, 3, 4;
-
- EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
- << "Actual:\n" << actual_jacobian
- << "\nExpected:\n" << expected_jacobian;
- }
+ ExpectedEvaluation expected = {
+ // Rows/columns
+ 3, 9,
+ // Cost
+ 7.0,
+ // Residuals
+ { 1.0, 2.0, 3.0 },
+ // Gradient
+ { 6.0, 12.0, // x
+ 6.0, 12.0, 18.0, // y
+ 6.0, 12.0, 18.0, 24.0, // z
+ },
+ // Jacobian
+ // x y z
+ { 1, 2, 1, 2, 3, 1, 2, 3, 4,
+ 1, 2, 1, 2, 3, 1, 2, 3, 4,
+ 1, 2, 1, 2, 3, 1, 2, 3, 4
+ }
+ };
+ CheckAllEvaluationCombinations(expected);
}
-
TEST_P(EvaluatorTest, SingleResidualProblemWithPermutedParameters) {
- ProblemImpl problem;
-
- // The values are ignored completely by the cost function.
- double x[2];
- double y[3];
- double z[4];
- double state[9];
-
// Add the parameters in explicit order to force the ordering in the program.
problem.AddParameterBlock(x, 2);
problem.AddParameterBlock(y, 3);
@@ -197,143 +248,76 @@
NULL,
z, y, x);
- scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
- scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
- ASSERT_EQ(3, jacobian->num_rows());
- ASSERT_EQ(9, jacobian->num_cols());
-
- // Cost only; no residuals and no jacobian.
- {
- double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
- EXPECT_EQ(7.0, cost);
- }
-
- // Cost and residuals, no jacobian.
- {
- double cost = -1;
- double residuals[3] = { -2, -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
- EXPECT_EQ(7.0, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(3.0, residuals[2]);
- }
-
- // Cost, residuals, and jacobian.
- {
- double cost = -1;
- double residuals[3] = { -2, -2, -2 };
- SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
- EXPECT_EQ(7.0, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(3.0, residuals[2]);
-
- Matrix actual_jacobian;
- jacobian->ToDenseMatrix(&actual_jacobian);
-
- Matrix expected_jacobian(3, 9);
- expected_jacobian
- // x y z
- << 1, 2, 1, 2, 3, 1, 2, 3, 4,
- 1, 2, 1, 2, 3, 1, 2, 3, 4,
- 1, 2, 1, 2, 3, 1, 2, 3, 4;
-
- EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
- << "Actual:\n" << actual_jacobian
- << "\nExpected:\n" << expected_jacobian;
- }
+ ExpectedEvaluation expected = {
+ // Rows/columns
+ 3, 9,
+ // Cost
+ 7.0,
+ // Residuals
+ { 1.0, 2.0, 3.0 },
+ // Gradient
+ { 6.0, 12.0, // x
+ 6.0, 12.0, 18.0, // y
+ 6.0, 12.0, 18.0, 24.0, // z
+ },
+ // Jacobian
+ // x y z
+ { 1, 2, 1, 2, 3, 1, 2, 3, 4,
+ 1, 2, 1, 2, 3, 1, 2, 3, 4,
+ 1, 2, 1, 2, 3, 1, 2, 3, 4
+ }
+ };
+ CheckAllEvaluationCombinations(expected);
}
+
TEST_P(EvaluatorTest, SingleResidualProblemWithNuisanceParameters) {
- ProblemImpl problem;
-
- // The values are ignored completely by the cost function.
- double x[2];
- double y[3];
- double z[4];
- double state[9];
-
// These parameters are not used.
- double w1[2];
- double w2[1];
- double w3[1];
- double w4[3];
+ double a[2];
+ double b[1];
+ double c[1];
+ double d[3];
// Add the parameters in a mixed order so the Jacobian is "checkered" with the
// values from the other parameters.
- problem.AddParameterBlock(w1, 2);
- problem.AddParameterBlock(x, 2);
- problem.AddParameterBlock(w2, 1);
- problem.AddParameterBlock(y, 3);
- problem.AddParameterBlock(w3, 1);
- problem.AddParameterBlock(z, 4);
- problem.AddParameterBlock(w4, 3);
+ problem.AddParameterBlock(a, 2);
+ problem.AddParameterBlock(x, 2);
+ problem.AddParameterBlock(b, 1);
+ problem.AddParameterBlock(y, 3);
+ problem.AddParameterBlock(c, 1);
+ problem.AddParameterBlock(z, 4);
+ problem.AddParameterBlock(d, 3);
problem.AddResidualBlock(new ParameterIgnoringCostFunction<1, 3, 2, 3, 4>,
NULL,
x, y, z);
- scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
- scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
- ASSERT_EQ(3, jacobian->num_rows());
- ASSERT_EQ(16, jacobian->num_cols());
-
- // Cost only; no residuals and no jacobian.
- {
- double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
- EXPECT_EQ(7.0, cost);
- }
-
- // Cost and residuals, no jacobian.
- {
- double cost = -1;
- double residuals[3] = { -2, -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
- EXPECT_EQ(7.0, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(3.0, residuals[2]);
- }
-
- // Cost, residuals, and jacobian.
- {
- double cost = -1;
- double residuals[3] = { -2, -2, -2 };
- SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
- EXPECT_EQ(7.0, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(3.0, residuals[2]);
-
- Matrix actual_jacobian;
- jacobian->ToDenseMatrix(&actual_jacobian);
-
- Matrix expected_jacobian(3, 16);
- expected_jacobian
- // w1 x w2 y w2 z w3
- << 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
- 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
- 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0;
-
- EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
- << "Actual:\n" << actual_jacobian
- << "\nExpected:\n" << expected_jacobian;
- }
+ ExpectedEvaluation expected = {
+ // Rows/columns
+ 3, 16,
+ // Cost
+ 7.0,
+ // Residuals
+ { 1.0, 2.0, 3.0 },
+ // Gradient
+ { 0.0, 0.0, // a
+ 6.0, 12.0, // x
+ 0.0, // b
+ 6.0, 12.0, 18.0, // y
+ 0.0, // c
+ 6.0, 12.0, 18.0, 24.0, // z
+ 0.0, 0.0, 0.0, // d
+ },
+ // Jacobian
+ // a x b y c z d
+ { 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
+ 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0,
+ 0, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0, 0, 0
+ }
+ };
+ CheckAllEvaluationCombinations(expected);
}
TEST_P(EvaluatorTest, MultipleResidualProblem) {
- ProblemImpl problem;
-
- // The values are ignored completely by the cost function.
- double x[2];
- double y[3];
- double z[4];
- double state[9];
-
// Add the parameters in explicit order to force the ordering in the program.
problem.AddParameterBlock(x, 2);
problem.AddParameterBlock(y, 3);
@@ -354,63 +338,22 @@
NULL,
y, z);
- scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
- scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
- ASSERT_EQ(9, jacobian->num_rows());
- ASSERT_EQ(9, jacobian->num_cols());
-
- // f g h
- double expected_cost = (1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0;
-
-
- // Cost only; no residuals and no jacobian.
- {
- double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
- EXPECT_EQ(expected_cost, cost);
- }
-
- // Cost and residuals, no jacobian.
- {
- double cost = -1;
- double residuals[9] = { -2, -2, -2, -2, -2, -2, -2, -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
- EXPECT_EQ(expected_cost, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(1.0, residuals[2]);
- EXPECT_EQ(2.0, residuals[3]);
- EXPECT_EQ(3.0, residuals[4]);
- EXPECT_EQ(1.0, residuals[5]);
- EXPECT_EQ(2.0, residuals[6]);
- EXPECT_EQ(3.0, residuals[7]);
- EXPECT_EQ(4.0, residuals[8]);
- }
-
- // Cost, residuals, and jacobian.
- {
- double cost = -1;
- double residuals[9] = { -2, -2, -2, -2, -2, -2, -2, -2, -2 };
- SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
- EXPECT_EQ(expected_cost, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(1.0, residuals[2]);
- EXPECT_EQ(2.0, residuals[3]);
- EXPECT_EQ(3.0, residuals[4]);
- EXPECT_EQ(1.0, residuals[5]);
- EXPECT_EQ(2.0, residuals[6]);
- EXPECT_EQ(3.0, residuals[7]);
- EXPECT_EQ(4.0, residuals[8]);
-
- Matrix actual_jacobian;
- jacobian->ToDenseMatrix(&actual_jacobian);
-
- Matrix expected_jacobian(9, 9);
- expected_jacobian <<
+ ExpectedEvaluation expected = {
+ // Rows/columns
+ 9, 9,
+ // Cost
+ // f g h
+ ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
+ // Residuals
+ { 1.0, 2.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0 },
+ // Gradient
+ { 15.0, 30.0, // x
+ 33.0, 66.0, 99.0, // y
+ 42.0, 84.0, 126.0, 168.0 // z
+ },
+ // Jacobian
// x y z
- /* f(x, y) */ 1, 2, 1, 2, 3, 0, 0, 0, 0,
+ { /* f(x, y) */ 1, 2, 1, 2, 3, 0, 0, 0, 0,
1, 2, 1, 2, 3, 0, 0, 0, 0,
/* g(x, z) */ 2, 4, 0, 0, 0, 2, 4, 6, 8,
@@ -420,23 +363,13 @@
/* h(y, z) */ 0, 0, 3, 6, 9, 3, 6, 9, 12,
0, 0, 3, 6, 9, 3, 6, 9, 12,
0, 0, 3, 6, 9, 3, 6, 9, 12,
- 0, 0, 3, 6, 9, 3, 6, 9, 12;
-
- EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
- << "Actual:\n" << actual_jacobian
- << "\nExpected:\n" << expected_jacobian;
- }
+ 0, 0, 3, 6, 9, 3, 6, 9, 12
+ }
+ };
+ CheckAllEvaluationCombinations(expected);
}
TEST_P(EvaluatorTest, MultipleResidualsWithLocalParameterizations) {
- ProblemImpl problem;
-
- // The values are ignored completely by the cost function.
- double x[2];
- double y[3];
- double z[4];
- double state[9];
-
// Add the parameters in explicit order to force the ordering in the program.
problem.AddParameterBlock(x, 2);
@@ -465,64 +398,22 @@
NULL,
y, z);
- scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
- scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
- ASSERT_EQ(9, jacobian->num_rows());
- ASSERT_EQ(7, jacobian->num_cols());
-
- // f g h
- double expected_cost = (1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0;
-
-
- // Cost only; no residuals and no jacobian.
- {
- double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
- EXPECT_EQ(expected_cost, cost);
- }
-
- // Cost and residuals, no jacobian.
- {
- double cost = -1;
- double residuals[9] = { -2, -2, -2, -2, -2, -2, -2, -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
- EXPECT_EQ(expected_cost, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(1.0, residuals[2]);
- EXPECT_EQ(2.0, residuals[3]);
- EXPECT_EQ(3.0, residuals[4]);
- EXPECT_EQ(1.0, residuals[5]);
- EXPECT_EQ(2.0, residuals[6]);
- EXPECT_EQ(3.0, residuals[7]);
- EXPECT_EQ(4.0, residuals[8]);
- }
-
- // Cost, residuals, and jacobian.
- {
- double cost = -1;
- double residuals[9] = { -2, -2, -2, -2, -2, -2, -2, -2, -2 };
- SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
- EXPECT_EQ(expected_cost, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(1.0, residuals[2]);
- EXPECT_EQ(2.0, residuals[3]);
- EXPECT_EQ(3.0, residuals[4]);
- EXPECT_EQ(1.0, residuals[5]);
- EXPECT_EQ(2.0, residuals[6]);
- EXPECT_EQ(3.0, residuals[7]);
- EXPECT_EQ(4.0, residuals[8]);
-
- Matrix actual_jacobian;
- jacobian->ToDenseMatrix(&actual_jacobian);
-
- // Note y and z are missing columns due to the subset parameterization.
- Matrix expected_jacobian(9, 7);
- expected_jacobian <<
- // x y z
- /* f(x, y) */ 1, 2, 2, 3, 0, 0, 0,
+ ExpectedEvaluation expected = {
+ // Rows/columns
+ 9, 7,
+ // Cost
+ // f g h
+ ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
+ // Residuals
+ { 1.0, 2.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0 },
+ // Gradient
+ { 15.0, 30.0, // x
+ 66.0, 99.0, // y
+ 42.0, 126.0, 168.0 // z
+ },
+ // Jacobian
+ // x y z
+ { /* f(x, y) */ 1, 2, 2, 3, 0, 0, 0,
1, 2, 2, 3, 0, 0, 0,
/* g(x, z) */ 2, 4, 0, 0, 2, 6, 8,
@@ -532,17 +423,13 @@
/* h(y, z) */ 0, 0, 6, 9, 3, 9, 12,
0, 0, 6, 9, 3, 9, 12,
0, 0, 6, 9, 3, 9, 12,
- 0, 0, 6, 9, 3, 9, 12;
-
- EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
- << "Actual:\n" << actual_jacobian
- << "\nExpected:\n" << expected_jacobian;
- }
+ 0, 0, 6, 9, 3, 9, 12
+ }
+ };
+ CheckAllEvaluationCombinations(expected);
}
TEST_P(EvaluatorTest, MultipleResidualProblemWithSomeConstantParameters) {
- ProblemImpl problem;
-
// The values are ignored completely by the cost function.
double x[2];
double y[3];
@@ -576,71 +463,30 @@
// Normally, the preprocessing of the program that happens in solver_impl
// takes care of this, but we don't want to invoke the solver here.
Program reduced_program;
- *reduced_program.mutable_residual_blocks() =
- problem.program().residual_blocks();
- *reduced_program.mutable_parameter_blocks() =
- problem.program().parameter_blocks();
+ vector<ParameterBlock*>* parameter_blocks =
+ problem.mutable_program()->mutable_parameter_blocks();
- // "z" is the last parameter; pop it off.
- reduced_program.mutable_parameter_blocks()->pop_back();
+ // "z" is the last parameter; save it for later and pop it off temporarily.
+ // Note that "z" will still get read during evaluation, so it cannot be
+ // deleted at this point.
+ ParameterBlock* parameter_block_z = parameter_blocks->back();
+ parameter_blocks->pop_back();
- scoped_ptr<Evaluator> evaluator(CreateEvaluator(&reduced_program));
- scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
- ASSERT_EQ(9, jacobian->num_rows());
- ASSERT_EQ(5, jacobian->num_cols());
-
- // f g h
- double expected_cost = (1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0;
-
-
- // Cost only; no residuals and no jacobian.
- {
- double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
- EXPECT_EQ(expected_cost, cost);
- }
-
- // Cost and residuals, no jacobian.
- {
- double cost = -1;
- double residuals[9] = { -2, -2, -2, -2, -2, -2, -2, -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
- EXPECT_EQ(expected_cost, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(1.0, residuals[2]);
- EXPECT_EQ(2.0, residuals[3]);
- EXPECT_EQ(3.0, residuals[4]);
- EXPECT_EQ(1.0, residuals[5]);
- EXPECT_EQ(2.0, residuals[6]);
- EXPECT_EQ(3.0, residuals[7]);
- EXPECT_EQ(4.0, residuals[8]);
- }
-
- // Cost, residuals, and jacobian.
- {
- double cost = -1;
- double residuals[9] = { -2, -2, -2, -2, -2, -2, -2, -2, -2 };
- SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
- EXPECT_EQ(expected_cost, cost);
- EXPECT_EQ(1.0, residuals[0]);
- EXPECT_EQ(2.0, residuals[1]);
- EXPECT_EQ(1.0, residuals[2]);
- EXPECT_EQ(2.0, residuals[3]);
- EXPECT_EQ(3.0, residuals[4]);
- EXPECT_EQ(1.0, residuals[5]);
- EXPECT_EQ(2.0, residuals[6]);
- EXPECT_EQ(3.0, residuals[7]);
- EXPECT_EQ(4.0, residuals[8]);
-
- Matrix actual_jacobian;
- jacobian->ToDenseMatrix(&actual_jacobian);
-
- Matrix expected_jacobian(9, 5);
- expected_jacobian <<
- // x y
- /* f(x, y) */ 1, 2, 1, 2, 3,
+ ExpectedEvaluation expected = {
+ // Rows/columns
+ 9, 5,
+ // Cost
+ // f g h
+ ( 1 + 4 + 1 + 4 + 9 + 1 + 4 + 9 + 16) / 2.0,
+ // Residuals
+ { 1.0, 2.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0 },
+ // Gradient
+ { 15.0, 30.0, // x
+ 33.0, 66.0, 99.0, // y
+ },
+ // Jacobian
+ // x y
+ { /* f(x, y) */ 1, 2, 1, 2, 3,
1, 2, 1, 2, 3,
/* g(x, z) */ 2, 4, 0, 0, 0,
@@ -650,31 +496,27 @@
/* h(y, z) */ 0, 0, 3, 6, 9,
0, 0, 3, 6, 9,
0, 0, 3, 6, 9,
- 0, 0, 3, 6, 9;
+ 0, 0, 3, 6, 9
+ }
+ };
+ CheckAllEvaluationCombinations(expected);
- EXPECT_TRUE((actual_jacobian.array() == expected_jacobian.array()).all())
- << "Actual:\n" << actual_jacobian
- << "\nExpected:\n" << expected_jacobian;
- }
+ // Restore parameter block z, so it will get freed in a consistent way.
+ parameter_blocks->push_back(parameter_block_z);
}
TEST_P(EvaluatorTest, EvaluatorAbortsForResidualsThatFailToEvaluate) {
- ProblemImpl problem;
-
- // The values are ignored completely by the cost function.
- double x[2];
- double y[3];
- double z[4];
- double state[9];
-
// Switch the return value to failure.
problem.AddResidualBlock(
new ParameterIgnoringCostFunction<20, 3, 2, 3, 4, false>, NULL, x, y, z);
+ // The values are ignored.
+ double state[9];
+
scoped_ptr<Evaluator> evaluator(CreateEvaluator(problem.mutable_program()));
scoped_ptr<SparseMatrix> jacobian(evaluator->CreateJacobian());
double cost;
- EXPECT_FALSE(evaluator->Evaluate(state, &cost, NULL, NULL));
+ EXPECT_FALSE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
}
// In the pairs, the first argument is the linear solver type, and the second
@@ -762,7 +604,7 @@
// Cost only; no residuals and no jacobian.
{
double cost = -1;
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL));
+ ASSERT_TRUE(evaluator->Evaluate(state, &cost, NULL, NULL, NULL));
EXPECT_EQ(48.5, cost);
}
@@ -770,7 +612,7 @@
{
double cost = -1;
double residuals[2] = { -2, -2 };
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL));
+ ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL, NULL));
EXPECT_EQ(48.5, cost);
EXPECT_EQ(4, residuals[0]);
EXPECT_EQ(9, residuals[1]);
@@ -781,7 +623,7 @@
double cost = -1;
double residuals[2] = { -2, -2};
SetSparseMatrixConstant(jacobian.get(), -1);
- ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, jacobian.get()));
+ ASSERT_TRUE(evaluator->Evaluate(state, &cost, residuals, NULL, jacobian.get()));
EXPECT_EQ(48.5, cost);
EXPECT_EQ(4, residuals[0]);
EXPECT_EQ(9, residuals[1]);
diff --git a/internal/ceres/program.cc b/internal/ceres/program.cc
index 3d62272..f6704e4 100644
--- a/internal/ceres/program.cc
+++ b/internal/ceres/program.cc
@@ -201,6 +201,15 @@
return max_parameters;
}
+int Program::MaxResidualsPerResidualBlock() const {
+ int max_residuals = 0;
+ for (int i = 0; i < residual_blocks_.size(); ++i) {
+ max_residuals = max(max_residuals,
+ residual_blocks_[i]->NumResiduals());
+ }
+ return max_residuals;
+}
+
bool Program::Evaluate(double* cost, double* residuals) {
*cost = 0.0;
diff --git a/internal/ceres/program.h b/internal/ceres/program.h
index 1386d3c..61fa06b 100644
--- a/internal/ceres/program.h
+++ b/internal/ceres/program.h
@@ -108,6 +108,7 @@
int MaxScratchDoublesNeededForEvaluate() const;
int MaxDerivativesPerResidualBlock() const;
int MaxParametersPerResidualBlock() const;
+ int MaxResidualsPerResidualBlock() const;
// Evaluate the cost and maybe the residuals for the program. If residuals is
// NULL, then residuals are not calculated. If the jacobian is needed, instead
diff --git a/internal/ceres/program_evaluator.h b/internal/ceres/program_evaluator.h
index b309aad..6c48e7d 100644
--- a/internal/ceres/program_evaluator.h
+++ b/internal/ceres/program_evaluator.h
@@ -120,6 +120,7 @@
bool Evaluate(const double* state,
double* cost,
double* residuals,
+ double* gradient,
SparseMatrix* jacobian) {
// The parameters are stateful, so set the state before evaluating.
if (!program_->StateVectorToParameterBlocks(state)) {
@@ -162,13 +163,16 @@
// Prepare block residuals if requested.
const ResidualBlock* residual_block = program_->residual_blocks()[i];
- double* block_residuals = (residuals != NULL)
- ? (residuals + residual_layout_[i])
- : NULL;
+ double* block_residuals = NULL;
+ if (residuals != NULL) {
+ block_residuals = residuals + residual_layout_[i];
+ } else if (gradient != NULL) {
+ block_residuals = scratch->residual_block_residuals.get();
+ }
// Prepare block jacobians if requested.
double** block_jacobians = NULL;
- if (jacobian != NULL) {
+ if (jacobian != NULL || gradient != NULL) {
preparer->Prepare(residual_block,
i,
jacobian,
@@ -178,10 +182,11 @@
// Evaluate the cost, residuals, and jacobians.
double block_cost;
- if (!residual_block->Evaluate(&block_cost,
- block_residuals,
- block_jacobians,
- scratch->scratch.get())) {
+ if (!residual_block->Evaluate(
+ &block_cost,
+ block_residuals,
+ block_jacobians,
+ scratch->residual_block_evaluate_scratch.get())) {
abort = true;
// This ensures that the OpenMP threads have a consistent view of 'abort'. Do
// the flush inside the failure case so that there is usually only one
@@ -192,19 +197,49 @@
scratch->cost += block_cost;
+ // Store the jacobians, if they were requested.
if (jacobian != NULL) {
jacobian_writer_.Write(i,
residual_layout_[i],
block_jacobians,
jacobian);
}
+
+ // Compute and store the gradient, if it was requested.
+ if (gradient != NULL) {
+ int num_residuals = residual_block->NumResiduals();
+ int num_parameter_blocks = residual_block->NumParameterBlocks();
+ for (int j = 0; j < num_parameter_blocks; ++j) {
+ const ParameterBlock* parameter_block =
+ residual_block->parameter_blocks()[j];
+ if (parameter_block->IsConstant()) {
+ continue;
+ }
+ MatrixRef block_jacobian(block_jacobians[j],
+ num_residuals,
+ parameter_block->LocalSize());
+ VectorRef block_gradient(scratch->gradient.get() +
+ parameter_block->delta_offset(),
+ parameter_block->LocalSize());
+ VectorRef block_residual(block_residuals, num_residuals);
+ block_gradient += block_residual.transpose() * block_jacobian;
+ }
+ }
}
if (!abort) {
- // Sum the cost from each thread.
+ // Sum the cost and gradient (if requested) from each thread.
(*cost) = 0.0;
+ int num_parameters = program_->NumEffectiveParameters();
+ if (gradient != NULL) {
+ VectorRef(gradient, num_parameters).setZero();
+ }
for (int i = 0; i < options_.num_threads; ++i) {
(*cost) += evaluate_scratch_[i].cost;
+ if (gradient != NULL) {
+ VectorRef(gradient, num_parameters) +=
+ VectorRef(evaluate_scratch_[i].gradient.get(), num_parameters);
+ }
}
}
return !abort;
@@ -228,16 +263,28 @@
}
private:
+ // Per-thread scratch space needed to evaluate and store each residual block.
struct EvaluateScratch {
void Init(int max_parameters_per_residual_block,
- int max_scratch_doubles_needed_for_evaluate) {
+ int max_scratch_doubles_needed_for_evaluate,
+ int max_residuals_per_residual_block,
+ int num_parameters) {
+ residual_block_evaluate_scratch.reset(
+ new double[max_scratch_doubles_needed_for_evaluate]);
+ gradient.reset(new double[num_parameters]);
+ VectorRef(gradient.get(), num_parameters).setZero();
+ residual_block_residuals.reset(
+ new double[max_residuals_per_residual_block]);
jacobian_block_ptrs.reset(
new double*[max_parameters_per_residual_block]);
- scratch.reset(new double[max_scratch_doubles_needed_for_evaluate]);
}
double cost;
- scoped_array<double> scratch;
+ scoped_array<double> residual_block_evaluate_scratch;
+ // The gradient in the local parameterization.
+ scoped_array<double> gradient;
+ // Enough space to store the residual for the largest residual block.
+ scoped_array<double> residual_block_residuals;
scoped_array<double*> jacobian_block_ptrs;
};
@@ -260,11 +307,16 @@
program.MaxParametersPerResidualBlock();
int max_scratch_doubles_needed_for_evaluate =
program.MaxScratchDoublesNeededForEvaluate();
+ int max_residuals_per_residual_block =
+ program.MaxResidualsPerResidualBlock();
+ int num_parameters = program.NumEffectiveParameters();
EvaluateScratch* evaluate_scratch = new EvaluateScratch[num_threads];
for (int i = 0; i < num_threads; i++) {
evaluate_scratch[i].Init(max_parameters_per_residual_block,
- max_scratch_doubles_needed_for_evaluate);
+ max_scratch_doubles_needed_for_evaluate,
+ max_residuals_per_residual_block,
+ num_parameters);
}
return evaluate_scratch;
}
diff --git a/internal/ceres/trust_region_minimizer.cc b/internal/ceres/trust_region_minimizer.cc
index c475690..f990b80 100644
--- a/internal/ceres/trust_region_minimizer.cc
+++ b/internal/ceres/trust_region_minimizer.cc
@@ -161,7 +161,7 @@
// Do initial cost and Jacobian evaluation.
double cost = 0.0;
- if (!evaluator->Evaluate(x.data(), &cost, residuals.data(), jacobian)) {
+ if (!evaluator->Evaluate(x.data(), &cost, residuals.data(), NULL, jacobian)) {
LOG(WARNING) << "Terminating: Residual and Jacobian evaluation failed.";
summary->termination_type = NUMERICAL_FAILURE;
return;
@@ -362,7 +362,9 @@
// Try this step.
double new_cost;
- if (!evaluator->Evaluate(x_plus_delta.data(), &new_cost, NULL, NULL)) {
+ if (!evaluator->Evaluate(x_plus_delta.data(),
+ &new_cost,
+ NULL, NULL, NULL)) {
summary->termination_type = NUMERICAL_FAILURE;
LOG(WARNING) << "Terminating: Cost evaluation failed.";
return;
@@ -394,7 +396,11 @@
x_norm = x.norm();
// Step looks good, evaluate the residuals and Jacobian at this
// point.
- if (!evaluator->Evaluate(x.data(), &cost, residuals.data(), jacobian)) {
+ if (!evaluator->Evaluate(x.data(),
+ &cost,
+ residuals.data(),
+ NULL,
+ jacobian)) {
summary->termination_type = NUMERICAL_FAILURE;
LOG(WARNING) << "Terminating: Residual and Jacobian evaluation failed.";
return;
diff --git a/internal/ceres/trust_region_minimizer_test.cc b/internal/ceres/trust_region_minimizer_test.cc
index 03297c0..414567a 100644
--- a/internal/ceres/trust_region_minimizer_test.cc
+++ b/internal/ceres/trust_region_minimizer_test.cc
@@ -83,6 +83,7 @@
virtual bool Evaluate(const double* state,
double* cost,
double* residuals,
+ double* /* gradient */,
SparseMatrix* jacobian) {
double x1 = state[0];
double x2 = state[1];