/* ---------------------------------------------------------------------------- * 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 ExpressionNode.h * @date May 10, 2015 * @author Frank Dellaert * @author Paul Furgale * @brief ExpressionNode class */ #pragma once #include #include #include #include // operator typeid #include #include class ExpressionFactorBinaryTest; // Forward declare for testing namespace gtsam { namespace internal { template T & upAlign(T & value, unsigned requiredAlignment = TraceAlignment) { // right now only word sized types are supported. // Easy to extend if needed, // by somehow inferring the unsigned integer of same size BOOST_STATIC_ASSERT(sizeof(T) == sizeof(size_t)); size_t & uiValue = reinterpret_cast(value); size_t misAlignment = uiValue % requiredAlignment; if (misAlignment) { uiValue += requiredAlignment - misAlignment; } return value; } template T upAligned(T value, unsigned requiredAlignment = TraceAlignment) { return upAlign(value, requiredAlignment); } //----------------------------------------------------------------------------- /** * Expression node. The superclass for objects that do the heavy lifting * An Expression has a pointer to an ExpressionNode underneath * allowing Expressions to have polymorphic behaviour even though they * are passed by value. This is the same way boost::function works. * http://loki-lib.sourceforge.net/html/a00652.html */ template class ExpressionNode { protected: size_t traceSize_; /// Constructor, traceSize is size of the execution trace of expression rooted here ExpressionNode(size_t traceSize = 0) : traceSize_(traceSize) { } public: /// Destructor virtual ~ExpressionNode() { } /// Print virtual void print(const std::string& indent = "") const = 0; /// Streaming GTSAM_EXPORT friend std::ostream& operator<<(std::ostream& os, const ExpressionNode& node) { os << "Expression of type " << demangle(typeid(T).name()); if (node.traceSize_ > 0) os << ", trace size = " << node.traceSize_; os << "\n"; return os; } /// Return keys that play in this expression as a set virtual std::set keys() const { std::set keys; return keys; } /// Return dimensions for each argument, as a map virtual void dims(std::map& map) const { } // Return size needed for memory buffer in traceExecution size_t traceSize() const { return traceSize_; } /// Return value virtual T value(const Values& values) const = 0; /// Construct an execution trace for reverse AD virtual T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* traceStorage) const = 0; }; //----------------------------------------------------------------------------- /// Constant Expression template class ConstantExpression: public ExpressionNode { /// The constant value T constant_; /// Constructor with a value, yielding a constant ConstantExpression(const T& value) : constant_(value) { } friend class Expression ; public: /// Destructor virtual ~ConstantExpression() { } /// Print void print(const std::string& indent = "") const override { std::cout << indent << "Constant" << std::endl; } /// Return value T value(const Values& values) const override { return constant_; } /// Construct an execution trace for reverse AD T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* traceStorage) const override { return constant_; } GTSAM_MAKE_ALIGNED_OPERATOR_NEW }; //----------------------------------------------------------------------------- /// Leaf Expression, if no chart is given, assume default chart and value_type is just the plain value template class LeafExpression: public ExpressionNode { typedef T value_type; /// The key into values Key key_; /// Constructor with a single key LeafExpression(Key key) : key_(key) { } friend class Expression; public: /// Destructor virtual ~LeafExpression() { } /// Print void print(const std::string& indent = "") const override { std::cout << indent << "Leaf, key = " << DefaultKeyFormatter(key_) << std::endl; } /// Return keys that play in this expression std::set keys() const override { std::set keys; keys.insert(key_); return keys; } /// Return dimensions for each argument void dims(std::map& map) const override { map[key_] = traits::dimension; } /// Return value T value(const Values& values) const override { return values.at(key_); } /// Construct an execution trace for reverse AD T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* traceStorage) const override { trace.setLeaf(key_); return values.at(key_); } }; //----------------------------------------------------------------------------- /// meta-function to generate fixed-size JacobianTA type template struct Jacobian { typedef Eigen::Matrix::dimension, traits::dimension> type; }; // Helper function for printing Jacobians with compact Eigen format, and trace template static void PrintJacobianAndTrace(const std::string& indent, const typename Jacobian::type& dTdA, const ExecutionTrace trace) { static const Eigen::IOFormat kMatlabFormat(0, 1, " ", "; ", "", "", "[", "]"); std::cout << indent << "D(" << demangle(typeid(T).name()) << ")/D(" << demangle(typeid(A).name()) << ") = " << dTdA.format(kMatlabFormat) << std::endl; trace.print(indent); } //----------------------------------------------------------------------------- /// Unary Function Expression template class UnaryExpression: public ExpressionNode { typedef typename Expression::template UnaryFunction::type Function; boost::shared_ptr > expression1_; Function function_; /// Constructor with a unary function f, and input argument e1 UnaryExpression(Function f, const Expression& e1) : expression1_(e1.root()), function_(f) { this->traceSize_ = upAligned(sizeof(Record)) + e1.traceSize(); } friend class Expression; public: /// Destructor virtual ~UnaryExpression() { } /// Print void print(const std::string& indent = "") const override { std::cout << indent << "UnaryExpression" << std::endl; expression1_->print(indent + " "); } /// Return value T value(const Values& values) const override { return function_(expression1_->value(values), boost::none); } /// Return keys that play in this expression std::set keys() const override { return expression1_->keys(); } /// Return dimensions for each argument void dims(std::map& map) const override { expression1_->dims(map); } // Inner Record Class struct Record: public CallRecordImplementor::dimension> { typename Jacobian::type dTdA1; ExecutionTrace trace1; A1 value1; /// Construct record by calling argument expression Record(const Values& values, const ExpressionNode& expression1, ExecutionTraceStorage* ptr) : value1(expression1.traceExecution(values, trace1, ptr + upAligned(sizeof(Record)))) {} /// Print to std::cout void print(const std::string& indent) const { std::cout << indent << "UnaryExpression::Record {" << std::endl; PrintJacobianAndTrace(indent, dTdA1, trace1); std::cout << indent << "}" << std::endl; } /// Start the reverse AD process void startReverseAD4(JacobianMap& jacobians) const { // 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. trace1.reverseAD1(dTdA1, jacobians); } /// Given df/dT, multiply in dT/dA and continue reverse AD process template void reverseAD4(const MatrixType & dFdT, JacobianMap& jacobians) const { trace1.reverseAD1(dFdT * dTdA1, jacobians); } }; /// Construct an execution trace for reverse AD T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* ptr) const override { assert(reinterpret_cast(ptr) % TraceAlignment == 0); // Create a Record in the memory pointed to by ptr // Calling the constructor will record the traces for all arguments // Write an Expression execution trace in record->trace // Iff Constant or Leaf, this will not write to traceStorage, only to trace. // Iff the expression is functional, write all Records in traceStorage buffer // Return value of type T is recorded in record->value // NOTE(frank, abe): The destructor on this record is never called due to this placement new // Records must only contain statically sized objects! Record* record = new (ptr) Record(values, *expression1_, ptr); // Our trace parameter is set to point to the Record trace.setFunction(record); // Finally, the function call fills in the Jacobian dTdA1 return function_(record->value1, record->dTdA1); } }; //----------------------------------------------------------------------------- /// Binary Expression template class BinaryExpression: public ExpressionNode { typedef typename Expression::template BinaryFunction::type Function; boost::shared_ptr > expression1_; boost::shared_ptr > expression2_; Function function_; /// Constructor with a binary function f, and two input arguments BinaryExpression(Function f, const Expression& e1, const Expression& e2) : expression1_(e1.root()), expression2_(e2.root()), function_(f) { this->traceSize_ = // upAligned(sizeof(Record)) + e1.traceSize() + e2.traceSize(); } friend class Expression; friend class ::ExpressionFactorBinaryTest; public: /// Destructor virtual ~BinaryExpression() { } /// Print void print(const std::string& indent = "") const override { std::cout << indent << "BinaryExpression" << std::endl; expression1_->print(indent + " "); expression2_->print(indent + " "); } /// Return value T value(const Values& values) const override { using boost::none; return function_(expression1_->value(values), expression2_->value(values), none, none); } /// Return keys that play in this expression std::set keys() const override { std::set keys = expression1_->keys(); std::set myKeys = expression2_->keys(); keys.insert(myKeys.begin(), myKeys.end()); return keys; } /// Return dimensions for each argument void dims(std::map& map) const override { expression1_->dims(map); expression2_->dims(map); } // Inner Record Class struct Record: public CallRecordImplementor::dimension> { typename Jacobian::type dTdA1; typename Jacobian::type dTdA2; ExecutionTrace trace1; ExecutionTrace trace2; // TODO(frank): These aren't needed kill them! A1 value1; A2 value2; /// Construct record by calling argument expressions Record(const Values& values, const ExpressionNode& expression1, const ExpressionNode& expression2, ExecutionTraceStorage* ptr) : value1(expression1.traceExecution(values, trace1, ptr += upAligned(sizeof(Record)))), value2(expression2.traceExecution(values, trace2, ptr += expression1.traceSize())) {} /// Print to std::cout void print(const std::string& indent) const { std::cout << indent << "BinaryExpression::Record {" << std::endl; PrintJacobianAndTrace(indent, dTdA1, trace1); PrintJacobianAndTrace(indent, dTdA2, trace2); std::cout << indent << "}" << std::endl; } /// Start the reverse AD process, see comments in UnaryExpression void startReverseAD4(JacobianMap& jacobians) const { trace1.reverseAD1(dTdA1, jacobians); trace2.reverseAD1(dTdA2, jacobians); } /// Given df/dT, multiply in dT/dA and continue reverse AD process template void reverseAD4(const MatrixType & dFdT, JacobianMap& jacobians) const { trace1.reverseAD1(dFdT * dTdA1, jacobians); trace2.reverseAD1(dFdT * dTdA2, jacobians); } }; /// Construct an execution trace for reverse AD, see UnaryExpression for explanation T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* ptr) const override { assert(reinterpret_cast(ptr) % TraceAlignment == 0); Record* record = new (ptr) Record(values, *expression1_, *expression2_, ptr); trace.setFunction(record); return function_(record->value1, record->value2, record->dTdA1, record->dTdA2); } }; //----------------------------------------------------------------------------- /// Ternary Expression template class TernaryExpression: public ExpressionNode { typedef typename Expression::template TernaryFunction::type Function; boost::shared_ptr > expression1_; boost::shared_ptr > expression2_; boost::shared_ptr > expression3_; Function function_; /// Constructor with a ternary function f, and two input arguments TernaryExpression(Function f, const Expression& e1, const Expression& e2, const Expression& e3) : expression1_(e1.root()), expression2_(e2.root()), expression3_(e3.root()), // function_(f) { this->traceSize_ = upAligned(sizeof(Record)) + // e1.traceSize() + e2.traceSize() + e3.traceSize(); } friend class Expression; public: /// Destructor virtual ~TernaryExpression() { } /// Print void print(const std::string& indent = "") const override { std::cout << indent << "TernaryExpression" << std::endl; expression1_->print(indent + " "); expression2_->print(indent + " "); expression3_->print(indent + " "); } /// Return value T value(const Values& values) const override { using boost::none; return function_(expression1_->value(values), expression2_->value(values), expression3_->value(values), none, none, none); } /// Return keys that play in this expression std::set keys() const override { std::set keys = expression1_->keys(); std::set myKeys = expression2_->keys(); keys.insert(myKeys.begin(), myKeys.end()); myKeys = expression3_->keys(); keys.insert(myKeys.begin(), myKeys.end()); return keys; } /// Return dimensions for each argument void dims(std::map& map) const override { expression1_->dims(map); expression2_->dims(map); expression3_->dims(map); } // Inner Record Class struct Record: public CallRecordImplementor::dimension> { typename Jacobian::type dTdA1; typename Jacobian::type dTdA2; typename Jacobian::type dTdA3; ExecutionTrace trace1; ExecutionTrace trace2; ExecutionTrace trace3; A1 value1; A2 value2; A3 value3; /// Construct record by calling 3 argument expressions Record(const Values& values, const ExpressionNode& expression1, const ExpressionNode& expression2, const ExpressionNode& expression3, ExecutionTraceStorage* ptr) : value1(expression1.traceExecution(values, trace1, ptr += upAligned(sizeof(Record)))), value2(expression2.traceExecution(values, trace2, ptr += expression1.traceSize())), value3(expression3.traceExecution(values, trace3, ptr += expression2.traceSize())) {} /// Print to std::cout void print(const std::string& indent) const { std::cout << indent << "TernaryExpression::Record {" << std::endl; PrintJacobianAndTrace(indent, dTdA1, trace1); PrintJacobianAndTrace(indent, dTdA2, trace2); PrintJacobianAndTrace(indent, dTdA3, trace3); std::cout << indent << "}" << std::endl; } /// Start the reverse AD process, see comments in Base void startReverseAD4(JacobianMap& jacobians) const { trace1.reverseAD1(dTdA1, jacobians); trace2.reverseAD1(dTdA2, jacobians); trace3.reverseAD1(dTdA3, jacobians); } /// Given df/dT, multiply in dT/dA and continue reverse AD process template void reverseAD4(const MatrixType & dFdT, JacobianMap& jacobians) const { trace1.reverseAD1(dFdT * dTdA1, jacobians); trace2.reverseAD1(dFdT * dTdA2, jacobians); trace3.reverseAD1(dFdT * dTdA3, jacobians); } }; /// Construct an execution trace for reverse AD, see UnaryExpression for explanation T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* ptr) const override { assert(reinterpret_cast(ptr) % TraceAlignment == 0); Record* record = new (ptr) Record(values, *expression1_, *expression2_, *expression3_, ptr); trace.setFunction(record); return function_(record->value1, record->value2, record->value3, record->dTdA1, record->dTdA2, record->dTdA3); } }; //----------------------------------------------------------------------------- /// Expression for scalar multiplication template class ScalarMultiplyNode : public ExpressionNode { // Check that T is a vector space BOOST_CONCEPT_ASSERT((gtsam::IsVectorSpace)); double scalar_; boost::shared_ptr > expression_; public: /// Constructor with a unary function f, and input argument e1 ScalarMultiplyNode(double s, const Expression& e) : scalar_(s), expression_(e.root()) { this->traceSize_ = upAligned(sizeof(Record)) + e.traceSize(); } /// Destructor virtual ~ScalarMultiplyNode() {} /// Print void print(const std::string& indent = "") const override { std::cout << indent << "ScalarMultiplyNode" << std::endl; expression_->print(indent + " "); } /// Return value T value(const Values& values) const override { return scalar_ * expression_->value(values); } /// Return keys that play in this expression std::set keys() const override { return expression_->keys(); } /// Return dimensions for each argument void dims(std::map& map) const override { expression_->dims(map); } // Inner Record Class struct Record : public CallRecordImplementor::dimension> { static const int Dim = traits::dimension; typedef Eigen::Matrix JacobianTT; double scalar_dTdA; ExecutionTrace trace; /// Print to std::cout void print(const std::string& indent) const { std::cout << indent << "ScalarMultiplyNode::Record {" << std::endl; std::cout << indent << "D(" << demangle(typeid(T).name()) << ")/D(" << demangle(typeid(T).name()) << ") = " << scalar_dTdA << std::endl; trace.print(); std::cout << indent << "}" << std::endl; } /// Start the reverse AD process void startReverseAD4(JacobianMap& jacobians) const { trace.reverseAD1(scalar_dTdA * JacobianTT::Identity(), jacobians); } /// Given df/dT, multiply in dT/dA and continue reverse AD process template void reverseAD4(const MatrixType& dFdT, JacobianMap& jacobians) const { trace.reverseAD1(dFdT * scalar_dTdA, jacobians); } }; /// Construct an execution trace for reverse AD T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* ptr) const override { assert(reinterpret_cast(ptr) % TraceAlignment == 0); Record* record = new (ptr) Record(); ptr += upAligned(sizeof(Record)); T value = expression_->traceExecution(values, record->trace, ptr); ptr += expression_->traceSize(); trace.setFunction(record); record->scalar_dTdA = scalar_; return scalar_ * value; } }; //----------------------------------------------------------------------------- /// Binary Sum Expression template class BinarySumNode : public ExpressionNode { typedef ExpressionNode NodeT; boost::shared_ptr > expression1_; boost::shared_ptr > expression2_; public: explicit BinarySumNode() { this->traceSize_ = upAligned(sizeof(Record)); } /// Constructor with a binary function f, and two input arguments BinarySumNode(const Expression& e1, const Expression& e2) : expression1_(e1.root()), expression2_(e2.root()) { this->traceSize_ = // upAligned(sizeof(Record)) + e1.traceSize() + e2.traceSize(); } /// Destructor virtual ~BinarySumNode() {} /// Print void print(const std::string& indent = "") const override { std::cout << indent << "BinarySumNode" << std::endl; expression1_->print(indent + " "); expression2_->print(indent + " "); } /// Return value T value(const Values& values) const override { return expression1_->value(values) + expression2_->value(values); } /// Return keys that play in this expression std::set keys() const override { std::set keys = expression1_->keys(); std::set myKeys = expression2_->keys(); keys.insert(myKeys.begin(), myKeys.end()); return keys; } /// Return dimensions for each argument void dims(std::map& map) const override { expression1_->dims(map); expression2_->dims(map); } // Inner Record Class struct Record : public CallRecordImplementor::dimension> { ExecutionTrace trace1; ExecutionTrace trace2; /// Print to std::cout void print(const std::string& indent) const { std::cout << indent << "BinarySumNode::Record {" << std::endl; trace1.print(indent); trace2.print(indent); std::cout << indent << "}" << std::endl; } /// If the BinarySumExpression is the root, we just start as many pipelines as there are terms. void startReverseAD4(JacobianMap& jacobians) const { // NOTE(frank): equivalent to trace.reverseAD1(dTdA, jacobians) with dTdA=Identity trace1.startReverseAD1(jacobians); trace2.startReverseAD1(jacobians); } /// If we are not the root, we simply pass on the adjoint matrix dFdT to all terms template void reverseAD4(const MatrixType& dFdT, JacobianMap& jacobians) const { // NOTE(frank): equivalent to trace.reverseAD1(dFdT * dTdA, jacobians) with dTdA=Identity trace1.reverseAD1(dFdT, jacobians); trace2.reverseAD1(dFdT, jacobians); } }; /// Construct an execution trace for reverse AD T traceExecution(const Values& values, ExecutionTrace& trace, ExecutionTraceStorage* ptr) const override { assert(reinterpret_cast(ptr) % TraceAlignment == 0); Record* record = new (ptr) Record(); trace.setFunction(record); ExecutionTraceStorage* ptr1 = ptr + upAligned(sizeof(Record)); ExecutionTraceStorage* ptr2 = ptr1 + expression1_->traceSize(); return expression1_->traceExecution(values, record->trace1, ptr1) + expression2_->traceExecution(values, record->trace2, ptr2); } }; } // namespace internal } // namespace gtsam