SDESystem

Construction of SDESystems

A SDESystem is represented by the state function

$$$dx = f(x, u, t) dt + h(x, u, t)dW$$$

where $t$ is the time, $x \in R^n$ is the value of state, $u \in R^p$ is the value of the input. $W$ is the Wiener process of the system. The output function is defined by

$$$y = g(x, u, t)$$$

where $y$ is the value of output at time $t$.

As an example consider a system with the following stochastic differential equation

$$$\begin{array}{l} dx = -x dt - x dW \end{array}$$$

and the following output equation

$$$y = x$$$

The state function statefunc and the output function outputfunc is defined as follows.

julia> using Causal # hide

julia> f(dx, x, u, t) = (dx[1] = -x[1])
f (generic function with 1 method)

julia> h(dx, x, u, t) = (dx[1] = -x[1])
h (generic function with 1 method)

The state function statefunc is the tuple of drift and diffusion functions

julia> statefunc = (f, h)
(Main.ex-sde_system_ex.f, Main.ex-sde_system_ex.h)

The output function outputfunc is defined as,

julia> g(x, u, t) = x
g (generic function with 1 method)

Note that the in drift function f and diffusion function g, the vector dx is mutated while in the output function g no mutation is done, but the output value is generated instead.

From the definition of drift function f and the diffusion function g, it is seen that the system does not have any input, that is, the input of the system is nothing. Since all the state variables are taken as outputs, the system needs an output bus of length 1. Thus,

julia> input = nothing

julia> output = Outport(1)
1-element Outport{Outpin{Float64}}:
Outpin(eltype:Float64, isbound:false)

At this point, we are ready to construct the system ds.

julia> ds = SDESystem(righthandside=statefunc, readout=g, state=[1.], input=input, output=output)
ERROR: UndefKeywordError: keyword argument drift not assigned

Basic Operation of SDESystems

The basic operation of a SDESystem is the same as those of other dynamical systems. When triggered from its trigger link, a SDESystem reads its time t from its trigger link, reads its input value from its input, solves its state equation, which is a stochastic differential equation, computes its output and writes its computed output to its output bus.

In this section, we continue with the system ds constructed in the previous section. To make ds drivable, we need to launch it.

julia> iport, trg, hnd = Inport(1), Outpin(), Inpin{Bool}()
(Inport(numpins:1, eltype:Inpin{Float64}), Outpin(eltype:Float64, isbound:false), Inpin(eltype:Bool, isbound:false))

julia> connect!(ds.output, iport)
ERROR: UndefVarError: ds not defined

julia> connect!(trg, ds.trigger)
ERROR: UndefVarError: ds not defined

julia> connect!(ds.handshake, hnd)
ERROR: UndefVarError: ds not defined

ERROR: UndefVarError: ds not defined

julia> task2 = @async while true
all(take!(iport) .=== NaN) && break
end
MethodError: no method matching take!(::Missing)
Closest candidates are:
take!(!Matched::IOBuffer) at iobuffer.jl:385
take!(!Matched::Base.GenericIOBuffer) at iobuffer.jl:370
take!(!Matched::Channel) at channels.jl:383
...
Stacktrace:
[1] take!(pin::Inpin{Float64})
@ Causal ~/.julia/packages/Causal/vOCIT/src/connections/pin.jl:111
[4] getindex
[5] macro expansion
[6] macro expansion
@ ./simdloop.jl:77 [inlined]
[7] copyto!
[8] copyto!
[9] copy
[10] materialize
[11] take!(inport::Inport{Inpin{Float64}})
@ Causal ~/.julia/packages/Causal/vOCIT/src/connections/port.jl:186
[12] macro expansion
@ ./none:2 [inlined]
[13] (::Main.ex-sde_system_ex.var"#1#2")()

When launched, ds can be driven. For this, either of the syntax put!(ds.trigger, t) or drive(ds, t) can be used.

julia> put!(trg, 1.)
ERROR: MethodError: no method matching iterate(::Missing)
Closest candidates are:
iterate(!Matched::Union{LinRange, StepRangeLen}) at range.jl:664
iterate(!Matched::Union{LinRange, StepRangeLen}, !Matched::Int64) at range.jl:664
iterate(!Matched::T) where T<:Union{Base.KeySet{var"#s79", var"#s78"} where {var"#s79", var"#s78"<:Dict}, Base.ValueIterator{var"#s77"} where var"#s77"<:Dict} at dict.jl:693
...

After this command, ds reads its time t from its trigger link, solves its state function and computes its output. The calculated output value is written to the buffer of output. To signal that, the step is takes with success, ds writes true to its handshake link. To further drive ds, this handshake link must be read. For this either of the syntax, take!(ds.handshake) or approve!(ds) can be used

missing

