There are two ways to create an e.g. ContinuousDS
. One is to use closures, like it is done for almost all systems:
function roessler(u0=rand(3); a = 0.2, b = 0.2, c = 5.7)
@inline @inbounds function eom_roessler!(du, u)
du[1] = -u[2]-u[3]
du[2] = u[1] + a*u[2]
du[3] = b + u[3]*(u[1] - c)
end
i = one(eltype(u0))
o = zero(eltype(u0))
J = zeros(eltype(u0), 3, 3)
J[1,:] .= [o, -i, -i]
J[2,:] .= [i, a, o]
J[3,:] .= [u0[3], o, u0[1] - c]
@inline @inbounds function jacob_roessler!(J, u)
J[3, 1] = u[3]; J[3,3] = u[1] - c
end
name = "Roessler76 system (a=$(a), b=$(b), c=$(c))"
return ContinuousDS(u0, eom_roessler!, jacob_roessler!, J; name = name)
end
The thing with this approach is that when one wants to change parameters, one needs to create a completely new instance of ContinuousDS
because the enclosing function has to be called:
ds = rossler(; a = 5)
. Maybe this is extremely inefficient.
A second way to do it is to make a Functor, e.g.:
truct Lorenz96{T <: Real} # Structure with the parameters of Lorenz96 system
F::T
N::Int
end
# Equations of motion
@inbounds function (obj::Lorenz96{T})(dx, x) where T
N, F = obj.N, obj.F
# 3 edge cases
dx[1] = (x[2] - x[N - 1]) * x[N] - x[1] + F
dx[2] = (x[3] - x[N]) * x[1] - x[2] + F
dx[N] = (x[1] - x[N - 2]) * x[N - 1] - x[N] + F
# then the general case
for n in 3:(N - 1)
dx[n] = (x[n + 1] - x[n - 2]) * x[n - 1] - x[n] + F
end
return dx
end
"""
lorenz96(N::Int, u0 = rand(M); F=0.01)
`N` is the chain length, `F` the forcing. Jacobian is created automatically.
"""
function lorenz96(N::Int, u0 = rand(N); F=0.01)
name = "Lorenz96 system, chain of $N (F = $(F))"
lor96 = Lorenz96(F, N) # create struct
return ContinuousDS(u0, lor96; name = name)
end
Becauses Lorenz96
is a mutable type, after the first creation of ds = lorenz96(5; F = 0.2)
one could then simply do ds.eom!.F = 23.4
to change the F
parameter, without needing to re-create a dynamical system instance.
In a recent update I have made all DynamicalSystem
types to be immutable instead. But still, maybe it is worth it to make this change and use functors everywhere?
Maybe @ChrisRackauckas has something to add here? Have you had this dilemma somewhere in DiffEq where you wondered "closure vs functor" ? I know that Julia translates all closures into functors but that pretty much creates a new instance of the functor each time.