├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include └── imesa │ ├── biased_prior-inl.h │ ├── biased_prior.h │ ├── imesa.h │ └── incremental_sam_agent.h ├── media ├── imesa_header_photo.png ├── len_5000_1_clip.gif ├── rpl.png └── scale_10_0.gif └── src ├── biased_prior.cpp └── imesa.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | ColumnLimit: 120 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ 3 | __pycache__/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(imesa CXX C) 2 | cmake_minimum_required(VERSION 2.8.3) 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | # Dependencies 6 | find_package(GTSAM REQUIRED) 7 | include_directories(${GTSAM_INCLUDE_DIR}) 8 | 9 | 10 | message(STATUS "================ iMESA ======================") 11 | # Build the iMESA library 12 | file(GLOB_RECURSE imesa_srcs "*.cpp" "*.h") 13 | add_library(imesa ${imesa_srcs}) 14 | target_link_libraries(imesa gtsam) 15 | target_include_directories(imesa PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Daniel McGann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Incremental Manifold Edge-based Separable ADMM (iMESA) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)  [](https://rpl.ri.cmu.edu/) 3 | 4 |

5 | Overview of the iMESA algorithm operating on a toy dataset. 8 | Overview of the iMESA algorithm operating on a toy dataset 9 |

10 | 11 | 12 | iMESA is an incremental distributed back-end algorithm for Collaborative Simultaneous Localization and Mapping (C-SLAM). For real-world deployments, robotic teams require algorithms to compute a consistent state estimate accurately, within online runtime constraints, and with potentially limited communication. In this package we provide the original implementation of "Incremental Manifold Edge-based Separable ADMM" (iMESA) a fully distributed C-SLAM back-end algorithm that can provide a multi-robot team with accurate state estimates in real-time with only sparse pair-wise communication between robots. This algorithm is described in full in our paper "iMESA: Incremental Distributed Optimization for Collaborative Simultaneous Localization and Mapping" which can be accessed on [arXiv](https://arxiv.org/pdf/2406.07371). If you use this package please cite our paper as: 13 | 14 | ``` 15 | @inproceedings{mcgann_imesa_2024, 16 | title = {{iMESA}: Incremental Distributed Optimization for Collaborative Simultaneous Localization and Mapping}, 17 | author = {D. McGann and M. Kaess}, 18 | fullauthor = {Daniel McGann and Michael Kaess}, 19 | booktitle = {Proc. Robotics: Science and Systems (RSS)}, 20 | year = 2024, 21 | pages = {n/a}, % will be added soon 22 | address = {Delft, {NL}} 23 | } 24 | ``` 25 | 26 | Extensive evaluation on real and synthetic data demonstrates that iMESA is able to outperform comparable state-of-the-art C-SLAM back-ends. Below we have an examples of such performance on a long 5 robot dataset, and a shorter 10 robot dataset. In these animations color represents the magnitude of error for individual poses. 27 | 28 |

29 | iMESA Compared to prior works on a long 5 robot synthetic dataset 32 | iMESA Compared to prior works on a long 5 robot synthetic dataset 33 |

34 | 35 |

36 | iMESA Compared to prior works on a short 10 robot synthetic dataset 39 | iMESA Compared to prior works on a short 10 robot synthetic dataset 40 |

41 | 42 | 43 | ## Documentation 44 | We provide the implementation of iMESA as a simple C++ library. This library provides a class `IMESAAgent` that can be used on-board each robot in a multi-robot team to solve their collaborative state estimate using the iMESA algorithm. Practically, this class is used much like existing incremental SLAM solvers like `ISAM2` as provided by `gtsam`, with extensions to incorporate information from communications with other robots. 45 | 46 | See `include/imesa/imesa.h` for inline documentation on this class's interface. Additionally, see our [experiments repository](https://github.com/rpl-cmu/imesa-experiments) for an example of how this library can be used. The best place for documentation on the algorithm itself is the paper discussed above! 47 | 48 | We additionally provide a more generic interface (`IncrementalSAMAgent`) that can be inherited from, to implement other incremental C-SLAM solvers that use the same 2-stage communication procedure. See our implementation of DDF-SAM2 in our [experiments repository](https://github.com/rpl-cmu/imesa-experiments) for an example of how to write a custom algorithm using this interface. 49 | 50 | ### Variable Conventions 51 | iMESA uses uses the keys of variables to implicitly identify which variables are shared with other robots. The library assumes that all variable keys are represented according to the `gtsam::Symbol` convention and consist of a character that denotes the unique identifier of the robot that owns the variable and a integer representing that index of the variable. For example the 15th pose of robot "a" will be `a15`. The iMESA algorithm identifies shared variables as any variable with a key denoting that it is owned by another robot. 52 | 53 | This library also provides functionality to handle global variables that are variables in the SLAM optimization that do not necessarily belong to any singular robot (i.e. landmarks). Such variables are marked with a `#` character in their variable key. Handling these variables requires some additional information to be shared during communication (keys of global vars). This functionality was not explicitly discussed in the paper above, but has been tested and users are safe to use this functionality! 54 | 55 | ## Dependencies 56 | The only dependency for iMESA is [GTSAM](https://github.com/borglab/gtsam). iMESA is sensitive to the version of GTSAM used. We have tested iMESA with GTSAM version `4.2.0` and recommend its use. 57 | 58 | iMESA additionally needs some development functionality in GTSAM that unfortunately is not included in any tagged release. The details of these changes are summarized in this [pull request](https://github.com/borglab/gtsam/pull/1504). For convenience we provide these changes cherry-picked onto GTSAM `4.2.0` via our [fork](https://github.com/DanMcGann/gtsam) under branch `4.2.0-imesa`. 59 | 60 | 61 | ## Installation Instructions 62 | 63 | As this package is only a library it is most frequently will be used as a 3rd party dependency within your project. We encourage users to use `FetchContext` to access and make the iMESA library available within their own project. 64 | 65 | You can include iMESA as a dependency in your project by adding the following to your `CMakeLists.txt` file: 66 | 67 | ``` 68 | include(FetchContent) 69 | FetchContent_Declare( 70 | imesa 71 | GIT_REPOSITORY https://github.com/rpl-cmu/imesa.git 72 | GIT_TAG main 73 | ) 74 | FetchContent_MakeAvailable(imesa) 75 | ``` 76 | 77 | The project in which iMESA is used will need to be build against the GTSAM version discussed above. An example of this in practice can be found in the [experiments repository](https://github.com/rpl-cmu/imesa-experiments). 78 | 79 | # Issues 80 | If you encounter issues with this library please fill out a bug report on github with details on how to reproduce the error! -------------------------------------------------------------------------------- /include/imesa/biased_prior-inl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /** @brief Implementation of Split and Geodesic Biased Priors. 3 | * 4 | * @author Dan McGann 5 | * @date October, 6th 2023 6 | */ 7 | 8 | #include "imesa/biased_prior.h" 9 | 10 | namespace biased_priors { 11 | 12 | /*********************************************************************************************************************/ 13 | template 14 | gtsam::Vector GeodesicBiasedPrior::constraintError(const VAR_TYPE& x, const VAR_TYPE& z, 15 | boost::optional Hx) { 16 | return gtsam::traits::Local(z, x, boost::none, Hx); 17 | } 18 | 19 | /*********************************************************************************************************************/ 20 | template 21 | gtsam::Vector GeodesicBiasedPrior::genericConstraintError(const gtsam::Value& x, const gtsam::Value& z, 22 | boost::optional Hx) { 23 | return constraintError(x.template cast(), z.template cast(), Hx); 24 | } 25 | 26 | /*********************************************************************************************************************/ 27 | template 28 | gtsam::Vector GeodesicBiasedPrior::evaluateError(const VAR_TYPE& val, 29 | boost::optional H) const { 30 | gtsam::Vector err_vec = 31 | constraintError(val, this->vals_ptr_->shared_estimate_lin_point->template cast(), H); 32 | if (H) (*H) = sqrt(this->vals_ptr_->penalty) * (*H); 33 | return sqrt(this->vals_ptr_->penalty) * (err_vec + (this->vals_ptr_->dual_lin_point / this->vals_ptr_->penalty)); 34 | } 35 | 36 | /*********************************************************************************************************************/ 37 | template 38 | gtsam::Vector SplitBiasedPrior::constraintError(const VAR_TYPE& x, const VAR_TYPE& z, 39 | boost::optional Hx) { 40 | // Translation Error 41 | gtsam::Matrix dtrans_dx, dlocal_dtrans; 42 | gtsam::Vector translation_error = gtsam::traits::Local( 43 | z.translation(), x.translation(&dtrans_dx), boost::none, &dlocal_dtrans); 44 | 45 | // Rotation Error 46 | gtsam::Matrix drot_dx, dlocal_drot; 47 | gtsam::Vector rotation_error = 48 | gtsam::traits::Local(z.rotation(), x.rotation(&drot_dx), boost::none, &dlocal_drot); 49 | 50 | // Construct the result 51 | gtsam::Vector constraint_satisfaction_gap = gtsam::Vector::Zero(translation_error.size() + rotation_error.size()); 52 | constraint_satisfaction_gap << translation_error, rotation_error; 53 | if (Hx) { 54 | gtsam::Matrix dtrans_err_dx = dlocal_dtrans * dtrans_dx; 55 | gtsam::Matrix drot_err_dx = dlocal_drot * drot_dx; 56 | 57 | // Vertical concatenation 58 | *Hx = gtsam::Matrix(dtrans_err_dx.rows() + drot_err_dx.rows(), dtrans_err_dx.cols()); 59 | *Hx << dtrans_err_dx, drot_err_dx; 60 | } 61 | 62 | return constraint_satisfaction_gap; 63 | } 64 | 65 | /*********************************************************************************************************************/ 66 | template 67 | gtsam::Vector SplitBiasedPrior::genericConstraintError(const gtsam::Value& x, const gtsam::Value& z, 68 | boost::optional Hx) { 69 | return constraintError(x.template cast(), z.template cast(), Hx); 70 | } 71 | 72 | /*********************************************************************************************************************/ 73 | template 74 | gtsam::Vector SplitBiasedPrior::evaluateError(const VAR_TYPE& val, boost::optional H) const { 75 | gtsam::Vector err_vec = 76 | constraintError(val, this->vals_ptr_->shared_estimate_lin_point->template cast(), H); 77 | if (H) (*H) = sqrt(this->vals_ptr_->penalty) * (*H); 78 | return sqrt(this->vals_ptr_->penalty) * (err_vec + (this->vals_ptr_->dual_lin_point / this->vals_ptr_->penalty)); 79 | } 80 | 81 | /*********************************************************************************************************************/ 82 | template 83 | LIE_TYPE baseInterpolateSLERP(const LIE_TYPE& pa, const LIE_TYPE& pb, double alpha) { 84 | return gtsam::traits::Retract(pa, alpha * gtsam::traits::Local(pa, pb)); 85 | } 86 | 87 | /*********************************************************************************************************************/ 88 | template 89 | POSE_TYPE baseInterpolateSPLIT(const POSE_TYPE& start_pose, const POSE_TYPE& end_pose, double alpha) { 90 | typename POSE_TYPE::Rotation rs = start_pose.rotation(); 91 | typename POSE_TYPE::Rotation re = end_pose.rotation(); 92 | typename POSE_TYPE::Rotation interp_rot = rs.compose(gtsam::traits::Expmap( 93 | alpha * gtsam::traits::Logmap(rs.inverse().compose(re)))); 94 | typename POSE_TYPE::Translation interp_trans = 95 | start_pose.translation() + (alpha * (end_pose.translation() - start_pose.translation())); 96 | return POSE_TYPE(interp_rot, interp_trans); 97 | } 98 | 99 | /*********************************************************************************************************************/ 100 | /// @brief Constructs a biased prior factor templatized 101 | template