DDESystem

Construction of DDESystem

A DDESystem is represented by the following state equation

\[ \dot{x} = f(x, h, u, t) \quad t \geq t_0\]

where $t$ is the time, $x$ is the value of the state, $u$ is the value of the input. $h$ is the history function for which

\[ x(t) = h(t) \quad t \leq t_0\]

and by the output equation

\[ y = g(x, u, t) \]

where $y$ is the value of the output.

As an example, consider a system with the state equation

\[ \begin{array}{l} \dot{x} = -x(t - \tau) \quad t \geq 0 \\ x(t) = 1. -\tau \leq t \leq 0 \\ \end{array}\]

First, we define the history function histfunc,


julia> const out = zeros(1)
1-element Vector{Float64}:
 0.0

julia> histfunc(out, u, t) = (out .= 1.);

Note that histfunc mutates a vector out. This mutation is for performance reasons. Next the state function can be defined

julia> function statefunc(dx, x, h, u, t)
           h(out, u, t - tau) # Update out vector
           dx[1] = out[1] + x[1]
       end
statefunc (generic function with 1 method)

and let us take all the state variables as outputs. Thus, the output function is

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

Next, we need to define the history for the system. History is defined by specifying a history function, and the type of the lags. There may be two different lag: constant lags which are independent of the state variable $x$ and the dependent lags which are mainly the functions of the state variable $x$. Note that for this example, the have constant lags. Thus,

julia> tau = 1
1

julia> conslags = [tau]
1-element Vector{Int64}:
 1

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

julia> ds = DDESystem(righthandside=statefunc, history=histfunc, readout=outputfunc, state=[1.],  input=nothing, output=Outport(), constlags=conslags, depslags=nothing)
ERROR: MethodError: no method matching FastBroadcast.BroadcastCharacteristics()
The applicable method may be too new: running in world age 47354, while current world is 95794.
Closest candidates are:
  FastBroadcast.BroadcastCharacteristics() at /juliateam/.julia/packages/FastBroadcast/2JngL/src/FastBroadcast.jl:111 (method too new to be called from this world context.)
  FastBroadcast.BroadcastCharacteristics(!Matched::Expr, !Matched::Vector{Symbol}, !Matched::Bool, !Matched::Bool) at /juliateam/.julia/packages/FastBroadcast/2JngL/src/FastBroadcast.jl:106 (method too new to be called from this world context.)
  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.)

Basic Operation of DDESystem

The basis operaiton of DDESystem is the same as those of other dynamical systems. When triggered from its trigger link, the DDESystem reads its time from its trigger link, reads input, solves its differential equation, computes its output and writes the computed output to its output bus. To drive DDESystem, we must first launch it,

julia> iport, trg, hnd = Inport(), 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

julia> task = launch(ds)
ERROR: UndefVarError: ds not defined

julia> task2 = @async while true
           all(take!(iport) .=== NaN) && break
           end
Task (failed) @0x00007fee0edb9270
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
  [2] _broadcast_getindex_evalf
    @ ./broadcast.jl:648 [inlined]
  [3] _broadcast_getindex
    @ ./broadcast.jl:621 [inlined]
  [4] getindex
    @ ./broadcast.jl:575 [inlined]
  [5] macro expansion
    @ ./broadcast.jl:984 [inlined]
  [6] macro expansion
    @ ./simdloop.jl:77 [inlined]
  [7] copyto!
    @ ./broadcast.jl:983 [inlined]
  [8] copyto!
    @ ./broadcast.jl:936 [inlined]
  [9] copy
    @ ./broadcast.jl:908 [inlined]
 [10] materialize
    @ ./broadcast.jl:883 [inlined]
 [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-dde_system_ex.var"#1#2")()
    @ Main.ex-dde_system_ex ./task.jl:406

When launched, ds is drivable. To drive ds, we can use the syntax drive(ds, t) or put!(ds.trigger, t) where t is the time until which ds is to be driven.

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 driven, ds reads the time t from its trigger link, (since its input is nothing, ds does nothing during its input reading stage), solves its differential equation, computes output and writes the value of its output to its output bus. To signify, the step was taken with success, ds writes true to its handshake which must be read to further drive ds. For this, we can use the syntax approve!(ds) or take!(ds.handshake).

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

When launched, we constructed a task whose state is running which implies that ds can be driven.

