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

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

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

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

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

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

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

vectorization x 1

Broadcast

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

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

broadcast 1

broadcast 2

Результат на выходе блока будет равен [5, 7; 7, 9], так как строки были автоматически согласованы. Этот подход позволяет легко выполнять операции между массивами разных размеров и значительно упрощает работу с многомерными сигналами в моделировании.

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

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

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

Тип BusSignal записывается следующим образом:

BusSignal{Names, Types, Dimensions, :BusName}

где

  • Names — кортеж имен сигналов (например (:a, :b, :c));

  • Types — соответствующие типы (например Tuple{Int, Float64, Vector{Float64}});

  • Dimensions — размерности каждого сигнала (например ((), (), (3,)) для скаляров и вектора длины 3);

  • BusName — имя шины (например :MyBus).

В Types указываются только базовые типы (например, Int, Float64 и т.д.), а не контейнеры (например, Vector{Int}). Размерности сигналов задаются отдельно в Dimensions.

Например, шину с тремя сигналами можно описать так:

bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Float64}, ((), (), (3,)), :MyBus}

Здесь:

  • :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})

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

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

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

Примеры:

# через именованный кортеж
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)

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

Примеры:

# через векторы
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,), ()), :MyBus)

Также поддерживаются вложенные структуры, где элементом одной шины может быть другая шина. Для указания типа вложенной шины среди типов ее элементов нужно указывать полный тип вложенной шины.

Пример вложенной шины:

Inner = BusSignal{(:x, :y), Tuple{Int, Int}, ((), ()), :InnerBus}
Outer = BusSignal{(:a, :b), Tuple{Float64, Inner}, ((), ()), :OuterBus}

Несмотря на то, что объекты шин в Engee представляют собой именованные кортежи, для их создания предусмотрены специальные функции-конструкторы. Эти функции гарантируют соответствие создаваемых объектов объявленному типу шины.

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

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

get_names_types_dims(::Type{BusSignal{Names, Types, Dimensions, BusName}})  # все параметры сразу

get_bus_names(::Type{BusSignal{Names, Types, Dimensions, BusName}}) # кортеж имен сигналов

get_bus_types(::Type{BusSignal{Names, Types, Dimensions, BusName}}) # кортеж базовых типов

get_bus_dimensions(::Type{BusSignal{Names, Types, Dimensions, BusName}})    # кортеж размерностей

get_bus_name(::Type{BusSignal{Names, Types, Dimensions, BusName}})  # имя шины

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

Примеры использования функций
# Задаем тип шины
bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int64, Float64, Int8}, ((), (2,), (2, 2)), :MyBus}

# Пример шины такого типа: (s1 = 5, s2 = [4.3, 5.4], s3 = Int8[1 2; 3 4])
# Все параметры шины сразу
get_names_types_dims(bus_type)
((:s1, :s2, :s3), (Int64, Float64, Int8), ((), (2,), (2, 2)))

В результате получаем кортеж из трех кортежей, каждый из которых описывает соответствующий параметр типа шины.

# Кортеж имен сигналов
get_bus_names(bus_type)
(:s1, :s2, :s3)
# Кортеж базовых типов
get_bus_types(bus_type)
(Int64, Float64, Int8)
# Кортеж размерностей
get_bus_dimensions(bus_type)
((), (2,), (2, 2))
# Имя шины
get_bus_name(bus_type)
:MyBus

Работа с шинами в блоках базовой библиотеки

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

user bus signal

В моделировании часто используют пустую шину и заполняют ее поля по мере прохождения сигнала по модели. Для этого, когда тип шины уже задан (например bus_type), создайте пустое значение так:

bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Float64}, ((), (), (3,)), :MyBus}
empty_bus = convert(bus_type, 0)
(s1 = 0, s2 = 0.0, s3 = [0.0, 0.0, 0.0])

Такое значение удобно подавать из блока Константа и далее заполнять через Управление шиной.

Рассмотрим пример шины: пусть она содержит поля a, b и c, где c — вложенная шина. Тогда тип шины (Тип выходной шины) можно задать так:

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

Здесь:

  • Первый аргумент (:a,:b,:c) — перечисление имен (Names) сигналов шины: a, b и c.

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

    • Int — целое число, тип данных сигнала a;

    • Float64 — число с плавающей точкой, тип данных сигнала b;

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

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

    • () — скаляр, размерность сигнала a;

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

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

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