Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove grad and gradx members from variable exprs #177

Merged
merged 12 commits into from
Aug 24, 2021
9 changes: 5 additions & 4 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
conda install conda-devenv
conda devenv
source activate autodiff
cmake -S . -B .build
cmake -S . -B .build -DCMAKE_BUILD_TYPE=${{ env.configuration }} -DAUTODIFF_TEST_SANITIZE=ON
- name: Cache
id: cache
uses: actions/cache@v2
Expand All @@ -50,15 +50,16 @@ jobs:
source activate autodiff
ccache -s
ccache -z
cmake --build .build --config ${{ env.configuration }} --parallel 3
cmake --build .build --parallel 3
ccache -s
- name: Install
shell: bash -l {0}
run: |
source activate autodiff
cmake --build .build --config ${{ env.configuration }} --target install
cmake --build .build --target install
- name: Tests
shell: bash -l {0}
run: |
source activate autodiff
cmake --build .build --config ${{ env.configuration }} --target tests
cmake -S . -B .build -DCMAKE_BUILD_TYPE=Debug
cmake --build .build --target tests --parallel 3
9 changes: 5 additions & 4 deletions .github/workflows/osx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,23 @@ jobs:
shell: bash -l {0}
run: |
source activate autodiff
cmake -S . -B .build
cmake -S . -B .build -DCMAKE_BUILD_TYPE=${{ env.configuration }} -DAUTODIFF_TEST_SANITIZE=ON
- name: Build
shell: bash -l {0}
run: |
source activate autodiff
ccache -s
ccache -z
cmake --build .build --config ${{ env.configuration }} --parallel 3
cmake --build .build --parallel 3
ccache -s
- name: Install
shell: bash -l {0}
run: |
source activate autodiff
cmake --build .build --config ${{ env.configuration }} --target install
cmake --build .build --target install
- name: Test
shell: bash -l {0}
run: |
source activate autodiff
cmake --build .build --config ${{ env.configuration }} --target tests
cmake -S . -B .build -DCMAKE_BUILD_TYPE=Debug
cmake --build .build --target tests --parallel 3
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# The minimum required cmake version
cmake_minimum_required(VERSION 3.0)

# Set the cmake module path of the project
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
# Add cmake modules of this project to the module path
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)

# Use ccache to speed up repeated compilations
include(CCache)
Expand Down Expand Up @@ -58,5 +58,5 @@ if(AUTODIFF_BUILD_DOCS)
add_subdirectory(docs)
endif()

# Install the cmake config files that permit users to use find_package(Reaktoro)
# Install the cmake config files that permit users to use find_package(autodiff)
include(autodiffInstallCMakeConfigFiles)
45 changes: 31 additions & 14 deletions autodiff/reverse/var/eigen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,54 +103,71 @@ auto gradient(const Variable<T>& y, Eigen::DenseBase<X>& x)
constexpr auto MaxRows = X::MaxRowsAtCompileTime;

const auto n = x.size();
using Gradient = Vec<U, Rows, MaxRows>;
Gradient g = Gradient::Zero(n);

for(auto i = 0; i < n; ++i)
x[i].seed();
x[i].expr->bind_value(&g[i]);

y.expr->propagate(1.0);

Vec<U, Rows, MaxRows> g(n);
for(auto i = 0; i < n; ++i)
g[i] = val(x[i].grad());
x[i].expr->bind_value(nullptr);

return g;
}

