JuliaDiff / JuliaDiff/DifferentiationInterface.jl

Bug(FastDifferentiation): hessian returns wrong values when a variable appears inside a nonlinear op and as a multiplier

Open
#1,014 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bot
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.hessian produces 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.