diff --git a/.github/scripts/unix.sh b/.github/scripts/unix.sh index 6abbb55965..7fb9255939 100644 --- a/.github/scripts/unix.sh +++ b/.github/scripts/unix.sh @@ -68,6 +68,8 @@ function configure() -DGTSAM_USE_QUATERNIONS=${GTSAM_USE_QUATERNIONS:-OFF} \ -DGTSAM_ROT3_EXPMAP=${GTSAM_ROT3_EXPMAP:-ON} \ -DGTSAM_POSE3_EXPMAP=${GTSAM_POSE3_EXPMAP:-ON} \ + -DGTSAM_USE_SYSTEM_EIGEN=${GTSAM_USE_SYSTEM_EIGEN:-OFF} \ + -DGTSAM_USE_SYSTEM_METIS=${GTSAM_USE_SYSTEM_METIS:-OFF} \ -DGTSAM_BUILD_WITH_MARCH_NATIVE=OFF \ -DBOOST_ROOT=$BOOST_ROOT \ -DBoost_NO_SYSTEM_PATHS=ON \ diff --git a/.github/workflows/build-special.yml b/.github/workflows/build-special.yml index 3bb3fa2988..647b9c0f18 100644 --- a/.github/workflows/build-special.yml +++ b/.github/workflows/build-special.yml @@ -55,6 +55,12 @@ jobs: version: "9" flag: cayley + - name: ubuntu-system-libs + os: ubuntu-18.04 + compiler: gcc + version: "9" + flag: system-libs + steps: - name: Checkout uses: actions/checkout@v2 @@ -126,6 +132,12 @@ jobs: echo "GTSAM_ROT3_EXPMAP=OFF" >> $GITHUB_ENV echo "GTSAM Uses Cayley map for Rot3" + - name: Use system versions of 3rd party libraries + if: matrix.flag == 'system' + run: | + echo "GTSAM_USE_SYSTEM_EIGEN=ON" >> $GITHUB_ENV + echo "GTSAM_USE_SYSTEM_METIS=ON" >> $GITHUB_ENV + - name: Build & Test run: | bash .github/scripts/unix.sh -t diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fdadc68a8..d2559705d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,11 +38,14 @@ if(${GTSAM_SOURCE_DIR} STREQUAL ${GTSAM_BINARY_DIR}) message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ") endif() +include(cmake/HandleGeneralOptions.cmake) # CMake build options + +# Libraries: include(cmake/HandleBoost.cmake) # Boost include(cmake/HandleCCache.cmake) # ccache include(cmake/HandleCPack.cmake) # CPack include(cmake/HandleEigen.cmake) # Eigen3 -include(cmake/HandleGeneralOptions.cmake) # CMake build options +include(cmake/HandleMetis.cmake) # metis include(cmake/HandleMKL.cmake) # MKL include(cmake/HandleOpenMP.cmake) # OpenMP include(cmake/HandlePerfTools.cmake) # Google perftools diff --git a/INSTALL.md b/INSTALL.md index 3a0f2896a4..9652463041 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -13,7 +13,8 @@ $ make install ## Important Installation Notes 1. GTSAM requires the following libraries to be installed on your system: - - BOOST version 1.65 or greater (install through Linux repositories or MacPorts) + - BOOST version 1.65 or greater (install through Linux repositories or MacPorts). Please see [Boost Notes](#boost-notes). + - Cmake version 3.0 or higher - Support for XCode 4.3 command line tools on Mac requires CMake 2.8.8 or higher @@ -66,11 +67,15 @@ execute commands as follows for an out-of-source build: This will build the library and unit tests, run all of the unit tests, and then install the library itself. +## Boost Notes + +Versions of Boost prior to 1.65 have a known bug that prevents proper "deep" serialization of objects, which means that objects encapsulated inside other objects don't get serialized. +This is particularly seen when using `clang` as the C++ compiler. + +For this reason we require Boost>=1.65, and recommend installing it through alternative channels when it is not available through your operating system's primary package manager. + ## Known Issues -- When using `GTSAM_BUILD_WITH_MARCH_NATIVE=ON`, you may encounter issues in running tests which we are still investigating: - - Use of a version of GCC < 7.5 results in an "Indeterminant Linear System" error for `testSmartProjectionFactor`. - - Use of Boost version < 1.65 with clang will give a segfault for mulitple test cases. - MSVC 2013 is not yet supported because it cannot build the serialization module of Boost 1.55 (or earlier). # Windows Installation diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake index e2b1df6e3b..0ecd4ca0e3 100644 --- a/cmake/FindTBB.cmake +++ b/cmake/FindTBB.cmake @@ -144,7 +144,8 @@ if(NOT TBB_FOUND) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") # OS X - set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb" + "/usr/local/opt/tbb") # TODO: Check to see which C++ library is being used by the compiler. if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) @@ -181,7 +182,18 @@ if(NOT TBB_FOUND) ################################## if(TBB_INCLUDE_DIRS) - file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) + set(_tbb_version_file_prior_to_tbb_2021_1 "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h") + set(_tbb_version_file_after_tbb_2021_1 "${TBB_INCLUDE_DIRS}/oneapi/tbb/version.h") + + if (EXISTS "${_tbb_version_file_prior_to_tbb_2021_1}") + file(READ "${_tbb_version_file_prior_to_tbb_2021_1}" _tbb_version_file ) + elseif (EXISTS "${_tbb_version_file_after_tbb_2021_1}") + file(READ "${_tbb_version_file_after_tbb_2021_1}" _tbb_version_file ) + else() + message(FATAL_ERROR "Found TBB installation: ${TBB_INCLUDE_DIRS} " + "missing version header.") + endif() + string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" TBB_VERSION_MAJOR "${_tbb_version_file}") string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" diff --git a/cmake/HandleMetis.cmake b/cmake/HandleMetis.cmake new file mode 100644 index 0000000000..9c29e5776a --- /dev/null +++ b/cmake/HandleMetis.cmake @@ -0,0 +1,44 @@ +############################################################################### +# Metis library + +# For both system or bundle version, a cmake target "metis-gtsam-if" is defined (interface library) + +# Dont try to use metis if GTSAM_SUPPORT_NESTED_DISSECTION is disabled: +if (NOT GTSAM_SUPPORT_NESTED_DISSECTION) + return() +endif() + +option(GTSAM_USE_SYSTEM_METIS "Find and use system-installed libmetis. If 'off', use the one bundled with GTSAM" OFF) + +if(GTSAM_USE_SYSTEM_METIS) + # Debian package: libmetis-dev + + find_path(METIS_INCLUDE_DIR metis.h REQUIRED) + find_library(METIS_LIBRARY metis REQUIRED) + + if(METIS_INCLUDE_DIR AND METIS_LIBRARY) + mark_as_advanced(METIS_INCLUDE_DIR) + mark_as_advanced(METIS_LIBRARY) + + add_library(metis-gtsam-if INTERFACE) + target_include_directories(metis-gtsam-if BEFORE INTERFACE ${METIS_INCLUDE_DIR}) + target_link_libraries(metis-gtsam-if INTERFACE ${METIS_LIBRARY}) + endif() +else() + # Bundled version: + option(GTSAM_BUILD_METIS_EXECUTABLES "Build metis library executables" OFF) + add_subdirectory(${GTSAM_SOURCE_DIR}/gtsam/3rdparty/metis) + + target_include_directories(metis-gtsam BEFORE PUBLIC + $ + $ + $ + $ + ) + + add_library(metis-gtsam-if INTERFACE) + target_link_libraries(metis-gtsam-if INTERFACE metis-gtsam) +endif() + +list(APPEND GTSAM_EXPORTED_TARGETS metis-gtsam-if) +install(TARGETS metis-gtsam-if EXPORT GTSAM-exports ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/cmake/HandlePrintConfiguration.cmake b/cmake/HandlePrintConfiguration.cmake index 4ffd00e544..ad6ac5c5cf 100644 --- a/cmake/HandlePrintConfiguration.cmake +++ b/cmake/HandlePrintConfiguration.cmake @@ -32,6 +32,7 @@ endif() print_build_options_for_target(gtsam) print_config("Use System Eigen" "${GTSAM_USE_SYSTEM_EIGEN} (Using version: ${GTSAM_EIGEN_VERSION})") +print_config("Use System Metis" "${GTSAM_USE_SYSTEM_METIS}") if(GTSAM_USE_TBB) print_config("Use Intel TBB" "Yes (Version: ${TBB_VERSION})") diff --git a/cmake/HandleTBB.cmake b/cmake/HandleTBB.cmake index cedee55eaf..118dc4dac1 100644 --- a/cmake/HandleTBB.cmake +++ b/cmake/HandleTBB.cmake @@ -1,24 +1,32 @@ ############################################################################### -# Find TBB -find_package(TBB 4.4 COMPONENTS tbb tbbmalloc) +if (GTSAM_WITH_TBB) + # Find TBB + find_package(TBB 4.4 COMPONENTS tbb tbbmalloc) -# Set up variables if we're using TBB -if(TBB_FOUND AND GTSAM_WITH_TBB) - set(GTSAM_USE_TBB 1) # This will go into config.h - if ((${TBB_VERSION_MAJOR} GREATER 2020) OR (${TBB_VERSION_MAJOR} EQUAL 2020)) - set(TBB_GREATER_EQUAL_2020 1) + # Set up variables if we're using TBB + if(TBB_FOUND) + set(GTSAM_USE_TBB 1) # This will go into config.h + + if ((${TBB_VERSION} VERSION_GREATER "2021.1") OR (${TBB_VERSION} VERSION_EQUAL "2021.1")) + message(FATAL_ERROR "TBB version greater than 2021.1 (oneTBB API) is not yet supported. Use an older version instead.") + endif() + + if ((${TBB_VERSION_MAJOR} GREATER 2020) OR (${TBB_VERSION_MAJOR} EQUAL 2020)) + set(TBB_GREATER_EQUAL_2020 1) + else() + set(TBB_GREATER_EQUAL_2020 0) + endif() + # all definitions and link requisites will go via imported targets: + # tbb & tbbmalloc + list(APPEND GTSAM_ADDITIONAL_LIBRARIES tbb tbbmalloc) else() - set(TBB_GREATER_EQUAL_2020 0) + set(GTSAM_USE_TBB 0) # This will go into config.h + endif() + + ############################################################################### + # Prohibit Timing build mode in combination with TBB + if(GTSAM_USE_TBB AND (CMAKE_BUILD_TYPE STREQUAL "Timing")) + message(FATAL_ERROR "Timing build mode cannot be used together with TBB. Use a sampling profiler such as Instruments or Intel VTune Amplifier instead.") endif() - # all definitions and link requisites will go via imported targets: - # tbb & tbbmalloc - list(APPEND GTSAM_ADDITIONAL_LIBRARIES tbb tbbmalloc) -else() - set(GTSAM_USE_TBB 0) # This will go into config.h -endif() -############################################################################### -# Prohibit Timing build mode in combination with TBB -if(GTSAM_USE_TBB AND (CMAKE_BUILD_TYPE STREQUAL "Timing")) - message(FATAL_ERROR "Timing build mode cannot be used together with TBB. Use a sampling profiler such as Instruments or Intel VTune Amplifier instead.") endif() diff --git a/docker/README.md b/docker/README.md index 0c136f94c8..37c47a27ff 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,21 +1,63 @@ # Instructions -Build all docker images, in order: +# Images on Docker Hub +There are 4 images available on https://hub.docker.com/orgs/borglab/repositories: + +- `borglab/ubuntu-boost-tbb`: 18.06 Linux (nicknamed `bionic`) base image, with Boost and TBB installed. +- `borglab/ubuntu-gtsam`: GTSAM Release version installed in `/usr/local`. +- `borglab/ubuntu-gtsam-python`: installed GTSAM with python wrapper. +- `borglab/ubuntu-gtsam-python-vnc`: image with GTSAM+python wrapper that will run a VNC server to connect to. + +# Using the images + +## Just GTSAM + +To start the Docker image, execute ```bash -(cd ubuntu-boost-tbb && ./build.sh) -(cd ubuntu-gtsam && ./build.sh) -(cd ubuntu-gtsam-python && ./build.sh) -(cd ubuntu-gtsam-python-vnc && ./build.sh) +docker run -it borglab/ubuntu-gtsam:bionic ``` +after you will find yourself in a bash shell, in the directory `/usr/src/gtsam/build`. +## GTSAM with Python wrapper -Then launch with: +To use GTSAM via the python wrapper, similarly execute +```bash +docker run -it borglab/ubuntu-gtsam-python:bionic +``` +and then launch `python3`: +```bash +python3 +>>> import gtsam +>>> gtsam.Pose2(1,2,3) +(1, 2, 3) +``` - docker run -p 5900:5900 dellaert/ubuntu-gtsam-python-vnc:bionic +## GTSAM with Python wrapper and VNC + +First, start the docker image, which will run a VNC server on port 5900: +```bash +docker run -p 5900:5900 borglab/ubuntu-gtsam-python-vnc:bionic +``` Then open a remote VNC X client, for example: - sudo apt-get install tigervnc-viewer - xtigervncviewer :5900 +### Linux +```bash +sudo apt-get install tigervnc-viewer +xtigervncviewer :5900 +``` +### Mac +The Finder's "Connect to Server..." with `vnc://127.0.0.1` does not work, for some reason. Using the free [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/), enter `0.0.0.0:5900` as the server. + +# Re-building the images locally + +To build all docker images, in order: +```bash +(cd ubuntu-boost-tbb && ./build.sh) +(cd ubuntu-gtsam && ./build.sh) +(cd ubuntu-gtsam-python && ./build.sh) +(cd ubuntu-gtsam-python-vnc && ./build.sh) +``` +Note: building GTSAM can take a lot of memory because of the heavy templating. It is advisable to give Docker enough resources, e.g., 8GB, to avoid OOM errors while compiling. \ No newline at end of file diff --git a/docker/ubuntu-boost-tbb/build.sh b/docker/ubuntu-boost-tbb/build.sh index 2dac4c3db3..35b349c6a5 100755 --- a/docker/ubuntu-boost-tbb/build.sh +++ b/docker/ubuntu-boost-tbb/build.sh @@ -1,3 +1,3 @@ # Build command for Docker image # TODO(dellaert): use docker compose and/or cmake -docker build --no-cache -t dellaert/ubuntu-boost-tbb:bionic . +docker build --no-cache -t borglab/ubuntu-boost-tbb:bionic . diff --git a/docker/ubuntu-gtsam-python-vnc/Dockerfile b/docker/ubuntu-gtsam-python-vnc/Dockerfile index 61ecd9b9ad..8039698c35 100644 --- a/docker/ubuntu-gtsam-python-vnc/Dockerfile +++ b/docker/ubuntu-gtsam-python-vnc/Dockerfile @@ -1,7 +1,7 @@ # This GTSAM image connects to the host X-server via VNC to provide a Graphical User Interface for interaction. # Get the base Ubuntu/GTSAM image from Docker Hub -FROM dellaert/ubuntu-gtsam-python:bionic +FROM borglab/ubuntu-gtsam-python:bionic # Things needed to get a python GUI ENV DEBIAN_FRONTEND noninteractive diff --git a/docker/ubuntu-gtsam-python-vnc/build.sh b/docker/ubuntu-gtsam-python-vnc/build.sh index 8d280252f6..a0bbb6a961 100755 --- a/docker/ubuntu-gtsam-python-vnc/build.sh +++ b/docker/ubuntu-gtsam-python-vnc/build.sh @@ -1,4 +1,4 @@ # Build command for Docker image # TODO(dellaert): use docker compose and/or cmake # Needs to be run in docker/ubuntu-gtsam-python-vnc directory -docker build -t dellaert/ubuntu-gtsam-python-vnc:bionic . +docker build -t borglab/ubuntu-gtsam-python-vnc:bionic . diff --git a/docker/ubuntu-gtsam-python-vnc/vnc.sh b/docker/ubuntu-gtsam-python-vnc/vnc.sh index c0ab692c68..b749093afe 100755 --- a/docker/ubuntu-gtsam-python-vnc/vnc.sh +++ b/docker/ubuntu-gtsam-python-vnc/vnc.sh @@ -2,4 +2,4 @@ docker run -it \ --workdir="/usr/src/gtsam" \ -p 5900:5900 \ - dellaert/ubuntu-gtsam-python-vnc:bionic \ No newline at end of file + borglab/ubuntu-gtsam-python-vnc:bionic \ No newline at end of file diff --git a/docker/ubuntu-gtsam-python/Dockerfile b/docker/ubuntu-gtsam-python/Dockerfile index ce5d8fdca9..85eed4d4e1 100644 --- a/docker/ubuntu-gtsam-python/Dockerfile +++ b/docker/ubuntu-gtsam-python/Dockerfile @@ -1,7 +1,7 @@ # GTSAM Ubuntu image with Python wrapper support. # Get the base Ubuntu/GTSAM image from Docker Hub -FROM dellaert/ubuntu-gtsam:bionic +FROM borglab/ubuntu-gtsam:bionic # Install pip RUN apt-get install -y python3-pip python3-dev @@ -22,7 +22,9 @@ RUN cmake \ .. # Build again, as ubuntu-gtsam image cleaned -RUN make -j4 install && make clean +RUN make -j4 install +RUN make python-install +RUN make clean # Needed to run python wrapper: RUN echo 'export PYTHONPATH=/usr/local/python/:$PYTHONPATH' >> /root/.bashrc diff --git a/docker/ubuntu-gtsam-python/build.sh b/docker/ubuntu-gtsam-python/build.sh index 1696f6c612..68827074d0 100755 --- a/docker/ubuntu-gtsam-python/build.sh +++ b/docker/ubuntu-gtsam-python/build.sh @@ -1,3 +1,3 @@ # Build command for Docker image # TODO(dellaert): use docker compose and/or cmake -docker build --no-cache -t dellaert/ubuntu-gtsam-python:bionic . +docker build --no-cache -t borglab/ubuntu-gtsam-python:bionic . diff --git a/docker/ubuntu-gtsam/Dockerfile b/docker/ubuntu-gtsam/Dockerfile index f2b476f156..ce6927fe8c 100644 --- a/docker/ubuntu-gtsam/Dockerfile +++ b/docker/ubuntu-gtsam/Dockerfile @@ -1,7 +1,7 @@ # Ubuntu image with GTSAM installed. Configured with Boost and TBB support. # Get the base Ubuntu image from Docker Hub -FROM dellaert/ubuntu-boost-tbb:bionic +FROM borglab/ubuntu-boost-tbb:bionic # Install git RUN apt-get update && \ diff --git a/docker/ubuntu-gtsam/build.sh b/docker/ubuntu-gtsam/build.sh index bf545e9c2b..790ee15751 100755 --- a/docker/ubuntu-gtsam/build.sh +++ b/docker/ubuntu-gtsam/build.sh @@ -1,3 +1,3 @@ # Build command for Docker image # TODO(dellaert): use docker compose and/or cmake -docker build --no-cache -t dellaert/ubuntu-gtsam:bionic . +docker build --no-cache -t borglab/ubuntu-gtsam:bionic . diff --git a/gtsam/3rdparty/CMakeLists.txt b/gtsam/3rdparty/CMakeLists.txt index 8b356393b2..a1e917bbe3 100644 --- a/gtsam/3rdparty/CMakeLists.txt +++ b/gtsam/3rdparty/CMakeLists.txt @@ -49,10 +49,7 @@ if(NOT GTSAM_USE_SYSTEM_EIGEN) endif() -option(GTSAM_BUILD_METIS_EXECUTABLES "Build metis library executables" OFF) -if(GTSAM_SUPPORT_NESTED_DISSECTION) - add_subdirectory(metis) -endif() +# metis: already handled in ROOT/cmake/HandleMetis.cmake add_subdirectory(ceres) diff --git a/gtsam/CMakeLists.txt b/gtsam/CMakeLists.txt index e2f2ad8289..535d60eb18 100644 --- a/gtsam/CMakeLists.txt +++ b/gtsam/CMakeLists.txt @@ -89,7 +89,8 @@ list(APPEND gtsam_srcs "${PROJECT_BINARY_DIR}/config.h" "${PROJECT_BINARY_DIR}/d install(FILES "${PROJECT_BINARY_DIR}/config.h" "${PROJECT_BINARY_DIR}/dllexport.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gtsam) if(GTSAM_SUPPORT_NESTED_DISSECTION) - list(APPEND GTSAM_ADDITIONAL_LIBRARIES metis-gtsam) + # target metis-gtsam-if is defined in both cases: embedded metis or system version: + list(APPEND GTSAM_ADDITIONAL_LIBRARIES metis-gtsam-if) endif() # Versions @@ -155,16 +156,6 @@ target_include_directories(gtsam SYSTEM BEFORE PUBLIC $ $ ) -if(GTSAM_SUPPORT_NESTED_DISSECTION) - target_include_directories(gtsam BEFORE PUBLIC - $ - $ - $ - $ - ) -endif() - - if(WIN32) # Add 'lib' prefix to static library to avoid filename collision with shared library if (NOT BUILD_SHARED_LIBS) diff --git a/gtsam/config.h.in b/gtsam/config.h.in index cebc1d0b60..e7623c52b7 100644 --- a/gtsam/config.h.in +++ b/gtsam/config.h.in @@ -78,5 +78,8 @@ // Support Metis-based nested dissection #cmakedefine GTSAM_TANGENT_PREINTEGRATION +// Whether to use the system installed Metis instead of the provided one +#cmakedefine GTSAM_USE_SYSTEM_METIS + // Toggle switch for BetweenFactor jacobian computation #cmakedefine GTSAM_SLOW_BUT_CORRECT_BETWEENFACTOR diff --git a/gtsam/geometry/Point2.h b/gtsam/geometry/Point2.h index 17f87f6564..cdb9f44809 100644 --- a/gtsam/geometry/Point2.h +++ b/gtsam/geometry/Point2.h @@ -25,6 +25,12 @@ namespace gtsam { /// As of GTSAM 4, in order to make GTSAM more lean, /// it is now possible to just typedef Point2 to Vector2 typedef Vector2 Point2; + +// Convenience typedef +using Point2Pair = std::pair; +GTSAM_EXPORT std::ostream &operator<<(std::ostream &os, const gtsam::Point2Pair &p); + +using Point2Pairs = std::vector; /// Distance of the point from the origin, with Jacobian GTSAM_EXPORT double norm2(const Point2& p, OptionalJacobian<1, 2> H = boost::none); @@ -34,10 +40,6 @@ GTSAM_EXPORT double distance2(const Point2& p1, const Point2& q, OptionalJacobian<1, 2> H1 = boost::none, OptionalJacobian<1, 2> H2 = boost::none); -// Convenience typedef -typedef std::pair Point2Pair; -GTSAM_EXPORT std::ostream &operator<<(std::ostream &os, const gtsam::Point2Pair &p); - // For MATLAB wrapper typedef std::vector > Point2Vector; diff --git a/gtsam/geometry/geometry.i b/gtsam/geometry/geometry.i index 0e303cbcd1..9baa49e8e8 100644 --- a/gtsam/geometry/geometry.i +++ b/gtsam/geometry/geometry.i @@ -31,6 +31,14 @@ class Point2 { // enable pickling in python void pickle() const; }; + +class Point2Pairs { + Point2Pairs(); + size_t size() const; + bool empty() const; + gtsam::Point2Pair at(size_t n) const; + void push_back(const gtsam::Point2Pair& point_pair); +}; // std::vector class Point2Vector { @@ -429,6 +437,8 @@ class Pose2 { // enable pickling in python void pickle() const; }; + +boost::optional align(const gtsam::Point2Pairs& pairs); #include class Pose3 { diff --git a/gtsam/inference/BayesTreeCliqueBase.h b/gtsam/inference/BayesTreeCliqueBase.h index 7b79ccf68a..c9bb6db942 100644 --- a/gtsam/inference/BayesTreeCliqueBase.h +++ b/gtsam/inference/BayesTreeCliqueBase.h @@ -70,16 +70,23 @@ namespace gtsam { /// @name Standard Constructors /// @{ - /** Default constructor */ + /// Default constructor BayesTreeCliqueBase() : problemSize_(1) {} - /** Construct from a conditional, leaving parent and child pointers uninitialized */ - BayesTreeCliqueBase(const sharedConditional& conditional) : conditional_(conditional), problemSize_(1) {} + /// Construct from a conditional, leaving parent and child pointers + /// uninitialized. + BayesTreeCliqueBase(const sharedConditional& conditional) + : conditional_(conditional), problemSize_(1) {} - /** Shallow copy constructor */ - BayesTreeCliqueBase(const BayesTreeCliqueBase& c) : conditional_(c.conditional_), parent_(c.parent_), children(c.children), problemSize_(c.problemSize_), is_root(c.is_root) {} + /// Shallow copy constructor. + BayesTreeCliqueBase(const BayesTreeCliqueBase& c) + : conditional_(c.conditional_), + parent_(c.parent_), + children(c.children), + problemSize_(c.problemSize_), + is_root(c.is_root) {} - /** Shallow copy assignment constructor */ + /// Shallow copy assignment constructor BayesTreeCliqueBase& operator=(const BayesTreeCliqueBase& c) { conditional_ = c.conditional_; parent_ = c.parent_; @@ -89,6 +96,9 @@ namespace gtsam { return *this; } + // Virtual destructor. + virtual ~BayesTreeCliqueBase() {} + /// @} /// This stores the Cached separator marginal P(S) @@ -119,7 +129,9 @@ namespace gtsam { bool equals(const DERIVED& other, double tol = 1e-9) const; /** print this node */ - virtual void print(const std::string& s = "", const KeyFormatter& keyFormatter = DefaultKeyFormatter) const; + virtual void print( + const std::string& s = "", + const KeyFormatter& keyFormatter = DefaultKeyFormatter) const; /// @} /// @name Standard Interface diff --git a/gtsam/inference/Key.h b/gtsam/inference/Key.h index 2cc19d07a5..31428a50e7 100644 --- a/gtsam/inference/Key.h +++ b/gtsam/inference/Key.h @@ -32,7 +32,7 @@ namespace gtsam { /// Typedef for a function to format a key, i.e. to convert it to a string -typedef std::function KeyFormatter; +using KeyFormatter = std::function; // Helper function for DefaultKeyFormatter GTSAM_EXPORT std::string _defaultKeyFormatter(Key key); @@ -83,28 +83,32 @@ class key_formatter { }; /// Define collection type once and for all - also used in wrappers -typedef FastVector KeyVector; +using KeyVector = FastVector; // TODO(frank): Nothing fast about these :-( -typedef FastList KeyList; -typedef FastSet KeySet; -typedef FastMap KeyGroupMap; +using KeyList = FastList; +using KeySet = FastSet; +using KeyGroupMap = FastMap; /// Utility function to print one key with optional prefix -GTSAM_EXPORT void PrintKey(Key key, const std::string& s = "", - const KeyFormatter& keyFormatter = DefaultKeyFormatter); +GTSAM_EXPORT void PrintKey( + Key key, const std::string &s = "", + const KeyFormatter &keyFormatter = DefaultKeyFormatter); /// Utility function to print sets of keys with optional prefix -GTSAM_EXPORT void PrintKeyList(const KeyList& keys, const std::string& s = "", - const KeyFormatter& keyFormatter = DefaultKeyFormatter); +GTSAM_EXPORT void PrintKeyList( + const KeyList &keys, const std::string &s = "", + const KeyFormatter &keyFormatter = DefaultKeyFormatter); /// Utility function to print sets of keys with optional prefix -GTSAM_EXPORT void PrintKeyVector(const KeyVector& keys, const std::string& s = - "", const KeyFormatter& keyFormatter = DefaultKeyFormatter); +GTSAM_EXPORT void PrintKeyVector( + const KeyVector &keys, const std::string &s = "", + const KeyFormatter &keyFormatter = DefaultKeyFormatter); /// Utility function to print sets of keys with optional prefix -GTSAM_EXPORT void PrintKeySet(const KeySet& keys, const std::string& s = "", - const KeyFormatter& keyFormatter = DefaultKeyFormatter); +GTSAM_EXPORT void PrintKeySet( + const KeySet &keys, const std::string &s = "", + const KeyFormatter &keyFormatter = DefaultKeyFormatter); // Define Key to be Testable by specializing gtsam::traits template struct traits; diff --git a/gtsam/inference/Ordering.cpp b/gtsam/inference/Ordering.cpp index 266c54ca61..440d2b8285 100644 --- a/gtsam/inference/Ordering.cpp +++ b/gtsam/inference/Ordering.cpp @@ -25,8 +25,12 @@ #include #ifdef GTSAM_SUPPORT_NESTED_DISSECTION +#ifdef GTSAM_USE_SYSTEM_METIS +#include +#else #include #endif +#endif using namespace std; diff --git a/gtsam/linear/linear.i b/gtsam/linear/linear.i index 63047bf4f3..8635c55f8c 100644 --- a/gtsam/linear/linear.i +++ b/gtsam/linear/linear.i @@ -16,9 +16,9 @@ virtual class Base { }; virtual class Gaussian : gtsam::noiseModel::Base { - static gtsam::noiseModel::Gaussian* Information(Matrix R); - static gtsam::noiseModel::Gaussian* SqrtInformation(Matrix R); - static gtsam::noiseModel::Gaussian* Covariance(Matrix R); + static gtsam::noiseModel::Gaussian* Information(Matrix R, bool smart = true); + static gtsam::noiseModel::Gaussian* SqrtInformation(Matrix R, bool smart = true); + static gtsam::noiseModel::Gaussian* Covariance(Matrix R, bool smart = true); bool equals(gtsam::noiseModel::Base& expected, double tol); @@ -37,9 +37,9 @@ virtual class Gaussian : gtsam::noiseModel::Base { }; virtual class Diagonal : gtsam::noiseModel::Gaussian { - static gtsam::noiseModel::Diagonal* Sigmas(Vector sigmas); - static gtsam::noiseModel::Diagonal* Variances(Vector variances); - static gtsam::noiseModel::Diagonal* Precisions(Vector precisions); + static gtsam::noiseModel::Diagonal* Sigmas(Vector sigmas, bool smart = true); + static gtsam::noiseModel::Diagonal* Variances(Vector variances, bool smart = true); + static gtsam::noiseModel::Diagonal* Precisions(Vector precisions, bool smart = true); Matrix R() const; // access to noise model @@ -69,9 +69,9 @@ virtual class Constrained : gtsam::noiseModel::Diagonal { }; virtual class Isotropic : gtsam::noiseModel::Diagonal { - static gtsam::noiseModel::Isotropic* Sigma(size_t dim, double sigma); - static gtsam::noiseModel::Isotropic* Variance(size_t dim, double varianace); - static gtsam::noiseModel::Isotropic* Precision(size_t dim, double precision); + static gtsam::noiseModel::Isotropic* Sigma(size_t dim, double sigma, bool smart = true); + static gtsam::noiseModel::Isotropic* Variance(size_t dim, double varianace, bool smart = true); + static gtsam::noiseModel::Isotropic* Precision(size_t dim, double precision, bool smart = true); // access to noise model double sigma() const; @@ -289,6 +289,13 @@ virtual class JacobianFactor : gtsam::GaussianFactor { JacobianFactor(size_t i1, Matrix A1, size_t i2, Matrix A2, size_t i3, Matrix A3, Vector b, const gtsam::noiseModel::Diagonal* model); JacobianFactor(const gtsam::GaussianFactorGraph& graph); + JacobianFactor(const gtsam::GaussianFactorGraph& graph, + const gtsam::VariableSlots& p_variableSlots); + JacobianFactor(const gtsam::GaussianFactorGraph& graph, + const gtsam::Ordering& ordering); + JacobianFactor(const gtsam::GaussianFactorGraph& graph, + const gtsam::Ordering& ordering, + const gtsam::VariableSlots& p_variableSlots); //Testable void print(string s = "", const gtsam::KeyFormatter& keyFormatter = diff --git a/gtsam/navigation/tests/testImuFactor.cpp b/gtsam/navigation/tests/testImuFactor.cpp index 801d87895d..585da38b19 100644 --- a/gtsam/navigation/tests/testImuFactor.cpp +++ b/gtsam/navigation/tests/testImuFactor.cpp @@ -819,7 +819,6 @@ struct ImuFactorMergeTest { loop_(Vector3(0, -kAngularVelocity, 0), Vector3(kVelocity, 0, 0)) { // arbitrary noise values p_->gyroscopeCovariance = I_3x3 * 0.01; - p_->accelerometerCovariance = I_3x3 * 0.02; p_->accelerometerCovariance = I_3x3 * 0.03; } diff --git a/gtsam/nonlinear/nonlinear.i b/gtsam/nonlinear/nonlinear.i index 61f164b435..d068bd7ee8 100644 --- a/gtsam/nonlinear/nonlinear.i +++ b/gtsam/nonlinear/nonlinear.i @@ -75,12 +75,15 @@ size_t Z(size_t j); } // namespace symbol_shorthand // Default keyformatter -void PrintKeyList(const gtsam::KeyList& keys); -void PrintKeyList(const gtsam::KeyList& keys, string s); -void PrintKeyVector(const gtsam::KeyVector& keys); -void PrintKeyVector(const gtsam::KeyVector& keys, string s); -void PrintKeySet(const gtsam::KeySet& keys); -void PrintKeySet(const gtsam::KeySet& keys, string s); +void PrintKeyList( + const gtsam::KeyList& keys, const string& s = "", + const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter); +void PrintKeyVector( + const gtsam::KeyVector& keys, const string& s = "", + const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter); +void PrintKeySet( + const gtsam::KeySet& keys, const string& s = "", + const gtsam::KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter); #include class LabeledSymbol { @@ -527,7 +530,14 @@ template virtual class GncParams { GncParams(const PARAMS& baseOptimizerParams); GncParams(); + void setVerbosityGNC(const This::Verbosity value); void print(const string& str) const; + + enum Verbosity { + SILENT, + SUMMARY, + VALUES + }; }; typedef gtsam::GncParams GncGaussNewtonParams; diff --git a/gtsam_unstable/partition/FindSeparator-inl.h b/gtsam_unstable/partition/FindSeparator-inl.h index ce657e7a0c..2e48b0d45e 100644 --- a/gtsam_unstable/partition/FindSeparator-inl.h +++ b/gtsam_unstable/partition/FindSeparator-inl.h @@ -20,6 +20,8 @@ #include "FindSeparator.h" +#ifndef GTSAM_USE_SYSTEM_METIS + extern "C" { #include #include "metislib.h" @@ -564,3 +566,5 @@ namespace gtsam { namespace partition { } }} //namespace + +#endif diff --git a/gtsam_unstable/partition/tests/testFindSeparator.cpp b/gtsam_unstable/partition/tests/testFindSeparator.cpp index fe49de9289..63acc8f183 100644 --- a/gtsam_unstable/partition/tests/testFindSeparator.cpp +++ b/gtsam_unstable/partition/tests/testFindSeparator.cpp @@ -20,6 +20,8 @@ using namespace std; using namespace gtsam; using namespace gtsam::partition; +#ifndef GTSAM_USE_SYSTEM_METIS + /* ************************************************************************* */ // x0 - x1 - x2 // l3 l4 @@ -227,6 +229,8 @@ TEST ( Partition, findSeparator3_with_reduced_camera ) LONGS_EQUAL(2, partitionTable[28]); } +#endif + /* ************************************************************************* */ int main() { TestResult tr; return TestRegistry::runAllTests(tr);} /* ************************************************************************* */ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 4254a21c69..c3524adad8 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -43,6 +43,7 @@ set(ignore gtsam::BetweenFactorPose2s gtsam::BetweenFactorPose3s gtsam::Point2Vector + gtsam::Point2Pairs gtsam::Point3Pairs gtsam::Pose3Pairs gtsam::Pose3Vector diff --git a/python/gtsam/examples/ImuFactorExample.py b/python/gtsam/examples/ImuFactorExample.py index bb707a8f5c..86613234dd 100644 --- a/python/gtsam/examples/ImuFactorExample.py +++ b/python/gtsam/examples/ImuFactorExample.py @@ -10,28 +10,30 @@ Author: Frank Dellaert, Varun Agrawal """ +# pylint: disable=no-name-in-module,unused-import,arguments-differ + from __future__ import print_function import argparse import math -import gtsam import matplotlib.pyplot as plt import numpy as np +from mpl_toolkits.mplot3d import Axes3D + +import gtsam from gtsam.symbol_shorthand import B, V, X from gtsam.utils.plot import plot_pose3 -from mpl_toolkits.mplot3d import Axes3D from PreintegrationExample import POSES_FIG, PreintegrationExample BIAS_KEY = B(0) - np.set_printoptions(precision=3, suppress=True) class ImuFactorExample(PreintegrationExample): - + """Class to run example of the Imu Factor.""" def __init__(self, twist_scenario="sick_twist"): self.velocity = np.array([2, 0, 0]) self.priorNoise = gtsam.noiseModel.Isotropic.Sigma(6, 0.1) @@ -42,9 +44,8 @@ def __init__(self, twist_scenario="sick_twist"): zero_twist=(np.zeros(3), np.zeros(3)), forward_twist=(np.zeros(3), self.velocity), loop_twist=(np.array([0, -math.radians(30), 0]), self.velocity), - sick_twist=(np.array([math.radians(30), -math.radians(30), 0]), - self.velocity) - ) + sick_twist=(np.array([math.radians(30), -math.radians(30), + 0]), self.velocity)) accBias = np.array([-0.3, 0.1, 0.2]) gyroBias = np.array([0.1, 0.3, -0.1]) @@ -55,19 +56,44 @@ def __init__(self, twist_scenario="sick_twist"): bias, dt) def addPrior(self, i, graph): + """Add priors at time step `i`.""" state = self.scenario.navState(i) - graph.push_back(gtsam.PriorFactorPose3( - X(i), state.pose(), self.priorNoise)) - graph.push_back(gtsam.PriorFactorVector( - V(i), state.velocity(), self.velNoise)) + graph.push_back( + gtsam.PriorFactorPose3(X(i), state.pose(), self.priorNoise)) + graph.push_back( + gtsam.PriorFactorVector(V(i), state.velocity(), self.velNoise)) + + def optimize(self, graph, initial): + """Optimize using Levenberg-Marquardt optimization.""" + params = gtsam.LevenbergMarquardtParams() + params.setVerbosityLM("SUMMARY") + optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial, params) + result = optimizer.optimize() + return result + + def plot(self, result): + """Plot resulting poses.""" + i = 0 + while result.exists(X(i)): + pose_i = result.atPose3(X(i)) + plot_pose3(POSES_FIG + 1, pose_i, 1) + i += 1 + plt.title("Estimated Trajectory") + + gtsam.utils.plot.set_axes_equal(POSES_FIG + 1) + + print("Bias Values", result.atConstantBias(BIAS_KEY)) + + plt.ioff() + plt.show() def run(self, T=12, compute_covariances=False, verbose=True): + """Main runner.""" graph = gtsam.NonlinearFactorGraph() # initialize data structure for pre-integrated IMU measurements pim = gtsam.PreintegratedImuMeasurements(self.params, self.actualBias) - T = 12 num_poses = T # assumes 1 factor per second initial = gtsam.Values() initial.insert(BIAS_KEY, self.actualBias) @@ -91,14 +117,13 @@ def run(self, T=12, compute_covariances=False, verbose=True): if k % 10 == 0: self.plotImu(t, measuredOmega, measuredAcc) - if (k+1) % int(1 / self.dt) == 0: + if (k + 1) % int(1 / self.dt) == 0: # Plot every second self.plotGroundTruthPose(t, scale=1) plt.title("Ground Truth Trajectory") # create IMU factor every second - factor = gtsam.ImuFactor(X(i), V(i), - X(i + 1), V(i + 1), + factor = gtsam.ImuFactor(X(i), V(i), X(i + 1), V(i + 1), BIAS_KEY, pim) graph.push_back(factor) @@ -108,34 +133,34 @@ def run(self, T=12, compute_covariances=False, verbose=True): pim.resetIntegration() - rotationNoise = gtsam.Rot3.Expmap(np.random.randn(3)*0.1) - translationNoise = gtsam.Point3(*np.random.randn(3)*1) + rotationNoise = gtsam.Rot3.Expmap(np.random.randn(3) * 0.1) + translationNoise = gtsam.Point3(*np.random.randn(3) * 1) poseNoise = gtsam.Pose3(rotationNoise, translationNoise) actual_state_i = self.scenario.navState(t + self.dt) print("Actual state at {0}:\n{1}".format( - t+self.dt, actual_state_i)) + t + self.dt, actual_state_i)) noisy_state_i = gtsam.NavState( actual_state_i.pose().compose(poseNoise), - actual_state_i.velocity() + np.random.randn(3)*0.1) + actual_state_i.velocity() + np.random.randn(3) * 0.1) - initial.insert(X(i+1), noisy_state_i.pose()) - initial.insert(V(i+1), noisy_state_i.velocity()) + initial.insert(X(i + 1), noisy_state_i.pose()) + initial.insert(V(i + 1), noisy_state_i.velocity()) i += 1 # add priors on end self.addPrior(num_poses - 1, graph) - initial.print_("Initial values:") + initial.print("Initial values:") - # optimize using Levenberg-Marquardt optimization - params = gtsam.LevenbergMarquardtParams() - params.setVerbosityLM("SUMMARY") - optimizer = gtsam.LevenbergMarquardtOptimizer(graph, initial, params) - result = optimizer.optimize() + result = self.optimize(graph, initial) - result.print_("Optimized values:") + result.print("Optimized values:") + print("------------------") + print(graph.error(initial)) + print(graph.error(result)) + print("------------------") if compute_covariances: # Calculate and print marginal covariances @@ -148,33 +173,26 @@ def run(self, T=12, compute_covariances=False, verbose=True): print("Covariance on vel {}:\n{}\n".format( i, marginals.marginalCovariance(V(i)))) - # Plot resulting poses - i = 0 - while result.exists(X(i)): - pose_i = result.atPose3(X(i)) - plot_pose3(POSES_FIG+1, pose_i, 1) - i += 1 - plt.title("Estimated Trajectory") - - gtsam.utils.plot.set_axes_equal(POSES_FIG+1) - - print("Bias Values", result.atConstantBias(BIAS_KEY)) - - plt.ioff() - plt.show() + self.plot(result) if __name__ == '__main__': parser = argparse.ArgumentParser("ImuFactorExample.py") parser.add_argument("--twist_scenario", default="sick_twist", - choices=("zero_twist", "forward_twist", "loop_twist", "sick_twist")) - parser.add_argument("--time", "-T", default=12, - type=int, help="Total time in seconds") + choices=("zero_twist", "forward_twist", "loop_twist", + "sick_twist")) + parser.add_argument("--time", + "-T", + default=12, + type=int, + help="Total time in seconds") parser.add_argument("--compute_covariances", - default=False, action='store_true') + default=False, + action='store_true') parser.add_argument("--verbose", default=False, action='store_true') args = parser.parse_args() - ImuFactorExample(args.twist_scenario).run( - args.time, args.compute_covariances, args.verbose) + ImuFactorExample(args.twist_scenario).run(args.time, + args.compute_covariances, + args.verbose) diff --git a/python/gtsam/gtsam.tpl b/python/gtsam/gtsam.tpl index 93e7ffbafd..b760e4eb5a 100644 --- a/python/gtsam/gtsam.tpl +++ b/python/gtsam/gtsam.tpl @@ -1,8 +1,8 @@ /** - * @file gtsam.cpp + * @file {module_name}.cpp * @brief The auto-generated wrapper C++ source code. - * @author Duy-Nguyen Ta, Fan Jiang, Matthew Sklar - * @date Aug. 18, 2020 + * @author Duy-Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal + * @date Aug. 18, 2020 * * ** THIS FILE IS AUTO-GENERATED, DO NOT MODIFY! ** */ diff --git a/python/gtsam/preamble/geometry.h b/python/gtsam/preamble/geometry.h index 498c7dc58f..35fe2a577a 100644 --- a/python/gtsam/preamble/geometry.h +++ b/python/gtsam/preamble/geometry.h @@ -10,9 +10,18 @@ * Without this they will be automatically converted to a Python object, and all * mutations on Python side will not be reflected on C++. */ +#include + +// Support for binding boost::optional types in C++11. +// https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html +namespace pybind11 { namespace detail { + template + struct type_caster> : optional_caster> {}; +}} PYBIND11_MAKE_OPAQUE( std::vector>); +PYBIND11_MAKE_OPAQUE(gtsam::Point2Pairs); PYBIND11_MAKE_OPAQUE(gtsam::Point3Pairs); PYBIND11_MAKE_OPAQUE(gtsam::Pose3Pairs); PYBIND11_MAKE_OPAQUE(std::vector); diff --git a/python/gtsam/specializations/geometry.h b/python/gtsam/specializations/geometry.h index e11d3cc4fe..a492ce8eb2 100644 --- a/python/gtsam/specializations/geometry.h +++ b/python/gtsam/specializations/geometry.h @@ -14,6 +14,7 @@ py::bind_vector< std::vector>>( m_, "Point2Vector"); +py::bind_vector>(m_, "Point2Pairs"); py::bind_vector>(m_, "Point3Pairs"); py::bind_vector>(m_, "Pose3Pairs"); py::bind_vector>(m_, "Pose3Vector"); diff --git a/python/gtsam/tests/test_Pose2.py b/python/gtsam/tests/test_Pose2.py index 9652b594ad..e5ffbad7d1 100644 --- a/python/gtsam/tests/test_Pose2.py +++ b/python/gtsam/tests/test_Pose2.py @@ -6,27 +6,64 @@ See LICENSE for the license information Pose2 unit tests. -Author: Frank Dellaert & Duy Nguyen Ta (Python) +Author: Frank Dellaert & Duy Nguyen Ta & John Lambert """ import unittest import numpy as np import gtsam -from gtsam import Pose2 +from gtsam import Point2, Point2Pairs, Pose2 from gtsam.utils.test_case import GtsamTestCase class TestPose2(GtsamTestCase): """Test selected Pose2 methods.""" - - def test_adjoint(self): + def test_adjoint(self) -> None: """Test adjoint method.""" xi = np.array([1, 2, 3]) expected = np.dot(Pose2.adjointMap_(xi), xi) actual = Pose2.adjoint_(xi, xi) np.testing.assert_array_equal(actual, expected) + def test_align(self) -> None: + """Ensure estimation of the Pose2 element to align two 2d point clouds succeeds. + + Two point clouds represent horseshoe-shapes of the same size, just rotated and translated: + + | X---X + | | + | X---X + ------------------ + | + | + O | O + | | | + O---O + """ + pts_a = [ + Point2(3, 1), + Point2(1, 1), + Point2(1, 3), + Point2(3, 3), + ] + pts_b = [ + Point2(1, -3), + Point2(1, -5), + Point2(-1, -5), + Point2(-1, -3), + ] + + # fmt: on + ab_pairs = Point2Pairs(list(zip(pts_a, pts_b))) + bTa = gtsam.align(ab_pairs) + aTb = bTa.inverse() + assert aTb is not None + + for pt_a, pt_b in zip(pts_a, pts_b): + pt_a_ = aTb.transformFrom(pt_b) + assert np.allclose(pt_a, pt_a_) + if __name__ == "__main__": unittest.main() diff --git a/python/gtsam/utils/plot.py b/python/gtsam/utils/plot.py index f324da63a7..7ea3930774 100644 --- a/python/gtsam/utils/plot.py +++ b/python/gtsam/utils/plot.py @@ -23,7 +23,10 @@ def set_axes_equal(fignum: int) -> None: fignum: An integer representing the figure number for Matplotlib. """ fig = plt.figure(fignum) - ax = fig.gca(projection='3d') + if not fig.axes: + ax = fig.add_subplot(projection='3d') + else: + ax = fig.axes[0] limits = np.array([ ax.get_xlim3d(), @@ -339,7 +342,11 @@ def plot_pose3( """ # get figure object fig = plt.figure(fignum) - axes = fig.gca(projection='3d') + if not fig.axes: + axes = fig.add_subplot(projection='3d') + else: + axes = fig.axes[0] + plot_pose3_on_axes(axes, pose, P=P, axis_length=axis_length) axes.set_xlabel(axis_labels[0]) diff --git a/wrap/gtwrap/interface_parser/classes.py b/wrap/gtwrap/interface_parser/classes.py index 11317962df..841c963c24 100644 --- a/wrap/gtwrap/interface_parser/classes.py +++ b/wrap/gtwrap/interface_parser/classes.py @@ -119,24 +119,27 @@ class Constructor: Can have 0 or more arguments. """ rule = ( - IDENT("name") # + Optional(Template.rule("template")) # + + IDENT("name") # + LPAREN # + ArgumentList.rule("args_list") # + RPAREN # + SEMI_COLON # BR - ).setParseAction(lambda t: Constructor(t.name, t.args_list)) + ).setParseAction(lambda t: Constructor(t.name, t.args_list, t.template)) def __init__(self, name: str, args: ArgumentList, + template: Union[Template, Any], parent: Union["Class", Any] = ''): self.name = name self.args = args + self.template = template self.parent = parent def __repr__(self) -> str: - return "Constructor: {}".format(self.name) + return "Constructor: {}{}".format(self.name, self.args) class Operator: @@ -260,17 +263,9 @@ def __init__(self, + RBRACE # + SEMI_COLON # BR ).setParseAction(lambda t: Class( - t.template, - t.is_virtual, - t.name, - t.parent_class, - t.members.ctors, - t.members.methods, - t.members.static_methods, - t.members.properties, - t.members.operators, - t.members.enums - )) + t.template, t.is_virtual, t.name, t.parent_class, t.members.ctors, t. + members.methods, t.members.static_methods, t.members.properties, t. + members.operators, t.members.enums)) def __init__( self, diff --git a/wrap/gtwrap/interface_parser/function.py b/wrap/gtwrap/interface_parser/function.py index 9fe1f56f0f..9e68c6ecec 100644 --- a/wrap/gtwrap/interface_parser/function.py +++ b/wrap/gtwrap/interface_parser/function.py @@ -81,7 +81,7 @@ def from_parse_result(parse_result: ParseResults): return ArgumentList([]) def __repr__(self) -> str: - return self.args_list.__repr__() + return repr(tuple(self.args_list)) def __len__(self) -> int: return len(self.args_list) diff --git a/wrap/gtwrap/template_instantiator.py b/wrap/gtwrap/template_instantiator.py index 0cda93d5db..f5beb0c69d 100644 --- a/wrap/gtwrap/template_instantiator.py +++ b/wrap/gtwrap/template_instantiator.py @@ -41,6 +41,8 @@ def instantiate_type(ctype: parser.Type, str_arg_typename = str(ctype.typename) + # Instantiate templates which have enumerated instantiations in the template. + # E.g. `template`. if str_arg_typename in template_typenames: idx = template_typenames.index(str_arg_typename) return parser.Type( @@ -51,14 +53,15 @@ def instantiate_type(ctype: parser.Type, is_ref=ctype.is_ref, is_basic=ctype.is_basic, ) + + # If a method has the keyword `This`, we replace it with the (instantiated) class. elif str_arg_typename == 'This': + # Check if the class is template instantiated + # so we can replace it with the instantiated version. if instantiated_class: name = instantiated_class.original.name namespaces_name = instantiated_class.namespaces() namespaces_name.append(name) - # print("INST: {}, {}, CPP: {}, CLS: {}".format( - # ctype, instantiations, cpp_typename, instantiated_class.instantiations - # ), file=sys.stderr) cpp_typename = parser.Typename( namespaces_name, instantiations=instantiated_class.instantiations) @@ -71,6 +74,14 @@ def instantiate_type(ctype: parser.Type, is_ref=ctype.is_ref, is_basic=ctype.is_basic, ) + + # Case when 'This' is present in the type namespace, e.g `This::Subclass`. + elif 'This' in str_arg_typename: + # Simply get the index of `This` in the namespace and replace it with the instantiated name. + namespace_idx = ctype.typename.namespaces.index('This') + ctype.typename.namespaces[namespace_idx] = cpp_typename.name + return ctype + else: return ctype @@ -368,19 +379,45 @@ def instantiate_ctors(self, typenames): """ instantiated_ctors = [] - for ctor in self.original.ctors: + def instantiate(instantiated_ctors, ctor, typenames, instantiations): instantiated_args = instantiate_args_list( ctor.args.list(), typenames, - self.instantiations, + instantiations, self.cpp_typename(), ) instantiated_ctors.append( parser.Constructor( name=self.name, args=parser.ArgumentList(instantiated_args), + template=self.original.template, parent=self, )) + return instantiated_ctors + + for ctor in self.original.ctors: + # Add constructor templates to the typenames and instantiations + if isinstance(ctor.template, parser.template.Template): + typenames.extend(ctor.template.typenames) + + # Get all combinations of template args + for instantiations in itertools.product( + *ctor.template.instantiations): + instantiations = self.instantiations + list(instantiations) + + instantiated_ctors = instantiate( + instantiated_ctors, + ctor, + typenames=typenames, + instantiations=instantiations) + + else: + # If no constructor level templates, just use the class templates + instantiated_ctors = instantiate( + instantiated_ctors, + ctor, + typenames=typenames, + instantiations=self.instantiations) return instantiated_ctors def instantiate_static_methods(self, typenames): diff --git a/wrap/tests/expected/matlab/MyFactorPosePoint2.m b/wrap/tests/expected/matlab/MyFactorPosePoint2.m index 2dd4b5428c..e4cd921962 100644 --- a/wrap/tests/expected/matlab/MyFactorPosePoint2.m +++ b/wrap/tests/expected/matlab/MyFactorPosePoint2.m @@ -15,9 +15,9 @@ function obj = MyFactorPosePoint2(varargin) if nargin == 2 && isa(varargin{1}, 'uint64') && varargin{1} == uint64(5139824614673773682) my_ptr = varargin{2}; - class_wrapper(56, my_ptr); + class_wrapper(62, my_ptr); elseif nargin == 4 && isa(varargin{1},'numeric') && isa(varargin{2},'numeric') && isa(varargin{3},'double') && isa(varargin{4},'gtsam.noiseModel.Base') - my_ptr = class_wrapper(57, varargin{1}, varargin{2}, varargin{3}, varargin{4}); + my_ptr = class_wrapper(63, varargin{1}, varargin{2}, varargin{3}, varargin{4}); else error('Arguments do not match any overload of MyFactorPosePoint2 constructor'); end @@ -25,7 +25,7 @@ end function delete(obj) - class_wrapper(58, obj.ptr_MyFactorPosePoint2); + class_wrapper(64, obj.ptr_MyFactorPosePoint2); end function display(obj), obj.print(''); end @@ -36,7 +36,7 @@ function delete(obj) % PRINT usage: print(string s, KeyFormatter keyFormatter) : returns void % Doxygen can be found at https://gtsam.org/doxygen/ if length(varargin) == 2 && isa(varargin{1},'char') && isa(varargin{2},'gtsam.KeyFormatter') - class_wrapper(59, this, varargin{:}); + class_wrapper(65, this, varargin{:}); return end error('Arguments do not match any overload of function MyFactorPosePoint2.print'); diff --git a/wrap/tests/expected/matlab/class_wrapper.cpp b/wrap/tests/expected/matlab/class_wrapper.cpp index fab9c14506..48d6b2dcec 100644 --- a/wrap/tests/expected/matlab/class_wrapper.cpp +++ b/wrap/tests/expected/matlab/class_wrapper.cpp @@ -33,6 +33,8 @@ typedef std::set*> Collector_Multip static Collector_MultipleTemplatesIntFloat collector_MultipleTemplatesIntFloat; typedef std::set*> Collector_ForwardKinematics; static Collector_ForwardKinematics collector_ForwardKinematics; +typedef std::set*> Collector_TemplatedConstructor; +static Collector_TemplatedConstructor collector_TemplatedConstructor; typedef std::set*> Collector_MyFactorPosePoint2; static Collector_MyFactorPosePoint2 collector_MyFactorPosePoint2; @@ -97,6 +99,12 @@ void _deleteAllObjects() collector_ForwardKinematics.erase(iter++); anyDeleted = true; } } + { for(Collector_TemplatedConstructor::iterator iter = collector_TemplatedConstructor.begin(); + iter != collector_TemplatedConstructor.end(); ) { + delete *iter; + collector_TemplatedConstructor.erase(iter++); + anyDeleted = true; + } } { for(Collector_MyFactorPosePoint2::iterator iter = collector_MyFactorPosePoint2.begin(); iter != collector_MyFactorPosePoint2.end(); ) { delete *iter; @@ -682,7 +690,76 @@ void ForwardKinematics_deconstructor_55(int nargout, mxArray *out[], int nargin, } } -void MyFactorPosePoint2_collectorInsertAndMakeBase_56(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void TemplatedConstructor_collectorInsertAndMakeBase_56(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef boost::shared_ptr Shared; + + Shared *self = *reinterpret_cast (mxGetData(in[0])); + collector_TemplatedConstructor.insert(self); +} + +void TemplatedConstructor_constructor_57(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef boost::shared_ptr Shared; + + Shared *self = new Shared(new TemplatedConstructor()); + collector_TemplatedConstructor.insert(self); + out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL); + *reinterpret_cast (mxGetData(out[0])) = self; +} + +void TemplatedConstructor_constructor_58(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef boost::shared_ptr Shared; + + string& arg = *unwrap_shared_ptr< string >(in[0], "ptr_string"); + Shared *self = new Shared(new TemplatedConstructor(arg)); + collector_TemplatedConstructor.insert(self); + out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL); + *reinterpret_cast (mxGetData(out[0])) = self; +} + +void TemplatedConstructor_constructor_59(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef boost::shared_ptr Shared; + + int arg = unwrap< int >(in[0]); + Shared *self = new Shared(new TemplatedConstructor(arg)); + collector_TemplatedConstructor.insert(self); + out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL); + *reinterpret_cast (mxGetData(out[0])) = self; +} + +void TemplatedConstructor_constructor_60(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + mexAtExit(&_deleteAllObjects); + typedef boost::shared_ptr Shared; + + double arg = unwrap< double >(in[0]); + Shared *self = new Shared(new TemplatedConstructor(arg)); + collector_TemplatedConstructor.insert(self); + out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL); + *reinterpret_cast (mxGetData(out[0])) = self; +} + +void TemplatedConstructor_deconstructor_61(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +{ + typedef boost::shared_ptr Shared; + checkArguments("delete_TemplatedConstructor",nargout,nargin,1); + Shared *self = *reinterpret_cast(mxGetData(in[0])); + Collector_TemplatedConstructor::iterator item; + item = collector_TemplatedConstructor.find(self); + if(item != collector_TemplatedConstructor.end()) { + delete self; + collector_TemplatedConstructor.erase(item); + } +} + +void MyFactorPosePoint2_collectorInsertAndMakeBase_62(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { mexAtExit(&_deleteAllObjects); typedef boost::shared_ptr> Shared; @@ -691,7 +768,7 @@ void MyFactorPosePoint2_collectorInsertAndMakeBase_56(int nargout, mxArray *out[ collector_MyFactorPosePoint2.insert(self); } -void MyFactorPosePoint2_constructor_57(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_constructor_63(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { mexAtExit(&_deleteAllObjects); typedef boost::shared_ptr> Shared; @@ -706,7 +783,7 @@ void MyFactorPosePoint2_constructor_57(int nargout, mxArray *out[], int nargin, *reinterpret_cast (mxGetData(out[0])) = self; } -void MyFactorPosePoint2_deconstructor_58(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_deconstructor_64(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { typedef boost::shared_ptr> Shared; checkArguments("delete_MyFactorPosePoint2",nargout,nargin,1); @@ -719,7 +796,7 @@ void MyFactorPosePoint2_deconstructor_58(int nargout, mxArray *out[], int nargin } } -void MyFactorPosePoint2_print_59(int nargout, mxArray *out[], int nargin, const mxArray *in[]) +void MyFactorPosePoint2_print_65(int nargout, mxArray *out[], int nargin, const mxArray *in[]) { checkArguments("print",nargout,nargin-1,2); auto obj = unwrap_shared_ptr>(in[0], "ptr_MyFactorPosePoint2"); @@ -909,16 +986,34 @@ void mexFunction(int nargout, mxArray *out[], int nargin, const mxArray *in[]) ForwardKinematics_deconstructor_55(nargout, out, nargin-1, in+1); break; case 56: - MyFactorPosePoint2_collectorInsertAndMakeBase_56(nargout, out, nargin-1, in+1); + TemplatedConstructor_collectorInsertAndMakeBase_56(nargout, out, nargin-1, in+1); break; case 57: - MyFactorPosePoint2_constructor_57(nargout, out, nargin-1, in+1); + TemplatedConstructor_constructor_57(nargout, out, nargin-1, in+1); break; case 58: - MyFactorPosePoint2_deconstructor_58(nargout, out, nargin-1, in+1); + TemplatedConstructor_constructor_58(nargout, out, nargin-1, in+1); break; case 59: - MyFactorPosePoint2_print_59(nargout, out, nargin-1, in+1); + TemplatedConstructor_constructor_59(nargout, out, nargin-1, in+1); + break; + case 60: + TemplatedConstructor_constructor_60(nargout, out, nargin-1, in+1); + break; + case 61: + TemplatedConstructor_deconstructor_61(nargout, out, nargin-1, in+1); + break; + case 62: + MyFactorPosePoint2_collectorInsertAndMakeBase_62(nargout, out, nargin-1, in+1); + break; + case 63: + MyFactorPosePoint2_constructor_63(nargout, out, nargin-1, in+1); + break; + case 64: + MyFactorPosePoint2_deconstructor_64(nargout, out, nargin-1, in+1); + break; + case 65: + MyFactorPosePoint2_print_65(nargout, out, nargin-1, in+1); break; } } catch(const std::exception& e) { diff --git a/wrap/tests/expected/python/class_pybind.cpp b/wrap/tests/expected/python/class_pybind.cpp index 4c2371a42c..f36c0a84ca 100644 --- a/wrap/tests/expected/python/class_pybind.cpp +++ b/wrap/tests/expected/python/class_pybind.cpp @@ -86,6 +86,12 @@ PYBIND11_MODULE(class_py, m_) { py::class_>(m_, "ForwardKinematics") .def(py::init(), py::arg("robot"), py::arg("start_link_name"), py::arg("end_link_name"), py::arg("joint_angles"), py::arg("l2Tp") = gtsam::Pose3()); + py::class_>(m_, "TemplatedConstructor") + .def(py::init<>()) + .def(py::init(), py::arg("arg")) + .def(py::init(), py::arg("arg")) + .def(py::init(), py::arg("arg")); + py::class_, std::shared_ptr>>(m_, "MyFactorPosePoint2") .def(py::init>(), py::arg("key1"), py::arg("key2"), py::arg("measured"), py::arg("noiseModel")) .def("print",[](MyFactor* self, const string& s, const gtsam::KeyFormatter& keyFormatter){ py::scoped_ostream_redirect output; self->print(s, keyFormatter);}, py::arg("s") = "factor: ", py::arg("keyFormatter") = gtsam::DefaultKeyFormatter) diff --git a/wrap/tests/expected/python/enum_pybind.cpp b/wrap/tests/expected/python/enum_pybind.cpp index ffc68ece04..73b74bd866 100644 --- a/wrap/tests/expected/python/enum_pybind.cpp +++ b/wrap/tests/expected/python/enum_pybind.cpp @@ -69,6 +69,16 @@ PYBIND11_MODULE(enum_py, m_) { .value("Groot", gtsam::MCU::GotG::Groot); + py::class_, std::shared_ptr>> optimizergaussnewtonparams(m_gtsam, "OptimizerGaussNewtonParams"); + optimizergaussnewtonparams + .def("setVerbosity",[](gtsam::Optimizer* self, const Optimizer::Verbosity value){ self->setVerbosity(value);}, py::arg("value")); + + py::enum_::Verbosity>(optimizergaussnewtonparams, "Verbosity", py::arithmetic()) + .value("SILENT", gtsam::Optimizer::Verbosity::SILENT) + .value("SUMMARY", gtsam::Optimizer::Verbosity::SUMMARY) + .value("VERBOSE", gtsam::Optimizer::Verbosity::VERBOSE); + + #include "python/specializations.h" diff --git a/wrap/tests/fixtures/class.i b/wrap/tests/fixtures/class.i index 9e30b17b5c..9923ffce76 100644 --- a/wrap/tests/fixtures/class.i +++ b/wrap/tests/fixtures/class.i @@ -7,6 +7,7 @@ class FunRange { template class Fun { + static This staticMethodWithThis(); template @@ -118,5 +119,14 @@ class ForwardKinematics { const gtsam::Pose3& l2Tp = gtsam::Pose3()); }; +// Test for templated constructor +class TemplatedConstructor { + TemplatedConstructor(); + + template + TemplatedConstructor(const T& arg); +}; + + class SuperCoolFactor; typedef SuperCoolFactor SuperCoolFactorPose3; diff --git a/wrap/tests/fixtures/enum.i b/wrap/tests/fixtures/enum.i index 9386a33df0..71918c25a4 100644 --- a/wrap/tests/fixtures/enum.i +++ b/wrap/tests/fixtures/enum.i @@ -42,4 +42,17 @@ class MCU { }; +template +class Optimizer { + enum Verbosity { + SILENT, + SUMMARY, + VERBOSE + }; + + void setVerbosity(const This::Verbosity value); +}; + +typedef gtsam::Optimizer OptimizerGaussNewtonParams; + } // namespace gtsam diff --git a/wrap/tests/test_interface_parser.py b/wrap/tests/test_interface_parser.py index 95987f42f2..25e4178a60 100644 --- a/wrap/tests/test_interface_parser.py +++ b/wrap/tests/test_interface_parser.py @@ -314,6 +314,25 @@ def test_constructor(self): self.assertEqual(5, len(ret.args)) self.assertEqual("gtsam::Pose3()", ret.args.list()[4].default) + def test_constructor_templated(self): + """Test for templated class constructor.""" + f = """ + template + Class(); + """ + ret = Constructor.rule.parseString(f)[0] + self.assertEqual("Class", ret.name) + self.assertEqual(0, len(ret.args)) + + f = """ + template + Class(const T& name); + """ + ret = Constructor.rule.parseString(f)[0] + self.assertEqual("Class", ret.name) + self.assertEqual(1, len(ret.args)) + self.assertEqual("const T & name", ret.args.args_list[0].to_cpp()) + def test_operator_overload(self): """Test for operator overloading.""" # Unary operator