66 ForwardDiff.hessian(f, x::AbstractArray, cfg::HessianConfig = HessianConfig(f, x), check=Val{true}())
77
88Return `H(f)` evaluated at `x`, assuming `f` is called as `f(x)`.
9+ The returned Hessian is exactly symmetric: its two triangles are filled from the same
10+ derivative values.
911
1012This method assumes that `isa(f(x), Real)`.
1113
2123"""
2224 ForwardDiff.hessian!(result::AbstractArray, f, x::AbstractArray, cfg::HessianConfig = HessianConfig(f, x), check=Val{true}())
2325
24- Compute `H(f)` (i.e. `J(∇(f))`) evaluated at `x` and store the result(s) in `result`,
25- assuming `f` is called as `f(x)`.
26+ Compute `H(f)` evaluated at `x` and store the result(s) in `result`, assuming `f` is
27+ called as `f(x)`. The stored Hessian is exactly symmetric: its two triangles are filled
28+ from the same derivative values.
2629
2730This method assumes that `isa(f(x), Real)`.
2831
@@ -32,17 +35,18 @@ function hessian!(result::AbstractArray, f::F, x::AbstractArray, cfg::HessianCon
3235require_one_based_indexing (result, x)
3336 CHK && checktag (T, f, x)
3437 xlen = structural_length (x)
35- H = result isa AbstractMatrix && size (result) == (xlen, xlen) ? result : reshape (result, xlen, xlen)
38+ H = result isa AbstractMatrix ? result : reshape (result, xlen, xlen)
3639symmetric_hessian! (H, f, x, cfg, nothing )
3740return result
3841end
3942
4043"""
4144 ForwardDiff.hessian!(result::DiffResult, f, x::AbstractArray, cfg::HessianConfig = HessianConfig(f, result, x), check=Val{true}())
4245
43- Exactly like `ForwardDiff.hessian!(result::AbstractArray, f, x::AbstractArray, cfg::HessianConfig)`, but
44- because `isa(result, DiffResult)`, `cfg` is constructed as `HessianConfig(f, result, x)` instead of
45- `HessianConfig(f, x)`.
46+ Exactly like `ForwardDiff.hessian!(result::AbstractArray, f, x::AbstractArray, cfg::HessianConfig)`,
47+ but also stores the value and gradient in `result`. The default `cfg` is constructed as
48+ `HessianConfig(f, result, x)`, though a config constructed as `HessianConfig(f, x)` may also
49+ be used.
4650
4751Set `check` to `Val{false}()` to disable tag checking. This can lead to perturbation confusion, so should be used with care.
4852"""
@@ -51,7 +55,7 @@ function hessian!(result::DiffResult, f::F, x::AbstractArray, cfg::HessianConfig
5155 CHK && checktag (T, f, x)
5256 xlen = structural_length (x)
5357 hess = DiffResults. hessian (result)
54- H = hess isa AbstractMatrix && size (hess) == (xlen, xlen) ? hess : reshape (hess, xlen, xlen)
58+ H = hess isa AbstractMatrix ? hess : reshape (hess, xlen, xlen)
5559 _, ydual = symmetric_hessian! (H, f, x, cfg, DiffResults. gradient (result))
5660 result = DiffResults. value! (result, value (T, value (T, ydual)))
5761return result
6367
6468const HESSIAN_ERROR = DimensionMismatch (" hessian(f, x) expects that f(x) is a real number. Perhaps you meant jacobian(f, x)?" )
6569
66- # Seed a chunk in either layer of the nested duals. A `nothing` seed clears that layer.
67- function seed_hessian_chunk! (duals:: AbstractArray{Dual{T,Dual{T,V,N},N}} , x, index,
68- iseeds:: Union{Nothing,NTuple{N,Partials{N,V}}} ,
69- oseeds:: Union {Nothing,NTuple{N,Partials{N,Dual{T,V,N}}}},
70- chunksize = N) where {T,V,N}
71- izero = zero (Partials{N,V})
72- ozero = zero (Partials{N,Dual{T,V,N}})
73- idxs = Iterators. drop (structural_eachindex (duals, x), index - 1 )
74- if isbitstype (V)
75- for (i, idx) in zip (1 : chunksize, idxs)
76- inner = Dual {T,V,N} (x[idx], iseeds === nothing ? izero : iseeds[i])
77- duals[idx] = Dual {T,Dual{T,V,N},N} (inner, oseeds === nothing ? ozero : oseeds[i])
78- end
79- else
80- for (i, idx) in zip (1 : chunksize, idxs)
81- if isassigned (x, idx)
82- inner = Dual {T,V,N} (x[idx], iseeds === nothing ? izero : iseeds[i])
83- duals[idx] = Dual {T,Dual{T,V,N},N} (inner, oseeds === nothing ? ozero : oseeds[i])
84- else
85- Base. _unsetindex! (duals, idx)
86- end
87- end
88- end
89- return duals
90- end
91-
9270# Copy a block from the nested partials and fill its transpose. On diagonal blocks, read
9371# only the upper triangle so the result is exactly symmetric.
9472function extract_hessian_chunk! (:: Type{T} , H, ydual, roffset, coffset, rsize, csize) where {T}
@@ -118,38 +96,39 @@ function symmetric_hessian_expr(result_definition::Expr)
11896throw (ArgumentError (lazy " chunk size cannot be greater than ForwardDiff.structural_length(x) ($(N) > $(structural_length(x)))" ))
11997end
12098
121- nblocks = xlen == 0 ? 1 : div (xlen + N - 1 , N)
99+ # `N == 0` only for empty inputs, which still need one evaluation to determine the
100+ # output type and value.
101+ nblocks = xlen == 0 ? 1 : cld (xlen, N)
122102
123103 xdual = cfg. gradient_config. duals
124104 iseeds = cfg. jacobian_config. seeds
125105 oseeds = cfg. gradient_config. seeds
126106
127- # Keep all unseeded blocks at zero between evaluations.
128- seed_hessian_chunk! (xdual, x, 1 , nothing , nothing , xlen)
129-
130- # The first evaluation determines the output type.
107+ # The first evaluation determines the output type. Seeding the first block and clearing
108+ # the untouched tail partitions the fresh buffer, so every element is initialized once.
131109seed_hessian_chunk! (xdual, x, 1 , iseeds, oseeds)
110+ seed_hessian_chunk! (xdual, x, N + 1 , nothing , nothing , xlen - N)
132111 ydual1 = f (xdual)
133112 ydual1 isa Real || throw (HESSIAN_ERROR)
134113$ (result_definition)
135114extract_hessian_chunk! (T, H, ydual1, 0 , 0 , N, N)
136115extract_hessian_gradient_chunk! (T, grad, ydual1, 1 , N)
137- seed_hessian_chunk! (xdual, x, 1 , nothing , nothing )
116+ nblocks > 1 && seed_hessian_chunk! (xdual, x, 1 , nothing , nothing )
138117
139118for q in 2 : nblocks
140119 qoffset = (q - 1 ) * N
141120 qsize = min (N, xlen - qoffset)
142- # Off-diagonal blocks: p seeds columns and q seeds rows.
121+ # Off-diagonal blocks: p seeds columns and q seeds rows. The outer seeds for q
122+ # remain unchanged throughout this loop.
123+ seed_hessian_chunk! (xdual, x, qoffset + 1 , nothing , oseeds, qsize)
143124for p in 1 : (q - 1 )
144125 poffset = (p - 1 ) * N
145126seed_hessian_chunk! (xdual, x, poffset + 1 , iseeds, nothing )
146- seed_hessian_chunk! (xdual, x, qoffset + 1 , nothing , oseeds, qsize)
147127 ydual = f (xdual)
148128extract_hessian_chunk! (T, H, ydual, qoffset, poffset, qsize, N)
149129seed_hessian_chunk! (xdual, x, poffset + 1 , nothing , nothing )
150- seed_hessian_chunk! (xdual, x, qoffset + 1 , nothing , nothing , qsize)
151130end
152- # Diagonal blocks seed both layers .
131+ # The diagonal block adds q's inner seeds while retaining its outer seeds .
153132seed_hessian_chunk! (xdual, x, qoffset + 1 , iseeds, oseeds, qsize)
154133 ydual = f (xdual)
155134extract_hessian_chunk! (T, H, ydual, qoffset, qoffset, qsize, qsize)
@@ -162,7 +141,7 @@ function symmetric_hessian_expr(result_definition::Expr)
162141end
163142
164143@eval function symmetric_hessian (f:: F , x, cfg:: HessianConfig{T,V,N} , grad) where {F,T,V,N}
165- $ (symmetric_hessian_expr (:(H = similar (x, typeof ( value ( T, value (T, ydual1))), xlen, xlen))))
144+ $ (symmetric_hessian_expr (:(H = similar (x, valtype ( T, valtype (T, typeof ( ydual1))), xlen, xlen))))
166145end
167146
168147@eval function symmetric_hessian! (H, f:: F , x, cfg:: HessianConfig{T,V,N} , grad) where {F,T,V,N}
0 commit comments