// Copyright Maksym Zhelyenzyakov 2025-2026. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_MATH_DIFFERENTIATION_AUTODIFF_HPP #define BOOST_MATH_DIFFERENTIATION_AUTODIFF_HPP #include #if defined(BOOST_MATH_REVERSE_MODE_ET_OFF) && defined(BOOST_MATH_REVERSE_MODE_ET_ON) #error "Cannot define both BOOST_MATH_REVERSE_MODE_ET_OFF and BOOST_MATH_REVERSE_MODE_ET_ON" #endif #if !defined(BOOST_MATH_REVERSE_MODE_ET_OFF) && !defined(BOOST_MATH_REVERSE_MODE_ET_ON) #define BOOST_MATH_REVERSE_MODE_ET_ON #endif #ifdef BOOST_MATH_REVERSE_MODE_ET_ON #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BOOST_MATH_BUFFER_SIZE 65536 namespace boost { namespace math { namespace differentiation { namespace reverse_mode { /* forward declarations for utitlity functions */ template struct expression; template class rvar; template struct abstract_binary_expression; template struct abstract_unary_expression; template class gradient_node; // forward declaration for tape // manages nodes in computational graph template class gradient_tape { /** @brief tape (graph) management class for autodiff * holds all the data structures for autodiff */ private: /* type decays to order - 1 to support higher order derivatives */ using inner_t = rvar_t; /* adjoints are the overall derivative, and derivatives are the "local" * derivative */ detail::flat_linear_allocator adjoints_; detail::flat_linear_allocator derivatives_; detail::flat_linear_allocator, buffer_size> gradient_nodes_; detail::flat_linear_allocator *, buffer_size> argument_nodes_; // compile time check if emplace_back calls on zero template gradient_node *fill_node_at_compile_time( std::true_type, gradient_node *node_ptr) { node_ptr->derivatives_ = derivatives_.template emplace_back_n(); node_ptr->argument_nodes_ = argument_nodes_.template emplace_back_n(); return node_ptr; } template gradient_node *fill_node_at_compile_time( std::false_type, gradient_node *node_ptr) { node_ptr->derivatives_ = nullptr; node_ptr->argument_adjoints_ = nullptr; node_ptr->argument_nodes_ = nullptr; return node_ptr; } public: /* gradient node stores iterators to its data memebers * (adjoint/derivative/arguments) so that in case flat linear allocator * reaches its block boundary and needs more memory for that node, the * iterator can be invoked to access it */ using adjoint_iterator = typename detail::flat_linear_allocator::iterator; using derivatives_iterator = typename detail::flat_linear_allocator::iterator; using gradient_nodes_iterator = typename detail::flat_linear_allocator, buffer_size>::iterator; using argument_nodes_iterator = typename detail::flat_linear_allocator *, buffer_size>::iterator; gradient_tape() { clear(); }; gradient_tape(const gradient_tape &) = delete; gradient_tape &operator=(const gradient_tape &) = delete; gradient_tape(gradient_tape &&other) = delete; gradient_tape operator=(gradient_tape &&other) = delete; ~gradient_tape() noexcept { clear(); } void clear() { adjoints_.clear(); derivatives_.clear(); gradient_nodes_.clear(); argument_nodes_.clear(); } // no derivatives or arguments gradient_node *emplace_leaf_node() { gradient_node *node = &*gradient_nodes_.emplace_back(); node->adjoint_ = adjoints_.emplace_back(); node->derivatives_ = derivatives_iterator(); // nullptr; node->argument_nodes_ = argument_nodes_iterator(); // nullptr; return node; }; // single argument, single derivative gradient_node *emplace_active_unary_node() { gradient_node *node = &*gradient_nodes_.emplace_back(); node->n_ = 1; node->adjoint_ = adjoints_.emplace_back(); node->derivatives_ = derivatives_.emplace_back(); return node; }; // arbitrary number of arguments/derivatives (compile time) template gradient_node *emplace_active_multi_node() { gradient_node *node = &*gradient_nodes_.emplace_back(); node->n_ = n; node->adjoint_ = adjoints_.emplace_back(); // emulate if constexpr return fill_node_at_compile_time(std::integral_constant 0)>{}, node); } // same as above at runtime gradient_node *emplace_active_multi_node(size_t n) { gradient_node *node = &*gradient_nodes_.emplace_back(); node->n_ = n; node->adjoint_ = adjoints_.emplace_back(); if (n > 0) { node->derivatives_ = derivatives_.emplace_back_n(n); node->argument_nodes_ = argument_nodes_.emplace_back_n(n); } return node; }; /* manual reset button for all adjoints */ void zero_grad() { const RealType zero = RealType(0.0); adjoints_.fill(zero); } // return type is an iterator auto begin() { return gradient_nodes_.begin(); } auto end() { return gradient_nodes_.end(); } auto find(gradient_node *node) { return gradient_nodes_.find(node); }; void add_checkpoint() { gradient_nodes_.add_checkpoint(); adjoints_.add_checkpoint(); derivatives_.add_checkpoint(); argument_nodes_.add_checkpoint(); }; auto last_checkpoint() { return gradient_nodes_.last_checkpoint(); }; auto first_checkpoint() { return gradient_nodes_.last_checkpoint(); }; auto checkpoint_at(size_t index) { return gradient_nodes_.get_checkpoint_at(index); }; void rewind_to_last_checkpoint() { gradient_nodes_.rewind_to_last_checkpoint(); adjoints_.rewind_to_last_checkpoint(); derivatives_.rewind_to_last_checkpoint(); argument_nodes_.rewind_to_last_checkpoint(); }; void rewind_to_checkpoint_at(size_t index) // index is "checkpoint" index. so // order which checkpoint was set { gradient_nodes_.rewind_to_checkpoint_at(index); adjoints_.rewind_to_checkpoint_at(index); derivatives_.rewind_to_checkpoint_at(index); argument_nodes_.rewind_to_checkpoint_at(index); } // rewind to beginning of computational graph void rewind() { gradient_nodes_.rewind(); adjoints_.rewind(); derivatives_.rewind(); argument_nodes_.rewind(); } // random acces gradient_node &operator[](size_t i) { return gradient_nodes_[i]; } const gradient_node &operator[](size_t i) const { return gradient_nodes_[i]; } }; // class rvar; template // no CRTP, just storage class gradient_node { /* * @brief manages adjoints, derivatives, and stores points to argument * adjoints pointers to arguments aren't needed here * */ public: using adjoint_iterator = typename gradient_tape::adjoint_iterator; using derivatives_iterator = typename gradient_tape::derivatives_iterator; using argument_nodes_iterator = typename gradient_tape::argument_nodes_iterator; private: size_t n_; using inner_t = rvar_t; /* these are iterators in case * flat linear allocator is at capacity, and needs to allocate a new block of * memory. */ adjoint_iterator adjoint_; derivatives_iterator derivatives_; argument_nodes_iterator argument_nodes_; public: friend class gradient_tape; friend class rvar; gradient_node() = default; explicit gradient_node(const size_t n) : n_(n) , adjoint_(nullptr) , derivatives_(nullptr) {} explicit gradient_node(const size_t n, RealType *adjoint, RealType *derivatives, rvar **arguments) : n_(n) , adjoint_(adjoint) , derivatives_(derivatives) { static_cast(arguments); } inner_t get_adjoint_v() const { return *adjoint_; } inner_t get_derivative_v(size_t arg_id) const { return derivatives_[static_cast(arg_id)]; }; inner_t get_argument_adjoint_v(size_t arg_id) const { return *argument_nodes_[static_cast(arg_id)]->adjoint_; } adjoint_iterator get_adjoint_ptr() { return adjoint_; } adjoint_iterator get_adjoint_ptr() const { return adjoint_; }; void update_adjoint_v(inner_t value) { *adjoint_ = value; }; void update_derivative_v(size_t arg_id, inner_t value) { derivatives_[static_cast(arg_id)] = value; }; void update_argument_adj_v(size_t arg_id, inner_t value) { argument_nodes_[static_cast(arg_id)]->update_adjoint_v(value); }; void update_argument_ptr_at(size_t arg_id, gradient_node *node_ptr) { argument_nodes_[static_cast(arg_id)] = node_ptr; } void backward() { if (!n_) // leaf node return; using boost::math::differentiation::reverse_mode::fabs; using std::fabs; if (!adjoint_ || fabs(*adjoint_) < 2 * std::numeric_limits::epsilon()) return; if (!argument_nodes_) // no arguments return; if (!derivatives_) // no derivatives return; for (size_t i = 0; i < n_; ++i) { auto adjoint = get_adjoint_v(); auto derivative = get_derivative_v(i); auto argument_adjoint = get_argument_adjoint_v(i); update_argument_adj_v(i, argument_adjoint + derivative * adjoint); } } }; /****************************************************************************************************************/ template inline gradient_tape &get_active_tape() { static BOOST_MATH_THREAD_LOCAL gradient_tape tape; return tape; } template class rvar : public expression> { private: using inner_t = rvar_t; friend class gradient_node; inner_t value_; gradient_node *node_ = nullptr; template friend class rvar; /*****************************************************************************************/ /** * @brief implementation helpers for get_value_at */ template struct get_value_at_impl { static_assert(target_order <= current_order, "Requested depth exceeds variable order."); /** @return value_ at rvar_t */ static auto &get(rvar &v) { return get_value_at_impl::get(v.get_value()); } /** @return const value_ at rvar_t */ static const auto &get(const rvar &v) { return get_value_at_impl::get(v.get_value()); } }; /** @brief base case specialization for target_order == current order */ template struct get_value_at_impl { /** @return value_ at rvar_t */ static auto &get(rvar &v) { return v; } /** @return const value_ at rvar_t */ static const auto &get(const rvar &v) { return v; } }; /*****************************************************************************************/ void make_leaf_node() { gradient_tape &tape = get_active_tape(); node_ = tape.emplace_leaf_node(); } void make_unary_node() { gradient_tape &tape = get_active_tape(); node_ = tape.emplace_active_unary_node(); } void make_multi_node(size_t n) { gradient_tape &tape = get_active_tape(); node_ = tape.emplace_active_multi_node(n); } template void make_multi_node() { gradient_tape &tape = get_active_tape(); node_ = tape.template emplace_active_multi_node(); } template void make_rvar_from_expr(const expression &expr) { make_multi_node>(); expr.template propagatex<0>(node_, inner_t(static_cast(1.0))); } RealType get_item_impl(std::true_type) const { return value_.get_item_impl(std::integral_constant 1)>{}); } RealType get_item_impl(std::false_type) const { return value_; } public: using value_type = RealType; static constexpr size_t DerivativeOrder_v = DerivativeOrder; rvar() : value_() { make_leaf_node(); } rvar(const RealType value) : value_(inner_t{static_cast(value)}) { make_leaf_node(); } rvar &operator=(RealType v) { value_ = inner_t(v); if (node_ == nullptr) { make_leaf_node(); } return *this; } rvar(const rvar &other) = default; rvar &operator=(const rvar &other) = default; template void propagatex(gradient_node *node, inner_t adj) const { node->update_derivative_v(arg_index, adj); node->update_argument_ptr_at(arg_index, node_); } template rvar(const expression &expr) { value_ = expr.evaluate(); make_rvar_from_expr(expr); } template && !is_same_v>> rvar(T v) : value_(inner_t{static_cast(v)}) { make_leaf_node(); } template rvar &operator=(const expression &expr) { value_ = expr.evaluate(); make_rvar_from_expr(expr); return *this; } /***************************************************************************************************/ template rvar &operator+=(const expression &expr) { *this = *this + expr; return *this; } template rvar &operator*=(const expression &expr) { *this = *this * expr; return *this; } template rvar &operator-=(const expression &expr) { *this = *this - expr; return *this; } template rvar &operator/=(const expression &expr) { *this = *this / expr; return *this; } /***************************************************************************************************/ rvar &operator+=(const RealType &v) { *this = *this + v; return *this; } rvar &operator*=(const RealType &v) { *this = *this * v; return *this; } rvar &operator-=(const RealType &v) { *this = *this - v; return *this; } rvar &operator/=(const RealType &v) { *this = *this / v; return *this; } /***************************************************************************************************/ const inner_t &adjoint() const { return *node_->get_adjoint_ptr(); } inner_t &adjoint() { return *node_->get_adjoint_ptr(); } const inner_t &evaluate() const { return value_; }; inner_t &get_value() { return value_; }; explicit operator RealType() const { return item(); } explicit operator int() const { return static_cast(item()); } explicit operator long() const { return static_cast(item()); } explicit operator long long() const { return static_cast(item()); } /** * @brief same as evaluate but returns proper depth for higher order derivatives * @return value_ at depth N */ template auto &get_value_at() { static_assert(N <= DerivativeOrder, "Requested depth exceeds variable order."); return get_value_at_impl::get(*this); } /** @brief same as above but const */ template const auto &get_value_at() const { static_assert(N <= DerivativeOrder, "Requested depth exceeds variable order."); return get_value_at_impl::get(*this); } RealType item() const { return get_item_impl(std::integral_constant 1)>{}); } void backward() { gradient_tape &tape = get_active_tape(); auto it = tape.find(node_); it->update_adjoint_v(inner_t(static_cast(1.0))); while (it != tape.begin()) { it->backward(); --it; } it->backward(); } }; template std::ostream &operator<<(std::ostream &os, const rvar var) { os << "rvar<" << DerivativeOrder << ">(" << var.item() << "," << var.adjoint() << ")"; return os; } template std::ostream &operator<<(std::ostream &os, const expression &expr) { rvar tmp = expr; os << "rvar<" << DerivativeOrder << ">(" << tmp.item() << "," << tmp.adjoint() << ")"; return os; } template rvar make_rvar(const RealType v) { static_assert(DerivativeOrder > 0, "rvar order must be >= 1"); return rvar(v); } template rvar make_rvar(const expression &expr) { static_assert(DerivativeOrder > 0, "rvar order must be >= 1"); return rvar(expr); } namespace detail { /** @brief helper overload for grad implementation. * @return vector of gradients of the autodiff graph. * specialization for autodiffing through autodiff. i.e. being able to * compute higher order grads */ template struct grad_op_impl { std::vector> operator()( rvar &f, std::vector *> &x) { auto &tape = get_active_tape(); tape.zero_grad(); f.backward(); std::vector> gradient_vector; gradient_vector.reserve(x.size()); for (auto &xi : x) { gradient_vector.emplace_back(xi->adjoint()); } return gradient_vector; } }; /** @brief helper overload for grad implementation. * @return vector of gradients of the autodiff graph. * base specialization for order 1 autodiff */ template struct grad_op_impl { std::vector operator()(rvar &f, std::vector *> &x) { gradient_tape &tape = get_active_tape(); tape.zero_grad(); f.backward(); std::vector gradient_vector; gradient_vector.reserve(x.size()); for (auto &xi : x) { gradient_vector.push_back(xi->adjoint()); } return gradient_vector; } }; /** @brief helper overload for higher order autodiff * @return nested vector representing N-d tensor of * higher order derivatives */ template struct grad_nd_impl { auto operator()(rvar &f, std::vector *> &x) { static_assert(N > 1, "N must be greater than 1 for this template"); auto current_grad = grad(f, x); // vector> or vector std::vector()( current_grad[0], x))> result; result.reserve(current_grad.size()); for (auto &g_i : current_grad) { result.push_back( grad_nd_impl()(g_i, x)); } return result; } }; /** @brief spcialization for order = 1, * @return vector> gradients */ template struct grad_nd_impl<1, RealType, DerivativeOrder_1, DerivativeOrder_2> { auto operator()(rvar &f, std::vector *> &x) { return grad(f, x); } }; template struct rvar_order; template struct rvar_order *> { static constexpr size_t value = DerivativeOrder; }; } // namespace detail /** * @brief grad computes gradient with respect to vector of pointers x * @param f -> computational graph * @param x -> variables gradients to record. Note ALL gradients of the graph * are computed simultaneously, only the ones w.r.t. x are returned * @return vector of gradients. in the case of DerivativeOrder_1 = 1 * rvar decays to T * * safe to call recursively with grad(grad(grad... */ template auto grad(rvar &f, std::vector *> &x) { static_assert(DerivativeOrder_1 <= DerivativeOrder_2, "variable differentiating w.r.t. must have order >= function order"); std::vector *> xx; xx.reserve(x.size()); for (auto &xi : x) xx.push_back(&(xi->template get_value_at())); return detail::grad_op_impl{}(f, xx); } /** @brief variadic overload of above */ template auto grad(rvar &f, First first, Other... other) { constexpr size_t DerivativeOrder_2 = detail::rvar_order::value; static_assert(DerivativeOrder_1 <= DerivativeOrder_2, "variable differentiating w.r.t. must have order >= function order"); std::vector *> x_vec = {first, other...}; return grad(f, x_vec); } /** @brief computes hessian matrix of computational graph w.r.t. * vector of variables x. * @return std::vector> hessian matrix * rvar decays to T * * NOT recursion safe, cannot do hess(hess( */ template auto hess(rvar &f, std::vector *> &x) { return detail::grad_nd_impl<2, RealType, DerivativeOrder_1, DerivativeOrder_2>{}(f, x); } /** @brief variadic overload of above */ template auto hess(rvar &f, First first, Other... other) { constexpr size_t DerivativeOrder_2 = detail::rvar_order::value; std::vector *> x_vec = {first, other...}; return hess(f, x_vec); } /** @brief comput N'th gradient of computational graph w.r.t. x * @return vector auto grad_nd(rvar &f, std::vector *> &x) { static_assert(DerivativeOrder_1 >= N, "Function order must be at least N"); static_assert(DerivativeOrder_2 >= DerivativeOrder_1, "Variable order must be at least function order"); return detail::grad_nd_impl()(f, x); } /** @brief variadic overload of above */ template auto grad_nd(ftype &f, First first, Other... other) { using RealType = typename ftype::value_type; constexpr size_t DerivativeOrder_1 = detail::rvar_order::value; constexpr size_t DerivativeOrder_2 = detail::rvar_order::value; std::vector *> x_vec = {first, other...}; return detail::grad_nd_impl{}(f, x_vec); } } // namespace reverse_mode } // namespace differentiation } // namespace math } // namespace boost namespace std { // copied from forward mode template class numeric_limits> : public numeric_limits::value_type> {}; } // namespace std #endif