Engee documentation
Notebook

Subsystems

The purpose of this demonstration is to show how to apply all kinds of subsystems implemented in Engee. To do this, let's look at some simple models.

In [ ]:
using Plots # Библиотека для отрисовки результатов моделирования

# Подключение вспомогательной функции запуска модели.
function run_model( name_model, path_to_folder )
    
    Path = path_to_folder * "/" * name_model * ".engee"
    
    if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
        model = engee.open( name_model ) # Открыть модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
    else
        model = engee.load( Path, force=true ) # Загрузить модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
        engee.close( name_model, force=true ); # Закрыть модель
    end

    return model_output
end

# Путь до папки с моделями
path = "$(@__DIR__)/models"
Out[0]:
"/user/subsystems/models"

Enabled Subsystem

The first subsystem variant we will consider in this example is the Enabled Subsystem. This is a conditionally executed subsystem that is started once for each major time step as long as the control signal has a positive value. If the signal crosses zero during a smaller time step, the subsystem is not enabled or disabled until the next major time step.

The control signal can be a scalar or a vector.

  1. If the scalar value is greater than zero, the subsystem is executed.
  2. If any of the vector values is greater than zero, the subsystem is executed.

To use this functionality, add a block to the Subsystem block or to the root level of the model referenced by the Model block. If you use Enable at the root level of the model, the following steps are required.

  1. For multirate models, set the solver to single-task mode.
  2. For fixed step size models, at least one block in the model must run at the specified fixed step size speed.

The figure below is an illustration of this example.

image.png

In this example, the control signal is a sine wave and the input signal is a counter.

In [ ]:
run_model("enabled_subsystem",path) # Запуск модели.
Building...
Progress 0%
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 40%
Progress 45%
Progress 50%
Progress 55%
Progress 60%
Progress 65%
Progress 70%
Progress 75%
Progress 80%
Progress 85%
Progress 90%
Progress 95%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 2 entries:
  "SubSystem.Out1" => 1001×2 DataFrame…
  "Sine Wave.1"    => 1001×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
data1 = simout["enabled_subsystem/SubSystem.Out1"];
data1 = collect(data1);
en1 = simout["enabled_subsystem/Sine Wave.1"];
en1 = collect(en1);

Let's plot the dependence of the output data on the control signal.

In [ ]:
plot(data1.time,data1.value) 
plot!(en1.time,en1.value) 
Out[0]:

As we can see from the graph, at the moments when the control signal drops below 0, the output signal freezes.

Action Subsystem

Action Port controls the execution of the subsystem. The unit adds an external input port for action signals to the subsystem. The action signal controls the signal connected to the Action port of the Subsystem unit.

Used in models containing blocks:

  1. If.

image.png

  1. Switch Case

image_3.png

Let's run these two models and analyse the results. Let's start with the model with if.

In [ ]:
run_model("if_subsystem",path) # Запуск модели.
Building...
Progress 0%
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 40%
Progress 45%
Progress 50%
Progress 55%
Progress 60%
Progress 65%
Progress 70%
Progress 75%
Progress 80%
Progress 85%
Progress 90%
Progress 95%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 2 entries:
  "SysOutput_1" => 1001×2 DataFrame…
  "SysOutput_2" => 1001×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
SysOutput_1 = simout["if_subsystem/SysOutput_1"];
SysOutput_1 = collect(SysOutput_1);
SysOutput_2 = simout["if_subsystem/SysOutput_2"];
SysOutput_2 = collect(SysOutput_2);
In [ ]:
plot(SysOutput_1.time,SysOutput_1.value) 
plot!(SysOutput_2.time,SysOutput_2.value) 
Out[0]:

As we can see, in the case of if, the input sinusoid was split into two signals: one larger than 0 and one smaller than 0.

