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

Engee Function

Использование кода Julia в моделях.

engee function

Описание

Блок Engee Function позволяет использовать код на языке Julia в моделях Engee.

Подробнее про язык программирования Julia читайте в документации.
В блоке Engee Function допустимо использовать большую часть возможностей языка Julia. При этом использование в блоке пакетного менеджера Pkg не предусматривается.

Использование

Для интеграции кода на Julia в модель Engee необходимо:

  1. Добавить в модель блок Engee Function из раздела Базовые/Пользовательские функции библиотеки блоков blocks library icon;

  2. В окне настроек debug article icon 1 на вкладке «Main» блока Engee Function нажать на кнопку Редактировать исходный код для открытия редактора исходного кода (EngeeFunctionCode):

engee function code editor

Ячейки исходного кода

Редактор исходного кода EngeeFunctionCode состоит из функциональных ячеек с кодом Julia. По умолчанию доступны три ячейки — вспомогательная (нередактируемая), Component struct code и Step method code (ячейки можно скрыть):

engee function all start cell

Для подключения дополнительных файлов исходного кода можно использовать функцию include() в ячейке Common code (описание ячейки см. ниже):

include("/user/engeefunction/source.jl")
Весь код блока Engee Function можно написать в ячейке Common code, получив полный контроль над структурой компонента, сигнатурами и количеством функций.

Для добавления/удаления других функциональных ячеек нажмите на кнопку «Управление методами» engee function all methods и установите/уберите флажки на нужных ячейках:

engee function сhoose methodsengee function сhoose methods 1

