Engee Function
Использование кода Julia в моделях.
Тип: EngeeFunction
Путь в библиотеке:
|
Описание
Блок Engee Function позволяет использовать код на языке Julia в моделях Engee.
Подробнее про язык программирования Julia читайте на странице Программирование. |
В блоке Engee Function допустимо использовать большую часть возможностей языка Julia. При этом использование в блоке пакетного менеджера Pkg не предусматривается. |
Использование
Для интеграции кода на Julia в модель Engee необходимо:
-
Добавить в модель блок Engee Function из раздела Базовые/Пользовательские функции библиотеки блоков
;
-
В окне настроек
на вкладке Main блока Engee Function нажать на кнопку Редактировать исходный код для открытия редактора исходного кода (EngeeFunctionCode):
Ячейки исходного кода
Редактор исходного кода EngeeFunctionCode состоит из функциональных ячеек с кодом Julia. По умолчанию доступны три ячейки: вспомогательная (нередактируемая), Component struct code и Step method code (ячейки можно скрыть):
Для подключения дополнительных файлов исходного кода можно использовать функцию
|
Весь код блока Engee Function можно написать в ячейке Common code, получив полный контроль над структурой компонента, сигнатурами и количеством функций. |
Для добавления/удаления других функциональных ячеек нажмите на кнопку «Управление методами» и установите/уберите флажки на нужных ячейках:
→
Каждая ячейка отвечает за уникальный функционал блока Engee Function. Рассмотрим их подробнее:
-
Информационная ячейка (нередактируемая) — автоматически отображает переменные блока Engee Function, атрибуты входных и выходных сигналов (размерность, тип, дискретность) и другие параметры, заданные пользователем. Ее содержимое обновляется динамически в зависимости от настроек блока. Ячейка всегда активна, но не выбирается в меню управления методами
. Имеет полупрозрачный текст, которым отображаются подсказки.
Изменение параметров блока влияет не только на содержимое информационной ячейки, но и на подсказки в других ячейках, которые также отображаются полупрозрачным текстом. -
Define component struct — добавляет ячейку Component struct code, в которой задается структура компонента блока Engee Function (наследуемая от типа
AbstractCausalComponent
). Поля структуры определяются между нередактируемыми строкамиstruct Block <: AbstractCausalComponent
иend
. По умолчанию создается параметрg
, инициализируемый значением блокаgain
, а конструкторBlock()
не принимает аргументов.
-
Use common code — добавляет ячейку Common code, в которой пишется код в свободной форме. По умолчанию ячейка пуста. Например, если стандартное объявление структуры в Component struct code не подходит (из-за нередактируемости
struct Block <: AbstractCausalComponent
), то можно отключить Define component struct, чтобы удалить ячейку, и определить структуру компонента вручную в Common code. То же самое касается любых функций из меню управления методами— вместо стандартных ячеек можно писать свой код в 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
. -
Define update method — добавляет ячейку Update method code, в которой метод
update!
обновляет внутреннее состояние блока Engee Function на каждом шаге симуляции. Первый аргументc::Block
— структура блока, второйt::Real
— время симуляции, далее передаются входные сигналы. Если блок не имеет внутреннего состояния, то метод может оставаться пустым и просто возвращатьc
.Если требуется определить несколько методов update!
или задать метод с другой сигнатурой, то ячейку Update method code можно отключить и написать код в ячейке Common code. Компилятор автоматически определит наличие методаupdate!
и использует его для симуляции. -
Define terminate method code — добавляет ячейку Terminate method code, которая выполняется при завершении симуляции блока Engee Function (с помощью метода
terminate!
). Первый аргументc::Block
— структура блока. По умолчанию метод не выполняет дополнительных действий и просто возвращаетc
. -
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 со всеми константами и функциями обращения:
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, а также командная строка 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 можно:
-
Передавать как параметры;
-
Использовать в структуре компонента (ячейка Component struct code);
-
Выполнять с ними арифметику через
emul
,esum
,ediv
в ячейке Step method code (подробнее см. в разделе Функции преобразования и типизированные арифметические операции:econvert
,esum
,emul
,ediv
); -
Применять при расчетах в
propagate_types(…)
,propagate_dimensions(…)
.
Работа с пользовательскими типами шин (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, а также в командной строке 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:
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
, которые позволяют взаимодействовать с системой диагностики модели на этапе симуляции, позволяя выводить сообщения в окне диагностики . Эти функции могут использоваться внутри следующих ячеек исходного кода:
-
Component struct code
-
Step method code
-
Update method code
-
Terminate method code
-
Common code
Функции Так, хотя эти функции формально допустимы в ячейке Component struct code, на практике их размещение там не имеет смысла: эта ячейка предназначена исключительно для описания структуры блока, а не для исполняемой логики. Чтобы диагностические функции работали корректно, их необходимо размещать внутри поддерживаемых методов симуляции: Step method, Update method, Terminate method, либо в Common code, если там переопределяется соответствующий метод. Пример корректного размещения:
|
По аналогии можно переопределить любой поддерживаемый метод, вызываемый во время симуляции (например, 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
В итоге будут получены следующие сообщения в окне диагностики модели:
Далее подробнее рассмотрим сами диагностические функции:
-
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)
приостанавливает выполнение симуляции и отображает указанное сообщение о паузе. Возобновление симуляции возможно вручную с помощью кнопки Продолжить:Эта функция может быть полезна для анализа состояния модели в интересующий момент времени. Пример:
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 можно задавать в качестве переменных вкладки Parameters для вставки в исходный код блока Engee Function. Если имя параметра и глобальной переменной совпадают, то значение параметра будет автоматически подставляться из глобальной переменной.
Чтобы задать параметр шины, задайте для параметра Value
значение шины в виде именованного кортежа, например (s1 = 5, s2 = 4)
.
Важно различать параметры блока, которые задаются во вкладке Parameters и являются глобальными переменными в исходном коде Engee Function, от глобальных переменных Engee в окне переменных ![]() |
Рассмотрим случай, когда глобальные переменные полностью соответствуют переменным в исходном коде. Например, были заданы три глобальные переменные , , со значениями 1
, 2
, 3
соответственно. Все три глобальные переменные используются в качестве параметров блока Engee Function:
Тогда исходный код с добавлением параметров будет выглядеть так:
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
В итоге будет получен следующий исходный код:
Параметры initial_condition
и k
инициализируются во вкладке Parameters настроек блока Engee Function:
На первом шаге симуляции модели внутреннее состояние блока 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 позволяют отображать параметры блока прямо под его названием в модели. Для их добавления откройте окно настроек блока Engee Function и перейдите на вкладку Аннотация. Выберите нужные маркеры свойств блока и добавьте их в текстовый редактор.
На этой вкладке:
-
Слева отображается список доступных параметров (кроме скрытых).
-
Справа находится текстовый редактор, где можно задать аннотацию с маркерами в формате
%<ИмяПараметра>
. -
Перенос параметров возможен вручную, через автодополнение или с помощью кнопки
.
После выхода из редактора (например, при клике вне его) аннотация применяется: маркеры автоматически заменяются на фактические значения параметров, и итоговый текст отображается под названием блока (или над ним, если название размещено сверху).
Для удаления аннотаций необходимо удалить соответствующий маркер в редакторе.
Доступные маркеры
Маркер свойств автоматически заменяется на актуальное значение параметра. Доступны следующие маркеры:
-
Порты:
%<Inputs>
,%<Outputs>
— количество входных и выходных портов;%<InputPort1Type>
,%<OutputPort1Type>
,%<InputPort1Size>
,%<OutputPort1Size>
— тип данных и размерность сигналов. -
Временные характеристики:
%<SampleTime>
— дискретность;%<SampleTimeInheritanceMethod>
— метод наследования дискретности. -
Кодовые блоки:
%<ComponentStructCode>
,%<StepMethodCode>
— код структуры и метода шага. -
Параметры:
%<Parameters>
,%<Parameter1Name>
,%<Parameter1Value>
— названия и значения параметров. -
Флаги включения:
%<DefineComponentStruct>
,%<UseCommonCode>
,%<DefineStepMethod>
,%<DefineUpdateMethod>
,%<DefineTerminateMethod>
— включение соответствующих секций кода. -
Методы переопределения:
%<OverrideTypesInhMethod>
,%<OverrideDimsInhMethod>
,%<OverrideSampleTimeInhMethod>
— настройки наследования типов. -
Прочее:
%<UseExternalCache>
— использование внешнего кэша.