Merged in feature/BAD_generic_chart_value (pull request #26)

Generic Chart Values
release/4.3a0
Michael Bosse 2014-10-28 23:56:15 +01:00
commit ef23309c84
15 changed files with 466 additions and 264 deletions

154
gtsam/base/ChartValue.h Normal file
View File

@ -0,0 +1,154 @@
/* ----------------------------------------------------------------------------
* 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 ChartValue.h
* @date Jan 26, 2012
* @author Michael Bosse, Abel Gawel, Renaud Dube
* based on DrivedValue.h by Duy Nguyen Ta
*/
#pragma once
#include <gtsam/base/GenericValue.h>
#include <gtsam/base/Manifold.h>
#include <boost/make_shared.hpp>
//////////////////
// The following includes windows.h in some MSVC versions, so we undef min, max, and ERROR
#include <boost/pool/singleton_pool.hpp>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#ifdef ERROR
#undef ERROR
#endif
//////////////////
namespace gtsam {
// ChartValue is derived from GenericValue<T> and Chart so that Chart can be zero sized (as in DefaultChart<T>)
// if the Chart is a member variable then it won't ever be zero sized.
template<class T, class Chart_=DefaultChart<T> >
class ChartValue : public GenericValue<T>, public Chart_ {
BOOST_CONCEPT_ASSERT((ChartConcept<Chart_>));
public:
typedef T type;
typedef Chart_ Chart;
public:
ChartValue() : GenericValue<T>(T()) {}
ChartValue(const T& value) : GenericValue<T>(value) {}
template<typename C>
ChartValue(const T& value, C chart_initializer) : GenericValue<T>(value), Chart(chart_initializer) {}
virtual ~ChartValue() {}
/**
* Create a duplicate object returned as a pointer to the generic Value interface.
* For the sake of performance, this function use singleton pool allocator instead of the normal heap allocator.
* The result must be deleted with Value::deallocate_, not with the 'delete' operator.
*/
virtual Value* clone_() const {
void *place = boost::singleton_pool<PoolTag, sizeof(ChartValue)>::malloc();
ChartValue* ptr = new(place) ChartValue(*this); // calls copy constructor to fill in
return ptr;
}
/**
* Destroy and deallocate this object, only if it was originally allocated using clone_().
*/
virtual void deallocate_() const {
this->~ChartValue(); // Virtual destructor cleans up the derived object
boost::singleton_pool<PoolTag, sizeof(ChartValue)>::free((void*)this); // Release memory from pool
}
/**
* Clone this value (normal clone on the heap, delete with 'delete' operator)
*/
virtual boost::shared_ptr<Value> clone() const {
return boost::make_shared<ChartValue>(*this);
}
/// just use the equals_ defined in GenericValue
// virtual bool equals_(const Value& p, double tol = 1e-9) const {
// }
/// Chart Value interface version of retract
virtual Value* retract_(const Vector& delta) const {
// Call retract on the derived class using the retract trait function
const T retractResult = Chart::retract(GenericValue<T>::value(), delta);
// Create a Value pointer copy of the result
void* resultAsValuePlace = boost::singleton_pool<PoolTag, sizeof(ChartValue)>::malloc();
Value* resultAsValue = new(resultAsValuePlace) ChartValue(retractResult, static_cast<const Chart&>(*this));
// Return the pointer to the Value base class
return resultAsValue;
}
/// Generic Value interface version of localCoordinates
virtual Vector localCoordinates_(const Value& value2) const {
// Cast the base class Value pointer to a templated generic class pointer
const GenericValue<T>& genericValue2 = static_cast<const GenericValue<T>&>(value2);
// Return the result of calling localCoordinates trait on the derived class
return Chart::local(GenericValue<T>::value(), genericValue2.value());
}
virtual size_t dim() const {
return Chart::getDimension(GenericValue<T>::value()); // need functional form here since the dimension may be dynamic
}
/// Assignment operator
virtual Value& operator=(const Value& rhs) {
// Cast the base class Value pointer to a derived class pointer
const ChartValue& derivedRhs = static_cast<const ChartValue&>(rhs);
// Do the assignment and return the result
*this = ChartValue(derivedRhs); // calls copy constructor
return *this;
}
protected:
// implicit assignment operator for (const ChartValue& rhs) works fine here
/// Assignment operator, protected because only the Value or DERIVED
/// assignment operators should be used.
// DerivedValue<DERIVED>& operator=(const DerivedValue<DERIVED>& rhs) {
// // Nothing to do, do not call base class assignment operator
// return *this;
// }
private:
/// Fake Tag struct for singleton pool allocator. In fact, it is never used!
struct PoolTag { };
};
namespace traits {
template <typename T, typename Chart>
struct dimension<ChartValue<T, Chart> > : public dimension<Chart> {};
}
template<typename Chart>
const Chart& Value::getChart() const {
// define Value::cast here since now ChartValue has been declared
return dynamic_cast<const Chart&>(*this);
}
} /* namespace gtsam */

View File

@ -19,25 +19,6 @@
#pragma once #pragma once
#include <gtsam/base/Value.h> #include <gtsam/base/Value.h>
#include <boost/make_shared.hpp>
//////////////////
// The following includes windows.h in some MSVC versions, so we undef min, max, and ERROR
#include <boost/pool/singleton_pool.hpp>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#ifdef ERROR
#undef ERROR
#endif
//////////////////
namespace gtsam { namespace gtsam {
@ -49,142 +30,44 @@ template<typename T>
return a.equals(b,tol); return a.equals(b,tol);
} }
// trait to compute the local coordinates of other with respect to origin
template<typename T>
Vector localCoordinates(const T& origin, const T& other) {
return origin.localCoordinates(other);
}
template<typename T>
T retract(const T& origin, const Vector& delta) {
return origin.retract(delta);
}
template<typename T> template<typename T>
void print(const T& obj, const std::string& str) { void print(const T& obj, const std::string& str) {
obj.print(str); obj.print(str);
} }
template<typename T>
size_t getDimension(const T& obj) {
return obj.dim();
}
} }
template<class T> template<class T>
class GenericValue : public Value { class GenericValue : public Value {
public: public:
typedef T ValueType; typedef T type;
typedef GenericValue This;
protected: protected:
T value_; T value_;
public: public:
GenericValue() {}
GenericValue(const T& value) : value_(value) {} GenericValue(const T& value) : value_(value) {}
T& value() { return value_; }
const T& value() const { return value_; } const T& value() const { return value_; }
T& value() { return value_; }
virtual ~GenericValue() {} virtual ~GenericValue() {}
/**
* Create a duplicate object returned as a pointer to the generic Value interface.
* For the sake of performance, this function use singleton pool allocator instead of the normal heap allocator.
* The result must be deleted with Value::deallocate_, not with the 'delete' operator.
*/
virtual Value* clone_() const {
void *place = boost::singleton_pool<PoolTag, sizeof(This)>::malloc();
This* ptr = new(place) This(*this);
return ptr;
}
/**
* Destroy and deallocate this object, only if it was originally allocated using clone_().
*/
virtual void deallocate_() const {
this->~GenericValue(); // Virtual destructor cleans up the derived object
boost::singleton_pool<PoolTag, sizeof(This)>::free((void*)this); // Release memory from pool
}
/**
* Clone this value (normal clone on the heap, delete with 'delete' operator)
*/
virtual boost::shared_ptr<Value> clone() const {
return boost::make_shared<This>(*this);
}
/// equals implementing generic Value interface /// equals implementing generic Value interface
virtual bool equals_(const Value& p, double tol = 1e-9) const { virtual bool equals_(const Value& p, double tol = 1e-9) const {
// Cast the base class Value pointer to a templated generic class pointer // Cast the base class Value pointer to a templated generic class pointer
const This& genericValue2 = static_cast<const This&>(p); const GenericValue& genericValue2 = static_cast<const GenericValue&>(p);
// Return the result of using the equals traits for the derived class // Return the result of using the equals traits for the derived class
return traits::equals<T>(this->value_, genericValue2.value_, tol); return traits::equals<T>(this->value_, genericValue2.value_, tol);
}
/// Generic Value interface version of retract
virtual Value* retract_(const Vector& delta) const {
// Call retract on the derived class using the retract trait function
const T retractResult = traits::retract<T>(value_,delta);
// Create a Value pointer copy of the result
void* resultAsValuePlace = boost::singleton_pool<PoolTag, sizeof(This)>::malloc();
Value* resultAsValue = new(resultAsValuePlace) This(retractResult);
// Return the pointer to the Value base class
return resultAsValue;
}
/// Generic Value interface version of localCoordinates
virtual Vector localCoordinates_(const Value& value2) const {
// Cast the base class Value pointer to a templated generic class pointer
const This& genericValue2 = static_cast<const This&>(value2);
// Return the result of calling localCoordinates trait on the derived class
return traits::localCoordinates<T>(value_,genericValue2.value_);
} }
virtual void print(const std::string& str) const { virtual void print(const std::string& str) const {
traits::print<T>(value_,str); traits::print<T>(value_,str);
} }
virtual size_t dim() const {
return traits::getDimension<T>(value_); // need functional form here since the dimension may be dynamic
}
/// Assignment operator
virtual Value& operator=(const Value& rhs) {
// Cast the base class Value pointer to a derived class pointer
const This& derivedRhs = static_cast<const This&>(rhs);
// Do the assignment and return the result
this->value_ = derivedRhs.value_;
return *this;
}
/// Conversion to the derived class
operator const T& () const {
return value_;
}
/// Conversion to the derived class
operator T& () {
return value_;
}
protected: protected:
/// Assignment operator, protected because only the Value or DERIVED /// Assignment operator for this class not needed since GenricValue<T> is an abstract class
/// assignment operators should be used.
// DerivedValue<DERIVED>& operator=(const DerivedValue<DERIVED>& rhs) {
// // Nothing to do, do not call base class assignment operator
// return *this;
// }
private:
/// Fake Tag struct for singleton pool allocator. In fact, it is never used!
struct PoolTag { };
}; };
// define Value::cast here since now GenericValue has been declared // define Value::cast here since now GenericValue has been declared