In [ ]:
run_model("switch_case_subsystem",path) # Запуск модели.
Building...
Progress 0%
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 40%
Progress 45%
Progress 50%
Progress 55%
Progress 60%
Progress 65%
Progress 70%
Progress 75%
Progress 80%
Progress 85%
Progress 90%
Progress 95%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 3 entries:
  "SysOutput_1"       => 1001×2 DataFrame…
  "Pulse Generator.1" => 1001×2 DataFrame…
  "SysOutput_2"       => 1001×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
SysOutput_1 = simout["switch_case_subsystem/SysOutput_1"];
SysOutput_1 = collect(SysOutput_1);
SysOutput_2 = simout["switch_case_subsystem/SysOutput_2"];
SysOutput_2 = collect(SysOutput_2);
En = simout["switch_case_subsystem/Pulse Generator.1"];
En = collect(En);

Let's plot the dependence of the output signals on the control signal.

In [ ]:
plot(SysOutput_1.time,SysOutput_1.value) 
plot!(SysOutput_2.time,SysOutput_2.value)
plot!(En.time,En.value)
Out[0]:

As we can see, when the control signal is equal to 1, the data is written from the upper subsystem. If the data is equal to 0, we get the graph of data from the second subsystem. Otherwise, the output signal on the subsystem freezes waiting for the control signal.

Triggered Subsystem

Unlike the Enabled Subsystem block, the Triggered Subsystem block always holds its output parameters at the last value between triggers. In addition, triggered subsystems cannot reset block states at runtime; the states of any discrete block are retained between triggers.

The Trigger block adds an external signal to control the execution of the subsystem. The subsystem will execute once per step when the value of the control signal is changed in the specified way. The block icon changes depending on the value selected for the Trigger type parameter. Alternatively, a Trigger block can be added to Enable. In this section we will look at both applications of the Triggered Subsystem.

First, let's look at an example of a simple Triggered Subsystem. The implementation of the model using this approach is shown in the figure below. image.png

In [ ]:
run_model("triggered_subsystem",path) # Запуск модели.
Building...
Progress 0%
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 40%
Progress 45%
Progress 50%
Progress 55%
Progress 60%
Progress 65%
Progress 70%
Progress 75%
Progress 80%
Progress 85%
Progress 90%
Progress 95%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 2 entries:
  "SubSystem.Out1" => 1001×2 DataFrame…
  "Sine Wave-1.1"  => 1001×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
SysOutput_1 = simout["triggered_subsystem/SubSystem.Out1"];
SysOutput_1 = collect(SysOutput_1);
En = simout["triggered_subsystem/Sine Wave-1.1"];
En = collect(En);
In [ ]:
plot(SysOutput_1.time,SysOutput_1.value) 
plot!(En.time,En.value)
Out[0]:

From the resulting data we see that such a subsystem is triggered only at the moment when the sinusoid crosses 0.

Now let's consider the variant of application of Trigger and Enable blocks in one subsystem. The figure below shows the model we have implemented.

image_2.png

In [ ]:
run_model("triggered_enabled_subsystem",path) # Запуск модели.
Building...
Progress 0%
Progress 0%
Progress 5%
Progress 10%
Progress 15%
Progress 20%
Progress 25%
Progress 30%
Progress 35%
Progress 40%
Progress 45%
Progress 50%
Progress 55%
Progress 60%
Progress 65%
Progress 70%
Progress 75%
Progress 80%
Progress 85%
Progress 90%
Progress 95%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 3 entries:
  "SubSystem.Out1" => 1001×2 DataFrame…
  "Enable.1"       => 1001×2 DataFrame…
  "Trigger.1"      => 1001×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
Data = simout["triggered_enabled_subsystem/SubSystem.Out1"];
Data = collect(Data);
Trigger = simout["triggered_enabled_subsystem/Trigger.1"];
Trigger = collect(Trigger);
Enable = simout["triggered_enabled_subsystem/Enable.1"];
Enable = collect(Enable);

Let's analyse the output in relation to the two control signals.

In [ ]:
plot(Data.time,Data.value) 
plot!(Trigger.time,Trigger.value)
plot!(Enable.time,Enable.value)
Out[0]:

