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

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

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

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

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

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

Например, если задать параметру Amplitude блока Генератор синусоиды матрицу значений [[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], так как строки были автоматически согласованы. Этот подход позволяет легко выполнять операции между массивами разных размеров и значительно упрощает работу с многомерными сигналами в моделировании.

Пользовательские типы шин

Перед изучением раздела о пользовательских типах шин рекомендуется сначала ознакомиться с блоками Создание шины и Выбор из шины.

В Engee существует поддержка пользовательских типов данных для шин, представленных типом BusSignal. Этот тип позволяет задавать структуру шин для блоков, которые работают с подобными данными. В качестве объектов шин в Engee используются именованные кортежи (NamedTuple), например:

bus = (s1 = 4, s2 = 5.5, s3 = [1, 2, 3])

Тип BusSignal параметризуется именами сигналов (Names), базовыми типами (BaseTypes), размерностями (Dims), и именем шины (BusName). Например, шину с тремя сигналами можно описать так:

bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Float64}, ((), (), (3,)), :MyBus}
В BaseTypes указываются только базовые типы (например, Int, Float64), а не контейнеры (например, Vector{Int}). Размерности массивов задаются отдельно в Dims.

здесь:

  • :s1, :s2, :s3 — имена сигналов;

  • Tuple{Int, Float64, Float64} — типы данных сигналов;

  • (), (), (3,) — размерности сигналов (например, s3 — массив длины 3).

  • MyBus — имя шины.

    В имени сигнала и шины допустимы любые символы, кроме кавычки (").

Работать с шинами можно двумя способами: создавать объекты шины (с конкретными данными) или задавать тип шины (только структура, без данных), чтобы затем использовать его в настройках блоков. Эти два подхода отличаются синтаксисом — используются фигурные {} и круглые () скобки.

Когда использовать фигурные и круглые скобки

Синтаксис Когда использовать Пример

BusSignal{…​}

Для описания параметрического типа шины или создания объекта шины с данными

bus_obj = BusSignal{(:x, :y), Tuple{Int, Int}, ((), ()), :Coords}(x=10, y=20)

BusSignal(…​)

Для создания значения типа шины (структуры без данных) — чтобы использовать его в настройках блоков

bus_type = BusSignal((:x, :y), (Int, Int), ((), ()), :Coords)

В BusSignal{…​} вы задаете параметры типа напрямую и можете сразу создать объект шины.

В BusSignal(…​) вы создаете значение типа шины, не указывая данные.

Ниже приведены сигнатуры и примеры вызова этих конструкторов.

Создание объекта шины

Для создания объекта шины используются следующие конструкторы структуры BusSignal:

BusSignal{Names, Types, Dimensions, BusName}(x::NamedTuple{TupleNames, TupleTypes}) where {Names, Types, Dimensions, BusName, TupleNames, TupleTypes}

BusSignal{Names, Types, Dimensions, BusName}(; kwargs...) where {Names, Types, Dimensions, BusName}

BusSignal{Names, Types, Dimensions, BusName}(x::Tuple) where {Names, Types, Dimensions, BusName}

BusSignal{Names, Types, Dimensions, BusName}(varargs...) where {Names, Types, Dimensions, BusName}

Примеры:

# через NamedTuple
bus_obj = BusSignal{(:a, :b), Tuple{Int, Float64}, ((), (3,)), :MyBus}((a=1, b=[1.0, 2.0, 3.0]))

# через ключевые аргументы
bus_obj = BusSignal{(:x, :y), Tuple{Int, Int}, ((), ()), :Coords}(x=10, y=20)

# через кортеж
bus_obj = BusSignal{(:p, :q), Tuple{Float64, Float64}, ((), ()), :Pair}((1.5, 2.5))

# через varargs
bus_obj = BusSignal{(:m, :n), Tuple{Int, Int}, ((), ()), :Nums}(5, 6)

Создание типа шины

Для создания типа шины (структуры без данных) используются следующие методы:

BusSignal(names::Vector{Symbol}, types::Vector{DataType}, dims::Vector{<:Dims}, bus_name::Symbol=:CustomBusSignal)

BusSignal(names::NTuple{N, Symbol}, types::NTuple{N, DataType}, dims::NTuple{N, Dims}, bus_name::Symbol=:CustomBusSignal) where N

Примеры:

# через векторы
bus_type = BusSignal([:a, :b], [Int, Float64], [(), (3,)], :MyBus)

# через кортежи
bus_type = BusSignal((:x, :y), (Int, Int), ((), ()), :Coords)

# вложенная шина
bus_type = BusSignal((:a, :b, :c),  (Int, Float64, NamedTuple{(:x, :y), Tuple{Int, Int}}), ((), (2,), ()))

Получение информации о шине

Для извлечения сведений о параметрах шины можно использовать следующие функции:

get_names_types_dims(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}

get_bus_names(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}

get_bus_types(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}

get_bus_dimensions(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}

get_bus_name(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}

Первая функция (get_names_types_dims) возвращает все параметры шины сразу, а остальные позволяют получить имена, типы, размерности и имя шины по отдельности.

Примеры использования функций
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:

BusSignal{Names, Types, Dimensions, BusName}(x::NamedTuple{TupleNames, TupleTypes}) where {Names, Types, Dimensions, BusName, TupleNames, TupleTypes}
BusSignal{Names, Types, Dimensions, BusName}(; kwargs...) where {Names, Types, Dimensions, BusName}
BusSignal{Names, Types, Dimensions, BusName}(x::Tuple) where {Names, Types, Dimensions, BusName}
BusSignal{Names, Types, Dimensions, BusName}(varargs...) where {Names, Types, Dimensions, BusName}
Примеры создания объекта шины

Сначала создадим тип шины, которому должны соответствовать данные:

bus_type = BusSignal((:s1, :s2), (Int64, Float64), ((), ()), :MyBus)

Теперь можно создавать объекты шины этого типа разными способами:

  • С именованным кортежем:

    bus1 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}((s1 = 5, s2 = 6.4))

    Передается NamedTuple, структура которого полностью соответствует типу bus_type.

  • С именованными аргументами:

    bus2 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}(s1 = 5, s2 = 6.4)

    Структура шины создается из именованных аргументов. Имена и типы сигналов (s1, s2) должны соответствовать параметрам bus_type.

  • С обычным кортежем:

    bus3 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}((5, 6.4))

    Аргументы передаются в порядке, определенном типом bus_type. Количество, типы и размерности должны совпадать с параметрами шины.

  • Через varargs:

    bus4 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}(5, 6.4)

    Значения передаются как позиционные аргументы без упаковки в кортеж.