Каждая ячейка отвечает за уникальный функционал блока Engee Function. Рассмотрим их подробнее:

  • Информационная ячейка (нередактируемая) — автоматически отображает переменные блока Engee Function, атрибуты входных и выходных сигналов (размерность, тип, дискретность) и другие параметры, заданные пользователем. Ее содержимое обновляется динамически в зависимости от настроек блока. Ячейка всегда активна, но не выбирается в меню управления методами engee function all methods. Имеет полупрозрачный текст, которым отображаются подсказки.

    engee function start cell

    Изменение параметров блока влияет не только на содержимое информационной ячейки, но и на подсказки в других ячейках, которые также отображаются полупрозрачным текстом.
  • Define component struct — добавляет ячейку Component struct code, в которой задается структура компонента блока Engee Function (наследуемая от типа AbstractCausalComponent). Поля структуры определяются между нередактируемыми строками struct Block <: AbstractCausalComponent и end. По умолчанию создается параметр g, инициализируемый значением блока gain, а конструктор Block() не принимает аргументов.

    engee function component struct code

  • Use common code — добавляет ячейку Common code, в которой пишется код в свободной форме. По умолчанию ячейка пуста. Например, если стандартное объявление структуры в Component struct code не подходит (из-за нередактируемости struct Block <: AbstractCausalComponent), то можно отключить Define component struct, чтобы удалить ячейку, и определить структуру компонента вручную в Common code. То же самое касается любых функций из меню управления методами engee function all methods — вместо стандартных ячеек можно писать свой код в Common code.

    engee function common code

    Объявление компонента и функтора обязательно для работы Engee Function. Если Define component struct и Define step method отключены, то их код необходимо задать в Common code, иначе блок не будет работать.
    Для переопределения функций наследования (Override, см. ниже) сначала нужно включить соответствующую ячейку, стереть ее содержимое, а затем написать новый код в Common code.
  • Define step method — добавляет ячейку Step method code, в которой задается метод Step, вычисляющий выходные сигналы блока Engee Function. Сигнатура метода генерируется автоматически в зависимости от значений соответствующих меток (Label) портов во вкладке «Ports». Метод представлен в виде функтора (подробнее см. здесь) и вызывается на каждом шаге симуляции. Поля определяются между нередактируемыми строками function (c::Block)(t::Real, in1) и end. Первый аргумент t::Real — время симуляции, далее передаются переменные входных сигналов. Вычисленные значения выходов возвращаются через ключевое слово return.

    engee function define step method

  • Define update method — добавляет ячейку Update method code, в которой метод update! обновляет внутреннее состояние блока Engee Function на каждом шаге симуляции. Первый аргумент c::Block — структура блока, второй t::Real — время симуляции, далее передаются входные сигналы. Если блок не имеет внутреннего состояния, то метод может оставаться пустым и просто возвращать c.

    engee function update method code

    Если требуется определить несколько методов update! или задать метод с другой сигнатурой, то ячейку Update method code можно отключить и написать код в ячейке Common code. Компилятор автоматически определит наличие метода update! и использует его для симуляции.
  • Define terminate method code — добавляет ячейку Terminate method code, которая выполняется при завершении симуляции блока Engee Function (с помощью метода terminate!). Первый аргумент c::Block — структура блока. По умолчанию метод не выполняет дополнительных действий и просто возвращает c.

    engee function terminate method code

  • Override type inheritance method — добавляет ячейку Types inheritance method, которая переопределяет метод наследования типов.

    Подробнее о методе наследования типов

    Задает метод наследования типов:

    • Если флажок снят (по умолчанию) — типы входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов.

    • Если флажок установлен — типы входных/выходных портов наследуются по правилам, указанным в функции propagate_types ячейки Types inheritance method в исходном коде.

      • Функция propagate_types принимает в себя один аргумент — вектор типов, по одному типу на каждый входной сигнал и возвращает вектор выходных типов.

    Код ячейки по умолчанию:

    function propagate_types(inputs_types::Vector{DataType})::Vector{DataType}
    # Функция, возвращающая массив типов сигналов на выходе.
    # Игнорируется, если используется алгоритм по умолчанию.
    # В данном случае учитываются тип входного сигнала и тип
    # параметра блока `gain`.
        input_type = first(inputs_types)
        # promote_type возвращает тип, к которому приводятся типы-аргументы
        # при арифметических операциях с объектами этих типов.
        output_type = promote_type(input_type, eltype(gain))
        return [output_type]
    end

    Здесь для наследования типов берется общий элемент входного сигнала и элемент параметра, заданного в настройках блока в разделе Parameters.

  • Override dimensions inheritance method — добавляет ячейку Dimensions inheritance method, которая переопределяет метод наследования размерностей.

    Подробнее о методе наследования размерностей

    Задает метод наследования размерностей:

    • Если флажок снят (по умолчанию) — размерности входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов.

    • Если флажок установлен — размерности входных/выходных портов наследуются по правилам, указанным в функции propagate_dimensions ячейки Dimensions inheritance method в исходном коде.

      • Функция propagate_dimensions принимает в себя массив кортежей (размерностей) на каждый входной сигнал и возвращает массив размерностей на выходном.

    Код ячейки по умолчанию:

    function propagate_dimensions(inputs_dimensions::Vector{<:Dimensions})::Vector{<:Dimensions}
    # Функция, возвращающая массив размерностей сигналов на выходе.
    # Игнорируется, если используется алгоритм по умолчанию.
    # В данном случае учитываются размерности входного сигнала и
    # параметра блока `gain`.
        input_dimensions = first(inputs_dimensions)
        mock_input = zeros(input_dimensions)
        mock_output = mock_input .* gain
        return [size(mock_output)]
    end

    Здесь для наследования размерностей берется массив с нужными размерностями (mock_input), состоящий из нулей и умножается на элемент параметра, заданного в настройках блока в разделе Parameters, после чего берется его размерность.

  • Use common method for types and dimensions inheritance — добавляет ячейку Common types and dimensions inheritance method, которая использует общий метод для переопределения наследования типов и размерностей одновременно.

    Подробнее о методе наследования типов и размерностей

    В отличие от конкретных методов для типов (Types inheritance method) или размерностей (Dimensions inheritance method), общий метод включает в себя и типы и размерности одновременно:

    • Если флажок снят (по умолчанию) — общий метод игнорируется, размерности и типы входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов или в методах наследования Override type inheritance method (если включено) и Override dimensions inheritance method (если включено).

    • Если флажок установлен — размерности и типы входных/выходных портов наследуются по правилам, указанным в функции propagate_types_and_dimensions ячейки Common types and dimensions inheritance method в исходном коде.

    Код ячейки по умолчанию:

    function propagate_types_and_dimensions(inputs_types::Vector{DataType}, inputs_dimensions::Vector{<:Dimensions})::Tuple{Vector{DataType}, Vector{<:Dimensions}}
    # Функция, возвращающая массив типов сигналов и массив
    # размерностей сигналов на выходе. Эту функцию можно использовать
    # если необходимо одновременно переопределить и алгоритм наследования
    # типов сигналов, и алгоритм наследования размерностей.
        outputs_types = propagate_types(inputs_types)
        outputs_dimensions = propagate_dimensions(inputs_dimensions)
        return outputs_types, outputs_dimensions
    end

    Зависимости

    Чтобы использовать эту ячейку, установите флажки методов Override type inheritance method и Override dimensions inheritance method.

  • Override sample time inheritance method — добавляет ячейку Sample times inheritance method, которая переопределяет метод наследования шага расчета.

    Подробнее о методе наследования шага расчета
    Код структуры шага расчета SampleTime и функции propagate_sample_times не будут автоматически добавлены в исходный код EngeeFunctionCode старых моделей Engee. Для доработки старых моделей добавьте структуру шага расчета и функцию самостоятельно.

    Задает метод наследования шага расчета:

    • Если флажок снят (по умолчанию) — используется предустановленный способ наследования шага расчета (по умолчанию Default) из параметра Sample time inheritance method вкладки Advanced. Подробнее о предустановленных способах читайте ниже.

    • Если флажок установлен — предустановленные способы вкладки Advanced игнорируются (параметр недоступен), используется самостоятельный способ из ячейки Sample times inheritance method в исходном коде EngeeFunctionCode.

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

    Код ячейки по умолчанию:

    function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime
    # Функция, возвращающая время дискретизации блока.
    # Используется только в режиме наследования `Custom`.
    # Параметр fixed_solver говорит о том, используется решатель
    # с постоянным шагом (true) или с переменным (false).
    # Более сложный пример работы с наследованием времени
    # дискретизации блока можно посмотреть в документации.
        return first(inputs_sample_times)
    end

    где шаг расчета имеет структуру:

    const SampleTime = NamedTuple{(:period, :offset, :mode), Tuple{Rational{Int64}, Rational{Int64}, Symbol}}
  • Override direct feedthrough setting method — добавляет ячейку Direct feedthrough setting method, которая определяет прямое сквозное соединение.

    Подробнее об определении прямого сквозного соединения

    Определяет прямое сквозное соединение:

    • Если флажок снят (по умолчанию), то прямое сквозное соединение недоступно. Это означает, что выходной сигнал не будет контролироваться значением входного порта и позволяет блоку размыкать петли.

    • Если флажок установлен, то доступно прямое сквозное соединение. Это означает, что выходной сигнал контролируется непосредственно значением входного порта.

    Код ячейки по умолчанию:

    function direct_feedthroughs()::Vector{Bool}
    # Функция, возвращающая массив булевых значений, определяющих,
    # сквозные соединения. Если i-ый элемент массива равен true,
    # то i-ый порт имеет сквозное соединение.
    # Игнорируется, если используется алгоритм по умолчанию.
        return [true]
    end

    Пример:

    function direct_feedthroughs()::Vector{Bool}
        if gain == 2
            return [false]
        else
            return [true]
        end
    end

