OK, that should about do it...

release/4.3a0
dellaert 2014-12-03 19:43:52 +01:00
parent 68422161a2
commit 40f5188b20
1 changed files with 113 additions and 49 deletions

View File

@ -16,33 +16,56 @@ Manifold
To optimize over continuous types, we assume they are manifolds. This is central to GTSAM and hence discussed in some more detail below. To optimize over continuous types, we assume they are manifolds. This is central to GTSAM and hence discussed in some more detail below.
[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 the vector space. [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 the vector space.
In GTSAM we assume that a manifold type can yield such a chart at any point, and we require that a functor `defaultChart` is available that, when called for any point on the manifold, returns a Chart type. In GTSAM we assume that a manifold type can yield such a chart at any point, and we require that a functor `defaultChart` is available that, when called for any point on the manifold, returns a Chart type. Hence, the functor itself can be seen as an *Atlas*.
* values: `dimension` In detail, we ask the following are defined for a MANIFOLD type:
* types: `Vector`, type that lives in tangent space
* * values:
* `DefaultChart` is the *type* of the chart returned by the functor `defaultChart` * `dimension`, an int 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.
* functor `defaultChart`, returns a `DefaultChart` * functors:
* invariants: `defaultChart::result_type == DefaultChart::type` * `defaultChart`, returns the default chart at a point p
* types:
* `TangentVector`, type that lives in tangent space. This will almost always be an `Eigen::Matrix<double,n,1>`.
Anything else? Anything else?
Chart Chart
----- -----
A given chart is implemented using a small class that defines the chart itself (from manifold to tangent space) and its inverse.
* types: `Manifold`, a pointer back to the type * types:
* values: `retract`, `local` * `Manifold`, a pointer back to the type
* valid expressions:
* `Chart chart(p)` constructor
* `v = chart.local(q)`, the chart, from manifold to tangent space, think of it as *p (-) q*
* `p = chart.retract(v)`, the inverse chart, from tangent space to manifold, think of it as *p (+) v*
Are these values? They are just methods. Anything else? For many differential manifolds, an obvious mapping is the `exponential map`, which associates staright 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:
* Sometimes, most notably for *SO(3)* and *SE(3)*, the exponential map is unnecessarily expensive for use in optimiazation. Hence, the `defaultChart` functor returns 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 `defaultChart` functor is defined by default for every vector space, GTSAM will never call it.
Group Group
----- -----
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.
* values: `identity` * values:
* values: `compose`, `inverse`, (`between`) * `identity`
* valid expressions:
* `compose(p,q)`
* `inverse(p)`
* `between(p,q)`
* invariants:
* `compose(p,inverse(p)) == identity`
* `compose(p,between(p,q)) == q`
* `between(p,q) == compose(inverse(p),q)`
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.
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).
Lie Group Lie Group
--------- ---------
@ -52,36 +75,49 @@ Implements both MANIFOLD and GROUP
Vector Space Vector Space
------------ ------------
Lie Group where compose == `+` Trivial Lie Group where
* `identity == 0`
* `inverse(p) == -p`
* `compose(p,q) == p+q`
* `between(p,q) == q-p`
* `chart.retract(q) == p-q`
* `chart.retract(v) == p+v`
This considerably simplifies certain operations.
Testable Testable
-------- --------
Unit tests heavily depend on the following two functions being defined for all types that need to be tested: Unit tests heavily depend on the following two functions being defined for all types that need to be tested:
* functions: `print`, `equals` * valid expressions:
* `print(p,s)` where s is an optional string
* `equals(p,q,tol)` where tol is an optional tolerance
Implementation Implementation
============== ==============
GTSAM Types start with Uppercase, e.g., `gtsam::Point2`, and are models of the TESTABLE, MANIFOLD, GROUP, LIE_GROUP, and VECTOR_SPACE concepts. `gtsam::traits` is our way to associate these concepts with types, and we also define a limited number of `gtsam::tags` to select the correct implementation of certain functions at compile time (tag dispatching). GTSAM Types start with Uppercase, e.g., `gtsam::Point2`, and are models of the TESTABLE, MANIFOLD, GROUP, LIE_GROUP, and VECTOR_SPACE concepts.
traits `gtsam::traits` is our way to associate these concepts with types, and we also define a limited number of `gtsam::tags` to select the correct implementation of certain functions at compile time (tag dispatching).
Traits
------ ------
We will not use Eigen-style or STL-style traits, that define many properties at once. Rather, we use boost::mpl style meta-programming functions to facilitate meta-programming. We will not use Eigen-style or STL-style traits, that define *many* properties at once. Rather, we use boost::mpl style meta-programming functions to facilitate meta-programming, which return a single type or value for every trait.
Traits allow us to play with types that are outside GTSAM control, e.g., `Eigen::VectorXd`. Traits allow us to play with types that are outside GTSAM control, e.g., `Eigen::VectorXd`. However, for GTSAM types, it is perfectly acceptable (and even desired) to define associated types as internal types, as well, rather than having to use traits internally.
The naming conventions are as follows: The conventions for `gtsam::traits` are as follows:
* Types: `gtsam::traits::SomeAssociatedType<T>::type`, i.e., they are MixedCase and define a `type`, for example: * Types: `gtsam::traits::SomeAssociatedType<T>::type`, i.e., they are MixedCase and define a *single* `type`, for example:
template<> template<>
gtsam::traits::TangentVector<Point2> { gtsam::traits::TangentVector<Point2> {
typedef Vector2 type; typedef Vector2 type;
} }
* Values: `gtsam::traits::someValue<T>::value`, i.e., they are mixedCase starting with a lowercase letter and define a `value`, but also a `value_type`. For example: * Values: `gtsam::traits::someValue<T>::value`, i.e., they are mixedCase starting with a lowercase letter and define a `value`, *and* a `value_type`. For example:
template<> template<>
gtsam::traits::dimension<Point2> { gtsam::traits::dimension<Point2> {
@ -89,7 +125,7 @@ The naming conventions are as follows:
typedef const int value_type; // const ? typedef const int value_type; // const ?
} }
* Functors: `gtsam::traits::someFunctor<T>::type`, i.e., they are mixedCase starting with a lowercase letter and define a functor `type`. The funcor itself should define a `result_type`. Example * Functors: `gtsam::traits::someFunctor<T>::type`, i.e., they are mixedCase starting with a lowercase letter and define a functor (i.e., no *type*). The functor itself should define a `result_type`. Example
struct Point2::retract { struct Point2::retract {
typedef Point2 result_type; typedef Point2 result_type;
@ -101,53 +137,62 @@ The naming conventions are as follows:
} }
template<> template<>
gtsam::traits::retract<Point2> { gtsam::traits::retract<Point2> : Point2::retract {}
typedef Point2::retract type;
}
The above is still up in the air. Do we need the type indirection? Could we just inherit the trait like so
template<>
gtsam::traits::retract<Point2> : Point2::retract {
}
In which case we could just say `gtsam::traits::retract<Point2>(p)(v)`. By *inherting* the trait from the functor, we can just use the [currying](http://en.wikipedia.org/wiki/Currying) style `gtsam::traits::retract<Point2>(p)(v)`. Note that, although technically a functor is a type, in spirit it is a free function and hence starts with a lowercase letter.
tags Tags
---- ----
Concepts are associated with a tag. Algebraic structure concepts are associated with the following tags
* `gtsam::tags::manifold_tag` * `gtsam::traits::manifold_tag`
* `gtsam::tags::group_tag` * `gtsam::traits::group_tag`
* `gtsam::tags::lie_group_tag` * `gtsam::traits::lie_group_tag`
* `gtsam::tags::vector_space_tag` * `gtsam::traits::vector_space_tag`
Can be queried `gtsam::traits::structure_tag<T>` which should be queryable by `gtsam::traits::structure<T>`
The group composition operation can be of two flavors:
* `gtsam::traits::additive_group_tag`
* `gtsam::traits::multiplicative_group_tag`
which should be queryable by `gtsam::traits::group_flavor<T>`
Examples Examples
-------- --------
An example of implementing a Manifold is here: An example of implementing a Manifold type is here:
// GTSAM type // GTSAM type
class Rot2 { class Rot2 {
... typedef Vector2 TangentVector;
class Chart { class Chart {
... Chart(const Rot2& R);
TangentVector local(const Rot2& R) const;
Rot2 retract(const TangentVector& v) const;
} }
Rot2 operator*(const Rot2&) const;
Rot2 transpose() const;
} }
namespace gtsam { namespace gtsam { namespace traits {
namespace traits {
template<> template<>
struct DefaultChart<Rot2> { struct dimension<Rot2> {
typedef Rot2::Chart type; static const int value = 2;
typedef int value_type;
} }
template<>
struct TangentVector<Rot2> {
typedef Rot2::TangentVector type;
}
template<>
struct defaultChart<Rot2> : Rot2::Chart {}
template<> template<>
struct Manifold<Rot2::Chart> { struct Manifold<Rot2::Chart> {
typedef Rot2 type; typedef Rot2 type;
@ -157,7 +202,26 @@ An example of implementing a Manifold is here:
struct Vector<Rot2::Chart> { struct Vector<Rot2::Chart> {
typedef Vector2 type; typedef Vector2 type;
} }
}
}}
But Rot2 is in fact also a Lie Group, after we define
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;}
Rot2 between(const Rot2& p, const Rot2& q) { return p*q;}
The only traits that needs to be implemented are the tags:
namespace gtsam { namespace traits {
template<>
struct structure<Rot2> : lie_group_tag {}
template<>
struct group_flavor<Rot2> : multiplicative_group_tag {}
}}