Model
Signal-Flow Approach in Modelling
Causal adopts signal-flow approach in systems modelling. In signal-flow approach, a Model
consists of connected components. The components are data processing units and the behavior, i.e, the mathematical model, of the component determines how the data is processed. Connections connects the components each other and the data is transferred between components by means of connections. The data flow through the connections is unidirectional, i.e., a component is driven by other components that write data to its input bus.
Construction of Models
A Model
consists of connected components. The components of are defined first and the Model
consisting of these components can be constructed. Or, an empty model can be constructed.
Let us continue with some examples. We will construct very simple Model
consisting of a SinewaveGenerator
and a Writer
. We construct an empty Model
first, then we add nodes and branches as desired.
julia> using Causal # hide
julia> model = Model()
Model(numnodes:0, numedges:0)
julia> addnode!(model, SinewaveGenerator(), label=:gen)
Node(component:SinewaveGenerator(amp:1.0, freq:1.0, phase:0.0, offset:0.0, delay:0.0), idx:1, label:gen)
julia> addnode!(model, Writer(Inport()), label=:writer)
ERROR: MethodError: no method matching Writer(::Inport{Inpin{Float64}})
Closest candidates are:
Writer(::A, !Matched::String, !Matched::FL, !Matched::var"##253", !Matched::var"##254", !Matched::var"##255", !Matched::Symbol, !Matched::var"##256", !Matched::var"##263", !Matched::Int, !Matched::var"##264", !Matched::var"##265", !Matched::var"##266", !Matched::var"##267") where {A, FL, var"##253", var"##254", var"##255", Symbol, var"##256", var"##263", Int, var"##264", var"##265", var"##266", var"##267"} at /home/runner/work/Causal.jl/Causal.jl/src/components/sinks/sinks.jl:122
Writer(; action, path, f, file, trigger, handshake, callbacks, name, id, input, buflen, plugin, timebuf, databuf, sinkcallback) at util.jl:448
julia> addbranch!(model, :gen => :writer, 1 => 1)
ERROR: BoundsError: attempt to access 0-element Array{Any,1} at index [1]
Simulation of Models
A Model
to to be simulated consists of components connected to each other an a time reference.
julia> model.nodes # Model components
1-element Array{Any,1}:
Node(component:SinewaveGenerator(amp:1.0, freq:1.0, phase:0.0, offset:0.0, delay:0.0), idx:1, label:gen)
julia> model.branches # Model components
Any[]
julia> model.clock # Model time reference
ERROR: type Model has no field clock
The time reference is used to sample the continuous time signals flowing through the busses of the model and to rigger the components. The simulation is performed by triggering the components with the pulses generated by the time reference at simulation sampling time intervals. Having been triggered, the components evolve themselves, compute their outputs and writes them to their outputs.
Simulation Stages
Inspection
The inspection stage is the first stage of the simulation process. In this stag,e the model is first inspected in terms of whether it is ready for simulation. This inspection is carried out to see whether the model has some inconsistencies such as unterminated busses or presence of algebraic loops. If the model has unterminated busses, the data that is supposed to flow those unterminated busses cannot flow through those busses and the simulation gets stuck. An algebraic is the subset of model components whose output depends directly on their inputs. In such a case, none of the components can produce outputs to break the loop which leads again the obstruction of simulation. Thus, to continue the simulation, the model must not contain any of those inconsistencies. The model inspection is done with inspect!
function.
Initialization
If the inspection stage results positive, the initialization stage comes next. In this stage, the tasks required for the busses of the model to be both readable and writable are activated and bound the busses. To this end, a reader and writer task are activated and bound to both sides of each bus. To initialize the model, initialize!
function is used.
When the model is initialized, the pairs of components and component tasks are recorded into the task manager of the model. During the rest of the simulation, task manager keeps track of the tasks. Any exception or error that is thrown during the run stage of the simulation can be observed by means of the task manager of the model.
Run
The run stage follows the initialization stage. The tasks activated in the initialization stage wait for the components to be triggered by the model time reference. During the run stage, time reference, that is the model clock, triggers the components by writing pulses that are generated in the intervals of the sampling period of the simulation to their trigger links. The job defined in a task is to read input dat a from the its input bus, to calculate its next state, if any, and output, and write its calculated output to its output bus. The run stage, starts at the initial time of the time reference and continues until the end time of the time reference. run!
function is used to run the models,
Termination
After the run stage, the tasks opened in the initialization stage are closed and the simulation is terminated. terminate!
function is used to terminate the model
Model
s are constructed to simulate!
them. During the simulation, components of the Model
process data and the data is transferred between the components via connection. Thus, to simulate the Model
s, the components must be connected. In our model, the writer
is used to record the output of gen
. Thus, the flows from gen
to writer
. Thus, we connect gen
output to writer
input.
During the Model
construction, the order of addition of nodes to the model is not important. The nodes can be given in any order.
Full API
Causal.Branch
— Typestruct Branch{NP, IP, LN<:(AbstractArray{var"#s301",1} where var"#s301"<:Link)}
Constructs a Branch
connecting the first and second element of nodepair
with links
. indexpair
determines the subindices by which the elements of nodepair
are connected.
Fields
nodepair::Any
Node pair connected by the branch
indexpair::Any
Indices of output and input ports
links::AbstractArray{var"#s301",1} where var"#s301"<:Link
Links of the branch
Causal.Model
— Typestruct Model{GR, ND, BR, TM, CB}
A Model
consists of nodes and branches. Nodes are connected to each other through the branches. A Model
instance is ready for simulation.
Fields
!!! warning Model
s are units that can be simulated. As the data flows through the branches i.e. input output busses of the components, its is important that the components must be connected to each other. See also: simulate!
Causal.Node
— Typestruct Node{CP, L}
Constructs a model Node
with component
. idx
is the index and label
is label of Node
.
Fields
component::Any
Node component
idx::Int64
Node index
label::Any
Node label
Causal.addbranch!
— Functionaddbranch!(model, nodepair)
addbranch!(model, nodepair, indexpair)
Adds branch
to branched of model
.
Causal.addnode!
— Methodaddnode!(model, component; label)
Adds a node to model
. Component is component
and label
is label
the label of node. Returns added node.
Example
julia> model = Model()
Model(numnodes:0, numedges:0, timesettings=(0.0, 0.01, 1.0))
julia> addnode!(model, SinewaveGenerator(), label=:gen)
Node(component:SinewaveGenerator(amp:1.0, freq:1.0, phase:0.0, offset:0.0, delay:0.0), idx:1, label:gen)
Causal.breakloop!
— Functionbreakloop!(model, loop)
breakloop!(model, loop, breakpoint)
Breaks the algebraic loop
of model
. The loop
of the model
is broken by inserting a Memory
at the breakpoint
of loop.
Causal.clean!
— Methodclean!(model)
Cleans the buffers of the links of the connections, internal buffers of components, current time of dynamical systems.
Causal.deletebranch!
— Methoddeletebranch!(model::Model, branch::Branch)
Deletes branch
from branched of model
.
deletebranch!(model::Model, srcnode::Node, dstnode::Node)
Deletes branch between srcnode
and dstnode
of the model
.
Causal.getcomponent
— Methodgetcomponent(model, specifier)
Returns the component of model
corresponding to specifier
that can be either index or label of the component.
Causal.getlinks
— Methodgetlinks(model, pair)
Returns the links of model
corresponding to the pair
which can be a pair of integers or symbols to specify the source and destination nodes of the branch.
Causal.getloops
— Methodgetloops(model)
Returns idx of nodes that constructs algrebraic loops.
Causal.getnode
— Methodgetnode(model, idx::Int)
Returns node of model
whose index is idx
.
getnode(model, label)
Returns node of model
whose label is label
.
Example
julia> model = Model()
Model(numnodes:0, numedges:0, timesettings=(0.0, 0.01, 1.0))
julia> addnode!(model, SinewaveGenerator(), label=:gen)
Node(component:SinewaveGenerator(amp:1.0, freq:1.0, phase:0.0, offset:0.0, delay:0.0), idx:1, label:gen)
julia> addnode!(model, Gain(), label=:gain)
Node(component:Gain(gain:1.0, input:Inport(numpins:1, eltype:Inpin{Float64}), output:Outport(numpins:1, eltype:Outpin{Float64})), idx:2, label:gain)
julia> getnode(model, :gen)
Node(component:SinewaveGenerator(amp:1.0, freq:1.0, phase:0.0, offset:0.0, delay:0.0), idx:1, label:gen)
julia> getnode(model, 2)
Node(component:Gain(gain:1.0, input:Inport(numpins:1, eltype:Inpin{Float64}), output:Outport(numpins:1, eltype:Outpin{Float64})), idx:2, label:gain)
Causal.initialize!
— Methodinitialize!(model, clock)
Initializes model
by launching component task for each of the component of model
. The pairs component and component tasks are recordedin the task manager of the model
. The model
clock is set!
and the files of Writer
are openned.
Causal.inspect!
— Functioninspect!(model)
inspect!(model, breakpoints)
Inspects the model
. If model
has some inconsistencies such as including algebraic loops or unterminated busses and error is thrown.
Causal.resize_sink_buffers!
— Methodresize_sink_buffers!(model, ln)
Resize buffers of the model components that are of type AbstractSink.
Causal.run!
— Functionrun!(model, clock)
run!(model, clock, withbar)
Runs the model
by triggering the components of the model
. This triggering is done by generating clock tick using the model clock model.clock
. Triggering starts with initial time of model clock, goes on with a step size of the sampling period of the model clock, and finishes at the finishing time of the model clock. If withbar
is true
, a progress bar indicating the simulation status is displayed on the console.
The model
must first be initialized to be run. See also: initialize!
.
Causal.signalflow
— Methodsignalflow(model, args; kwargs...)
Plots the signal flow of model
. args
and kwargs
are passed into gplot
function.
Causal.simulate!
— Methodsimulate!(model, clock; simdir, simprefix, simname, logtofile, loglevel, reportsim, withbar, breakpoints)
Simulates model
. simdir
is the path of the directory into which simulation files are saved. simprefix
is the prefix of the simulation name simname
. If logtofile
is true
, a log file for the simulation is constructed. loglevel
determines the logging level. If reportsim
is true
, model components are saved into files. If withbar
is true
, a progress bar indicating the simualation status is displayed on the console.
Causal.simulate!
— Methodsimulate!(model, ti, dt, tf; kwargs...)
Simulates the model
starting from the initial time t0
until the final time tf
with the sampling interval of tf
. For kwargs
are
logtofile::Bool
: Iftrue
, a log file is contructed logging each step of the simulation.reportsim::Bool
: Iftrue
,model
components are written files after the simulation. When this file is read back, the model components can be consructed back with their status at the end of the simulation.simdir::String
: The path of the directory in which simulation file are recorded.
Causal.terminate!
— Methodterminate!(model)
Terminates model
by terminating all the components of the model
, i.e., the components tasks in the task manager of the model
is terminated.
Causal.troubleshoot
— Methodtroubleshoot(model)
Prints the exceptions of the tasks that are failed during the simulation of model
.
Causal.@defmodel
— Macro@defmodel name ex
Construts a model. The expected syntax is.
@defmodel mymodel begin
@nodes begin
label1 = Component1()
label2 = Component1()
⋮
end
@branches begin
src1 => dst1
src2 => dst2
⋮
end
end
Here @nodes
and @branches
blocks adefine the nodes and branches of the model, respectively.