/// Return the Hessian matrix of variable y with respect to variables x.
template<typename T, typename X, typename Vec>
auto hessian(const Variable<T>& y, Eigen::DenseBase<X>& x, Vec& g)
template<typename T, typename X, typename GradientVec>
auto hessian(const Variable<T>& y, Eigen::DenseBase<X>& x, GradientVec& g)
{
using U = VariableValueType<T>;

using ScalarX = typename X::Scalar;
static_assert(isVariable<ScalarX>, "Argument x is not a vector with Variable<T> (aka var) objects.");

using ScalarG = typename Vec::Scalar;
using ScalarG = typename GradientVec::Scalar;
static_assert(std::is_same_v<U, ScalarG>, "Argument g does not have the same arithmetic type as y.");

constexpr auto Rows = X::RowsAtCompileTime;
constexpr auto MaxRows = X::MaxRowsAtCompileTime;

const auto n = x.size();

// Form a vector containing gradient expressions for each variable
using ExpressionGradient = Vec<ScalarX, Rows, MaxRows>;
ExpressionGradient G(n);

for(auto k = 0; k < n; ++k)
x[k].seedx();
x[k].expr->bind_expr(&G(k).expr);

/* Build a full gradient expression in DFS tree traversal, updating
* gradient expressions when encountering variables
*/
y.expr->propagatex(constant<T>(1.0));

for(auto k = 0; k < n; ++k) {
x[k].expr->bind_expr(nullptr);
}

// Read the gradient value from gradient expressions' cached values
g.resize(n);
for(auto i = 0; i < n; ++i)
g[i] = val(x[i].gradx());
g[i] = val(G[i]);

Mat<U, Rows, Rows, MaxRows, MaxRows> H(n, n);
// Form a numeric hessian using the gradient expressions
using Hessian = Mat<U, Rows, Rows, MaxRows, MaxRows>;
Hessian H = Hessian::Zero(n, n);
for(auto i = 0; i < n; ++i)
{
for(auto k = 0; k < n; ++k)
x[k].seed();
x[k].expr->bind_value(&H(i, k));

auto dydxi = x[i].gradx();
dydxi->propagate(1.0);
// Propagate a second derivative value calculation down the gradient expression tree for variable i
G[i].expr->propagate(1.0);

for(auto j = i; j < n; ++j)
H(i, j) = H(j, i) = val(x[j].grad());
for(auto k = 0; k < n; ++k)
x[k].expr->bind_value(nullptr);
}

return H;
Expand Down
105 changes: 39 additions & 66 deletions autodiff/reverse/var/var.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ struct Expr
/// Destructor (to avoid warning)
virtual ~Expr() {}

/// Bind a value pointer for writing the derivative during propagation
virtual void bind_value(T* /* grad */) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short (Doxygen ///) comment here for these bind_* functions. I like your approach.

/// Bind an expression pointer for writing the derivative expression during propagation
virtual void bind_expr(ExprPtr<T>* /* gradx */) {}

/// Update the contribution of this expression in the derivative of the root node of the expression tree.
/// @param wprime The derivative of the root expression node w.r.t. the child expression of this expression node.
virtual void propagate(const T& wprime) = 0;
Expand All @@ -267,67 +272,61 @@ struct Expr
template<typename T>
struct VariableExpr : Expr<T>
{
/// The derivative of the root expression node with respect to this variable.
T grad = {};
/// The derivative value of the root expression node w.r.t. this variable.
T* gradPtr = {};

/// The derivative of the root expression node with respect to this variable (as an expression for higher-order derivatives).
ExprPtr<T> gradx = {};
/// The derivative expression of the root expression node w.r.t. this variable (reusable for higher-order derivatives).
ExprPtr<T>* gradxPtr = {};

/// Construct a VariableExpr object with given value.
VariableExpr(const T& v) : Expr<T>(v) {}

virtual void bind_value(T* grad) { gradPtr = grad; }
virtual void bind_expr(ExprPtr<T>* gradx) { gradxPtr = gradx; }
};

/// The node in the expression tree representing an independent variable.
template<typename T>
struct IndependentVariableExpr : VariableExpr<T>
{
// Using declarations for data members of base class
using VariableExpr<T>::grad;
using VariableExpr<T>::gradx;
using VariableExpr<T>::gradPtr;
using VariableExpr<T>::gradxPtr;

/// Construct an IndependentVariableExpr object with given value.
IndependentVariableExpr(const T& v) : VariableExpr<T>(v)
{
gradx = constant<T>(0.0); // TODO: Check if this can be done at the seed function.
}
IndependentVariableExpr(const T& v) : VariableExpr<T>(v) {}

virtual void propagate(const T& wprime)
{
grad += wprime;
virtual void propagate(const T& wprime) {
if(gradPtr) { *gradPtr += wprime; }
}

virtual void propagatex(const ExprPtr<T>& wprime)
{
gradx = gradx + wprime;
if(gradxPtr) { *gradxPtr = *gradxPtr + wprime; }
}
};

/// The node in the expression tree representing a dependent variable.
template<typename T>
struct DependentVariableExpr : VariableExpr<T>
{
// Using declarations for data members of base class
using VariableExpr<T>::grad;
using VariableExpr<T>::gradx;
using VariableExpr<T>::gradPtr;
using VariableExpr<T>::gradxPtr;

/// The expression tree that defines how the dependent variable is calculated.
ExprPtr<T> expr;

/// Construct an DependentVariableExpr object with given value.
DependentVariableExpr(const ExprPtr<T>& e) : VariableExpr<T>(e->val), expr(e)
{
gradx = constant<T>(0.0); // TODO: Check if this can be done at the seed function.
}
DependentVariableExpr(const ExprPtr<T>& e) : VariableExpr<T>(e->val), expr(e) {}

virtual void propagate(const T& wprime)
{
grad += wprime;
if(gradPtr) { *gradPtr += wprime; }
expr->propagate(wprime);
}

virtual void propagatex(const ExprPtr<T>& wprime)
{
gradx = gradx + wprime;
if(gradxPtr) { *gradxPtr = *gradxPtr + wprime; }
expr->propagatex(wprime);
}
};
Expand Down Expand Up @@ -1057,21 +1056,6 @@ struct Variable
/// Default copy assignment
Variable &operator=(const Variable &) = default;

/// Return a pointer to the underlying VariableExpr object in this variable.
auto __variableExpr() const { return static_cast<VariableExpr<T>*>(expr.get()); }

/// Return the derivative value stored in this variable.
auto grad() const { return __variableExpr()->grad; }

/// Return the derivative expression stored in this variable.
auto gradx() const { return __variableExpr()->gradx; }

/// Reeet the derivative value stored in this variable to zero.
auto seed() { __variableExpr()->grad = 0; }

/// Reeet the derivative expression stored in this variable to zero expression.
auto seedx() { __variableExpr()->gradx = constant<T>(0); }

/// Implicitly convert this Variable object into an expression pointer.
operator ExprPtr<T>() const { return expr; }

Expand Down Expand Up @@ -1280,37 +1264,22 @@ auto wrt(Args&&... args)
return Wrt<Args&&...>{ std::forward_as_tuple(std::forward<Args>(args)...) };
}

/// Seed each variable in the **wrt** list.
template<typename... Vars>
auto seed(const Wrt<Vars...>& wrt)
/// Return the derivatives of a dependent variable y with respect given independent variables.
template<typename T, typename... Vars>
auto derivatives(const Variable<T>& y, const Wrt<Vars...>& wrt)
{
constexpr static auto N = sizeof...(Vars);
For<N>([&](auto i) constexpr {
std::get<i>(wrt.args).seed();
});
}
constexpr auto N = sizeof...(Vars);
std::array<T, N> values;
values.fill(0.0);

/// Seed each variable in the **wrt** list.
template<typename... Vars>
auto seedx(const Wrt<Vars...>& wrt)
{
constexpr static auto N = sizeof...(Vars);
For<N>([&](auto i) constexpr {
std::get<i>(wrt.args).seedx();
std::get<i>(wrt.args).expr->bind_value(&values.at(i));
});
}

/// Return the derivatives of a dependent variable y with respect given independent variables.
template<typename T, typename... Vars>
auto derivatives(const Variable<T>& y, const Wrt<Vars...>& wrt)
{
seed(wrt);
y.expr->propagate(1.0);

constexpr static auto N = sizeof...(Vars);
std::array<T, N> values;
For<N>([&](auto i) constexpr {
values[i.index] = std::get<i>(wrt.args).grad();
std::get<i>(wrt.args).expr->bind_value(nullptr);
});

return values;
Expand All @@ -1320,13 +1289,17 @@ auto derivatives(const Variable<T>& y, const Wrt<Vars...>& wrt)
template<typename T, typename... Vars>
auto derivativesx(const Variable<T>& y, const Wrt<Vars...>& wrt)
{
seedx(wrt);
constexpr auto N = sizeof...(Vars);
std::array<Variable<T>, N> values;

For<N>([&](auto i) constexpr {
std::get<i>(wrt.args).expr->bind_expr(&values.at(i).expr);
});

y.expr->propagatex(constant<T>(1.0));

constexpr static auto N = sizeof...(Vars);
std::array<Variable<T>, N> values;
For<N>([&](auto i) constexpr {
values[i.index] = std::get<i>(wrt.args).gradx();
std::get<i>(wrt.args).expr->bind_expr(nullptr);
});

return values;
Expand Down
11 changes: 11 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,14 @@ add_custom_target(tests
COMMENT "Running C++ tests..."
COMMAND $<TARGET_FILE:autodiff-cpptests>
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

if(AUTODIFF_TEST_SANITIZE)
include(CheckCXXCompilerFlag)
set(CMAKE_REQUIRED_FLAGS "-fsanitize=address") # Also needs to be a link flag for test to work
check_cxx_compiler_flag(-fsanitize=address HAVE_ASAN)
unset(CMAKE_REQUIRED_FLAGS)
if(HAVE_ASAN)
target_compile_options(autodiff-cpptests PRIVATE "-fsanitize=address")
target_link_options(autodiff-cpptests PRIVATE "-fsanitize=address")
endif()
endif()