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

Engee Function

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

engee function

Описание

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

Подробнее о языке Julia смотрите здесь.

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

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

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

  • Нажать на кнопку Редактировать исходный код, после чего будет открыт редактор с примером минимально необходимого исходного кода:

Редактор исходного кода блока Engee Function

Во вкладке Executable code (ExeCode) редактора исходного кода определяется объект с методом вызова (такие объекты называются функторами, подробнее здесь). Для этого:

  1. Определяется структура, наследуемая от типа AbstractCausalComponent.

  2. В структуре перечисляются имена и поля. Данный конструктор не должен принимать аргументы.

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

  4. Если блок имеет внутренние состояния, для обновления их значений в ходе симуляции необходимо определить метод update!(). Первым аргументом в него передается экземпляр структуры, вторым – время симуляции, после чего перечисляются переменные, соответствующие входным сигналам блока.

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

include("/user/engeefunction/source.jl")

Во вкладке InhMethodsCode (InheritMethodsCode) редактора исходного кода определяются методы наследования атрибутов сигналов и управление параметрами Direct feedthrough.

Написанные функции наследования используются только при проставлении флажка переопределения соответствующего метода. В ином случае они игнорируются.

На данный момент поддерживается переопределение таких методов как:

  • Наследование размерностей сигналов (propagate_dimensions).

  • Наследование типов сигналов (propagate_types).

  • Размыкание петель входных портов (direct_feedthrough).


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

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

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

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

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

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

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

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

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

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

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

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

  • Direct feedthrough — определяет прямое сквозное соединение:

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

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

Выход

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

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

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

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

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

Параметры

Основные

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

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

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

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

Override type inheritance method — включение или отключение метода наследования типов
выключено (по умолчанию) | включено

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

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

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

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

Пример:

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

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

Override dimensions inheritance method — включение или отключение метода наследования размерностей
выключено (по умолчанию) | включено

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

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

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

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

Пример:

function propagate_dimensions(inputs_dimensions::Vector{<:Dimensions})::Vector{<:Dimensions}
    input_dimensions = first(inputs_dimensions)
    mock_input = zeros(input_dimensions)
    mock_output = mock_input .* gain
    return [size(mock_output)]
end

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

Override direct feedthrough setting method — определяет прямое сквозное соединение
выключено (по умолчанию) | включено

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

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

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

Пример:

function direct_feedthroughs()::Vector{Bool}
    if gain == 2
        return [false]
    else
        return [true]
    end
end
Use external cache for non-scalar output — использовать внешний кэш для нескалярных выходных сигналов
выключено (по умолчанию) | включено

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

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

  • Если флажок установлен — сигнал сможет принимать еще один аргумент cache, который нужно внести в ExeCode. В коде требуется написание функторов в зависимости от размерности выходного сигнала:

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

      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 | Custom

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

Способ наследования Default всегда используется в случае, когда блок Engee Function не является дискретным или непрерывным.
  • Default — способ наследования шага расчета по умолчанию, когда значение параметра Sample time (-1 for inherited) равно -1. Способ получает любой вид шага расчета. При выборе этого способа блок Engee Function будет наследовать шаг расчета по следующим принципам:

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

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

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

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

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

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

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

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

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

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

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

  • Custom — способ самостоятельного переопределения наследования шага расчета. Для работы способа необходимо зайти в редактор кода блока Engee Function (Редактировать исходный код) во вкладку InhMethodsCode и найти строку функции propagate_sample_times и вручную задать нужный шаг расчета. По умолчанию:

    function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime
        return first(inputs_sample_times)
    end

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

    # Функция, возвращающая время дискретизации блока.
    # Используется только в режиме наследования `Custom`.
    # Параметр fixed_solver говорит о том, используется решатель
    # с постоянным шагом (true) или с переменным (false).
    const SampleTime = NamedTuple{(:period, :offset, :mode), Tuple{Rational{Int64}, Rational{Int64}, Symbol}}

    Пример переопределения функции 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), то такой шаг расчета будет воспринят как дискретный с шагом решателя.
Sample time (−1 for inherited) — интервал между шагами расчета
−1 (по умолчанию)

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

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

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

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

Parameter — определяет параметр как переменную
2 (по умолчанию)

Определяет параметр как переменную для использования в исходном коде. Значение и имя параметра могут быть изменены. Имя первого параметра по умолчанию — gain. Новые параметры называются parameter2 и далее по возрастанию. Значение новых параметров равно нулю.

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

Пример кода

В данном примере представлена упрощенная реализация блока 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!.