Skip to content

Commit d5817b5

Browse files
committed
Finish up preliminary implementation of Jacobians for DiffEq wrappers
1 parent 98f931b commit d5817b5

File tree

2 files changed

+113
-70
lines changed

2 files changed

+113
-70
lines changed

src/finitediff.jl

Lines changed: 111 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,51 @@ end
1414
eps_cbrt * max(one(T), abs(x))
1515
end
1616

17-
@inline function compute_epsilon{T<:Complex}(::Type{Val{:complex}}, x::T)
17+
@inline function compute_epsilon{T<:Complex}(::Type{Val{:Complex}}, x::T)
1818
eps(real(x))
1919
end
2020

21+
@inline function compute_epsilon_factor{T<:Real}(fdtype::DataType, ::Type{T})
22+
if fdtype==Val{:forward}
23+
return sqrt(eps(T))
24+
elseif fdtype==Val{:central}
25+
return cbrt(eps(T))
26+
else
27+
error("Unrecognized fdtype: must be Val{:forward} or Val{:central}.")
28+
end
29+
end
30+
2131

2232
#=
2333
Compute the derivative df of a real-valued callable f on a collection of points x.
2434
Generic fallbacks for AbstractArrays that are not StridedArrays.
35+
# TODO: test the fallbacks
2536
=#
26-
function finite_difference{T<:Real}(f, x::AbstractArray{T}, ::Type{Val{:central}}, ::Union{Void,AbstractArray{T}}=nothing)
27-
df = zeros(T, size(x))
28-
finite_difference!(df, f, x, Val{:central})
29-
end
30-
31-
function finite_difference!{T<:Real}(df::AbstractArray{T}, f, x::AbstractArray{T}, ::Type{Val{:central}}, ::Union{Void,AbstractArray{T}}=nothing)
32-
eps_sqrt = sqrt(eps(T))
33-
epsilon = compute_epsilon.(Val{:central}, x, eps_sqrt)
34-
@. df = (f(x+epsilon) - f(x-epsilon)) / (2 * epsilon)
35-
end
36-
37-
function finite_difference{T<:Real}(f, x::AbstractArray{T}, ::Type{Val{:forward}}, f_x::AbstractArray{T}=f.(x))
37+
function finite_difference{T<:Real}(f, x::AbstractArray{T}, fdtype::DataType, fx::Union{Void,AbstractArray{T}}=nothing)
3838
df = zeros(T, size(x))
39-
finite_difference!(df, f, x, Val{:forward}, f_x)
39+
finite_difference!(df, f, x, fdtype, fx)
4040
end
4141

4242
function finite_difference!{T<:Real}(df::AbstractArray{T}, f, x::AbstractArray{T}, ::Type{Val{:forward}}, f_x::AbstractArray{T}=f.(x))
43-
eps_cbrt = cbrt(eps(T))
44-
epsilon = compute_epsilon.(Val{:forward}, x, eps_cbrt)
43+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
44+
@. epsilon = compute_epsilon(Val{:forward}, x)
4545
@. df = (f(x+epsilon) - f_x) / epsilon
4646
end
4747

48+
function finite_difference!{T<:Real}(df::AbstractArray{T}, f, x::AbstractArray{T}, ::Type{Val{:central}}, ::Union{Void,AbstractArray{T}}=nothing)
49+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
50+
@. epsilon = compute_epsilon(Val{:central}, x, epsilon_factor)
51+
@. df = (f(x+epsilon) - f(x-epsilon)) / (2 * epsilon)
52+
end
4853

