Skip to content

Commit 69f5bee

Browse files
Fix marginalization of non-stamped variables in fixed-lag smoother
Old stamped variables (e.g. expired poses) were incorrectly shielded from marginalization when they shared a cross-window constraint with a recent stamped variable (e.g. an odometry factor linking kf_N to kf_N+1). This prevented prompt marginalization of expired poses and, more importantly, kept non-stamped variables like landmarks alive in the graph indefinitely even after all their associated poses left the sliding window. The fix skips old stamped variables when building the connected (protected) set in VariableStampIndex::query(), so they are returned as marginalization candidates alongside any exclusively-attached landmarks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bec8515 commit 69f5bee

3 files changed

Lines changed: 27 additions & 8 deletions

File tree

vesta_optimizers/include/vesta_optimizers/variable_stamp_index.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ class VariableStampIndex
142142
}
143143
}
144144

145-
// Now find all of the variables connected to the recent variables
145+
// Now find all of the variables connected to the recent variables.
146+
// Old stamped variables (stamp < threshold) are NOT added to the connected
147+
// set even if they share a constraint with a recent variable (e.g. via an
148+
// odometry factor). This ensures that expired poses—and the non-stamped
149+
// variables attached exclusively to them—are marginalized promptly instead
150+
// of being kept alive by cross-window constraints.
146151
std::unordered_set<vesta_core::UUID> connected_variable_uuids;
147152
for (const auto& recent_variable_uuid : recent_variable_uuids)
148153
{
@@ -160,6 +165,14 @@ class VariableStampIndex
160165
{
161166
for (const auto& connected_variable_uuid : constraints_iter->second)
162167
{
168+
// Skip old stamped variables: they are candidates for
169+
// marginalization and should not be protected by cross-window
170+
// constraints (e.g. odometry linking an old pose to a recent one)
171+
const auto stamped_iter = stamped_index_.find(connected_variable_uuid);
172+
if (stamped_iter != stamped_index_.end() && stamped_iter->second < stamp)
173+
{
174+
continue;
175+
}
163176
connected_variable_uuids.insert(connected_variable_uuid);
164177
}
165178
}

vesta_optimizers/src/fixed_lag_smoother.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@ ceres::Solver::Summary FixedLagSmoother::optimize()
164164
marginal_transaction_ = vesta_constraints::marginalizeVariables(
165165
"FixedLagSmoother", computeVariablesToMarginalize(lag_expiration_), *graph_);
166166

167-
// TODO: marginalize non stamped variables that are connected to the
168-
// marginalized subgraph and not the window subgraph
169-
170167
// Perform any post-marginal cleanup
171168
postprocessMarginalization(marginal_transaction_);
172169
// Note: The marginal transaction will not be applied until the next

vesta_optimizers/test/test_variable_stamp_index.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,19 @@ TEST(VariableStampIndex, Query)
310310
transaction.addConstraint(c5);
311311
index.addNewTransaction(transaction);
312312

313-
auto expected1 = std::vector<vesta_core::UUID>{};
313+
// x1 (stamp=1) is old at threshold 1.5, and its only cross-window link is
314+
// to x2 via c1. Old stamped variables are no longer shielded by
315+
// cross-window constraints, so x1 is now returned for marginalization.
316+
auto expected1 = std::vector<vesta_core::UUID>{ x1->uuid() };
317+
std::sort(expected1.begin(), expected1.end());
314318
auto actual1 = std::vector<vesta_core::UUID>();
315319
index.query(vesta_core::Timestamp(1, 500000), std::back_inserter(actual1));
320+
std::sort(actual1.begin(), actual1.end());
316321
EXPECT_EQ(expected1, actual1);
317322

318-
auto expected2 = std::vector<vesta_core::UUID>{ x1->uuid(), l1->uuid() };
323+
// At threshold 2.5, x1 (stamp=1) and x2 (stamp=2) are both old.
324+
// l1 is connected only to old variables. All three are returned.
325+
auto expected2 = std::vector<vesta_core::UUID>{ x1->uuid(), x2->uuid(), l1->uuid() };
319326
std::sort(expected2.begin(), expected2.end());
320327
auto actual2 = std::vector<vesta_core::UUID>();
321328
index.query(vesta_core::Timestamp(2, 500000), std::back_inserter(actual2));
@@ -367,8 +374,10 @@ TEST(VariableStampIndex, MarginalTransaction)
367374
// The x1 variable should be removed
368375
EXPECT_EQ(4u, index.size());
369376

370-
// And the marginal constraint x3->l1 should not affect future queries
371-
auto expected = std::vector<vesta_core::UUID>{ l1->uuid() };
377+
// At threshold 2.5 only x3 (stamp=3) is recent. x2 (stamp=2) is old and
378+
// no longer shielded by the cross-window constraint c2(x2,x3). l1 is
379+
// connected only to x2 (old). Both x2 and l1 are returned.
380+
auto expected = std::vector<vesta_core::UUID>{ x2->uuid(), l1->uuid() };
372381
std::sort(expected.begin(), expected.end());
373382
auto actual = std::vector<vesta_core::UUID>();
374383
index.query(vesta_core::Timestamp(2, 500000), std::back_inserter(actual));

0 commit comments

Comments
 (0)