2014-12-03 23:04:06 +08:00
GTSAM Concepts
==============
2014-12-04 01:24:49 +08:00
As discussed in [Generic Programming Techniques ](http://www.boost.org/community/generic_programming.html ), concepts define
2014-12-03 23:04:06 +08:00
* associated types
2014-12-04 00:49:43 +08:00
* valid expressions, like functions and values
2014-12-03 23:04:06 +08:00
* invariants
* complexity guarantees
2014-12-04 01:24:49 +08:00
Below we discuss the most important concepts use in GTSAM, and after that we discuss how they are implemented/used/enforced.
2014-12-04 00:49:43 +08:00
Manifold
--------
To optimize over continuous types, we assume they are manifolds. This is central to GTSAM and hence discussed in some more detail below.
2014-12-06 00:03:08 +08:00
[Manifolds ](http://en.wikipedia.org/wiki/Manifold#Charts.2C_atlases.2C_and_transition_maps ) and [charts ](http://en.wikipedia.org/wiki/Manifold#Charts.2C_atlases.2C_and_transition_maps ) are intimately linked concepts. We are only interested here in [differentiable manifolds ](http://en.wikipedia.org/wiki/Differentiable_manifold#Definition ), continuous spaces that can be locally approximated *at any point* using a local vector space, called the [tangent space ](http://en.wikipedia.org/wiki/Tangent_space ). A *chart* is an invertible map from the manifold to that tangent space.
2014-12-04 00:49:43 +08:00
2014-12-11 23:28:32 +08:00
In GTSAM, all properties and operations needed to use a type must be defined through template specialization of the struct `gtsam::manifold::traits` . Concept checks are used to check that all required functions are implemented.
In detail, we ask the following are defined in the traits object:
2014-12-04 02:43:52 +08:00
* values:
2014-12-12 17:05:03 +08:00
* `enum { dimension = D};` , an enum that indicates the dimensionality *n* of the manifold. In Eigen-fashion, we also support manifolds whose dimenionality is only defined at runtime, by specifying the value -1.
2014-12-04 02:43:52 +08:00
* types:
2014-12-08 16:51:27 +08:00
* `TangentVector` , type that lives in tangent space. This will almost always be an `Eigen::Matrix<double,n,1>` .
2014-12-12 17:05:03 +08:00
* `ChartJacobian` , a typedef for `OptionalJacobian<dimension, dimension>` .
* `ManifoldType` , a pointer back to the type.
* `structure_category` , a tag type that defines what requirements the type fulfills, and therefore what requirements this traits class must fulfill. It should be defined to be one of the following:
* `gtsam::traits::manifold_tag` -- Everything in this list is expected
* `gtsam::traits::group_tag` -- Everything in this list is expected, plus the functions defined under **Groups** below.
* `gtsam::traits::lie_group_tag` -- Everything in this list is expected, plus the functions defined under **Groups** , and **Lie Groups** below.
* `gtsam::traits::vector_space_tag` -- Everything in this list is expected, plus the functions defined under **Groups** , and **Lie Groups** below.
2014-12-04 05:09:35 +08:00
* valid expressions:
2014-12-11 23:28:32 +08:00
* `size_t dim = traits<T>::getDimension(p);` static function should be defined. This is mostly useful if the size is not known at compile time.
* `v = traits<T>::Local(p,q)` , the chart, from manifold to tangent space, think of it as *q (-) p* , where *p* and *q* are elements of the manifold and the result, *v* is an element of the vector space.
2014-12-12 17:05:03 +08:00
* `v = traits<T>::Local(p,q, Hp, Hq)` .
2014-12-11 23:28:32 +08:00
* `p = traits<T>::Retract(p,v)` , the inverse chart, from tangent space to manifold, think of it as *p (+) v* , where *p* is an element of the manifold and the result, *v* is an element of the vector space.
* `p = traits<T>::Retract(p,v, Hp, Hv)` .
2014-12-04 02:43:52 +08:00
2014-12-11 23:28:32 +08:00
In the functions above, the `H` arguments stand for optional Jacobians. When provided, it is assumed
2014-12-08 21:08:39 +08:00
that the function will return the derivatives of the chart (and inverse) with respect to its arguments.
2014-12-11 23:28:32 +08:00
* invariants
2014-12-12 17:05:03 +08:00
* `Retract(p, Local(p,q)) == q`
* `Local(p, Retract(p, v)) == v`
2014-12-08 21:08:39 +08:00
For many differential manifolds, an obvious mapping is the `exponential map` ,
which associates straight lines in the tangent space with geodesics on the manifold
(and it's inverse, the log map). However, there are two cases in which we deviate from this:
2014-12-04 00:49:43 +08:00
2014-12-11 23:28:32 +08:00
* Sometimes, most notably for *SO(3)* and *SE(3)* , the exponential map is unnecessarily expensive for use in optimization. Hence, the `Local` and `Retract` refer to a chart that is much cheaper to evaluate.
* While vector spaces (see below) are in principle also manifolds, it is overkill to think about charts etc. Really, we should simply think about vector addition and subtraction. Hence, while a these functions are defined for every vector space, GTSAM will never invoke them. (IS THIS TRUE?)
2014-12-04 00:49:43 +08:00
Group
-----
2014-12-11 23:28:32 +08:00
A [group ](http://en.wikipedia.org/wiki/Group_(mathematics )) should be well known from grade school :-), and provides a type with a composition operation that is closed, associative, has an identity element, and an inverse for each element. The following should be added to the traits class for a group:
2014-12-04 02:43:52 +08:00
* valid expressions:
2014-12-11 23:28:32 +08:00
* `r = traits<M>::Compose(p,q)` , where *p* , *q* , and *r* are elements of the manifold.
* `q = traits<M>::Inverse(p)` , where *p* and*q* are elements of the manifold.
* `r = traits<M>::Between(p,q)` , where *p* , *q* , and *r* are elements of the manifold.
* static members:
2014-12-12 17:05:03 +08:00
* `traits<M>::Identity` , a static const member that represents the group's identity element.
2014-12-11 23:28:32 +08:00
* invariants:
* `Compose(p,Inverse(p)) == Identity`
* `Compose(p,Between(p,q)) == q`
* `Between(p,q) == Compose(Inverse(p),q)`
The `gtsam::group::traits` namespace defines the following:
* values:
2014-12-12 17:05:03 +08:00
* `traits<M>::Identity` -- The identity element for this group stored as a static const.
* `traits<M>::group_flavor` -- the flavor of this group's `compose()` operator, either:
* `gtsam::traits::group_multiplicative_tag` for multiplicative operator syntax ,or
* `gtsam::traits::group_additive_tag` for additive operator syntax.
2014-12-11 23:28:32 +08:00
2014-12-04 02:43:52 +08:00
We do *not* at this time support more than one composition operator per type. Although mathematically possible, it is hardly ever needed, and the machinery to support it would be burdensome and counter-intuitive.
2014-12-04 00:49:43 +08:00
2014-12-04 02:43:52 +08:00
Also, a type should provide either multiplication or addition operators depending on the flavor of the operation. To distinguish between the two, we will use a tag (see below).
2014-12-04 00:49:43 +08:00
2014-12-06 04:22:58 +08:00
Group Action
------------
2014-12-08 17:42:56 +08:00
Group actions are concepts in and of themselves that can be concept checked (see below).
In particular, a group can *act* on another space.
For example, the [cyclic group of order 6 ](http://en.wikipedia.org/wiki/Cyclic_group ) can rotate 2D vectors around the origin:
2014-12-06 00:03:08 +08:00
q = R(i)*p
where R(i) = R(60)^i, where R(60) rotates by 60 degrees
Hence, we formalize by the following extension of the concept:
* valid expressions:
2014-12-11 23:28:32 +08:00
* `q = traits<T>::Act(g,p)` , for some instance, *p* , of a space *S* , that can be acted upon by the group element *g* to produce *q* in *S* .
* `q = traits<T>::Act(g,p,Hp)` , if the space acted upon is a continuous differentiable manifold. *
2014-12-06 04:22:58 +08:00
2014-12-11 23:28:32 +08:00
In the latter case, if *S* is an n-dimensional manifold, *Hp* is an output argument that should be
filled with the *nxn* Jacobian matrix of the action with respect to a change in *p* . It typically depends
on the group element *g* , but in most common example will *not* depend on the value of *p* . For example, in
2014-12-08 17:42:56 +08:00
the cyclic group example above, we simply have
2014-12-11 23:28:32 +08:00
Hp = R(i)
2014-12-06 00:03:08 +08:00
2014-12-08 17:42:56 +08:00
Note there is no derivative of the action with respect to a change in g. That will only be defined
for Lie groups, which we introduce now.
2014-12-04 00:49:43 +08:00
Lie Group
---------
2014-12-08 17:42:56 +08:00
A Lie group is both a manifold *and* a group. Hence, a LIE_GROUP type should implements both MANIFOLD and GROUP concepts.
However, we now also need to be able to evaluate the derivatives of compose and inverse.
2014-12-11 23:28:32 +08:00
Hence, we have the following extra valid static functions defined in the struct `gtsam::manifold::traits<M>` :
2014-12-06 00:03:08 +08:00
2014-12-11 23:28:32 +08:00
* `r = traits<M>::Compose(p,q,Hq,Hp)`
* `q = traits<M>::Inverse(p,Hp)`
* `r = traits<M>::Between(p,q,Hq,H2p)`
2014-12-06 00:03:08 +08:00
2014-12-11 23:28:32 +08:00
where above the *H* arguments stand for optional Jacobian arguments.
2014-12-08 17:42:56 +08:00
That makes it possible to create factors implementing priors (PriorFactor) or relations between
2014-12-08 18:26:04 +08:00
two instances of a Lie group type (BetweenFactor).
2014-12-06 00:03:08 +08:00
2014-12-11 23:28:32 +08:00
In addition, a Lie group has a Lie algebra, which affords two extra valid expressions:
2014-12-08 21:08:39 +08:00
2014-12-11 23:28:32 +08:00
* `v = Chart::Log(p,Hp)` , the log map, with optional Jacobian
* `p = Chart::Exp(v,Hv)` , the exponential map, with optional Jacobian
2014-12-08 21:08:39 +08:00
Note that in the Lie group case, the usual valid expressions for Retract and Local can be generated automatically, e.g.
2014-12-12 17:05:03 +08:00
```
2014-12-08 21:08:39 +08:00
T Retract(p,v,Hp,Hv) {
2014-12-11 23:28:32 +08:00
T q = Exp(v,Hqv);
T r = Compose(p,q,Hrp,Hrq);
2014-12-08 21:08:39 +08:00
Hv = Hrq * Hqv; // chain rule
return r;
}
2014-12-12 17:05:03 +08:00
```
2014-12-08 21:08:39 +08:00
2014-12-06 04:22:58 +08:00
Lie Group Action
----------------
When a Lie group acts on a space, we have two derivatives to care about:
2014-12-06 00:03:08 +08:00
2014-12-11 23:28:32 +08:00
* `gtasm::manifold::traits<M>::act(g,p,Hg,Hp)` , if the space acted upon is a continuous differentiable manifold.
2014-12-08 17:42:56 +08:00
An example is a *similarity transform* in 3D, which can act on 3D space, like
q = s*R*p + t
2014-12-11 23:28:32 +08:00
Note that again the derivative in *p* , *Hp* is simply *s R* , which depends on *g* but not on *p* .
The derivative in *g* , *Hg* , is in general more complex.
2014-12-06 00:26:06 +08:00
For now, we won't care about Lie groups acting on non-manifolds.
2014-12-04 00:49:43 +08:00
2014-12-06 00:43:21 +08:00
Matrix Group
------------
2014-12-11 23:28:32 +08:00
Most Lie groups we care about are *Matrix groups* , continuous sub-groups of *GL(n)* , the group of *n x n* invertible matrices.
2014-12-06 00:43:21 +08:00
In this case, a lot of the derivatives calculations needed can be standardized.
2014-12-04 00:49:43 +08:00
Vector Space
------------
2014-12-06 00:26:06 +08:00
Trivial Lie group where
2014-12-04 02:43:52 +08:00
2014-12-11 23:28:32 +08:00
* `Identity == 0`
* `Inverse(p) == -p`
* `Compose(p,q) == p+q`
* `Between(p,q) == q-p`
* `Local(q) == p-q`
* `Retract(v) == p+v`
2014-12-04 02:43:52 +08:00
This considerably simplifies certain operations.
2014-12-04 00:49:43 +08:00
2014-12-04 01:24:49 +08:00
Testable
--------
Unit tests heavily depend on the following two functions being defined for all types that need to be tested:
2014-12-04 02:43:52 +08:00
* valid expressions:
2014-12-11 23:28:32 +08:00
* `Print(p,s)` where s is an optional string
* `Equals(p,q,tol)` where tol is an optional (double) tolerance
2014-12-04 01:24:49 +08:00
Implementation
==============
2014-12-06 05:18:53 +08:00
GTSAM Types start with Uppercase, e.g., `gtsam::Point2` , and are models of the
TESTABLE, MANIFOLD, GROUP, LIE_GROUP, and VECTOR_SPACE concepts.
2014-12-04 02:43:52 +08:00
2014-12-11 23:28:32 +08:00
`gtsam::manifold::traits` is our way to associate these concepts with types,
2014-12-06 05:18:53 +08:00
and we also define a limited number of `gtsam::tags` to select the correct implementation
2014-12-11 23:28:32 +08:00
of certain functions at compile time (tag dispatching).
2014-12-06 05:18:53 +08:00
2014-12-04 02:43:52 +08:00
Traits
2014-12-03 23:41:11 +08:00
------
2014-12-06 05:18:53 +08:00
However, a base class is not a good way to implement/check the other concepts, as we would like these
to apply equally well to types that are outside GTSAM control, e.g., `Eigen::VectorXd` . This is where
[traits ](http://www.boost.org/doc/libs/1_57_0/libs/type_traits/doc/html/boost_typetraits/background.html ) come in.
2014-12-03 23:04:06 +08:00
2014-12-11 23:28:32 +08:00
We will use Eigen-style or STL-style traits, that define *many* properties at once.
2014-12-03 23:04:06 +08:00
2014-12-11 23:28:32 +08:00
Note that not everything that makes a concept is defined by traits. Valid expressions such as traits< T > ::Compose are
defined simply as static functions within the traits class.
2014-12-06 05:18:53 +08:00
Finally, for GTSAM types, it is perfectly acceptable (and even desired) to define associated types as internal types,
rather than having to use traits internally.
2014-12-04 05:09:35 +08:00
2014-12-03 23:04:06 +08:00
2014-12-11 23:28:32 +08:00
** THE EXAMPLES ARE NOT UPDATED YET **
2014-12-04 05:09:35 +08:00
2014-12-04 04:05:57 +08:00
Manifold Example
----------------
2014-12-03 23:04:06 +08:00
2014-12-04 02:43:52 +08:00
An example of implementing a Manifold type is here:
2014-12-03 23:04:06 +08:00
2014-12-04 05:24:49 +08:00
```
#!c++
2014-12-03 23:51:22 +08:00
// GTSAM type
class Rot2 {
2014-12-12 17:05:03 +08:00
typedef Vector2 TangentVector; // internal typedef, not required
2014-12-06 05:18:53 +08:00
class Chart : gtsam::Chart< Rot2 , Chart > {
2014-12-12 17:05:03 +08:00
static TangentVector local(const Rot2& R1, const Rot2& R2);
static Rot2 retract(const Rot2& R, const TangentVector& v);
2014-12-03 23:51:22 +08:00
}
2014-12-04 02:43:52 +08:00
Rot2 operator*(const Rot2& ) const;
Rot2 transpose() const;
2014-12-03 23:51:22 +08:00
}
2014-12-03 23:04:06 +08:00
2014-12-04 02:43:52 +08:00
namespace gtsam { namespace traits {
2014-12-03 23:04:06 +08:00
2014-12-03 23:51:22 +08:00
template< >
2014-12-04 02:43:52 +08:00
struct dimension< Rot2 > {
static const int value = 2;
typedef int value_type;
2014-12-03 23:51:22 +08:00
}
2014-12-04 02:43:52 +08:00
template< >
struct TangentVector< Rot2 > {
typedef Rot2::TangentVector type;
}
template< >
struct defaultChart< Rot2 > : Rot2::Chart {}
}}
2014-12-04 05:24:49 +08:00
```
2014-12-04 02:43:52 +08:00
But Rot2 is in fact also a Lie Group, after we define
2014-12-04 05:24:49 +08:00
```
#!c++
namespace manifold {
2014-12-04 02:43:52 +08:00
Rot2 inverse(const Rot2& p) { return p.transpose();}
Rot2 operator*(const Rot2& p, const Rot2& q) { return p*q;}
Rot2 compose(const Rot2& p, const Rot2& q) { return p*q;}
2014-12-04 04:05:57 +08:00
Rot2 between(const Rot2& p, const Rot2& q) { return inverse(p)*q;}
2014-12-04 05:24:49 +08:00
}
```
2014-12-04 02:43:52 +08:00
The only traits that needs to be implemented are the tags:
2014-12-04 05:24:49 +08:00
```
#!c++
namespace gtsam {
namespace traits {
2014-12-04 02:43:52 +08:00
template< >
struct structure< Rot2 > : lie_group_tag {}
template< >
struct group_flavor< Rot2 > : multiplicative_group_tag {}
2014-12-03 23:04:06 +08:00
2014-12-04 05:24:49 +08:00
}
}
```
2014-12-03 23:04:06 +08:00
2014-12-06 00:03:08 +08:00
Group Example
-------------
As an example of a group, let's do a cyclic group, acting on Point2:
```
#!c++
// GTSAM type
class Cyclic {
CyclicGroup(size_t n) {}
Cyclic operator+(const Cyclic& ) const; // add modulo n
Cyclic operator-() const; // negate modulo n
// Act on R2, rotate by i*360/n, derivative will simply be 2*2 rotation matrix
Point2 operator*(cons Point2& p, OptionalJacobian< 2 , 2 > H) const;
}
namespace group {
// make Cyclic obey GROUP concept
Cyclic compose(const Cyclic& g, const Cyclic& h) { return g+h;}
Cyclic between(const Cyclic& g, const Cyclic& h) { return h-g;}
Cyclic inverse(const Cyclic& g) { return -p;}
// implement acting on 2D
Vector2 act(Const Cyclic& g, cons Point2& p) { return g*p;}
}
```
Lie Group Example
-----------------
As an example of a Lie group, let's do a similarity transform, acting on Point3:
```
#!c++
// GTSAM type
class Similarity3 {
... constructors and Manifold stuff...
2014-12-06 00:43:21 +08:00
Similarity3 compose(const Similarity3& , OptionalJacobian< 7 , 7 > H1, OptionalJacobian< 7 , 7 > H2) const;
Similarity3 between(const Similarity3& , OptionalJacobian< 7 , 7 > H1, OptionalJacobian< 7 , 7 > H2) const;
Similarity3 inverse(OptionalJacobian< 7 , 7 > H) const; // matrix inverse
2014-12-06 00:03:08 +08:00
2014-12-06 00:43:21 +08:00
Similarity3 operator*(const Similarity3& g) const { return compose(h);} // compose sugar
2014-12-06 00:03:08 +08:00
// Act on R3
2014-12-06 00:43:21 +08:00
Point3 act(cons Point3& p, OptionalJacobian< 3 , 7 > Hg, OptionalJacobian< 3 , 3 > Hp) const {
2014-12-06 00:03:08 +08:00
if (Hg) *Hg << - s * R * [p], s * R, R * p; // TODO check !
if (Hp) *Hp = s*R;
return s*R*p + t;
}
2014-12-06 00:43:21 +08:00
Point3 operator*(cons Point3& p); // act sugar
2014-12-06 00:03:08 +08:00
}
namespace group {
// make Similarity3 obey GROUP concept
2014-12-06 00:43:21 +08:00
Similarity3 compose(const Similarity3& g, const Similarity3& h,
OptionalJacobian< 7 , 7 > H1, OptionalJacobian< 7 , 7 > H2) { return g.operator*(p,H1,H2);}
Similarity3 between(const Similarity3& g, const Similarity3& h,
OptionalJacobian< 7 , 7 > H1, OptionalJacobian< 7 , 7 > H2) { return g.between(h,H1,H2);}
Similarity3 inverse(const Similarity3& g, OptionalJacobian< 7 , 7 > H) { return p.inverse(H);}
2014-12-06 00:03:08 +08:00
2014-12-06 00:43:21 +08:00
// implement acting on 3D
Vector2 act(Const Similarity3& g, cons Point3& p,
OptionalJacobian< 3 , 7 > Hg, OptionalJacobian< 3 , 3 > Hp) { return g.act(p,Hg,Hp);}
2014-12-06 00:03:08 +08:00
}
```
2014-12-04 04:05:57 +08:00
Vector Space Example
--------------------
Providing the Vector space concept is easier:
2014-12-04 05:24:49 +08:00
```
#!c++
2014-12-04 04:05:57 +08:00
// GTSAM type
class Point2 {
static const int dimension = 2;
Point2 operator+(const Point2& ) const;
Point2 operator-(const Point2& ) const;
Point2 operator-() const;
// Still needs to be defined, unless Point2 *is* Vector2
class Chart {
2014-12-12 17:05:03 +08:00
static Vector2 local(const Point2& p, const Point2& q) const;
static Rot2 retract(const Point2& p, const Point2& v) const;
2014-12-04 04:05:57 +08:00
}
}
2014-12-04 05:24:49 +08:00
```
2014-12-04 04:05:57 +08:00
The following macro, called inside the gtsam namespace,
2014-12-12 17:05:03 +08:00
DEFINE_VECTOR_SPACE_TRAITS(Point2)
2014-12-04 04:05:57 +08:00
should automatically define
2014-12-04 05:24:49 +08:00
```
#!c++
namespace traits {
2014-12-04 04:05:57 +08:00
template< >
struct dimension< Point2 > {
static const int value = Point2::dimension;
typedef int value_type;
}
template< >
struct TangentVector< Point2 > {
typedef Matrix::Eigen< double , Point2::dimension , 1 > type;
}
template< >
struct defaultChart< Point2 > : Point2::Chart {}
template< >
struct Manifold< Point2::Chart > {
typedef Point2 type;
}
template< >
struct structure< Point2 > : vector_space_tag {}
template< >
struct group_flavor< Point2 > : additive_group_tag {}
2014-12-04 05:24:49 +08:00
}
```
2014-12-04 04:05:57 +08:00
and
2014-12-04 05:24:49 +08:00
```
#!c++
namespace manifold {
2014-12-04 04:12:03 +08:00
Point2 inverse(const Point2& p) { return -p;}
2014-12-04 04:05:57 +08:00
Point2 operator+(const Point2& p, const Point2& q) { return p+q;}
Point2 compose(const Point2& p, const Point2& q) { return p+q;}
Point2 between(const Point2& p, const Point2& q) { return q-p;}
2014-12-04 05:24:49 +08:00
}
```
2014-12-03 23:04:06 +08:00
2014-12-04 05:09:35 +08:00
Concept Checks
--------------
Boost provides a nice way to check whether a given type satisfies a concept. For example, the following
2014-12-04 08:12:18 +08:00
BOOST_CONCEPT_ASSERT(ChartConcept< gtsam::traits::defaultChart < Point2 > >)
2014-12-04 05:09:35 +08:00
Using the following from Mike Bosse's prototype:
2014-12-04 05:24:49 +08:00
2014-12-04 05:09:35 +08:00
```
2014-12-04 05:14:18 +08:00
#!c++
2014-12-04 05:09:35 +08:00
template< class C >
struct ChartConcept {
typedef gtsam::traits::Manifold< C > ::type type;
typedef gtsam::traits::TangentVector< type > ::type vector;
BOOST_CONCEPT_USAGE(ChartConcept) {
2014-12-04 08:12:18 +08:00
2014-12-04 05:09:35 +08:00
// Returns Retraction update of val_
type retract_ret = C::retract(val_, vec_);
// Returns local coordinates of another object
vec_ = C::local(val_, retract_ret);
}
private:
type val_;
vector vec_;
int dim_;
};
```