julia> take!(hnd)
ERROR: MethodError: no method matching take!(::Missing)
Closest candidates are:
take!(!Matched::IOBuffer) at iobuffer.jl:385
take!(!Matched::Base.GenericIOBuffer) at iobuffer.jl:370
take!(!Matched::Channel) at channels.jl:383
...

At this point, we can further drive ds.

julia> for t in 2. : 10.
put!(trg, t)
take!(hnd)
end
ERROR: MethodError: no method matching iterate(::Missing)
Closest candidates are:
iterate(!Matched::Union{LinRange, StepRangeLen}) at range.jl:664
iterate(!Matched::Union{LinRange, StepRangeLen}, !Matched::Int64) at range.jl:664
iterate(!Matched::T) where T<:Union{Base.KeySet{var"#s79", var"#s78"} where {var"#s79", var"#s78"<:Dict}, Base.ValueIterator{var"#s77"} where var"#s77"<:Dict} at dict.jl:693
...

Note that during the evolution, the output of ds is written into the buffers of output bus.

ERROR: type Missing has no field buffer
Warning

The values of the output is written into buffers if the output of the systems is not nothing.

When we launched ds, we constructed a task whose state is running which implies that the ds can be drivable. As long as this task is running, ds can be drivable.

Warning

The state of the task is different from running in case an exception is thrown.

To terminate the task securely, we need to terminate ds securely. To do that, can use terminate!(ds).

julia> put!(trg, NaN)
ERROR: MethodError: no method matching iterate(::Missing)
Closest candidates are:
iterate(!Matched::Union{LinRange, StepRangeLen}) at range.jl:664
iterate(!Matched::Union{LinRange, StepRangeLen}, !Matched::Int64) at range.jl:664
iterate(!Matched::T) where T<:Union{Base.KeySet{var"#s79", var"#s78"} where {var"#s79", var"#s78"<:Dict}, Base.ValueIterator{var"#s77"} where var"#s77"<:Dict} at dict.jl:693
...

julia> put!(ds.output, [NaN])
ERROR: UndefVarError: ds not defined

Note that the task is terminated without a hassle.

MethodError: no method matching take!(::Missing)
Closest candidates are:
take!(!Matched::IOBuffer) at iobuffer.jl:385
take!(!Matched::Base.GenericIOBuffer) at iobuffer.jl:370
take!(!Matched::Channel) at channels.jl:383
...
Stacktrace:
[1] take!(pin::Inpin{Float64})
@ Causal ~/.julia/packages/Causal/vOCIT/src/connections/pin.jl:111
[4] getindex
[5] macro expansion
[6] macro expansion
@ ./simdloop.jl:77 [inlined]
[7] copyto!
[8] copyto!
[9] copy
[10] materialize
[11] take!(inport::Inport{Inpin{Float64}})
@ Causal ~/.julia/packages/Causal/vOCIT/src/connections/port.jl:186
[12] macro expansion
@ ./none:2 [inlined]
[13] (::Main.ex-sde_system_ex.var"#1#2")()

Full API

Causal.@def_sde_systemMacro
@def_sde_system ex

where ex is the expression to define to define a new AbstractSDESystem component type. The usage is as follows:

@def_sde_system mutable struct MySDESystem{T1,T2,T3,...,TN,OP,RH,RO,ST,IP,OP} <: AbstractSDESystem
param1::T1 = param1_default                 # optional field
param2::T2 = param2_default                 # optional field
param3::T3 = param3_default                 # optional field
⋮
paramN::TN = paramN_default                 # optional field
drift::DR = drift_function                  # mandatory field
diffusion::DF = diffusion_function          # mandatory field
state::ST = state_default                   # mandatory field
input::IP = input_default                   # mandatory field
output::OP = output_default                 # mandatory field
end

Here, MySDESystem has N parameters. MySDESystem is represented by the drift, diffusion and readout function. state, input and output is the initial state, input port and output port of MySDESystem.

Warning

drift must have the signature