4954
#=
5055
Compute the derivative df of a real-valued callable f on a collection of points x.
5156
Optimized implementations for StridedArrays.
5257
=#
53-
function finite_difference{T<:Real}(f, x::StridedArray{T}, ::Type{Val{:central}}, ::Union{Void,StridedArray{T}}=nothing)
54-
df = zeros(T, size(x))
55-
finite_difference!(df, f, x, Val{:central})
56-
end
57-
5858
function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, ::Union{Void,StridedArray{T}}=nothing)
59-
eps_sqrt = sqrt(eps(T))
59+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
6060
@inbounds for i in 1 : length(x)
61-
epsilon = compute_epsilon(Val{:central}, x[i], eps_sqrt)
61+
epsilon = compute_epsilon(Val{:central}, x[i], epsilon_factor)
6262
epsilon_double_inv = one(T) / (2*epsilon)
6363
x_plus, x_minus = x[i]+epsilon, x[i]-epsilon
6464
df[i] = (f(x_plus) - f(x_minus)) * epsilon_double_inv
@@ -77,9 +77,9 @@ function finite_difference{T<:Real}(f, x::StridedArray{T}, ::Type{Val{:forward}}
7777
end
7878

7979
function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}})
80-
eps_cbrt = cbrt(eps(T))
80+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
8181
@inbounds for i in 1 : length(x)
82-
epsilon = compute_epsilon(Val{:forward}, x[i], eps_cbrt)
82+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
8383
epsilon_inv = one(T) / epsilon
8484
x_plus = x[i] + epsilon
8585
df[i] = (f(x_plus) - f(x[i])) * epsilon_inv
@@ -88,9 +88,9 @@ function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T},
8888
end
8989

9090
function finite_difference!{T<:Real}(df::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T})
91-
eps_cbrt = cbrt(eps(T))
91+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
9292
@inbounds for i in 1 : length(x)
93-
epsilon = compute_epsilon(Val{:forward}, x[i], eps_cbrt)
93+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
9494
epsilon_inv = one(T) / epsilon
9595
x_plus = x[i] + epsilon
9696
df[i] = (f(x_plus) - fx[i]) * epsilon_inv
@@ -102,16 +102,16 @@ end
102102
Compute the derivative df of a real-valued callable f on a collection of points x.
103103
Single point implementations.
104104
=#
105-
function finite_difference{T<:Real}(f, x::T, t::DataType, f_x::Union{Void,T}=nothing)
106-
epsilon = compute_epsilon(t, x)
107-
finite_difference_kernel(f, x, t, epsilon, f_x)
105+
function finite_difference{T<:Real}(f, x::T, fdtype::DataType, f_x::Union{Void,T}=nothing)
106+
epsilon = compute_epsilon(fdtype, x)
107+
finite_difference_kernel(f, x, fdtype, epsilon, f_x)
108108
end
109109

110-
@inline function finite_difference_kernel{T<:Real}(f, x::T, ::Type{Val{:forward}}, epsilon::T, f_x::Union{Void,T})
111-
if typeof(f_x) == Void
110+
@inline function finite_difference_kernel{T<:Real}(f, x::T, ::Type{Val{:forward}}, epsilon::T, fx::Union{Void,T})
111+
if typeof(fx) == Void
112112
return (f(x+epsilon) - f(x)) / epsilon
113113
else
114-
return (f(x+epsilon) - f_x) / epsilon
114+
return (f(x+epsilon) - fx) / epsilon
115115
end
116116
end
117117

@@ -125,53 +125,56 @@ end
125125
#=
126126
Compute the Jacobian matrix of a real-valued callable f: R^n -> R^m.
127127
=#
128-
function finite_difference_jacobian{T<:Real}(f, x::AbstractArray{T}, t::DataType)
129-
fx = f.(x)
128+
function finite_difference_jacobian{T<:Real}(f, x::AbstractArray{T}, fdtype::DataType=Val{:central}, funtype::DataType=Val{:Default})
129+
if funtype==Val{:Default}
130+
fx = f.(x)
131+
elseif funtype==Val{:DiffEqJacobianWrapper}
132+
f(fx, x)
133+
else
134+
error("Unrecognized funtype: must be Val{:Default} or Val{:DiffEqJacobianWrapper}.")
135+
end
130136
J = zeros(T, length(fx), length(x))
131-
finite_difference_jacobian!(J, f, x, t, fx)
137+
finite_difference_jacobian!(J, f, x, fdtype, fx, funtype)
132138
end
133139

