Tape¶
Tape
¶
template <typename T> class Tape;
Tape data type to record operations for adjoint computations, using the underlying scalar type T
(which may in turn be an active type for higher-order derivative calculations).
Typical usage
Tape<double> tape;
// initialize independent variables
AReal<double> x1 = 1.2, x2 = 12.1;
// register independents with the tape
tape.registerInput(x1);
tape.registerInput(x2);
// start recording derivatives on tape
tape.newRecording();
AReal<double> y = sin(x1) + x1*x2;
// register output and set adjoint values
tape.registerOutput(y);
derivative(y) = 1.0;
// compute the adjoints of the independent variables
tape.computeAdjoints();
// output/use results
std::cout << "y = " << value(y) << "\n"
<< "dy/dx1 = " << derivative(x1) << "\n"
<< "dy/dx2 = " << derivative(x2) << "\n";
For usability, it is recommended to use the type definitions decribed in AD Mode Interfaces instead of using this tape type directly.
Member Typedefs¶
size_type
¶
Type for sizes
slot_type
¶
Type used to represent a slot of a specific active variable
position_type
¶
Type to represent a position in the tape (same as slot_type
)
active_type
¶
Active data type that records on this type of tape
value_type
¶
The value type of the tape, i.e. T
tape_type
¶
The tape's type itself - for generic code
callback_type
¶
The callback type used for checkpoints, i.e. CheckpointCallback<tape_type>*
Construct, Destruct, and Assign¶
A tape can be created and moved, but it is not copyable.
explicit Tape(bool activate = true); // (1) constructor
Tape(Tape&&); // (2) move-constructor
Tape& operator=(Tape&&); // (3) move-assignment
~Tape(); // (4) destructor
The constructor (1)
constructs a new tape, and activates it if needed. If active
is true
, a global thread-local pointer is set to this constructed instance, resulting all operations and instantiations of active data types that follow to get automatically associated with this tape instance.
It may throw TapeAlreadyActive
, if activate
is true and another tape is already active for the current thread
The other constructors facilitate moving a tape, but it cannot be copied.
Recording Control¶
activate
¶
void activate()
sets a global thread-local pointer to this tape instance, resulting all registerInput
calls and operations of active data types depending on such inputs to get associated with this tape instance.
It may throw TapeAlreadyActive
if another tape is already active for the current thread.
deactivate
¶
void deactivate()
resets the global thread-local pointer to NULL, hence deactivating this tape.
isActive
¶
bool isActive() const
check if the current instance is the currently active tape.
getActive
¶
static Tape* getActive()
get a pointer to the currently active tape, or !c++ nullptr
if no active tape has been set.
Note that this is a thread-local pointer - calling this function in different threads gives different results.
setActive
¶
static void setActive(Tape* t)
static function that sets the given tape as the globally active one. This is equivalent to t.activate()
.
It may throw TapeAlreadyActive
if another tape is already active for the current thread.
deactivateAll
¶
static void deactivateAll()
deactivates any currently active tapes. Equivalent to auto t = Tape::getActive(); if (t) t->deactivate();
.
registerInput
¶
void registerInput(active_type& inp)
registers the given variable with the tape and start recording dependents of it. A call to this function or its overloads is required in order to calculate adjoints.
Other overloads are:
void registerInput(std::complex<active_type>& inp)
for complex values
registerInputs
¶
template <typename Inner> void registerInputs(std::vector<Inner>& v)
is a convenience function to register all variables in a vector as an input.
template <typename It> void registerInputs(It first, It last)
is a convenience iterator interface to register variables in a range with the tape.
registerOutput
¶
void registerOutput(active_type& inp)
registers the given variable as an output with the tape. A call to this function or its overloads is required in order to allow seeding derivatives (adjoints).
Other overloads are:
void registerOutput(std::complex<active_type>& inp)
registers a complex-valued output.
registerOutputs
¶
template <typename Inner> void registerOutputs(std::vector<Inner>& v)
is a convenience function to register all variables in a vector as an input.
template <typename It> void registerOutputs(It first, It last)
is a convenience iterator interface to register variables in a range with the tape.
newRecording
¶
void newRecording()
starts recording derivatives.
This function should be called after the independent variables are initialized and registered, as the computeAdjoints
method will roll back the adjoints until the point where newRecording
was called.
computeAdjoints
¶
void computeAdjoints()
propagates adjoints by interpreting the operations on the tape.
This function should be called after the output derivatives (adjoints) have been initialized to a non-zero value.
After this call, the derivatives of the independent variables are set and can be obtained.
It throws DerivativesNotInitialized
if called without setting any derivative first.
Gives strong exception safety guarantee - tape state unchanged in case of exception.
getPosition
¶
position_type getPosition()
returns the current position in the tape as an opaque integer (its value is internal and should not be relied upon in client code). This posiiton can later be used in the methods clearDerivativesAfter
, resetTo
, and computeAdjointsTo
.
clearDerivativesAfter
¶
void clearDerivativesAfter(position_type pos)
clears all derivatives after the given position in the tape (resets them to zero). Derivatives before this point keep their value, meaning that further calls to computeAdjoints
will potentially increment these adjoints further.
resetTo
¶
void resetTo(position_type pos)
resets the tape back to the given position. All statements recorded after this point will be discarded.
Warning
If variables registered after the given postion (i.e. dependent variables computed after this position) are used again after a call to resetTo
, the behaviour is undefined, as their slot in the tape is no longer valid.
computeAdjointsTo
¶
void computeAdjointsTo(position_type pos)
works like computeAdjoints
, but stops rolling back the adjoints at the given position in the tape.
clearAll
¶
void clearAll()
clears the stored tape info and brings it back to its initial state.
While this clears the content, it leaves allocated memory untouched. This may be a performance gain compared to repeated construction/destruction of tapes of the same time, for example in a path-wise AD Monte-Carlo.
Derivatives¶
derivative
¶
T& derivative(slot_type s)
gets a reference to the derivative associated with the slot s
.
It throws OutOfRange
if the given slot is not associated with a stored derivative. (Note that it is only thrown in debug mode for performance reasons, otherwise the behaviour is undefined in this case)
Gives strong exception safety guarantee - tape state unchanged in case of exception.
The const version const T& derivative(slot_type s) const
gets a const reference to the derivative associated with the slot s
and otherwise behaves the same.
getDerivative
¶
T getDerivative(slot_type s) const
gets a copy of the value of the derivative associated with the slot s
.
It throws OutOfRange
if the given slot is not associated with a stored derivative. (Note that it is only thrown in debug mode for performance reasons, otherwise the behaviour is undefined in this case)
Gives strong exception safety guarantee - tape state unchanged in case of exception.
setDerivative
¶
void setDerivative(slot_type s, const T& v)
sets the value of the derivative associated with the slot s
to v
.
It throws OutOfRange
if the given slot is not associated with a stored derivative. (Note that it is only thrown in debug mode for performance reasons, otherwise the behaviour is undefined in this case)
Gives strong exception safety guarantee - tape state unchanged in case of exception.
clearDerivatives
¶
void clearDerivatives()
resets all stored derivatives to 0 (but leaving the recorded data in place). This can be used to calculate derivatives w.r.t. multiple outputs, as the same tape can be rolled back multiple times.
Status¶
printStatus
¶
void printStatus() const
prints the number of recorded operations, statements, and registered variables to stdout.
getMemory
¶
std::size_t getMemory() const
returns the memory in bytes that is occupied by the tape.
Checkpointing¶
insertCallback
¶
void insertCallback(callback_type cb)
inserts a checkpoint callback into the tape.
During computing adjoints (computeAdjoints
), this callback is called when the tape reaches the current position, allowing users to implement their own adjoint computation.
Note that the parameter is provided by pointer (callback_type
is a pointer), but the tape does not take ownership. It is the responsibility of the user to free the memory for the callback object. Alternatively, the Checkpoint Callback Memory Management API can be used to have the tape destroy the callbacks automatically.
getAndResetOutputAdjoint
¶
T getAndResetOutputAdjoint(slot_type slot)
obtains the output adjoint stored in slot
and resets it to 0.
This function should be called by CheckpointCallback<TapeType>::computeAdjoints
to get the current value of the adjoint. It also resets its adjoint to 0 on the tape to allow re-use of that variable.
It throws OutOfRange
if the given slot is not associated with a stored derivative. (Note that it is only thrown in debug mode, otherwise the behaviour is undefined)
Gives strong exception safety guarantee - tape state unchanged in case of exception.
incrementAdjoint
¶
void incrementAdjoint(slot_type slot, const T& x)
increments the adjoint of the given slot by the value x
.
This function should be called at the end of a CheckpointCallback<TapeType>::computeAdjoints
implementation, to update the input adjoints with the computed adjoint increments.
It throws OutOfRange
if the given slot is not associated with a stored derivative. (Note that it is only thrown in debug mode, otherwise the behaviour is undefined)
Gives strong exception safety guarantee - tape state unchanged in case of exception.
newNestedRecording
¶
void newNestedRecording()
starts a new nested recording that can be rolled-back on its own. It must be ended with endNestedRecording
.
It is intended for use within a CheckpointCallback<TapeType>::computeAdjoints
implementation, when from a checkpoint, the adjoints are computed using XAD in a nested recording.
To avoid forgetting the call to endNestedRecording
, consider using the RAII class ScopedNestedRecording
.
endNestedRecording
¶
void endNestedRecording()
ends a nested recording.
Checkpoint Callback Memory Management¶
pushCallback
¶
void pushCallback(callback_type cb)
lets this tape handle the de-allocation of the given callback, destroying the dynamically-allocated cb
.
When the tape is destructed, it also destructs all callbacks that have been registered using this function.
Use this if checkpoints are created in a stateless function to avoid having to track and destroy checkpoint callbacks manually.
getLastCallback
¶
callback_type getLastCallback()
obtains the last CheckpointCallback
object that has been pushed with pushCallback
.
This can be useful if multiple subsequent checkpoints can be added to the same checkpoint callback object.
Throws OutOfRange
: if the callback stack is empty.
Gives strong exception safety guarantee - tape state unchanged in case of exception.
getNumCallbacks
¶
size_type getNumCallbacks() const
gets the number of callback objects that have been pushed by pushCallback
.
haveCallbacks
¶
bool haveCallbacks() const
checks if there have been any checkpoint callbacks registered by pushCallback
.
popCallback
¶
void popCallback()
removes the callback object that has been last pushed by pushCallback
. It throws OutOfRange
if the stack of callbacks is empty
Gives strong exception safety guarantee - tape state unchanged in case of exception.
ScopedNestedRecording
¶
Convenience RAII class to ensure that a call to Tape<T>::newNestedRecording
is always followed by the corresponding Tape<T>::endNestedRecording
.
It should be constructed on the stack. On creation it starts a nested recording on the corresponding tape, and on destruction it ends the nested recording. This is useful for checkpoint callbacks, i.e. within the implementation of CheckpointCallback<TapeType>::computeAdjoints
.
Construct and Destruct¶
The constructor starts a new nested recording on the given tape and track it with this object. It calls newNestedRecording
on the given tape.
The destructor calles endNestedRecording
on the given tape automatically.
Member Functions¶
computeAdjoints
¶
void computeAdjoints()
computes adjoints within the nested recording.
incrementAdjoint
¶
void incrementAdjoint(TapeType::slot_type slot, const TapeType::value_type& value)
increments the adjoint given by the slot by the given value, similar to Tape<T>::incrementAdjoint
.
getTape
¶
TapeType* getTape()
returns the underlying tape for this nested recording.