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

Engee Function

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

Тип: EngeeFunction

Путь в библиотеке:

/Basic/User-Defined Functions/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

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

  • 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), состоящий из нулей, и умножается на элемент параметра, заданного в настройках блока в разделе Настройка параметров, после чего берется его размерность.

  • 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 — начало симуляции из настроек модели.

  • STOP_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

struct Block <: AbstractCausalComponent end

function (c::Block)(t::Real, x1, x2)
    y1 = [START_TIME, STOP_TIME]
    y2 = collect(INPUT_SIGNAL_ATTRIBUTES[1].dimensions)
    y3 = OUTPUT_SIGNAL_ATTRIBUTES[1].dims[1]
    y4 = (INPUT_SIGNAL_ATTRIBUTES[2].type == Int64)
    y5 = (OUTPUT_SIGNAL_ATTRIBUTES[4].tp == Bool)
    y6 = INPUT_SIGNAL_ATTRIBUTES[1].sample_time.period
    y7 = OUTPUT_SIGNAL_ATTRIBUTES[1].st.p
    y8 = INPUT_SIGNAL_ATTRIBUTES[1].sample_time.offset
    y9 = OUTPUT_SIGNAL_ATTRIBUTES[2].st.o
    y10 = INPUT_SIGNAL_ATTRIBUTES[1].direct_feedthrough
    y11 = INPUT_SIGNAL_ATTRIBUTES[2].df
    return (y1, y2, y3, y4, y5, y6, y7, y8, y9, y10, y11)
end

Работа с фиксированной точкой и пользовательскими шинами

Блок Engee Function, а также командная строка img 41 1 2 Engee, поддерживают работу с фиксированной точкой (Fixed) и пользовательскими типами шин (BusSignal). Эти конструкции помогают управлять поведением операций при работе с целыми, вещественными, фиксированными и комплексными типами данных.

Типы и функции фиксированной точки (Fixed-Point)

В статье Арифметика с фиксированной точкой (Fixed-Point) в Engee описывается работа с фиксированной точкой в Engee. Приведенные в статье конструкции справедливы и для блока Engee Function, так в блоке поддерживаются:

  • FixedPoint — абстрактный тип всех фиксированных чисел;

  • Fixed — конкретный тип фиксированного числа, создаётся вручную с указанием битового представления;

  • fi(…​) — создание значения типа Fixed с помощью числового значения и параметров представления;

  • fixdt(…​) — создание типа Fixed с указанием знака, ширины и количества дробных битов.

Например:

a = fi(5, 1, 16, 4)     # Знаковое число, 16 бит, 4 дробных
b = fi(10, 0, 8, 0)     # Беззнаковое целое 8 бит
T = fixdt(1, 24, 8)     # Тип Fixed с 24 битами, 8 из них — дробные
c = Fixed(0x01ff, T)    # Создание фиксированного числа напрямую по битовому представлению

Фиксированные числа в Engee Function можно:

Работа с пользовательскими типами шин (BusSignal)

Пользовательские типы шин позволяют задать типы входных и выходных сигналов в виде структурированных кортежей (NamedTuple) с описанием имен, типов и размерностей внутри Engee Function. Доступные функции:

  • BusSignal{...} — тип шины с именами, типами и размерностями;

  • get_bus_names(type) — получить список имён сигналов;

  • get_bus_types(type) — получить типы сигналов;

  • get_bus_dimensions(type) — получить размерности сигналов;

  • get_names_types_dims(type) — получить всё сразу;

  • get_bus_signal_type(::NamedTuple) — определить тип BusSignal по значению.

Пример определения и анализа типа шины:

bus = (a = 1, b = [2.0, 3.0], c = (x = 3, y = 4))
bus_type = get_bus_signal_type(bus)

get_bus_names(bus_type)       # => (:a, :b, :c)
get_bus_types(bus_type)       # => (Int64, Vector{Float64}, NamedTuple{...})
get_bus_dimensions(bus_type)  # => ((), (2,), ((), ()))

