@@ -20,6 +20,214 @@ differencing functions at different `x` values (or with different `f` functions)
20
20
while if you want a quick and dirty numerical answer, directly call a differencing
21
21
function.
22
22
23
+ ## Tutorials
24
+
25
+ ### Tutorial 1: Fast Dense Jacobians
26
+
27
+ It's always fun to start out with a tutorial before jumping into the details!
28
+ Suppose we had the functions:
29
+
30
+ ``` julia
31
+ fcalls = 0
32
+ function f (dx,x) # in-place
33
+ global fcalls += 1
34
+ for i in 2 : length (x)- 1
35
+ dx[i] = x[i- 1 ] - 2 x[i] + x[i+ 1 ]
36
+ end
37
+ dx[1 ] = - 2 x[1 ] + x[2 ]
38
+ dx[end ] = x[end - 1 ] - 2 x[end ]
39
+ nothing
40
+ end
41
+
42
+ handleleft (x,i) = i== 1 ? zero (eltype (x)) : x[i- 1 ]
43
+ handleright (x,i) = i== length (x) ? zero (eltype (x)) : x[i+ 1 ]
44
+ function g (x) # out-of-place
45
+ global fcalls += 1
46
+ @SVector [handleleft (x,i) - 2 x[i] + handleright (x,i) for i in 1 : length (x)]
47
+ end
48
+ ```
49
+
50
+ and we wanted to calculate the derivatives of them. The simplest thing we can
51
+ do is ask for the Jacobian. If we want to allocate the result, we'd use the
52
+ allocating function ` finite_difference_jacobian ` on a 1-argument function ` g ` :
53
+
54
+ ``` julia
55
+ using StaticArrays
56
+ x = @SVector rand (10 )
57
+ FiniteDiff. finite_difference_jacobian (g,x)
58
+
59
+ #=
60
+ 10×10 SArray{Tuple{10,10},Float64,2,100} with indices SOneTo(10)×SOneTo(10):
61
+ -2.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
62
+ 1.0 -2.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
63
+ 0.0 1.0 -2.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
64
+ 0.0 0.0 1.0 -2.0 1.0 0.0 0.0 0.0 0.0 0.0
65
+ 0.0 0.0 0.0 1.0 -2.0 1.0 0.0 0.0 0.0 0.0
66
+ 0.0 0.0 0.0 0.0 1.0 -2.0 1.0 0.0 0.0 0.0
67
+ 0.0 0.0 0.0 0.0 0.0 1.0 -2.0 1.0 0.0 0.0
68
+ 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -2.0 1.0 0.0
69
+ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -2.0 1.0
70
+ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -2.0
71
+ =#
72
+ ```
73
+
74
+ FiniteDiff.jl assumes you're a smart cookie, and so if you used an
75
+ out-of-place function then it'll not mutate vectors at all, and is thus compatible
76
+ with objects like StaticArrays and will give you a fast Jacobian.
77
+
78
+ But if you wanted to use mutatiion, then we'd have to use the in-place function
79
+ ` f ` and call the mutating form:
80
+
81
+ ``` julia
82
+ x = rand (10 )
83
+ output = zeros (10 ,10 )
84
+ FiniteDiff. finite_difference_jacobian! (output,f,x)
85
+ output
86
+
87
+ #=
88
+ 10×10 Array{Float64,2}:
89
+ -2.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
90
+ 1.0 -2.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
91
+ 0.0 1.0 -2.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
92
+ 0.0 0.0 1.0 -2.0 1.0 0.0 0.0 0.0 0.0 0.0
93
+ 0.0 0.0 0.0 1.0 -2.0 1.0 0.0 0.0 0.0 0.0
94
+ 0.0 0.0 0.0 0.0 1.0 -2.0 1.0 0.0 0.0 0.0
95
+ 0.0 0.0 0.0 0.0 0.0 1.0 -2.0 1.0 0.0 0.0
96
+ 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -2.0 1.0 0.0
97
+ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -2.0 1.0
98
+ 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 -2.0
99
+ =#
100
+ ```
101
+
102
+ But what if you want this to be completely non-allocating on your mutating form?
103
+ Then you need to preallocate a cache:
104
+
105
+ ``` julia
106
+ cache = FiniteDiff. JacobianCache (x)
107
+ ```
108
+
109
+ and now using this cache avoids allocating:
110
+
111
+ ``` julia
112
+ @time FiniteDiff. finite_difference_jacobian! (output,f,x,cache) # 0.000008 seconds (7 allocations: 224 bytes)
113
+ ```
114
+
115
+ And that's pretty much it! Gradients and Hessians work similarly: out of place
116
+ doesn't index, and in-place avoids allocations. Either way, you're fast. GPUs
117
+ etc. all work.
118
+
119
+ ### Tutorial 2: Fast Sparse Jacobians
120
+
121
+ Now let's exploit sparsity. If we knew the sparsity pattern we could write it
122
+ down analytically as a sparse matrix, but let's assume we don't. Thus we can
123
+ use [ SparsityDetection.jl] ( https://github.com/JuliaDiffEq/SparsityDetection.jl )
124
+ to automatically get the sparsity pattern of the Jacobian as a sparse matrix:
125
+
126
+ ``` julia
127
+ using SparsityDetection, SparseArrays
128
+ in = rand (10 )
129
+ out = similar (in)
130
+ sparsity_pattern = sparsity! (f,out,in)
131
+ sparsejac = Float64 .(sparse (sparsity_pattern))
132
+ ```
133
+
134
+ Then we can use [ SparseDiffTools.jl] ( https://github.com/JuliaDiffEq/SparseDiffTools.jl )
135
+ to get the color vector:
136
+
137
+ ``` julia
138
+ using SparseDiffTools
139
+ colors = matrix_colors (sparsejac)
140
+ ```
141
+
142
+ Now we can do sparse differentiation by passing the color vector and the sparsity
143
+ pattern:
144
+
145
+ ``` julia
146
+ sparsecache = FiniteDiff. JacobianCache (x,colorvec= colors,sparsity= sparsejac)
147
+ FiniteDiff. finite_difference_jacobian! (sparsejac,f,x,sparsecache)
148
+ ```
149
+
150
+ Note that the number of ` f ` evaluations to fill a Jacobian is ` 1+maximum(colors) ` .
151
+ By default, ` colors=1:length(x) ` , so in this case we went from 10 function calls
152
+ to 4. The sparser the matrix, the more the gain! We can measure this as well:
153
+
154
+ ``` julia
155
+ fcalls = 0
156
+ FiniteDiff. finite_difference_jacobian! (output,f,x,cache)
157
+ fcalls # 11
158
+
159
+ fcalls = 0
160
+ FiniteDiff. finite_difference_jacobian! (sparsejac,f,x,sparsecache)
161
+ fcalls # 4
162
+ ```
163
+
164
+ ### Tutorial 3: Fast Tridiagonal Jacobians
165
+
166
+ Handling dense matrices? Easy. Handling sparse matrices? Cool stuff. Automatically
167
+ specializing on the exact structure of a matrix? Even better. FiniteDiff can
168
+ specialize on types which implement the
169
+ [ ArrayInterface.jl] ( https://github.com/JuliaDiffEq/ArrayInterface.jl ) interface.
170
+ This includes:
171
+
172
+ - Diagonal
173
+ - Bidiagonal
174
+ - UpperTriangular and LowerTriangular
175
+ - Tridiagonal and SymTridiagonal
176
+ - [ BandedMatrices.jl] ( https://github.com/JuliaMatrices/BandedMatrices.jl )
177
+ - [ BlockBandedMatrices.jl] ( https://github.com/JuliaMatrices/BlockBandedMatrices.jl )
178
+
179
+ Our previous example had a Tridiagonal Jacobian, so let's use this. If we just
180
+ do
181
+
182
+ ``` julia
183
+ using ArrayInterface, LinearAlgebra
184
+ tridiagjac = Tridiagonal (output)
185
+ colors = matrix_colors (jac)
186
+ ```
187
+
188
+ we get the analytical solution to the optimal matrix colors for our structured
189
+ Jacobian. Now we can use this in our differencing routines:
190
+
191
+ ``` julia
192
+ tridiagcache = FiniteDiff. JacobianCache (x,colorvec= colors,sparsity= tridiagjac)
193
+ FiniteDiff. finite_difference_jacobian! (tridiagjac,f,x,tridiagcache)
194
+ ```
195
+
196
+ It'll use a special iteration scheme dependent on the matrix type to accelerate
197
+ it beyond general sparse usage.
198
+
199
+ ### Tutorial 4: Fast Block Banded Matrices
200
+
201
+ Now let's showcase a difficult example. Say we had a large system of partial
202
+ differential equations, with a function like:
203
+
204
+ ``` julia
205
+ function pde (out, x)
206
+ x = reshape (x, 100 , 100 )
207
+ out = reshape (out, 100 , 100 )
208
+ for i in 1 : 100
209
+ for j in 1 : 100
210
+ out[i, j] = x[i, j] + x[max (i - 1 , 1 ), j] + x[min (i+ 1 , size (x, 1 )), j] + x[i, max (j- 1 , 1 )] + x[i, min (j+ 1 , size (x, 2 ))]
211
+ end
212
+ end
213
+ return vec (out)
214
+ end
215
+ x = rand (10000 )
216
+ ```
217
+
218
+ In this case, we can see that our sparsity pattern is a BlockBandedMatrix, so
219
+ let's specialize the Jacobian calculation on this fact:
220
+
221
+ ``` julia
222
+ using FillArrays, BlockBandedMatrices
223
+ Jbbb = BandedBlockBandedMatrix (Ones (10000 , 10000 ), fill (100 , 100 ), fill (100 , 100 ), (1 , 1 ), (1 , 1 ))
224
+ colorsbbb = ArrayInterface. matrix_colors (Jbbb)
225
+ bbbcache = FiniteDiff. JacobianCache (x,colorvec= colorsbbb,sparsity= Jbbb)
226
+ FiniteDiff. finite_difference_jacobian! (Jbbb, pde, x, colorvec= colorsbbb)
227
+ ```
228
+
229
+ And boom, a fast Jacobian filling algorithm on your special matrix.
230
+
23
231
## General Structure
24
232
25
233
The general structure of the library is as follows. You can call the differencing
0 commit comments