function drift((dx, x, u, t, args...; kwargs...)
dx .= .... # update dx
end

and diffusion must have the signature

function diffusion((dx, x, u, t, args...; kwargs...)
dx .= .... # update dx
end

and readout must have the signature

y = ...
return y
end
Warning

New SDE system must be a subtype of AbstractSDESystem to function properly.

Example

julia> @def_sde_system mutable struct MySDESystem{DR, DF, RO, IP, OP} <: AbstractSDESystem
η::Float64 = 1.
drift::DR = (dx, x, u, t) -> (dx .= x)
diffusion::DF = (dx, x, u, t, η=η) -> (dx .= η)
readout::RO = (x, u, t) -> x
state::Vector{Float64} = rand(2)
input::IP = nothing
output::OP = Outport(2)
end

julia> ds = MySDESystem();
Causal.SDESystemType
mutable struct SDESystem{DR, DF, RO, ST<:(AbstractVector{var"#s16875"} where var"#s16875"<:Real), IP<:(Union{var"#s16874", var"#s16873"} where {var"#s16874"<:Inport, var"#s16873"<:Nothing}), OP<:(Union{var"#s16872", var"#s16632"} where {var"#s16872"<:Outport, var"#s16632"<:Nothing}), var"4004", var"4005", var"4006", Symbol, var"4007", Float64, var"4008", var"4009", var"4010", var"4011", var"4012", var"4013"} <: AbstractSDESystem

Constructs a SDE system.

Fields

• drift::Any

Drift function

• diffusion::Any

diffusion function

• state::AbstractVector{var"#s16875"} where var"#s16875"<:Real

State

• input::Union{var"#s16874", var"#s16873"} where {var"#s16874"<:Inport, var"#s16873"<:Nothing}

Input. Expected to be an Inport or Nothing

• output::Union{var"#s16872", var"#s16632"} where {var"#s16872"<:Outport, var"#s16632"<:Nothing}

Output port

• trigger::Any

• handshake::Any

• callbacks::Any

• name::Any

• id::Any

• t::Any

• modelargs::Any

• modelkwargs::Any

• solverargs::Any

• solverkwargs::Any

• alg::Any

• integrator::Any

Causal.NoisyLorenzSystemType
mutable struct NoisyLorenzSystem{T1<:Real, T2<:Real, T3<:Real, T4<:Real, T5<:Real, DR, DF, RO, ST<:(AbstractVector{var"#s16875"} where var"#s16875"<:Real), IP<:(Union{var"#s16874", var"#s16873"} where {var"#s16874"<:Inport, var"#s16873"<:Nothing}), OP<:(Union{var"#s16872", var"#s16632"} where {var"#s16872"<:Outport, var"#s16632"<:Nothing}), var"4004", var"4005", var"4006", Symbol, var"4007", Float64, var"4008", var"4009", var"4010", var"4011", var"4012", var"4013"} <: AbstractSDESystem

Constructs a noisy Lorenz system

Fields

• σ::Real

σ

• β::Real

β

• ρ::Real

ρ

• η::Real

η

• γ::Real

γ

• drift::Any

Drift function

• diffusion::Any

Diffusion function

• state::AbstractVector{var"#s16875"} where var"#s16875"<:Real

State

• input::Union{var"#s16874", var"#s16873"} where {var"#s16874"<:Inport, var"#s16873"<:Nothing}

Input. Expected to be an Inport or Nothing

• output::Union{var"#s16872", var"#s16632"} where {var"#s16872"<:Outport, var"#s16632"<:Nothing}

Output port

• trigger::Any

• handshake::Any

• callbacks::Any

• name::Any

• id::Any

• t::Any

• modelargs::Any

• modelkwargs::Any

• solverargs::Any

• solverkwargs::Any

• alg::Any

• integrator::Any

Causal.ForcedNoisyLorenzSystemType
mutable struct ForcedNoisyLorenzSystem{T1<:Real, T2<:Real, T3<:Real, T4<:Real, CM<:(AbstractMatrix{var"#s16875"} where var"#s16875"<:Real), T5<:Real, DR, DF, RO, ST<:(AbstractVector{var"#s16874"} where var"#s16874"<:Real), IP<:(Union{var"#s16873", var"#s16872"} where {var"#s16873"<:Inport, var"#s16872"<:Nothing}), OP<:(Union{var"#s16632", var"#s16631"} where {var"#s16632"<:Outport, var"#s16631"<:Nothing}), var"4004", var"4005", var"4006", Symbol, var"4007", Float64, var"4008", var"4009", var"4010", var"4011", var"4012", var"4013"} <: AbstractSDESystem

Constructs a noisy Lorenz system

Fields

• σ::Real

σ

• β::Real

β

• ρ::Real

ρ

• η::Real

η

• cplmat::AbstractMatrix{var"#s16875"} where var"#s16875"<:Real

Input coupling matrix. Expected to be a diagonal matrix.

• γ::Real

γ

• drift::Any

Drift function

• diffusion::Any

Diffusion function

• state::AbstractVector{var"#s16874"} where var"#s16874"<:Real

State

• input::Union{var"#s16873", var"#s16872"} where {var"#s16873"<:Inport, var"#s16872"<:Nothing}

Input. Expected to be an Inport or Nothing

• output::Union{var"#s16632", var"#s16631"} where {var"#s16632"<:Outport, var"#s16631"<:Nothing}

Output port

• trigger::Any

• handshake::Any

• callbacks::Any

• name::Any

• id::Any

• t::Any

• modelargs::Any

• modelkwargs::Any

• solverargs::Any

• solverkwargs::Any

• alg::Any

• integrator::Any