Check validity of residual block before removal in RemoveResidualBlock.
- Breaking change: Problem::Options::enable_fast_parameter_block_removal
is now Problem::Options::enable_fast_removal, as it now controls
the behaviour for both parameter and residual blocks.
- Previously we did not check that the specified residual block to
remove in RemoveResidualBlock actually represented a valid residual
for the problem.
- This meant that Ceres would die unexpectedly if the user passed an
uninitialised residual_block, or more likely attempted to remove a
residual block that had already been removed automatically after
the user removed a parameter block upon on which it was dependent.
- RemoveResidualBlock now verifies the validity of the given
residual_block to remove. Either by checking against a hash set of
all residuals maintained in ProblemImpl iff enable_fast_removal
is enabled. Or by a full scan of the residual blocks if not.
Change-Id: I9ab178e2f68a74135f0a8e20905b16405c77a62b
diff --git a/docs/source/modeling.rst b/docs/source/modeling.rst
index 33a4098..f06ef0d 100644
--- a/docs/source/modeling.rst
+++ b/docs/source/modeling.rst
@@ -1363,7 +1363,10 @@
Remove a residual block from the problem. Any parameters that the residual
block depends on are not removed. The cost and loss functions for the
residual block will not get deleted immediately; won't happen until the
- problem itself is deleted.
+ problem itself is deleted. If Problem::Options::enable_fast_removal is
+ true, then the removal is fast (almost constant time). Otherwise, removing a
+ residual block will incur a scan of the entire Problem object to verify that
+ the residual_block represents a valid residual in the problem.
**WARNING:** Removing a residual or parameter block will destroy
the implicit ordering, rendering the jacobian or residuals returned
@@ -1378,7 +1381,7 @@
of the problem (similar to cost/loss functions in residual block
removal). Any residual blocks that depend on the parameter are also
removed, as described above in RemoveResidualBlock(). If
- Problem::Options::enable_fast_parameter_block_removal is true, then
+ Problem::Options::enable_fast_removal is true, then
the removal is fast (almost constant time). Otherwise, removing a
parameter block will incur a scan of the entire Problem object.
@@ -1456,7 +1459,7 @@
Get all the residual blocks that depend on the given parameter
block.
- If `Problem::Options::enable_fast_parameter_block_removal` is
+ If `Problem::Options::enable_fast_removal` is
`true`, then getting the residual blocks is fast and depends only
on the number of residual blocks. Otherwise, getting the residual
blocks for a parameter block will incur a scan of the entire
diff --git a/include/ceres/problem.h b/include/ceres/problem.h
index a7d7815..bed792a 100644
--- a/include/ceres/problem.h
+++ b/include/ceres/problem.h
@@ -124,7 +124,7 @@
: cost_function_ownership(TAKE_OWNERSHIP),
loss_function_ownership(TAKE_OWNERSHIP),
local_parameterization_ownership(TAKE_OWNERSHIP),
- enable_fast_parameter_block_removal(false),
+ enable_fast_removal(false),
disable_all_safety_checks(false) {}
// These flags control whether the Problem object owns the cost
@@ -138,17 +138,21 @@
Ownership loss_function_ownership;
Ownership local_parameterization_ownership;
- // If true, trades memory for a faster RemoveParameterBlock() operation.
+ // If true, trades memory for faster RemoveResidualBlock() and
+ // RemoveParameterBlock() operations.
//
- // RemoveParameterBlock() takes time proportional to the size of the entire
- // Problem. If you only remove parameter blocks from the Problem
- // occassionaly, this may be acceptable. However, if you are modifying the
- // Problem frequently, and have memory to spare, then flip this switch to
+ // By default, RemoveParameterBlock() and RemoveResidualBlock() take time
+ // proportional to the size of the entire problem. If you only ever remove
+ // parameters or residuals from the problem occassionally, this might be
+ // acceptable. However, if you have memory to spare, enable this option to
// make RemoveParameterBlock() take time proportional to the number of
- // residual blocks that depend on it. The increase in memory usage is an
- // additonal hash set per parameter block containing all the residuals that
- // depend on the parameter block.
- bool enable_fast_parameter_block_removal;
+ // residual blocks that depend on it, and RemoveResidualBlock() take (on
+ // average) constant time.
+ //
+ // The increase in memory usage is twofold: an additonal hash set per
+ // parameter block containing all the residuals that depend on the parameter
+ // block; and a hash set in the problem containing all residuals.
+ bool enable_fast_removal;
// By default, Ceres performs a variety of safety checks when constructing
// the problem. There is a small but measurable performance penalty to
@@ -276,7 +280,7 @@
// residual blocks that depend on the parameter are also removed, as
// described above in RemoveResidualBlock().
//
- // If Problem::Options::enable_fast_parameter_block_removal is true, then the
+ // If Problem::Options::enable_fast_removal is true, then the
// removal is fast (almost constant time). Otherwise, removing a parameter
// block will incur a scan of the entire Problem object.
//
@@ -362,7 +366,7 @@
// Get all the residual blocks that depend on the given parameter block.
//
- // If Problem::Options::enable_fast_parameter_block_removal is true, then
+ // If Problem::Options::enable_fast_removal is true, then
// getting the residual blocks is fast and depends only on the number of
// residual blocks. Otherwise, getting the residual blocks for a parameter
// block will incur a scan of the entire Problem object.
diff --git a/internal/ceres/problem_impl.cc b/internal/ceres/problem_impl.cc
index 99f3f89..d9fb2af 100644
--- a/internal/ceres/problem_impl.cc
+++ b/internal/ceres/problem_impl.cc
@@ -142,7 +142,7 @@
// For dynamic problems, add the list of dependent residual blocks, which is
// empty to start.
- if (options_.enable_fast_parameter_block_removal) {
+ if (options_.enable_fast_removal) {
new_parameter_block->EnableResidualBlockDependencies();
}
parameter_block_map_[values] = new_parameter_block;
@@ -150,6 +150,26 @@
return new_parameter_block;
}
+void ProblemImpl::InternalRemoveResidualBlock(ResidualBlock* residual_block) {
+ CHECK_NOTNULL(residual_block);
+ // Perform no check on the validity of residual_block, that is handled in
+ // the public method: RemoveResidualBlock().
+
+ // If needed, remove the parameter dependencies on this residual block.
+ if (options_.enable_fast_removal) {
+ const int num_parameter_blocks_for_residual =
+ residual_block->NumParameterBlocks();
+ for (int i = 0; i < num_parameter_blocks_for_residual; ++i) {
+ residual_block->parameter_blocks()[i]
+ ->RemoveResidualBlock(residual_block);
+ }
+
+ ResidualBlockSet::iterator it = residual_block_set_.find(residual_block);
+ residual_block_set_.erase(it);
+ }
+ DeleteBlockInVector(program_->mutable_residual_blocks(), residual_block);
+}
+
// Deletes the residual block in question, assuming there are no other
// references to it inside the problem (e.g. by another parameter). Referenced
// cost and loss functions are tucked away for future deletion, since it is not
@@ -278,13 +298,18 @@
program_->residual_blocks_.size());
// Add dependencies on the residual to the parameter blocks.
- if (options_.enable_fast_parameter_block_removal) {
+ if (options_.enable_fast_removal) {
for (int i = 0; i < parameter_blocks.size(); ++i) {
parameter_block_ptrs[i]->AddResidualBlock(new_residual_block);
}
}
program_->residual_blocks_.push_back(new_residual_block);
+
+ if (options_.enable_fast_removal) {
+ residual_block_set_.insert(new_residual_block);
+ }
+
return new_residual_block;
}
@@ -475,30 +500,46 @@
void ProblemImpl::RemoveResidualBlock(ResidualBlock* residual_block) {
CHECK_NOTNULL(residual_block);
- // If needed, remove the parameter dependencies on this residual block.
- if (options_.enable_fast_parameter_block_removal) {
- const int num_parameter_blocks_for_residual =
- residual_block->NumParameterBlocks();
- for (int i = 0; i < num_parameter_blocks_for_residual; ++i) {
- residual_block->parameter_blocks()[i]
- ->RemoveResidualBlock(residual_block);
- }
+ // Verify that residual_block identifies a residual in the current problem.
+ const string residual_not_found_message =
+ StringPrintf("Residual block to remove: %p not found. This usually means "
+ "one of three things have happened:\n"
+ " 1) residual_block is uninitialised and points to a random "
+ "area in memory.\n"
+ " 2) residual_block represented a residual that was added to"
+ " the problem, but referred to a parameter block which has "
+ "since been removed, which removes all residuals which "
+ "depend on that parameter block, and was thus removed.\n"
+ " 3) residual_block referred to a residual that has already "
+ "been removed from the problem (by the user).",
+ residual_block);
+ if (options_.enable_fast_removal) {
+ CHECK(residual_block_set_.find(residual_block) !=
+ residual_block_set_.end())
+ << residual_not_found_message;
+ } else {
+ // Perform a full search over all current residuals.
+ CHECK(std::find(program_->residual_blocks().begin(),
+ program_->residual_blocks().end(),
+ residual_block) != program_->residual_blocks().end())
+ << residual_not_found_message;
}
- DeleteBlockInVector(program_->mutable_residual_blocks(), residual_block);
+
+ InternalRemoveResidualBlock(residual_block);
}
void ProblemImpl::RemoveParameterBlock(double* values) {
ParameterBlock* parameter_block =
FindParameterBlockOrDie(parameter_block_map_, values);
- if (options_.enable_fast_parameter_block_removal) {
+ if (options_.enable_fast_removal) {
// Copy the dependent residuals from the parameter block because the set of
// dependents will change after each call to RemoveResidualBlock().
vector<ResidualBlock*> residual_blocks_to_remove(
parameter_block->mutable_residual_blocks()->begin(),
parameter_block->mutable_residual_blocks()->end());
for (int i = 0; i < residual_blocks_to_remove.size(); ++i) {
- RemoveResidualBlock(residual_blocks_to_remove[i]);
+ InternalRemoveResidualBlock(residual_blocks_to_remove[i]);
}
} else {
// Scan all the residual blocks to remove ones that depend on the parameter
@@ -510,7 +551,7 @@
const int num_parameter_blocks = residual_block->NumParameterBlocks();
for (int j = 0; j < num_parameter_blocks; ++j) {
if (residual_block->parameter_blocks()[j] == parameter_block) {
- RemoveResidualBlock(residual_block);
+ InternalRemoveResidualBlock(residual_block);
// The parameter blocks are guaranteed unique.
break;
}
@@ -784,7 +825,7 @@
FindParameterBlockOrDie(parameter_block_map_,
const_cast<double*>(values));
- if (options_.enable_fast_parameter_block_removal) {
+ if (options_.enable_fast_removal) {
// In this case the residual blocks that depend on the parameter block are
// stored in the parameter block already, so just copy them out.
CHECK_NOTNULL(residual_blocks)->resize(
diff --git a/internal/ceres/problem_impl.h b/internal/ceres/problem_impl.h
index 75bdc2b..e846c03 100644
--- a/internal/ceres/problem_impl.h
+++ b/internal/ceres/problem_impl.h
@@ -45,6 +45,7 @@
#include "ceres/internal/macros.h"
#include "ceres/internal/port.h"
#include "ceres/internal/scoped_ptr.h"
+#include "ceres/collections_port.h"
#include "ceres/problem.h"
#include "ceres/types.h"
@@ -63,6 +64,7 @@
class ProblemImpl {
public:
typedef map<double*, ParameterBlock*> ParameterMap;
+ typedef HashSet<ResidualBlock*> ResidualBlockSet;
ProblemImpl();
explicit ProblemImpl(const Problem::Options& options);
@@ -160,9 +162,15 @@
Program* mutable_program() { return program_.get(); }
const ParameterMap& parameter_map() const { return parameter_block_map_; }
+ const ResidualBlockSet& residual_block_set() const {
+ CHECK(options_.enable_fast_removal)
+ << "Fast removal not enabled, residual_block_set is not maintained.";
+ return residual_block_set_;
+ }
private:
ParameterBlock* InternalAddParameterBlock(double* values, int size);
+ void InternalRemoveResidualBlock(ResidualBlock* residual_block);
bool InternalEvaluate(Program* program,
double* cost,
@@ -184,6 +192,9 @@
// The mapping from user pointers to parameter blocks.
map<double*, ParameterBlock*> parameter_block_map_;
+ // Iff enable_fast_removal is enabled, contains the current residual blocks.
+ ResidualBlockSet residual_block_set_;
+
// The actual parameter and residual blocks.
internal::scoped_ptr<internal::Program> program_;
diff --git a/internal/ceres/problem_test.cc b/internal/ceres/problem_test.cc
index 4507dfa..eb75e3a 100644
--- a/internal/ceres/problem_test.cc
+++ b/internal/ceres/problem_test.cc
@@ -378,7 +378,7 @@
struct DynamicProblem : public ::testing::TestWithParam<bool> {
DynamicProblem() {
Problem::Options options;
- options.enable_fast_parameter_block_removal = GetParam();
+ options.enable_fast_removal = GetParam();
problem.reset(new ProblemImpl(options));
}
@@ -390,9 +390,26 @@
}
bool HasResidualBlock(ResidualBlock* residual_block) {
- return find(problem->program().residual_blocks().begin(),
- problem->program().residual_blocks().end(),
- residual_block) != problem->program().residual_blocks().end();
+ 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
@@ -550,7 +567,7 @@
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(0, problem->NumResidualBlocks());
+ 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());
@@ -559,12 +576,12 @@
// removing it.
problem->RemoveParameterBlock(w);
ASSERT_EQ(2, problem->NumParameterBlocks());
- ASSERT_EQ(0, problem->NumResidualBlocks());
+ 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, problem->NumResidualBlocks());
+ 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());
@@ -572,12 +589,12 @@
// Now remove z, which is in the middle, and add it back.
problem->RemoveParameterBlock(z);
ASSERT_EQ(2, problem->NumParameterBlocks());
- ASSERT_EQ(0, problem->NumResidualBlocks());
+ 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, problem->NumResidualBlocks());
+ 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());
@@ -586,20 +603,20 @@
// y
problem->RemoveParameterBlock(y);
ASSERT_EQ(2, problem->NumParameterBlocks());
- ASSERT_EQ(0, problem->NumResidualBlocks());
+ 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, problem->NumResidualBlocks());
+ ASSERT_EQ(0, NumResidualBlocks());
EXPECT_EQ(w, GetParameterBlock(0)->user_state());
// w
problem->RemoveParameterBlock(w);
EXPECT_EQ(0, problem->NumParameterBlocks());
- EXPECT_EQ(0, problem->NumResidualBlocks());
+ EXPECT_EQ(0, NumResidualBlocks());
}
TEST_P(DynamicProblem, RemoveParameterBlockWithResiduals) {
@@ -607,7 +624,7 @@
problem->AddParameterBlock(z, 5);
problem->AddParameterBlock(w, 3);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(0, problem->NumResidualBlocks());
+ 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());
@@ -630,12 +647,12 @@
ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
EXPECT_EQ(3, problem->NumParameterBlocks());
- EXPECT_EQ(7, problem->NumResidualBlocks());
+ 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, problem->NumResidualBlocks());
+ ASSERT_EQ(3, NumResidualBlocks());
ASSERT_FALSE(HasResidualBlock(r_yzw));
ASSERT_TRUE (HasResidualBlock(r_yz ));
@@ -648,7 +665,7 @@
// Remove z, which will remove almost everything else.
problem->RemoveParameterBlock(z);
ASSERT_EQ(1, problem->NumParameterBlocks());
- ASSERT_EQ(1, problem->NumResidualBlocks());
+ ASSERT_EQ(1, NumResidualBlocks());
ASSERT_FALSE(HasResidualBlock(r_yzw));
ASSERT_FALSE(HasResidualBlock(r_yz ));
@@ -661,7 +678,7 @@
// Remove y; all gone.
problem->RemoveParameterBlock(y);
EXPECT_EQ(0, problem->NumParameterBlocks());
- EXPECT_EQ(0, problem->NumResidualBlocks());
+ EXPECT_EQ(0, NumResidualBlocks());
}
TEST_P(DynamicProblem, RemoveResidualBlock) {
@@ -699,14 +716,14 @@
EXPECT_TRUE(GetParameterBlock(2)->mutable_residual_blocks() == NULL);
}
EXPECT_EQ(3, problem->NumParameterBlocks());
- EXPECT_EQ(7, problem->NumResidualBlocks());
+ 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, problem->NumResidualBlocks());
+ ASSERT_EQ(6, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_yw, r_y);
ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
@@ -722,7 +739,7 @@
// Remove r_yw.
problem->RemoveResidualBlock(r_yw);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(5, problem->NumResidualBlocks());
+ ASSERT_EQ(5, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_y);
ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
@@ -737,7 +754,7 @@
// Remove r_zw.
problem->RemoveResidualBlock(r_zw);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(4, problem->NumResidualBlocks());
+ ASSERT_EQ(4, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_y);
ExpectParameterBlockContains(z, r_yz, r_z);
@@ -751,7 +768,7 @@
// Remove r_w.
problem->RemoveResidualBlock(r_w);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(3, problem->NumResidualBlocks());
+ ASSERT_EQ(3, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_yz, r_y);
ExpectParameterBlockContains(z, r_yz, r_z);
@@ -764,7 +781,7 @@
// Remove r_yz.
problem->RemoveResidualBlock(r_yz);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(2, problem->NumResidualBlocks());
+ ASSERT_EQ(2, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y, r_y);
ExpectParameterBlockContains(z, r_z);
@@ -777,7 +794,7 @@
problem->RemoveResidualBlock(r_z);
problem->RemoveResidualBlock(r_y);
ASSERT_EQ(3, problem->NumParameterBlocks());
- ASSERT_EQ(0, problem->NumResidualBlocks());
+ ASSERT_EQ(0, NumResidualBlocks());
if (GetParam()) {
ExpectParameterBlockContains(y);
ExpectParameterBlockContains(z);
@@ -785,6 +802,56 @@
}
}
+TEST_P(DynamicProblem, RemoveInvalidResidualBlockDies) {
+ problem->AddParameterBlock(y, 4);
+ problem->AddParameterBlock(z, 5);
+ problem->AddParameterBlock(w, 3);
+
+ // 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, NULL, y, z, w);
+ ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, NULL, y, z);
+ ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, NULL, y, w);
+ ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, NULL, z, w);
+ ResidualBlock* r_y = problem->AddResidualBlock(cost_y, NULL, y);
+ ResidualBlock* r_z = problem->AddResidualBlock(cost_z, NULL, z);
+ ResidualBlock* r_w = problem->AddResidualBlock(cost_w, NULL, w);
+
+ // 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;
+ ResidualBlock* 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 vector<T>& b) {