Evaluate ResidualBlocks without LossFunction if needed.
1. Add the ability to evaluate the problem without loss function.
2. Remove static Evaluator::Evaluate
3. Refactor the common code from problem_test.cc and
evaluator_test.cc into evaluator_test_utils.cc
Change-Id: I1aa841580afe91d288fbb65288b0ffdd1e43e827
diff --git a/include/ceres/problem.h b/include/ceres/problem.h
index bab3bfe..b1ccbab 100644
--- a/include/ceres/problem.h
+++ b/include/ceres/problem.h
@@ -331,7 +331,8 @@
// Options struct to control Problem::Evaluate.
struct EvaluateOptions {
EvaluateOptions()
- : num_threads(1) {
+ : apply_loss_function(true),
+ num_threads(1) {
}
// The set of parameter blocks for which evaluation should be
@@ -360,6 +361,15 @@
// they were added to the problem. But, this may change if the
// user removes any residual blocks from the problem.
vector<ResidualBlockId> residual_blocks;
+
+ // Even though the residual blocks in the problem may contain loss
+ // functions, setting apply_loss_function to false will turn off
+ // the application of the loss function to the output of the cost
+ // function. This is of use for example if the user wishes to
+ // analyse the solution quality by studying the distribution of
+ // residuals before and after the solve.
+ bool apply_loss_function;
+
int num_threads;
};
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 5440713..703a9e7 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -215,7 +215,11 @@
IF (${BUILD_TESTING} AND ${GFLAGS})
ADD_LIBRARY(gtest gmock_gtest_all.cc gmock_main.cc)
- ADD_LIBRARY(test_util test_util.cc numeric_diff_test_utils.cc)
+ ADD_LIBRARY(test_util
+ evaluator_test_utils.cc
+ numeric_diff_test_utils.cc
+ test_util.cc)
+
TARGET_LINK_LIBRARIES(gtest ${GFLAGS_LIB} ${GLOG_LIB})
MACRO (CERES_TEST NAME)
diff --git a/internal/ceres/evaluator.cc b/internal/ceres/evaluator.cc
index a3ce6f0..31a4176 100644
--- a/internal/ceres/evaluator.cc
+++ b/internal/ceres/evaluator.cc
@@ -72,76 +72,5 @@
}
}
-bool Evaluator::Evaluate(Program* program,
- int num_threads,
- double* cost,
- vector<double>* residuals,
- vector<double>* gradient,
- CRSMatrix* output_jacobian) {
- CHECK_GE(num_threads, 1)
- << "This is a Ceres bug; please contact the developers!";
- CHECK_NOTNULL(cost);
-
- // Setup the Parameter indices and offsets before an evaluator can
- // be constructed and used.
- program->SetParameterOffsetsAndIndex();
-
- Evaluator::Options evaluator_options;
- evaluator_options.linear_solver_type = SPARSE_NORMAL_CHOLESKY;
- evaluator_options.num_threads = num_threads;
-
- string error;
- scoped_ptr<Evaluator> evaluator(
- Evaluator::Create(evaluator_options, program, &error));
- if (evaluator.get() == NULL) {
- LOG(ERROR) << "Unable to create an Evaluator object. "
- << "Error: " << error
- << "This is a Ceres bug; please contact the developers!";
- return false;
- }
-
- if (residuals !=NULL) {
- residuals->resize(evaluator->NumResiduals());
- }
-
- if (gradient != NULL) {
- gradient->resize(evaluator->NumEffectiveParameters());
- }
-
- scoped_ptr<CompressedRowSparseMatrix> jacobian;
- if (output_jacobian != NULL) {
- jacobian.reset(
- down_cast<CompressedRowSparseMatrix*>(evaluator->CreateJacobian()));
- }
-
- // Point the state pointers to the user state pointers. This is
- // needed so that we can extract a parameter vector which is then
- // passed to Evaluator::Evaluate.
- program->SetParameterBlockStatePtrsToUserStatePtrs();
-
- // Copy the value of the parameter blocks into a vector, since the
- // Evaluate::Evaluate method needs its input as such. The previous
- // call to SetParameterBlockStatePtrsToUserStatePtrs ensures that
- // these values are the ones corresponding to the actual state of
- // the parameter blocks, rather than the temporary state pointer
- // used for evaluation.
- Vector parameters(program->NumParameters());
- program->ParameterBlocksToStateVector(parameters.data());
-
- if (!evaluator->Evaluate(parameters.data(),
- cost,
- residuals != NULL ? &(*residuals)[0] : NULL,
- gradient != NULL ? &(*gradient)[0] : NULL,
- jacobian.get())) {
- return false;
- }
-
- if (output_jacobian != NULL) {
- jacobian->ToCRSMatrix(output_jacobian);
- }
-
- return true;
-}
-
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/evaluator.h b/internal/ceres/evaluator.h
index 14a8818..07cfa37 100644
--- a/internal/ceres/evaluator.h
+++ b/internal/ceres/evaluator.h
@@ -72,7 +72,6 @@
Program* program,
string* error);
-
// This is used for computing the cost, residual and Jacobian for
// returning to the user. For actually solving the optimization
// problem, the optimization algorithm uses the ProgramEvaluator
@@ -116,6 +115,18 @@
// Schur complement based methods.
virtual SparseMatrix* CreateJacobian() const = 0;
+
+ // Options struct to control Evaluator::Evaluate;
+ struct EvaluateOptions {
+ EvaluateOptions()
+ : apply_loss_function(true) {
+ }
+
+ // If false, the loss function correction is not applied to the
+ // residual blocks.
+ bool apply_loss_function;
+ };
+
// Evaluate the cost function for the given state. Returns the cost,
// residuals, and jacobian in the corresponding arguments. Both residuals and
// jacobian are optional; to avoid computing them, pass NULL.
@@ -125,12 +136,29 @@
//
// state is an array of size NumParameters(), cost is a pointer to a single
// double, and residuals is an array of doubles of size NumResiduals().
- virtual bool Evaluate(const double* state,
+ virtual bool Evaluate(const EvaluateOptions& evaluate_options,
+ const double* state,
double* cost,
double* residuals,
double* gradient,
SparseMatrix* jacobian) = 0;
+ // Variant of Evaluator::Evaluate where the user wishes to use the
+ // default EvaluateOptions struct. This is mostly here as a
+ // convenience method.
+ virtual bool Evaluate(const double* state,
+ double* cost,
+ double* residuals,
+ double* gradient,
+ SparseMatrix* jacobian) {
+ return Evaluate(EvaluateOptions(),
+ state,
+ cost,
+ residuals,
+ gradient,
+ jacobian);
+ }
+
// Make a change delta (of size NumEffectiveParameters()) to state (of size
// NumParameters()) and store the result in state_plus_delta.
//
diff --git a/internal/ceres/evaluator_test.cc b/internal/ceres/evaluator_test.cc
index 9e9d31b..ea24504 100644
--- a/internal/ceres/evaluator_test.cc
+++ b/internal/ceres/evaluator_test.cc
@@ -36,6 +36,7 @@
#include "ceres/casts.h"
#include "ceres/cost_function.h"
#include "ceres/crs_matrix.h"
+#include "ceres/evaluator_test_utils.h"
#include "ceres/internal/eigen.h"
#include "ceres/internal/scoped_ptr.h"
#include "ceres/local_parameterization.h"
@@ -90,65 +91,6 @@
}
};
-struct ExpectedEvaluation {
- int num_rows;
- int num_cols;
- double cost;
- const double residuals[50];
- const double gradient[50];
- const double jacobian[200];
-};
-
-void CompareEvaluations(int expected_num_rows,
- int expected_num_cols,
- double expected_cost,
- const double* expected_residuals,
- const double* expected_gradient,
- const double* expected_jacobian,
- const double actual_cost,
- const double* actual_residuals,
- const double* actual_gradient,
- const double* actual_jacobian) {
- EXPECT_EQ(expected_cost, actual_cost);
-
- if (expected_residuals != NULL) {
- ConstVectorRef expected_residuals_vector(expected_residuals,
- expected_num_rows);
- ConstVectorRef actual_residuals_vector(actual_residuals,
- expected_num_rows);
- EXPECT_TRUE((actual_residuals_vector.array() ==
- expected_residuals_vector.array()).all())
- << "Actual:\n" << actual_residuals_vector
- << "\nExpected:\n" << expected_residuals_vector;
- }
-
- if (expected_gradient != NULL) {
- ConstVectorRef expected_gradient_vector(expected_gradient,
- expected_num_cols);
- ConstVectorRef actual_gradient_vector(actual_gradient,
- expected_num_cols);
-
- EXPECT_TRUE((actual_gradient_vector.array() ==
- expected_gradient_vector.array()).all())
- << "Actual:\n" << actual_gradient_vector.transpose()
- << "\nExpected:\n" << expected_gradient_vector.transpose();
- }
-
- if (expected_jacobian != NULL) {
- ConstMatrixRef expected_jacobian_matrix(expected_jacobian,
- expected_num_rows,
- expected_num_cols);
- ConstMatrixRef actual_jacobian_matrix(actual_jacobian,
- expected_num_rows,
- expected_num_cols);
- EXPECT_TRUE((actual_jacobian_matrix.array() ==
- expected_jacobian_matrix.array()).all())
- << "Actual:\n" << actual_jacobian_matrix
- << "\nExpected:\n" << expected_jacobian_matrix;
- }
-}
-
-
struct EvaluatorTest
: public ::testing::TestWithParam<pair<LinearSolverType, int> > {
Evaluator* CreateEvaluator(Program* program) {
@@ -692,272 +634,5 @@
}
}
-// Simple cost function used for testing Evaluator::Evaluate.
-//
-// r_i = i - (j + 1) * x_ij^2
-template <int kNumResiduals, int kNumParameterBlocks >
-class QuadraticCostFunction : public CostFunction {
- public:
- QuadraticCostFunction() {
- CHECK_GT(kNumResiduals, 0);
- CHECK_GT(kNumParameterBlocks, 0);
- set_num_residuals(kNumResiduals);
- for (int i = 0; i < kNumParameterBlocks; ++i) {
- mutable_parameter_block_sizes()->push_back(kNumResiduals);
- }
- }
-
- virtual bool Evaluate(double const* const* parameters,
- double* residuals,
- double** jacobians) const {
- for (int i = 0; i < kNumResiduals; ++i) {
- residuals[i] = i;
- for (int j = 0; j < kNumParameterBlocks; ++j) {
- residuals[i] -= (j + 1.0) * parameters[j][i] * parameters[j][i];
- }
- }
-
- if (jacobians == NULL) {
- return true;
- }
-
- for (int j = 0; j < kNumParameterBlocks; ++j) {
- if (jacobians[j] != NULL) {
- MatrixRef(jacobians[j], kNumResiduals, kNumResiduals) =
- (-2.0 * (j + 1.0) *
- ConstVectorRef(parameters[j], kNumResiduals)).asDiagonal();
- }
- }
-
- return true;
- }
-};
-
-// Convert a CRSMatrix to a dense Eigen matrix.
-void CRSToDenseMatrix(const CRSMatrix& input, Matrix* output) {
- Matrix& m = *CHECK_NOTNULL(output);
- m.resize(input.num_rows, input.num_cols);
- m.setZero();
- for (int row = 0; row < input.num_rows; ++row) {
- for (int j = input.rows[row]; j < input.rows[row + 1]; ++j) {
- const int col = input.cols[j];
- m(row, col) = input.values[j];
- }
- }
-}
-
-
-class StaticEvaluateTest : public ::testing::Test {
- protected:
- void SetUp() {
- for (int i = 0; i < 6; ++i) {
- parameters_[i] = static_cast<double>(i + 1);
- }
-
- CostFunction* cost_function = new QuadraticCostFunction<2, 2>;
-
- // f(x, y)
- problem_.AddResidualBlock(cost_function,
- NULL,
- parameters_,
- parameters_ + 2);
- // g(y, z)
- problem_.AddResidualBlock(cost_function,
- NULL, parameters_ + 2,
- parameters_ + 4);
- // h(z, x)
- problem_.AddResidualBlock(cost_function,
- NULL,
- parameters_ + 4,
- parameters_);
- }
-
-
-
- void EvaluateAndCompare(const int expected_num_rows,
- const int expected_num_cols,
- const double expected_cost,
- const double* expected_residuals,
- const double* expected_gradient,
- const double* expected_jacobian) {
- double cost;
- vector<double> residuals;
- vector<double> gradient;
- CRSMatrix jacobian;
-
- EXPECT_TRUE(Evaluator::Evaluate(
- problem_.mutable_program(),
- 1,
- &cost,
- expected_residuals != NULL ? &residuals : NULL,
- expected_gradient != NULL ? &gradient : NULL,
- expected_jacobian != NULL ? &jacobian : NULL));
-
- if (expected_residuals != NULL) {
- EXPECT_EQ(residuals.size(), expected_num_rows);
- }
-
- if (expected_gradient != NULL) {
- EXPECT_EQ(gradient.size(), expected_num_cols);
- }
-
- if (expected_jacobian != NULL) {
- EXPECT_EQ(jacobian.num_rows, expected_num_rows);
- EXPECT_EQ(jacobian.num_cols, expected_num_cols);
- }
-
- Matrix dense_jacobian;
- if (expected_jacobian != NULL) {
- CRSToDenseMatrix(jacobian, &dense_jacobian);
- }
-
- CompareEvaluations(expected_num_rows,
- expected_num_cols,
- expected_cost,
- expected_residuals,
- expected_gradient,
- expected_jacobian,
- cost,
- residuals.size() > 0 ? &residuals[0] : NULL,
- gradient.size() > 0 ? &gradient[0] : NULL,
- dense_jacobian.data());
- }
-
- void CheckAllEvaluationCombinations(const ExpectedEvaluation& expected ) {
- for (int i = 0; i < 8; ++i) {
- EvaluateAndCompare(expected.num_rows,
- expected.num_cols,
- expected.cost,
- (i & 1) ? expected.residuals : NULL,
- (i & 2) ? expected.gradient : NULL,
- (i & 4) ? expected.jacobian : NULL);
- }
-
- // The Evaluate call should only depend on the parameter block
- // values in the user provided pointers, and the current state of
- // the parameter block should not matter. So, create a new
- // parameters vector, and update the parameter block states with
- // it. The results from the Evaluate call should not change.
- double new_parameters[6];
- for (int i = 0; i < 6; ++i) {
- new_parameters[i] = 0.0;
- }
-
- problem_.mutable_program()->StateVectorToParameterBlocks(new_parameters);
-
- for (int i = 0; i < 8; ++i) {
- EvaluateAndCompare(expected.num_rows,
- expected.num_cols,
- expected.cost,
- (i & 1) ? expected.residuals : NULL,
- (i & 2) ? expected.gradient : NULL,
- (i & 4) ? expected.jacobian : NULL);
- }
- }
-
- ProblemImpl problem_;
- double parameters_[6];
-};
-
-
-TEST_F(StaticEvaluateTest, MultipleParameterAndResidualBlocks) {
- ExpectedEvaluation expected = {
- // Rows/columns
- 6, 6,
- // Cost
- 7607.0,
- // Residuals
- { -19.0, -35.0, // f
- -59.0, -87.0, // g
- -27.0, -43.0 // h
- },
- // Gradient
- { 146.0, 484.0, // x
- 582.0, 1256.0, // y
- 1450.0, 2604.0, // z
- },
- // Jacobian
- // x y z
- { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
- 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
- /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
- 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
- /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
- 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
- }
- };
-
- CheckAllEvaluationCombinations(expected);
-}
-
-TEST_F(StaticEvaluateTest, ConstantParameterBlock) {
- ExpectedEvaluation expected = {
- // Rows/columns
- 6, 6,
- // Cost
- 7607.0,
- // Residuals
- { -19.0, -35.0, // f
- -59.0, -87.0, // g
- -27.0, -43.0 // h
- },
-
- // Gradient
- { 146.0, 484.0, // x
- 0.0, 0.0, // y
- 1450.0, 2604.0, // z
- },
-
- // Jacobian
- // x y z
- { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
- 0.0, -4.0, 0.0, 0.0, 0.0, 0.0,
- /* g(y, z) */ 0.0, 0.0, 0.0, 0.0, -20.0, 0.0,
- 0.0, 0.0, 0.0, 0.0, 0.0, -24.0,
- /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
- 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
- }
- };
-
- problem_.SetParameterBlockConstant(parameters_ + 2);
- CheckAllEvaluationCombinations(expected);
-}
-
-TEST_F(StaticEvaluateTest, LocalParameterization) {
- ExpectedEvaluation expected = {
- // Rows/columns
- 6, 5,
- // Cost
- 7607.0,
- // Residuals
- { -19.0, -35.0, // f
- -59.0, -87.0, // g
- -27.0, -43.0 // h
- },
- // Gradient
- { 146.0, 484.0, // x
- 1256.0, // y with SubsetParameterization
- 1450.0, 2604.0, // z
- },
- // Jacobian
- // x y z
- { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0,
- 0.0, -4.0, -16.0, 0.0, 0.0,
- /* g(y, z) */ 0.0, 0.0, 0.0, -20.0, 0.0,
- 0.0, 0.0, -8.0, 0.0, -24.0,
- /* h(z, x) */ -4.0, 0.0, 0.0, -10.0, 0.0,
- 0.0, -8.0, 0.0, 0.0, -12.0
- }
- };
-
- vector<int> constant_parameters;
- constant_parameters.push_back(0);
- problem_.SetParameterization(parameters_ + 2,
- new SubsetParameterization(2,
- constant_parameters));
-
- CheckAllEvaluationCombinations(expected);
-}
-
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/evaluator_test_utils.cc b/internal/ceres/evaluator_test_utils.cc
new file mode 100644
index 0000000..a09be2d
--- /dev/null
+++ b/internal/ceres/evaluator_test_utils.cc
@@ -0,0 +1,89 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2013 Google Inc. All rights reserved.
+// http://code.google.com/p/ceres-solver/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: keir@google.com (Keir Mierle)
+// sameeragarwal@google.com (Sameer Agarwal)
+
+#include "ceres/evaluator_test_utils.h"
+#include "ceres/internal/eigen.h"
+#include "gtest/gtest.h"
+
+namespace ceres {
+namespace internal {
+
+void CompareEvaluations(int expected_num_rows,
+ int expected_num_cols,
+ double expected_cost,
+ const double* expected_residuals,
+ const double* expected_gradient,
+ const double* expected_jacobian,
+ const double actual_cost,
+ const double* actual_residuals,
+ const double* actual_gradient,
+ const double* actual_jacobian) {
+ EXPECT_EQ(expected_cost, actual_cost);
+
+ if (expected_residuals != NULL) {
+ ConstVectorRef expected_residuals_vector(expected_residuals,
+ expected_num_rows);
+ ConstVectorRef actual_residuals_vector(actual_residuals,
+ expected_num_rows);
+ EXPECT_TRUE((actual_residuals_vector.array() ==
+ expected_residuals_vector.array()).all())
+ << "Actual:\n" << actual_residuals_vector
+ << "\nExpected:\n" << expected_residuals_vector;
+ }
+
+ if (expected_gradient != NULL) {
+ ConstVectorRef expected_gradient_vector(expected_gradient,
+ expected_num_cols);
+ ConstVectorRef actual_gradient_vector(actual_gradient,
+ expected_num_cols);
+
+ EXPECT_TRUE((actual_gradient_vector.array() ==
+ expected_gradient_vector.array()).all())
+ << "Actual:\n" << actual_gradient_vector.transpose()
+ << "\nExpected:\n" << expected_gradient_vector.transpose();
+ }
+
+ if (expected_jacobian != NULL) {
+ ConstMatrixRef expected_jacobian_matrix(expected_jacobian,
+ expected_num_rows,
+ expected_num_cols);
+ ConstMatrixRef actual_jacobian_matrix(actual_jacobian,
+ expected_num_rows,
+ expected_num_cols);
+ EXPECT_TRUE((actual_jacobian_matrix.array() ==
+ expected_jacobian_matrix.array()).all())
+ << "Actual:\n" << actual_jacobian_matrix
+ << "\nExpected:\n" << expected_jacobian_matrix;
+ }
+}
+
+} // namespace internal
+} // namespace ceres
diff --git a/internal/ceres/evaluator_test_utils.h b/internal/ceres/evaluator_test_utils.h
new file mode 100644
index 0000000..ae0663a
--- /dev/null
+++ b/internal/ceres/evaluator_test_utils.h
@@ -0,0 +1,60 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2013 Google Inc. All rights reserved.
+// http://code.google.com/p/ceres-solver/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: keir@google.com (Keir Mierle)
+// sameeragarwal@google.com (Sameer Agarwal)
+//
+// Test utils used for evaluation testing.
+
+namespace ceres {
+namespace internal {
+
+// Fixed sized struct for storing an evaluation.
+struct ExpectedEvaluation {
+ int num_rows;
+ int num_cols;
+ double cost;
+ const double residuals[50];
+ const double gradient[50];
+ const double jacobian[200];
+};
+
+// Compare two evaluations.
+void CompareEvaluations(int expected_num_rows,
+ int expected_num_cols,
+ double expected_cost,
+ const double* expected_residuals,
+ const double* expected_gradient,
+ const double* expected_jacobian,
+ const double actual_cost,
+ const double* actual_residuals,
+ const double* actual_gradient,
+ const double* actual_jacobian);
+
+} // namespace internal
+} // namespace ceres
diff --git a/internal/ceres/problem_impl.cc b/internal/ceres/problem_impl.cc
index 1fb9e39..21d1144 100644
--- a/internal/ceres/problem_impl.cc
+++ b/internal/ceres/problem_impl.cc
@@ -651,7 +651,12 @@
program.ParameterBlocksToStateVector(parameters.data());
double tmp_cost = 0;
- bool status = evaluator->Evaluate(parameters.data(),
+
+ Evaluator::EvaluateOptions evaluator_evaluate_options;
+ evaluator_evaluate_options.apply_loss_function =
+ evaluate_options.apply_loss_function;
+ bool status = evaluator->Evaluate(evaluator_evaluate_options,
+ parameters.data(),
&tmp_cost,
residuals != NULL ? &(*residuals)[0] : NULL,
gradient != NULL ? &(*gradient)[0] : NULL,
diff --git a/internal/ceres/problem_test.cc b/internal/ceres/problem_test.cc
index 888eb7c..5f3bc94 100644
--- a/internal/ceres/problem_test.cc
+++ b/internal/ceres/problem_test.cc
@@ -35,6 +35,7 @@
#include "ceres/casts.h"
#include "ceres/cost_function.h"
#include "ceres/crs_matrix.h"
+#include "ceres/evaluator_test_utils.cc"
#include "ceres/internal/eigen.h"
#include "ceres/internal/scoped_ptr.h"
#include "ceres/local_parameterization.h"
@@ -46,7 +47,6 @@
#include "ceres/types.h"
#include "gtest/gtest.h"
-
namespace ceres {
namespace internal {
@@ -703,71 +703,6 @@
// Test for Problem::Evaluate
-// TODO(sameeragarwal): The following struct and function are shared
-// with evaluator_test.cc. Once things settle down, do an
-// evaluate_utils.h or some such thing to reduce code duplication. The
-// best time is perhaps when we remove the support for
-// Solver::Summary::initial_*
-struct ExpectedEvaluation {
- int num_rows;
- int num_cols;
- double cost;
- const double residuals[50];
- const double gradient[50];
- const double jacobian[200];
-};
-
-void CompareEvaluations(int expected_num_rows,
- int expected_num_cols,
- double expected_cost,
- const double* expected_residuals,
- const double* expected_gradient,
- const double* expected_jacobian,
- const double actual_cost,
- const double* actual_residuals,
- const double* actual_gradient,
- const double* actual_jacobian) {
- EXPECT_EQ(expected_cost, actual_cost);
-
- if (expected_residuals != NULL) {
- ConstVectorRef expected_residuals_vector(expected_residuals,
- expected_num_rows);
- ConstVectorRef actual_residuals_vector(actual_residuals,
- expected_num_rows);
- EXPECT_TRUE((actual_residuals_vector.array() ==
- expected_residuals_vector.array()).all())
- << "Actual:\n" << actual_residuals_vector
- << "\nExpected:\n" << expected_residuals_vector;
- }
-
- if (expected_gradient != NULL) {
- ConstVectorRef expected_gradient_vector(expected_gradient,
- expected_num_cols);
- ConstVectorRef actual_gradient_vector(actual_gradient,
- expected_num_cols);
-
- EXPECT_TRUE((actual_gradient_vector.array() ==
- expected_gradient_vector.array()).all())
- << "Actual:\n" << actual_gradient_vector.transpose()
- << "\nExpected:\n" << expected_gradient_vector.transpose();
- }
-
- if (expected_jacobian != NULL) {
- ConstMatrixRef expected_jacobian_matrix(expected_jacobian,
- expected_num_rows,
- expected_num_cols);
- ConstMatrixRef actual_jacobian_matrix(actual_jacobian,
- expected_num_rows,
- expected_num_cols);
- EXPECT_TRUE((actual_jacobian_matrix.array() ==
- expected_jacobian_matrix.array()).all())
- << "Actual:\n" << actual_jacobian_matrix
- << "\nExpected:\n" << expected_jacobian_matrix;
- }
-}
-
-// Simple cost function used for testing Problem::Evaluate.
-//
// r_i = i - (j + 1) * x_ij^2
template <int kNumResiduals, int kNumParameterBlocks>
class QuadraticCostFunction : public CostFunction {
diff --git a/internal/ceres/program_evaluator.h b/internal/ceres/program_evaluator.h
index a19cdf8..de56ac2 100644
--- a/internal/ceres/program_evaluator.h
+++ b/internal/ceres/program_evaluator.h
@@ -120,7 +120,8 @@
return jacobian_writer_.CreateJacobian();
}
- bool Evaluate(const double* state,
+ bool Evaluate(const Evaluator::EvaluateOptions& evaluate_options,
+ const double* state,
double* cost,
double* residuals,
double* gradient,
@@ -196,6 +197,7 @@
// Evaluate the cost, residuals, and jacobians.
double block_cost;
if (!residual_block->Evaluate(
+ evaluate_options.apply_loss_function,
&block_cost,
block_residuals,
block_jacobians,
diff --git a/internal/ceres/residual_block.cc b/internal/ceres/residual_block.cc
index 7f78960..b91b0ed 100644
--- a/internal/ceres/residual_block.cc
+++ b/internal/ceres/residual_block.cc
@@ -62,7 +62,8 @@
parameter_blocks_.get());
}
-bool ResidualBlock::Evaluate(double* cost,
+bool ResidualBlock::Evaluate(const bool apply_loss_function,
+ double* cost,
double* residuals,
double** jacobians,
double* scratch) const {
@@ -154,7 +155,7 @@
}
}
- if (loss_function_ == NULL) {
+ if (loss_function_ == NULL || !apply_loss_function) {
*cost = 0.5 * squared_norm;
return true;
}
diff --git a/internal/ceres/residual_block.h b/internal/ceres/residual_block.h
index 3921d1d..9c3671b 100644
--- a/internal/ceres/residual_block.h
+++ b/internal/ceres/residual_block.h
@@ -93,11 +93,16 @@
// 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.
- bool Evaluate(double* cost,
+ //
+ // apply_loss_function as the name implies allows the user to switch
+ // the application of the loss function on and off.
+ bool Evaluate(bool apply_loss_function,
+ double* cost,
double* residuals,
double** jacobians,
double* scratch) const;
+
const CostFunction* cost_function() const { return cost_function_; }
const LossFunction* loss_function() const { return loss_function_; }
diff --git a/internal/ceres/residual_block_test.cc b/internal/ceres/residual_block_test.cc
index fddd44e..1e03e7d 100644
--- a/internal/ceres/residual_block_test.cc
+++ b/internal/ceres/residual_block_test.cc
@@ -105,12 +105,12 @@
// Verify cost-only evaluation.
double cost;
- residual_block.Evaluate(&cost, NULL, NULL, scratch);
+ residual_block.Evaluate(true, &cost, NULL, NULL, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
// Verify cost and residual evaluation.
double residuals[3];
- residual_block.Evaluate(&cost, residuals, NULL, scratch);
+ residual_block.Evaluate(true, &cost, residuals, NULL, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
EXPECT_EQ(0.0, residuals[0]);
EXPECT_EQ(1.0, residuals[1]);
@@ -134,7 +134,7 @@
jacobian_rz.data()
};
- residual_block.Evaluate(&cost, residuals, jacobian_ptrs, scratch);
+ residual_block.Evaluate(true, &cost, residuals, jacobian_ptrs, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
EXPECT_EQ(0.0, residuals[0]);
EXPECT_EQ(1.0, residuals[1]);
@@ -153,7 +153,7 @@
jacobian_ptrs[1] = NULL; // Don't compute the jacobian for y.
- residual_block.Evaluate(&cost, residuals, jacobian_ptrs, scratch);
+ residual_block.Evaluate(true, &cost, residuals, jacobian_ptrs, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
EXPECT_EQ(0.0, residuals[0]);
EXPECT_EQ(1.0, residuals[1]);
@@ -244,12 +244,12 @@
// Verify cost-only evaluation.
double cost;
- residual_block.Evaluate(&cost, NULL, NULL, scratch);
+ residual_block.Evaluate(true, &cost, NULL, NULL, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
// Verify cost and residual evaluation.
double residuals[3];
- residual_block.Evaluate(&cost, residuals, NULL, scratch);
+ residual_block.Evaluate(true, &cost, residuals, NULL, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
EXPECT_EQ(0.0, residuals[0]);
EXPECT_EQ(1.0, residuals[1]);
@@ -273,7 +273,7 @@
jacobian_rz.data()
};
- residual_block.Evaluate(&cost, residuals, jacobian_ptrs, scratch);
+ residual_block.Evaluate(true, &cost, residuals, jacobian_ptrs, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
EXPECT_EQ(0.0, residuals[0]);
EXPECT_EQ(1.0, residuals[1]);
@@ -311,7 +311,7 @@
jacobian_ptrs[1] = NULL; // Don't compute the jacobian for y.
- residual_block.Evaluate(&cost, residuals, jacobian_ptrs, scratch);
+ residual_block.Evaluate(true, &cost, residuals, jacobian_ptrs, scratch);
EXPECT_EQ(0.5 * (0*0 + 1*1 + 2*2), cost);
EXPECT_EQ(0.0, residuals[0]);
EXPECT_EQ(1.0, residuals[1]);
diff --git a/internal/ceres/residual_block_utils_test.cc b/internal/ceres/residual_block_utils_test.cc
index 24723b3..d3c917a 100644
--- a/internal/ceres/residual_block_utils_test.cc
+++ b/internal/ceres/residual_block_utils_test.cc
@@ -62,7 +62,8 @@
double jacobian;
double* jacobians[] = { &jacobian };
- EXPECT_EQ(residual_block.Evaluate(&cost,
+ EXPECT_EQ(residual_block.Evaluate(true,
+ &cost,
&residuals,
jacobians,
scratch.get()), is_good);
diff --git a/internal/ceres/solver_impl.cc b/internal/ceres/solver_impl.cc
index 16fdbf6..0ef0a27 100644
--- a/internal/ceres/solver_impl.cc
+++ b/internal/ceres/solver_impl.cc
@@ -914,8 +914,11 @@
// The residual is constant and will be removed, so its cost is
// added to the variable fixed_cost.
double cost = 0.0;
- if (!residual_block->Evaluate(
- &cost, NULL, NULL, residual_block_evaluate_scratch.get())) {
+ if (!residual_block->Evaluate(true,
+ &cost,
+ NULL,
+ NULL,
+ residual_block_evaluate_scratch.get())) {
*error = StringPrintf("Evaluation of the residual %d failed during "
"removal of fixed residual blocks.", i);
return false;
diff --git a/internal/ceres/solver_impl_test.cc b/internal/ceres/solver_impl_test.cc
index 8e443ba..2471ea2 100644
--- a/internal/ceres/solver_impl_test.cc
+++ b/internal/ceres/solver_impl_test.cc
@@ -242,7 +242,8 @@
ResidualBlock *expected_removed_block = program.residual_blocks()[0];
scoped_array<double> scratch(
new double[expected_removed_block->NumScratchDoublesForEvaluate()]);
- expected_removed_block->Evaluate(&expected_fixed_cost,
+ expected_removed_block->Evaluate(true,
+ &expected_fixed_cost,
NULL,
NULL,
scratch.get());
diff --git a/internal/ceres/trust_region_minimizer_test.cc b/internal/ceres/trust_region_minimizer_test.cc
index 4bc9409..9c2780b 100644
--- a/internal/ceres/trust_region_minimizer_test.cc
+++ b/internal/ceres/trust_region_minimizer_test.cc
@@ -82,7 +82,8 @@
return dense_jacobian;
}
- virtual bool Evaluate(const double* state,
+ virtual bool Evaluate(const Evaluator::EvaluateOptions& evaluate_options,
+ const double* state,
double* cost,
double* residuals,
double* /* gradient */,