CoDiPack  2.2.0
A Code Differentiation Package
SciComp TU Kaiserslautern
Loading...
Searching...
No Matches
Tutorial 5 - Repeated tape recordings

Goal: Be aware of possible side effects in multiple reverse evaluations

Prerequisite: Tutorial 2 - Reverse mode AD

Function:

template<typename Real>
Real func(const Real& x, bool updateGlobal, Real& global) {
Real t = x * x;
if(updateGlobal) {
global = t * x;
}
Real t2 = x + t;
return global * t2;
}
Represents a concrete lvalue in the CoDiPack expression tree.
Definition: activeType.hpp:52

Full code:

#include <codi.hpp>
#include <iostream>
template<typename Real>
Real func(const Real& x, bool updateGlobal, Real& global) {
Real t = x * x;
if(updateGlobal) {
global = t * x;
}
Real t2 = x + t;
return global * t2;
}
template<typename Real>
void run() {
using Tape = typename Real::Tape;
Real global = Real(0.0);
Real x = 4.0;
Tape& tape = Real::getTape();
tape.setActive();
// Step 1: Compute the gradient and update the global variable
tape.registerInput(x);
Real y = func(x, true, global);
tape.registerOutput(y);
tape.setPassive();
y.setGradient(1.0);
tape.evaluate();
std::cout << "Update global:" << std::endl;
std::cout << "f(4.0) = " << y << std::endl;
std::cout << "df/dx(4.0) = " << x.getGradient() << std::endl;
tape.reset();
// Step 2: Compute the gradient but do not update the global variable.
tape.setActive();
tape.registerInput(x);
y = func(x, false, global);
tape.registerOutput(y);
tape.setPassive();
y.setGradient(1.0);
tape.evaluate();
std::cout << "No update global:" << std::endl;
std::cout << "f(4.0) = " << y << std::endl;
std::cout << "df/dx(4.0) = " << x.getGradient() << std::endl;
tape.reset();
// Step 3: Reset the identifier on the global variable.
tape.setActive();
tape.deactivateValue(global);
tape.registerInput(x);
y = func(x, false, global);
tape.registerOutput(y);
tape.setPassive();
y.setGradient(1.0);
tape.evaluate();
std::cout << "No update global with reset:" << std::endl;
std::cout << "f(4.0) = " << y << std::endl;
std::cout << "df/dx(4.0) = " << x.getGradient() << std::endl;
tape.reset();
}
int main(int nargs, char** args) {
std::cout << "With linear index management:" << std::endl;
run<codi::RealReverse>();
std::cout << std::endl;
std::cout << "With index reuse management:" << std::endl;
run<codi::RealReverseIndex>();
return 0;
}
static Tape & getTape()
Get a reference to the tape which manages this expression.
Definition: activeType.hpp:99
T_Tape Tape
See ActiveType.
Definition: activeType.hpp:55
void setGradient(Gradient const &g)
Set the gradient of this lvalue in the tape.
Definition: lhsExpressionInterface.hpp:120
Gradient getGradient() const
Get the gradient of this lvalue from the tape.
Definition: lhsExpressionInterface.hpp:115

The example in Tutorial 2 already covers everything that is required for multiple tape recordings. Most importantly, it explains that only a call to reset will completely delete the last recorded function evaluation (setActive does not reset a CoDiPack tape). In the context of functions with no side effects this will work without any problems. Though problems can occur if the functions have side effects and left over values from old computations are used during the tape recording.

The problem is caused by the linear index management of the CoDiPack codi::RealReverse type. Identifiers from this index managers are not global and reused during each new tape recording (after a reset of the tape). CoDiPack can not check whether an identifier is generated during the current recording or if it is an old one. A variable contains an old identifier if the variable has not been overwritten in the current recording and was active in the previous one.

This is demonstrated in the code above. The global variable in func is only updated if the parameter is true. In Step 1 the global variable is updated which creates the side effect. Step 2 still uses, but does not update the global variable and it is should be treated as a constant in the CoDiPack taping process. However, since the variable has not been reset, it still holds the old identifier and the result is wrong ( $4^3(4+4^2)\neq 756$). In Step 3 the identifier of the global variable is reset with a call to deactivateValue. Afterwards the derivative result is correct (576).

The same evaluation is done with the codi::RealReverseIndex type. With this type the result is correct without handling the side effect. The codi::RealReverseIndex type uses a reuse index manager and these identifiers are global. Therefore, on a tape reset the identifiers are not invalidated. Nevertheless, it is still a good idea to deactivate unused values, such that the run time activity analysis of CoDiPack only records values, that are dependent on the registered input variables.

Have a look at Active type overview for the properties of the default CoDiPack types.