Можно явно описывать шины:

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

Также поддерживаются вложенные шины:

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

Использование в блоке Engee Function

Типы Fixed и BusSignal можно использовать в различных частях блока Engee Function:

  • В параметрах блока — можно задать значения фиксированной точки через fi(…​), а также передать структуры шины как NamedTuple. Тип шины при этом можно определить автоматически через get_bus_signal_type(…​).

  • В ячейке Common code — можно определить типы данных (Fixed, BusSignal{…​}), структуры компонента (struct Block), создать вспомогательные функции или инициализировать значения. Также сюда можно перенести основную логику, если отключены Step method code или Component struct code.

  • В ячейке Component struct code — при описании полей структуры можно использовать значения типа Fixed, а также типы BusSignal, если поля представляют собой составные сигналы.

  • В ячейке Step method code — реализуется основная логика блока. Здесь фиксированные числа и сигналы шин могут участвовать в вычислениях, сравнениях и обработке структур.

  • В ячейке Types inheritance method — можно использовать типы Fixed и BusSignal для анализа входов и задания типа выхода компонента.

  • В ячейке Dimensions inheritance method — можно использовать данные Fixed и значения из шин, чтобы определить размерности выходных сигналов.

  • В ячейке Update method code — если блок имеет внутреннее состояние, поля типа Fixed или BusSignal можно использовать для хранения или изменения этого состояния на каждом шаге симуляции.

  • В ячейке Terminate method code — допускается использование этих типов для записи итогового состояния, если структура содержит соответствующие поля.

  • В ячейке Common types and dimensions inheritance method — если требуется одновременно обрабатывать и типы, и размерности, Fixed и BusSignal также могут участвовать в логике расчёта.

Следовательно, Fixed, fi(…​), fixdt(…​), BusSignal и get_bus функции применимы во всех аспектах конфигурации и работы блока Engee Function — как на этапе выполнения, так и на этапе генерации типа и размерности сигнала. Их можно свободно использовать как в параметрах, так и в исходном коде во всех ячейках (при корректном соблюдении их работы).

Функции преобразования и типизированные арифметические операции: econvert, esum, emul, ediv

В блоке Engee Function, а также в командной строке img 41 1 2 Engee доступны функции, которые позволяют выполнять арифметические операции с указанием выходного типа, преобразовывать значения между типами с контролем округления и переполнения, а также заранее определять тип результата с помощью специализированных правил (_promote_type). Эти функции используются в логике Engee Function, написанной в ячейках исходного кода Step method code, Common code и других.

Доступные функции:

  • econvert — преобразование значений между типами;

  • esum, esub, emul, ediv — арифметические операции с заданием выходного типа;

  • emul! — матричное умножение с кэшированием результата;

  • *_promote_type — функции выведения результата по входным типам.

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

Поддерживаемые типы

Все функции из этого набора работают со следующими типами:

  • Вещественные: Float16, Float32, Float64

  • Целочисленные: Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128

  • Логические: Bool

  • Фиксированные: Fixed (создаются через fi(…​) или fixdt(…​))

  • Комплексные: Complex{T}, где T — любой из указанных выше типов

Функции можно использовать также с массивами и векторизированными выражениями, с применением точечного синтаксиса (.). Далее рассмотрим функции подробнее.

Преобразование чисел с помощью econvert

Функция econvert позволяет привести значение к заданному типу, задав метод округления и способ обработки переполнения:

econvert(type::Type{T1}, var::T2; rounding_mode::RoundingMode=RoundDown, overflow::Bool=true)

Здесь:

  • type — тип, в который нужно преобразовать значение. Если var — комплексное число, указывается базовый вещественный тип. Например, econvert(Float32, Complex{Int16}(1 + 2im)) вернет Complex{Float32};

  • var — значение для преобразования;

  • rounding_mode — метод округления (RoundDown, RoundUp, RoundNearest, RoundNearestTiesAway, RoundToZero);

  • overflow — если true, то используется поведение с переполнением (wrap); если false, включается насыщение (saturation) — значение ограничивается диапазоном типа.

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

