Работа с сигналами разных размерностей
Векторизация и broadcast являются важными приемами при работе с моделями в Engee, позволяющими эффективно обрабатывать сигналы разных размерностей. Используя векторизацию, можно выполнять вычисления над массивами целиком, избегая явных циклов по элементам, что ускоряет вычисления и упрощает код. Broadcast, в свою очередь, обеспечивает автоматическое согласование размерностей между массивами при выполнении операций, позволяя удобно работать с данными разных форматов.
Использование векторизации и broadcast в моделировании позволяет эффективно обрабатывать и анализировать многомерные данные. Векторизация сокращает количество операций за счет работы с массивами целиком, а broadcast устраняет необходимость ручного согласования размерностей.
Векторизация в моделировании
Векторизация в моделировании позволяет сразу оценивать поведение модели для множества значений параметров, что особенно полезно при анализе чувствительности и тестировании. Это значит, что вместо проведения отдельных запусков для каждого значения, можно задать матрицу параметров и получить результаты для всех значений одновременно.
Например, если задать параметру Amplitude блока Sine Wave матрицу значений [[1, 2]; [3, 4]]
, то это будет означать, что блок генерирует несколько сигналов с амплитудами, указанными в матрице. На изображении ниже видно, как блок Sine Wave с заданной амплитудой формирует несколько сигналов, что позволяет сразу увидеть результат для разных значений параметра Amplitude:
Broadcast
Broadcast позволяет согласовывать размерности массивов для выполнения операций над ними. В Engee этот механизм автоматически расширяет размерности массивов, позволяя выполнять операции с несовместимыми на первый взгляд массивами. Например, если один массив имеет размер 2x2
, а другой — 1x2
, то при сложении массивы будут приведены к совместимым размерностям.
Рассмотрим пример с двумя блоками Constant и одним блоком Add. Блок Constant с параметром [3, 4; 5, 6]
добавляется к блоку Constant с параметром [2, 3]
. При этом broadcast автоматически приводит массивы к совместимым размерам и выполняет поэлементное сложение:
Результат на выходе блока будет равен [5, 7; 7, 9]
, так как строки были автоматически согласованы. Этот подход позволяет легко выполнять операции между массивами разных размеров и значительно упрощает работу с многомерными сигналами в моделировании.
Пользовательские типы шин
Перед изучением раздела о пользовательских типах шин рекомендуется сначала ознакомиться с блоками Bus Creator и Bus Selector. |
В Engee существует поддержка пользовательских типов данных для шин, представленных типом BusSignal
. Этот тип позволяет задавать структуру шин для блоков, которые работают с подобными данными. В качестве объектов шин в Engee используются именованные кортежи (NamedTuple
), например:
bus = (s1 = 4, s2 = 5.5, s3 = [1, 2, 3])
Тип BusSignal
параметризуется именами сигналов (Names
), базовыми типами (BaseTypes
), и размерностями (Dims
). Например, шину с тремя сигналами можно описать так:
bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Vector{Int}}, ((), (), (3,))}
здесь:
-
:s1
,:s2
,:s3
— имена сигналов; -
Tuple{Int, Float64, Vector{Int}}
— типы данных сигналов; -
(), (), (3,)
— размерности сигналов (например,s3
— массив длины3
).
Можно создавать объекты шины через функции или задавать тип шины в виде переменной, чтобы использовать ее в настройках блоков. Для упрощения работы с типами шин доступны функции, которые извлекают имена, типы данных и размерности:
get_names_types_dims(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}
get_bus_names(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}
get_bus_types(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}
get_bus_dimensions(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}
Первая функция позволяет достать из типа шины информацию сразу обо всех параметрах шины, а следующие три функции достают информацию об именах, типах и размерностях шины по отдельности.
Примеры использования функций
bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int64, Float64, Int8}, ((), (2,), (2, 2))}
# Экземпляром шины такого типом могла бы выглядеть, например, так: (s1 = 5, s2 = [4.3, 5.4], s3 = Int8[1 2; 3 4])
get_names_types_dims(but_type) # Результат: ((:s1, :s2, :s3), (Int64, Float64, Int8), ((), (2,), (2, 2))) - кортеж из трех кортежей, каждый из которых описывает свой параметр типа шины
get_names(but_type) # Результат: (:s1, :s2, :s3)
get_types(but_type) # Результат: Tuple{Int64, Float64, Int8}, то есть параметр пока что возвращается в исходном виде, что несколько неудобно. В одной из ближайших задач будет исправлено и результатом будет кортеж (Int64, Float64, Int8)
get_dimensions(but_type) # Результат: ((), (2,), (2, 2))
Например, метод get_bus_signal_type
преобразует именованный кортеж в соответствующий тип шины:
bus = (s1 = 5, s2 = [4.3, 5.4], s3 = (a = 4, b = 5.5))
bus_type = get_bus_signal_type(bus)
# bus_type равен BusSignal{(:s1, :s2, :s3), Tuple{Int64, Float64, BusSignal{(:a, :b), Tuple{Int64, Float64}, ((), ())}}, ((), (2,), ())}
Также поддерживаются вложенные структуры, где элементом одной шины может быть другая шина (пример выше). Для указания типа вложенной шины среди типов ее элементов нужно указать полный тип вложенной шины. |
Несмотря на то что объекты шин в Engee представляют собой именованные кортежи, для их создания предусмотрены специальные функции-конструкторы. Эти функции позволяют создавать объекты шин, гарантируя соответствие их структуры заданному типу шины. Для создания объектов шин можно использовать следующие варианты:
-
С именованным кортежем:
BusSignal{Names, Types, Dimensions}(x::NamedTuple{TupleNames, TupleTypes}) where {Names, Types, Dimensions, TupleNames, TupleTypes}
-
С именованными аргументами:
BusSignal{Names, Types, Dimensions}(kwargs...) where {Names, Types, Dimensions}
-
С обычным кортежем:
BusSignal{Names, Types, Dimensions}(x::Tuple) where {Names, Types, Dimensions}
Пример создания объекта шины
Рассмотрим создание объекта шины с типом:
bus_type = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ())}
-
Для именованного кортежа:
bus1 = bus_type((s1 = 5, s2 = 6.4))
Здесь передается именованный кортеж, структура которого полностью соответствует типу
bus_type
. -
Для именованных аргументов:
bus2 = bus_type(s1 = 5, s2 = 6.4)
Здесь структура шины создается из именованных аргументов. Имена сигналов (
s1
,s2
) и их типы должны соответствовать определениюbus_type
. -
Для обычного кортежа:
bus3 = bus_type(5, 6.4)
Здесь аргументы передаются в порядке, определенном типом
bus_type
. Количество, типы и размеры аргументов должны соответствовать параметрам шины.
Так, в каждом примере будет создана шина и того же типа bus_type
. Однако следует учитывать, чтобы:
-
Для
bus1
иbus2
имена, типы и размеры сигналов совпадали с параметрами шины; -
Для
bus3
количество, типы и размеры переданных аргументов соответствовали требованиям типа шины.
Для блоков, таких как Constant, можно указать значение типа шины через параметр Output data types, после чего задать ее структуру в Output bus type.
Например, пусть значение шины bus_value = (a = 10, b = [2.5, 3.5], c = (x = 1, y = 2))
. Тогда ее тип задается как:
bus_type = BusSignal{(:a, :b, :c), Tuple{Int, Vector{Float64}, NamedTuple{(:x, :y), Tuple{Int, Int>>}, ((), (2,), ((), ())}}