Description
GTPSA, designed for high order AD using the truncated power series TPS
structure, does not support nesting of derivatives. This is intentional because at high orders it is it is always faster to use a TPS with higher order truncation order, than to nest TPSs of low truncation orders. However, this can make it challenging to compute in a backend agnostic way nested derivatives. Consider the following scenario:
using DifferentiationInterface, ForwardDiff, GTPSA
function foo(x0, k)
x_1 = 1*x0[1] + 2*x0[2] + 5*k*x0[1] + 6*k*x0[2]
x_2 = 3*x0[1] + 4*x0[2] + 7*k*x0[1] + 8*k*x0[2]
return [x_1, x_2]
end
k = 1
x0 = [0, 0]
x = foo(x0, k)
# Now, suppose we want the dependence of x on the parameter k:
# Using ForwardDiff:
derivative(k->jacobian(xk->foo(xk[1:2],xk[3]), AutoForwardDiff(), [0,0,k]), AutoForwardDiff(), 0)
#= Output:
2×3 Matrix{Int64}:
5 6 0
7 8 0
=#
# To use GTPSA (not possible now using Differentiation interface):
const D = Descriptor(3,2) # 3 variables, 2nd order
Δx = @vars(D)[1:2]
Δk = @vars(D)[3]
x_TPS = foo(x0+Δx, k+Δk)
# GTPSA.jacobian, just extracts the coefficients of the TPS, while derivative
# returns a TPS after taking a derivative wrt variable 3
GTPSA.jacobian(GTPSA.deriv.(x_TPS, 3))
#= Output:
2×3 Matrix{Float64}:
5.0 6.0 0.0
7.0 8.0 0.0
=#
One option I imagine that could work is to use a flag for the internal jacobian, specifying that there is nesting. Then, for nested Jacobian, instead of return a matrix of Float64
s, GTPSA would return a matrix of its TPS
type, which then the outer jacobian can extract derivatives from. Though this solution would require the outermost jacobian to know the number of internal derivatives/order at which to truncated the TPS at, which is not the most generic. I wonder if there has been any thought or work towards finding a generic way to handle nested derivatives?