Константы и функции получения атрибутов

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

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

  • START_TIME — начало симуляции из настроек модели.

  • END_TIME — конец симуляции из настроек модели.

  • INPUT_SIGNAL_ATTRIBUTES — списки из атрибутов по каждому входному порту. Например, чтобы узнать атрибуты первого входного сигнала — используйте INPUT_SIGNAL_ATTRIBUTES[1], где 1 — первый входной порт блока Engee Function.

  • OUTPUT_SIGNAL_ATTRIBUTES — списки из атрибутов по каждому выходному порту. Например, чтобы узнать атрибуты первого выходного сигнала — используйте OUTPUT_SIGNAL_ATTRIBUTES[1], где 1 — первый выходной порт блока Engee Function.

Чтобы узнать дополнительную информацию конкретного порта блока, можно обратиться к атрибутам его сигнала, добавив точку . после константы INPUT_SIGNAL_ATTRIBUTES[i], где [i] — номер входного порта, и OUTPUT_SIGNAL_ATTRIBUTES[i], где [i] — номер выходного порта соответственно. Можно узнать дополнительную информацию через следующие функции обращения:

  • dimensions — размерность сигнала. Можно сократить до dims.

  • type — тип сигнала. Можно сократить до tp.

  • sample_time — шаг расчета. Представляет собой структуру по аналогии с атрибутами сигналов, к которой можно обращаться через точку .. Доступны две функции обращения:

    • period — период шага расчета. Полная функция обращения — sample_time.period. Можно сократить до st.p.

    • offset — смещение шага расчета. Полная функция обращения — sample_time.offset. Можно сократить до st.o.

  • direct_feedthrough — указывает, размыкает ли порт петли. Используется только для входных портов (проверяет атрибуты только для входного порта). Можно сократить до df.

