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
endthe 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 :additionalinformation
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
endThe 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
endwhich 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 --> dimensionORwiener --> 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.σ --> Float64orσ --> T(ifTis 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))
AssertionErrorotherwise) 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.66667If 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 (falseby 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 (falseby 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 (falseby default).- Alternative keywords:
:sparsevola,:sparsediffusivity,:sparsevolatility,:sparseσ.
- Alternative keywords:
linear --> true(orfalse) to indicate that a diffusion has a linear structure (falseby default).diagonalbmat --> true(orfalse) to indicate that aBmatrix of a linear diffusion (with a driftb(x):=Bx+β) is diagonal (falseby default).- Alternative keywords:
:diagonalb,:diagonalbmatrix.
- Alternative keywords:
sparsebmat --> true(orfalse) to indicate that aBmatrix of a linear diffusion (with a driftb(x):=Bx+β) is sparse (falseby 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.