| // Ceres Solver - A fast non-linear least squares minimizer |
| // Copyright 2023 Google Inc. All rights reserved. |
| // http://ceres-solver.org/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // * Redistributions of source code must retain the above copyright notice, |
| // this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // * Neither the name of Google Inc. nor the names of its contributors may be |
| // used to endorse or promote products derived from this software without |
| // specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
| // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| // POSSIBILITY OF SUCH DAMAGE. |
| // |
| // Author: sameeragarwal@google.com (Sameer Agarwal) |
| // sergiu.deitsch@gmail.com (Sergiu Deitsch) |
| // |
| |
| #ifndef CERES_PUBLIC_PRODUCT_MANIFOLD_H_ |
| #define CERES_PUBLIC_PRODUCT_MANIFOLD_H_ |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <cstddef> |
| #include <numeric> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "ceres/internal/eigen.h" |
| #include "ceres/internal/fixed_array.h" |
| #include "ceres/internal/port.h" |
| #include "ceres/manifold.h" |
| |
| namespace ceres { |
| |
| // Construct a manifold by taking the Cartesian product of a number of other |
| // manifolds. This is useful, when a parameter block is the Cartesian product |
| // of two or more manifolds. For example the parameters of a camera consist of |
| // a rotation and a translation, i.e., SO(3) x R^3. |
| // |
| // Example usage: |
| // |
| // ProductManifold<QuaternionManifold, EuclideanManifold<3>> se3; |
| // |
| // is the manifold for a rigid transformation, where the rotation is |
| // represented using a quaternion. |
| // |
| // Manifolds can be copied and moved to ProductManifold: |
| // |
| // SubsetManifold manifold1(5, {2}); |
| // SubsetManifold manifold2(3, {0, 1}); |
| // ProductManifold<SubsetManifold, SubsetManifold> manifold(manifold1, |
| // manifold2); |
| // |
| // In advanced use cases, manifolds can be dynamically allocated and passed as |
| // (smart) pointers: |
| // |
| // ProductManifold<std::unique_ptr<QuaternionManifold>, EuclideanManifold<3>> |
| // se3{std::make_unique<QuaternionManifold>(), EuclideanManifold<3>{}}; |
| // |
| // In C++17, the template parameters can be left out as they are automatically |
| // deduced making the initialization much simpler: |
| // |
| // ProductManifold se3{QuaternionManifold{}, EuclideanManifold<3>{}}; |
| // |
| // The manifold implementations must be either default constructible, copyable |
| // or moveable to be usable in a ProductManifold. |
| template <typename Manifold0, typename Manifold1, typename... ManifoldN> |
| class ProductManifold final : public Manifold { |
| public: |
| // ProductManifold constructor perfect forwards arguments to store manifolds. |
| // |
| // Either use default construction or if you need to copy or move-construct a |
| // manifold instance, you need to pass an instance as an argument for all |
| // types given as class template parameters. |
| template <typename... Args, |
| std::enable_if_t<std::is_constructible< |
| std::tuple<Manifold0, Manifold1, ManifoldN...>, |
| Args...>::value>* = nullptr> |
| explicit ProductManifold(Args&&... manifolds) |
| : ProductManifold{std::make_index_sequence<kNumManifolds>{}, |
| std::forward<Args>(manifolds)...} {} |
| |
| int AmbientSize() const override { return ambient_size_; } |
| int TangentSize() const override { return tangent_size_; } |
| |
| bool Plus(const double* x, |
| const double* delta, |
| double* x_plus_delta) const override { |
| return PlusImpl( |
| x, delta, x_plus_delta, std::make_index_sequence<kNumManifolds>{}); |
| } |
| |
| bool Minus(const double* y, |
| const double* x, |
| double* y_minus_x) const override { |
| return MinusImpl( |
| y, x, y_minus_x, std::make_index_sequence<kNumManifolds>{}); |
| } |
| |
| bool PlusJacobian(const double* x, double* jacobian_ptr) const override { |
| MatrixRef jacobian(jacobian_ptr, AmbientSize(), TangentSize()); |
| jacobian.setZero(); |
| internal::FixedArray<double> buffer(buffer_size_); |
| |
| return PlusJacobianImpl( |
| x, jacobian, buffer, std::make_index_sequence<kNumManifolds>{}); |
| } |
| |
| bool MinusJacobian(const double* x, double* jacobian_ptr) const override { |
| MatrixRef jacobian(jacobian_ptr, TangentSize(), AmbientSize()); |
| jacobian.setZero(); |
| internal::FixedArray<double> buffer(buffer_size_); |
| |
| return MinusJacobianImpl( |
| x, jacobian, buffer, std::make_index_sequence<kNumManifolds>{}); |
| } |
| |
| private: |
| static constexpr std::size_t kNumManifolds = 2 + sizeof...(ManifoldN); |
| |
| template <std::size_t... Indices, typename... Args> |
| explicit ProductManifold(std::index_sequence<Indices...>, Args&&... manifolds) |
| : manifolds_{std::forward<Args>(manifolds)...}, |
| buffer_size_{(std::max)( |
| {(Dereference(std::get<Indices>(manifolds_)).TangentSize() * |
| Dereference(std::get<Indices>(manifolds_)).AmbientSize())...})}, |
| ambient_sizes_{ |
| Dereference(std::get<Indices>(manifolds_)).AmbientSize()...}, |
| tangent_sizes_{ |
| Dereference(std::get<Indices>(manifolds_)).TangentSize()...}, |
| ambient_offsets_{ExclusiveScan(ambient_sizes_)}, |
| tangent_offsets_{ExclusiveScan(tangent_sizes_)}, |
| ambient_size_{ |
| std::accumulate(ambient_sizes_.begin(), ambient_sizes_.end(), 0)}, |
| tangent_size_{ |
| std::accumulate(tangent_sizes_.begin(), tangent_sizes_.end(), 0)} {} |
| |
| template <std::size_t Index0, std::size_t... Indices> |
| bool PlusImpl(const double* x, |
| const double* delta, |
| double* x_plus_delta, |
| std::index_sequence<Index0, Indices...>) const { |
| if (!Dereference(std::get<Index0>(manifolds_)) |
| .Plus(x + ambient_offsets_[Index0], |
| delta + tangent_offsets_[Index0], |
| x_plus_delta + ambient_offsets_[Index0])) { |
| return false; |
| } |
| |
| return PlusImpl(x, delta, x_plus_delta, std::index_sequence<Indices...>{}); |
| } |
| |
| static constexpr bool PlusImpl(const double* /*x*/, |
| const double* /*delta*/, |
| double* /*x_plus_delta*/, |
| std::index_sequence<>) noexcept { |
| return true; |
| } |
| |
| template <std::size_t Index0, std::size_t... Indices> |
| bool MinusImpl(const double* y, |
| const double* x, |
| double* y_minus_x, |
| std::index_sequence<Index0, Indices...>) const { |
| if (!Dereference(std::get<Index0>(manifolds_)) |
| .Minus(y + ambient_offsets_[Index0], |
| x + ambient_offsets_[Index0], |
| y_minus_x + tangent_offsets_[Index0])) { |
| return false; |
| } |
| |
| return MinusImpl(y, x, y_minus_x, std::index_sequence<Indices...>{}); |
| } |
| |
| static constexpr bool MinusImpl(const double* /*y*/, |
| const double* /*x*/, |
| double* /*y_minus_x*/, |
| std::index_sequence<>) noexcept { |
| return true; |
| } |
| |
| template <std::size_t Index0, std::size_t... Indices> |
| bool PlusJacobianImpl(const double* x, |
| MatrixRef& jacobian, |
| internal::FixedArray<double>& buffer, |
| std::index_sequence<Index0, Indices...>) const { |
| if (!Dereference(std::get<Index0>(manifolds_)) |
| .PlusJacobian(x + ambient_offsets_[Index0], buffer.data())) { |
| return false; |
| } |
| |
| jacobian.block(ambient_offsets_[Index0], |
| tangent_offsets_[Index0], |
| ambient_sizes_[Index0], |
| tangent_sizes_[Index0]) = |
| MatrixRef( |
| buffer.data(), ambient_sizes_[Index0], tangent_sizes_[Index0]); |
| |
| return PlusJacobianImpl( |
| x, jacobian, buffer, std::index_sequence<Indices...>{}); |
| } |
| |
| static constexpr bool PlusJacobianImpl( |
| const double* /*x*/, |
| MatrixRef& /*jacobian*/, |
| internal::FixedArray<double>& /*buffer*/, |
| std::index_sequence<>) noexcept { |
| return true; |
| } |
| |
| template <std::size_t Index0, std::size_t... Indices> |
| bool MinusJacobianImpl(const double* x, |
| MatrixRef& jacobian, |
| internal::FixedArray<double>& buffer, |
| std::index_sequence<Index0, Indices...>) const { |
| if (!Dereference(std::get<Index0>(manifolds_)) |
| .MinusJacobian(x + ambient_offsets_[Index0], buffer.data())) { |
| return false; |
| } |
| |
| jacobian.block(tangent_offsets_[Index0], |
| ambient_offsets_[Index0], |
| tangent_sizes_[Index0], |
| ambient_sizes_[Index0]) = |
| MatrixRef( |
| buffer.data(), tangent_sizes_[Index0], ambient_sizes_[Index0]); |
| |
| return MinusJacobianImpl( |
| x, jacobian, buffer, std::index_sequence<Indices...>{}); |
| } |
| |
| static constexpr bool MinusJacobianImpl( |
| const double* /*x*/, |
| MatrixRef& /*jacobian*/, |
| internal::FixedArray<double>& /*buffer*/, |
| std::index_sequence<>) noexcept { |
| return true; |
| } |
| |
| template <typename T, std::size_t N> |
| static std::array<T, N> ExclusiveScan(const std::array<T, N>& values) { |
| std::array<T, N> result; |
| // TODO Replace with std::exclusive_scan once all platforms have full C++17 |
| // STL support. |
| T init = 0; |
| for (std::size_t i = 0; i != N; ++i) { |
| result[i] = init; |
| init += values[i]; |
| } |
| return result; |
| } |
| |
| template <typename T, typename E = void> |
| struct IsDereferenceable : std::false_type {}; |
| |
| template <typename T> |
| struct IsDereferenceable<T, std::void_t<decltype(*std::declval<T>())>> |
| : std::true_type {}; |
| |
| template <typename T, |
| std::enable_if_t<!IsDereferenceable<T>::value>* = nullptr> |
| static constexpr decltype(auto) Dereference(T& value) { |
| return value; |
| } |
| |
| // Support dereferenceable types such as std::unique_ptr, std::shared_ptr, raw |
| // pointers etc. |
| template <typename T, |
| std::enable_if_t<IsDereferenceable<T>::value>* = nullptr> |
| static constexpr decltype(auto) Dereference(T& value) { |
| return *value; |
| } |
| |
| template <typename T> |
| static constexpr decltype(auto) Dereference(T* p) { |
| assert(p != nullptr); |
| return *p; |
| } |
| |
| std::tuple<Manifold0, Manifold1, ManifoldN...> manifolds_; |
| int buffer_size_; |
| std::array<int, kNumManifolds> ambient_sizes_; |
| std::array<int, kNumManifolds> tangent_sizes_; |
| std::array<int, kNumManifolds> ambient_offsets_; |
| std::array<int, kNumManifolds> tangent_offsets_; |
| int ambient_size_; |
| int tangent_size_; |
| }; |
| |
| // C++17 deduction guide that allows the user to avoid explicitly specifying |
| // the template parameters of ProductManifold. The class can instead be |
| // instantiated as follows: |
| // |
| // ProductManifold manifold{QuaternionManifold{}, EuclideanManifold<3>{}}; |
| // |
| template <typename Manifold0, typename Manifold1, typename... Manifolds> |
| ProductManifold(Manifold0&&, Manifold1&&, Manifolds&&...) |
| -> ProductManifold<Manifold0, Manifold1, Manifolds...>; |
| |
| } // namespace ceres |
| |
| #endif // CERES_PUBLIC_PRODUCT_MANIFOLD_H_ |