Документация Engee
Notebook

Subsystems

Открыть пример в Engee

Цель данной демонстрации – показать, как применять все виды подсистем, реализованных в Engee. Для этого рассмотрим несколько простых моделей.

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

Первый вариант подсистемы, который мы рассмотрим в этом примере, – это Enabled Subsystem. Речь идет об условно выполняемой подсистеме, которая запускается один раз на каждом основном временном шаге, пока управляющий сигнал имеет положительное значение. Если сигнал пересекает ноль во время меньшего временного шага, подсистема не включается и не отключается до следующего основного временного шага.

Сигнал управления может быть скаляром или вектором.

  1. Если скалярное значение больше нуля, подсистема выполняется.
  2. Если какое-либо из значений векторного элемента больше нуля, подсистема выполняется.

Чтобы использовать эту функциональность, добавьте блок в блок Subsystem или на корневой уровень модели, на которую ссылается блок Model. Если вы используете Enable на корневом уровне модели, требуется выполнить следующие шаги.

  1. Для многоскоростных моделей установите для решателя однозадачный режим.
  2. Для моделей с фиксированным размером шага хотя бы один блок в модели должен работать с заданной скоростью фиксированного размера шага.

На риснке ниже – иллюстрация этого примера.

image.png

В данном примере сигнал управления – это синусоида, а входной сигнал – это счётчик.

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);

Построим график зависимости выходных данных от сигнала управления.

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

Как мы видим из графика, в те моменты, когда управляющий сигнал опускается ниже 0, происходит замирание выходного сигнала.

Action Subsystem

Action Port контролирует выполнение подсистемы. Блок добавляет к подсистеме внешний входной порт для сигналов действий. Сигнал действия управляет сигналом, подключенным к порту Action блока Subsystem.

Используется в моделях, содержащих блоки:

  1. If

image.png

  1. Switch Case

image_3.png

Запустим эти две модели и проанализируем полученные результаты. Начнём с модели с 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]:

Как мы видим, в случае с if входная синусоида была разбита на два сигнала: по условию больше 0 и меньше 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);

Построим график зависимости выходных сигналов от сигнала управления.

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

Как мы видим, когда сигнал управления равен 1, данные пишутся с верхней подсистемы. Если данные равны 0, то мы получаем график данных со второй подсистемы. В противном случае выходной сигнал на подсистеме замирает в ожидании сигнала управления.

Triggered Subsystem

В отличие от блока Enabled Subsystem, блок Triggered Subsystem всегда удерживает свои выходные параметры на последнем значении между триггерами. Кроме того, запущенные подсистемы не могут сбрасывать состояния блоков при выполнении; состояния любого дискретного блока сохраняются между триггерами.

Блок Trigger добавляет внешний сигнал для управления выполнением подсистемы. Подсистема будет выполняться один раз на каждом шаге, когда значение управляющего сигнала изменяется указанным способом. Значок блока меняется в зависимости от значения выбранного для параметра Trigger type. Кроме того, блок Trigger может быть добавлен к Enable. В данном разделе мы разберем оба варианта применения Triggered Subsystem.

Для начала рассмотрим пример простой Triggered Subsystem. Реализация модели с применением такого подхода показана на рисунке ниже. 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]:

По результирующим данным мы видим, что такая подсистема срабатывает только в момент пересечения синусоидой 0.

Теперь рассмотрим вариант применения блоков Trigger и Enable в одной подсистеме. На рисунке ниже показана реализованная нами модель.

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);

Проанализируем выход относительно двух управляющих сигналов.

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

Как мы видим, частота Enable сигнала в три раза выше, чем частота Trigger, поэтому эти два сигнала пересеклись всего один раз и тем самым запустили подсистему.

For Each Subsystem

Блок For Each – управляющий блок для For Each Subsystem. Данная подсистема позволяет обрабатывать части входных сигналов.

Каждый блок внутри этой подсистемы поддерживает отдельный набор состояний для каждого элемента или подмассива, который он обрабатывает. По мере того, как набор блоков подсистемы обрабатывает элементы или подмассивы, подсистема объединяет результаты для формирования выходных сигналов.

На рисунках ниже показана модель, в которой применяется блок For Each.

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]

Как мы видим, в результате было выполнено поэлементное умножение входных данных на 2.

Также этот блок позволяет формировать индексы для входных данных и выдавать их как порт. На рисунках ниже показано такое применение For Each. image.png

image_2.png

В настройках счётчика также указано, что количество итераций равно 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]

Как мы видим, в данном случае входная единичная матрица была умножена на порт счётчика при учёте того, что итераций обработки входных данных всего три.

Function-Call Subsystems

Блок Function-Call Subsystem – это условно выполняемая подсистема, которая запускается каждый раз, когда порт управления получает событие вызова функции. Chart, блок Function-Call Generator или блок Engee Function могут предоставлять события вызова функции.

Подсистема вызова функций аналогична функции в процедурном языке программирования. Вызов подсистемы вызова функций запускает методы вывода блоков внутри подсистемы в порядке выполнения. На рисунках ниже показан пример использования Function-Call Generator и 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);

Входная синусоида.

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

Выходная сумма.

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

Как мы видим на данном примере, по условию функции управления выполняется приращение счётчика внутри подсистемы.

Вывод

В данной демонстрации мы разобрали все варианты Subsystems, которые вы можете встретить в Engee и использовать в своих проектах.

Блоки, использованные в примере