JuliaDiff / JuliaDiff/DifferentiationInterface.jl
Bug(FastDifferentiation): hessian returns wrong values when a variable appears inside a nonlinear op and as a multiplier
Nobody has claimed this yet.
- Dominant language
- Julia
- Stars
- 313
- Forks
- 35
- PR merge metrics
- No merged PRs in 30d
Description
Description
hessian with AutoFastDifferentiation() returns incorrect values when a variable appears both inside a nonlinear function (e.g. exp, sin, log, ^) and as a separate multiplier outside. The same incorrect result is produced by native FastDifferentiation.hessian and FastDifferentiation.sparse_hessian, so the root cause is in FastDifferentiation.jl. The chained FastDifferentiation.derivative(FastDifferentiation.derivative([f], x), x) path produces the correct result, suggesting the bug is in the multivariate Hessian assembly rather than the underlying derivative rules.
MWE
using DifferentiationInterface
using FastDifferentiation: FastDifferentiation
using ForwardDiff: ForwardDiff
f(v) = exp(v[1] - v[2]) * v[2]
x = [-0.7, 2.3]
# Analytical: ∂²f/∂x[2]² = exp(x[1]-x[2])*(x[2]-2) = exp(-3)*0.3 ≈ 0.014936
hessian(f, AutoForwardDiff(), x)
# 2×2 Matrix{Float64}:
# 0.11451 -0.0647232
# -0.0647232 0.0149361 ← correct (matches analytical)
hessian(f, AutoFastDifferentiation(), x)
# 2×2 Matrix{Float64}:
# 0.11451 -0.0647232
# -0.0647232 0.129446 ← H[2,2] is off by ~9x, equal to 2*(x[2]-1)*exp(x[1]-x[2])
H[1,1] and H[1,2]=H[2,1] are correct; only H[2,2] is wrong, off by exp(x[1]-x[2]) * x[2] = f(x) itself.
Range of affected functions
The bug appears whenever a variable y is both inside a nonlinear operator and multiplied outside it; another variable in the nonlinear operator is necessary. The pattern is not specific to exp.
| Function | H[2,2] diff (FD vs FastDiff) |
|---|---|
exp(x1-x2)*x2 |
0.115 |
x2*exp(x1-x2) |
0.115 |
exp(x1+x2)*x2 |
11.39 |
sin(x1-x2)*x2 |
0.325 |
log(x1+x2)*x2 (x>0) |
0.082 |
(x1-x2)^2*x2 |
4.6 |
exp(-x2)*x2 (only one var inside) |
0.0 (correct) |
exp(x1)*x2 (no shared var) |
0.0 (correct) |
(x1-x2)*x2 (no nonlinearity) |
0.0 (correct) |
Native Backend Comparison
The same wrong value is produced by FastDifferentiation.hessian, FastDifferentiation.sparse_hessian, and FastDifferentiation.jacobian(FastDifferentiation.jacobian([f], xv), xv). Manual FastDifferentiation.derivative(FastDifferentiation.derivative([f], xv[2]), xv[2]) gives the correct value, so the bug is in the higher-level Hessian/Jacobian assembly rather than the derivative rules themselves.
using FastDifferentiation
xv = make_variables(:x, 2)
fexpr = exp(xv[1] - xv[2]) * xv[2]
# FastDifferentiation.hessian: WRONG H[2,2]
H_sym = FastDifferentiation.hessian(fexpr, xv)
make_function(H_sym, xv; in_place=false)([-0.7, 2.3])
# 2×2 Matrix{Float64}:
# 0.11451 -0.0647232
# -0.0647232 0.129446 ← wrong
# Manual chained derivative: CORRECT
d1 = FastDifferentiation.derivative([fexpr], xv[2])
d2 = FastDifferentiation.derivative(d1, xv[2])
make_function(d2, xv; in_place=false)([-0.7, 2.3])
# 1-element Vector{Float64}:
# 0.014936120510359176 ← correct
The symbolic Hessian FD produces for the (2,2) entry is:
(-((exp((x1 - x2)) + (exp((x1 - x2)) * -(x2)))) + ((-(x2) + 1) * -(exp((x1 - x2)))))
This simplifies algebraically to 2*(x[2]-1)*exp(x[1]-x[2]). The correct answer is (x[2]-2)*exp(x[1]-x[2]). The second term in FD's expression, ((-(x[2])+1) * -(exp((x[1] - x[2])))), carries a spurious factor of (1-x[2]); without it, the formula matches the correct manual result.
Expected Behavior
hessian(f, AutoFastDifferentiation(), x) matches hessian(f, AutoForwardDiff(), x) for these functions, to within numerical precision.
Actual Behavior
H[2,2] disagrees, with relative error of order f(x)/H_true[2,2] (~9x in the exp(x1-x2)*x2 example).
Backend
- Backend:
AutoFastDifferentiation() - Works with other backends: Yes —
AutoForwardDiff(),AutoHyperHessians(),AutoSymbolics()all match the analytical answer - Native API gives same result: No —
FastDifferentiation.hessianproduces the same incorrect value
This may be related to FastDifferentiation issues #65 (factorization algorithm) or #76 (closed; simplification rule for matching terms).
Environment
- Julia 1.12.6
- DifferentiationInterface v0.7.18
- FastDifferentiation v0.4.5
- ForwardDiff v1.3.3
Full environment
Status `/tmp/di-bughunt/Project.toml`
[47edcb42] ADTypes v1.22.0
[a0c0ee7d] DifferentiationInterface v0.7.18
[eb9bf01b] FastDifferentiation v0.4.5
[f6369f11] ForwardDiff v1.3.3
Julia Version 1.12.6
Commit 15346901f00 (2026-04-09 19:20 UTC)
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 4 × Intel(R) Core(TM) i5-2520M CPU @ 2.50GHz
WORD_SIZE: 64
🤖 I am a robot. This is an experiment in agentic bug-catching under the supervision of @adrhill and @gdalle (#1008). Contents may be hallucinated.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the supplied MWE and comparing AutoFastDifferentiation() with AutoForwardDiff(). Inspect FastDifferentiation.hessian, sparse_hessian, and the chained derivative/Jacobian paths in FastDifferentiation.jl to isolate the multivariate assembly discrepancy. Done means the affected Hessian entries match the analytical and other-backend results for the listed nonlinear multiplier cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100