134-
function finite_difference_jacobian!{T<:Real}(J::AbstractArray{T}, f, x::AbstractArray{T}, t::DataType, fx::AbstractArray{T})
140+
function finite_difference_jacobian!{T<:Real}(J::AbstractArray{T}, f, x::AbstractArray{T}, fdtype::DataType, fx::AbstractArray{T}, ::DataType)
141+
# This is an inefficient fallback that only makes sense if setindex/getindex are unavailable, e.g. GPUArrays etc.
135142
m, n = size(J)
143+
epsilon_factor = compute_epsilon_factor(fdtype, T)
136144
if t == Val{:forward}
137145
shifted_x = copy(x)
138-
eps_sqrt = sqrt(eps(T))
139-
for i in 1 : n
140-
epsilon = compute_epsilon(t, x, eps_sqrt)
146+
for i in 1:n
147+
epsilon = compute_epsilon(t, x[i], epsilon_factor)
141148
shifted_x[i] += epsilon
142-
@. J[:, i] = (f(shifted_x) - f_x) / epsilon
149+
J[:, i] .= (f(shifted_x) - f_x) / epsilon
143150
shifted_x[i] = x[i]
144151
end
145152
elseif t == Val{:central}
146-
shifted_x_plus = copy(x)
153+
shifted_x_plus = copy(x)
147154
shifted_x_minus = copy(x)
148-
eps_cbrt = cbrt(eps(T))
149-
for i in 1 : n
150-
epsilon = compute_epsilon(t, x, eps_cbrt)
151-
shifted_x_plus[i] += epsilon
155+
for i in 1:n
156+
epsilon = compute_epsilon(t, x[i], epsilon_factor)
157+
shifted_x_plus[i] += epsilon
152158
shifted_x_minus[i] -= epsilon
153-
@. J[:, i] = (f(shifted_x_plus) - f(shifted_x_minus)) / (epsilon + epsilon)
154-
shifted_x_plus[i] = x[i]
159+
J[:, i] .= (f(shifted_x_plus) - f(shifted_x_minus)) / (epsilon + epsilon)
160+
shifted_x_plus[i] = x[i]
155161
shifted_x_minus[i] = x[i]
156162
end
163+
else
164+
error("Unrecognized fdtype: must be Val{:forward} or Val{:central}.")
157165
end
158166
J
159167
end
160168

161-
function finite_difference_jacobian{T<:Real}(f, x::StridedArray{T}, t::DataType, fx::StridedArray{T})
162-
J = zeros(T, length(fx), length(x))
163-
finite_difference_jacobian!(J, f, x, t, fx)
164-
end
165-
166-
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T})
169+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, fx::StridedArray{T}, ::Type{Val{:Default}})
167170
m, n = size(J)
168-
eps_sqrt = sqrt(eps(T))
169-
@inbounds for i = 1 : n
170-
epsilon = compute_epsilon(Val{:forward}, x[i], eps_sqrt)
171-
epsilon_inv = one(T) / epsilon
172-
for j in 1 : m
173-
if i == j
174-
J[j,i] = (f(x[j]+epsilon) - fx[j]) * epsilon_inv
171+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
172+
@inbounds for i in 1:n
173+
epsilon = compute_epsilon(Val{:central}, x[i], epsilon_factor)
174+
epsilon_double_inv = one(T) / (2 * epsilon)
175+
for j in 1:m
176+
if i==j
177+
J[j,i] = (f(x[j]+epsilon) - f(x[j]-epsilon)) * epsilon_double_inv
175178
else
176179
J[j,i] = zero(T)
177180
end
@@ -180,15 +183,15 @@ function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedA
180183
J
181184
end
182185