Пример модели Engee Function со всеми константами и функциями обращения:

engee function constants

Порты

Вход

Input Port — входной порт
скаляр | вектор | матрица

Details

Входной порт, заданный в виде скаляра, вектора или матрицы.

Настройте входной порт во вкладке Ports блока с помощью следующих опций:

  • Label — задайте имя входного порта. По умолчанию ячейка Label не заполнена (имя не задано).

  • Type — тип данных входного сигнала. Выберите один из вариантов:

    • Определенный тип (все, кроме Inherit) — проверяется, что на входной порт подается сигнал определенного типа. Выберите конкретный тип данных для входного порта. Поддерживаемые сигналы: Float16, Float32, Float64, ComplexF32, ComplexF64, Bool, Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128.

    • Inherit (по умолчанию) — наследует тип данных от связанного блока. Может быть любым типом данных.

  • Size — размерность входного сигнала:

    • Наследование всех размерностей (-1 по умолчанию) — наследует размерность поданного на входной порт сигнала (сигнал может иметь любую размерность).

    • Определенные размерности — входной сигнал должен иметь заданное количество элементов. Размерности указываются в Julia-нотации (в виде кортежа), например, (2,) для одномерного сигнала из двух элементов или (2, 3, 4) для многомерного. Если указано -1, то размерность наследуется.

    • Наследование одной из размерностей — наследует размерность поданного на входной порт сигнала с явным указанием структуры данных. Например, (-1, 2) — ожидается, что первая размерность наследуется, а вторая задается явно.

  • Output bus type — входной тип шины, заменяет размерность входного сигнала Size в случае, если у типа данных входного сигнала Type выбран тип BusSignal (шина). По умолчанию имеет значение BusSignal{(), Tuple{}, ()}. Чтобы блок Engee Function понял, какая шина придет на вход, достаточно установить Type в значение Inherit. Явное указание типа необходимо только для выходного сигнала.

    Блок Engee Function не наследует шины на выходные порты, хотя и может получать их на входы. Для наследования шин на выходные порты (для передачи в другие блоки) нужно явно задать тип шины в параметре Output bus type.
  • Direct feedthrough — определяет прямое сквозное соединение:

    • Если флажок установлен (по умолчанию), то доступно прямое сквозное соединение. Это означает, что выходной сигнал контролируется непосредственно значением входного порта.

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

Выход

Output port — выходной порт
скаляр | вектор | матрица

Details

Выходной порт, заданный в виде скаляра, вектора или матрицы.

Настройте выходной порт во вкладке Ports блока с помощью следующих опций:

  • Label — задайте имя выходного порта. По умолчанию ячейка Label не заполнена (имя не задано).

  • Type — тип данных выходного сигнала. Выберите один из вариантов:

    • Определенный тип (все, кроме Inherit) — определяем тип данных выходного сигнала. Выберите конкретный тип данных для выходного сигнала. Поддерживаемые сигналы: Float16, Float32, Float64, ComplexF32, ComplexF64, Bool, Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128.

    • Inherit (по умолчанию) — наследует тип данных выходного сигнала. Вычисляет наименьший общий тип при наличии нескольких входных сигналов разного типа. Может быть любым типом данных.

  • Size — размерность выходного сигнала:

    • Наследование всех размерностей (-1 по умолчанию) — наследует размерность поданного на выходной порт сигнала. Выходной сигнал будет иметь размерности, полученные в результате broadcast механизма — Julia автоматически расширит размерность меньшего массива данных до размерности большего, чтобы корректно унаследовать размерность.

    • Определенные размерности — выходной сигнал должен иметь заданное количество элементов. Размерности указываются в Julia-нотации (в виде кортежа), например, (2,) для одномерного сигнала из двух элементов или (2, 3, 4) для многомерного. Если указано -1, то размерность наследуется.

    • Наследование одной из размерностей — наследует размерность поданного на выходной порт сигнала с явным указанием структуры данных. Например, (-1, 2) — ожидается, что первая размерность наследуется, а вторая задается явно.

  • Output bus type — выходной тип шины, заменяет размерность входного сигнала Size в случае, если у типа данных выходного сигнала Type выбран тип BusSignal (шина). По умолчанию имеет значение BusSignal{(), Tuple{}, ()}. Чтобы блок Engee Function выдавал шину на выход, необходимо явно указать ее тип.

