Description
When f does not propagate the input through GTPSA's TPS (e.g. f(x) = 42.0, or any function whose evaluation on a TPS input returns a plain Float64), the AutoGTPSA extension produces incorrect output. Two symptoms are visible, both rooted in the extension assuming f(xt) is always a TPS:
gradient, jacobian, hessian, hvp, second_derivative, value_and_gradient raise MethodError.
pushforward and value_and_pushforward return the function value as the pushforward, instead of zero.
Native GTPSA.gradient!/jacobian! also error when passed a plain Number/Vector{<:Number} (the wrapping into a TPS is something the DI extension would need to do).
MWE
using DifferentiationInterface
using GTPSA: GTPSA
backend = AutoGTPSA()
x = [1.0, 2.0]
v = [0.5, 0.7]
fc(x) = 42.0 # scalar-output, no dependence on x
fv(x) = [42.0, 7.0] # vector-output, no dependence on x
# 1. Errors --------------------------------------------------------------
gradient(fc, backend, x) # MethodError
jacobian(fv, backend, x) # MethodError
hessian(fc, backend, x) # MethodError
hvp(fc, backend, x, (v,)) # MethodError
second_derivative(t -> 42.0, backend, 1.5) # BoundsError
# 2. Silently wrong values -----------------------------------------------
pushforward(fc, backend, x, (v,))[1] # returns 42.0, expected 0.0
pushforward(fv, backend, x, (v,))[1] # returns [42.0, 7.0], expected [0.0, 0.0]
value_and_pushforward(fc, backend, x, (v,)) # returns (42.0, (42.0,)), expected (42.0, (0.0,))
Workaround: force TPS propagation, e.g. f(x) = 0*x[1] + 42.0. Then gradient returns [0.0, 0.0] and pushforward returns 0.0, as expected.
Stacktraces
gradient(fc, AutoGTPSA(), [1.0, 2.0])
ERROR: MethodError: no method matching gradient!(::Vector{Float64}, ::Float64; include_params::Bool, unsafe_inbounds::Bool)
The function `gradient!` exists, but no method is defined for this combination of argument types.
Closest candidates are:
gradient!(::Any, !Matched::GTPSA.TPS; include_params, unsafe_inbounds)
@ GTPSA ~/.julia/packages/GTPSA/IhdRA/src/getset.jl:362
Stacktrace:
[1] gradient(::Function, ::DifferentiationInterfaceGTPSAExt.GTPSAOneArgGradientPrep{...}, ::AutoGTPSA{Nothing}, ::Vector{Float64})
@ DifferentiationInterfaceGTPSAExt ~/dev/DifferentiationInterface.jl/DifferentiationInterface/ext/DifferentiationInterfaceGTPSAExt/onearg.jl:145
[2] gradient(::var"#fc#fc##0", ::AutoGTPSA{Nothing}, ::Vector{Float64})
@ DifferentiationInterface ~/dev/DifferentiationInterface.jl/DifferentiationInterface/src/first_order/gradient.jl:63
jacobian(fv, AutoGTPSA(), [1.0, 2.0])
ERROR: MethodError: no method matching jacobian!(::Matrix{Float64}, ::Vector{Float64}; include_params::Bool, unsafe_inbounds::Bool)
The function `jacobian!` exists, but no method is defined for this combination of argument types.
Closest candidates are:
jacobian!(::Any, !Matched::AbstractArray{<:GTPSA.TPS}; include_params, unsafe_inbounds)
@ GTPSA ~/.julia/packages/GTPSA/IhdRA/src/getset.jl:417
second_derivative(t -> 42.0, AutoGTPSA(), 1.5)
ERROR: BoundsError
Expected Behavior
For f that does not depend on x:
- All differentiation operators return zeros (of the appropriate shape and type), and
value_and_* returns (f(x), zeros).
Actual Behavior
| Operator |
Behavior |
gradient, value_and_gradient |
MethodError |
jacobian |
MethodError |
hessian |
MethodError |
hvp |
MethodError |
second_derivative |
BoundsError |
pushforward (scalar output) |
returns f(x) instead of 0 |
pushforward (vector output) |
returns f(x) instead of zeros |
value_and_pushforward |
tangent equals f(x) instead of zeros |
derivative (scalar input) |
returns f(x) (also incorrect for the same reason) |
Root Cause
In ext/DifferentiationInterfaceGTPSAExt/onearg.jl, the pushforward implementation does:
yt = fc(prep.xt)
if yt isa Number
return yt[1] # if yt is Float64, yt[1] == yt
else
dy = map(t -> t[1], yt) # if eltype(yt) is Number, t[1] == t
return dy
end
When f does not propagate the TPS, yt is a plain Float64 (or Vector{Float64}), and yt[1] returns the value rather than the first-order coefficient.
The same path is taken by gradient/jacobian/hessian which then pass the plain Float64/Vector{Float64} to GTPSA.gradient!/GTPSA.jacobian!, neither of which accepts non-TPS input.
A check on whether yt isa TPS (or eltype(yt) <: TPS) and returning zero(...) in the non-TPS case would match the behavior of other backends. ForwardDiff, for instance, returns [0.0, 0.0] for gradient(x -> 42.0, AutoForwardDiff(), [1.0, 2.0]).
Backend
- Backend:
AutoGTPSA()
- Works with other backends: ForwardDiff, Enzyme, FiniteDiff all return zeros for the same inputs
- Native API behavior:
GTPSA.gradient!(g, fc(xt)) errors with the same MethodError (fc(xt) produces a Float64 rather than a TPS). Wrapping in a TPS first (y_tps = TPS{Float64}(use=d); y_tps[0] = fc(xt)) then calling GTPSA.gradient!(g, y_tps) returns [0.0, 0.0].
This is the same class of issue as #1013 (AutoHyperHessians), but for AutoGTPSA it also affects first-order operators and produces silently-wrong values from pushforward.
Environment
- Julia 1.12.5
- DifferentiationInterface v0.7.18 (this branch)
- GTPSA v1.5.3
- ADTypes v1.22.0
Full environment
julia> using Pkg; Pkg.status()
Status `~/dev/DifferentiationInterface.jl/DifferentiationInterface/test/Back/GTPSA/Project.toml`
[47edcb42] ADTypes v1.22.0
[a0c0ee7d] DifferentiationInterface v0.7.18 `../../..`
[a82114a7] DifferentiationInterfaceTest v0.11.0 `../../../../DifferentiationInterfaceTest`
[7d51a73a] ExplicitImports v1.15.0
[f6369f11] ForwardDiff v1.3.3
[b27dd330] GTPSA v1.5.3
[c3a54625] JET v0.11.3
[9f842d2f] SparseConnectivityTracer v1.2.1
[0a514795] SparseMatrixColorings v0.4.27
[8dfed614] Test v1.11.0
julia> versioninfo()
Julia Version 1.12.5
Commit 5fe89b8ddc1 (2026-02-09 16:05 UTC)
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 4 × Intel(R) Core(TM) i5-2520M CPU @ 2.50GHz
WORD_SIZE: 64
LLVM: libLLVM-18.1.7 (ORCJIT, sandybridge)
Threads: 1 default, 1 interactive, 1 GC (on 4 virtual cores)
🤖 I am a robot. This is an experiment in agentic bug-catching under the supervision of @adrhill and @gdalle (#1008). Contents may be hallucinated.
Description
When
fdoes not propagate the input through GTPSA'sTPS(e.g.f(x) = 42.0, or any function whose evaluation on aTPSinput returns a plainFloat64), theAutoGTPSAextension produces incorrect output. Two symptoms are visible, both rooted in the extension assumingf(xt)is always aTPS:gradient,jacobian,hessian,hvp,second_derivative,value_and_gradientraiseMethodError.pushforwardandvalue_and_pushforwardreturn the function value as the pushforward, instead of zero.Native
GTPSA.gradient!/jacobian!also error when passed a plainNumber/Vector{<:Number}(the wrapping into aTPSis something the DI extension would need to do).MWE
Workaround: force
TPSpropagation, e.g.f(x) = 0*x[1] + 42.0. Thengradientreturns[0.0, 0.0]andpushforwardreturns0.0, as expected.Stacktraces
Expected Behavior
For
fthat does not depend onx:value_and_*returns(f(x), zeros).Actual Behavior
gradient,value_and_gradientMethodErrorjacobianMethodErrorhessianMethodErrorhvpMethodErrorsecond_derivativeBoundsErrorpushforward(scalar output)f(x)instead of0pushforward(vector output)f(x)instead ofzerosvalue_and_pushforwardf(x)instead of zerosderivative(scalar input)f(x)(also incorrect for the same reason)Root Cause
In
ext/DifferentiationInterfaceGTPSAExt/onearg.jl, the pushforward implementation does:When
fdoes not propagate theTPS,ytis a plainFloat64(orVector{Float64}), andyt[1]returns the value rather than the first-order coefficient.The same path is taken by gradient/jacobian/hessian which then pass the plain
Float64/Vector{Float64}toGTPSA.gradient!/GTPSA.jacobian!, neither of which accepts non-TPSinput.A check on whether
yt isa TPS(oreltype(yt) <: TPS) and returningzero(...)in the non-TPScase would match the behavior of other backends.ForwardDiff, for instance, returns[0.0, 0.0]forgradient(x -> 42.0, AutoForwardDiff(), [1.0, 2.0]).Backend
AutoGTPSA()GTPSA.gradient!(g, fc(xt))errors with the sameMethodError(fc(xt)produces aFloat64rather than aTPS). Wrapping in aTPSfirst (y_tps = TPS{Float64}(use=d); y_tps[0] = fc(xt)) then callingGTPSA.gradient!(g, y_tps)returns[0.0, 0.0].This is the same class of issue as #1013 (
AutoHyperHessians), but forAutoGTPSAit also affects first-order operators and produces silently-wrong values frompushforward.Environment
Full environment
🤖 I am a robot. This is an experiment in agentic bug-catching under the supervision of @adrhill and @gdalle (#1008). Contents may be hallucinated.