julia> task
ERROR: UndefVarError: task not defined

julia> task2
Task (failed) @0x00007fee0edb9270
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
  [2] _broadcast_getindex_evalf
    @ ./broadcast.jl:648 [inlined]
  [3] _broadcast_getindex
    @ ./broadcast.jl:621 [inlined]
  [4] getindex
    @ ./broadcast.jl:575 [inlined]
  [5] macro expansion
    @ ./broadcast.jl:984 [inlined]
  [6] macro expansion
    @ ./simdloop.jl:77 [inlined]
  [7] copyto!
    @ ./broadcast.jl:983 [inlined]
  [8] copyto!
    @ ./broadcast.jl:936 [inlined]
  [9] copy
    @ ./broadcast.jl:908 [inlined]
 [10] materialize
    @ ./broadcast.jl:883 [inlined]
 [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-dde_system_ex.var"#1#2")()
    @ Main.ex-dde_system_ex ./task.jl:406

As long as the state of the task is running, ds can be driven. To terminate task safely, we need to terminate the 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
  ...

Note that the state of task is done which implies that ds is not drivable any more.

Note that the output values of ds is written to its output bus.

julia> iport[1].link.buffer
ERROR: type Missing has no field buffer

Full API

Causal.@def_dde_systemMacro
@def_dde_system ex

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

@def_dde_system mutable struct MyDDESystem{T1,T2,T3,...,TN,OP,RH,RO,ST,IP,OP} <: AbstractDDESystem
    param1::T1 = param1_default                 # optional field 
    param2::T2 = param2_default                 # optional field 
    param3::T3 = param3_default                 # optional field
        ⋮
    paramN::TN = paramN_default                 # optional field 
    constlags::CL = constlags_default           # mandatory field
    depslags::DL = depslags_default             # mandatory field
    righthandside::RH = righthandside_function  # mandatory field
    history::HST = history_function             # mandatory field
    readout::RO = readout_function              # mandatory field
    state::ST = state_default                   # mandatory field
    input::IP = input_defauult                  # mandatory field
    output::OP = output_default                 # mandatory field
end

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

Warning

righthandside must have the signature

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

and readout must have the signature

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

New DDE system must be a subtype of AbstractDDESystem to function properly.

Example

julia> _delay_feedback_system_cache = zeros(1)
1-element Array{Float64,1}:
 0.0

julia> _delay_feedback_system_tau = 1.
1.0

julia> _delay_feedback_system_constlags = [1.]
1-element Array{Float64,1}:
 1.0

julia> _delay_feedback_system_history(cache, u, t) = (cache .= 1.)
_delay_feedback_system_history (generic function with 1 method)

julia> function _delay_feedback_system_rhs(dx, x, h, u, t, 
           cache=_delay_feedback_system_cache, τ=_delay_feedback_system_tau)
           h(cache, u, t - τ)  # Update cache 
           dx[1] = cache[1] + x[1]
       end
_delay_feedback_system_rhs (generic function with 3 methods)

julia> @def_dde_system mutable struct MyDDESystem{RH, HST, RO, IP, OP} <: AbstractDDESystem
           constlags::Vector{Float64} = _delay_feedback_system_constlags
           depslags::Nothing = nothing
           righthandside::RH = _delay_feedback_system_rhs
           history::HST = _delay_feedback_system_history
           readout::RO = (x, u, t) -> x 
           state::Vector{Float64} = rand(1)
           input::IP = nothing 
           output::OP = Outport(1)
       end

julia> ds = MyDDESystem();
Causal.DDESystemType
mutable struct DDESystem{CL, DL, RH, HST, 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"} <: AbstractDDESystem

Construct a generic DDE system

Fields

  • constlags::Any

    Constant lags

  • depslags::Any

    Dependent lags

  • righthandside::Any

    Right-hand-side function

  • history::Any

    History function

  • readout::Any

    Readout 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.DelayFeedbackSystemType
mutable struct DelayFeedbackSystem{CL<:(AbstractVector{var"#s16875"} where var"#s16875"<:Real), RH, HST, 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"} <: AbstractDDESystem

Constructs DelayFeedbackSystem

Fields

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

    Constant lags

  • depslags::Nothing

    Dependent lags

  • righthandside::Any

    Right-hand-side function

  • history::Any

    History function

  • readout::Any

    Readout 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 of 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