blob: 29524d10ac33c2601d27156d50d108c7943c3012 [file] [log] [blame]
// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2023 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// 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: sameeragarwal@google.com (Sameer Agarwal)
// keir@google.com (Keir Mierle)
#include "ceres/problem.h"
#include <memory>
#include <string>
#include <vector>
#include "absl/log/check.h"
#include "ceres/autodiff_cost_function.h"
#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/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"
#include "ceres/types.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace ceres::internal {
// The following three classes are for the purposes of defining
// function signatures. They have dummy Evaluate functions.
// Trivial cost function that accepts a single argument.
class UnaryCostFunction : public CostFunction {
public:
UnaryCostFunction(int num_residuals, int32_t parameter_block_size) {
set_num_residuals(num_residuals);
mutable_parameter_block_sizes()->push_back(parameter_block_size);
}
bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const final {
for (int i = 0; i < num_residuals(); ++i) {
residuals[i] = 1;
}
return true;
}
};
// Trivial cost function that accepts two arguments.
class BinaryCostFunction : public CostFunction {
public:
BinaryCostFunction(int num_residuals,
int32_t parameter_block1_size,
int32_t parameter_block2_size) {
set_num_residuals(num_residuals);
mutable_parameter_block_sizes()->push_back(parameter_block1_size);
mutable_parameter_block_sizes()->push_back(parameter_block2_size);
}
bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const final {
for (int i = 0; i < num_residuals(); ++i) {
residuals[i] = 2;
}
return true;
}
};
// Trivial cost function that accepts three arguments.
class TernaryCostFunction : public CostFunction {
public:
TernaryCostFunction(int num_residuals,
int32_t parameter_block1_size,
int32_t parameter_block2_size,
int32_t parameter_block3_size) {
set_num_residuals(num_residuals);
mutable_parameter_block_sizes()->push_back(parameter_block1_size);
mutable_parameter_block_sizes()->push_back(parameter_block2_size);
mutable_parameter_block_sizes()->push_back(parameter_block3_size);
}
bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const final {
for (int i = 0; i < num_residuals(); ++i) {
residuals[i] = 3;
}
return true;
}
};
TEST(Problem, MoveConstructor) {
Problem src;
double x;
src.AddParameterBlock(&x, 1);
Problem dst(std::move(src));
EXPECT_TRUE(dst.HasParameterBlock(&x));
}
TEST(Problem, MoveAssignment) {
Problem src;
double x;
src.AddParameterBlock(&x, 1);
Problem dst;
dst = std::move(src);
EXPECT_TRUE(dst.HasParameterBlock(&x));
}
TEST(Problem, AddResidualWithNullCostFunctionDies) {
double x[3], y[4], z[5];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
problem.AddParameterBlock(z, 5);
EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(nullptr, nullptr, x),
"cost_function != nullptr");
}
TEST(Problem, AddResidualWithIncorrectNumberOfParameterBlocksDies) {
double x[3], y[4], z[5];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
problem.AddParameterBlock(z, 5);
// UnaryCostFunction takes only one parameter, but two are passed.
EXPECT_DEATH_IF_SUPPORTED(
problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x, y),
"num_parameter_blocks");
}
TEST(Problem, AddResidualWithDifferentSizesOnTheSameVariableDies) {
double x[3];
Problem problem;
problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
EXPECT_DEATH_IF_SUPPORTED(
problem.AddResidualBlock(
new UnaryCostFunction(2, 4 /* 4 != 3 */), nullptr, x),
"different block sizes");
}
TEST(Problem, AddResidualWithDuplicateParametersDies) {
double x[3], z[5];
Problem problem;
EXPECT_DEATH_IF_SUPPORTED(
problem.AddResidualBlock(new BinaryCostFunction(2, 3, 3), nullptr, x, x),
"Duplicate parameter blocks");
EXPECT_DEATH_IF_SUPPORTED(
problem.AddResidualBlock(
new TernaryCostFunction(1, 5, 3, 5), nullptr, z, x, z),
"Duplicate parameter blocks");
}
TEST(Problem, AddResidualWithIncorrectSizesOfParameterBlockDies) {
double x[3], y[4], z[5];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
problem.AddParameterBlock(z, 5);
// The cost function expects the size of the second parameter, z, to be 4
// instead of 5 as declared above. This is fatal.
EXPECT_DEATH_IF_SUPPORTED(
problem.AddResidualBlock(new BinaryCostFunction(2, 3, 4), nullptr, x, z),
"different block sizes");
}
TEST(Problem, AddResidualAddsDuplicatedParametersOnlyOnce) {
double x[3], y[4], z[5];
Problem problem;
problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
problem.AddResidualBlock(new UnaryCostFunction(2, 4), nullptr, y);
problem.AddResidualBlock(new UnaryCostFunction(2, 5), nullptr, z);
EXPECT_EQ(3, problem.NumParameterBlocks());
EXPECT_EQ(12, problem.NumParameters());
}
TEST(Problem, AddParameterWithDifferentSizesOnTheSameVariableDies) {
double x[3], y[4];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(x, 4),
"different block sizes");
}
static double* IntToPtr(int i) {
return reinterpret_cast<double*>(sizeof(double) * i); // NOLINT
}
TEST(Problem, AddParameterWithAliasedParametersDies) {
// Layout is
//
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// [x] x x x x [y] y y
// o==o==o o==o==o o==o
// o--o--o o--o--o o--o o--o--o
//
// Parameter block additions are tested as listed above; expected successful
// ones marked with o==o and aliasing ones marked with o--o.
Problem problem;
problem.AddParameterBlock(IntToPtr(5), 5); // x
problem.AddParameterBlock(IntToPtr(13), 3); // y
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 2),
"Aliasing detected");
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 3),
"Aliasing detected");
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 9),
"Aliasing detected");
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(8), 3),
"Aliasing detected");
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(12), 2),
"Aliasing detected");
EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(14), 3),
"Aliasing detected");
// These ones should work.
problem.AddParameterBlock(IntToPtr(2), 3);
problem.AddParameterBlock(IntToPtr(10), 3);
problem.AddParameterBlock(IntToPtr(16), 2);
ASSERT_EQ(5, problem.NumParameterBlocks());
}
TEST(Problem, AddParameterIgnoresDuplicateCalls) {
double x[3], y[4];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
// Creating parameter blocks multiple times is ignored.
problem.AddParameterBlock(x, 3);
problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
// ... even repeatedly.
problem.AddParameterBlock(x, 3);
problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
// More parameters are fine.
problem.AddParameterBlock(y, 4);
problem.AddResidualBlock(new UnaryCostFunction(2, 4), nullptr, y);
EXPECT_EQ(2, problem.NumParameterBlocks());
EXPECT_EQ(7, problem.NumParameters());
}
class DestructorCountingCostFunction : public SizedCostFunction<3, 4, 5> {
public:
explicit DestructorCountingCostFunction(int* num_destructions)
: num_destructions_(num_destructions) {}
~DestructorCountingCostFunction() override { *num_destructions_ += 1; }
bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const final {
return true;
}
private:
int* num_destructions_;
};
TEST(Problem, ReusedCostFunctionsAreOnlyDeletedOnce) {
double y[4], z[5];
int num_destructions = 0;
// Add a cost function multiple times and check to make sure that
// the destructor on the cost function is only called once.
{
Problem problem;
problem.AddParameterBlock(y, 4);
problem.AddParameterBlock(z, 5);
CostFunction* cost = new DestructorCountingCostFunction(&num_destructions);
problem.AddResidualBlock(cost, nullptr, y, z);
problem.AddResidualBlock(cost, nullptr, y, z);
problem.AddResidualBlock(cost, nullptr, y, z);
EXPECT_EQ(3, problem.NumResidualBlocks());
}
// Check that the destructor was called only once.
ASSERT_EQ(num_destructions, 1);
}
TEST(Problem, GetCostFunctionForResidualBlock) {
double x[3];
Problem problem;
CostFunction* cost_function = new UnaryCostFunction(2, 3);
const ResidualBlockId residual_block =
problem.AddResidualBlock(cost_function, nullptr, x);
EXPECT_EQ(problem.GetCostFunctionForResidualBlock(residual_block),
cost_function);
EXPECT_TRUE(problem.GetLossFunctionForResidualBlock(residual_block) ==
nullptr);
}
TEST(Problem, GetLossFunctionForResidualBlock) {
double x[3];
Problem problem;
CostFunction* cost_function = new UnaryCostFunction(2, 3);
LossFunction* loss_function = new TrivialLoss();
const ResidualBlockId residual_block =
problem.AddResidualBlock(cost_function, loss_function, x);
EXPECT_EQ(problem.GetCostFunctionForResidualBlock(residual_block),
cost_function);
EXPECT_EQ(problem.GetLossFunctionForResidualBlock(residual_block),
loss_function);
}
TEST(Problem, CostFunctionsAreDeletedEvenWithRemovals) {
double y[4], z[5], w[4];
int num_destructions = 0;
{
Problem problem;
problem.AddParameterBlock(y, 4);
problem.AddParameterBlock(z, 5);
CostFunction* cost_yz =
new DestructorCountingCostFunction(&num_destructions);
CostFunction* cost_wz =
new DestructorCountingCostFunction(&num_destructions);
ResidualBlock* r_yz = problem.AddResidualBlock(cost_yz, nullptr, y, z);
ResidualBlock* r_wz = problem.AddResidualBlock(cost_wz, nullptr, w, z);
EXPECT_EQ(2, problem.NumResidualBlocks());
problem.RemoveResidualBlock(r_yz);
ASSERT_EQ(num_destructions, 1);
problem.RemoveResidualBlock(r_wz);
ASSERT_EQ(num_destructions, 2);
EXPECT_EQ(0, problem.NumResidualBlocks());
}
ASSERT_EQ(num_destructions, 2);
}
// Make the dynamic problem tests (e.g. for removing residual blocks)
// parameterized on whether the low-latency mode is enabled or not.
//
// This tests against ProblemImpl instead of Problem in order to inspect the
// state of the resulting Program; this is difficult with only the thin Problem
// interface.
struct DynamicProblem : public ::testing::TestWithParam<bool> {
DynamicProblem() {
Problem::Options options;
options.enable_fast_removal = GetParam();
problem = std::make_unique<ProblemImpl>(options);
}
ParameterBlock* GetParameterBlock(int block) {
return problem->program().parameter_blocks()[block];
}
ResidualBlock* GetResidualBlock(int block) {
return problem->program().residual_blocks()[block];
}
bool HasResidualBlock(ResidualBlock* residual_block) {
bool have_residual_block = true;
if (GetParam()) {
have_residual_block &=
(problem->residual_block_set().find(residual_block) !=
problem->residual_block_set().end());
}
have_residual_block &=
find(problem->program().residual_blocks().begin(),
problem->program().residual_blocks().end(),
residual_block) != problem->program().residual_blocks().end();
return have_residual_block;
}
int NumResidualBlocks() {
// Verify that the hash set of residuals is maintained consistently.
if (GetParam()) {
EXPECT_EQ(problem->residual_block_set().size(),
problem->NumResidualBlocks());
}
return problem->NumResidualBlocks();
}
// The next block of functions until the end are only for testing the
// residual block removals.
void ExpectParameterBlockContainsResidualBlock(
double* values, ResidualBlock* residual_block) {
ParameterBlock* parameter_block =
FindOrDie(problem->parameter_map(), values);
EXPECT_TRUE(ContainsKey(*(parameter_block->mutable_residual_blocks()),
residual_block));
}
void ExpectSize(double* values, int size) {
ParameterBlock* parameter_block =
FindOrDie(problem->parameter_map(), values);
EXPECT_EQ(size, parameter_block->mutable_residual_blocks()->size());
}
// Degenerate case.
void ExpectParameterBlockContains(double* values) { ExpectSize(values, 0); }
void ExpectParameterBlockContains(double* values, ResidualBlock* r1) {
ExpectSize(values, 1);
ExpectParameterBlockContainsResidualBlock(values, r1);
}
void ExpectParameterBlockContains(double* values,
ResidualBlock* r1,
ResidualBlock* r2) {
ExpectSize(values, 2);
ExpectParameterBlockContainsResidualBlock(values, r1);
ExpectParameterBlockContainsResidualBlock(values, r2);
}
void ExpectParameterBlockContains(double* values,
ResidualBlock* r1,
ResidualBlock* r2,
ResidualBlock* r3) {
ExpectSize(values, 3);
ExpectParameterBlockContainsResidualBlock(values, r1);
ExpectParameterBlockContainsResidualBlock(values, r2);
ExpectParameterBlockContainsResidualBlock(values, r3);
}
void ExpectParameterBlockContains(double* values,
ResidualBlock* r1,
ResidualBlock* r2,
ResidualBlock* r3,
ResidualBlock* r4) {
ExpectSize(values, 4);
ExpectParameterBlockContainsResidualBlock(values, r1);
ExpectParameterBlockContainsResidualBlock(values, r2);
ExpectParameterBlockContainsResidualBlock(values, r3);
ExpectParameterBlockContainsResidualBlock(values, r4);
}
std::unique_ptr<ProblemImpl> problem;
double y[4], z[5], w[3];
};
TEST(Problem, SetParameterBlockConstantWithUnknownPtrDies) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockConstant(y),
"Parameter block not found:");
}
TEST(Problem, SetParameterBlockVariableWithUnknownPtrDies) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockVariable(y),
"Parameter block not found:");
}
TEST(Problem, IsParameterBlockConstant) {
double x1[3];
double x2[3];
Problem problem;
problem.AddParameterBlock(x1, 3);
problem.AddParameterBlock(x2, 3);
EXPECT_FALSE(problem.IsParameterBlockConstant(x1));
EXPECT_FALSE(problem.IsParameterBlockConstant(x2));
problem.SetParameterBlockConstant(x1);
EXPECT_TRUE(problem.IsParameterBlockConstant(x1));
EXPECT_FALSE(problem.IsParameterBlockConstant(x2));
problem.SetParameterBlockConstant(x2);
EXPECT_TRUE(problem.IsParameterBlockConstant(x1));
EXPECT_TRUE(problem.IsParameterBlockConstant(x2));
problem.SetParameterBlockVariable(x1);
EXPECT_FALSE(problem.IsParameterBlockConstant(x1));
EXPECT_TRUE(problem.IsParameterBlockConstant(x2));
}
TEST(Problem, IsParameterBlockConstantWithUnknownPtrDies) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
EXPECT_DEATH_IF_SUPPORTED(problem.IsParameterBlockConstant(y),
"Parameter block not found:");
}
TEST(Problem, SetManifoldWithUnknownPtrDies) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
EXPECT_DEATH_IF_SUPPORTED(problem.SetManifold(y, new EuclideanManifold<3>),
"Parameter block not found:");
}
TEST(Problem, RemoveParameterBlockWithUnknownPtrDies) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
EXPECT_DEATH_IF_SUPPORTED(problem.RemoveParameterBlock(y),
"Parameter block not found:");
}
TEST(Problem, GetManifold) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 2);
Manifold* manifold = new EuclideanManifold<3>;
problem.SetManifold(x, manifold);
EXPECT_EQ(problem.GetManifold(x), manifold);
EXPECT_TRUE(problem.GetManifold(y) == nullptr);
}
TEST(Problem, HasManifold) {
double x[3];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 2);
Manifold* manifold = new EuclideanManifold<3>;
problem.SetManifold(x, manifold);
EXPECT_TRUE(problem.HasManifold(x));
EXPECT_FALSE(problem.HasManifold(y));
}
TEST(Problem, RepeatedAddParameterBlockResetsManifold) {
double x[4];
double y[2];
Problem problem;
problem.AddParameterBlock(x, 4, new SubsetManifold(4, {0, 1}));
problem.AddParameterBlock(y, 2);
EXPECT_FALSE(problem.HasManifold(y));
EXPECT_TRUE(problem.HasManifold(x));
EXPECT_EQ(problem.ParameterBlockSize(x), 4);
EXPECT_EQ(problem.ParameterBlockTangentSize(x), 2);
EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 4);
EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 2);
problem.AddParameterBlock(x, 4, static_cast<Manifold*>(nullptr));
EXPECT_FALSE(problem.HasManifold(x));
EXPECT_EQ(problem.ParameterBlockSize(x), 4);
EXPECT_EQ(problem.ParameterBlockTangentSize(x), 4);
EXPECT_EQ(problem.GetManifold(x), nullptr);
problem.AddParameterBlock(x, 4, new SubsetManifold(4, {0, 1, 2}));
problem.AddParameterBlock(y, 2);
EXPECT_TRUE(problem.HasManifold(x));
EXPECT_EQ(problem.ParameterBlockSize(x), 4);
EXPECT_EQ(problem.ParameterBlockTangentSize(x), 1);
EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 4);
EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 1);
}
TEST(Problem, ParameterBlockQueryTestUsingManifold) {
double x[3];
double y[4];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
std::vector<int> constant_parameters;
constant_parameters.push_back(0);
problem.SetManifold(x, new SubsetManifold(3, constant_parameters));
EXPECT_EQ(problem.ParameterBlockSize(x), 3);
EXPECT_EQ(problem.ParameterBlockTangentSize(x), 2);
EXPECT_EQ(problem.ParameterBlockTangentSize(y), 4);
std::vector<double*> parameter_blocks;
problem.GetParameterBlocks(&parameter_blocks);
EXPECT_EQ(parameter_blocks.size(), 2);
EXPECT_NE(parameter_blocks[0], parameter_blocks[1]);
EXPECT_TRUE(parameter_blocks[0] == x || parameter_blocks[0] == y);
EXPECT_TRUE(parameter_blocks[1] == x || parameter_blocks[1] == y);
EXPECT_TRUE(problem.HasParameterBlock(x));
problem.RemoveParameterBlock(x);
EXPECT_FALSE(problem.HasParameterBlock(x));
problem.GetParameterBlocks(&parameter_blocks);
EXPECT_EQ(parameter_blocks.size(), 1);
EXPECT_TRUE(parameter_blocks[0] == y);
}
TEST(Problem, ParameterBlockQueryTest) {
double x[3];
double y[4];
Problem problem;
problem.AddParameterBlock(x, 3);
problem.AddParameterBlock(y, 4);
std::vector<int> constant_parameters;
constant_parameters.push_back(0);
problem.SetManifold(x, new SubsetManifold(3, constant_parameters));
EXPECT_EQ(problem.ParameterBlockSize(x), 3);
EXPECT_EQ(problem.ParameterBlockTangentSize(x), 2);
EXPECT_EQ(problem.ParameterBlockTangentSize(y), 4);
std::vector<double*> parameter_blocks;
problem.GetParameterBlocks(&parameter_blocks);
EXPECT_EQ(parameter_blocks.size(), 2);
EXPECT_NE(parameter_blocks[0], parameter_blocks[1]);
EXPECT_TRUE(parameter_blocks[0] == x || parameter_blocks[0] == y);
EXPECT_TRUE(parameter_blocks[1] == x || parameter_blocks[1] == y);
EXPECT_TRUE(problem.HasParameterBlock(x));
problem.RemoveParameterBlock(x);
EXPECT_FALSE(problem.HasParameterBlock(x));
problem.GetParameterBlocks(&parameter_blocks);
EXPECT_EQ(parameter_blocks.size(), 1);
EXPECT_TRUE(parameter_blocks[0] == y);
}
TEST_P(DynamicProblem, RemoveParameterBlockWithNoResiduals) {
problem->AddParameterBlock(y, 4);
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(y, GetParameterBlock(0)->user_state());
EXPECT_EQ(z, GetParameterBlock(1)->user_state());
EXPECT_EQ(w, GetParameterBlock(2)->user_state());
// w is at the end, which might break the swapping logic so try adding and
// removing it.
problem->RemoveParameterBlock(w);
ASSERT_EQ(2, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(y, GetParameterBlock(0)->user_state());
EXPECT_EQ(z, GetParameterBlock(1)->user_state());
problem->AddParameterBlock(w, 3);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(y, GetParameterBlock(0)->user_state());
EXPECT_EQ(z, GetParameterBlock(1)->user_state());
EXPECT_EQ(w, GetParameterBlock(2)->user_state());
// Now remove z, which is in the middle, and add it back.
problem->RemoveParameterBlock(z);
ASSERT_EQ(2, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(y, GetParameterBlock(0)->user_state());
EXPECT_EQ(w, GetParameterBlock(1)->user_state());
problem->AddParameterBlock(z, 5);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(y, GetParameterBlock(0)->user_state());
EXPECT_EQ(w, GetParameterBlock(1)->user_state());
EXPECT_EQ(z, GetParameterBlock(2)->user_state());
// Now remove everything.
// y
problem->RemoveParameterBlock(y);
ASSERT_EQ(2, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(z, GetParameterBlock(0)->user_state());
EXPECT_EQ(w, GetParameterBlock(1)->user_state());
// z
problem->RemoveParameterBlock(z);
ASSERT_EQ(1, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(w, GetParameterBlock(0)->user_state());
// w
problem->RemoveParameterBlock(w);
EXPECT_EQ(0, problem->NumParameterBlocks());
EXPECT_EQ(0, NumResidualBlocks());
}
TEST_P(DynamicProblem, RemoveParameterBlockWithResiduals) {
problem->AddParameterBlock(y, 4);
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(y, GetParameterBlock(0)->user_state());
EXPECT_EQ(z, GetParameterBlock(1)->user_state());
EXPECT_EQ(w, GetParameterBlock(2)->user_state());
// clang-format off
// Add all combinations of cost functions.
CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
CostFunction* cost_y = new UnaryCostFunction (1, 4);
CostFunction* cost_z = new UnaryCostFunction (1, 5);
CostFunction* cost_w = new UnaryCostFunction (1, 3);
ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
EXPECT_EQ(3, problem->NumParameterBlocks());
EXPECT_EQ(7, NumResidualBlocks());
// Remove w, which should remove r_yzw, r_yw, r_zw, r_w.
problem->RemoveParameterBlock(w);
ASSERT_EQ(2, problem->NumParameterBlocks());
ASSERT_EQ(3, NumResidualBlocks());
ASSERT_FALSE(HasResidualBlock(r_yzw));
ASSERT_TRUE (HasResidualBlock(r_yz ));
ASSERT_FALSE(HasResidualBlock(r_yw ));
ASSERT_FALSE(HasResidualBlock(r_zw ));
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_TRUE (HasResidualBlock(r_z ));
ASSERT_FALSE(HasResidualBlock(r_w ));
// Remove z, which will remove almost everything else.
problem->RemoveParameterBlock(z);
ASSERT_EQ(1, problem->NumParameterBlocks());
ASSERT_EQ(1, NumResidualBlocks());
ASSERT_FALSE(HasResidualBlock(r_yzw));
ASSERT_FALSE(HasResidualBlock(r_yz ));
ASSERT_FALSE(HasResidualBlock(r_yw ));
ASSERT_FALSE(HasResidualBlock(r_zw ));
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_FALSE(HasResidualBlock(r_z ));
ASSERT_FALSE(HasResidualBlock(r_w ));
// Remove y; all gone.
problem->RemoveParameterBlock(y);
EXPECT_EQ(0, problem->NumParameterBlocks());
EXPECT_EQ(0, NumResidualBlocks());
// clang-format on
}
TEST_P(DynamicProblem, RemoveResidualBlock) {
problem->AddParameterBlock(y, 4);
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
// clang-format off
// Add all combinations of cost functions.
CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
CostFunction* cost_y = new UnaryCostFunction (1, 4);
CostFunction* cost_z = new UnaryCostFunction (1, 5);
CostFunction* cost_w = new UnaryCostFunction (1, 3);
ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
if (GetParam()) {
// In this test parameterization, there should be back-pointers from the
// parameter blocks to the residual blocks.
ExpectParameterBlockContains(y, r_yzw, r_yz, r_yw, r_y);
ExpectParameterBlockContains(z, r_yzw, r_yz, r_zw, r_z);
ExpectParameterBlockContains(w, r_yzw, r_yw, r_zw, r_w);
} else {
// Otherwise, nothing.
EXPECT_TRUE(GetParameterBlock(0)->mutable_residual_blocks() == nullptr);
EXPECT_TRUE(GetParameterBlock(1)->mutable_residual_blocks() == nullptr);
EXPECT_TRUE(GetParameterBlock(2)->mutable_residual_blocks() == nullptr);
}
EXPECT_EQ(3, problem->NumParameterBlocks());
EXPECT_EQ(7, NumResidualBlocks());
// Remove each residual and check the state after each removal.
// Remove r_yzw.
problem->RemoveResidualBlock(r_yzw);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(6, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_yw, r_y);
ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
ExpectParameterBlockContains(w, r_yw, r_zw, r_w);
}
ASSERT_TRUE (HasResidualBlock(r_yz ));
ASSERT_TRUE (HasResidualBlock(r_yw ));
ASSERT_TRUE (HasResidualBlock(r_zw ));
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_TRUE (HasResidualBlock(r_z ));
ASSERT_TRUE (HasResidualBlock(r_w ));
// Remove r_yw.
problem->RemoveResidualBlock(r_yw);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(5, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_y);
ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
ExpectParameterBlockContains(w, r_zw, r_w);
}
ASSERT_TRUE (HasResidualBlock(r_yz ));
ASSERT_TRUE (HasResidualBlock(r_zw ));
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_TRUE (HasResidualBlock(r_z ));
ASSERT_TRUE (HasResidualBlock(r_w ));
// Remove r_zw.
problem->RemoveResidualBlock(r_zw);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(4, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_y);
ExpectParameterBlockContains(z, r_yz, r_z);
ExpectParameterBlockContains(w, r_w);
}
ASSERT_TRUE (HasResidualBlock(r_yz ));
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_TRUE (HasResidualBlock(r_z ));
ASSERT_TRUE (HasResidualBlock(r_w ));
// Remove r_w.
problem->RemoveResidualBlock(r_w);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(3, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_y);
ExpectParameterBlockContains(z, r_yz, r_z);
ExpectParameterBlockContains(w);
}
ASSERT_TRUE (HasResidualBlock(r_yz ));
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_TRUE (HasResidualBlock(r_z ));
// Remove r_yz.
problem->RemoveResidualBlock(r_yz);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(2, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_y);
ExpectParameterBlockContains(z, r_z);
ExpectParameterBlockContains(w);
}
ASSERT_TRUE (HasResidualBlock(r_y ));
ASSERT_TRUE (HasResidualBlock(r_z ));
// Remove the last two.
problem->RemoveResidualBlock(r_z);
problem->RemoveResidualBlock(r_y);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(0, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y);
ExpectParameterBlockContains(z);
ExpectParameterBlockContains(w);
}
// clang-format on
}
TEST_P(DynamicProblem, RemoveInvalidResidualBlockDies) {
problem->AddParameterBlock(y, 4);
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
// clang-format off
// Add all combinations of cost functions.
CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
CostFunction* cost_y = new UnaryCostFunction (1, 4);
CostFunction* cost_z = new UnaryCostFunction (1, 5);
CostFunction* cost_w = new UnaryCostFunction (1, 3);
ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
// clang-format on
// Remove r_yzw.
problem->RemoveResidualBlock(r_yzw);
ASSERT_EQ(3, problem->NumParameterBlocks());
ASSERT_EQ(6, NumResidualBlocks());
// Attempt to remove r_yzw again.
EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yzw), "not found");
// Attempt to remove a cast pointer never added as a residual.
int trash_memory = 1234;
auto* invalid_residual = reinterpret_cast<ResidualBlock*>(&trash_memory);
EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(invalid_residual),
"not found");
// Remove a parameter block, which in turn removes the dependent residuals
// then attempt to remove them directly.
problem->RemoveParameterBlock(z);
ASSERT_EQ(2, problem->NumParameterBlocks());
ASSERT_EQ(3, NumResidualBlocks());
EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yz), "not found");
EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_zw), "not found");
EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_z), "not found");
problem->RemoveResidualBlock(r_yw);
problem->RemoveResidualBlock(r_w);
problem->RemoveResidualBlock(r_y);
}
// Check that a null-terminated array, a, has the same elements as b.
template <typename T>
void ExpectVectorContainsUnordered(const T* a, const std::vector<T>& b) {
// Compute the size of a.
int size = 0;
while (a[size]) {
++size;
}
ASSERT_EQ(size, b.size());
// Sort a.
std::vector<T> a_sorted(size);
copy(a, a + size, a_sorted.begin());
sort(a_sorted.begin(), a_sorted.end());
// Sort b.
std::vector<T> b_sorted(b);
sort(b_sorted.begin(), b_sorted.end());
// Compare.
for (int i = 0; i < size; ++i) {
EXPECT_EQ(a_sorted[i], b_sorted[i]);
}
}
static void ExpectProblemHasResidualBlocks(
const ProblemImpl& problem,
const ResidualBlockId* expected_residual_blocks) {
std::vector<ResidualBlockId> residual_blocks;
problem.GetResidualBlocks(&residual_blocks);
ExpectVectorContainsUnordered(expected_residual_blocks, residual_blocks);
}
TEST_P(DynamicProblem, GetXXXBlocksForYYYBlock) {
problem->AddParameterBlock(y, 4);
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
// clang-format off
// Add all combinations of cost functions.
CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
CostFunction* cost_y = new UnaryCostFunction (1, 4);
CostFunction* cost_z = new UnaryCostFunction (1, 5);
CostFunction* cost_w = new UnaryCostFunction (1, 3);
ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
{
ResidualBlockId expected_residuals[] = {r_yzw, nullptr};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
{
ResidualBlockId expected_residuals[] = {r_yzw, r_yz, nullptr};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
{
ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, nullptr};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
{
ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, nullptr};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
{
ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, r_y, nullptr};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
{
ResidualBlock *expected_residuals[] = {
r_yzw, r_yz, r_yw, r_zw, r_y, r_z, nullptr
};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
{
ResidualBlock *expected_residuals[] = {
r_yzw, r_yz, r_yw, r_zw, r_y, r_z, r_w, nullptr
};
ExpectProblemHasResidualBlocks(*problem, expected_residuals);
}
std::vector<double*> parameter_blocks;
std::vector<ResidualBlockId> residual_blocks;
// Check GetResidualBlocksForParameterBlock() for all parameter blocks.
struct GetResidualBlocksForParameterBlockTestCase {
double* parameter_block;
ResidualBlockId expected_residual_blocks[10];
};
GetResidualBlocksForParameterBlockTestCase get_residual_blocks_cases[] = {
{ y, { r_yzw, r_yz, r_yw, r_y, nullptr} },
{ z, { r_yzw, r_yz, r_zw, r_z, nullptr} },
{ w, { r_yzw, r_yw, r_zw, r_w, nullptr} },
{ nullptr, { nullptr } }
};
for (int i = 0; get_residual_blocks_cases[i].parameter_block; ++i) {
problem->GetResidualBlocksForParameterBlock(
get_residual_blocks_cases[i].parameter_block,
&residual_blocks);
ExpectVectorContainsUnordered(
get_residual_blocks_cases[i].expected_residual_blocks,
residual_blocks);
}
// Check GetParameterBlocksForResidualBlock() for all residual blocks.
struct GetParameterBlocksForResidualBlockTestCase {
ResidualBlockId residual_block;
double* expected_parameter_blocks[10];
};
GetParameterBlocksForResidualBlockTestCase get_parameter_blocks_cases[] = {
{ r_yzw, { y, z, w, nullptr } },
{ r_yz , { y, z, nullptr } },
{ r_yw , { y, w, nullptr } },
{ r_zw , { z, w, nullptr } },
{ r_y , { y, nullptr } },
{ r_z , { z, nullptr } },
{ r_w , { w, nullptr } },
{ nullptr, { nullptr } }
};
for (int i = 0; get_parameter_blocks_cases[i].residual_block; ++i) {
problem->GetParameterBlocksForResidualBlock(
get_parameter_blocks_cases[i].residual_block,
&parameter_blocks);
ExpectVectorContainsUnordered(
get_parameter_blocks_cases[i].expected_parameter_blocks,
parameter_blocks);
}
// clang-format on
}
INSTANTIATE_TEST_SUITE_P(OptionsInstantiation,
DynamicProblem,
::testing::Values(true, false));
// Test for Problem::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);
}
}
bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const final {
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 == nullptr) {
return true;
}
for (int j = 0; j < kNumParameterBlocks; ++j) {
if (jacobians[j] != nullptr) {
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.
static void CRSToDenseMatrix(const CRSMatrix& input, Matrix* output) {
ASSERT_TRUE(output != nullptr);
Matrix& m = *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 ProblemEvaluateTest : public ::testing::Test {
protected:
void SetUp() override {
for (int i = 0; i < 6; ++i) {
parameters_[i] = static_cast<double>(i + 1);
}
parameter_blocks_.push_back(parameters_);
parameter_blocks_.push_back(parameters_ + 2);
parameter_blocks_.push_back(parameters_ + 4);
CostFunction* cost_function = new QuadraticCostFunction<2, 2>;
// f(x, y)
residual_blocks_.push_back(problem_.AddResidualBlock(
cost_function, nullptr, parameters_, parameters_ + 2));
// g(y, z)
residual_blocks_.push_back(problem_.AddResidualBlock(
cost_function, nullptr, parameters_ + 2, parameters_ + 4));
// h(z, x)
residual_blocks_.push_back(problem_.AddResidualBlock(
cost_function, nullptr, parameters_ + 4, parameters_));
}
void TearDown() override { EXPECT_TRUE(problem_.program().IsValid()); }
void EvaluateAndCompare(const Problem::EvaluateOptions& options,
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;
std::vector<double> residuals;
std::vector<double> gradient;
CRSMatrix jacobian;
EXPECT_TRUE(
problem_.Evaluate(options,
&cost,
expected_residuals != nullptr ? &residuals : nullptr,
expected_gradient != nullptr ? &gradient : nullptr,
expected_jacobian != nullptr ? &jacobian : nullptr));
if (expected_residuals != nullptr) {
EXPECT_EQ(residuals.size(), expected_num_rows);
}
if (expected_gradient != nullptr) {
EXPECT_EQ(gradient.size(), expected_num_cols);
}
if (expected_jacobian != nullptr) {
EXPECT_EQ(jacobian.num_rows, expected_num_rows);
EXPECT_EQ(jacobian.num_cols, expected_num_cols);
}
Matrix dense_jacobian;
if (expected_jacobian != nullptr) {
CRSToDenseMatrix(jacobian, &dense_jacobian);
}
CompareEvaluations(expected_num_rows,
expected_num_cols,
expected_cost,
expected_residuals,
expected_gradient,
expected_jacobian,
cost,
!residuals.empty() ? &residuals[0] : nullptr,
!gradient.empty() ? &gradient[0] : nullptr,
dense_jacobian.data());
}
void CheckAllEvaluationCombinations(const Problem::EvaluateOptions& options,
const ExpectedEvaluation& expected) {
for (int i = 0; i < 8; ++i) {
EvaluateAndCompare(options,
expected.num_rows,
expected.num_cols,
expected.cost,
(i & 1) ? expected.residuals : nullptr,
(i & 2) ? expected.gradient : nullptr,
(i & 4) ? expected.jacobian : nullptr);
}
}
ProblemImpl problem_;
double parameters_[6];
std::vector<double*> parameter_blocks_;
std::vector<ResidualBlockId> residual_blocks_;
};
TEST_F(ProblemEvaluateTest, MultipleParameterAndResidualBlocks) {
// clang-format off
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
}
};
// clang-format on
CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
}
TEST_F(ProblemEvaluateTest, ParameterAndResidualBlocksPassedInOptions) {
// clang-format off
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
}
};
// clang-format on
Problem::EvaluateOptions evaluate_options;
evaluate_options.parameter_blocks = parameter_blocks_;
evaluate_options.residual_blocks = residual_blocks_;
CheckAllEvaluationCombinations(evaluate_options, expected);
}
TEST_F(ProblemEvaluateTest, ReorderedResidualBlocks) {
// clang-format off
ExpectedEvaluation expected = {
// Rows/columns
6, 6,
// Cost
7607.0,
// Residuals
{ -19.0, -35.0, // f
-27.0, -43.0, // h
-59.0, -87.0 // g
},
// 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,
/* 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,
/* 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
}
};
// clang-format on
Problem::EvaluateOptions evaluate_options;
evaluate_options.parameter_blocks = parameter_blocks_;
// f, h, g
evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
CheckAllEvaluationCombinations(evaluate_options, expected);
}
TEST_F(ProblemEvaluateTest,
ReorderedResidualBlocksAndReorderedParameterBlocks) {
// clang-format off
ExpectedEvaluation expected = {
// Rows/columns
6, 6,
// Cost
7607.0,
// Residuals
{ -19.0, -35.0, // f
-27.0, -43.0, // h
-59.0, -87.0 // g
},
// Gradient
{ 1450.0, 2604.0, // z
582.0, 1256.0, // y
146.0, 484.0, // x
},
// Jacobian
// z y x
{ /* f(x, y) */ 0.0, 0.0, -12.0, 0.0, -2.0, 0.0,
0.0, 0.0, 0.0, -16.0, 0.0, -4.0,
/* h(z, x) */ -10.0, 0.0, 0.0, 0.0, -4.0, 0.0,
0.0, -12.0, 0.0, 0.0, 0.0, -8.0,
/* g(y, z) */ -20.0, 0.0, -6.0, 0.0, 0.0, 0.0,
0.0, -24.0, 0.0, -8.0, 0.0, 0.0
}
};
// clang-format on
Problem::EvaluateOptions evaluate_options;
// z, y, x
evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
evaluate_options.parameter_blocks.push_back(parameter_blocks_[1]);
evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
// f, h, g
evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
CheckAllEvaluationCombinations(evaluate_options, expected);
}
TEST_F(ProblemEvaluateTest, ConstantParameterBlock) {
// clang-format off
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
}
};
// clang-format on
problem_.SetParameterBlockConstant(parameters_ + 2);
CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
}
TEST_F(ProblemEvaluateTest, ExcludedAResidualBlock) {
// clang-format off
ExpectedEvaluation expected = {
// Rows/columns
4, 6,
// Cost
2082.0,
// Residuals
{ -19.0, -35.0, // f
-27.0, -43.0 // h
},
// Gradient
{ 146.0, 484.0, // x
228.0, 560.0, // y
270.0, 516.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,
/* 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
}
};
// clang-format on
Problem::EvaluateOptions evaluate_options;
evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
CheckAllEvaluationCombinations(evaluate_options, expected);
}
TEST_F(ProblemEvaluateTest, ExcludedParameterBlock) {
// clang-format off
ExpectedEvaluation expected = {
// Rows/columns
6, 4,
// 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
1450.0, 2604.0, // z
},
// Jacobian
// x z
{ /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
0.0, -4.0, 0.0, 0.0,
/* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
0.0, 0.0, 0.0, -24.0,
/* h(z, x) */ -4.0, 0.0, -10.0, 0.0,
0.0, -8.0, 0.0, -12.0
}
};
// clang-format on
Problem::EvaluateOptions evaluate_options;
// x, z
evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
evaluate_options.residual_blocks = residual_blocks_;
CheckAllEvaluationCombinations(evaluate_options, expected);
}
TEST_F(ProblemEvaluateTest, ExcludedParameterBlockAndExcludedResidualBlock) {
// clang-format off
ExpectedEvaluation expected = {
// Rows/columns
4, 4,
// Cost
6318.0,
// Residuals
{ -19.0, -35.0, // f
-59.0, -87.0, // g
},
// Gradient
{ 38.0, 140.0, // x
1180.0, 2088.0, // z
},
// Jacobian
// x z
{ /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
0.0, -4.0, 0.0, 0.0,
/* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
0.0, 0.0, 0.0, -24.0,
}
};
// clang-format on
Problem::EvaluateOptions evaluate_options;
// x, z
evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
CheckAllEvaluationCombinations(evaluate_options, expected);
}
TEST_F(ProblemEvaluateTest, Manifold) {
// clang-format off
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 SubsetManifold
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
}
};
// clang-format on
std::vector<int> constant_parameters;
constant_parameters.push_back(0);
problem_.SetManifold(parameters_ + 2,
new SubsetManifold(2, constant_parameters));
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 constexpr bool kNewPoint = true;
static constexpr bool kNotNewPoint = false;
static double loss_function_scale_;
protected:
ProblemImpl problem_;
double x_[2] = {1, 2};
double y_[3] = {1, 2, 3};
};
double ProblemEvaluateResidualBlockTest::loss_function_scale_ = 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,
kNewPoint,
&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,
kNewPoint,
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,
kNewPoint,
&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,
kNewPoint,
&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,
kNewPoint,
&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,
kNewPoint,
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(),
new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP),
x_,
y_);
Vector expected_f(5);
expected_f << 1, 2, 1, 2, 3;
expected_f *= std::sqrt(loss_function_scale_);
Matrix expected_dfdx = Matrix::Zero(5, 2);
expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
expected_dfdx *= std::sqrt(loss_function_scale_);
Matrix expected_dfdy = Matrix::Zero(5, 3);
expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
expected_dfdy *= std::sqrt(loss_function_scale_);
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,
kNewPoint,
&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(),
new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP),
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,
kNewPoint,
&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, OneResidualBlockWithOneManifold) {
ResidualBlockId residual_block_id =
problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
problem_.SetManifold(x_, new SubsetManifold(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,
kNewPoint,
&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, OneResidualBlockWithTwoManifolds) {
ResidualBlockId residual_block_id =
problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
problem_.SetManifold(x_, new SubsetManifold(2, {1}));
problem_.SetManifold(y_, new SubsetManifold(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,
kNewPoint,
&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,
kNewPoint,
&actual_cost,
actual_f.data(),
jacobians));
jacobians[0] = nullptr;
EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
kApplyLossFunction,
kNewPoint,
&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,
kNewPoint,
&actual_cost,
actual_f.data(),
jacobians));
jacobians[0] = nullptr;
EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
kApplyLossFunction,
kNewPoint,
&actual_cost,
actual_f.data(),
jacobians));
jacobians[1] = nullptr;
EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
kApplyLossFunction,
kNewPoint,
&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,
kNewPoint,
&actual_cost,
actual_f.data(),
jacobians));
jacobians[0] = nullptr;
EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
kApplyLossFunction,
kNewPoint,
&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};
problem.AddParameterBlock(x, 2);
EXPECT_EQ(problem.GetParameterLowerBound(x, 0),
-std::numeric_limits<double>::max());
EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
-std::numeric_limits<double>::max());
problem.SetParameterLowerBound(x, 0, -1.0);
EXPECT_EQ(problem.GetParameterLowerBound(x, 0), -1.0);
EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
-std::numeric_limits<double>::max());
problem.SetParameterLowerBound(x, 0, -2.0);
EXPECT_EQ(problem.GetParameterLowerBound(x, 0), -2.0);
EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
-std::numeric_limits<double>::max());
problem.SetParameterLowerBound(x, 0, -std::numeric_limits<double>::max());
EXPECT_EQ(problem.GetParameterLowerBound(x, 0),
-std::numeric_limits<double>::max());
EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
-std::numeric_limits<double>::max());
}
TEST(Problem, SetAndGetParameterUpperBound) {
Problem problem;
double x[] = {1.0, 2.0};
problem.AddParameterBlock(x, 2);
EXPECT_EQ(problem.GetParameterUpperBound(x, 0),
std::numeric_limits<double>::max());
EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
std::numeric_limits<double>::max());
problem.SetParameterUpperBound(x, 0, -1.0);
EXPECT_EQ(problem.GetParameterUpperBound(x, 0), -1.0);
EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
std::numeric_limits<double>::max());
problem.SetParameterUpperBound(x, 0, -2.0);
EXPECT_EQ(problem.GetParameterUpperBound(x, 0), -2.0);
EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
std::numeric_limits<double>::max());
problem.SetParameterUpperBound(x, 0, std::numeric_limits<double>::max());
EXPECT_EQ(problem.GetParameterUpperBound(x, 0),
std::numeric_limits<double>::max());
EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
std::numeric_limits<double>::max());
}
TEST(Problem, SetManifoldTwice) {
Problem problem;
double x[] = {1.0, 2.0, 3.0};
problem.AddParameterBlock(x, 3);
problem.SetManifold(x, new SubsetManifold(3, {1}));
EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 3);
EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 2);
problem.SetManifold(x, new SubsetManifold(3, {0, 1}));
EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 3);
EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 1);
}
TEST(Problem, SetManifoldAndThenClearItWithNull) {
Problem problem;
double x[] = {1.0, 2.0, 3.0};
problem.AddParameterBlock(x, 3);
problem.SetManifold(x, new SubsetManifold(3, {1}));
EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 3);
EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 2);
problem.SetManifold(x, nullptr);
EXPECT_EQ(problem.GetManifold(x), nullptr);
EXPECT_EQ(problem.ParameterBlockTangentSize(x), 3);
EXPECT_EQ(problem.ParameterBlockSize(x), 3);
}
TEST(Solver, ZeroTangentSizedManifoldMeansParameterBlockIsConstant) {
double x = 0.0;
double y = 1.0;
Problem problem;
problem.AddResidualBlock(new BinaryCostFunction(1, 1, 1), nullptr, &x, &y);
problem.SetManifold(&y, new SubsetManifold(1, {0}));
EXPECT_TRUE(problem.IsParameterBlockConstant(&y));
}
class MockEvaluationCallback : public EvaluationCallback {
public:
MOCK_METHOD2(PrepareForEvaluation, void(bool, bool));
};
TEST(ProblemEvaluate, CallsEvaluationCallbackWithoutJacobian) {
constexpr bool kDoNotComputeJacobians = false;
constexpr bool kNewPoint = true;
MockEvaluationCallback evaluation_callback;
EXPECT_CALL(evaluation_callback,
PrepareForEvaluation(kDoNotComputeJacobians, kNewPoint))
.Times(1);
Problem::Options options;
options.evaluation_callback = &evaluation_callback;
ProblemImpl problem(options);
double x_[2] = {1, 2};
double y_[3] = {1, 2, 3};
problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
double actual_cost;
EXPECT_TRUE(problem.Evaluate(
Problem::EvaluateOptions(), &actual_cost, nullptr, nullptr, nullptr));
}
TEST(ProblemEvaluate, CallsEvaluationCallbackWithJacobian) {
constexpr bool kComputeJacobians = true;
constexpr bool kNewPoint = true;
MockEvaluationCallback evaluation_callback;
EXPECT_CALL(evaluation_callback,
PrepareForEvaluation(kComputeJacobians, kNewPoint))
.Times(1);
Problem::Options options;
options.evaluation_callback = &evaluation_callback;
ProblemImpl problem(options);
double x_[2] = {1, 2};
double y_[3] = {1, 2, 3};
problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
double actual_cost;
ceres::CRSMatrix jacobian;
EXPECT_TRUE(problem.Evaluate(
Problem::EvaluateOptions(), &actual_cost, nullptr, nullptr, &jacobian));
}
TEST(ProblemEvaluateResidualBlock, NewPointCallsEvaluationCallback) {
constexpr bool kComputeJacobians = true;
constexpr bool kNewPoint = true;
MockEvaluationCallback evaluation_callback;
EXPECT_CALL(evaluation_callback,
PrepareForEvaluation(kComputeJacobians, kNewPoint))
.Times(1);
Problem::Options options;
options.evaluation_callback = &evaluation_callback;
ProblemImpl problem(options);
double x_[2] = {1, 2};
double y_[3] = {1, 2, 3};
ResidualBlockId residual_block_id =
problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
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, true, true, &actual_cost, actual_f.data(), jacobians));
}
TEST(ProblemEvaluateResidualBlock, OldPointCallsEvaluationCallback) {
constexpr bool kComputeJacobians = true;
constexpr bool kOldPoint = false;
MockEvaluationCallback evaluation_callback;
EXPECT_CALL(evaluation_callback,
PrepareForEvaluation(kComputeJacobians, kOldPoint))
.Times(1);
Problem::Options options;
options.evaluation_callback = &evaluation_callback;
ProblemImpl problem(options);
double x_[2] = {1, 2};
double y_[3] = {1, 2, 3};
ResidualBlockId residual_block_id =
problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
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,
true,
false,
&actual_cost,
actual_f.data(),
jacobians));
}
} // namespace ceres::internal