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

Работа с сигналами разных размерностей

Векторизация и broadcast являются важными приемами при работе с моделями в Engee, позволяющими эффективно обрабатывать сигналы разных размерностей. Используя векторизацию, можно выполнять вычисления над массивами целиком, избегая явных циклов по элементам, что ускоряет вычисления и упрощает код. Broadcast, в свою очередь, обеспечивает автоматическое согласование размерностей между массивами при выполнении операций, позволяя удобно работать с данными разных форматов.

Использование векторизации и broadcast в моделировании позволяет эффективно обрабатывать и анализировать многомерные данные. Векторизация сокращает количество операций за счет работы с массивами целиком, а broadcast устраняет необходимость ручного согласования размерностей.

Векторизация в моделировании

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

Например, если задать параметру Amplitude блока Sine Wave матрицу значений [[1, 2]; [3, 4]], то это будет означать, что блок генерирует несколько сигналов с амплитудами, указанными в матрице. На изображении ниже видно, как блок Sine Wave с заданной амплитудой формирует несколько сигналов, что позволяет сразу увидеть результат для разных значений параметра Amplitude:

vectorization x 1

Broadcast

Broadcast позволяет согласовывать размерности массивов для выполнения операций над ними. В Engee этот механизм автоматически расширяет размерности массивов, позволяя выполнять операции с несовместимыми на первый взгляд массивами. Например, если один массив имеет размер 2x2, а другой — 1x2, то при сложении массивы будут приведены к совместимым размерностям.

Рассмотрим пример с двумя блоками Constant и одним блоком Add. Блок Constant с параметром [3, 4; 5, 6] добавляется к блоку Constant с параметром [2, 3]. При этом broadcast автоматически приводит массивы к совместимым размерам и выполняет поэлементное сложение:

broadcast 1

broadcast 2

Результат на выходе блока будет равен [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.

user bus signal

Например, пусть значение шины 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,), ((), ())}}