508 lines
17 KiB
C++
508 lines
17 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
|
|
* Atlanta, Georgia 30332-0415
|
|
* All Rights Reserved
|
|
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
|
|
|
|
* See LICENSE for the license information
|
|
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* @file Basis.h
|
|
* @brief Compute an interpolating basis
|
|
* @author Varun Agrawal, Jing Dong, Frank Dellaert
|
|
* @date July 4, 2020
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <gtsam/base/Matrix.h>
|
|
#include <gtsam/base/OptionalJacobian.h>
|
|
#include <gtsam/basis/ParameterMatrix.h>
|
|
|
|
#include <iostream>
|
|
|
|
/**
|
|
* This file supports creating continuous functions `f(x;p)` as a linear
|
|
* combination of `basis functions` such as the Fourier basis on SO(2) or a set
|
|
* of Chebyshev polynomials on [-1,1].
|
|
*
|
|
* In the expression `f(x;p)` the variable `x` is
|
|
* the continuous argument at which the function is evaluated, and `p` are
|
|
* the parameters which are coefficients of the different basis functions,
|
|
* e.g. p = [4; 3; 2] => 4 + 3x + 2x^2 for a polynomial.
|
|
* However, different parameterizations are also possible.
|
|
|
|
* The `Basis` class below defines a number of functors that can be used to
|
|
* evaluate `f(x;p)` at a given `x`, and these functors also calculate
|
|
* the Jacobian of `f(x;p)` with respect to the parameters `p`.
|
|
* This is actually the most important calculation, as it will allow GTSAM
|
|
* to optimize over the parameters `p`.
|
|
|
|
* This functionality is implemented using the `CRTP` or "Curiously recurring
|
|
* template pattern" C++ idiom, which is a meta-programming technique in which
|
|
* the derived class is passed as a template argument to `Basis<DERIVED>`.
|
|
* The DERIVED class is assumed to satisfy a C++ concept,
|
|
* i.e., we expect it to define the following types and methods:
|
|
|
|
- type `Parameters`: the parameters `p` in f(x;p)
|
|
- `CalculateWeights(size_t N, double x, double a=default, double b=default)`
|
|
- `DerivativeWeights(size_t N, double x, double a=default, double b=default)`
|
|
|
|
where `Weights` is an N*1 row vector which defines the basis values for the
|
|
polynomial at the specified point `x`.
|
|
|
|
E.g. A Fourier series would give the following:
|
|
- `CalculateWeights` -> For N=5, the values for the bases:
|
|
[1, cos(x), sin(x), cos(2x), sin(2x)]
|
|
- `DerivativeWeights` -> For N=5, these are:
|
|
[0, -sin(x), cos(x), -2sin(2x), 2cos(x)]
|
|
|
|
Note that for a pseudo-spectral basis (as in Chebyshev2), the weights are
|
|
instead the values for the Barycentric interpolation formula, since the values
|
|
at the polynomial points (e.g. Chebyshev points) define the bases.
|
|
*/
|
|
|
|
namespace gtsam {
|
|
|
|
using Weights = Eigen::Matrix<double, 1, -1>; /* 1xN vector */
|
|
|
|
/**
|
|
* @brief Function for computing the kronecker product of the 1*N Weight vector
|
|
* `w` with the MxM identity matrix `I` efficiently.
|
|
* The main reason for this is so we don't need to use Eigen's Unsupported
|
|
* library.
|
|
*
|
|
* @tparam M Size of the identity matrix.
|
|
* @param w The weights of the polynomial.
|
|
* @return Mx(M*N) kronecker product [w(0)*I, w(1)*I, ..., w(N-1)*I]
|
|
*
|
|
* @ingroup Basis
|
|
*/
|
|
template <size_t M>
|
|
Matrix kroneckerProductIdentity(const Weights& w) {
|
|
Matrix result(M, w.cols() * M);
|
|
result.setZero();
|
|
|
|
for (int i = 0; i < w.cols(); i++) {
|
|
result.block(0, i * M, M, M).diagonal().array() = w(i);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* CRTP Base class for function bases
|
|
* @ingroup Basis
|
|
*/
|
|
template <typename DERIVED>
|
|
class Basis {
|
|
public:
|
|
/**
|
|
* Calculate weights for all x in vector X.
|
|
* Returns M*N matrix where M is the size of the vector X,
|
|
* and N is the number of basis functions.
|
|
*/
|
|
static Matrix WeightMatrix(size_t N, const Vector& X) {
|
|
Matrix W(X.size(), N);
|
|
for (int i = 0; i < X.size(); i++)
|
|
W.row(i) = DERIVED::CalculateWeights(N, X(i));
|
|
return W;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculate weights for all x in vector X, with interval [a,b].
|
|
*
|
|
* @param N The number of basis functions.
|
|
* @param X The vector for which to compute the weights.
|
|
* @param a The lower bound for the interval range.
|
|
* @param b The upper bound for the interval range.
|
|
* @return Returns M*N matrix where M is the size of the vector X.
|
|
*/
|
|
static Matrix WeightMatrix(size_t N, const Vector& X, double a, double b) {
|
|
Matrix W(X.size(), N);
|
|
for (int i = 0; i < X.size(); i++)
|
|
W.row(i) = DERIVED::CalculateWeights(N, X(i), a, b);
|
|
return W;
|
|
}
|
|
|
|
/**
|
|
* An instance of an EvaluationFunctor calculates f(x;p) at a given `x`,
|
|
* applied to Parameters `p`.
|
|
* This functor is used to evaluate a parameterized function at a given scalar
|
|
* value x. When given a specific N*1 vector of Parameters, returns the scalar
|
|
* value of the function at x, possibly with Jacobians wrpt the parameters.
|
|
*/
|
|
class EvaluationFunctor {
|
|
protected:
|
|
Weights weights_;
|
|
|
|
public:
|
|
/// For serialization
|
|
EvaluationFunctor() {}
|
|
|
|
/// Constructor with interval [a,b]
|
|
EvaluationFunctor(size_t N, double x)
|
|
: weights_(DERIVED::CalculateWeights(N, x)) {}
|
|
|
|
/// Constructor with interval [a,b]
|
|
EvaluationFunctor(size_t N, double x, double a, double b)
|
|
: weights_(DERIVED::CalculateWeights(N, x, a, b)) {}
|
|
|
|
/// Regular 1D evaluation
|
|
double apply(const typename DERIVED::Parameters& p,
|
|
OptionalJacobian<-1, -1> H = boost::none) const {
|
|
if (H) *H = weights_;
|
|
return (weights_ * p)(0);
|
|
}
|
|
|
|
/// c++ sugar
|
|
double operator()(const typename DERIVED::Parameters& p,
|
|
OptionalJacobian<-1, -1> H = boost::none) const {
|
|
return apply(p, H); // might call apply in derived
|
|
}
|
|
|
|
void print(const std::string& s = "") const {
|
|
std::cout << s << (s != "" ? " " : "") << weights_ << std::endl;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* VectorEvaluationFunctor at a given x, applied to ParameterMatrix<M>.
|
|
* This functor is used to evaluate a parameterized function at a given scalar
|
|
* value x. When given a specific M*N parameters, returns an M-vector the M
|
|
* corresponding functions at x, possibly with Jacobians wrpt the parameters.
|
|
*/
|
|
template <int M>
|
|
class VectorEvaluationFunctor : protected EvaluationFunctor {
|
|
protected:
|
|
using VectorM = Eigen::Matrix<double, M, 1>;
|
|
using Jacobian = Eigen::Matrix<double, /*MxMN*/ M, -1>;
|
|
Jacobian H_;
|
|
|
|
/**
|
|
* Calculate the `M*(M*N)` Jacobian of this functor with respect to
|
|
* the M*N parameter matrix `P`.
|
|
* We flatten assuming column-major order, e.g., if N=3 and M=2, we have
|
|
* H =[ w(0) 0 w(1) 0 w(2) 0
|
|
* 0 w(0) 0 w(1) 0 w(2) ]
|
|
* i.e., the Kronecker product of weights_ with the MxM identity matrix.
|
|
*/
|
|
void calculateJacobian() {
|
|
H_ = kroneckerProductIdentity<M>(this->weights_);
|
|
}
|
|
|
|
public:
|
|
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
|
|
|
/// For serialization
|
|
VectorEvaluationFunctor() {}
|
|
|
|
/// Default Constructor
|
|
VectorEvaluationFunctor(size_t N, double x) : EvaluationFunctor(N, x) {
|
|
calculateJacobian();
|
|
}
|
|
|
|
/// Constructor, with interval [a,b]
|
|
VectorEvaluationFunctor(size_t N, double x, double a, double b)
|
|
: EvaluationFunctor(N, x, a, b) {
|
|
calculateJacobian();
|
|
}
|
|
|
|
/// M-dimensional evaluation
|
|
VectorM apply(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*MxN*/ -1, -1> H = boost::none) const {
|
|
if (H) *H = H_;
|
|
return P.matrix() * this->weights_.transpose();
|
|
}
|
|
|
|
/// c++ sugar
|
|
VectorM operator()(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*MxN*/ -1, -1> H = boost::none) const {
|
|
return apply(P, H);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Given a M*N Matrix of M-vectors at N polynomial points, an instance of
|
|
* VectorComponentFunctor computes the N-vector value for a specific row
|
|
* component of the M-vectors at all the polynomial points.
|
|
*
|
|
* This component is specified by the row index i, with 0<i<M.
|
|
*/
|
|
template <int M>
|
|
class VectorComponentFunctor : public EvaluationFunctor {
|
|
protected:
|
|
using Jacobian = Eigen::Matrix<double, /*1xMN*/ 1, -1>;
|
|
size_t rowIndex_;
|
|
Jacobian H_;
|
|
|
|
/*
|
|
* Calculate the `1*(M*N)` Jacobian of this functor with respect to
|
|
* the M*N parameter matrix `P`.
|
|
* We flatten assuming column-major order, e.g., if N=3 and M=2, we have
|
|
* H=[w(0) 0 w(1) 0 w(2) 0] for rowIndex==0
|
|
* H=[0 w(0) 0 w(1) 0 w(2)] for rowIndex==1
|
|
* i.e., one row of the Kronecker product of weights_ with the
|
|
* MxM identity matrix. See also VectorEvaluationFunctor.
|
|
*/
|
|
void calculateJacobian(size_t N) {
|
|
H_.setZero(1, M * N);
|
|
for (int j = 0; j < EvaluationFunctor::weights_.size(); j++)
|
|
H_(0, rowIndex_ + j * M) = EvaluationFunctor::weights_(j);
|
|
}
|
|
|
|
public:
|
|
/// For serialization
|
|
VectorComponentFunctor() {}
|
|
|
|
/// Construct with row index
|
|
VectorComponentFunctor(size_t N, size_t i, double x)
|
|
: EvaluationFunctor(N, x), rowIndex_(i) {
|
|
calculateJacobian(N);
|
|
}
|
|
|
|
/// Construct with row index and interval
|
|
VectorComponentFunctor(size_t N, size_t i, double x, double a, double b)
|
|
: EvaluationFunctor(N, x, a, b), rowIndex_(i) {
|
|
calculateJacobian(N);
|
|
}
|
|
|
|
/// Calculate component of component rowIndex_ of P
|
|
double apply(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*1xMN*/ -1, -1> H = boost::none) const {
|
|
if (H) *H = H_;
|
|
return P.row(rowIndex_) * EvaluationFunctor::weights_.transpose();
|
|
}
|
|
|
|
/// c++ sugar
|
|
double operator()(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*1xMN*/ -1, -1> H = boost::none) const {
|
|
return apply(P, H);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Manifold EvaluationFunctor at a given x, applied to ParameterMatrix<M>.
|
|
* This functor is used to evaluate a parameterized function at a given scalar
|
|
* value x. When given a specific M*N parameters, returns an M-vector the M
|
|
* corresponding functions at x, possibly with Jacobians wrpt the parameters.
|
|
*
|
|
* The difference with the VectorEvaluationFunctor is that after computing the
|
|
* M*1 vector xi=F(x;P), with x a scalar and P the M*N parameter vector, we
|
|
* also retract xi back to the T manifold.
|
|
* For example, if T==Rot3, then we first compute a 3-vector xi using x and P,
|
|
* and then map that 3-vector xi back to the Rot3 manifold, yielding a valid
|
|
* 3D rotation.
|
|
*/
|
|
template <class T>
|
|
class ManifoldEvaluationFunctor
|
|
: public VectorEvaluationFunctor<traits<T>::dimension> {
|
|
enum { M = traits<T>::dimension };
|
|
using Base = VectorEvaluationFunctor<M>;
|
|
|
|
public:
|
|
/// For serialization
|
|
ManifoldEvaluationFunctor() {}
|
|
|
|
/// Default Constructor
|
|
ManifoldEvaluationFunctor(size_t N, double x) : Base(N, x) {}
|
|
|
|
/// Constructor, with interval [a,b]
|
|
ManifoldEvaluationFunctor(size_t N, double x, double a, double b)
|
|
: Base(N, x, a, b) {}
|
|
|
|
/// Manifold evaluation
|
|
T apply(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*MxMN*/ -1, -1> H = boost::none) const {
|
|
// Interpolate the M-dimensional vector to yield a vector in tangent space
|
|
Eigen::Matrix<double, M, 1> xi = Base::operator()(P, H);
|
|
|
|
// Now call retract with this M-vector, possibly with derivatives
|
|
Eigen::Matrix<double, M, M> D_result_xi;
|
|
T result = T::ChartAtOrigin::Retract(xi, H ? &D_result_xi : 0);
|
|
|
|
// Finally, if derivatives are asked, apply chain rule where H is Mx(M*N)
|
|
// derivative of interpolation and D_result_xi is MxM derivative of
|
|
// retract.
|
|
if (H) *H = D_result_xi * (*H);
|
|
|
|
// and return a T
|
|
return result;
|
|
}
|
|
|
|
/// c++ sugar
|
|
T operator()(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*MxN*/ -1, -1> H = boost::none) const {
|
|
return apply(P, H); // might call apply in derived
|
|
}
|
|
};
|
|
|
|
/// Base class for functors below that calculate derivative weights
|
|
class DerivativeFunctorBase {
|
|
protected:
|
|
Weights weights_;
|
|
|
|
public:
|
|
/// For serialization
|
|
DerivativeFunctorBase() {}
|
|
|
|
DerivativeFunctorBase(size_t N, double x)
|
|
: weights_(DERIVED::DerivativeWeights(N, x)) {}
|
|
|
|
DerivativeFunctorBase(size_t N, double x, double a, double b)
|
|
: weights_(DERIVED::DerivativeWeights(N, x, a, b)) {}
|
|
|
|
void print(const std::string& s = "") const {
|
|
std::cout << s << (s != "" ? " " : "") << weights_ << std::endl;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An instance of a DerivativeFunctor calculates f'(x;p) at a given `x`,
|
|
* applied to Parameters `p`.
|
|
* When given a scalar value x and a specific N*1 vector of Parameters,
|
|
* this functor returns the scalar derivative value of the function at x,
|
|
* possibly with Jacobians wrpt the parameters.
|
|
*/
|
|
class DerivativeFunctor : protected DerivativeFunctorBase {
|
|
public:
|
|
/// For serialization
|
|
DerivativeFunctor() {}
|
|
|
|
DerivativeFunctor(size_t N, double x) : DerivativeFunctorBase(N, x) {}
|
|
|
|
DerivativeFunctor(size_t N, double x, double a, double b)
|
|
: DerivativeFunctorBase(N, x, a, b) {}
|
|
|
|
double apply(const typename DERIVED::Parameters& p,
|
|
OptionalJacobian</*1xN*/ -1, -1> H = boost::none) const {
|
|
if (H) *H = this->weights_;
|
|
return (this->weights_ * p)(0);
|
|
}
|
|
/// c++ sugar
|
|
double operator()(const typename DERIVED::Parameters& p,
|
|
OptionalJacobian</*1xN*/ -1, -1> H = boost::none) const {
|
|
return apply(p, H); // might call apply in derived
|
|
}
|
|
};
|
|
|
|
/**
|
|
* VectorDerivativeFunctor at a given x, applied to ParameterMatrix<M>.
|
|
*
|
|
* This functor is used to evaluate the derivatives of a parameterized
|
|
* function at a given scalar value x. When given a specific M*N parameters,
|
|
* returns an M-vector the M corresponding function derivatives at x, possibly
|
|
* with Jacobians wrpt the parameters.
|
|
*/
|
|
template <int M>
|
|
class VectorDerivativeFunctor : protected DerivativeFunctorBase {
|
|
protected:
|
|
using VectorM = Eigen::Matrix<double, M, 1>;
|
|
using Jacobian = Eigen::Matrix<double, /*MxMN*/ M, -1>;
|
|
Jacobian H_;
|
|
|
|
/**
|
|
* Calculate the `M*(M*N)` Jacobian of this functor with respect to
|
|
* the M*N parameter matrix `P`.
|
|
* We flatten assuming column-major order, e.g., if N=3 and M=2, we have
|
|
* H =[ w(0) 0 w(1) 0 w(2) 0
|
|
* 0 w(0) 0 w(1) 0 w(2) ]
|
|
* i.e., the Kronecker product of weights_ with the MxM identity matrix.
|
|
*/
|
|
void calculateJacobian() {
|
|
H_ = kroneckerProductIdentity<M>(this->weights_);
|
|
}
|
|
|
|
public:
|
|
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
|
|
|
/// For serialization
|
|
VectorDerivativeFunctor() {}
|
|
|
|
/// Default Constructor
|
|
VectorDerivativeFunctor(size_t N, double x) : DerivativeFunctorBase(N, x) {
|
|
calculateJacobian();
|
|
}
|
|
|
|
/// Constructor, with optional interval [a,b]
|
|
VectorDerivativeFunctor(size_t N, double x, double a, double b)
|
|
: DerivativeFunctorBase(N, x, a, b) {
|
|
calculateJacobian();
|
|
}
|
|
|
|
VectorM apply(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*MxMN*/ -1, -1> H = boost::none) const {
|
|
if (H) *H = H_;
|
|
return P.matrix() * this->weights_.transpose();
|
|
}
|
|
/// c++ sugar
|
|
VectorM operator()(
|
|
const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*MxMN*/ -1, -1> H = boost::none) const {
|
|
return apply(P, H);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Given a M*N Matrix of M-vectors at N polynomial points, an instance of
|
|
* ComponentDerivativeFunctor computes the N-vector derivative for a specific
|
|
* row component of the M-vectors at all the polynomial points.
|
|
*
|
|
* This component is specified by the row index i, with 0<i<M.
|
|
*/
|
|
template <int M>
|
|
class ComponentDerivativeFunctor : protected DerivativeFunctorBase {
|
|
protected:
|
|
using Jacobian = Eigen::Matrix<double, /*1xMN*/ 1, -1>;
|
|
size_t rowIndex_;
|
|
Jacobian H_;
|
|
|
|
/*
|
|
* Calculate the `1*(M*N)` Jacobian of this functor with respect to
|
|
* the M*N parameter matrix `P`.
|
|
* We flatten assuming column-major order, e.g., if N=3 and M=2, we have
|
|
* H=[w(0) 0 w(1) 0 w(2) 0] for rowIndex==0
|
|
* H=[0 w(0) 0 w(1) 0 w(2)] for rowIndex==1
|
|
* i.e., one row of the Kronecker product of weights_ with the
|
|
* MxM identity matrix. See also VectorDerivativeFunctor.
|
|
*/
|
|
void calculateJacobian(size_t N) {
|
|
H_.setZero(1, M * N);
|
|
for (int j = 0; j < this->weights_.size(); j++)
|
|
H_(0, rowIndex_ + j * M) = this->weights_(j);
|
|
}
|
|
|
|
public:
|
|
/// For serialization
|
|
ComponentDerivativeFunctor() {}
|
|
|
|
/// Construct with row index
|
|
ComponentDerivativeFunctor(size_t N, size_t i, double x)
|
|
: DerivativeFunctorBase(N, x), rowIndex_(i) {
|
|
calculateJacobian(N);
|
|
}
|
|
|
|
/// Construct with row index and interval
|
|
ComponentDerivativeFunctor(size_t N, size_t i, double x, double a, double b)
|
|
: DerivativeFunctorBase(N, x, a, b), rowIndex_(i) {
|
|
calculateJacobian(N);
|
|
}
|
|
/// Calculate derivative of component rowIndex_ of F
|
|
double apply(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*1xMN*/ -1, -1> H = boost::none) const {
|
|
if (H) *H = H_;
|
|
return P.row(rowIndex_) * this->weights_.transpose();
|
|
}
|
|
/// c++ sugar
|
|
double operator()(const ParameterMatrix<M>& P,
|
|
OptionalJacobian</*1xMN*/ -1, -1> H = boost::none) const {
|
|
return apply(P, H);
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
} // namespace gtsam
|