Параметры

Main

Number of input ports — определяет количество входных портов
1 (по умолчанию)

Details

Определяет количество входных портов блока. Значение параметра Number of input ports будет соответствовать количеству входных портов.

Number of output ports — определяет количество выходных портов
1 (по умолчанию)

Details

Определяет количество выходных портов блока. Значение параметра Number of output ports будет соответствовать количеству выходных портов.

Sample time — интервал между шагами расчета
−1 (по умолчанию)

Details

Укажите интервал между шагами расчета как неотрицательное число. Чтобы наследовать шаг расчета, установите для этого параметра значение −1.

Advanced

Use external cache for non-scalar output — использовать внешний кэш для нескалярных выходных сигналов
выключено (по умолчанию) | включено

Details

Укажите использование внешнего кэша для нескалярного (многомерного) выходного сигнала для экономии оперативной памяти Engee. Используется если у блока Engee Function только один выходной порт:

  • Если флажок снят (по умолчанию) — внешний кэш не используется.

  • Если флажок установлен — выходной сигнал сможет принимать дополнительный аргумент cache, который необходимо учесть в исходном коде EngeeFunctionCode. Если у блока один выходной порт, то в ячейке Step method code аргумент cache автоматически добавляется в список аргументов функции, кроме случая, когда размерность порта явно задана как () (скаляр). В исходном коде требуется написание функторов в зависимости от размерности выходного сигнала. Функторы можно определить в ячейке Common code:

    • Если выходной сигнал скалярный:

      function (c::Block)(t::Real, x)
          return c.g * x
      end
    • Если выходной сигнал нескалярный:

      function (c::Block)(t::Real, cache, x)
          cache .= c.g .* x
          nothing
      end

      где t — время, x — аргумент (информация с входных портов). Параметр времени должен указываться, даже если он отсутствует в параметрах блока.

Sample time inheritance method — определение метода наследования шага расчета
Default (по умолчанию) | Discrete | Continuous

Details
Настройка Sample time inheritance method пропадает с вкладки «Advanced» в случае, если в исходном коде EngeeFunctionCode установлен флажок Override sample time inheritance method. В этом случае метод наследования шага расчета определяется кодом функциональной ячейки Sample times inheritance method.

Определяет метод наследования шага расчета в зависимости от выбранного значения:

Если в поле Sample Time указано значение -1, то шаг расчета наследуется согласно методу, указанному в поле Sample time inheritance method в зависимости от выбранного значения (Default, Discrete, Continuous). В остальных случаях (Sample Time не равно -1 и SampleTime больше или равно 0) — блок Engee Function работает с заданным значением поля Sample Time, игнорируя Sample time inheritance method.
  • Default — способ наследования шага расчета по умолчанию. Способ наследования Default всегда используется в случае, когда блок Engee Function не является дискретным или непрерывным. Способ получает любой вид шага расчета. При выборе этого способа блок Engee Function будет наследовать шаг расчета по следующим принципам:

    • Если у блока нет входных портов — на выходе непрерывный шаг расчета.

    • Если на входе все шаги расчета одинаковы — на выходе шаг расчета совпадает со входом.

    • Если среди входных шагов расчета есть непрерывные — на выходе тоже есть непрерывные шаги расчета.

    • Если среди входных шагов расчета есть фиксированный малый (FiM, Fixed-in-Minor), нет непрерывного шага расчета и решатель с переменным шагом — на выходе фиксированный малый.

    • Если на входе нет непрерывного и фиксированного малого шагов расчета и не все шаги расчета равны — рассматриваются только дискретные шаги расчета на входе, для которых справедлив один из вариантов:

      • Если наибольший общий делитель дискретных шагов расчета совпадает с одним из входных шагов расчета или используется решатель с постоянным шагом — на выходе дискретный шаг расчета с шагом наибольшего общего делителя.

      • Если решатель с переменным шагом и наибольший общий делитель входных дискретных шагов расчета не совпадают ни с одним из входных шагов расчета — на выходе фиксированный малый.

  • Discrete — способ наследования для получения дискретного шага расчета. При выборе этого способа блок Engee Function будет наследовать шаг расчета по следующим принципам:

    • Если на входе есть непрерывный или фиксированный малый шаги расчета — на выходе дискретный шаг расчета с шагом решателя (даже если решатель с переменным шагом).

    • Если на входе среди шагов расчета есть дискретные — на выходе дискретный шаг расчета с наибольшим общим делителем от входных дискретных шагов расчета.

  • Continuous — способ наследования для получения непрерывного шага расчета независимо от входных шагов расчета.

