Документация 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const SampleTime = NamedTuple{(:period, :offset, :mode), Tuple{Rational{Int64}, Rational{Int64}, Symbol}}
    Код структуры шага расчета const SampleTime и функции propagate_sample_times не будут автоматически добавлены в поле InhMethodsCode старых моделей Engee. Для доработки старых моделей добавьте структуру шага расчета и функцию самостоятельно.

    Пример переопределения функции 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 — определяет параметр как переменную
скаляр | вектор | массив

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

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

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

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

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


Важно различать параметры блока, которые задаются во вкладке 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

Поля , и структуры представляют собой параметры блока, которые строго проверяются на типы в конструкторе. Каждый из этих параметров должен быть вещественным скаляром (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!.