View File

@ -67,12 +67,13 @@ struct is_manifold: public std::false_type {
}; };
// dimension, can return Eigen::Dynamic (-1) if not known at compile time // dimension, can return Eigen::Dynamic (-1) if not known at compile time
typedef std::integral_constant<int, Eigen::Dynamic> Dynamic;
template<typename T> template<typename T>
struct dimension; struct dimension : public Dynamic {}; //default to dynamic
/** /**
* zero<T>::value is intended to be the origin of a canonical coordinate system * zero<T>::value is intended to be the origin of a canonical coordinate system
* with canonical(t) == DefaultChart<T>(zero<T>::value).apply(t) * with canonical(t) == DefaultChart<T>::local(zero<T>::value, t)
* Below we provide the group identity as zero *in case* it is a group * Below we provide the group identity as zero *in case* it is a group
*/ */
template<typename T> struct zero: public identity<T> { template<typename T> struct zero: public identity<T> {
@ -112,8 +113,6 @@ struct is_manifold<Eigen::Matrix<double, M, N, Options> > : public std::true_typ
// TODO: Could be more sophisticated using Eigen traits and SFINAE? // TODO: Could be more sophisticated using Eigen traits and SFINAE?
typedef std::integral_constant<int, Eigen::Dynamic> Dynamic;
template<int Options> template<int Options>
struct dimension<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Options> > : public Dynamic { struct dimension<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Options> > : public Dynamic {
}; };
@ -141,62 +140,105 @@ struct zero<Eigen::Matrix<double, M, N, Options> > : public std::integral_consta
} }
}; };
template <typename T> struct is_chart : public std::false_type {};
} // \ namespace traits } // \ namespace traits
// Chart is a map from T -> vector, retract is its inverse // Chart is a map from T -> vector, retract is its inverse
template<typename T> template<typename T>
struct DefaultChart { struct DefaultChart {
BOOST_STATIC_ASSERT(traits::is_manifold<T>::value); //BOOST_STATIC_ASSERT(traits::is_manifold<T>::value);
typedef T type;
typedef Eigen::Matrix<double, traits::dimension<T>::value, 1> vector; typedef Eigen::Matrix<double, traits::dimension<T>::value, 1> vector;
T t_;
DefaultChart(const T& t) : static vector local(const T& origin, const T& other) {
t_(t) { return origin.localCoordinates(other);
} }
vector apply(const T& other) { static T retract(const T& origin, const vector& d) {
return t_.localCoordinates(other); return origin.retract(d);
} }
T retract(const vector& d) { static int getDimension(const T& origin) {
return t_.retract(d); return origin.dim();
} }
}; };
namespace traits {
// populate default traits
template <typename T> struct is_chart<DefaultChart<T> > : public std::true_type {};
template <typename T> struct dimension<DefaultChart<T> > : public dimension<T> {};
}
template<class C>
struct ChartConcept {
public:
typedef typename C::type type;
typedef typename C::vector vector;
BOOST_CONCEPT_USAGE(ChartConcept) {
// is_chart trait should be true
BOOST_STATIC_ASSERT((traits::is_chart<C>::value));
/**
* Returns Retraction update of val_
*/
type retract_ret = C::retract(val_, vec_);
/**
* Returns local coordinates of another object
*/
vec_ = C::local(val_, retract_ret);
// a way to get the dimension that is compatible with dynamically sized types
dim_ = C::getDimension(val_);
}
private:
type val_;
vector vec_;
int dim_;
};
/** /**
* Canonical<T>::value is a chart around zero<T>::value * CanonicalChart<Chart<T> > is a chart around zero<T>::value
* Canonical<T> is CanonicalChart<DefaultChart<T> >
* An example is Canonical<Rot3> * An example is Canonical<Rot3>
*/ */
template<typename T> struct Canonical { template<typename C> struct CanonicalChart {
typedef T type; BOOST_CONCEPT_ASSERT((ChartConcept<C>));
typedef typename DefaultChart<T>::vector vector;
DefaultChart<T> chart; typedef C Chart;
Canonical() : typedef typename Chart::type type;
chart(traits::zero<T>::value()) { typedef typename Chart::vector vector;
}
// Convert t of type T into canonical coordinates // Convert t of type T into canonical coordinates
vector apply(const T& t) { vector local(const type& t) {
return chart.apply(t); return Chart::local(traits::zero<type>::value(), t);
} }
// Convert back from canonical coordinates to T // Convert back from canonical coordinates to T
T retract(const vector& v) { type retract(const vector& v) {
return chart.retract(v); return Chart::retract(traits::zero<type>::value(), v);
} }
}; };
template <typename T> struct Canonical : public CanonicalChart<DefaultChart<T> > {};
// double // double
template<> template<>
struct DefaultChart<double> { struct DefaultChart<double> {
typedef double type;
typedef Eigen::Matrix<double, 1, 1> vector; typedef Eigen::Matrix<double, 1, 1> vector;
double t_;
DefaultChart(double t) : static vector local(double origin, double other) {
t_(t) {
}
vector apply(double other) {
vector d; vector d;
d << other - t_; d << other - origin;
return d; return d;
} }
double retract(const vector& d) { static double retract(double origin, const vector& d) {
return t_ + d[0]; return origin + d[0];
}
static const int getDimension(double) {
return 1;
} }
}; };
@ -204,22 +246,23 @@ struct DefaultChart<double> {
template<int M, int N, int Options> template<int M, int N, int Options>
struct DefaultChart<Eigen::Matrix<double, M, N, Options> > { struct DefaultChart<Eigen::Matrix<double, M, N, Options> > {
typedef Eigen::Matrix<double, M, N, Options> T; typedef Eigen::Matrix<double, M, N, Options> type;
typedef type T;
typedef Eigen::Matrix<double, traits::dimension<T>::value, 1> vector; typedef Eigen::Matrix<double, traits::dimension<T>::value, 1> vector;
BOOST_STATIC_ASSERT_MSG((M!=Eigen::Dynamic && N!=Eigen::Dynamic), BOOST_STATIC_ASSERT_MSG((M!=Eigen::Dynamic && N!=Eigen::Dynamic),
"DefaultChart has not been implemented yet for dynamically sized matrices"); "DefaultChart has not been implemented yet for dynamically sized matrices");
T t_; static vector local(const T& origin, const T& other) {
DefaultChart(const T& t) : T diff = other - origin;
t_(t) {
}
vector apply(const T& other) {
T diff = other - t_;
Eigen::Map<vector> map(diff.data()); Eigen::Map<vector> map(diff.data());
return vector(map); return vector(map);
// Why is this function not : return other - origin; ?? what is the Eigen::Map used for?
} }
T retract(const vector& d) { static T retract(const T& origin, const vector& d) {
Eigen::Map<const T> map(d.data()); Eigen::Map<const T> map(d.data());
return t_ + map; return origin + map;
}
static int getDimension(const T&origin) {
return origin.rows()*origin.cols();
} }
}; };
@ -227,16 +270,16 @@ struct DefaultChart<Eigen::Matrix<double, M, N, Options> > {
template<> template<>
struct DefaultChart<Vector> { struct DefaultChart<Vector> {
typedef Vector T; typedef Vector T;
typedef T type;
typedef T vector; typedef T vector;
T t_; static vector local(const T& origin, const T& other) {
DefaultChart(const T& t) : return other - origin;
t_(t) {
} }
vector apply(const T& other) { static T retract(const T& origin, const vector& d) {
return other - t_; return origin + d;
} }
T retract(const vector& d) { static int getDimension(const T& origin) {
return t_ + d; return origin.size();
} }
}; };

View File

@ -120,12 +120,17 @@ namespace gtsam {
virtual Vector localCoordinates_(const Value& value) const = 0; virtual Vector localCoordinates_(const Value& value) const = 0;
/** Assignment operator */ /** Assignment operator */
virtual Value& operator=(const Value& rhs) = 0; virtual Value& operator=(const Value& rhs) {
//needs a empty definition so recursion in implicit derived assignment operators work
return *this;
}
/** Cast to known ValueType */ /** Cast to known ValueType */
template<typename ValueType> template<typename ValueType>
const ValueType& cast() const; const ValueType& cast() const;
template<typename Chart>
const Chart& getChart() const;
/** Virutal destructor */ /** Virutal destructor */
virtual ~Value() {} virtual ~Value() {}

View File

@ -73,7 +73,7 @@ Vector numericalGradient(boost::function<double(const X&)> h, const X& x,
typedef typename ChartX::vector TangentX; typedef typename ChartX::vector TangentX;
// get chart at x // get chart at x
ChartX chartX(x); ChartX chartX;
// Prepare a tangent vector to perturb x with, only works for fixed size // Prepare a tangent vector to perturb x with, only works for fixed size
TangentX d; TangentX d;
@ -82,9 +82,9 @@ Vector numericalGradient(boost::function<double(const X&)> h, const X& x,
Vector g = zero(N); // Can be fixed size Vector g = zero(N); // Can be fixed size
for (int j = 0; j < N; j++) { for (int j = 0; j < N; j++) {
d(j) = delta; d(j) = delta;
double hxplus = h(chartX.retract(d)); double hxplus = h(chartX.retract(x, d));
d(j) = -delta; d(j) = -delta;
double hxmin = h(chartX.retract(d)); double hxmin = h(chartX.retract(x, d));
d(j) = 0; d(j) = 0;
g(j) = (hxplus - hxmin) * factor; g(j) = (hxplus - hxmin) * factor;
} }
@ -121,14 +121,14 @@ Matrix numericalDerivative11(boost::function<Y(const X&)> h, const X& x,
// get value at x, and corresponding chart // get value at x, and corresponding chart
Y hx = h(x); Y hx = h(x);
ChartY chartY(hx); ChartY chartY;
// Bit of a hack for now to find number of rows // Bit of a hack for now to find number of rows
TangentY zeroY = chartY.apply(hx); TangentY zeroY = chartY.local(hx, hx);
size_t m = zeroY.size(); size_t m = zeroY.size();
// get chart at x // get chart at x
ChartX chartX(x); ChartX chartX;
// Prepare a tangent vector to perturb x with, only works for fixed size // Prepare a tangent vector to perturb x with, only works for fixed size
TangentX dx; TangentX dx;
@ -139,9 +139,9 @@ Matrix numericalDerivative11(boost::function<Y(const X&)> h, const X& x,
double factor = 1.0 / (2.0 * delta); double factor = 1.0 / (2.0 * delta);
for (int j = 0; j < N; j++) { for (int j = 0; j < N; j++) {
dx(j) = delta; dx(j) = delta;
TangentY dy1 = chartY.apply(h(chartX.retract(dx))); TangentY dy1 = chartY.local(hx, h(chartX.retract(x, dx)));
dx(j) = -delta; dx(j) = -delta;
TangentY dy2 = chartY.apply(h(chartX.retract(dx))); TangentY dy2 = chartY.local(hx, h(chartX.retract(x, dx)));
dx(j) = 0; dx(j) = 0;
H.col(j) << (dy1 - dy2) * factor; H.col(j) << (dy1 - dy2) * factor;
} }

View File

@ -220,5 +220,24 @@ private:
} }
/// @} /// @}
};} };
// Define GTSAM traits
namespace traits {
template<>
struct is_group<CalibratedCamera> : public std::true_type {
};
template<>
struct is_manifold<CalibratedCamera> : public std::true_type {
};
template<>
struct dimension<CalibratedCamera> : public std::integral_constant<int, 6> {
};
}
}

View File

@ -142,7 +142,7 @@ TEST( Rot3, retract)
// test Canonical coordinates // test Canonical coordinates
Canonical<Rot3> chart; Canonical<Rot3> chart;
Vector v2 = chart.apply(R); Vector v2 = chart.local(R);
CHECK(assert_equal(R, chart.retract(v2))); CHECK(assert_equal(R, chart.retract(v2)));
} }