Арифметика с контролем типа с помощью esum, esub, emul, ediv, emul!

Функции типизированных арифметических операций esum, esub, emul, ediv, emul! имеют одинаковый интерфейс:

esum(x1, x2, out_type, overflow=true, rm=RoundDown)
esub(x1, x2, out_type, overflow=true, rm=RoundDown)
emul(x1, x2, out_type, overflow=true, rm=RoundDown)
ediv(x1, x2, out_type, overflow=true, rm=RoundDown)
emul!(cache, x1, x2, out_type, overflow=true, rm=RoundDown)

Аргументы:

  • x1, x2 — аргументы операции (можно скаляры или массивы);

  • out_type — тип, в котором производится и возвращается результат;

  • overflow — включение переполнения (true) или насыщения (false);

  • rm — метод округления (см. список выше).

Функции esum, esub, emul, ediv возвращают значение, соответствующее указанному out_type.

Функция emul! используется для ускорения при работе с матрицами: она не создает новый массив, а записывает результат в уже выделенный cache. Важно, чтобы eltype(cache) совпадал с out_type.

Результат зависит от типа out_type: он влияет не только на финальный результат, но и на поведение промежуточных вычислений — что особенно важно для Fixed и Complex.

Выведение типа результата с помощью *_promote_type

Если параметры блока (например, SumType или MulType) заданы как строка "Inherit", тип результата определяется автоматически на основе типов входных данных с помощью функций *_promote_type.

Если требуется вычислить тип результата какой-либо операции, то воспользуйтесь перечисленными функциями:

  • Определяет выходной тип при сложении n значений одного типа T:

    sum_promote_type(::Type{T}, n::Integer=1, hdlsetup::Bool=false)
  • Определяет выходной тип при сложении двух разных типов T1 и T2 по n значений:

    sum_promote_type(::Type{T1}, ::Type{T2}, n::Integer=1, hdlsetup::Bool=false)
  • Определяет выходной тип при сложении произвольного количества аргументов с разными типами:

    sum_promote_type_from_inputs(types::Type...; hdlsetup::Bool=false)
  • Определяет тип аккумулятора при сложении n значений одного типа T:

    sum_accumulator_promote_type(::Type{T}, n::Integer=1, hdlsetup::Bool=false)
  • Определяет тип аккумулятора при сложении двух разных типов T1 и T2 по n значений:

    sum_accumulator_promote_type(::Type{T1}, ::Type{T2}, n::Integer=1, hdlsetup::Bool=false)
  • Определяет тип аккумулятора при сложении произвольного количества аргументов с разными типами:

    sum_accumulator_promote_type_from_inputs(types::Type...; hdlsetup::Bool=false)
  • Определяет выходной тип при умножении значений одного типа T:

    mul_promote_type(::Type{T}, hdlsetup::Bool=false)
  • Определяет выходной тип при умножении двух различных типов T1 и T2:

    mul_promote_type(::Type{T1}, ::Type{T2}, hdlsetup::Bool=false)
  • Определяет выходной тип при делении одного значения типа T:

    div_promote_type(::Type{T}, hdlsetup::Bool=false)
  • Определяет выходной тип при делении значения типа T1 на значение типа T2:

    div_promote_type(::Type{T1}, ::Type{T2}, hdlsetup::Bool=false)

    Здесь:

    • hdlsetup — логический параметр, который определяет, нужно ли учитывать специфику аппаратной платформы (TargetHardware). По умолчанию:

      hdlsetup = TargetHardware == "C" ? false : true

Функции удобно использовать в ячейке Types inheritance method для точного определения выходного типа блока на основе входов и параметров.

Пример использования

Ниже пример компонента, реализующего выражение a*x + b. Типы промежуточного умножения и финального сложения задаются через параметры MulType и SumType. Если указано "Inherit", то тип выводится автоматически.

Пример использования `a*x + b`