183-
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, ::Union{Void,StridedArray{T}}=nothing)
186+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T}, ::Type{Val{:Default}})
184187
m, n = size(J)
185-
eps_cbrt = cbrt(eps(T))
186-
@inbounds for i = 1 : n
187-
epsilon = compute_epsilon(Val{:central}, x[i], eps_cbrt)
188-
epsilon_double_inv = one(T) / (2 * epsilon)
189-
for j in 1 : m
188+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
189+
@inbounds for i in 1:n
190+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
191+
epsilon_inv = one(T) / epsilon
192+
for j in 1:m
190193
if i==j
191-
J[j,i] = (f(x[j]+epsilon) - f(x[j]-epsilon)) * epsilon_double_inv
194+
J[j,i] = (f(x[j]+epsilon) - fx[j]) * epsilon_inv
192195
else
193196
J[j,i] = zero(T)
194197
end
@@ -197,4 +200,44 @@ function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedA
197200
J
198201
end
199202

203+
# efficient implementations for OrdinaryDiffEq Jacobian wrappers, assuming the system function supplies StridedArrays
204+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:forward}}, fx::StridedArray{T}, ::Type{Val{:JacobianWrapper}})
205+
m, n = size(J)
206+
epsilon_factor = compute_epsilon_factor(Val{:forward}, T)
207+
x1, fx1 = f.x1, f.fx1
208+
copy!(x1, x)
209+
copy!(fx1, fx)
210+
@inbounds for i in 1:n
211+
epsilon = compute_epsilon(Val{:forward}, x[i], epsilon_factor)
212+
epsilon_inv = one(T) / epsilon
213+
x1[i] += epsilon
214+
f(fx, x)
215+
f(fx1, x1)
216+
@. J[:,i] = (fx-fx1) * epsilon_inv
217+
x1[i] -= epsilon
218+
end
219+
J
220+
end
221+
222+
function finite_difference_jacobian!{T<:Real}(J::StridedArray{T}, f, x::StridedArray{T}, ::Type{Val{:central}}, fx::StridedArray{T}, ::Type{Val{:JacobianWrapper}})
223+
m, n = size(J)
224+
epsilon_factor = compute_epsilon_factor(Val{:central}, T)
225+
x1, fx1 = f.x1, f.fx1
226+
copy!(x1, x)
227+
copy!(fx1, fx)
228+
@inbounds for i in 1:n
229+
epsilon = compute_epsilon(Val{:central}, x[i], epsilon_factor)
230+
epsilon_double_inv = one(T) / (2 * epsilon)
231+
x[i] += epsilon
232+
x1[i] -= epsilon
233+
f(fx, x)
234+
f(fx1, x1)
235+
@. J[:,i] = (fx-fx1) * epsilon_double_inv
236+
x[i] -= epsilon
237+
x1[i] += epsilon
238+
end
239+
J
240+
end
241+
242+
200243
# TODO: Jacobians for complex-valued callables

test/finitedifftests.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ df_ref = cos.(x)
66
# TODO: add tests for non-StridedArrays and with more complicated functions
77

88
# derivative tests
9-
@test maximum(abs.(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:central}) - df_ref)) < 1e-8
109
@test maximum(abs.(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:forward}) - df_ref)) < 1e-4
1110
@test maximum(abs.(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:forward}, y) - df_ref)) < 1e-4
11+
@test maximum(abs.(DiffEqDiffTools.finite_difference!(df, sin, x, Val{:central}) - df_ref)) < 1e-8
1212

1313
# Jacobian tests
1414
using Calculus
15-
@test DiffEqDiffTools.finite_difference_jacobian(sin, x, Val{:central}) Calculus.finite_difference_jacobian(sin, x, :central)
1615
@test DiffEqDiffTools.finite_difference_jacobian(sin, x, Val{:forward}) Calculus.finite_difference_jacobian(sin, x, :forward)
16+
@test DiffEqDiffTools.finite_difference_jacobian(sin, x, Val{:central}) Calculus.finite_difference_jacobian(sin, x, :central)

0 commit comments

Comments
 (0)