Subsystems

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

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"
"/user/subsystems/models"

Enabled Subsystem

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

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

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

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

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

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

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

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

image.png

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

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%
Dict{String, DataFrame} with 2 entries:
  "SubSystem.Out1" => 1001×2 DataFrame…
  "Sine Wave.1"    => 1001×2 DataFrame…
# Считывание из simout залогированных сигналов
data1 = simout["enabled_subsystem/SubSystem.Out1"];
data1 = collect(data1);
en1 = simout["enabled_subsystem/Sine Wave.1"];
en1 = collect(en1);

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

plot(data1.time,data1.value)
plot!(en1.time,en1.value)

interactive-scripts/images/base_simulation_demo_Subsystems/e3e2cf96f09b26a5716e0becb3b6add44cb240b1

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

Action Subsystem

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

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

  1. If

image.png

  1. Switch Case

image_3.png

Запустим эти две модели и проанализируем полученные результаты. Начнём с модели с if.

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%
Dict{String, DataFrame} with 2 entries:
  "SysOutput_1" => 1001×2 DataFrame…
  "SysOutput_2" => 1001×2 DataFrame…
# Считывание из 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);
plot(SysOutput_1.time,SysOutput_1.value)
plot!(SysOutput_2.time,SysOutput_2.value)

interactive-scripts/images/base_simulation_demo_Subsystems/f81e98b73360cd6a6305cffa4387cf1947170e10

Как мы видим, в случае с if входная синусоида была разбита на два сигнала: по условию больше 0 и меньше 0.

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%
Dict{String, DataFrame} with 3 entries:
  "SysOutput_1"       => 1001×2 DataFrame…
  "Pulse Generator.1" => 1001×2 DataFrame…
  "SysOutput_2"       => 1001×2 DataFrame…
# Считывание из 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);

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

plot(SysOutput_1.time,SysOutput_1.value)
plot!(SysOutput_2.time,SysOutput_2.value)
plot!(En.time,En.value)

interactive-scripts/images/base_simulation_demo_Subsystems/6dd853f79e88978e6bbd1aa010f83764fbadd885

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

Triggered Subsystem

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

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

Для начала рассмотрим пример простой Triggered Subsystem. Реализация модели с применением такого подхода показана на рисунке ниже. image.png

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%
Dict{String, DataFrame} with 2 entries:
  "SubSystem.Out1" => 1001×2 DataFrame…
  "Sine Wave-1.1"  => 1001×2 DataFrame…
# Считывание из simout залогированных сигналов
SysOutput_1 = simout["triggered_subsystem/SubSystem.Out1"];
SysOutput_1 = collect(SysOutput_1);
En = simout["triggered_subsystem/Sine Wave-1.1"];
En = collect(En);
plot(SysOutput_1.time,SysOutput_1.value)
plot!(En.time,En.value)

interactive-scripts/images/base_simulation_demo_Subsystems/2effad80ed8ec9b396014f1919fed2b42e921bee

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

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

image_2.png

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%
Dict{String, DataFrame} with 3 entries:
  "SubSystem.Out1" => 1001×2 DataFrame…
  "Enable.1"       => 1001×2 DataFrame…
  "Trigger.1"      => 1001×2 DataFrame…
# Считывание из 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);

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

plot(Data.time,Data.value)
plot!(Trigger.time,Trigger.value)
plot!(Enable.time,Enable.value)

interactive-scripts/images/base_simulation_demo_Subsystems/7a6684c09e6bfa4d5cbd22b71f7fffcfdcf3710d

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

For Each Subsystem

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

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

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

image.png

image_3.png

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%
Dict{String, DataFrame} with 2 entries:
  "SysOutput_1" => 11×2 DataFrame…
  "Constant.1"  => 11×2 DataFrame…
# Считывание из 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

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%
Dict{String, DataFrame} with 2 entries:
  "SysOutput_1"   => 11×2 DataFrame…
  "SubSystem.idx" => 11×2 DataFrame…
# Считывание из simout залогированных сигналов
Data_out = simout["for_each_model2/SysOutput_1"];
Data_out = collect(Data_out);
idx = simout["for_each_model2/SubSystem.idx"];
idx = collect(idx);
Data_out.value[1]
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
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

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

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

plot(inp.time,inp.value)

interactive-scripts/images/base_simulation_demo_Subsystems/77fda70a2acc52278e2d96c72467d0e77d5df0c4

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

plot(out.time,out.value)

interactive-scripts/images/base_simulation_demo_Subsystems/781757d3b2f5a4c74480696feebb8d14f47a1f53

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

Вывод

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

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