Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue258 add fixed size optimizer #261

Open
wants to merge 18 commits into
base: issue258-windowed-optimizer-base-class
Choose a base branch
from

Conversation

jakemclaughlin6
Copy link
Contributor

No description provided.

@jakemclaughlin6
Copy link
Contributor Author

@svwilliams Can this be reviewed soon, not sure if you would like some tests written for it since the other optimizers dont have any either, but the changes I made to timestamp tracker have been tested and I've also been using this optimizer for my own code and it seems to work well.

@svwilliams
Copy link
Contributor

It may be a couple of weeks before I can take a look. But I'll do my best to get to it sooner.

@jakemclaughlin6
Copy link
Contributor Author

This PR implements a fixed size window optimizer where the window is determined by the number of "states" in it. The number of states are determined by the total # of unique timestamps exist in the graph.

Another addition from this PR is the fix to the following issue: #248. It would seem that what was occurring in a fringe edge case was a sensor model was sending a transaction and adding constraints to variables that were already chosen to be marginalized out in the previous cycle, the fix involved just checking for these added constraints and removing them. I am curious if this fix is suitable or if there should be another method to actually re marginalize the constraints that have been added @svwilliams.

Lastly, a duplicate of the reset service was implemented but as a message callback, this is for the case where a system would like to call a reset however only knows when it would like to reset in its own message callbacks. Calling the service inside the sensors callback created a deadlock, making a message callback version allows for the sensor model to ask for a reset and entrust that all onStart() and onStop() functions perform the required logic to reset the system

Copy link
Contributor

@svwilliams svwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My big concern is in the VariableStampIndex. It seems to me that timestamps can be removed while they are still in use by other variables.

Additionally, there is a bit of cleanup needed in CMakelists and numerous linter errors that need to be fixed to conform to ROS coding conventions:

/fuse_optimizers/include/fuse_optimizers/variable_stamp_index.h:195:  At least two spaces is best between code and comments  [whitespace/comments] [2]
/fuse_optimizers/include/fuse_optimizers/variable_stamp_index.h:195:  Add #include <set> for set<>  [build/include_what_you_use] [4]
/fuse_optimizers/include/fuse_optimizers/windowed_optimizer.h:188:  At least two spaces is best between code and comments  [whitespace/comments] [2]
/fuse_optimizers/src/fixed_size_smoother.cpp:89:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/fixed_size_smoother.cpp:89:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/fixed_size_smoother.cpp:89:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/fixed_size_smoother.cpp:89:  Using C-style cast.  Use static_cast<int>(...) instead  [readability/casting] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:50:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:50:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:50:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:58:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:58:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:58:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:62:  Line ends in whitespace.  Consider deleting these extra spaces.  [whitespace/end_of_line] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:64:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:64:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:65:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:65:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:65:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:67:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:67:  Missing space before ( in for(  [whitespace/parens] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:67:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:113:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:113:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:113:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:139:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/variable_stamp_index.cpp:139:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:139:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/variable_stamp_index.cpp:66:  Add #include <set> for set<>  [build/include_what_you_use] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:110:  Line ends in whitespace.  Consider deleting these extra spaces.  [whitespace/end_of_line] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:180:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:180:  Missing space around colon in range-based for loop  [whitespace/forcolon] [2]
/fuse_optimizers/src/windowed_optimizer.cpp:180:  Missing space before ( in for(  [whitespace/parens] [5]
/fuse_optimizers/src/windowed_optimizer.cpp:180:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/windowed_optimizer.cpp:181:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:181:  Missing space around colon in range-based for loop  [whitespace/forcolon] [2]
/fuse_optimizers/src/windowed_optimizer.cpp:181:  Missing space before ( in for(  [whitespace/parens] [5]
/fuse_optimizers/src/windowed_optimizer.cpp:181:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/windowed_optimizer.cpp:182:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:183:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:190:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:190:  Missing space before ( in if(  [whitespace/parens] [5]
/fuse_optimizers/src/windowed_optimizer.cpp:190:  Missing space before {  [whitespace/braces] [5]
/fuse_optimizers/src/windowed_optimizer.cpp:192:  when starting a new scope, { should be on a line by itself  [whitespace/braces] [4]
/fuse_optimizers/src/windowed_optimizer.cpp:192:  Missing space around colon in range-based for loop  [whitespace/forcolon] [2]

@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.0.2)
project(fuse_optimizers)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should never set the CMAKE_CXX_FLAGS directly in a catkin package. If you are using catkin_make, then a single makefile is created for the entire workspace. Modifying CXX_FLAGS like this pollutes the entire workspace. It's better to add the required compile flags on a per-target basis.

Suggested change
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")

And you should find_package(OpenMP) before assuming OpenMP is available.

And then add something like this to the library config

target_link_libraries(${PROJECT_NAME}
  ${OpenMP_CXX_LIBRARIES}
)
target_compile_options(${PROJECT_NAME}
  PRIVATE ${OpenMP_CXX_FLAGS}
)

/*
* Software License Agreement (BSD License)
*
* Copyright (c) 2019, Locus Robotics
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (c) 2019, Locus Robotics
* Copyright (c) 2022, Locus Robotics

namespace fuse_optimizers
{
/**
* @brief A fixed-Size smoother implementation that marginalizes out variables that are older than a defined Size time
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief A fixed-Size smoother implementation that marginalizes out variables that are older than a defined Size time
* @brief A fixed-size smoother implementation that marginalizes out variables that are older than a defined size

Or maybe?

Suggested change
* @brief A fixed-Size smoother implementation that marginalizes out variables that are older than a defined Size time
* @brief A fixed-size smoother implementation that marginalizes out variables that are older than a defined buffer length

Also, it looks like find&replace got away from you. Review all of the comments and make sure Size is properly capitalized and makes sense in the sentence.

* (1) new variables and constraints are added to the graph
* (2) the augmented graph is optimized and the variable values are updated
* (3) all motion models, sensors, and publishers are notified of the updated graph
* (4) all variables older than "current time - Size duration" are marginalized out.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this statement should be revised

SMART_PTR_DEFINITIONS(FixedSizeSmootherParams);

/**
* @brief The duration of the smoothing window in seconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement needs to be revised

Comment on lines 137 to 147
// remove from unique stamps
auto stamp = stamped_index_[variable_uuid];
if(unique_stamps_.find(stamp) != unique_stamps_.end()){
unique_stamps_.erase(stamp);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is important:
Multiple variables may be associated with the same timestamp. The motion model in the fuse_models package uses position, orientation, linear velocity, angular velocity, and linear acceleration variables all at the exact same timestamp. Just because you remove the linear acceleration variable doesn't mean that the other variables are also removed. It feels like you really need to keep track of the individual variables associated with each timestamp, and only remove a timestamp once all variables have been removed.
Some sort of structure like:

using StampToVariablesMap = std::map<ros::Time, std::unordered_set<fuse_core::UUID>>;
StampToVariablesMap unique_stamps_;

Comment on lines 178 to 190
// check if new transaction has added constraints to variables that are to be marginalized
std::vector<fuse_core::UUID> faulty_constraints;
for(auto& c: new_transaction->addedConstraints()){
for(auto var_uuid: c.variables()){
for (auto marginal_uuid : marginal_transaction_.removedVariables()) {
if (var_uuid == marginal_uuid) {
faulty_constraints.push_back(c.uuid());
break;
}
}
}
}
if(faulty_constraints.size() > 0){
ROS_WARN_STREAM("Removing invalid constraints.");
for(auto& faulty_constraint: faulty_constraints){
new_transaction->removeConstraint(faulty_constraint);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. There is indeed a timing problem. At the end of Loop1 we compute a set of variables to marginalize. But those variables are not removed until the graph is updated in Loop2. The sensor models have no way of knowing a variable was removed in Loop1 until after Loop2 has been processed and the updated graph is sent back to the sensor models. So a sensor model can very reasonably send new constraints involving variables that should have been removed.

I originally implemented it this way to avoid optimizing the graph twice. Once when new constraints are added, and a second time to remove the marginalized variables. In retrospect, marginalizing variables from the graph should not alter the optimized values of the remaining variables. So I could just remove the marginalized variables at the end of the loop without reoptimizing. But I wouldn't want to make that change without a lot of testing first.

For now, your fix should be valid. Thanks.

Comment on lines 426 to 445
// Tell all the plugins to stop
stopPlugins();
// Reset the optimizer state
{
std::lock_guard<std::mutex> lock(optimization_requested_mutex_);
optimization_request_ = false;
}
started_ = false;
ignited_ = false;
setStartTime(ros::Time(0, 0));
// DANGER: The optimizationLoop() function obtains the lock optimization_mutex_ lock and the
// pending_transactions_mutex_ lock at the same time. We perform a parallel locking scheme here to
// prevent the possibility of deadlocks.
{
std::lock_guard<std::mutex> lock(optimization_mutex_);
// Clear all pending transactions
{
std::lock_guard<std::mutex> lock(pending_transactions_mutex_);
pending_transactions_.clear();
}
// Clear the graph and marginal tracking states
graph_->clear();
marginal_transaction_ = fuse_core::Transaction();
}
// Perform any required reset operations for derived classes
onReset();
// Tell all the plugins to start
startPlugins();
// Test for auto-start
autostart();
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is identical to the code in the resetServiceCallback(). Can you pull this out into a reset() function, and call that function from both resetServiceCallback() and resetMessageCallback()?

Comment on lines 335 to 345
int N = 10;

for(int i = 0; i < N; i++){
auto x = StampedVariable::make_shared(ros::Time(i, 0));
auto transaction = fuse_core::Transaction();
transaction.addVariable(x);
index.addNewTransaction(transaction);
}

// Verify the number of unique stamps
EXPECT_EQ((size_t)N, index.numStates());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use unsigned types for N and i, you won't need to typecast anything.

Suggested change
int N = 10;
for(int i = 0; i < N; i++){
auto x = StampedVariable::make_shared(ros::Time(i, 0));
auto transaction = fuse_core::Transaction();
transaction.addVariable(x);
index.addNewTransaction(transaction);
}
// Verify the number of unique stamps
EXPECT_EQ((size_t)N, index.numStates());
unsigned int N = 10u;
for(auto i = 0u; i < N; i++)
{
auto x = StampedVariable::make_shared(ros::Time(i, 0));
auto transaction = fuse_core::Transaction();
transaction.addVariable(x);
index.addNewTransaction(transaction);
}
// Verify the number of unique stamps
EXPECT_EQ(N, index.numStates());

Comment on lines 367 to 368
int N = 10;
for(int i = 0; i < N; i++){
auto x = StampedVariable::make_shared(ros::Time(i, 0));
auto transaction = fuse_core::Transaction();
transaction.addVariable(x);
index.addNewTransaction(transaction);
}

// Verify the stamps of the states using ithStamp function
for(int i = 0; i < N; i++){
EXPECT_EQ(ros::Time(i, 0), index.ithStamp((size_t)i));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Suggested change
int N = 10;
for(int i = 0; i < N; i++){
auto x = StampedVariable::make_shared(ros::Time(i, 0));
auto transaction = fuse_core::Transaction();
transaction.addVariable(x);
index.addNewTransaction(transaction);
}
// Verify the stamps of the states using ithStamp function
for(int i = 0; i < N; i++){
EXPECT_EQ(ros::Time(i, 0), index.ithStamp((size_t)i));
}
unsigned int N = 10u;
for(auto i = 0u; i < N; i++)
{
auto x = StampedVariable::make_shared(ros::Time(i, 0));
auto transaction = fuse_core::Transaction();
transaction.addVariable(x);
index.addNewTransaction(transaction);
}
// Verify the stamps of the states using ithStamp function
for(auto i = 0u; i < N; i++)
{
EXPECT_EQ(ros::Time(i, 0), index.ithStamp(i));
}

@jakemclaughlin6 jakemclaughlin6 force-pushed the issue258-add-fixed-size-optimizer branch from 74876a6 to 70c03e5 Compare January 18, 2023 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants