Removed #ifdef blocks and documented the AD process by numbering the methods in the order they are called

release/4.3a0
dellaert 2014-11-29 13:09:17 +01:00
parent 7989a8c0dc
commit e2e29dac68
4 changed files with 116 additions and 126 deletions

View File

@ -190,24 +190,24 @@ public:
* Called only once, either inserts I into Jacobians (Leaf) or starts AD (Function) * Called only once, either inserts I into Jacobians (Leaf) or starts AD (Function)
*/ */
typedef Eigen::Matrix<double, Dim, Dim> JacobianTT; typedef Eigen::Matrix<double, Dim, Dim> JacobianTT;
void startReverseAD(JacobianMap& jacobians) const { void startReverseAD1(JacobianMap& jacobians) const {
if (kind == Leaf) { if (kind == Leaf) {
// This branch will only be called on trivial Leaf expressions, i.e. Priors // This branch will only be called on trivial Leaf expressions, i.e. Priors
static const JacobianTT I = JacobianTT::Identity(); static const JacobianTT I = JacobianTT::Identity();
handleLeafCase(I, jacobians, content.key); handleLeafCase(I, jacobians, content.key);
} else if (kind == Function) } else if (kind == Function)
// This is the more typical entry point, starting the AD pipeline // This is the more typical entry point, starting the AD pipeline
// Inside the startReverseAD that the correctly dimensioned pipeline is chosen. // Inside startReverseAD2 the correctly dimensioned pipeline is chosen.
content.ptr->startReverseAD(jacobians); content.ptr->startReverseAD2(jacobians);
} }
// Either add to Jacobians (Leaf) or propagate (Function) // Either add to Jacobians (Leaf) or propagate (Function)
template<typename DerivedMatrix> template<typename DerivedMatrix>
void reverseAD(const Eigen::MatrixBase<DerivedMatrix> & dTdA, void reverseAD1(const Eigen::MatrixBase<DerivedMatrix> & dTdA,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
if (kind == Leaf) if (kind == Leaf)
handleLeafCase(dTdA, jacobians, content.key); handleLeafCase(dTdA, jacobians, content.key);
else if (kind == Function) else if (kind == Function)
content.ptr->reverseAD(dTdA, jacobians); content.ptr->reverseAD2(dTdA, jacobians);
} }
/// Define type so we can apply it as a meta-function /// Define type so we can apply it as a meta-function
@ -470,10 +470,10 @@ struct FunctionalBase: ExpressionNode<T> {
struct Record { struct Record {
void print(const std::string& indent) const { void print(const std::string& indent) const {
} }
void startReverseAD(JacobianMap& jacobians) const { void startReverseAD4(JacobianMap& jacobians) const {
} }
template<typename SomeMatrix> template<typename SomeMatrix>
void reverseAD(const SomeMatrix & dFdT, JacobianMap& jacobians) const { void reverseAD4(const SomeMatrix & dFdT, JacobianMap& jacobians) const {
} }
}; };
/// Construct an execution trace for reverse AD /// Construct an execution trace for reverse AD
@ -505,9 +505,9 @@ struct JacobianTrace {
typename Jacobian<T, A>::type dTdA; typename Jacobian<T, A>::type dTdA;
}; };
/** // Recursive Definition of Functional ExpressionNode
* Recursive Definition of Functional ExpressionNode // The reason we inherit from Argument<T, A, N> is because we can then
*/ // case to this unique signature to retrieve the expression at any level
template<class T, class A, class Base> template<class T, class A, class Base>
struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base { struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base {
@ -528,7 +528,9 @@ struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base {
This::expression->dims(map); This::expression->dims(map);
} }
/// Recursive Record Class for Functional Expressions // Recursive Record Class for Functional Expressions
// The reason we inherit from JacobianTrace<T, A, N> is because we can then
// case to this unique signature to retrieve the value/trace at any level
struct Record: JacobianTrace<T, A, N>, Base::Record { struct Record: JacobianTrace<T, A, N>, Base::Record {
typedef T return_type; typedef T return_type;
@ -543,17 +545,26 @@ struct GenerateFunctionalNode: Argument<T, A, Base::N + 1>, Base {
} }
/// Start the reverse AD process /// Start the reverse AD process
void startReverseAD(JacobianMap& jacobians) const { void startReverseAD4(JacobianMap& jacobians) const {
Base::Record::startReverseAD(jacobians); Base::Record::startReverseAD4(jacobians);
This::trace.reverseAD(This::dTdA, jacobians); // This is the crucial point where the size of the AD pipeline is selected.
// One pipeline is started for each argument, but the number of rows in each
// pipeline is the same, namely the dimension of the output argument T.
// For example, if the entire expression is rooted by a binary function
// yielding a 2D result, then the matrix dTdA will have 2 rows.
// ExecutionTrace::reverseAD1 just passes this on to CallRecord::reverseAD2
// which calls the correctly sized CallRecord::reverseAD3, which in turn
// calls reverseAD4 below.
This::trace.reverseAD1(This::dTdA, jacobians);
} }
/// Given df/dT, multiply in dT/dA and continue reverse AD process /// Given df/dT, multiply in dT/dA and continue reverse AD process
// Cols is always known at compile time
template<int Rows, int Cols> template<int Rows, int Cols>
void reverseAD(const Eigen::Matrix<double, Rows, Cols> & dFdT, void reverseAD4(const Eigen::Matrix<double, Rows, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
Base::Record::reverseAD(dFdT, jacobians); Base::Record::reverseAD4(dFdT, jacobians);
This::trace.reverseAD(dFdT * This::dTdA, jacobians); This::trace.reverseAD1(dFdT * This::dTdA, jacobians);
} }
}; };
@ -614,8 +625,8 @@ struct FunctionalNode {
struct Record: public internal::CallRecordImplementor<Record, struct Record: public internal::CallRecordImplementor<Record,
traits::dimension<T>::value>, public Base::Record { traits::dimension<T>::value>, public Base::Record {
using Base::Record::print; using Base::Record::print;
using Base::Record::startReverseAD; using Base::Record::startReverseAD4;
using Base::Record::reverseAD; using Base::Record::reverseAD4;
virtual ~Record() { virtual ~Record() {
} }

View File

@ -209,7 +209,7 @@ private:
ExecutionTraceStorage traceStorage[size]; ExecutionTraceStorage traceStorage[size];
ExecutionTrace<T> trace; ExecutionTrace<T> trace;
T value(traceExecution(values, trace, traceStorage)); T value(traceExecution(values, trace, traceStorage));
trace.startReverseAD(jacobians); trace.startReverseAD1(jacobians);
return value; return value;
} }

View File

@ -32,12 +32,6 @@ class JacobianMap;
// forward declaration // forward declaration
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
/**
* MaxVirtualStaticRows defines how many separate virtual reverseAD with specific
* static rows (1..MaxVirtualStaticRows) methods will be part of the CallRecord interface.
*/
#define MaxVirtualStaticRows 4
namespace internal { namespace internal {
/** /**
@ -57,7 +51,8 @@ struct ConvertToVirtualFunctionSupportedMatrixType {
template<> template<>
struct ConvertToVirtualFunctionSupportedMatrixType<false> { struct ConvertToVirtualFunctionSupportedMatrixType<false> {
template<typename Derived> template<typename Derived>
static const Eigen::Matrix<double, Derived::RowsAtCompileTime, Derived::ColsAtCompileTime> convert( static const Eigen::Matrix<double, Derived::RowsAtCompileTime,
Derived::ColsAtCompileTime> convert(
const Eigen::MatrixBase<Derived> & x) { const Eigen::MatrixBase<Derived> & x) {
return x; return x;
} }
@ -72,73 +67,68 @@ struct ConvertToVirtualFunctionSupportedMatrixType<false> {
} // namespace internal } // namespace internal
/** /**
* The CallRecord class stores the Jacobians of applying a function * The CallRecord is an abstract base class for the any class that stores
* with respect to each of its arguments. It also stores an execution trace * the Jacobians of applying a function with respect to each of its arguments,
* (defined below) for each of its arguments. * as well as an execution trace for each of its arguments.
*
* It is implemented in the function-style ExpressionNode's nested Record class below.
*/ */
template<int Cols> template<int Cols>
struct CallRecord { struct CallRecord {
// Print entire record, recursively
inline void print(const std::string& indent) const { inline void print(const std::string& indent) const {
_print(indent); _print(indent);
} }
inline void startReverseAD(JacobianMap& jacobians) const { // Main entry point for the reverse AD process of a functional expression.
_startReverseAD(jacobians); // Called *once* by the main AD entry point, ExecutionTrace::startReverseAD1
// This function then calls ExecutionTrace::reverseAD for every argument
// which will in turn call the reverseAD method below.
// This non-virtual function _startReverseAD3, implemented in derived
inline void startReverseAD2(JacobianMap& jacobians) const {
_startReverseAD3(jacobians);
} }
// Dispatch the reverseAD2 calls issued by ExecutionTrace::reverseAD1
// Here we convert to dynamic if the
template<typename Derived> template<typename Derived>
inline void reverseAD(const Eigen::MatrixBase<Derived> & dFdT, inline void reverseAD2(const Eigen::MatrixBase<Derived> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
_reverseAD( _reverseAD3(
internal::ConvertToVirtualFunctionSupportedMatrixType< internal::ConvertToVirtualFunctionSupportedMatrixType<
(Derived::RowsAtCompileTime > MaxVirtualStaticRows) (Derived::RowsAtCompileTime > 5)>::convert(dFdT),
>::convert(dFdT), jacobians);
jacobians
);
} }
inline void reverseAD(const Matrix & dFdT, JacobianMap& jacobians) const { // TODO: remove once Hannes agrees this is never called as handled by above
_reverseAD(dFdT, jacobians); // inline void reverseAD2(const Matrix & dFdT, JacobianMap& jacobians) const {
} // _reverseAD3(dFdT, jacobians);
// }
virtual ~CallRecord() { virtual ~CallRecord() {
} }
private: private:
virtual void _print(const std::string& indent) const = 0;
virtual void _startReverseAD(JacobianMap& jacobians) const = 0;
virtual void _reverseAD(const Matrix & dFdT, JacobianMap& jacobians) const = 0; virtual void _print(const std::string& indent) const = 0;
virtual void _reverseAD( virtual void _startReverseAD3(JacobianMap& jacobians) const = 0;
virtual void _reverseAD3(const Matrix & dFdT,
JacobianMap& jacobians) const = 0;
virtual void _reverseAD3(
const Eigen::Matrix<double, Eigen::Dynamic, Cols> & dFdT, const Eigen::Matrix<double, Eigen::Dynamic, Cols> & dFdT,
JacobianMap& jacobians) const = 0; JacobianMap& jacobians) const = 0;
#if MaxVirtualStaticRows >= 1
virtual void _reverseAD( virtual void _reverseAD3(const Eigen::Matrix<double, 1, Cols> & dFdT,
const Eigen::Matrix<double, 1, Cols> & dFdT,
JacobianMap& jacobians) const = 0; JacobianMap& jacobians) const = 0;
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 2, Cols> & dFdT,
#if MaxVirtualStaticRows >= 2
virtual void _reverseAD(
const Eigen::Matrix<double, 2, Cols> & dFdT,
JacobianMap& jacobians) const = 0; JacobianMap& jacobians) const = 0;
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 3, Cols> & dFdT,
#if MaxVirtualStaticRows >= 3
virtual void _reverseAD(
const Eigen::Matrix<double, 3, Cols> & dFdT,
JacobianMap& jacobians) const = 0; JacobianMap& jacobians) const = 0;
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 4, Cols> & dFdT,
#if MaxVirtualStaticRows >= 4
virtual void _reverseAD(
const Eigen::Matrix<double, 4, Cols> & dFdT,
JacobianMap& jacobians) const = 0; JacobianMap& jacobians) const = 0;
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 5, Cols> & dFdT,
#if MaxVirtualStaticRows >= 5
virtual void _reverseAD(
const Eigen::Matrix<double, 5, Cols> & dFdT,
JacobianMap& jacobians) const = 0; JacobianMap& jacobians) const = 0;
#endif
}; };
namespace internal { namespace internal {
@ -149,59 +139,48 @@ namespace internal {
template<typename Derived, int Cols> template<typename Derived, int Cols>
struct CallRecordImplementor: public CallRecord<Cols> { struct CallRecordImplementor: public CallRecord<Cols> {
private: private:
const Derived & derived() const { const Derived & derived() const {
return static_cast<const Derived&>(*this); return static_cast<const Derived&>(*this);
} }
virtual void _print(const std::string& indent) const { virtual void _print(const std::string& indent) const {
derived().print(indent); derived().print(indent);
} }
virtual void _startReverseAD(JacobianMap& jacobians) const {
derived().startReverseAD(jacobians); virtual void _startReverseAD3(JacobianMap& jacobians) const {
derived().startReverseAD4(jacobians);
} }
virtual void _reverseAD(const Matrix & dFdT, JacobianMap& jacobians) const { virtual void _reverseAD3(const Matrix & dFdT, JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
virtual void _reverseAD(
virtual void _reverseAD3(
const Eigen::Matrix<double, Eigen::Dynamic, Cols> & dFdT, const Eigen::Matrix<double, Eigen::Dynamic, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
#if MaxVirtualStaticRows >= 1 virtual void _reverseAD3(const Eigen::Matrix<double, 1, Cols> & dFdT,
virtual void _reverseAD(
const Eigen::Matrix<double, 1, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 2, Cols> & dFdT,
#if MaxVirtualStaticRows >= 2
virtual void _reverseAD(
const Eigen::Matrix<double, 2, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 3, Cols> & dFdT,
#if MaxVirtualStaticRows >= 3
virtual void _reverseAD(
const Eigen::Matrix<double, 3, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 4, Cols> & dFdT,
#if MaxVirtualStaticRows >= 4
virtual void _reverseAD(
const Eigen::Matrix<double, 4, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
#endif virtual void _reverseAD3(const Eigen::Matrix<double, 5, Cols> & dFdT,
#if MaxVirtualStaticRows >= 5
virtual void _reverseAD(
const Eigen::Matrix<double, 5, Cols> & dFdT,
JacobianMap& jacobians) const { JacobianMap& jacobians) const {
derived().reverseAD(dFdT, jacobians); derived().reverseAD4(dFdT, jacobians);
} }
#endif
}; };
} // namespace internal } // namespace internal

View File

@ -33,7 +33,7 @@ static const int Cols = 3;
int dynamicIfAboveMax(int i){ int dynamicIfAboveMax(int i){
if(i > MaxVirtualStaticRows){ if(i > 5){
return Eigen::Dynamic; return Eigen::Dynamic;
} }
else return i; else return i;
@ -76,20 +76,20 @@ struct Record: public internal::CallRecordImplementor<Record, Cols> {
} }
void print(const std::string& indent) const { void print(const std::string& indent) const {
} }
void startReverseAD(JacobianMap& jacobians) const { void startReverseAD4(JacobianMap& jacobians) const {
} }
mutable CallConfig cc; mutable CallConfig cc;
private: private:
template<typename SomeMatrix> template<typename SomeMatrix>
void reverseAD(const SomeMatrix & dFdT, JacobianMap& jacobians) const { void reverseAD4(const SomeMatrix & dFdT, JacobianMap& jacobians) const {
cc.compTimeRows = SomeMatrix::RowsAtCompileTime; cc.compTimeRows = SomeMatrix::RowsAtCompileTime;
cc.compTimeCols = SomeMatrix::ColsAtCompileTime; cc.compTimeCols = SomeMatrix::ColsAtCompileTime;
cc.runTimeRows = dFdT.rows(); cc.runTimeRows = dFdT.rows();
cc.runTimeCols = dFdT.cols(); cc.runTimeCols = dFdT.cols();
} }
template<typename Derived, int Rows, int OtherCols> template<typename Derived, int Rows>
friend struct internal::CallRecordImplementor; friend struct internal::CallRecordImplementor;
}; };
@ -102,56 +102,56 @@ TEST(CallRecord, virtualReverseAdDispatching) {
Record record; Record record;
{ {
const int Rows = 1; const int Rows = 1;
record.CallRecord::reverseAD(Eigen::Matrix<double, Rows, Cols>(), NJM); record.CallRecord::reverseAD2(Eigen::Matrix<double, Rows, Cols>(), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols))));
record.CallRecord::reverseAD(DynRowMat(Rows, Cols), NJM); record.CallRecord::reverseAD2(DynRowMat(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols))));
record.CallRecord::reverseAD(Eigen::MatrixXd(Rows, Cols), NJM); record.CallRecord::reverseAD2(Eigen::MatrixXd(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols))));
} }
{ {
const int Rows = 2; const int Rows = 2;
record.CallRecord::reverseAD(Eigen::Matrix<double, Rows, Cols>(), NJM); record.CallRecord::reverseAD2(Eigen::Matrix<double, Rows, Cols>(), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols))));
record.CallRecord::reverseAD(DynRowMat(Rows, Cols), NJM); record.CallRecord::reverseAD2(DynRowMat(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols))));
record.CallRecord::reverseAD(Eigen::MatrixXd(Rows, Cols), NJM); record.CallRecord::reverseAD2(Eigen::MatrixXd(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols))));
} }
{ {
const int Rows = 3; const int Rows = 3;
record.CallRecord::reverseAD(Eigen::Matrix<double, Rows, Cols>(), NJM); record.CallRecord::reverseAD2(Eigen::Matrix<double, Rows, Cols>(), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols))));
record.CallRecord::reverseAD(DynRowMat(Rows, Cols), NJM); record.CallRecord::reverseAD2(DynRowMat(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols))));
record.CallRecord::reverseAD(Eigen::MatrixXd(Rows, Cols), NJM); record.CallRecord::reverseAD2(Eigen::MatrixXd(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols))));
} }
{ {
const int Rows = MaxVirtualStaticRows; const int Rows = 4;
record.CallRecord::reverseAD(Eigen::Matrix<double, Rows, Cols>(), NJM); record.CallRecord::reverseAD2(Eigen::Matrix<double, Rows, Cols>(), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols))));
record.CallRecord::reverseAD(DynRowMat(Rows, Cols), NJM); record.CallRecord::reverseAD2(DynRowMat(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols))));
record.CallRecord::reverseAD(Eigen::MatrixXd(Rows, Cols), NJM); record.CallRecord::reverseAD2(Eigen::MatrixXd(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols))));
} }
{ {
const int Rows = MaxVirtualStaticRows + 1; const int Rows = 5;
record.CallRecord::reverseAD(Eigen::Matrix<double, Rows, Cols>(), NJM); record.CallRecord::reverseAD2(Eigen::Matrix<double, Rows, Cols>(), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols))));
record.CallRecord::reverseAD(DynRowMat(Rows, Cols), NJM); record.CallRecord::reverseAD2(DynRowMat(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols))));
record.CallRecord::reverseAD(Eigen::MatrixXd(Rows, Cols), NJM); record.CallRecord::reverseAD2(Eigen::MatrixXd(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols))));
} }
{ {
const int Rows = MaxVirtualStaticRows + 2; const int Rows = 6;
record.CallRecord::reverseAD(Eigen::Matrix<double, Rows, Cols>(), NJM); record.CallRecord::reverseAD2(Eigen::Matrix<double, Rows, Cols>(), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Rows, Cols))));
record.CallRecord::reverseAD(DynRowMat(Rows, Cols), NJM); record.CallRecord::reverseAD2(DynRowMat(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Cols, Rows, Cols))));
record.CallRecord::reverseAD(Eigen::MatrixXd(Rows, Cols), NJM); record.CallRecord::reverseAD2(Eigen::MatrixXd(Rows, Cols), NJM);
EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols)))); EXPECT((assert_equal(record.cc, CallConfig(Eigen::Dynamic, Eigen::Dynamic, Rows, Cols))));
} }
} }