Defining diffusion
The main utility macro introduced in this package is @diffusion_process
. It facilitates very concise definitions of structs characterizing diffusion processes.
There are two (optionally three) parts expected by the macro:
- the name of the diffusion (which, optionally, may contain template parameters in the curly brackets)
- and the recipe for defining a struct
@diffusion_process NAME{TEMPLATE_PARAMETERS} begin
RECIPE
end
the snippet of code above creates a struct named NAME
according to specifications listed in the RECIPE
.
Customization of a struct
The RECIPE
may contain information pertinent to five distinct categories:
- Specification of
:dimensions
- Specification of
:parameters
(their names and datatypes) - Specification of
:constant_parameters
(their names and datatypes) - Specification of
:auxiliary_info
:additional
information
Each type needs to be announced to julia
by starting the list with the corresponding Symbol
(or QuoteNode
).
For many users knowing only about :dimensions
, :parameters
and :additional
will be sufficient and the other categories will not be of much importance.
Example
It's best to look at a simple example. Consider the definition of a Lorenz system:
@diffusion_process Lorenz{T} begin
:dimensions
process --> 3
wiener --> 3
:parameters
_ --> (3, T)
σ --> Float64
:additional
constdiff --> true
diagonaldiff --> true
end
The macro above expands to:
mutable struct Lorenz{T} <: DiffusionProcess{Float64, 3, 3, UnboundedStateSpace()}
p1::T
p2::T
p3::T
σ::Float64
function Lorenz(p1::T, p2::T, p3::T, σ::Float64) where T
new{T}(p1, p2, p3, σ)
end
function Lorenz(; p1::T, p2::T, p3::T, σ::Float64) where T
new{T}(p1, p2, p3, σ)
end
end
which defines a parametric type Lorenz{T}
, together with some handy auxiliary functions specific to any instance of Lorenz{T}
. We may now instantiate the newly defined struct as in
P_f64 = Lorenz(10.0, 28.0, 8.0/3.0, 0.2)
# or
P_f32 = Lorenz(10.0f0, 28.0f0, 8.0f0/3.0f0, 0.2)
We can also call some functions that were auto-generated for the newly defined Lorenz
struct, for instance
DD.parameter_names(Lorenz) == (:p1, :p2, :p3, :σ)
DD.parameter_names(P_f64) == (:p1, :p2, :p3, :σ)
DD.parameters(P_f64) == Dict(:p1 => 10.0, :p2 => 28.0, :p3 => 8.0/3.0, :σ => 0.2)
More functions are automatically defined in the background for each generated DiffusionProcess
, to learn more about them see the list of Utility Functions.
Systematic explanations
The following information can be specified in the definition of a diffusion law when calling a macro @diffusion_process
.
All keywords read by @diffusion_process
are case-insensitive.
Category 1: :dimensions
Specification of the dimension of the process and the dimension of the driving Brownian motion. Must be written in a format:
process --> dimension
ORwiener --> dimension
(eg.process --> 4
).
In both cases, if left unspecified then defaults to dimension=1
.
:dimensions
is not the only keyword that will be recognized by @diffusion_process
as declaring the dimensions of the process. Alternative names that could be used in place of :dimensions
are: :dim
, :dims
, :dimension
Category 2: :parameters
List of all parameters that the law depends on.
@diffusion_process
understands _
as "the user doesn't care about the name, so let's use a generic name based on the letter p
and append it with a disambiguation number so that if there are more than one p
's they are not confused with each other"
The parameters must be specified in one of the following formats:
single-parameter-name --> single-data-type
, (eg.σ --> Float64
orσ --> T
(ifT
is one of the template's labels) or_ --> Vector{Int32}
).
single-parameter-name --> (multiple-data-types,)
(eg.σ --> (Float64, Int64)
,_ --> (Int32, T)
).
single-parameter-name --> (number-of-parameters, data-type)
(eg.σ --> (3, Float64)
)
number-of-parameters
-many parameters of the same data type.(multiple-parameter-names,) --> single-data-type
(eg.(α, β, γ) --> Int64
)
(multiple-parameter-names,) --> (multiple-data-types,)
(eg.(α, β, γ) --> (Float64, Int64, T)
)
AssertionError
otherwise) and sets them to be of the corresponding type.
:parameters
keyword alternatives: :param
, :params
Category 3: :constant_parameters
These can be defined in exactly the same way as :parameters
. The only purpose for splitting the parameters into :constant_parameters
and :parameters
is to indicate to Julia
that the set of all parameters may be split into two conceptually different groups. In particular, @diffusion_process
defines utility functions that act differently with :constant_parameters
and :parameters
:
DiffusionDefinition.parameters
— Functionparameters(P::DiffusionProcess)
Return a tuple of pairs of parameter_name
=> parameter_value
.
DiffusionDefinition.const_parameters
— Functionconst_parameters(P::DiffusionProcess)
Return a tuple of pairs of parameter_name
=> parameter_value
. Return only those parameteres that are considered to be constant
.
DiffusionDefinition.var_parameters
— Functionvar_parameters(P::DiffusionProcess)
Return a tuple of pairs of parameter_name
=> parameter_value
. Return only those parameteres that are considered to be variable
.
DiffusionDefinition.parameter_names
— Functionparameter_names(P::DiffusionProcess)
Return a tuple with the names of all paremeters.
parameter_names(::Type{<:DiffusionProcess})
Return a tuple with the names of all paremeters.
DiffusionDefinition.const_parameter_names
— Functionconst_parameter_names(P::DiffusionProcess)
Return a tuple with the names of all paremeters that are considered to be constant
.
const_parameter_names(P::Type{<:DiffusionProcess})
Return a tuple with the names of all paremeters that are considered to be constant
.
DiffusionDefinition.var_parameter_names
— Functionvar_parameter_names(P::DiffusionProcess)
Return a tuple with the names of all paremeters that are considered to be variable
.
var_parameter_names(P::Type{<:DiffusionProcess})
Return a tuple with the names of all paremeters that are considered to be variable
.
The split into constant
and variable
parameters is not done at a compile time and can also be done by hand after the struct
with the diffusion has been constructed. For instance, in the Lorenz
example above we have:
julia> DD.parameters(P_f64)
Dict{Symbol,Float64} with 4 entries:
:p2 => 28.0
:σ => 0.2
:p1 => 10.0
:p3 => 2.66667
julia> DD.const_parameters(P_f64)
Dict{Any,Any} with 0 entries
julia> DD.var_parameters(P_f64)
Dict{Symbol,Float64} with 4 entries:
:p2 => 28.0
:σ => 0.2
:p1 => 10.0
:p3 => 2.66667
If we wanted to change our mind and define, say, :p1
and :p3
as constant
parameters we could do that by overwriting the definition of the const_parameter_names(::Type{<:CustomDiffusionLaw})
method, as all other functions of this type are computed as a byproduct of parameter_names(::Type{<:CustomDiffusionLaw})
and const_parameter_names(::Type{<:CustomDiffusionLaw})
and the former one should never change.
DD.const_parameter_names(::Type{<:Lorenz}) = (:p1, :p3)
That's all that needs to be changed.
:constant_parameters
keyword alternatives: const_parameters
, :const_param
, :const_params
, :constparameters
, :constparam
, :constparams
, :constant_param
, :constant_params
, :constantparameters
, :constantparam
, and :constantparams
.
Category 4: :auxiliary_info
Information about the end-points of the diffusion. This is a useful feature for guided proposals or simulation of diffusion bridges, where the process is conditioned to hit a certain end-point. In particular, it is used quite extensively in the package GuidedProposals.jl. The following fields can be defined:
t0
(also accepts:t_0
): the starting time-pointT
: the final time-pointv0
(also accepts:obs0
,:v_0
,:obs_0
): the starting observationvT
(also accepts:obsT
,:v_T
,:obs_T
): the observation at the terminal time
Each one of these fields can be defined in a format field-name --> field-type
(e.g. T --> Float64
).
auxiliary_info
keyword alternatives: :aux_info
, :end_points
, :end_point_info
Category 5: :additional
The additional information provides some additional decorators that helps the compiler use specialized functions when called on instances of corresponding diffusion processes. The following information can be specified
constdiff --> true
(orfalse
) depending on whether the volatility coefficient is independent from the state variable (false
by default).- Alternative keywords:
:constvola
,:constdiffusivity
,:constvolatility
,:constσ
,:constantdiff
,:constantvola
,:constantdiffusivity
,:constantvolatility
,:constantσ
.
- Alternative keywords:
diagonaldiff --> true
(orfalse
) to indicate that the volatility coefficient is represented by a diagonal matrix (false
by default).- Alternative keywords:
:diagonalvola
,:diagonaldiffusivity
,:diagonalvolatility
,:diagonalσ
,:diagdiff
,:diagvola
,:diagdiffusivity
,:diagvolatility
,:diagσ
.
- Alternative keywords:
sparsediff --> true
(orfalse
) to indicate that the volatility coefficient is a represented by a sparse matrix (false
by default).- Alternative keywords:
:sparsevola
,:sparsediffusivity
,:sparsevolatility
,:sparseσ
.
- Alternative keywords:
linear --> true
(orfalse
) to indicate that a diffusion has a linear structure (false
by default).diagonalbmat --> true
(orfalse
) to indicate that aB
matrix of a linear diffusion (with a driftb(x):=Bx+β
) is diagonal (false
by default).- Alternative keywords:
:diagonalb
,:diagonalbmatrix
.
- Alternative keywords:
sparsebmat --> true
(orfalse
) to indicate that aB
matrix of a linear diffusion (with a driftb(x):=Bx+β
) is sparse (false
by default).- Alternative keywords:
:sparseb
,:sparsebmatrix
.
- Alternative keywords:
statespace --> type-of-state-space-restrictions
(eg.statespace --> UnboundedStateSpace()
) indicates any restrictions made on the state-space of a diffusion process.eltype --> type-of-parameter
(eg.eltype --> Float64
) disambiguate the parameter type in case multiple types are used. This is useful for automatic differentiation where the derivatives of only a subset of parameters are taken and it is the eltype of those parameters that is of interest. [TODO come back, not sure anymore if it has any use]
:additional
keyword alternatives: :extra
Additional functions
For each struct defining a diffusion law we may define some additional functions that are not automatically generated by the @diffusion_process
macro. These additional functions can provide us with some extra functionality implemented in other packages of the JuliaDiffusionBayes suite.
There are two groups of extra functions
Functions that are needed for the conjugate updates from the package DiffusionMCMC.jl. They are discussed in detail here.
Functions needed for blocking, that can be implemented for instance with the package GuidedProposals.jl or DiffusionMCMC.jl. These need to be overwritten only if your diffusion is hypoelliptic, otherwise the defaults will work just fine. The first one is a function
nonhypo
, with the documentation that can be found here. It can be overwritten directly, or can be defined automatically when defining conjugate updates. The second function is
DiffusionDefinition.nonhypo_σ
— Functionnonhypo_σ((t,i)::IndexedTime, x, P::DiffusionProcess)
Return a sub-matrix of the full volatility matrix σ
that consists of non-zero rows of σ
.
nonhypo_σ(t::Float64, x, P)
Return a sub-matrix of the full volatility matrix σ
that consists of non-zero rows of σ
.
The functions from the latter group are not discussed any further in this documentation. Please see the tutorial on blocking from GuidedProposals.jl to learn more.