As we can see, the frequency of Enable signal is three times higher than the frequency of Trigger, so these two signals crossed only once and thus started the subsystem.

For Each Subsystem

The For Each block is a control block for the For Each Subsystem. This subsystem allows you to process parts of input signals.

Each block within this subsystem maintains a separate set of states for each element or subarray it processes. As a set of subsystem blocks process elements or subarrays, the subsystem combines the results to form output signals.

The figures below show a model in which the For Each block is applied.

image.png

image_3.png

In [ ]:
run_model("for_each_model1",path) # Запуск модели.
Building...
Progress 10%
Progress 20%
Progress 30%
Progress 40%
Progress 50%
Progress 60%
Progress 70%
Progress 80%
Progress 90%
Progress 100%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 2 entries:
  "SysOutput_1" => 11×2 DataFrame…
  "Constant.1"  => 11×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
Data_in = simout["for_each_model1/Constant.1"];
Data_in = collect(Data_in);
Data_out = simout["for_each_model1/SysOutput_1"];
Data_out = collect(Data_out);
print(string(Data_in.value[1]) *" --> " * string(Data_out.value[1]))
[1.0, 2.0, 3.0] --> [2.0, 4.0, 6.0]

As we can see, the result was a piecewise multiplication of the input data by 2.

This block also allows to form indices for input data and output them as a port. The figures below show such an application of For Each. image.png

image_2.png

The counter settings also specify that the number of iterations is 3.

image_4.png

In [ ]:
run_model("for_each_model2",path) # Запуск модели
Building...
Progress 10%
Progress 20%
Progress 30%
Progress 40%
Progress 50%
Progress 60%
Progress 70%
Progress 80%
Progress 90%
Progress 100%
Progress 100%
Out[0]:
Dict{String, DataFrame} with 2 entries:
  "SysOutput_1"   => 11×2 DataFrame…
  "SubSystem.idx" => 11×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
Data_out = simout["for_each_model2/SysOutput_1"];
Data_out = collect(Data_out);
idx = simout["for_each_model2/SubSystem.idx"];
idx = collect(idx);
In [ ]:
Data_out.value[1]
Out[0]:
2×6 Matrix{Float64}:
 0.0  0.0  1.0  1.0  2.0  2.0
 0.0  0.0  1.0  1.0  2.0  2.0
In [ ]:
print(string(idx.value[1]))
[0.0, 1.0, 2.0]

As we can see, in this case, the input unit matrix has been multiplied by the counter port given that there are only three iterations of processing the input data.

Function-Call Subsystems

A Function-Call Subsystem block is a conditionally executable subsystem that is started whenever the control port receives a function call event. Chart, Function-Call Generator block or Engee Function block can provide function call events.

The function call subsystem is similar to a function in a procedural programming language. A call to the function call subsystem fires the output methods of blocks within the subsystem in the order of execution. The figures below show an example of using Function-Call Generator and Function-Call Subsystems.

image.png

image_2.png

In [ ]:
run_model("function_call",path) # Запуск модели.
Building...
Progress 100%
Out[0]:
Dict{String, DataFrames.DataFrame} with 2 entries:
  "SysOutput_1" => 1001×2 DataFrame…
  "Sine Wave.1" => 1001×2 DataFrame
In [ ]:
# Считывание из simout залогированных сигналов
out = simout["function_call/SysOutput_1"];
out = collect(out);
inp = simout["function_call/Sine Wave.1"];
inp = collect(inp);

Input sinusoid.

In [ ]:
plot(inp.time,inp.value) 
Out[0]:

Output sum.

In [ ]:
plot(out.time,out.value) 
Out[0]:

As we can see in this example, by the condition of the control function the increment of the counter inside the subsystem is performed.

Conclusion

In this demonstration we have broken down all the variants of Subsystems that you may encounter in Engee and use in your projects.

Blocks used in example