Обработка данных симуляции
Обработка данных моделирования
В этом примере рассмотрим, как получать данные симуляции из модели, а также, как в скриптах Engee корректно и эффективно обрабатывать полученные данные.
Введение
Для того, чтобы в процессе симуляции записываемые сигналы модели сразу попали в рабочую область, есть два пути:
- Использовать переменную simout
- Использовать блоки To Workspace
Каждый имеет свои плюсы и минусы, особенности подходов при использовании.
1. Используем simout
1.1. Получение переменной
Чтобы необходимые данные из модели записались в переменную simout, нужно сделать следующее:
- Поставить необходимые сигналы на запись.
- Включить в настройках модели опцию "Сохранять результаты симуляции в рабочую область".
Рекомендация
Для удобства работы записываемым сигналам рекомендуется определить имя.
Возможность
При установке сигналов на запись и снятия с записи рекомендуемый к использованию инструмент - редактор данных модели.

Теперь, после выполнения модели мы получаем в рабочей области переменную simout:
model1_path = "$(@__DIR__)/simulation1.engee"
engee.load(model1_path) |> engee.run; # загрузили и выполнили модель
engee.close(engee.gcm(), force=true) # закрыли модель
simout
1.2. Обработка переменной simout
В полученной переменной simout, в словаре dictхранятся пары ключ (имя сигнала) => значение (сигнал - время и значения/вектор значений):
# получаем все имена сигналов из simout
dict_keys = simout.dict |> keys |> collect
Значения для первого ключа - simulation/sin_5Hz будут выглядеть так:
collect(simout.dict["simulation1/sin_5Hz"])
То, есть, в значении словаря dict для имени нашего сигнала simulation/sin_5Hz хранится объект WorkspaceArray с полями time и value. Обращаясь к этим полям, можно получить отметки времени сигнала и сами значения и построить график:
t1 = simout.dict["simulation1/sin_5Hz"].time
val1 = simout.dict["simulation1/sin_5Hz"].value
gr(format=:png)
plot(t1, val1; label="simulation1/sin_5Hz")
1.3. Векторный сигнал в value
Сигнал, который мы взяли для построения выше был скалярным - в один момент времени записывалось одно значение типа Float64.
Рассмотрим иной случай, когда сигнал simulation/two_sin - вектор значений Float64:
simout.dict["simulation1/two_sin"].value[1] # получаем первый элемент из массива значений сигнала simulation/two_sin
Это, как видно, вектор из двух значений. О том же самом нам сообщит и поле dimension словаря dict
simout.dict["simulation1/two_sin"].dimension # получаем значение, хранящееся в поле dimension
Отсюда следует: размерность значений поля value - 2×1. И построить их таким же способом, как раньше, не получится:
t2 = simout.dict["simulation1/two_sin"].time
val2 = simout.dict["simulation1/two_sin"].value
plot(t2, val2; label="simulation1/two_sin")
Чтобы получить индексированные массивы отдельных сигналов, и построить их, используем getindex():
val2_1 = getindex.(val2, 1)
val2_2 = getindex.(val2, 2)
plot(t2, val2_1; label="simulation1/two_sin[1]")
plot!(t2, val2_2; label="simulation1/two_sin[2]")
1.4. Неименованный сигнал
В случае, если при моделировании имя сигнала на сигнальной линии не было определено, имя сигнала в simout будет определено по имени блока и номеру порта, из которого он исходит.
collect(simout.dict["simulation1/Генератор синусоиды.1"])
Рекомендация
Такой вариант, безусловно, рабочий, но в сравнении с именованными сигналами менее красив, его сложнее идентифицировать и он менее удобен при обработке.
1.5. Прореживание
Размерность массивов, которые мы передаём в plot() для построения графика, составляет всего лишь:
simout.dict["simulation1/sin_5Hz"].value |> size # определяем размерность вектора значений сигнала simulation1/sin_5Hz
101 элемент в векторе, который в gr() бэкенде Plots, да ещё и с форматом изображения format = :png выводится на график достаточно быстро.
Предостережение
Например, если попытаться построить изображение svg в интерактивном бэкенде plotlyjs() для вектора из 100 000 элементов, придётся использовать значительно больше ресурсов и времени.
В случае, если для визуализации важная не каждая точка, можно использовать самый простой вариант - ленивые срезы WorkspaceArray:
simout.dict["simulation1/sin_5Hz"].value[1:2:end] |> size # получаем вектор значений сигнала с шагом 2 и определяем новую размерность
Ленивые срезы массива позволяют использовать для построения не все значения, а с шагом 2 ([1:2:end]) или иным ([1:3:end], [1:4:end], [1:5:end] и т.д.).
Ленивый срез поможет также выделить из сигнала начало ([1:20]) или конец ([40:end]).
Предостережение
Такой подход удобен для прореживания постоянных, слабоизменяющихся, а также гармонических сигналов. При прореживании последних необходимо использовать теорему Котельникова - максимальная частота в сигнале после прореживания должна быть меньше половины новой частоты дискретизации для выполнения условия Найквиста.
Рекомендация
Для сигналов с непериодическими переходными процессами следует использовать более гибкие способы прореживания, использовать оценку изменения сигнала и прореживание исходя из динамики его изменения.
2. Используем блоки To Workspace
2.1. Получение переменных
Чтобы необходимые данные из модели записались в рабочую область, нужно сделать следующее:
- На каждый записываемый сигнал (массив) установить блок ToWorkspace (В рабочую область).
- Дать имена записываемых в блоках переменных
Критично
На втором шаге нужно быть предельно внимательными - имя переменной в блоке должно быть задано с учетом правил именования переменных Julia.

Теперь, после выполнения модели мы получаем в рабочей области переменные с заданными в блоках именами.
model2_path = "$(@__DIR__)/simulation2.engee"
engee.load(model2_path) |> engee.run; # загрузили и выполнили модель
engee.close(engee.gcm(), force=true) # закрыли модель
sin_2Hz, sin_5Hz, two_sin, Генератор_синусоиды
2.2. Обработка переменных
Эти переменные также имеют типы WorspaceArray, и к ним применимы те же способы обработки, которые мы использовали в разделах 1.2 и 1.3.
2.3 Прореживание
Помимо программных способов прореживания, которые были упомянуты в разделе 1.5, удобно также предварительно использовать стандартные для блоков способы понижения частоты дискретизации:
- путём снижения частоты дискретизации в блоке ToWorkspace,
- при помощи блока Rate Transition (Переход между частотами дискретизации)
Снижение частоты дискретизации (Sample time: -1 -> 0.1)
Переход между частотами дискретизации:
.png)
Заключение
Оба представленных способа позволяют организовать программную обработку результатов симуляции. В первом случае в большей степени требуется умение работать с программными конструкциями языка Julia и знание программных сущностей Engee. Во втором случае требуется устанавливать дополнительные блоки на каждый записываемый сигнал.
Определяющими факторами здесь являются потребности, компетенции и удобство пользователя.

