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. Its size is `(structural_length(x), structural_length(x))`; for
11+ structured matrix inputs, only structurally stored elements are treated as variables.
912
1013This method assumes that `isa(f(x), Real)`.
1114
2124"""
2225 ForwardDiff.hessian!(result::AbstractArray, f, x::AbstractArray, cfg::HessianConfig = HessianConfig(f, x), check=Val{true}())
2326
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)`.
27+ Compute `H(f)` evaluated at `x` and store the result(s) in `result`, assuming `f` is
28+ called as `f(x)`. The stored Hessian is exactly symmetric: its two triangles are filled
29+ from the same derivative values. `result` must store a
30+ `structural_length(x)`-by-`structural_length(x)` matrix.
2631
2732This method assumes that `isa(f(x), Real)`.
2833
@@ -32,17 +37,18 @@ function hessian!(result::AbstractArray, f::F, x::AbstractArray, cfg::HessianCon
3237 require_one_based_indexing (result, x)
3338 CHK && checktag (T, f, x)
3439 xlen = structural_length (x)
35- H = result isa AbstractMatrix && size (result) == (xlen, xlen) ? result : reshape (result, xlen, xlen)
40+ H = result isa AbstractMatrix ? result : reshape (result, xlen, xlen)
3641 symmetric_hessian! (H, f, x, cfg, nothing )
3742 return result
3843end
3944
4045"""
4146 ForwardDiff.hessian!(result::DiffResult, f, x::AbstractArray, cfg::HessianConfig = HessianConfig(f, result, x), check=Val{true}())
4247
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)`.
48+ Exactly like `ForwardDiff.hessian!(result::AbstractArray, f, x::AbstractArray, cfg::HessianConfig)`,
49+ but also stores the value and gradient in `result`. The default `cfg` is constructed as
50+ `HessianConfig(f, result, x)`, though a config constructed as `HessianConfig(f, x)` may also
51+ be used.
4652
4753Set `check` to `Val{false}()` to disable tag checking. This can lead to perturbation confusion, so should be used with care.
4854"""
@@ -51,7 +57,7 @@ function hessian!(result::DiffResult, f::F, x::AbstractArray, cfg::HessianConfig
5157 CHK && checktag (T, f, x)
5258 xlen = structural_length (x)
5359 hess = DiffResults. hessian (result)
54- H = hess isa AbstractMatrix && size (hess) == (xlen, xlen) ? hess : reshape (hess, xlen, xlen)
60+ H = hess isa AbstractMatrix ? hess : reshape (hess, xlen, xlen)
5561 _, ydual = symmetric_hessian! (H, f, x, cfg, DiffResults. gradient (result))
5662 result = DiffResults. value! (result, value (T, value (T, ydual)))
5763 return result
6369
6470const HESSIAN_ERROR = DimensionMismatch (" hessian(f, x) expects that f(x) is a real number. Perhaps you meant jacobian(f, x)?" )
6571
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-
9272# Copy a block from the nested partials and fill its transpose. On diagonal blocks, read
9373# only the upper triangle so the result is exactly symmetric.
9474function extract_hessian_chunk! (:: Type{T} , H, ydual, roffset, coffset, rsize, csize) where {T}
@@ -118,38 +98,39 @@ function symmetric_hessian_expr(result_definition::Expr)
11898 throw (ArgumentError (lazy " chunk size cannot be greater than ForwardDiff.structural_length(x) ($(N) > $(structural_length(x)))" ))
11999 end
120100
121- nblocks = xlen == 0 ? 1 : div (xlen + N - 1 , N)
101+ # `N == 0` only for empty inputs, which still need one evaluation to determine the
102+ # output type and value.
103+ nblocks = xlen == 0 ? 1 : cld (xlen, N)
122104
123105 xdual = cfg. gradient_config. duals
124106 iseeds = cfg. jacobian_config. seeds
125107 oseeds = cfg. gradient_config. seeds
126108
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.
109+ # The first evaluation determines the output type. Seeding the first block and clearing
110+ # the untouched tail partitions the fresh buffer, so every element is initialized once.
131111 seed_hessian_chunk! (xdual, x, 1 , iseeds, oseeds)
112+ seed_hessian_chunk! (xdual, x, N + 1 , nothing , nothing , xlen - N)
132113 ydual1 = f (xdual)
133114 ydual1 isa Real || throw (HESSIAN_ERROR)
134115 $ (result_definition)
135116 extract_hessian_chunk! (T, H, ydual1, 0 , 0 , N, N)
136117 extract_hessian_gradient_chunk! (T, grad, ydual1, 1 , N)
137- seed_hessian_chunk! (xdual, x, 1 , nothing , nothing )
118+ nblocks > 1 && seed_hessian_chunk! (xdual, x, 1 , nothing , nothing )
138119
139120 for q in 2 : nblocks
140121 qoffset = (q - 1 ) * N
141122 qsize = min (N, xlen - qoffset)
142- # Off-diagonal blocks: p seeds columns and q seeds rows.
123+ # Off-diagonal blocks: p seeds columns and q seeds rows. The outer seeds for q
124+ # remain unchanged throughout this loop.
125+ seed_hessian_chunk! (xdual, x, qoffset + 1 , nothing , oseeds, qsize)
143126 for p in 1 : (q - 1 )
144127 poffset = (p - 1 ) * N
145128 seed_hessian_chunk! (xdual, x, poffset + 1 , iseeds, nothing )
146- seed_hessian_chunk! (xdual, x, qoffset + 1 , nothing , oseeds, qsize)
147129 ydual = f (xdual)
148130 extract_hessian_chunk! (T, H, ydual, qoffset, poffset, qsize, N)
149131 seed_hessian_chunk! (xdual, x, poffset + 1 , nothing , nothing )
150- seed_hessian_chunk! (xdual, x, qoffset + 1 , nothing , nothing , qsize)
151132 end
152- # Diagonal blocks seed both layers .
133+ # The diagonal block adds q's inner seeds while retaining its outer seeds .
153134 seed_hessian_chunk! (xdual, x, qoffset + 1 , iseeds, oseeds, qsize)
154135 ydual = f (xdual)
155136 extract_hessian_chunk! (T, H, ydual, qoffset, qoffset, qsize, qsize)
@@ -162,7 +143,7 @@ function symmetric_hessian_expr(result_definition::Expr)
162143end
163144
164145@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))))
146+ $ (symmetric_hessian_expr (:(H = similar (x, valtype ( T, valtype (T, typeof ( ydual1))), xlen, xlen))))
166147end
167148
168149@eval function symmetric_hessian! (H, f:: F , x, cfg:: HessianConfig{T,V,N} , grad) where {F,T,V,N}
0 commit comments