Understanding in-place and out-of-place methods and how to call them
DiffusionDefinition.jl provides support for both in-place and out-of-place sampling. So which one should you use? In this tutorial we will illustrate which one of the two modes should be used on the example of a Lorenz96 system.
Lorenz '96 system
In this tutorial we will be using a Lorenz '96 system. More details about it can be found here. We will be using it because it is a model that can be defined for different dimension $D$ of the state space. The model is pre-defined in the package and can be simply loaded in with @load_variable_diffusion
, however, for didactic purposes we will define it explicitly using @diffusion_process
.
Out-of-place methods
Out-of-place methods are the type of methods that are allowed to allocate new space for performing computations. If you are able to ascertain that your variables live on a stack allocated memory
, then out-of-place operations can be very efficient. For DiffusionDefinition.jl this corresponds to the state of the diffusion process being represented either by Number
s or SArray
s. So the question becomes: if the state-space of your diffusion is $\RR^d$, when does it make sense to have it represented by SArray
s? Should we simply always use SArray
s? To answer this question you should simply consult the documentation of StaticArrays. It states clearly that if your objects become greater in dimension than $100$, then the benefits of using SArray
s start to taper off and it is better to use regular arrays.
For diffusion processes the largest element will most often be a volatility matrix. If it is represented by a dense array, then for diffusions living on $\RR^{10}$ and higher we should start re-considering use of out-of-place methods. If the volatility matrix can be kept sparse (or in particular diagonal) then the limit for using out-of-place methods are rather diffusions living in $\RR^{100}$. As a general rule of thumb you should use SArray
s (or Number
s) for dimension less than that and regular Array
s otherwise.
Example
Let's define a 50 dimensional Lorenz '96 system:
@diffusion_process Lorenz96_OOP begin
:dimensions
process --> 50
wiener --> 50
:parameters
(θ, σ) --> Float64
:additional
constdiff --> true
end
const si1 = SVector{50,Int64}(mod1.(2:51, 50))
const si2 = SVector{50,Int64}(mod1.(-1:48, 50))
const si3 = SVector{50,Int64}(mod1.(0:49, 50))
const si4 = SVector{50,Int64}(1:50)
@inline function b(t, x, P::Lorenz96_OOP)
( view(x, si1) .- view(x, si2) ) .* view(x, si3) .- view(x, si4) .+ P.θ
end
σ(t, x, P::Lorenz96_OOP) = P.σ * SDiagonal{50,Float64}(I)
Note that any subindexing must be done with SVector
s to avoid creating regular arrays first that are later converted to SArray
s.
In-place methods
In-place methods are those assumed to have a pre-allocated, fixed amount of memory on which all operations are to be performed. Based on the previous section, you can imagine that in-place methods should simply be used whenever out-of-place methods cannot.
Example
For instance if we bump the dimension of the Lorenz '96 system to, say 10000
, then we should most certainly switch to regular arrays. This time however, the output of the drift and volatility should be saved into a pre-allocated buffer
.
@diffusion_process Lorenz96_IP begin
:dimensions
process --> 10000
wiener --> 10000
:parameters
(θ, σ) --> Float64
:additional
constdiff --> true
end
const i1 = mod1.(2:10001, 10000)
const i2 = mod1.(-1:9998, 10000)
const i3 = mod1.(0:9999, 10000)
const i4 = 1:10000
function b!(buffer, t, x, P::Lorenz96_IP)
buffer.b .= ( view(x, i1) .- view(x, i2) ) .* view(x, i3) .- view(x, i4) .+ P.θ
end
σ!(buffer, t, x, P::Lorenz96_IP) = (buffer.σ.diag .= P.σ)