Skip to content

Commit 1d92c76

Browse files
authored
Merge pull request #10 from JuliaDiffEq/finitediff
Finite difference branch (Calculus.jl replacement)
2 parents 2781441 + b76ba94 commit 1d92c76

File tree

4 files changed

+304
-3
lines changed

4 files changed

+304
-3
lines changed

src/DiffEqDiffTools.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ __precompile__()
22

33
module DiffEqDiffTools
44

5-
# package code goes here
5+
include("finitediff.jl")
66

77
end # module

src/finitediff.jl

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
#=
2+
Very heavily inspired by Calculus.jl, but with an emphasis on performance and DiffEq API convenience.
3+
=#
4+
5+
#=
6+
Compute the finite difference interval epsilon.
7+
Reference: Numerical Recipes, chapter 5.7.
8+
=#
9+
@inline function compute_epsilon{T<:Real}(::Type{Val{:forward}}, x::T, eps_sqrt::T=sqrt(eps(T)))
10+
eps_sqrt * max(one(T), abs(x))
11+
end
12+
13+
@inline function compute_epsilon{T<:Real}(::Type{Val{:central}}, x::T, eps_cbrt::T=cbrt(eps(T)))
14+
eps_cbrt * max(one(T), abs(x))
15+
end
16+
17+
@inline function compute_epsilon_factor{T<:Real}(fdtype::DataType, ::Type{T})
18+
if fdtype==Val{:forward}
19+
return sqrt(eps(T))
20+
elseif fdtype==Val{:central}
21+
return cbrt(eps(T))
22+
else
23+
error("Unrecognized fdtype: must be Val{:forward} or Val{:central}.")
24+
end
25+
end
26+
27+
28+
#=
29+
Compute the derivative df of a real-valued callable f on a collection of points x.
30+
Generic fallbacks for AbstractArrays that are not StridedArrays.
31+
# TODO: test the fallbacks
32+
=#
33+
function finite_difference{T<:Real}(f, x::AbstractArray{T}, fdtype::DataType, fx::Union{Void,AbstractArray{T}}=nothing, funtype::DataType=Val{:Default})
34+
df = zeros(T, size(x))
35+
finite_difference!(df, f, x, fdtype, fx, funtype)
36+
end
37+
38+
function finite_difference!{T<:Real}(df::AbstractArray{T}, f, x::AbstractArray{T}, fdtype::DataType, fx::Union{Void,AbstractArray{T}}, ::Type{Val{:Default}})
39+
if fdtype == Val{:forward}
40+
epsilon_factor = compute_epsilon_factor(fdtype, T)
41+
@. epsilon = compute_epsilon(fdtype, x, epsilon_factor)
42+
if typeof(fx) == Void
43+
@. df = (f(x+epsilon) - f(x)) / epsilon
44+
else
45+
@. df = (f(x+epsilon) - fx) / epsilon
46+
end
47+
elseif fdtype == Val{:central}
48+
epsilon_factor = compute_epsilon_factor(fdtype, T)
49+
@. epsilon = compute_epsilon(fdtype, x, epsilon_factor)
50+
@. df = (f(x+epsilon) - f(x-epsilon)) / (2 * epsilon)
51+
elseif fdtype == Val{:complex}
52+
epsilon = eps(T)
53+
@. df = imag(f(x+im*epsilon)) / epsilon
54+
end
55+
df
56+
end
57+
58+
function finite_difference!{T<:Real}(df::AbstractArray{T}, f, x::T, fdtype::DataType, fx::AbstractArray{T}, ::Type{Val{:DiffEqDerivativeWrapper}})
59+
fx1 = f.fx1
60+
if fdtype == Val{:forward}
61+
epsilon = compute_epsilon(fdtype, x)
62+
f(fx, x)
63+
f(fx1, x+epsilon)
64+
@. df = (fx1 - fx) / epsilon
65+
elseif fdtype == Val{:central}
66+
epsilon = compute_epsilon(fdtype, x)
67+
f(fx, x-epsilon)
68+
f(fx1, x+epsilon)
69+
@. df = (fx1 - fx) / (2 * epsilon)
70+
elseif fdtype == Val{:complex}
71+
epsilon = eps(T)
72+
f(fx, f(x+im*epsilon))
73+
@. df = imag(fx) / epsilon
74+
end
75+
df
76+
end
77+
78+
#=
79+
Compute the derivative df of a real-valued callable f on a collection of points x.
80+
Optimized implementations for StridedArrays.
81+
=#
82+
function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, ::Union{Void,StridedArray{T}}, ::Type{Val{:Default}})
83+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
84+
@inbounds for i in 1 : length(x)
85+
epsilon = compute_epsilon(Val{:central}, x[i], epsilon_factor)
86+
epsilon_double_inv = one(T) / (2*epsilon)
87+
x_plus, x_minus = x[i]+epsilon, x[i]-epsilon
88+
df[i] = (f(x_plus) - f(x_minus)) * epsilon_double_inv
89+
end
90+
df
91+
end
92+
93+
function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, ::Void, ::Type{Val{:Default}})
94+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
95+
@inbounds for i in 1 : length(x)
96+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
97+
epsilon_inv = one(T) / epsilon
98+
x_plus = x[i] + epsilon
99+
df[i] = (f(x_plus) - f(x[i])) * epsilon_inv
100+
end
101+
df
102+
end
103+
104+
function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T}, ::Type{Val{:Default}})
105+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
106+
@inbounds for i in 1 : length(x)
107+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
108+
epsilon_inv = one(T) / epsilon
109+
x_plus = x[i] + epsilon
110+
df[i] = (f(x_plus) - fx[i]) * epsilon_inv
111+
end
112+
df
113+
end
114+
115+
#=
116+
Compute the derivative df of a real-valued callable f on a collection of points x.
117+
Single point implementations.
118+
=#
119+
function finite_difference{T<:Real}(f, x::T, fdtype::DataType, f_x::Union{Void,T}=nothing)
120+
if fdtype == Val{:complex}
121+
epsilon = eps(T)
122+
return imag(f(x+im*epsilon)) / epsilon
123+
else
124+
epsilon = compute_epsilon(fdtype, x)
125+
return finite_difference_kernel(f, x, fdtype, epsilon, f_x)
126+
end
127+
end
128+
129+
@inline function finite_difference_kernel{T<:Real}(f, x::T, ::Type{Val{:forward}}, epsilon::T, fx::Union{Void,T})
130+
if typeof(fx) == Void
131+
return (f(x+epsilon) - f(x)) / epsilon
132+
else
133+
return (f(x+epsilon) - fx) / epsilon
134+
end
135+
end
136+
137+
@inline function finite_difference_kernel{T<:Real}(f, x::T, ::Type{Val{:central}}, epsilon::T, ::Union{Void,T}=nothing)
138+
(f(x+epsilon) - f(x-epsilon)) / (2 * epsilon)
139+
end
140+
141+
# TODO: derivatives for complex-valued callables
142+
143+
144+
#=
145+
Compute the Jacobian matrix of a real-valued callable f: R^n -> R^m.
146+
=#
147+
function finite_difference_jacobian{T<:Real}(f, x::AbstractArray{T}, fdtype::DataType=Val{:central}, funtype::DataType=Val{:Default})
148+
if funtype==Val{:Default}
149+
fx = f.(x)
150+
elseif funtype==Val{:DiffEqJacobianWrapper}
151+
fx = f(x)
152+
else
153+
error("Unrecognized funtype: must be Val{:Default} or Val{:DiffEqJacobianWrapper}.")
154+
end
155+
J = zeros(T, length(fx), length(x))
156+
finite_difference_jacobian!(J, f, x, fdtype, fx, funtype)
157+
end
158+
159+
function finite_difference_jacobian!{T<:Real}(J::AbstractArray{T}, f, x::AbstractArray{T}, fdtype::DataType, fx::AbstractArray{T}, ::DataType)
160+
# This is an inefficient fallback that only makes sense if setindex/getindex are unavailable, e.g. GPUArrays etc.
161+
m, n = size(J)
162+
epsilon_factor = compute_epsilon_factor(fdtype, T)
163+
if t == Val{:forward}
164+
shifted_x = copy(x)
165+
for i in 1:n
166+
epsilon = compute_epsilon(t, x[i], epsilon_factor)
167+
shifted_x[i] += epsilon
168+
J[:, i] .= (f(shifted_x) - f_x) / epsilon
169+
shifted_x[i] = x[i]
170+
end
171+
elseif t == Val{:central}
172+
shifted_x_plus = copy(x)
173+
shifted_x_minus = copy(x)
174+
for i in 1:n
175+
epsilon = compute_epsilon(t, x[i], epsilon_factor)
176+
shifted_x_plus[i] += epsilon
177+
shifted_x_minus[i] -= epsilon
178+
J[:, i] .= (f(shifted_x_plus) - f(shifted_x_minus)) / (epsilon + epsilon)
179+
shifted_x_plus[i] = x[i]
180+
shifted_x_minus[i] = x[i]
181+
end
182+
else
183+
error("Unrecognized fdtype: must be Val{:forward} or Val{:central}.")
184+
end
185+
J
186+
end
187+
188+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, fx::StridedArray{T}, ::Type{Val{:Default}})
189+
m, n = size(J)
190+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
191+
@inbounds for i in 1:n
192+
epsilon = compute_epsilon(Val{:central}, x[i], epsilon_factor)
193+
epsilon_double_inv = one(T) / (2 * epsilon)
194+
for j in 1:m
195+
if i==j
196+
J[j,i] = (f(x[j]+epsilon) - f(x[j]-epsilon)) * epsilon_double_inv
197+
else
198+
J[j,i] = zero(T)
199+
end
200+
end
201+
end
202+
J
203+
end
204+
205+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T}, ::Type{Val{:Default}})
206+
m, n = size(J)
207+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
208+
@inbounds for i in 1:n
209+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
210+
epsilon_inv = one(T) / epsilon
211+
for j in 1:m
212+
if i==j
213+
J[j,i] = (f(x[j]+epsilon) - fx[j]) * epsilon_inv
214+
else
215+
J[j,i] = zero(T)
216+
end
217+
end
218+
end
219+
J
220+
end
221+
222+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:complex}}, fx::StridedArray{T}, ::Type{Val{:Default}})
223+
m, n = size(J)
224+
epsilon = eps(T)
225+
epsilon_inv = one(T) / epsilon
226+
@inbounds for i in 1:n
227+
for j in 1:m
228+
if i==j
229+
J[j,i] = imag(f(x[j]+im*epsilon)) * epsilon_inv
230+
else
231+
J[j,i] = zero(T)
232+
end
233+
end
234+
end
235+
J
236+
end
237+
238+
# efficient implementations for OrdinaryDiffEq Jacobian wrappers, assuming the system function supplies StridedArrays
239+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T}, ::Type{Val{:JacobianWrapper}})
240+
m, n = size(J)
241+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
242+
x1, fx1 = f.x1, f.fx1
243+
copy!(x1, x)
244+
copy!(fx1, fx)
245+
@inbounds for i in 1:n
246+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
247+
epsilon_inv = one(T) / epsilon
248+
x1[i] += epsilon
249+
f(fx, x)
250+
f(fx1, x1)
251+
@. J[:,i] = (fx-fx1) * epsilon_inv
252+
x1[i] -= epsilon
253+
end
254+
J
255+
end
256+
257+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, fx::StridedArray{T}, ::Type{Val{:JacobianWrapper}})
258+
m, n = size(J)
259+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
260+
x1, fx1 = f.x1, f.fx1
261+
copy!(x1, x)
262+
copy!(fx1, fx)
263+
@inbounds for i in 1:n
264+
epsilon = compute_epsilon(Val{:central}, x[i], epsilon_factor)
265+
epsilon_double_inv = one(T) / (2 * epsilon)
266+
x[i] += epsilon
267+
x1[i] -= epsilon
268+
f(fx, x)
269+
f(fx1, x1)
270+
@. J[:,i] = (fx-fx1) * epsilon_double_inv
271+
x[i] -= epsilon
272+
x1[i] += epsilon
273+
end
274+
J
275+
end
276+
277+
278+
# TODO: Jacobians for complex-valued callables

test/finitedifftests.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
x = collect(linspace(-2π, 2π, 100))
2+
y = sin.(x)
3+
df = zeros(100)
4+
df_ref = cos.(x)
5+
J_ref = diagm(cos.(x))
6+
7+
err_func(a,b) = maximum(abs.(a-b))
8+
9+
# TODO: add tests for non-StridedArrays and with more complicated functions
10+
11+
# derivative tests
12+
@test err_func(DiffEqDiffTools.finite_difference(sin, x, Val{:forward}), df_ref) < 1e-4
13+
@test err_func(DiffEqDiffTools.finite_difference(sin, x, Val{:forward}, y), df_ref) < 1e-4
14+
@test err_func(DiffEqDiffTools.finite_difference(sin, x, Val{:central}), df_ref) < 1e-8
15+
@test err_func(DiffEqDiffTools.finite_difference(sin, x, Val{:complex}), df_ref) < 1e-15
16+
@test err_func(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:forward}, nothing, Val{:Default}), df_ref) < 1e-4
17+
@test err_func(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:forward}, y, Val{:Default}), df_ref) < 1e-4
18+
@test err_func(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:central}, nothing, Val{:Default}), df_ref) < 1e-8
19+
@test err_func(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:complex}, nothing, Val{:Default}), df_ref) < 1e-15
20+
21+
# Jacobian tests
22+
@test err_func(DiffEqDiffTools.finite_difference_jacobian(sin, x, Val{:forward}), J_ref) < 1e-4
23+
@test err_func(DiffEqDiffTools.finite_difference_jacobian(sin, x, Val{:central}), J_ref) < 1e-8
24+
@test err_func(DiffEqDiffTools.finite_difference_jacobian(sin, x, Val{:complex}), J_ref) < 1e-15

test/runtests.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using DiffEqDiffTools
22
using Base.Test
33

4-
# write your own tests here
5-
@test 1 == 2
4+
include("finitedifftests.jl")

0 commit comments

Comments
 (0)