View File

@ -65,19 +65,19 @@ namespace gtsam {
}; };
// partial specialized version for ValueType == Value // partial specialized version for ValueType == Value
template<class CastedKeyValuePairType, class KeyValuePairType> template<class CastedKeyValuePairType, class KeyValuePairType>
struct ValuesCastHelper<Value,CastedKeyValuePairType,KeyValuePairType> { struct ValuesCastHelper<Value, CastedKeyValuePairType, KeyValuePairType> {
static CastedKeyValuePairType cast(KeyValuePairType key_value) { static CastedKeyValuePairType cast(KeyValuePairType key_value) {
// Static cast because we already checked the type during filtering // Static cast because we already checked the type during filtering
// in this case the casted and keyvalue pair are essentially the same type (key,Value&) so perhaps this could be done with just a cast of the key_value? // in this case the casted and keyvalue pair are essentially the same type (key, Value&) so perhaps this could be done with just a cast of the key_value?
return CastedKeyValuePairType(key_value.key, key_value.value); return CastedKeyValuePairType(key_value.key, key_value.value);
} }
}; };
// partial specialized version for ValueType == Value // partial specialized version for ValueType == Value
template<class CastedKeyValuePairType, class KeyValuePairType> template<class CastedKeyValuePairType, class KeyValuePairType>
struct ValuesCastHelper<const Value,CastedKeyValuePairType,KeyValuePairType> { struct ValuesCastHelper<const Value, CastedKeyValuePairType, KeyValuePairType> {
static CastedKeyValuePairType cast(KeyValuePairType key_value) { static CastedKeyValuePairType cast(KeyValuePairType key_value) {
// Static cast because we already checked the type during filtering // Static cast because we already checked the type during filtering
// in this case the casted and keyvalue pair are essentially the same type (key,Value&) so perhaps this could be done with just a cast of the key_value? // in this case the casted and keyvalue pair are essentially the same type (key, Value&) so perhaps this could be done with just a cast of the key_value?
return CastedKeyValuePairType(key_value.key, key_value.value); return CastedKeyValuePairType(key_value.key, key_value.value);
} }
}; };
@ -262,11 +262,11 @@ namespace gtsam {
throw ValuesKeyDoesNotExist("retrieve", j); throw ValuesKeyDoesNotExist("retrieve", j);
// Check the type and throw exception if incorrect // Check the type and throw exception if incorrect
if(typeid(*item->second) != typeid(GenericValue<ValueType>)) try {
return dynamic_cast<const GenericValue<ValueType>&>(*item->second).value();
} catch (std::bad_cast &) {
throw ValuesIncorrectType(j, typeid(*item->second), typeid(ValueType)); throw ValuesIncorrectType(j, typeid(*item->second), typeid(ValueType));
}
// We have already checked the type, so do a "blind" static_cast, not dynamic_cast
return static_cast<const GenericValue<ValueType>&>(*item->second).value();
} }
/* ************************************************************************* */ /* ************************************************************************* */
@ -276,25 +276,49 @@ namespace gtsam {
KeyValueMap::const_iterator item = values_.find(j); KeyValueMap::const_iterator item = values_.find(j);
if(item != values_.end()) { if(item != values_.end()) {
// Check the type and throw exception if incorrect // dynamic cast the type and throw exception if incorrect
if(typeid(*item->second) != typeid(GenericValue<ValueType>)) try {
return dynamic_cast<const GenericValue<ValueType>&>(*item->second).value();
} catch (std::bad_cast &) {
throw ValuesIncorrectType(j, typeid(*item->second), typeid(ValueType)); throw ValuesIncorrectType(j, typeid(*item->second), typeid(ValueType));
}
// We have already checked the type, so do a "blind" static_cast, not dynamic_cast
return static_cast<const GenericValue<ValueType>&>(*item->second).value();
} else { } else {
return boost::none; return boost::none;
} }
} }
/* ************************************************************************* */ /* ************************************************************************* */
// insert a plain value using the default chart
template<typename ValueType> template<typename ValueType>
void Values::insert(Key j, const ValueType& val) { void Values::insert(Key j, const ValueType& val) {
insert(j, static_cast<const Value&>(GenericValue<ValueType>(val))); insert(j, static_cast<const Value&>(ChartValue<ValueType, DefaultChart<ValueType> >(val)));
} }
// insert with custom chart type
template<typename ValueType, typename Chart>
void Values::insert(Key j, const ValueType& val) {
insert(j, static_cast<const Value&>(ChartValue<ValueType, Chart>(val)));
}
// overloaded insert with chart initializer
template<typename ValueType, typename Chart, typename ChartInit>
void Values::insert(Key j, const ValueType& val, ChartInit chart) {
insert(j, static_cast<const Value&>(ChartValue<ValueType, Chart>(val, chart)));
}
// update with default chart
template <typename ValueType> template <typename ValueType>
void Values::update(Key j, const ValueType& val) { void Values::update(Key j, const ValueType& val) {
update(j, static_cast<const Value&>(GenericValue<ValueType>(val))); update(j, static_cast<const Value&>(ChartValue<ValueType, DefaultChart<ValueType> >(val)));
} }
// update with custom chart
template <typename ValueType, typename Chart>
void Values::update(Key j, const ValueType& val) {
update(j, static_cast<const Value&>(ChartValue<ValueType, Chart>(val)));
}
// update with chart initializer, /todo: perhaps there is a way to init chart from old value...
template<typename ValueType, typename Chart, typename ChartInit>
void Values::update(Key j, const ValueType& val, ChartInit chart) {
update(j, static_cast<const Value&>(ChartValue<ValueType, Chart>(val, chart)));
}
} }

