Allow LocalParameterizations to have zero local size.
Local parameterizations with zero tangent/local size will cause the
corresponding parameter block to be treated as constant.
https://github.com/ceres-solver/ceres-solver/issues/347
Change-Id: I554a2acc420f5dd9d0cc7f97b691877eb057b2c0
diff --git a/docs/source/nnls_modeling.rst b/docs/source/nnls_modeling.rst
index 0883c6d..b95796b 100644
--- a/docs/source/nnls_modeling.rst
+++ b/docs/source/nnls_modeling.rst
@@ -1776,7 +1776,11 @@
.. function:: bool Problem::IsParameterBlockConstant(const double* values) const
- Returns true if a parameter block is set constant, and false otherwise.
+ Returns ``true`` if a parameter block is set constant, and false
+ otherwise. A parameter block may be set constant in two
+ ways. Either by calling ``SetParameterBlockConstant`` or by
+ associating a ``LocalParameterization`` with a zero dimensional
+ tangent space with it.
.. function:: void Problem::SetParameterization(double* values, LocalParameterization* local_parameterization)
diff --git a/include/ceres/problem.h b/include/ceres/problem.h
index e43616e..977a708 100644
--- a/include/ceres/problem.h
+++ b/include/ceres/problem.h
@@ -308,7 +308,11 @@
// Allow the indicated parameter block to vary during optimization.
void SetParameterBlockVariable(double* values);
- // Returns true if a parameter block is set constant, and false otherwise.
+ // Returns true if a parameter block is set constant, and false
+ // otherwise. A parameter block may be set constant in two
+ // ways. Either by calling SetParameterBlockConstant or by
+ // associating a LocalParameterization with a zero dimensional
+ // tangent space with it.
bool IsParameterBlockConstant(const double* values) const;
// Set the local parameterization for one of the parameter blocks.
diff --git a/internal/ceres/covariance_test.cc b/internal/ceres/covariance_test.cc
index ad5ffe6..34a36c6 100644
--- a/internal/ceres/covariance_test.cc
+++ b/internal/ceres/covariance_test.cc
@@ -37,6 +37,7 @@
#include <memory>
#include <utility>
+#include "ceres/autodiff_cost_function.h"
#include "ceres/compressed_row_sparse_matrix.h"
#include "ceres/cost_function.h"
#include "ceres/covariance_impl.h"
@@ -1194,6 +1195,87 @@
ComputeAndCompareCovarianceBlocks(options, expected_covariance);
}
+struct LinearCostFunction {
+ template <typename T>
+ bool operator()(const T* x, const T* y, T* residual) const {
+ residual[0] = T(10.0) - *x;
+ residual[1] = T(5.0) - *y;
+ return true;
+ }
+ static CostFunction* Create() {
+ return new AutoDiffCostFunction<LinearCostFunction, 2, 1, 1>(
+ new LinearCostFunction);
+ }
+};
+
+TEST(Covariance, ZeroSizedLocalParameterizationGetCovariance) {
+ double x = 0.0;
+ double y = 1.0;
+ Problem problem;
+ problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
+ problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
+ // J = [-1 0]
+ // [ 0 0]
+ Covariance::Options options;
+ options.algorithm_type = DENSE_SVD;
+ Covariance covariance(options);
+ vector<pair<const double*, const double*>> covariance_blocks;
+ covariance_blocks.push_back(std::make_pair(&x, &x));
+ covariance_blocks.push_back(std::make_pair(&x, &y));
+ covariance_blocks.push_back(std::make_pair(&y, &x));
+ covariance_blocks.push_back(std::make_pair(&y, &y));
+ EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem));
+
+ double value = -1;
+ covariance.GetCovarianceBlock(&x, &x, &value);
+ EXPECT_NEAR(value, 1.0, std::numeric_limits<double>::epsilon());
+
+ value = -1;
+ covariance.GetCovarianceBlock(&x, &y, &value);
+ EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
+
+ value = -1;
+ covariance.GetCovarianceBlock(&y, &x, &value);
+ EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
+
+ value = -1;
+ covariance.GetCovarianceBlock(&y, &y, &value);
+ EXPECT_NEAR(value, 0.0, std::numeric_limits<double>::epsilon());
+}
+
+TEST(Covariance, ZeroSizedLocalParameterizationGetCovarianceInTangentSpace) {
+ double x = 0.0;
+ double y = 1.0;
+ Problem problem;
+ problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
+ problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
+ // J = [-1 0]
+ // [ 0 0]
+ Covariance::Options options;
+ options.algorithm_type = DENSE_SVD;
+ Covariance covariance(options);
+ vector<pair<const double*, const double*>> covariance_blocks;
+ covariance_blocks.push_back(std::make_pair(&x, &x));
+ covariance_blocks.push_back(std::make_pair(&x, &y));
+ covariance_blocks.push_back(std::make_pair(&y, &x));
+ covariance_blocks.push_back(std::make_pair(&y, &y));
+ EXPECT_TRUE(covariance.Compute(covariance_blocks, &problem));
+
+ double value = -1;
+ covariance.GetCovarianceBlockInTangentSpace(&x, &x, &value);
+ EXPECT_NEAR(value, 1.0, std::numeric_limits<double>::epsilon());
+
+ value = -1;
+ // The following three calls, should not touch this value, since the
+ // tangent space is of size zero
+ covariance.GetCovarianceBlockInTangentSpace(&x, &y, &value);
+ EXPECT_EQ(value, -1);
+ covariance.GetCovarianceBlockInTangentSpace(&y, &x, &value);
+ EXPECT_EQ(value, -1);
+ covariance.GetCovarianceBlockInTangentSpace(&y, &y, &value);
+ EXPECT_EQ(value, -1);
+}
+
class LargeScaleCovarianceTest : public ::testing::Test {
protected:
void SetUp() final {
diff --git a/internal/ceres/parameter_block.h b/internal/ceres/parameter_block.h
index 3b7ae49..d8432c7 100644
--- a/internal/ceres/parameter_block.h
+++ b/internal/ceres/parameter_block.h
@@ -124,7 +124,6 @@
// Set this parameter block to vary or not.
void SetConstant() { is_set_constant_ = true; }
void SetVarying() { is_set_constant_ = false; }
- bool IsSetConstantByUser() const { return is_set_constant_; }
bool IsConstant() const { return (is_set_constant_ || LocalSize() == 0); }
double UpperBound(int index) const {
@@ -187,9 +186,9 @@
<< "size of " << new_parameterization->GlobalSize() << ". Did you "
<< "accidentally use the wrong parameter block or parameterization?";
- CHECK_GT(new_parameterization->LocalSize(), 0)
+ CHECK_GE(new_parameterization->LocalSize(), 0)
<< "Invalid parameterization. Parameterizations must have a "
- << "positive dimensional tangent space.";
+ << "non-negative dimensional tangent space.";
local_parameterization_ = new_parameterization;
local_parameterization_jacobian_.reset(
diff --git a/internal/ceres/parameter_block_test.cc b/internal/ceres/parameter_block_test.cc
index 4b65c40..a5a4230 100644
--- a/internal/ceres/parameter_block_test.cc
+++ b/internal/ceres/parameter_block_test.cc
@@ -36,7 +36,7 @@
namespace ceres {
namespace internal {
-TEST(ParameterBlock, SetLocalParameterizationDiesOnSizeMismatch) {
+TEST(ParameterBlock, SetParameterizationDiesOnSizeMismatch) {
double x[3] = {1.0, 2.0, 3.0};
ParameterBlock parameter_block(x, 3, -1);
std::vector<int> indices;
@@ -46,7 +46,7 @@
parameter_block.SetParameterization(&subset_wrong_size), "global");
}
-TEST(ParameterBlock, SetLocalParameterizationWithSameExistingParameterization) {
+TEST(ParameterBlock, SetParameterizationWithSameExistingParameterization) {
double x[3] = {1.0, 2.0, 3.0};
ParameterBlock parameter_block(x, 3, -1);
std::vector<int> indices;
@@ -56,19 +56,34 @@
parameter_block.SetParameterization(&subset);
}
-TEST(ParameterBlock, SetParameterizationDiesOnZeroLocalSize) {
+TEST(ParameterBlock, SetParameterizationAllowsResettingToNull) {
double x[3] = {1.0, 2.0, 3.0};
ParameterBlock parameter_block(x, 3, -1);
std::vector<int> indices;
- indices.push_back(0);
indices.push_back(1);
- indices.push_back(2);
SubsetParameterization subset(3, indices);
- EXPECT_DEATH_IF_SUPPORTED(parameter_block.SetParameterization(&subset),
- "positive dimensional tangent");
+ parameter_block.SetParameterization(&subset);
+ EXPECT_EQ(parameter_block.local_parameterization(), &subset);
+ parameter_block.SetParameterization(nullptr);
+ EXPECT_EQ(parameter_block.local_parameterization(), nullptr);
}
-TEST(ParameterBlock, SetLocalParameterizationAndNormalOperation) {
+TEST(ParameterBlock,
+ SetParameterizationAllowsResettingToDifferentParameterization) {
+ double x[3] = {1.0, 2.0, 3.0};
+ ParameterBlock parameter_block(x, 3, -1);
+ std::vector<int> indices;
+ indices.push_back(1);
+ SubsetParameterization subset(3, indices);
+ parameter_block.SetParameterization(&subset);
+ EXPECT_EQ(parameter_block.local_parameterization(), &subset);
+
+ SubsetParameterization subset_different(3, indices);
+ parameter_block.SetParameterization(&subset_different);
+ EXPECT_EQ(parameter_block.local_parameterization(), &subset_different);
+}
+
+TEST(ParameterBlock, SetParameterizationAndNormalOperation) {
double x[3] = {1.0, 2.0, 3.0};
ParameterBlock parameter_block(x, 3, -1);
std::vector<int> indices;
diff --git a/internal/ceres/problem_impl.cc b/internal/ceres/problem_impl.cc
index 7a36c2b..6cc4d33 100644
--- a/internal/ceres/problem_impl.cc
+++ b/internal/ceres/problem_impl.cc
@@ -493,8 +493,7 @@
CHECK(parameter_block != nullptr)
<< "Parameter block not found: " << values << ". You must add the "
<< "parameter block to the problem before it can be queried.";
-
- return parameter_block->IsSetConstantByUser();
+ return parameter_block->IsConstant();
}
void ProblemImpl::SetParameterBlockVariable(double* values) {
diff --git a/internal/ceres/problem_test.cc b/internal/ceres/problem_test.cc
index 12e8dc2..f2b5da5 100644
--- a/internal/ceres/problem_test.cc
+++ b/internal/ceres/problem_test.cc
@@ -2123,5 +2123,18 @@
EXPECT_EQ(problem.ParameterBlockSize(x), 3);
}
+TEST(Solver, ZeroSizedLocalParameterizationHoldsParameterBlockConstant) {
+ double x = 0.0;
+ double y = 1.0;
+ Problem problem;
+ problem.AddResidualBlock(new BinaryCostFunction(1, 1, 1), nullptr, &x, &y);
+ problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
+ // Zero dimensional tangent space means that the block is
+ // effectively constant, but because the user did not mark it
+ // constant explicitly, the user will not see it as constant when
+ // querying IsParameterBlockConstant.
+ EXPECT_TRUE(problem.IsParameterBlockConstant(&y));
+}
+
} // namespace internal
} // namespace ceres
diff --git a/internal/ceres/solver_test.cc b/internal/ceres/solver_test.cc
index 601c1e4..58148b2 100644
--- a/internal/ceres/solver_test.cc
+++ b/internal/ceres/solver_test.cc
@@ -1,5 +1,5 @@
// Ceres Solver - A fast non-linear least squares minimizer
-// Copyright 2015 Google Inc. All rights reserved.
+// Copyright 2019 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
@@ -30,16 +30,18 @@
#include "ceres/solver.h"
+#include <cmath>
#include <limits>
#include <memory>
-#include <cmath>
#include <vector>
-#include "gtest/gtest.h"
-#include "ceres/evaluation_callback.h"
+
#include "ceres/autodiff_cost_function.h"
-#include "ceres/sized_cost_function.h"
+#include "ceres/evaluation_callback.h"
+#include "ceres/local_parameterization.h"
#include "ceres/problem.h"
#include "ceres/problem_impl.h"
+#include "ceres/sized_cost_function.h"
+#include "gtest/gtest.h"
namespace ceres {
namespace internal {
@@ -61,8 +63,8 @@
}
struct QuadraticCostFunctor {
- template <typename T> bool operator()(const T* const x,
- T* residual) const {
+ template <typename T>
+ bool operator()(const T* const x, T* residual) const {
residual[0] = T(5.0) - *x;
return true;
}
@@ -74,14 +76,14 @@
};
struct RememberingCallback : public IterationCallback {
- explicit RememberingCallback(double *x) : calls(0), x(x) {}
+ explicit RememberingCallback(double* x) : calls(0), x(x) {}
virtual ~RememberingCallback() {}
CallbackReturnType operator()(const IterationSummary& summary) final {
x_values.push_back(*x);
return SOLVER_CONTINUE;
}
int calls;
- double *x;
+ double* x;
std::vector<double> x_values;
};
@@ -89,8 +91,8 @@
virtual ~NoOpEvaluationCallback() {}
void PrepareForEvaluation(bool evaluate_jacobians,
bool new_evaluation_point) final {
- (void) evaluate_jacobians;
- (void) new_evaluation_point;
+ (void)evaluate_jacobians;
+ (void)new_evaluation_point;
}
};
@@ -114,8 +116,8 @@
// First: update_state_every_iteration=false, evaluation_callback=nullptr.
Solve(options, &problem, &summary);
- num_iterations = summary.num_successful_steps +
- summary.num_unsuccessful_steps;
+ num_iterations =
+ summary.num_successful_steps + summary.num_unsuccessful_steps;
EXPECT_GT(num_iterations, 1);
for (int i = 0; i < callback.x_values.size(); ++i) {
EXPECT_EQ(50.0, callback.x_values[i]);
@@ -126,8 +128,8 @@
options.update_state_every_iteration = true;
callback.x_values.clear();
Solve(options, &problem, &summary);
- num_iterations = summary.num_successful_steps +
- summary.num_unsuccessful_steps;
+ num_iterations =
+ summary.num_successful_steps + summary.num_unsuccessful_steps;
EXPECT_GT(num_iterations, 1);
EXPECT_EQ(original_x, callback.x_values[0]);
EXPECT_NE(original_x, callback.x_values[1]);
@@ -158,8 +160,8 @@
options.update_state_every_iteration = true;
callback.x_values.clear();
Solve(options, &problem, &summary);
- num_iterations = summary.num_successful_steps +
- summary.num_unsuccessful_steps;
+ num_iterations =
+ summary.num_successful_steps + summary.num_unsuccessful_steps;
EXPECT_GT(num_iterations, 1);
EXPECT_EQ(original_x, callback.x_values[0]);
EXPECT_NE(original_x, callback.x_values[1]);
@@ -169,8 +171,8 @@
options.update_state_every_iteration = false;
callback.x_values.clear();
Solve(options, &problem, &summary);
- num_iterations = summary.num_successful_steps +
- summary.num_unsuccessful_steps;
+ num_iterations =
+ summary.num_successful_steps + summary.num_unsuccessful_steps;
EXPECT_GT(num_iterations, 1);
EXPECT_EQ(original_x, callback.x_values[0]);
EXPECT_NE(original_x, callback.x_values[1]);
@@ -199,20 +201,17 @@
EXPECT_EQ(summary.termination_type, CONVERGENCE);
}
-
// The parameters must be in separate blocks so that they can be individually
// set constant or not.
struct Quadratic4DCostFunction {
- template <typename T> bool operator()(const T* const x,
- const T* const y,
- const T* const z,
- const T* const w,
- T* residual) const {
+ template <typename T>
+ bool operator()(const T* const x,
+ const T* const y,
+ const T* const z,
+ const T* const w,
+ T* residual) const {
// A 4-dimension axis-aligned quadratic.
- residual[0] = T(10.0) - *x +
- T(20.0) - *y +
- T(30.0) - *z +
- T(40.0) - *w;
+ residual[0] = T(10.0) - *x + T(20.0) - *y + T(30.0) - *z + T(40.0) - *w;
return true;
}
@@ -423,7 +422,8 @@
EXPECT_FALSE(options.IsValid(&message));
}
-TEST(Solver, IterativeSchurWithClusterTridiagonalPerconditionerNoSparseLibrary) {
+TEST(Solver,
+ IterativeSchurWithClusterTridiagonalPerconditionerNoSparseLibrary) {
Solver::Options options;
options.sparse_linear_algebra_library_type = NO_SPARSE;
options.linear_solver_type = ITERATIVE_SCHUR;
@@ -458,9 +458,8 @@
EXPECT_TRUE(options.IsValid(&message));
options.linear_solver_type = SPARSE_SCHUR;
-#if defined(CERES_NO_SUITESPARSE) && \
- defined(CERES_NO_CXSPARSE) && \
- !defined(CERES_USE_EIGEN_SPARSE)
+#if defined(CERES_NO_SUITESPARSE) && defined(CERES_NO_CXSPARSE) && \
+ !defined(CERES_USE_EIGEN_SPARSE)
EXPECT_FALSE(options.IsValid(&message));
#else
EXPECT_TRUE(options.IsValid(&message));
@@ -500,5 +499,40 @@
EXPECT_EQ(summary.iterations.size(), 0);
}
+struct LinearCostFunction {
+ template <typename T>
+ bool operator()(const T* x, const T* y, T* residual) const {
+ residual[0] = T(10.0) - *x;
+ residual[1] = T(5.0) - *y;
+ return true;
+ }
+ static CostFunction* Create() {
+ return new AutoDiffCostFunction<LinearCostFunction, 2, 1, 1>(
+ new LinearCostFunction);
+ }
+};
+
+TEST(Solver, ZeroSizedLocalParameterizationHoldsParameterBlockConstant) {
+ double x = 0.0;
+ double y = 1.0;
+ Problem problem;
+ problem.AddResidualBlock(LinearCostFunction::Create(), nullptr, &x, &y);
+ problem.SetParameterization(&y, new SubsetParameterization(1, {0}));
+ // Zero dimensional tangent space means that the block is
+ // effectively constant, but because the user did not mark it
+ // constant explicitly, the user will not see it as constant when
+ // querying IsParameterBlockConstant.
+ EXPECT_TRUE(problem.IsParameterBlockConstant(&y));
+ Solver::Options options;
+ options.function_tolerance = 0.0;
+ options.gradient_tolerance = 0.0;
+ options.parameter_tolerance = 0.0;
+ Solver::Summary summary;
+ Solve(options, &problem, &summary);
+ EXPECT_EQ(summary.termination_type, CONVERGENCE);
+ EXPECT_NEAR(x, 10.0, 1e-7);
+ EXPECT_EQ(y, 1.0);
+}
+
} // namespace internal
} // namespace ceres