Point Cloud Library (PCL) 1.15.1-dev
Loading...
Searching...
No Matches
anderson_acceleration.h
1#pragma once
2
3#include <pcl/exceptions.h>
4#include <pcl/memory.h>
5
6#include <Eigen/Core>
7#include <Eigen/QR>
8
9#include <algorithm>
10#include <cstddef>
11
12namespace pcl {
13namespace registration {
14
15/**
16 * \brief Lightweight Anderson acceleration helper used to speed up fixed-point
17 * iterations in FRICP.
18 *
19 * The class stores a short history of the most recent residuals and solves the
20 * normal equations following the scheme described in "Fast and Robust Iterative
21 * Closest Point", Zhang et al., 2022.
22 *
23 * Only double precision is supported internally to maximise numerical stability.
24 */
26public:
28
29 /**
30 * \brief Initialise the accelerator with a circular buffer of size \a history.
31 *
32 * \param[in] history Number of previous iterates to keep (m in the paper).
33 * \param[in] dimension Dimensionality of the flattened state vector.
34 * \param[in] initial_state Pointer to the initial state (expects
35 * \a dimension doubles).
36 */
37 inline void
38 init(std::size_t history, std::size_t dimension, const double* initial_state)
39 {
40 if ((history == 0) || (dimension == 0)) {
41 PCL_THROW_EXCEPTION(pcl::BadArgumentException,
42 "AndersonAcceleration::init expects non-zero sizes");
43 }
44
45 history_length_ = history;
46 dimension_ = dimension;
47 iteration_ = 0;
48 column_index_ = 0;
49 initialized_ = true;
50
51 current_u_.resize(dimension_);
52 current_F_.resize(dimension_);
53 prev_dG_.setZero(dimension_, history_length_);
54 prev_dF_.setZero(dimension_, history_length_);
55 normal_eq_matrix_.setZero(history_length_, history_length_);
56 theta_.setZero(history_length_);
57 dF_scale_.setZero(history_length_);
58
59 current_u_ = Eigen::Map<const Eigen::VectorXd>(initial_state, dimension_);
60 }
61
62 inline bool
64 {
65 return initialized_;
66 }
67
68 inline std::size_t
69 history() const
70 {
71 return history_length_;
72 }
73
74 inline std::size_t
75 dimension() const
76 {
77 return dimension_;
78 }
79
80 inline void
81 replace(const double* state)
82 {
83 if (!initialized_)
84 return;
85 current_u_ = Eigen::Map<const Eigen::VectorXd>(state, dimension_);
86 }
87
88 inline void
89 reset(const double* state)
90 {
91 if (!initialized_)
92 return;
93 iteration_ = 0;
94 column_index_ = 0;
95 current_u_ = Eigen::Map<const Eigen::VectorXd>(state, dimension_);
96 }
97
98 /**
99 * \brief Apply one Anderson acceleration update.
100 *
101 * \param[in] next_state Flattened state obtained from the fixed-point iteration.
102 * \return Reference to the accelerated state vector.
103 */
104 inline const Eigen::VectorXd&
105 compute(const double* next_state)
106 {
107 if (!initialized_) {
108 PCL_THROW_EXCEPTION(pcl::PCLException,
109 "AndersonAcceleration::compute called before init");
110 }
111
112 Eigen::Map<const Eigen::VectorXd> G(next_state, dimension_);
113 current_F_ = G - current_u_;
114
115 constexpr double eps = 1e-14;
116
117 if (iteration_ == 0) {
118 prev_dF_.col(0) = -current_F_;
119 prev_dG_.col(0) = -G;
120 current_u_ = G;
121 }
122 else {
123 prev_dF_.col(column_index_) += current_F_;
124 prev_dG_.col(column_index_) += G;
125
126 double scale = std::max(eps, prev_dF_.col(column_index_).norm());
127 dF_scale_(column_index_) = scale;
128 prev_dF_.col(column_index_) /= scale;
129
130 const std::size_t m_k = std::min(history_length_, iteration_);
131
132 if (m_k == 1) {
133 theta_(0) = 0.0;
134 const double dF_norm = prev_dF_.col(column_index_).norm();
135 normal_eq_matrix_(0, 0) = dF_norm * dF_norm;
136 if (dF_norm > eps) {
137 theta_(0) = (prev_dF_.col(column_index_) / dF_norm).dot(current_F_ / dF_norm);
138 }
139 }
140 else {
141 // update Gram matrix row/column corresponding to the newest column
142 const Eigen::RowVectorXd new_inner_prod =
143 prev_dF_.col(column_index_).transpose() *
144 prev_dF_.block(0, 0, dimension_, m_k);
145 normal_eq_matrix_.block(column_index_, 0, 1, m_k) = new_inner_prod;
146 normal_eq_matrix_.block(0, column_index_, m_k, 1) = new_inner_prod.transpose();
147
148 cod_.compute(normal_eq_matrix_.block(0, 0, m_k, m_k));
149 theta_.head(m_k) =
150 cod_.solve(prev_dF_.block(0, 0, dimension_, m_k).transpose() * current_F_);
151 }
152
153 const Eigen::ArrayXd scaled_theta =
154 theta_.head(m_k).array() / dF_scale_.head(m_k).array();
155 current_u_ = G - prev_dG_.block(0, 0, dimension_, m_k) * scaled_theta.matrix();
156
157 column_index_ = (column_index_ + 1) % history_length_;
158 prev_dF_.col(column_index_) = -current_F_;
159 prev_dG_.col(column_index_) = -G;
160 }
161
162 ++iteration_;
163 return current_u_;
164 }
165
166private:
167 std::size_t history_length_{0};
168 std::size_t dimension_{0};
169 std::size_t iteration_{0};
170 std::size_t column_index_{0};
171 bool initialized_{false};
172
173 Eigen::VectorXd current_u_;
174 Eigen::VectorXd current_F_;
175 Eigen::MatrixXd prev_dG_;
176 Eigen::MatrixXd prev_dF_;
177 Eigen::MatrixXd normal_eq_matrix_;
178 Eigen::VectorXd theta_;
179 Eigen::VectorXd dF_scale_;
180 Eigen::CompleteOrthogonalDecomposition<Eigen::MatrixXd> cod_;
181
183};
184
185} // namespace registration
186} // namespace pcl
An exception that is thrown when the arguments number or type is wrong/unhandled.
Definition exceptions.h:259
A base class for all pcl exceptions which inherits from std::runtime_error.
Definition exceptions.h:67
Lightweight Anderson acceleration helper used to speed up fixed-point iterations in FRICP.
void init(std::size_t history, std::size_t dimension, const double *initial_state)
Initialise the accelerator with a circular buffer of size history.
const Eigen::VectorXd & compute(const double *next_state)
Apply one Anderson acceleration update.
#define PCL_MAKE_ALIGNED_OPERATOR_NEW
Macro to signal a class requires a custom allocator.
Definition memory.h:86
Defines functions, macros and traits for allocating and using memory.