# RODESystem

## Construction of RODESystem

A RODESystem is represented by the state function

$$$\begin{array}{l} dx = f(x, u, t, W) \end{array}$$$

and the output function

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

where $t$ is the time, $x \in R^n$ is the state, $u \in R^p$ and $y \in R^m$ is output of the system. Therefore to construct a RODESystem, we need to define statefunc and outputfunc with the corresponding syntax,

function statefunc(dx, x, u, t)
dx .= ... # Update dx
end

and

function outputfunc(x, u, t)
y = ... # Compute y
return y
end

As an example, consider the system with the state function

$$$\begin{array}{l} dx_1 = 2 x_1 sin(W_1 - W_2) \\ dx_2 = -2 x_2 cos(W_1 + W_2) \end{array}$$$

and with the output function

$$$y = x$$$

That is, all the state variable are taken as output. The statefunc and the outputfunc is defined as,


julia> function statefunc(dx, x, u, t, W)
dx[1] = 2x[1]*sin(W[1] - W[2])
dx[2] = -2x[2]*cos(W[1] + W[2])
end
statefunc (generic function with 1 method)

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

To construct the RODESystem, we need to specify the initial condition and time.

julia> x0 = [1., 1.]
2-element Vector{Float64}:
1.0
1.0

julia> t = 0.
0.0

Note from statefunc, the system has not any input, i.e. input is nothing, and has an output with a dimension of 1.

julia> input = nothing

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

We are ready to construct the system

julia> ds = RODESystem(righthandside=statefunc, readout=outputfunc, state=x0, input=input, output=output, solverkwargs=(dt=0.01,))
The applicable method may be too new: running in world age 47354, while current world is 95809.
Closest candidates are:
FastBroadcast.BroadcastCharacteristics(!Matched::Any, !Matched::Any, !Matched::Any, !Matched::Any) at /juliateam/.julia/packages/FastBroadcast/2JngL/src/FastBroadcast.jl:106 (method too new to be called from this world context.)

Note that ds has a solver to solve its state function statefunc which is random differential equation. To solve its statefunc, the step size of the solver must be specified. See Random Differential Equtions of DifferentialEquations package.

## Basic Operation of RODESystem

When a RODESystem is triggered from its trigger link, it read the current time from its trigger link, reads its input (if available, i.e. its input is not nothing), solves its state function, computes its output value and writes its output value its output bus (again, if available, i.e., its output bus is not nothing). To drive a RODESystem, it must be launched. Let us continue with ds constructed in the previous section.

julia> iport, trg, hnd = Inport(2), Outpin(), Inpin{Bool}()
(Inport(numpins:2, 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-rode_system_ex.var"#1#2")()
@ Main.ex-rode_system_ex ./task.jl:406

When launched, ds is ready to be driven. We can drive ds by drive(ds, t) or put!(ds.trigger, t) where t is the time until which we will drive ds.

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
...

When triggered, ds read the time t from its trigger link, solved its differential equation, computed its value and writes its output value to its output bus. To signal that, the evolution is succeeded, ds writes true to its handshake link which must be taken to further drive ds. (approve!(ds)) can also be used.

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
...

We can continue to 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
...

After each evolution, ds writes its current output value to its output bus.

julia> [outbuf(pin.link.buffer) for pin in iport]
ERROR: type Missing has no field buffer

When launched, a task was constructed which still running. As long as no exception is thrown during the evolution of ds, the state of task is running which implies ds can be driven.

julia> task

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-rode_system_ex.var"#1#2")()
@ Main.ex-rode_system_ex ./task.jl:406

To terminate the task safely, ds should be terminated safely.

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, NaN])
ERROR: UndefVarError: ds not defined

Note that the state of task is done which implies the task has been terminated safely.

julia> task

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-rode_system_ex.var"#1#2")()
@ Main.ex-rode_system_ex ./task.jl:406

## Full API

Causal.@def_rode_systemMacro
@def_rode_system ex

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

@def_rode_system mutable struct MyRODESystem{T1,T2,T3,...,TN,OP,RH,RO,ST,IP,OP} <: AbstractRODESystem
param1::T1 = param1_default                 # optional field
param2::T2 = param2_default                 # optional field
param3::T3 = param3_default                 # optional field
⋮
paramN::TN = paramN_default                 # optional field
righthandside::RH = righthandside_function  # mandatory field
state::ST = state_default                   # mandatory field
input::IP = input_default                   # mandatory field
output::OP = output_default                 # mandatory field
end

Here, MyRODESystem has N parameters. MyRODESystem is represented by the righthandside and readout function. state, input and output is the initial state, input port and output port of MyRODESystem.

Warning

righthandside must have the signature

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

and readout must have the signature

function readout(x, u, t)
y = ...
return y
end
Warning

New RODE system must be a subtype of AbstractRODESystem to function properly.

Example

julia> @def_rode_system mutable struct MySystem{RH, RO, IP, OP} <: AbstractRODESystem
A::Matrix{Float64} = [2. 0.; 0 -2]
righthandside::RH = (dx, x, u, t, W) -> (dx .= A * x * W)
readout::RO = (x, u, t) -> x
state::Vector{Float64} = rand(2)
input::IP = nothing
output::OP = Outport(2)
end

julia> ds = MySystem();
Causal.RODESystemType
mutable struct RODESystem{RH, 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"} <: AbstractRODESystem

Constructs a generic RODE system

Fields

• righthandside::Any

Right-hand-side function

• readout::Any

• 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.MultiplicativeNoiseLinearSystemType
mutable struct MultiplicativeNoiseLinearSystem{T1<:(AbstractMatrix{var"#s16875"} where var"#s16875"<:Real), RH, 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"} <: AbstractRODESystem

Constructs a MultiplicativeNoiseLinearSystem with the dynamics

$$$\begin{array}{l} \dot{x} = A x W \end{array} where W is the noise process.$$$

Fields

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

A

• righthandside::Any

Right-hand-side function

• readout::Any

• 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