engee function example 3

Параметры Engee Function:

a = fi(5, 1, 16, 4)
b = fi(11, 1, 24, 7)
MulType = "Inherit"
SumType = "Inherit"

Код блока (в ячейке Common code, Dimensions inheritance method и Types inheritance method):

  • Ячейка Common code:

    struct Block{SumType, MulType, aType, bType} <: AbstractCausalComponent
        a::aType
        b::bType
        function Block()
            sum_type = OUTPUT_SIGNAL_ATTRIBUTES[1].type
            mul_type = if MulType == "Inherit"
                mul_promote_type(INPUT_SIGNAL_ATTRIBUTES[1].type, eltype(a))
            else
                MulType
            end
            new{sum_type, mul_type, typeof(a), typeof(b)}(a, b)
        end
    end
  • Ячейка Dimensions inheritance method:

    function propagate_dimensions(inputs_dimensions::Vector{<:Dimensions})::Vector{<:Dimensions}
        input_dimensions = first(inputs_dimensions)
        mock_input = zeros(input_dimensions)
        mock_output = mock_input .* a .* b
        return [size(mock_output)]
    end
  • Ячейка Types inheritance method:

    function propagate_types(inputs_types::Vector{DataType})::Vector{DataType}
        mul_type = if MulType == "Inherit"
            mul_promote_type(inputs_types[1], eltype(a))
        else
            MulType
        end
        sum_type = if SumType == "Inherit"
            sum_promote_type(mul_type, eltype(b))
        else
            SumType
        end
        return [sum_type]
    end

Таким образом, функции econvert, esum, emul, ediv и правила promote_type позволяют управлять типами и поведением вычислений внутри блока Engee Function. Они поддерживают все основные числовые типы, включая фиксированную точку и комплексные значения, и могут быть использованы в вычислениях, сравнении, логике наследования и настройке поведения компонентов с помощью блока Engee Function.

Диагностические функции warning, stop_simulation, pause_simulation, info

В исходном коде блока Engee Function также доступны функции warning, stop_simulation, pause_simulation и info, которые позволяют взаимодействовать с системой диагностики модели на этапе симуляции, позволяя выводить сообщения в окне диагностики model diagnosis main. Эти функции могут использоваться внутри следующих ячеек исходного кода:

  • Component struct code

  • Step method code

  • Update method code

  • Terminate method code

  • Common code

Функции warning, stop_simulation, pause_simulation, info можно использовать только в тех участках кода, которые выполняются во время симуляции модели.

Так, хотя эти функции формально допустимы в ячейке Component struct code, на практике их размещение там не имеет смысла: эта ячейка предназначена исключительно для описания структуры блока, а не для исполняемой логики. Чтобы диагностические функции работали корректно, их необходимо размещать внутри поддерживаемых методов симуляции: Step method, Update method, Terminate method, либо в Common code, если там переопределяется соответствующий метод. Пример корректного размещения:

  • Component struct code:

    #
    struct Block <: AbstractCausalComponent
        g::Real
        function Block()
            new(gain)
        end
    end
  • Common code, функтор function (c::Block)(t::Real, in1) взят с Step method code, сам метод отключен:

    function (c::Block)(t::Real, in1)
        if t == 5.0
            warning("time == $t")
        end
        return c.g .* in1
    end

По аналогии можно переопределить любой поддерживаемый метод, вызываемый во время симуляции (например, update!, terminate, step), в ячейке Common code вручную — если отключить соответствующую стандартную ячейку (например, Define update method, Define step method и др.).

Для корректной работы в ячейке Common code функции warning, stop_simulation, pause_simulation и info могут использоваться:

  • Внутри функций, переопределяющих поведение методов, отвечающих за выполнение модели — таких как step, update!, terminate!. Например, если функтор, обычно задаваемый в ячейке Step method code, определен в Common code, то диагностические функции внутри него будут работать корректно.

  • Внутри вспомогательных функций, которые вызываются из методов, отвечающих за выполнение модели. То есть если вспомогательная функция определена в Common code и используется, например, в step, то вызовы диагностических функций внутри нее также будут выполняться корректно.

    Переопределение update! или других методов не заменяет обязательную реализацию step (function (c::Block)(t, in…​)). Engee требует наличие этой функции как точки входа в симуляцию.

