Skip to content

Commit 89fcd8b

Browse files
Making autodiff Jacobian reusable (#211)
* jacobian code WIP * Separating Jacobian from tangent_rule * inserting new jacobian back into tangent_rule * tests * import not needed * docstring * changelog and version change
1 parent 8735b71 commit 89fcd8b

File tree

8 files changed

+86
-28
lines changed

8 files changed

+86
-28
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# v3.9.0
2+
3+
A new function `jacobian` that generates Jacobian rule for any `ds<:CoreDynamicalSystem` via
4+
automatic differentiation with `ForwardDiff.jl`.
5+
16
# v3.8.0
27

38
Even more amazing level of integration with ModelingToolkit.jl!

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DynamicalSystemsBase"
22
uuid = "6e36e845-645a-534a-86f2-f5d4aa5a06b4"
33
repo = "https://github.com/JuliaDynamics/DynamicalSystemsBase.jl.git"
4-
version = "3.8.3"
4+
version = "3.9.0"
55

66
[deps]
77
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ CoreDynamicalSystem
7474
TangentDynamicalSystem
7575
current_deviations
7676
set_deviations!
77+
jacobian
7778
orthonormal
7879
```
7980

src/DynamicalSystemsBase.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ include("core_systems/discrete_time_map.jl")
2424
include("core_systems/continuous_time_ode.jl")
2525
include("core_systems/arbitrary_steppable.jl")
2626
include("core_systems/additional_supertypes.jl")
27+
include("core_systems/jacobian.jl")
2728

2829
include("derived_systems/stroboscopic_map.jl")
2930
include("derived_systems/parallel_systems.jl")

src/core_systems/jacobian.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export jacobian
2+
3+
import ForwardDiff
4+
5+
"""
6+
jacobian(ds::CoreDynamicalSystem)
7+
8+
Construct the Jacobian rule for the dynamical system `ds`.
9+
This is done via automatic differentiation using module
10+
[`ForwardDiff`](https://github.com/JuliaDiff/ForwardDiff.jl).
11+
12+
## Description
13+
14+
For out-of-place systems, `jacobian` returns the Jacobian rule as a
15+
function `Jf(u, p, t) -> J0::SMatrix`. Calling `Jf(u, p, t)` will compute the Jacobian
16+
at the state `u`, parameters `p` and time `t` and return the result as `J0`.
17+
For in-place systems, `jacobian` returns the Jacobian rule as a function
18+
`Jf!(J0, u, p, t)`. Calling `Jf!(J0, u, p, t)` will compute the Jacobian
19+
at the state `u`, parameters `p` and time `t` and save the result in `J0`.
20+
"""
21+
function jacobian(ds::CoreDynamicalSystem{IIP}) where {IIP}
22+
_jacobian(ds, Val{IIP}())
23+
end
24+
25+
function _jacobian(ds, ::Val{true})
26+
f = dynamic_rule(ds)
27+
u0 = current_state(ds)
28+
cfg = ForwardDiff.JacobianConfig(
29+
(du, u) -> f(du, u, p, p), deepcopy(u0), deepcopy(u0)
30+
)
31+
Jf! = (J0, u, p, t) -> begin
32+
uv = @view u[:, 1]
33+
du = copy(u)
34+
ForwardDiff.jacobian!(
35+
J0, (du, u) -> f(du, u, p, t), view(du, :, 1), uv, cfg, Val{false}()
36+
)
37+
nothing
38+
end
39+
return Jf!
40+
end
41+
42+
function _jacobian(ds, ::Val{false})
43+
f = dynamic_rule(ds)
44+
Jf = (u, p, t) -> ForwardDiff.jacobian((x) -> f(x, p, t), u)
45+
return Jf
46+
end

src/derived_systems/tangent_space.jl

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ function TangentDynamicalSystem(ds::CoreDynamicalSystem{IIP};
113113
u0_correct = correct_state(Val{IIP}(), u0)
114114
Q0_correct = correct_matrix_type(Val{IIP}(), Q0)
115115
newstate = hcat(u0_correct, Q0_correct)
116+
J = isnothing(J) ? jacobian(ds) : J
116117
newrule = tangent_rule(f, J, J0, Val{IIP}(), Val{k}(), u0_correct)
117118

118119
# Pass everything to analytic system constructors
@@ -137,7 +138,6 @@ correct_matrix_type(::Val{true}, Q::AbstractMatrix) = ismutable(Q) ? Q : Array(Q
137138
###########################################################################################
138139
# Creation of tangent rule
139140
###########################################################################################
140-
import ForwardDiff
141141
# IIP Tangent space dynamics
142142
function tangent_rule(f::F, J::JAC, J0, ::Val{true}, ::Val{k}, u0) where {F, JAC, k}
143143
tangentf = (du, u, p, t) -> begin
@@ -149,37 +149,12 @@ function tangent_rule(f::F, J::JAC, J0, ::Val{true}, ::Val{k}, u0) where {F, JAC
149149
end
150150
return tangentf
151151
end
152-
# for the case of autodiffed systems, a specialized version is created
153-
# so that f! is not called twice in ForwardDiff
154-
function tangent_rule(f::F, ::Nothing, J0, ::Val{true}, ::Val{k}, u0) where {F, k}
155-
let
156-
cfg = ForwardDiff.JacobianConfig(
157-
(du, u) -> f(du, u, p, p), deepcopy(u0), deepcopy(u0)
158-
)
159-
tangentf = (du, u, p, t) -> begin
160-
uv = @view u[:, 1]
161-
ForwardDiff.jacobian!(
162-
J0, (du, u) -> f(du, u, p, t), view(du, :, 1), uv, cfg, Val{false}()
163-
)
164-
mul!((@view du[:, 2:k+1]), J0, (@view u[:, 2:k+1]))
165-
nothing
166-
end
167-
return tangentf
168-
end
169-
end
170152

171153
# OOP Tangent space dynamics
172154
function tangent_rule(f::F, J::JAC, J0, ::Val{false}, ::Val{k}, u0) where {F, JAC, k}
173-
# out of place
174-
if JAC == Nothing
175-
# There is no config needed here
176-
Jf = (u, p, t) -> ForwardDiff.jacobian((x) -> f(x, p, t), u)
177-
else
178-
Jf = J
179-
end
180155
# Initial matrix `J0` is ignored
181156
ws_index = SVector{k, Int}(2:(k+1)...)
182-
tangentf = TangentOOP(f, Jf, ws_index)
157+
tangentf = TangentOOP(f, J, ws_index)
183158
return tangentf
184159
end
185160
struct TangentOOP{F, JAC, k} <: Function

test/jacobian.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using DynamicalSystemsBase, Test
2+
3+
function oop(u, p, t)
4+
return p[1] * SVector(u[1], -u[2])
5+
end
6+
7+
function iip(du, u, p, t)
8+
du .= oop(u, p, t)
9+
return nothing
10+
end
11+
12+
#%%
13+
@testset "IDT=$(IDT), IIP=$(IIP)" for IDT in (true, false), IIP in (false, true)
14+
SystemType = IDT ? DeterministicIteratedMap : CoupledODEs
15+
rule = IIP ? iip : oop
16+
p = 3.0
17+
u0 = [1.0, 1.0]
18+
result = [p 0.0; 0.0 -p]
19+
20+
ds = SystemType(rule, u0, p)
21+
J0 = zeros(dimension(ds), dimension(ds))
22+
J = jacobian(ds)
23+
if IIP
24+
J(J0, current_state(ds), current_parameters(ds), 0.0)
25+
@test J0 == result
26+
else
27+
@test J(current_state(ds), current_parameters(ds), 0.0) == result
28+
end
29+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ testfile(file, testname=defaultname(file)) = @testset "$testname" begin; include
1616
testfile("successful_step.jl")
1717
testfile("mtk_integration.jl")
1818
testfile("trajectory.jl")
19+
testfile("jacobian.jl")
1920
end

0 commit comments

Comments
 (0)