The great collapse: instead of two recursively defined classes, there is now only one. The Record class is now a (recursive) inner class.
parent
bc9e11f43c
commit
da0e5fe52f
|
@ -38,6 +38,9 @@ struct TestBinaryExpression;
|
|||
|
||||
namespace MPL = boost::mpl::placeholders;
|
||||
|
||||
class ExpressionFactorBinaryTest;
|
||||
// Forward declare for testing
|
||||
|
||||
namespace gtsam {
|
||||
|
||||
template<typename T>
|
||||
|
@ -421,84 +424,6 @@ public:
|
|||
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// meta-function to generate fixed-size JacobianTA type
|
||||
template<class T, class A>
|
||||
struct Jacobian {
|
||||
typedef Eigen::Matrix<double, T::dimension, A::dimension> type;
|
||||
typedef boost::optional<type&> optional;
|
||||
};
|
||||
|
||||
/**
|
||||
* Building block for Recursive Record Class
|
||||
* Records the evaluation of a single argument in a functional expression
|
||||
* The integer argument N is to guarantee a unique type signature,
|
||||
* so we are guaranteed to be able to extract their values by static cast.
|
||||
*/
|
||||
template<class T, class A, size_t N>
|
||||
struct JacobianTrace {
|
||||
ExecutionTrace<A> trace;
|
||||
typename Jacobian<T, A>::type dTdA;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursive Record Class for Functional Expressions
|
||||
*/
|
||||
template<class T, class A, class Base>
|
||||
struct GenerateRecord: JacobianTrace<T, A, Base::N + 1>, Base {
|
||||
|
||||
typedef T return_type;
|
||||
static size_t const N = Base::N + 1;
|
||||
typedef JacobianTrace<T, A, N> This;
|
||||
|
||||
/// Print to std::cout
|
||||
virtual void print(const std::string& indent) const {
|
||||
Base::print(indent);
|
||||
static const Eigen::IOFormat matlab(0, 1, " ", "; ", "", "", "[", "]");
|
||||
std::cout << This::dTdA.format(matlab) << std::endl;
|
||||
This::trace.print(indent);
|
||||
}
|
||||
|
||||
/// Start the reverse AD process
|
||||
virtual void startReverseAD(JacobianMap& jacobians) const {
|
||||
Base::startReverseAD(jacobians);
|
||||
Select<T::dimension, A>::reverseAD(This::trace, This::dTdA, jacobians);
|
||||
}
|
||||
|
||||
/// Given df/dT, multiply in dT/dA and continue reverse AD process
|
||||
virtual void reverseAD(const Matrix& dFdT, JacobianMap& jacobians) const {
|
||||
Base::reverseAD(dFdT, jacobians);
|
||||
This::trace.reverseAD(dFdT * This::dTdA, jacobians);
|
||||
}
|
||||
|
||||
/// Version specialized to 2-dimensional output
|
||||
typedef Eigen::Matrix<double, 2, T::dimension> Jacobian2T;
|
||||
virtual void reverseAD2(const Jacobian2T& dFdT,
|
||||
JacobianMap& jacobians) const {
|
||||
Base::reverseAD2(dFdT, jacobians);
|
||||
This::trace.reverseAD2(dFdT * This::dTdA, jacobians);
|
||||
}
|
||||
};
|
||||
|
||||
/// Recursive Record class Generator
|
||||
template<class T, class TYPES>
|
||||
struct Record: public boost::mpl::fold<TYPES, CallRecord<T::dimension>,
|
||||
GenerateRecord<T, MPL::_2, MPL::_1> >::type {
|
||||
|
||||
/// Access Trace
|
||||
template<class A, size_t N>
|
||||
ExecutionTrace<A>& trace() {
|
||||
return static_cast<JacobianTrace<T, A, N>&>(*this).trace;
|
||||
}
|
||||
|
||||
/// Access Jacobian
|
||||
template<class A, size_t N>
|
||||
typename Jacobian<T, A>::type& jacobian() {
|
||||
return static_cast<JacobianTrace<T, A, N>&>(*this).dTdA;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Below we use the "Class Composition" technique described in the book
|
||||
// C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost
|
||||
|
@ -530,6 +455,13 @@ struct Record: public boost::mpl::fold<TYPES, CallRecord<T::dimension>,
|
|||
// by invoking boost::mpl::fold over the meta-function GenerateFunctionalNode
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/// meta-function to generate fixed-size JacobianTA type
|
||||
template<class T, class A>
|
||||
struct Jacobian {
|
||||
typedef Eigen::Matrix<double, T::dimension, A::dimension> type;
|
||||
typedef boost::optional<type&> optional;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base case for recursive FunctionalNode class
|
||||
*/
|
||||
|
@ -537,10 +469,10 @@ template<class T>
|
|||
struct FunctionalBase: ExpressionNode<T> {
|
||||
static size_t const N = 0; // number of arguments
|
||||
|
||||
typedef CallRecord<T::dimension> Record2;
|
||||
typedef CallRecord<T::dimension> Record;
|
||||
|
||||
/// Construct an execution trace for reverse AD
|
||||
void trace(const Values& values, Record2* record, char*& raw) const {
|
||||
void trace(const Values& values, Record* record, char*& raw) const {
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -555,6 +487,16 @@ struct Argument {
|
|||
boost::shared_ptr<ExpressionNode<A> > expression;
|
||||
};
|
||||
|
||||
/**
|
||||
* Building block for Recursive Record Class
|
||||
* Records the evaluation of a single argument in a functional expression
|
||||
*/
|
||||
template<class T, class A, size_t N>
|
||||
struct JacobianTrace {
|
||||
ExecutionTrace<A> trace;
|
||||
typename Jacobian<T, A>::type dTdA;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursive Definition of Functional ExpressionNode
|
||||
*/
|
||||
|
@ -572,17 +514,15 @@ struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base {
|
|||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive Record Class for Functional Expressions
|
||||
*/
|
||||
struct Record2: JacobianTrace<T, A, N>, Base::Record2 {
|
||||
/// Recursive Record Class for Functional Expressions
|
||||
struct Record: JacobianTrace<T, A, N>, Base::Record {
|
||||
|
||||
typedef T return_type;
|
||||
typedef JacobianTrace<T, A, N> This;
|
||||
|
||||
/// Print to std::cout
|
||||
virtual void print(const std::string& indent) const {
|
||||
Base::Record2::print(indent);
|
||||
Base::Record::print(indent);
|
||||
static const Eigen::IOFormat matlab(0, 1, " ", "; ", "", "", "[", "]");
|
||||
std::cout << This::dTdA.format(matlab) << std::endl;
|
||||
This::trace.print(indent);
|
||||
|
@ -590,13 +530,13 @@ struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base {
|
|||
|
||||
/// Start the reverse AD process
|
||||
virtual void startReverseAD(JacobianMap& jacobians) const {
|
||||
Base::Record2::startReverseAD(jacobians);
|
||||
Base::Record::startReverseAD(jacobians);
|
||||
Select<T::dimension, A>::reverseAD(This::trace, This::dTdA, jacobians);
|
||||
}
|
||||
|
||||
/// Given df/dT, multiply in dT/dA and continue reverse AD process
|
||||
virtual void reverseAD(const Matrix& dFdT, JacobianMap& jacobians) const {
|
||||
Base::Record2::reverseAD(dFdT, jacobians);
|
||||
Base::Record::reverseAD(dFdT, jacobians);
|
||||
This::trace.reverseAD(dFdT * This::dTdA, jacobians);
|
||||
}
|
||||
|
||||
|
@ -604,15 +544,16 @@ struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base {
|
|||
typedef Eigen::Matrix<double, 2, T::dimension> Jacobian2T;
|
||||
virtual void reverseAD2(const Jacobian2T& dFdT,
|
||||
JacobianMap& jacobians) const {
|
||||
Base::Record2::reverseAD2(dFdT, jacobians);
|
||||
Base::Record::reverseAD2(dFdT, jacobians);
|
||||
This::trace.reverseAD2(dFdT * This::dTdA, jacobians);
|
||||
}
|
||||
};
|
||||
|
||||
/// Construct an execution trace for reverse AD
|
||||
void trace(const Values& values, Record2* record, char*& raw) const {
|
||||
void trace(const Values& values, Record* record, char*& raw) const {
|
||||
Base::trace(values, record, raw);
|
||||
A a = This::expression->traceExecution(values, record->Record2::This::trace, raw);
|
||||
A a = This::expression->traceExecution(values, record->Record::This::trace,
|
||||
raw);
|
||||
raw = raw + This::expression->traceSize();
|
||||
}
|
||||
};
|
||||
|
@ -639,10 +580,27 @@ struct FunctionalNode {
|
|||
return static_cast<Argument<T, A, N> const &>(*this).expression;
|
||||
}
|
||||
|
||||
/// Provide convenience access to Record storage
|
||||
struct Record: public Base::Record {
|
||||
|
||||
/// Access Trace
|
||||
template<class A, size_t N>
|
||||
ExecutionTrace<A>& trace() {
|
||||
return static_cast<JacobianTrace<T, A, N>&>(*this).trace;
|
||||
}
|
||||
|
||||
/// Access Jacobian
|
||||
template<class A, size_t N>
|
||||
typename Jacobian<T, A>::type& jacobian() {
|
||||
return static_cast<JacobianTrace<T, A, N>&>(*this).dTdA;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// Construct an execution trace for reverse AD
|
||||
virtual T traceExecution(const Values& values, ExecutionTrace<T>& trace,
|
||||
char* raw) const {
|
||||
typename Base::Record2* record = new (raw) typename Base::Record2();
|
||||
Record* record = new (raw) Record();
|
||||
trace.setFunction(record);
|
||||
raw = (char*) (record + 1);
|
||||
|
||||
|
@ -658,12 +616,11 @@ struct FunctionalNode {
|
|||
template<class T, class A1>
|
||||
class UnaryExpression: public FunctionalNode<T, boost::mpl::vector<A1> >::type {
|
||||
|
||||
/// The automatically generated Base class
|
||||
typedef FunctionalNode<T, boost::mpl::vector<A1> > Base;
|
||||
|
||||
public:
|
||||
|
||||
typedef boost::function<T(const A1&, typename Jacobian<T, A1>::optional)> Function;
|
||||
typedef typename FunctionalNode<T, boost::mpl::vector<A1> >::type Base;
|
||||
typedef typename Base::Record Record;
|
||||
|
||||
private:
|
||||
|
||||
|
@ -694,10 +651,6 @@ public:
|
|||
return Augmented<T>(t, dTdA1, a1.jacobians());
|
||||
}
|
||||
|
||||
/// CallRecord structure for reverse AD
|
||||
typedef boost::mpl::vector<A1> Arguments;
|
||||
typedef Record<T, Arguments> Record;
|
||||
|
||||
/// Construct an execution trace for reverse AD
|
||||
virtual T traceExecution(const Values& values, ExecutionTrace<T>& trace,
|
||||
char* raw) const {
|
||||
|
@ -724,6 +677,8 @@ public:
|
|||
typedef boost::function<
|
||||
T(const A1&, const A2&, typename Jacobian<T, A1>::optional,
|
||||
typename Jacobian<T, A2>::optional)> Function;
|
||||
typedef typename FunctionalNode<T, boost::mpl::vector<A1, A2> >::type Base;
|
||||
typedef typename Base::Record Record;
|
||||
|
||||
private:
|
||||
|
||||
|
@ -740,7 +695,7 @@ private:
|
|||
}
|
||||
|
||||
friend class Expression<T> ;
|
||||
friend struct ::TestBinaryExpression;
|
||||
friend class ::ExpressionFactorBinaryTest;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -765,10 +720,6 @@ public:
|
|||
return Augmented<T>(t, dTdA1, a1.jacobians(), dTdA2, a2.jacobians());
|
||||
}
|
||||
|
||||
/// CallRecord structure for reverse AD
|
||||
typedef boost::mpl::vector<A1, A2> Arguments;
|
||||
typedef Record<T, Arguments> Record;
|
||||
|
||||
/// Construct an execution trace for reverse AD
|
||||
virtual T traceExecution(const Values& values, ExecutionTrace<T>& trace,
|
||||
char* raw) const {
|
||||
|
@ -801,6 +752,8 @@ public:
|
|||
T(const A1&, const A2&, const A3&, typename Jacobian<T, A1>::optional,
|
||||
typename Jacobian<T, A2>::optional,
|
||||
typename Jacobian<T, A3>::optional)> Function;
|
||||
typedef typename FunctionalNode<T, boost::mpl::vector<A1, A2, A3> >::type Base;
|
||||
typedef typename Base::Record Record;
|
||||
|
||||
private:
|
||||
|
||||
|
@ -847,10 +800,6 @@ public:
|
|||
a3.jacobians());
|
||||
}
|
||||
|
||||
/// CallRecord structure for reverse AD
|
||||
typedef boost::mpl::vector<A1, A2, A3> Arguments;
|
||||
typedef Record<T, Arguments> Record;
|
||||
|
||||
/// Construct an execution trace for reverse AD
|
||||
virtual T traceExecution(const Values& values, ExecutionTrace<T>& trace,
|
||||
char* raw) const {
|
||||
|
|
|
@ -48,7 +48,7 @@ Point2_ p(2);
|
|||
|
||||
/* ************************************************************************* */
|
||||
// Leaf
|
||||
TEST(ExpressionFactor, leaf) {
|
||||
TEST(ExpressionFactor, Leaf) {
|
||||
using namespace leaf;
|
||||
|
||||
// Create old-style factor to create expected value and derivatives
|
||||
|
@ -64,7 +64,7 @@ TEST(ExpressionFactor, leaf) {
|
|||
|
||||
/* ************************************************************************* */
|
||||
// non-zero noise model
|
||||
TEST(ExpressionFactor, model) {
|
||||
TEST(ExpressionFactor, Model) {
|
||||
using namespace leaf;
|
||||
|
||||
SharedNoiseModel model = noiseModel::Diagonal::Sigmas(Vector2(0.1, 0.01));
|
||||
|
@ -82,7 +82,7 @@ TEST(ExpressionFactor, model) {
|
|||
|
||||
/* ************************************************************************* */
|
||||
// Constrained noise model
|
||||
TEST(ExpressionFactor, constrained) {
|
||||
TEST(ExpressionFactor, Constrained) {
|
||||
using namespace leaf;
|
||||
|
||||
SharedDiagonal model = noiseModel::Constrained::MixedSigmas(Vector2(0.2, 0));
|
||||
|
@ -100,7 +100,7 @@ TEST(ExpressionFactor, constrained) {
|
|||
|
||||
/* ************************************************************************* */
|
||||
// Unary(Leaf))
|
||||
TEST(ExpressionFactor, unary) {
|
||||
TEST(ExpressionFactor, Unary) {
|
||||
|
||||
// Create some values
|
||||
Values values;
|
||||
|
@ -121,25 +121,21 @@ TEST(ExpressionFactor, unary) {
|
|||
boost::dynamic_pointer_cast<JacobianFactor>(gf);
|
||||
EXPECT( assert_equal(expected, *jf, 1e-9));
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
struct TestBinaryExpression {
|
||||
static Point2 myUncal(const Cal3_S2& K, const Point2& p,
|
||||
boost::optional<Matrix25&> Dcal, boost::optional<Matrix2&> Dp) {
|
||||
return K.uncalibrate(p, Dcal, Dp);
|
||||
}
|
||||
Cal3_S2_ K_;
|
||||
Point2_ p_;
|
||||
BinaryExpression<Point2, Cal3_S2, Point2> binary_;
|
||||
TestBinaryExpression() :
|
||||
K_(1), p_(2), binary_(myUncal, K_, p_) {
|
||||
}
|
||||
};
|
||||
/* ************************************************************************* */
|
||||
static Point2 myUncal(const Cal3_S2& K, const Point2& p,
|
||||
boost::optional<Matrix25&> Dcal, boost::optional<Matrix2&> Dp) {
|
||||
return K.uncalibrate(p, Dcal, Dp);
|
||||
}
|
||||
|
||||
// Binary(Leaf,Leaf)
|
||||
TEST(ExpressionFactor, binary) {
|
||||
TEST(ExpressionFactor, Binary) {
|
||||
|
||||
typedef BinaryExpression<Point2, Cal3_S2, Point2> Binary;
|
||||
TestBinaryExpression tester;
|
||||
|
||||
Cal3_S2_ K_(1);
|
||||
Point2_ p_(2);
|
||||
Binary binary(myUncal, K_, p_);
|
||||
|
||||
// Create some values
|
||||
Values values;
|
||||
|
@ -156,14 +152,14 @@ TEST(ExpressionFactor, binary) {
|
|||
EXPECT_LONGS_EQUAL(expectedRecordSize, sizeof(Binary::Record));
|
||||
|
||||
// Check size
|
||||
size_t size = tester.binary_.traceSize();
|
||||
size_t size = binary.traceSize();
|
||||
CHECK(size);
|
||||
EXPECT_LONGS_EQUAL(expectedRecordSize, size);
|
||||
// Use Variable Length Array, allocated on stack by gcc
|
||||
// Note unclear for Clang: http://clang.llvm.org/compatibility.html#vla
|
||||
char raw[size];
|
||||
ExecutionTrace<Point2> trace;
|
||||
Point2 value = tester.binary_.traceExecution(values, trace, raw);
|
||||
Point2 value = binary.traceExecution(values, trace, raw);
|
||||
// trace.print();
|
||||
|
||||
// Expected Jacobians
|
||||
|
@ -181,7 +177,7 @@ TEST(ExpressionFactor, binary) {
|
|||
}
|
||||
/* ************************************************************************* */
|
||||
// Unary(Binary(Leaf,Leaf))
|
||||
TEST(ExpressionFactor, shallow) {
|
||||
TEST(ExpressionFactor, Shallow) {
|
||||
|
||||
// Create some values
|
||||
Values values;
|
||||
|
@ -434,27 +430,9 @@ namespace mpl = boost::mpl;
|
|||
template<class T> struct Incomplete;
|
||||
|
||||
typedef mpl::vector<Pose3, Point3, Cal3_S2> MyTypes;
|
||||
typedef Record<Point2, MyTypes> Generated;
|
||||
typedef FunctionalNode<Point2, MyTypes>::type Generated;
|
||||
//Incomplete<Generated> incomplete;
|
||||
//BOOST_MPL_ASSERT((boost::is_same< Matrix25, Generated::JacobianTA >));
|
||||
BOOST_MPL_ASSERT((boost::is_same< Matrix2, Generated::Jacobian2T >));
|
||||
|
||||
Generated generated;
|
||||
|
||||
typedef mpl::vector1<Point3> OneType;
|
||||
typedef mpl::pop_front<OneType>::type Empty;
|
||||
typedef mpl::pop_front<Empty>::type Bad;
|
||||
//typedef ProtoTrace<OneType> UnaryTrace;
|
||||
//BOOST_MPL_ASSERT((boost::is_same< UnaryTrace::A, Point3 >));
|
||||
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/mpl/plus.hpp>
|
||||
#include <boost/mpl/int.hpp>
|
||||
//#include <boost/mpl/print.hpp>
|
||||
|
||||
typedef struct {
|
||||
} Expected0;
|
||||
BOOST_MPL_ASSERT((boost::is_same< Expected0, Expected0 >));
|
||||
BOOST_MPL_ASSERT((boost::is_same< Matrix2, Generated::Record::Jacobian2T >));
|
||||
|
||||
/* ************************************************************************* */
|
||||
int main() {
|
||||
|
|
Loading…
Reference in New Issue