Click here to Skip to main content
15,896,606 members
Articles / Programming Languages / C++

Fast Automatic Differentiation in C++

Rate me:
Please Sign up or sign in to vote.
4.67/5 (12 votes)
1 Sep 2006CPOL12 min read 76.4K   2.9K   27  
An article on fast automatic differentiation (FAD), another way of computing the derivatives of a function.
//////////////////////////////////////////////////////////////////////////
/// The Reed Library 
/// Copyright (c) 2006 Yevgeniy Shatokhin 

/// File: asection.h 
/// Contains definitions necessary for active section manipulation.

/// 
/// Last update: August 29, 2006

#ifndef __ASECTION_H__E079034E_E787_4946_8B13_17202FD9FEAD_INCLUDED_
#define __ASECTION_H__E079034E_E787_4946_8B13_17202FD9FEAD_INCLUDED_

/// Define REED_MULTITHREADED if you intend to use the Reed-instrumented
/// code in several threads sumultaneously. Otherwise it's better to 
/// undefine this macro to (probably) get better performance.
#ifdef REED_MULTITHREADED
#define REED_THREAD __declspec(thread)
#else
#define REED_THREAD
#endif	// #ifdef REED_MULTITHREADED

// I do not like bools, so I prefer to use ints instead.
// If TRUE and FALSE are not defined we define them here.
#ifndef FALSE
#define FALSE 0
#define TRUE !FALSE
#endif

#include <reed/mem_pool.h>	// special allocator
#include <reed/node.h>		// CG node classes
#include <cassert>
#include <vector>

namespace reed
{
	class CADouble;	// forward declaration

	//////////////////////////////////////////////////////////////////////////
	/// CActiveSection class \n
	/// Instances of this class represent an "active section" (AS), a program
	/// fragment the structure of which is to be recorded in the CG (computational
	/// graph, "graf algoritma" in russian).
	//////////////////////////////////////////////////////////////////////////
	class CActiveSection
	{
	public:
		/// 
		/// Number of code execution modes. (See 'EMode' below for their descriptions.)
		enum {NMODES = 4};

		///
		/// Type for the current mode of code execution in this thread.
		enum EMode
		{
			/// executing code outside any AS (this mode cannot be assigned to
			/// any particular AS, it's just for convenience).
			AS_MODE_OUTSIDE		= 0,	
			AS_MODE_PLAIN		= 1,	///< executing code within this AS, recording new CG
			AS_MODE_CACHING1	= 2,	///< executing code within this AS, using cached CG
			AS_MODE_CACHING2	= 3		///< (reserved)
		};

	public:
		// Static methods

		/// 
		/// Get current execution mode.
		static EMode getMode()
		{ 
			return (activeAS == NULL) ? AS_MODE_OUTSIDE : getCurrentAS().mode; 
		}

		///
		/// Get a reference to the current AS (for the current thread).
		static CActiveSection& getCurrentAS()
		{ 
			assert(activeAS != NULL);
			return (*activeAS);
		}

	public:
		// typedefs
		typedef std::vector<CADouble> TADVector;
		typedef std::vector<double> TDoubleVector;

	public:
		// Non-static service methods.

		///
		/// Constructor.
		CActiveSection();

		/// 
		/// Copy constructor. Not sure if it's really necessary. 
        CActiveSection(const CActiveSection& rhs);

		///
		/// Destructor.
		~CActiveSection();

		///
		/// Return TRUE if CG caching is enabled, FALSE otherwise.
		int isCacheEnabled() const
		{ return bCacheEnabled; }

		///
		/// Set AS recording mode to plain (no caching)
		void setPlainMode()
		{
			mode = AS_MODE_PLAIN;
		}

		///
		/// Pass TRUE to enable or FALSE to disable CG caching. \n
		/// WARNING. This method can be called from outside this AS only.
		void enableCache(int bEnable = TRUE)
		{ 
			assert(activeAS != this);	// We must not be within this AS.
			if (bEnable) 
			{
				bCacheEnabled = TRUE;
			}
			else
			{
				bCacheEnabled = FALSE;
				mode = AS_MODE_PLAIN;
			}
		}

		/// 
		/// Mark the beginning of the AS.
		void begin();

		///
		/// Mark the end of the AS.
		void end();

		/// Add a copy of the given node to the CG. Pointers to the arguments
		/// should be already set.
		template <CNodeBase::EOpCode _Op>
		CNodeBase* addNode(const CNode<_Op>& node)
		{
			CNode<_Op>* p = static_cast<CNode<_Op>*>(alloc.allocate());

			*((unsigned long*)p) = *((unsigned long*)&node); //__vfptr, always the 1st 4 bytes
			p->left = node.left;
			p->right = node.right;
			p->value.d = node.value.d;
			
			return p;
		}