Пример корректного переопределения update! в Common code:

# Структура с Component struct code (если отключена, то должна быть обязательно вынесена в Common code)
struct Block <: AbstractCausalComponent
    g::Real
    function Block()
        new(gain)
    end
end

# Step method (обязательный метод, вызываемый на каждом шаге симуляции)
function (c::Block)(t::Real, in1)
    c = update!(c, t, in1)
    return c.g .* in1
end

# Переопределенный метод update!
function update!(c::Block, t::Real, in1)
    if t == 5.0
        info("update triggered at t = $t")  # диагностическое сообщение
    end
    return c
end

В итоге будут получены следующие сообщения в окне диагностики модели:

engee function continue 1


Далее подробнее рассмотрим сами диагностические функции:

  • warning — функция warning(msg::String) выводит предупреждающее сообщение. Симуляция при этом продолжается. Это может быть полезно для указания на некритичные проблемы или состояния, которые требуют внимания, но не останавливают выполнение. Пример:

    if t == 5.0 || t == 7.5
        warning("time == $t")
    end
  • stop_simulation — функция stop_simulation(msg::String) немедленно завершает симуляцию и выводит сообщение о завершении. Используется для указания на критическое условие, при котором продолжение моделирования невозможно или нежелательно. Пример:

    if t == 5.0
        stop_simulation("time == $t")
    end
  • pause_simulation — функция pause_simulation(msg::String) приостанавливает выполнение симуляции и отображает указанное сообщение о паузе. Возобновление симуляции возможно вручную с помощью кнопки Продолжить:

    engee function continue

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

    if t == 5.0
        pause_simulation("time == $t")
    end
  • info — функция info(msg::String) выводит информационное сообщение. Используется для отображения промежуточных значений без влияния на выполнение симуляции. Пример:

    if t == 5.0 || t == 7.5
        info("time == $t")
    end

Порты

Вход

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, ссылающийся на внешний код. Это позволяет инициализировать переменные из внешнего кода (при их наличии) в исходном коде.
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока.

Пример кода

В данном примере представлена упрощенная реализация блока Дискретный интегратор, основанная на интеграции кода Julia в модель Engee. В качестве метода интегрирования выбран прямой метод Эйлера. На вкладке Advanced блока Engee Function установите значение Discrete у параметра Sample time inheritance method. Далее заполните ячейки исходного кода следующим образом:

  • В ячейке Common code:

    mutable struct Block{T} <: AbstractCausalComponent
        const dt::Float64
        state::T
        gain::Float64
    
        function Block()
            dt = OUTPUT_SIGNAL_ATTRIBUTES[1].sample_time.period
            state = initial_condition
            gain = k
            new{typeof(state)}(dt, state, gain)
        end
    end
  • В ячейке Step method code:

        return c.state
  • В ячейке Update method code:

        c.state += in1 * c.dt * c.gain
        return c

В итоге будет получен следующий исходный код:

engee function example 1

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

engee function example 2

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

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

Структура компонента переопределяется в ячейке Common code, а не в Component struct code, так как требуется более гибкое определение: структура должна быть изменяемой (mutable) и параметризованной типом T, соответствующим типу состояния. Стандартное определение в Component struct code подходит только для неизменяемых (immutable) и непараметризованных структур.

Продвинутое использование

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

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

  • Преобразование входных данных в вектор с переопределением выходных параметров;

  • Использование кэширования и строго типизированных структур для повышения производительности;

  • Реализация блоков без прямой передачи (Direct feedthrough) для разрыва алгебраических петель;

  • Задание пользовательского периода дискретизации выходного сигнала.

Аннотации

Аннотации в 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> — использование внешнего кэша.