@@ -24,13 +24,17 @@ needed.
2424### Using dualcache
2525
2626``` julia
27- dualcache (u:: AbstractArray , N = Val{default_cache_size (length (u))})
27+ dualcache (u:: AbstractArray , N:: Int = ForwardDiff. pickchunksize (length (u)); levels:: Int = 1 )
28+ dualcache (u:: AbstractArray , N:: AbstractArray{<:Int} )
2829```
2930
3031The ` dualcache ` function builds a ` DualCache ` object that stores both a version
3132of the cache for ` u ` and for the ` Dual ` version of ` u ` , allowing use of
32- pre-cached vectors with forward-mode automatic differentiation. To access the
33- caches, one uses:
33+ pre-cached vectors with forward-mode automatic differentiation. Note that
34+ ` dualcache ` , due to its design, is only compatible with arrays that contain concretely
35+ typed elements.
36+
37+ To access the caches, one uses:
3438
3539``` julia
3640get_tmp (tmp:: DualCache , u)
@@ -41,19 +45,29 @@ version of the cache. Otherwise it returns the standard cache (for use in the
4145calls without automatic differentiation).
4246
4347In order to preallocate to the right size, the ` dualcache ` needs to be specified
44- to have the corrent ` N ` matching the chunk size of the dual numbers or larger.
48+ to have the correct ` N ` matching the chunk size of the dual numbers or larger.
49+ If the chunk size ` N ` specified is too large, ` get_tmp ` will automatically resize
50+ when dispatching; this remains type-stable and non-allocating, but comes at the
51+ expense of additional memory.
52+
4553In a differential equation, optimization, etc., the default chunk size is computed
4654from the state vector ` u ` , and thus if one creates the ` dualcache ` via
4755` dualcache(u) ` it will match the default chunking of the solver libraries.
4856
57+ ` dualcache ` is also compatible with nested automatic differentiation calls through
58+ the ` levels ` keyword (` N ` for each level computed using based on the size of the
59+ state vector) or by specifying ` N ` as an array of integers of chunk sizes, which
60+ enables full control of chunk sizes on all differentation levels.
61+
4962### dualcache Example 1: Direct Usage
5063
5164``` julia
52- randmat = rand (10 , 2 )
65+ using ForwardDiff, PreallocationTools
66+ randmat = rand (5 , 3 )
5367sto = similar (randmat)
5468stod = dualcache (sto)
5569
56- function claytonsample! (sto, τ; randmat= randmat)
70+ function claytonsample! (sto, τ, α ; randmat= randmat)
5771 sto = get_tmp (sto, τ)
5872 sto .= randmat
5973 τ == 0 && return sto
@@ -62,14 +76,23 @@ function claytonsample!(sto, τ; randmat=randmat)
6276 for i in 1 : n
6377 v = sto[i, 2 ]
6478 u = sto[i, 1 ]
79+ sto[i, 1 ] = (1 - u^ (- τ) + u^ (- τ)* v^ (- (τ/ (1 + τ))))^ (- 1 / τ)* α
6580 sto[i, 2 ] = (1 - u^ (- τ) + u^ (- τ)* v^ (- (τ/ (1 + τ))))^ (- 1 / τ)
6681 end
6782 return sto
6883end
6984
70- ForwardDiff. derivative (τ -> claytonsample! (stod, τ), 0.3 )
85+ ForwardDiff. derivative (τ -> claytonsample! (stod, τ, 0.0 ), 0.3 )
86+ ForwardDiff. jacobian (x -> claytonsample! (stod, x[1 ], x[2 ]), [0.3 ; 0.0 ])
7187```
7288
89+ In the above, the chunk size of the dual numbers has been selected based on the size
90+ of ` randmat ` , resulting in a chunk size of 8 in this case. However, since the derivative
91+ is calculated with respect to τ and the Jacobian is calculated with respect to τ and α,
92+ specifying the ` dualcache ` with ` stod = dualcache(sto, 1) ` or ` stod = dualcache(sto, 2) ` ,
93+ respectively, would have been the most memory efficient way of performing these calculations
94+ (only really relevant for much larger problems).
95+
7396### dualcache Example 2: ODEs
7497
7598``` julia
@@ -80,7 +103,7 @@ function foo(du, u, (A, tmp), t)
80103 nothing
81104end
82105prob = ODEProblem (foo, ones (5 , 5 ), (0. , 1.0 ), (ones (5 ,5 ), zeros (5 ,5 )))
83- solve (prob, Rosenbrock23 ())
106+ solve (prob, TRBDF2 ())
84107```
85108
86109fails because ` tmp ` is only real numbers, but during automatic differentiation
@@ -96,7 +119,7 @@ function foo(du, u, (A, tmp), t)
96119 nothing
97120end
98121chunk_size = 5
99- prob = ODEProblem (foo, ones (5 , 5 ), (0. , 1.0 ), (ones (5 ,5 ), dualcache (zeros (5 ,5 ), Val{ chunk_size} )))
122+ prob = ODEProblem (foo, ones (5 , 5 ), (0. , 1.0 ), (ones (5 ,5 ), dualcache (zeros (5 ,5 ), chunk_size)))
100123solve (prob, TRBDF2 (chunk_size= chunk_size))
101124```
102125
@@ -114,6 +137,46 @@ chunk_size = 5
114137prob = ODEProblem (foo, ones (5 , 5 ), (0. , 1.0 ), (ones (5 ,5 ), dualcache (zeros (5 ,5 ))))
115138solve (prob, TRBDF2 ())
116139```
140+ ### dualcache Example 3: Nested AD calls in an optimization problem involving a Hessian matrix
141+
142+ ``` julia
143+ using LinearAlgebra, OrdinaryDiffEq, PreallocationTools, Optim, GalacticOptim
144+ function foo (du, u, p, t)
145+ tmp = p[2 ]
146+ A = reshape (p[1 ], size (tmp. du))
147+ tmp = get_tmp (tmp, u)
148+ mul! (tmp, A, u)
149+ @. du = u + tmp
150+ nothing
151+ end
152+
153+ coeffs = - collect (0.1 : 0.1 : 0.4 )
154+ cache = dualcache (zeros (2 ,2 ), levels = 3 )
155+ prob = ODEProblem (foo, ones (2 , 2 ), (0. , 1.0 ), (coeffs, cache))
156+ realsol = solve (prob, TRBDF2 (), saveat = 0.0 : 0.1 : 10.0 , reltol = 1e-8 )
157+
158+ function objfun (x, prob, realsol, cache)
159+ prob = remake (prob, u0 = eltype (x).(prob. u0), p = (x, cache))
160+ sol = solve (prob, TRBDF2 (), saveat = 0.0 : 0.1 : 10.0 , reltol = 1e-8 )
161+
162+ ofv = 0.0
163+ if any ((s. retcode != :Success for s in sol))
164+ ofv = 1e12
165+ else
166+ ofv = sum ((sol.- realsol). ^ 2 )
167+ end
168+ return ofv
169+ end
170+ fn (x,p) = objfun (x, p[1 ], p[2 ], p[3 ])
171+ optfun = OptimizationFunction (fn, GalacticOptim. AutoForwardDiff ())
172+ optprob = OptimizationProblem (optfun, zeros (length (coeffs)), (prob, realsol, cache))
173+ solve (optprob, Newton ())
174+ ```
175+ Solves an optimization problem for the coefficients, ` coeffs ` , appearing in a differential equation.
176+ The optimization is done with [ Optim.jl] ( https://github.com/JuliaNLSolvers/Optim.jl ) 's ` Newton() `
177+ algorithm. Since this involves automatic differentiation in the ODE solver and the calculation
178+ of Hessians, three automatic differentiations are nested within each other. Therefore, the ` dualcache `
179+ is specified with ` levels = 3 ` .
117180
118181## LazyBufferCache
119182
0 commit comments