Пример переопределения функции propagate_sample_times с работой, аналогичной способу наследования Default:

+

function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime
    nonnegative_sample_times = filter(
        st -> st.period >= 0,
        collect(values(inputs_sample_times)),
    )
    finite_periods = filter(
        st -> !isinf(st.period),
        nonnegative_sample_times,
    ) .|> (st -> st.period)
    output_sample_time = if !isempty(nonnegative_sample_times)
        if allequal(nonnegative_sample_times)
            first(nonnegative_sample_times)
        elseif any(st -> st.period == 0 // 1 && st.mode == :Continuous, nonnegative_sample_times)
            (period = 0 // 1, offset = 0 // 1, mode = :Continuous)
        elseif any(st -> st.mode == :FiM, nonnegative_sample_times) && !fixed_solver
            (period = 0 // 1, offset = 0 // 1, mode = :FiM)
        elseif (
            all(x -> x.period > 0 // 1, nonnegative_sample_times) &&
            (fixed_solver || gcd(finite_periods) in finite_periods)
            )
            (period = gcd(finite_periods), offset = 0 // 1, mode = :Discrete)
        else
            (period = 0 // 1, offset = 0 // 1, mode = :FiM)
        end
    else
        (period = 0 // 1, offset = 0 // 1, mode = :Continuous)
    end
    return output_sample_time
end
Eсли функция propagate_sample_times возвращает (period = 0 // 1, offset = 0 // 1, mode = :Discrete), то такой шаг расчета будет воспринят как дискретный с шагом решателя.

Настройка параметров

Number of parameters — укажите число параметров
1 (по умолчанию)

Details

Число параметров, используемых в блоке.

Parameter — определяет параметр как переменную
скаляр | вектор | массив

Details

Определяет параметр для использования в исходном коде блока Engee Function. В параметре можно задать:

  • Name — имя параметра;

  • Value — значение параметра. В качестве значений можно задавать любые выражения на языке Julia.

Значение и имя параметра могут быть изменены во вкладке Parameters. Имя первого параметра (присутствующего по умолчанию) — gain, значение которого равно 2. Новые параметры называются parameter2 и далее по возрастанию. Значение новых параметров по умолчанию равно 0.

Глобальные переменные, доступные в окне переменных Engee variables icon можно задавать в качестве переменных вкладки Parameters для вставки в исходный код блока Engee Function. Если имя параметра и глобальной переменной совпадают, то значение параметра будет автоматически подставляться из глобальной переменной.

Чтобы задать параметр шины, задайте для параметра Value значение шины в виде именованного кортежа, например (s1 = 5, s2 = 4).


Важно различать параметры блока, которые задаются во вкладке Parameters и являются глобальными переменными в исходном коде Engee Function, от глобальных переменных Engee в окне переменных variables icon. Хотя в параметрах блока и можно использовать значения и имена глобальных переменных Engee, эти сущности абсолютно различны. Нельзя управлять глобальными переменными Engee через параметры блока или из его исходного кода. Однако, для исходного кода параметры блока (из вкладки Parameters) являются глобальными переменными и могут быть использованы в любой его части без привязки к конкретной функции или блоку кода.

Рассмотрим случай, когда глобальные переменные полностью соответствуют переменным в исходном коде. Например, были заданы три глобальные переменные , , со значениями 1, 2, 3 соответственно. Все три глобальные переменные используются в качестве параметров блока Engee Function:

engee function param explain 1

Тогда исходный код с добавлением параметров будет выглядеть так:

struct Block <: AbstractCausalComponent
    a::Real
    b::Real
    c::Real

    function Block()
        new(a_param, b_param, c_param)
    end
end

function (c::Block)(t::Real, x::Vector{<:Real})
    return (c.a .* x .+ c.b) ./ c.c
end

Этот исходный код определяет структуру Block, что позволяет настраивать поведение компонента. В примере используются имена параметров блока a_param, b_param, и c_param, чтобы задать параметры структуры , , и соответственно. В коде также определяется метод function(c::Block)(t::Real, x::Vector{<:Real}), который масштабирует каждый элемент вектора x на параметр блока , добавляет и делит результат на . Это позволяет гибко изменять и нормализовать вектор x в соответствии со значениями параметров блока.


Рассмотрим случай, где используются только параметры вкладки Parameters:

struct Block <: AbstractCausalComponent end

function (c::Block)(t::Real, x::Vector{<:Real})
    return (a_param .* x .+ b_param) ./ c_param
end

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


Рассмотрим случай, когда параметры не полностью соответствуют исходному коду. Например, есть параметр a_param, равный 100. В исходном коде есть поле структуры :

struct Block <: AbstractCausalComponent
    a::Real

    function Block()
        new(a_param/10)
    end
end

function (c::Block)(t::Real, x::Vector{<:Real})
    c.a
end

В этом коде параметр a_param используется для инициализации поля структуры struct Block через ее конструктор, который делит значение параметра на 10. В данном случае в функторе блока возвращается поле .


Переменные можно сделать глобальными прямо в исходном коде:

a = 1;
b = 2;
с = 3;

struct Block <: AbstractCausalComponent; end

function (c::Block)(t::Real, x::Vector{<:Real})
    return (a .* x .+ b) ./ c
end

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


Если вы не хотите использовать параметры из вкладки Parameters, то можно инициализировать переменные прямо в исходном коде:

struct Block <: AbstractCausalComponent; end

function (c::Block)(t::Real, x)
    a = 1;
    b = 2;
    c = 3;
    return (a .* x .+ b) ./ c
end

В коде создается структура Block и определяет функцию, которая выполняет математические операции с локальными переменными , и , применяя их к переменной x.

Рассмотрим самый эффективный и правильный способ выполнения. Чтобы блок Engee Function работал корректно, установите флажок для опции Use external cache for non-scalar output на вкладке Main блока Engee Function. Исходный код будет выглядеть так:

struct Block{Ta, Tb, Tc} <: AbstractCausalComponent
    a::Ta
    b::Tb
    c::Tc

    function Block()
        Ta = typeof(a_param); Tb = typeof(b_param); Tc = typeof(c_param)
        all(isreal, (a_param, b_param, c_param)) ||
          error("Параметры блока должны быть вещественными")
        all(x->isempty(size(x)), (a_param, b_param, c_param)) ||
          error("Параметры блока должны быть скалярами")
        new{Ta, Tb, Tc}(a_param, b_param, c_param)
    end
end

function (c::Block)(t::Real, cache::Vector{<:Real}, x::Vector{<:Real})
    cache .= (c.a .* x .+ c.b) ./ c.c
    nothing
end
Подобный код можно написать только в ячейке Common code, так как стандартные ячейки не позволяют редактировать определение структуры компонента и сигнатуру функтора.

Поля , и структуры представляют собой параметры блока, которые строго проверяются на типы в конструкторе. Каждый из этих параметров должен быть вещественным скаляром (Real), что обеспечивает точность вычислений во время выполнения.

Конструктор Block() проверяет типы переданных параметров , и . Если хотя бы один из них не является вещественным скаляром или имеет несоответствующие размерности (должны быть скалярами), конструктор генерирует ошибку с соответствующим сообщением. После проверки конструктор инициализирует поля структуры значениями , и заданных типов Ta, Tb и Tc.

Вычисляемая функция, определенная для экземпляров Block, принимает время t, внешний кэш и вектор x вещественных чисел. В этой функции используются поля , и структуры Block для вычисления значений, которые затем записываются в cache. Это позволяет избежать лишних выделений памяти путем повторного использования предоставленного кэша.

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


Если вы хотите менять параметры блока, то необходимо использовать mutable структуру. Рассмотрим пример:

mutable struct Counter{T} <: AbstractCausalComponent
    limit::T
    iter::T

    function Counter()
      isempty(size(limit)) || error("Предел блока $BLOCK_NAME должен быть скаляром")
      isreal(limit) || error("Предел блока $BLOCK_NAME должен быть вещественным числом")
      T = typeof(limit)
      iter = zero(T)
      new{T}(limit, iter)
    end
end

function (c::Counter)(t::Real)
    return c.iter
end

function update!(c::Counter, t::Real)
  c.iter += 1
  if c.iter > c.limit
    c.iter = zero(c.iter)
  end
  c
end

Структура Counter — это mutable тип данных, который используется для подсчета итераций с заданным пределом и является строго типизированной. Поля limit и iter структуры представляют собой параметры блока:

  • limit — это предельное значение счетчика;

  • iter — текущее значение счетчика.

В конструкторе структуры выполняется проверка (валидация) параметра limit на то, является ли параметр скаляром и вещественным типом данных. После чего инициализируется поле iter с нулевым значением соответствующего типа T. Функция update! обновляет состояние счетчика , увеличивая значение iter на единицу при каждом вызове. Если текущее значение iter превышает limit, то оно обнуляется и позволяет счетчику циклически возвращаться к начальному состоянию.

В исходном коде блока Engee Function можно использовать include, ссылающийся на внешний код. Это позволяет инициализировать переменные из внешнего кода (при их наличии) в исходном коде.
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока.

Пример кода

В данном примере представлена упрощенная реализация блока Discrete-Time Integrator, основанная на интеграции кода Julia в модель Engee. В качестве метода интегрирования выбран прямой метод Эйлера:

mutable struct Integrator{T} <: AbstractCausalComponent
    const dt::Float64
    state::T
    gain::Float64

    function Integrator()
        dt = OUTPUT_SIGNAL_ATTRIBUTES[1].sample_time.period
        state = initial_condition
        gain = k
        new{typeof(state)}(dt, state, gain)
    end
end

(c::Integrator)(t::Real, x) = c.state

function update!(c::Integrator, t::Real, x)
    c.state += x * c.dt * c.gain
    return c
end

Параметры initial_condition и k инициализируются во вкладке Parameters настроек блока Engee Function.

На первом шаге симуляции модели внутреннее состояние блока c.state инициализируется значением параметра initial_condition.

После чего на каждом шаге расчета блок возвращает внутреннее состояние c.state в качестве выходного сигнала и пересчитывает его значение в методе update!.

Аннотации

Аннотации в Engee Function позволяют отображать параметры блока прямо под его названием в модели. Для их добавления откройте окно настроек debug article icon 1 блока Engee Function и перейдите на вкладку «Аннотация». Выберите нужные маркеры свойств блока и добавьте их в текстовый редактор.

engee function annotations

На этой вкладке:

  • Слева отображается список доступных параметров (кроме скрытых).

  • Справа находится текстовый редактор, где можно задать аннотацию с маркерами в формате %<ИмяПараметра>.

  • Перенос параметров возможен вручную, через автодополнение или с помощью кнопки engee function annotations 1.

После выхода из редактора (например, при клике вне его) аннотация применяется: маркеры автоматически заменяются на фактические значения параметров, и итоговый текст отображается под названием блока (или над ним, если название размещено сверху).

Для удаления аннотаций необходимо удалить соответствующий маркер в редакторе.

Доступные маркеры

Маркер свойств автоматически заменяется на актуальное значение параметра. Доступны следующие маркеры:

  • Порты: %<Inputs>, %<Outputs> — количество входных и выходных портов; %<InputPort1Type>, %<OutputPort1Type>, %<InputPort1Size>, %<OutputPort1Size> — тип данных и размерность сигналов.

  • Временные характеристики: %<SampleTime> — дискретность; %<SampleTimeInheritanceMethod> — метод наследования дискретности.

  • Кодовые блоки: %<ComponentStructCode>, %<StepMethodCode> — код структуры и метода шага.

  • Параметры: %<Parameters>, %<Parameter1Name>, %<Parameter1Value> — названия и значения параметров.

  • Флаги включения: %<DefineComponentStruct>, %<UseCommonCode>, %<DefineStepMethod>, %<DefineUpdateMethod>, %<DefineTerminateMethod> — включение соответствующих секций кода.

  • Методы переопределения: %<OverrideTypesInhMethod>, %<OverrideDimsInhMethod>, %<OverrideSampleTimeInhMethod> — настройки наследования типов.

  • Прочее: %<UseExternalCache> — использование внешнего кэша.