		/// Retrieve accumulated values from the CG nodes associated with 
		/// the variables listed in the arg array.\n
		/// The previous contents of the 'val' array will be deleted in this function.
		void getAccumulators(TADVector& arg, TDoubleVector& val);

		/// Set accumulated values to the CG nodes associated with the variables 
		/// listed in the arg array.\n
		/// The 'val' vector contains the new values to set. 'arg' and 'val' must
		/// have equal number of the elements.
		void setAccumulators(TADVector& arg, TDoubleVector& val);

	public:
		// algorithms operating on the AS

		/// Update (if possible) the recorded CG of this active section.\n
        /// Arguments:\n
		/// [in] arg - a vector of the independent ('input') variables that 
		/// will have new values.\n
		/// [in] val - new values for these variables (in the same order).\n
		///	The independent variables not included in ths vector retain their
		///	previous values.\n
		/// 'arg' and 'val' vectors must have the same size.\n
		/// Return value - TRUE if the update was successful (the CG structure
		/// is the same for new values as for the old ones), FALSE if any of the
		/// relational operators changed its value. \n
		/// If update() succeeds, 'value' fields in the CG nodes are updated. 
		/// Otherwise the function has no effect, 'value' fields remain the same.\n
		/// (NB) This doesn't concern the accumulators. Their contents may (and most
		/// likely will) change in course of this function.
		/// You can use getAccumulators() to save the accumulated values of interest
		/// to a different location before calling update.
		int update(TADVector& arg, TDoubleVector& val);

		/// Perform the 1st order scalar FAD (fast automatic differentiation) in the
		/// forward mode.\n
		/// Arguments:\n
		/// [in] arg - a vector of the independent ('input') variables of interest.
		/// The differentiation will take place against these.\n
		/// [in] s - a vector of multipliers for these variables (in the same order) - 
		///	see below.\n
		/// 'arg' and 's' vectors must have the same size.\n
		/// Return value: status of derivative computation (see CNodeBase::EDerivStatus).
		/// After this 'forward pass' each node's accumulator contains the inner (dot)
		/// product of s vector and the gradient of the function F, which is defined 
		/// by a subgraph of the CG beginning from the nodes associated with 
		/// arg[0], arg[1], ... etc. and ending with this particular node.\n
		/// Example. \n
		/// Let y(u, v) be defined as follows: y = f(g(u, v)). arg[0]=u, arg[1]=v.
		/// After the forward pass the nodes will contain the following in their accumulators:
		/// (except the nodes for relational operators)\n
		/// acc(u) = s[0], acc(v) = s[1], acc(g) = s[0]*(dg/du)+s[1]*(dg/dv),
		/// acc(f) = s[0]*(df/dg)*(dg/du)+s[1]*(df/dg)*(dg/dv), \n
		/// i.e. acc(f)=s[0]*(dy/du)+s[1]*(dy/dv).\n 
		/// If w[i]==1, s[j]==0, for all j!=i then acc(f) = dy/darg[i].\n
		/// The forward mode allows to compute derivatives of all functions of interest
		/// against one independent variable in a single pass.
		int forward(TADVector& arg, TDoubleVector& s);

		/// Compute a derivative of the 'func' function against the independent variable 
		/// 'arg' and return it in 'out'. If arg is not independent the result is undefined.
		/// Return value - see 'forward(TADVector& arg, TDoubleVector& s)'.
		int forward(CADouble arg, CADouble func, double& out);

		/// Compute the inner products of 's' and gradients of the functions from 'func'
		/// against the independent varibles from 'arg'. The results are stored in 'out'.\n
		/// For further description of the return value, s and arg see 
		/// 'forward(TADVector& arg, TDoubleVector& s)'.\n
		/// Ex. out[0] = s[0]*(dfunc[0]/darg[0]) + s[1]*(dfunc[0]/darg[1]) + ...\n
		/// (NB) Previous contents of 'out' vector will be cleared in this function.
		int forward(TADVector& arg, TADVector& func, TDoubleVector& s, TDoubleVector& out);