View File

@ -44,8 +44,7 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include <gtsam/base/Value.h> #include <gtsam/base/ChartValue.h>
#include <gtsam/base/GenericValue.h>
#include <gtsam/base/FastMap.h> #include <gtsam/base/FastMap.h>
#include <gtsam/inference/Key.h> #include <gtsam/inference/Key.h>
@ -249,15 +248,22 @@ namespace gtsam {
/** Add a variable with the given j, throws KeyAlreadyExists<J> if j is already present */ /** Add a variable with the given j, throws KeyAlreadyExists<J> if j is already present */
void insert(Key j, const Value& val); void insert(Key j, const Value& val);
/** Templated verion to add a variable with the given j,
* throws KeyAlreadyExists<J> if j is already present
* will wrap the val into a GenericValue<ValueType> to insert*/
template <typename ValueType> void insert(Key j, const ValueType& val);
/** Add a set of variables, throws KeyAlreadyExists<J> if a key is already present */ /** Add a set of variables, throws KeyAlreadyExists<J> if a key is already present */
void insert(const Values& values); void insert(const Values& values);
/** Templated verion to add a variable with the given j,
* throws KeyAlreadyExists<J> if j is already present
* if no chart is specified, the DefaultChart<ValueType> is used
*/
template <typename ValueType>
void insert(Key j, const ValueType& val);
template <typename ValueType, typename Chart>
void insert(Key j, const ValueType& val);
// overloaded insert version that also specifies a chart initializer
template <typename ValueType, typename Chart, typename ChartInit>
void insert(Key j, const ValueType& val, ChartInit chart);
/** insert that mimics the STL map insert - if the value already exists, the map is not modified /** insert that mimics the STL map insert - if the value already exists, the map is not modified
* and an iterator to the existing value is returned, along with 'false'. If the value did not * and an iterator to the existing value is returned, along with 'false'. If the value did not
* exist, it is inserted and an iterator pointing to the new element, along with 'true', is * exist, it is inserted and an iterator pointing to the new element, along with 'true', is
@ -266,7 +272,17 @@ namespace gtsam {
/** single element change of existing element */ /** single element change of existing element */
void update(Key j, const Value& val); void update(Key j, const Value& val);
template <typename T> void update(Key j, const T& val);
/** Templated verion to update a variable with the given j,
* throws KeyAlreadyExists<J> if j is already present
* if no chart is specified, the DefaultChart<ValueType> is used
*/
template <typename T>
void update(Key j, const T& val);
template <typename T, typename Chart>
void update(Key j, const T& val);
template <typename T, typename Chart, typename ChartInit>
void update(Key j, const T& val, ChartInit chart);
/** update the current available values without adding new ones */ /** update the current available values without adding new ones */
void update(const Values& values); void update(const Values& values);
@ -377,9 +393,9 @@ namespace gtsam {
// supplied \c filter function. // supplied \c filter function.
template<class ValueType> template<class ValueType>
static bool filterHelper(const boost::function<bool(Key)> filter, const ConstKeyValuePair& key_value) { static bool filterHelper(const boost::function<bool(Key)> filter, const ConstKeyValuePair& key_value) {
BOOST_STATIC_ASSERT((!std::is_same<ValueType,Value>::value)); BOOST_STATIC_ASSERT((!std::is_same<ValueType, Value>::value));
// Filter and check the type // Filter and check the type
return filter(key_value.key) && (typeid(GenericValue<ValueType>) == typeid(key_value.value) ); return filter(key_value.key) && (dynamic_cast<const GenericValue<ValueType>*>(&key_value.value));
} }
/** Serialization function */ /** Serialization function */

View File

@ -38,7 +38,7 @@ static double inf = std::numeric_limits<double>::infinity();
using symbol_shorthand::X; using symbol_shorthand::X;
using symbol_shorthand::L; using symbol_shorthand::L;
const Symbol key1('v',1), key2('v',2), key3('v',3), key4('v',4); const Symbol key1('v', 1), key2('v', 2), key3('v', 3), key4('v', 4);
class TestValueData { class TestValueData {
@ -61,16 +61,25 @@ public:
Vector localCoordinates(const TestValue&) const { return Vector(); } Vector localCoordinates(const TestValue&) const { return Vector(); }
}; };
namespace gtsam {
namespace traits {
template <>
struct is_manifold<TestValue> : public std::true_type {};
template <>
struct dimension<TestValue> : public std::integral_constant<int, 0> {};
}
}
/* ************************************************************************* */ /* ************************************************************************* */
TEST( Values, equals1 ) TEST( Values, equals1 )
{ {
Values expected; Values expected;
LieVector v((Vector(3) << 5.0, 6.0, 7.0)); LieVector v((Vector(3) << 5.0, 6.0, 7.0));
expected.insert(key1,v); expected.insert(key1, v);
Values actual; Values actual;
actual.insert(key1,v); actual.insert(key1, v);
CHECK(assert_equal(expected,actual)); CHECK(assert_equal(expected, actual));
} }
/* ************************************************************************* */ /* ************************************************************************* */
@ -260,7 +269,7 @@ TEST(Values, expmap_d)
CHECK(config0.equals(config0)); CHECK(config0.equals(config0));
Values poseconfig; Values poseconfig;
poseconfig.insert(key1, Pose2(1,2,3)); poseconfig.insert(key1, Pose2(1, 2, 3));
poseconfig.insert(key2, Pose2(0.3, 0.4, 0.5)); poseconfig.insert(key2, Pose2(0.3, 0.4, 0.5));
CHECK(equal(config0, config0)); CHECK(equal(config0, config0));
@ -330,7 +339,7 @@ TEST(Values, update)
Values expected; Values expected;
expected.insert(key1, LieVector((Vector(1) << -1.))); expected.insert(key1, LieVector((Vector(1) << -1.)));
expected.insert(key2, LieVector((Vector(1) << -2.))); expected.insert(key2, LieVector((Vector(1) << -2.)));
CHECK(assert_equal(expected,config0)); CHECK(assert_equal(expected, config0));
} }
/* ************************************************************************* */ /* ************************************************************************* */
@ -410,9 +419,9 @@ TEST(Values, Symbol_filter) {
Values values; Values values;
values.insert(X(0), pose0); values.insert(X(0), pose0);
values.insert(Symbol('y',1), pose1); values.insert(Symbol('y', 1), pose1);
values.insert(X(2), pose2); values.insert(X(2), pose2);
values.insert(Symbol('y',3), pose3); values.insert(Symbol('y', 3), pose3);
int i = 0; int i = 0;
BOOST_FOREACH(const Values::Filtered<Value>::KeyValuePair& key_value, values.filter(Symbol::ChrTest('y'))) { BOOST_FOREACH(const Values::Filtered<Value>::KeyValuePair& key_value, values.filter(Symbol::ChrTest('y'))) {

View File

@ -59,8 +59,8 @@ public:
using ceres::internal::AutoDiff; using ceres::internal::AutoDiff;
// Make arguments // Make arguments
Vector1 v1 = chart1.apply(a1); Vector1 v1 = chart1.local(a1);
Vector2 v2 = chart2.apply(a2); Vector2 v2 = chart2.local(a2);
bool success; bool success;
VectorT result; VectorT result;

View File

@ -293,8 +293,9 @@ public:
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
/// Leaf Expression /// Leaf Expression
template<class T> template<class T, class Chart=DefaultChart<T> >
class LeafExpression: public ExpressionNode<T> { class LeafExpression: public ExpressionNode<T> {
typedef ChartValue<T,Chart> value_type; // perhaps this can be something else like a std::pair<T,Chart> ??
/// The key into values /// The key into values
Key key_; Key key_;
@ -303,6 +304,53 @@ class LeafExpression: public ExpressionNode<T> {
LeafExpression(Key key) : LeafExpression(Key key) :
key_(key) { key_(key) {
} }
// todo: do we need a virtual destructor here too?
friend class Expression<value_type> ;
public:
/// Return keys that play in this expression
virtual std::set<Key> keys() const {
std::set<Key> keys;
keys.insert(key_);
return keys;
}
/// Return dimensions for each argument
virtual void dims(std::map<Key, size_t>& map) const {
// get dimension from the chart; only works for fixed dimension charts
map[key_] = traits::dimension<Chart>::value;
}
/// Return value
virtual const value_type& value(const Values& values) const {
return dynamic_cast<const value_type&>(values.at(key_));
}
/// Construct an execution trace for reverse AD
virtual const value_type& traceExecution(const Values& values, ExecutionTrace<value_type>& trace,
char* raw) const {
trace.setLeaf(key_);
return dynamic_cast<const value_type&>(values.at(key_));
}
};
//-----------------------------------------------------------------------------
/// Leaf Expression, if no chart is given, assume default chart and value_type is just the plain value
template<typename T>
class LeafExpression<T, DefaultChart<T> >: public ExpressionNode<T> {
typedef T value_type;
/// The key into values
Key key_;
/// Constructor with a single key
LeafExpression(Key key) :
key_(key) {
}
// todo: do we need a virtual destructor here too?
friend class Expression<T> ; friend class Expression<T> ;
@ -374,7 +422,7 @@ struct Jacobian {
/// meta-function to generate JacobianTA optional reference /// meta-function to generate JacobianTA optional reference
template<class T, class A> template<class T, class A>
struct Optional { struct OptionalJacobian {
typedef Eigen::Matrix<double, traits::dimension<T>::value, typedef Eigen::Matrix<double, traits::dimension<T>::value,
traits::dimension<A>::value> Jacobian; traits::dimension<A>::value> Jacobian;
typedef boost::optional<Jacobian&> type; typedef boost::optional<Jacobian&> type;
@ -504,7 +552,7 @@ struct FunctionalNode {
// Argument types and derived, note these are base 0 ! // Argument types and derived, note these are base 0 !
typedef TYPES Arguments; typedef TYPES Arguments;
typedef typename boost::mpl::transform<TYPES, Jacobian<T, MPL::_1> >::type Jacobians; typedef typename boost::mpl::transform<TYPES, Jacobian<T, MPL::_1> >::type Jacobians;
typedef typename boost::mpl::transform<TYPES, Optional<T, MPL::_1> >::type Optionals; typedef typename boost::mpl::transform<TYPES, OptionalJacobian<T, MPL::_1> >::type Optionals;
/// Reset expression shared pointer /// Reset expression shared pointer
template<class A, size_t N> template<class A, size_t N>
@ -559,7 +607,7 @@ class UnaryExpression: public FunctionalNode<T, boost::mpl::vector<A1> >::type {
public: public:
typedef boost::function<T(const A1&, typename Optional<T, A1>::type)> Function; typedef boost::function<T(const A1&, typename OptionalJacobian<T, A1>::type)> Function;
typedef typename FunctionalNode<T, boost::mpl::vector<A1> >::type Base; typedef typename FunctionalNode<T, boost::mpl::vector<A1> >::type Base;
typedef typename Base::Record Record; typedef typename Base::Record Record;
@ -604,8 +652,8 @@ class BinaryExpression: public FunctionalNode<T, boost::mpl::vector<A1, A2> >::t
public: public:
typedef boost::function< typedef boost::function<
T(const A1&, const A2&, typename Optional<T, A1>::type, T(const A1&, const A2&, typename OptionalJacobian<T, A1>::type,
typename Optional<T, A2>::type)> Function; typename OptionalJacobian<T, A2>::type)> Function;
typedef typename FunctionalNode<T, boost::mpl::vector<A1, A2> >::type Base; typedef typename FunctionalNode<T, boost::mpl::vector<A1, A2> >::type Base;
typedef typename Base::Record Record; typedef typename Base::Record Record;
@ -658,8 +706,8 @@ class TernaryExpression: public FunctionalNode<T, boost::mpl::vector<A1, A2, A3>
public: public:
typedef boost::function< typedef boost::function<
T(const A1&, const A2&, const A3&, typename Optional<T, A1>::type, T(const A1&, const A2&, const A3&, typename OptionalJacobian<T, A1>::type,
typename Optional<T, A2>::type, typename Optional<T, A3>::type)> Function; typename OptionalJacobian<T, A2>::type, typename OptionalJacobian<T, A3>::type)> Function;
typedef typename FunctionalNode<T, boost::mpl::vector<A1, A2, A3> >::type Base; typedef typename FunctionalNode<T, boost::mpl::vector<A1, A2, A3> >::type Base;
typedef typename Base::Record Record; typedef typename Base::Record Record;

View File

@ -61,7 +61,7 @@ public:
/// Construct a nullary method expression /// Construct a nullary method expression
template<typename A> template<typename A>
Expression(const Expression<A>& expression, Expression(const Expression<A>& expression,
T (A::*method)(typename Optional<T, A>::type) const) : T (A::*method)(typename OptionalJacobian<T, A>::type) const) :
root_(new UnaryExpression<T, A>(boost::bind(method, _1, _2), expression)) { root_(new UnaryExpression<T, A>(boost::bind(method, _1, _2), expression)) {
} }
@ -75,8 +75,8 @@ public:
/// Construct a unary method expression /// Construct a unary method expression
template<typename A1, typename A2> template<typename A1, typename A2>
Expression(const Expression<A1>& expression1, Expression(const Expression<A1>& expression1,
T (A1::*method)(const A2&, typename Optional<T, A1>::type, T (A1::*method)(const A2&, typename OptionalJacobian<T, A1>::type,
typename Optional<T, A2>::type) const, typename OptionalJacobian<T, A2>::type) const,
const Expression<A2>& expression2) : const Expression<A2>& expression2) :
root_( root_(
new BinaryExpression<T, A1, A2>(boost::bind(method, _1, _2, _3, _4), new BinaryExpression<T, A1, A2>(boost::bind(method, _1, _2, _3, _4),

View File

@ -201,7 +201,7 @@ Pose3 pose;
// Now, let's create the optional Jacobian arguments // Now, let's create the optional Jacobian arguments
typedef Point3 T; typedef Point3 T;
typedef boost::mpl::vector<Pose3, Point3> TYPES; typedef boost::mpl::vector<Pose3, Point3> TYPES;
typedef boost::mpl::transform<TYPES, Optional<T, MPL::_1> >::type Optionals; typedef boost::mpl::transform<TYPES, OptionalJacobian<T, MPL::_1> >::type Optionals;
// Unfortunately this is moot: we need a pointer to a function with the // Unfortunately this is moot: we need a pointer to a function with the
// optional derivatives; I don't see a way of calling a function that we // optional derivatives; I don't see a way of calling a function that we

View File

@ -17,6 +17,7 @@
* @brief unit tests for Block Automatic Differentiation * @brief unit tests for Block Automatic Differentiation
*/ */
#include <gtsam/geometry/Point2.h>
#include <gtsam/geometry/PinholeCamera.h> #include <gtsam/geometry/PinholeCamera.h>
#include <gtsam/geometry/Pose3.h> #include <gtsam/geometry/Pose3.h>
#include <gtsam/geometry/Cal3_S2.h> #include <gtsam/geometry/Cal3_S2.h>
@ -65,48 +66,48 @@ TEST(Manifold, _dimension) {
// charts // charts
TEST(Manifold, DefaultChart) { TEST(Manifold, DefaultChart) {
DefaultChart<Point2> chart1(Point2(0, 0)); DefaultChart<Point2> chart1;
EXPECT(chart1.apply(Point2(1,0))==Vector2(1,0)); EXPECT(chart1.local(Point2(0, 0), Point2(1, 0)) == Vector2(1, 0));
EXPECT(chart1.retract(Vector2(1,0))==Point2(1,0)); EXPECT(chart1.retract(Point2(0, 0), Vector2(1, 0)) == Point2(1, 0));
Vector v2(2); Vector v2(2);
v2 << 1, 0; v2 << 1, 0;
DefaultChart<Vector2> chart2(Vector2(0, 0)); DefaultChart<Vector2> chart2;
EXPECT(assert_equal(v2,chart2.apply(Vector2(1,0)))); EXPECT(assert_equal(v2, chart2.local(Vector2(0, 0), Vector2(1, 0))));
EXPECT(chart2.retract(v2)==Vector2(1,0)); EXPECT(chart2.retract(Vector2(0, 0), v2) == Vector2(1, 0));
DefaultChart<double> chart3(0); DefaultChart<double> chart3;
Vector v1(1); Vector v1(1);
v1 << 1; v1 << 1;
EXPECT(assert_equal(v1,chart3.apply(1))); EXPECT(assert_equal(v1, chart3.local(0, 1)));
EXPECT_DOUBLES_EQUAL(chart3.retract(v1), 1, 1e-9); EXPECT_DOUBLES_EQUAL(chart3.retract(0, v1), 1, 1e-9);
// Dynamic does not work yet ! // Dynamic does not work yet !
Vector z = zero(2), v(2); Vector z = zero(2), v(2);
v << 1, 0; v << 1, 0;
DefaultChart<Vector> chart4(z); DefaultChart<Vector> chart4;
EXPECT(assert_equal(chart4.apply(v),v)); EXPECT(assert_equal(chart4.local(z, v), v));
EXPECT(assert_equal(chart4.retract(v),v)); EXPECT(assert_equal(chart4.retract(z, v), v));
Vector v3(3); Vector v3(3);
v3 << 1, 1, 1; v3 << 1, 1, 1;
Rot3 I = Rot3::identity(); Rot3 I = Rot3::identity();
Rot3 R = I.retract(v3); Rot3 R = I.retract(v3);
DefaultChart<Rot3> chart5(I); DefaultChart<Rot3> chart5;
EXPECT(assert_equal(v3,chart5.apply(R))); EXPECT(assert_equal(v3, chart5.local(I, R)));
EXPECT(assert_equal(chart5.retract(v3),R)); EXPECT(assert_equal(chart5.retract(I, v3), R));
// Check zero vector // Check zero vector
DefaultChart<Rot3> chart6(R); DefaultChart<Rot3> chart6;
EXPECT(assert_equal(zero(3),chart6.apply(R))); EXPECT(assert_equal(zero(3), chart6.local(R, R)));
} }
/* ************************************************************************* */ /* ************************************************************************* */
// zero // zero
TEST(Manifold, _zero) { TEST(Manifold, _zero) {
EXPECT(assert_equal(Pose3(),traits::zero<Pose3>::value())); EXPECT(assert_equal(Pose3(), traits::zero<Pose3>::value()));
Cal3Bundler cal(0, 0, 0); Cal3Bundler cal(0, 0, 0);
EXPECT(assert_equal(cal,traits::zero<Cal3Bundler>::value())); EXPECT(assert_equal(cal, traits::zero<Cal3Bundler>::value()));
EXPECT(assert_equal(Camera(Pose3(),cal),traits::zero<Camera>::value())); EXPECT(assert_equal(Camera(Pose3(), cal), traits::zero<Camera>::value()));
} }
/* ************************************************************************* */ /* ************************************************************************* */
@ -114,48 +115,48 @@ TEST(Manifold, _zero) {
TEST(Manifold, Canonical) { TEST(Manifold, Canonical) {
Canonical<Point2> chart1; Canonical<Point2> chart1;
EXPECT(chart1.apply(Point2(1,0))==Vector2(1,0)); EXPECT(chart1.local(Point2(1, 0))==Vector2(1, 0));
EXPECT(chart1.retract(Vector2(1,0))==Point2(1,0)); EXPECT(chart1.retract(Vector2(1, 0))==Point2(1, 0));
Vector v2(2); Vector v2(2);
v2 << 1, 0; v2 << 1, 0;
Canonical<Vector2> chart2; Canonical<Vector2> chart2;
EXPECT(assert_equal(v2,chart2.apply(Vector2(1,0)))); EXPECT(assert_equal(v2, chart2.local(Vector2(1, 0))));
EXPECT(chart2.retract(v2)==Vector2(1,0)); EXPECT(chart2.retract(v2)==Vector2(1, 0));
Canonical<double> chart3; Canonical<double> chart3;
Eigen::Matrix<double, 1, 1> v1; Eigen::Matrix<double, 1, 1> v1;
v1 << 1; v1 << 1;
EXPECT(chart3.apply(1)==v1); EXPECT(chart3.local(1)==v1);
EXPECT_DOUBLES_EQUAL(chart3.retract(v1), 1, 1e-9); EXPECT_DOUBLES_EQUAL(chart3.retract(v1), 1, 1e-9);
Canonical<Point3> chart4; Canonical<Point3> chart4;
Point3 point(1, 2, 3); Point3 point(1, 2, 3);
Vector v3(3); Vector v3(3);
v3 << 1, 2, 3; v3 << 1, 2, 3;
EXPECT(assert_equal(v3,chart4.apply(point))); EXPECT(assert_equal(v3, chart4.local(point)));
EXPECT(assert_equal(chart4.retract(v3),point)); EXPECT(assert_equal(chart4.retract(v3), point));
Canonical<Pose3> chart5; Canonical<Pose3> chart5;
Pose3 pose(Rot3::identity(), point); Pose3 pose(Rot3::identity(), point);
Vector v6(6); Vector v6(6);
v6 << 0, 0, 0, 1, 2, 3; v6 << 0, 0, 0, 1, 2, 3;
EXPECT(assert_equal(v6,chart5.apply(pose))); EXPECT(assert_equal(v6, chart5.local(pose)));
EXPECT(assert_equal(chart5.retract(v6),pose)); EXPECT(assert_equal(chart5.retract(v6), pose));
Canonical<Camera> chart6; Canonical<Camera> chart6;
Cal3Bundler cal0(0, 0, 0); Cal3Bundler cal0(0, 0, 0);
Camera camera(Pose3(), cal0); Camera camera(Pose3(), cal0);
Vector z9 = Vector9::Zero(); Vector z9 = Vector9::Zero();
EXPECT(assert_equal(z9,chart6.apply(camera))); EXPECT(assert_equal(z9, chart6.local(camera)));
EXPECT(assert_equal(chart6.retract(z9),camera)); EXPECT(assert_equal(chart6.retract(z9), camera));
Cal3Bundler cal; // Note !! Cal3Bundler() != zero<Cal3Bundler>::value() Cal3Bundler cal; // Note !! Cal3Bundler() != zero<Cal3Bundler>::value()
Camera camera2(pose, cal); Camera camera2(pose, cal);
Vector v9(9); Vector v9(9);
v9 << 0, 0, 0, 1, 2, 3, 1, 0, 0; v9 << 0, 0, 0, 1, 2, 3, 1, 0, 0;
EXPECT(assert_equal(v9,chart6.apply(camera2))); EXPECT(assert_equal(v9, chart6.local(camera2)));
EXPECT(assert_equal(chart6.retract(v9),camera2)); EXPECT(assert_equal(chart6.retract(v9), camera2));
} }
/* ************************************************************************* */ /* ************************************************************************* */