Таким образом, во всех примерах создаются объекты одной и той же шины :MyBus. При этом:

  • Для bus1 и bus2 имена, типы и размеры сигналов должны строго совпадать с параметрами шины.

  • Для bus3 и bus4 количество, типы и размеры переданных аргументов должны соответствовать требованиям типа шины.


Для блоков, таких как Константа, можно указать значение типа шины через параметр Output data type, после чего задать ее структуру в Output bus type.

user bus signal

Например, пусть значение шины (параметр Constant Value, bus_value) равно (a = 10, b = [2.5, 3.5], c = (x = 1, y = 2)), то ее тип (Output bus type) задается как:

BusSignal((:a,:b,:c),(Int,Float64,BusSignal((:x,:y),(Int,Int),((),()),:InnerBus)),((),(2,),()),:MyBus)

Здесь:

  • BusSignal((:a,:b,:c), …​) — перечисление сигналов шины: :a, :b, :c.

  • Второй аргумент (Int, Float64, BusSignal((:x,:y),(Int,Int),((),()),:InnerBus)) — базовые типы полей:

    • a :: Int — скалярное целое.

    • b :: Float64 — базовый тип элемента; векторность задается не здесь, а в Dims.

    • c — вложенная шина с именами (:x,:y), типами (Int,Int) и размерностями ((),()); ее собственное имя — :InnerBus.

  • Третий аргумент ((), (2,), ()) — размерности (Dims):

    • a — скаляр ().

    • b — вектор длины 2 (2,), поэтому значение должно быть вида [2.5, 3.5].

    • c — скалярная вложенная шина (). Ее внутренние размерности заданы внутри описания :InnerBus.

  • Четвертый аргумент :MyBus — символическое имя шины.