Skip to content

Commit ca97bf8

Browse files
add a tutorial
1 parent 684b519 commit ca97bf8

File tree

2 files changed

+214
-6
lines changed

2 files changed

+214
-6
lines changed

README.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,214 @@ differencing functions at different `x` values (or with different `f` functions)
2020
while if you want a quick and dirty numerical answer, directly call a differencing
2121
function.
2222

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] - 2x[i] + x[i+1]
36+
end
37+
dx[1] = -2x[1] + x[2]
38+
dx[end] = x[end-1] - 2x[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) - 2x[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+
23231
## General Structure
24232

25233
The general structure of the library is as follows. You can call the differencing

src/jacobians.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,9 @@ function finite_difference_jacobian(
145145
sparsity = cache.sparsity,
146146
jac_prototype = nothing,
147147
dir=true) where {T1,T2,T3,cType,sType,fdtype,returntype}
148-
148+
149149
x1, fx, fx1 = cache.x1, cache.fx, cache.fx1
150-
150+
151151
if !(f_in isa Nothing)
152152
vecfx = vec(f_in)
153153
elseif fdtype == Val{:forward}
@@ -161,13 +161,13 @@ function finite_difference_jacobian(
161161
vecx1 = vec(x1)
162162
J = jac_prototype isa Nothing ? (sparsity isa Nothing ? false.*vecfx.*vecx' : zeros(eltype(x),size(sparsity))) : zero(jac_prototype)
163163
nrows, ncols = size(J)
164-
164+
165165
if !(sparsity isa Nothing)
166166
rows_index, cols_index = ArrayInterface.findstructralnz(sparsity)
167167
rows_index = [rows_index[i] for i in 1:length(rows_index)]
168168
cols_index = [cols_index[i] for i in 1:length(cols_index)]
169169
end
170-
170+
171171
if fdtype == Val{:forward}
172172
@inbounds for color_i 1:maximum(colorvec)
173173
if sparsity isa Nothing
@@ -348,7 +348,7 @@ function finite_difference_jacobian!(
348348
elseif fdtype == Val{:central}
349349
vfx1 = vec(fx1)
350350
@inbounds for color_i 1:maximum(colorvec)
351-
if sparsity isa Nothing
351+
if sparsity isa Nothing
352352
x_save = ArrayInterface.allowed_getindex(x,color_i)
353353
x1_save = ArrayInterface.allowed_getindex(x1,color_i)
354354
epsilon = compute_epsilon(Val{:central}, x_save, relstep, absstep, dir)
@@ -384,7 +384,7 @@ function finite_difference_jacobian!(
384384
elseif fdtype==Val{:complex} && returntype<:Real
385385
epsilon = eps(eltype(x))
386386
@inbounds for color_i 1:maximum(colorvec)
387-
if sparsity isa Nothing
387+
if sparsity isa Nothing
388388
x1_save = ArrayInterface.allowed_getindex(x1,color_i)
389389
ArrayInterface.allowed_setindex!(x1,x1_save + im*epsilon, color_i)
390390
f(fx,x1)

0 commit comments

Comments
 (0)