		/// Perform the 1st order scalar FAD (fast automatic differentiation) in the
		/// reverse mode.\n
		/// The CG must contain at least one node.\n
		/// Arguments:\n
		/// [in] func - a vector of the dependent ('output') variables of interest.
		/// It's the derivatives of these we want to get.\n
		/// [in] w - a weight vector for these functions (in the same order) - see below.\n
		/// 'func' and 'w' vectors must have the same size.\n
		/// Return value: status of derivative computation (see CNodeBase::EDerivStatus).
		/// After this 'reverse pass' each node (x) contains in the accumulator the inner (dot)
		/// product of w and (dfunc[0]/dx, dfunc[1]/dx, ...) 
		/// (except the nodes for relational operators)\n
		/// Example. \n
		/// Let y(u, v) and z(u, v) be defined as follows: 
		/// y = f(g(u, v)), z = h(g(u, v)); func[0]=f, func[1]=h.
		/// After the reverse pass the nodes will contain the following in their accumulators:\n
		/// acc(f)=w[0], acc(h)=w[1], acc(g)=w[0]*(df/dg)+w[1]*(dh/dg),
		/// acc(u)=w[0]*(df/dg)*(dg/du)+w[1]*(dh/dg)*(dg/du), acc(v)=w[0]*(df/dg)*(dg/dv)+w[1]*(dh/dg)*(dg/dv).
		/// i.e. 
		/// If w[0]==1, w[1]==0, then acc(u) = dy/du, acc(v) = dy/dv.
		/// The reverse mode allows to compute the complete gradient of the given function
		/// in a single pass.
		int reverse(TADVector& func, TDoubleVector& w);

		/// Compute a derivative of the 'func' function against the variable 
		/// 'arg' and return it in 'out'. If arg can be a dependent variable.
		/// Return value - see 'reverse(TADVector& func, TDoubleVector& w)'.
		int reverse(CADouble arg, CADouble func, double& out);

		/// Compute the 'weighted' derivatives of the functions from 'func'
		/// against the varibles from 'arg'. The results are stored in 'out'.\n
		/// For further description of the return value, w and arg see 
		/// 'reverse(TADVector& func, TDoubleVector& w)'.
		/// Ex. out[0] = w[0]*(dfunc[0]/darg[0]) + w[1]*(dfunc[1]/darg[0]) + ...\n
		/// (NB) Previous contents of 'out' vector will be cleared in this function.
		int reverse(TADVector& arg, TADVector& func, TDoubleVector& w, TDoubleVector& out);

		/// Compute lower estimate of rounding error using a 'forward-like' pass.\n
		/// Arguments:\n
		/// [in] arg - a vector of the independent ('input') variables of interest,
		/// for which we'd like to specify initial errors different from the default.
		/// [in] err - a vector of these initial errors for the variables from 'arg' 
		/// (in the same order). 'arg' and 'err' must have the same size.\n
		/// If an independent variable is not mentioned in 'arg', default initial error
		/// is used for this variable. The default error is the 'storage error', derr(x).
		/// See reed::derr(x) in util.h for more information. 
		/// Pass empty arg and err if you don't want to overwrite default error values.\n
		/// Return value: status of derivative computation (see CNodeBase::EDerivStatus).\n
		/// When the function returns, each node (except those for relational operators)
		/// contains in its accumulator the lower error estimate of the value in this node.\n
		/// It's assumed there are 2 main sources of rounding errors:\n
		/// 1. 'Storage error' mentioned above that is due to a limited width of the double's
		/// bit representation. (According to the IEEE standard, of all 64 bits in a double
		/// one is used to store a sign, 11 bits hold the order and 52 are left for mantissa).\n
		/// 2. 'Uncertainty error' that arises from any math operation on inexact arguments,
		/// that is, the arguments that possibly have values only known with some error.\n
		/// E.g., if a == 1 (exactly) than sin(a) == sin(1) exactly. However, if all we know
		/// about a is that it resides in some interval like [1.0 - da, 1.0 + da],  
		/// the sin(a) value can be only estimated rather than computed exactly.\n
		/// It's also assumed that the internal computation errors are negligible (i.e. less
		/// than storage errors). That means the number of trusted bits in the IEEE-representation
		/// of x and sin(x) is the same. Not quite sure if this is the case, but let's assume
		/// that for now at least.\n
        int ree_lower(TADVector& arg, TDoubleVector& err);

		/// Compute the lower error estimates for the functions from 'func'.
		/// The independent varibles from 'arg' get their initial error estimates equal
		/// to those specified in 'err'. The results are stored in 'out'.\n
		/// For further description of the return value, err and arg see 
		/// 'ree_lower(TADVector& arg, TDoubleVector& err)'.\n
		/// (NB) Previous contents of 'out' vector will be cleared in this function.
		int ree_lower(TADVector& arg, TADVector& func, TDoubleVector& err, TDoubleVector& out);

	private:
		///
		/// The active section we are in (NULL if there is none).
		REED_THREAD static CActiveSection* activeAS;

	private:
		///
		/// Current mode of code execution regarding this AS (the "state" of the AS).
		EMode mode;

		///
		/// TRUE if automatic caching is allowed for the CG in this AS.
		int bCacheEnabled;

	public:
		CMemoryPool<CNodeBase> alloc; ///< Special allocator for CG nodes.

		CNodeBase* pCurrent; ///< Pointer to current node (used to implement caching).
	};

} // namespace reed

#endif //__ASECTION_H__E079034E_E787_4946_8B13_17202FD9FEAD_INCLUDED_

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions