-
Notifications
You must be signed in to change notification settings - Fork 119
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
base: issue258-windowed-optimizer-base-class
Are you sure you want to change the base?
Issue258 add fixed size optimizer #261
Conversation
@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. |
It may be a couple of weeks before I can take a look. But I'll do my best to get to it sooner. |
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 |
There was a problem hiding this 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]
fuse_optimizers/CMakeLists.txt
Outdated
@@ -1,6 +1,8 @@ | |||
cmake_minimum_required(VERSION 3.0.2) | |||
project(fuse_optimizers) | |||
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") |
There was a problem hiding this comment.
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.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @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?
* @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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
// remove from unique stamps | ||
auto stamp = stamped_index_[variable_uuid]; | ||
if(unique_stamps_.find(stamp) != unique_stamps_.end()){ | ||
unique_stamps_.erase(stamp); | ||
} |
There was a problem hiding this comment.
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_;
// 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); | ||
} | ||
} |
There was a problem hiding this comment.
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.
// 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; |
There was a problem hiding this comment.
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()
?
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()); |
There was a problem hiding this comment.
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.
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()); |
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)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
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)); | |
} |
Fix invalid constraints
…volved threads, added mutex guards to the fixed-lag smoother. Testing is still needed.
74876a6
to
70c03e5
Compare
No description provided.