Engee Function
Использование кода Julia в моделях.
Описание
Блок 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
Здесь для наследования типов берется общий элемент входного сигнала и элемент параметра, заданного в настройках блока в разделе Parameters.
-
-
Override dimensions inheritance method — добавляет ячейку Dimensions inheritance method, которая переопределяет метод наследования размерностей.
Подробнее о методе наследования размерностей
Задает метод наследования размерностей:
-
Если флажок снят (по умолчанию) — размерности входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов.
-
Если флажок установлен — размерности входных/выходных портов наследуются по правилам, указанным в функции
propagate_dimensions
ячейки Dimensions inheritance method в исходном коде.-
Функция
propagate_dimensions
принимает в себя массив кортежей (размерностей) на каждый входной сигнал и возвращает массив размерностей на выходном.
-
Код ячейки по умолчанию:
function propagate_dimensions(inputs_dimensions::Vector{<:Dimensions})::Vector{<:Dimensions} # Функция, возвращающая массив размерностей сигналов на выходе. # Игнорируется, если используется алгоритм по умолчанию. # В данном случае учитываются размерности входного сигнала и # параметра блока `gain`. input_dimensions = first(inputs_dimensions) mock_input = zeros(input_dimensions) mock_output = mock_input .* gain return [size(mock_output)] end
Здесь для наследования размерностей берется массив с нужными размерностями (mock_input), состоящий из нулей и умножается на элемент параметра, заданного в настройках блока в разделе Parameters, после чего берется его размерность.
-
-
Use common method for types and dimensions inheritance — добавляет ячейку Common types and dimensions inheritance method, которая использует общий метод для переопределения наследования типов и размерностей одновременно.
Подробнее о методе наследования типов и размерностей
В отличие от конкретных методов для типов (Types inheritance method) или размерностей (Dimensions inheritance method), общий метод включает в себя и типы и размерности одновременно:
-
Если флажок снят (по умолчанию) — общий метод игнорируется, размерности и типы входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов или в методах наследования Override type inheritance method (если включено) и Override dimensions inheritance method (если включено).
-
Если флажок установлен — размерности и типы входных/выходных портов наследуются по правилам, указанным в функции
propagate_types_and_dimensions
ячейки Common types and dimensions inheritance method в исходном коде.
Код ячейки по умолчанию:
function propagate_types_and_dimensions(inputs_types::Vector{DataType}, inputs_dimensions::Vector{<:Dimensions})::Tuple{Vector{DataType}, Vector{<:Dimensions}} # Функция, возвращающая массив типов сигналов и массив # размерностей сигналов на выходе. Эту функцию можно использовать # если необходимо одновременно переопределить и алгоритм наследования # типов сигналов, и алгоритм наследования размерностей. outputs_types = propagate_types(inputs_types) outputs_dimensions = propagate_dimensions(inputs_dimensions) return outputs_types, outputs_dimensions end
Зависимости
Чтобы использовать эту ячейку, установите флажки методов Override type inheritance method и Override dimensions inheritance method.
-
-
Override sample time inheritance method — добавляет ячейку Sample times inheritance method, которая переопределяет метод наследования шага расчета.
Подробнее о методе наследования шага расчета
Код структуры шага расчета SampleTime
и функцииpropagate_sample_times
не будут автоматически добавлены в исходный код EngeeFunctionCode старых моделей Engee. Для доработки старых моделей добавьте структуру шага расчета и функцию самостоятельно.Задает метод наследования шага расчета:
-
Если флажок снят (по умолчанию) — используется предустановленный способ наследования шага расчета (по умолчанию
Default
) из параметра Sample time inheritance method вкладки Advanced. Подробнее о предустановленных способах читайте ниже. -
Если флажок установлен — предустановленные способы вкладки Advanced игнорируются (параметр недоступен), используется самостоятельный способ из ячейки Sample times inheritance method в исходном коде EngeeFunctionCode.
Для работы самостоятельного способа необходимо найти строку функции
propagate_sample_times
и вручную задать нужный шаг расчета.Код ячейки по умолчанию:
function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime # Функция, возвращающая время дискретизации блока. # Используется только в режиме наследования `Custom`. # Параметр fixed_solver говорит о том, используется решатель # с постоянным шагом (true) или с переменным (false). # Более сложный пример работы с наследованием времени # дискретизации блока можно посмотреть в документации. return first(inputs_sample_times) end
где шаг расчета имеет структуру:
const SampleTime = NamedTuple{(:period, :offset, :mode), Tuple{Rational{Int64}, Rational{Int64}, Symbol}}
-
-
Override direct feedthrough setting method — добавляет ячейку Direct feedthrough setting method, которая определяет прямое сквозное соединение.
Подробнее об определении прямого сквозного соединения
Определяет прямое сквозное соединение:
-
Если флажок снят (по умолчанию), то прямое сквозное соединение недоступно. Это означает, что выходной сигнал не будет контролироваться значением входного порта и позволяет блоку размыкать петли.
-
Если флажок установлен, то доступно прямое сквозное соединение. Это означает, что выходной сигнал контролируется непосредственно значением входного порта.
Код ячейки по умолчанию:
function direct_feedthroughs()::Vector{Bool} # Функция, возвращающая массив булевых значений, определяющих, # сквозные соединения. Если i-ый элемент массива равен true, # то i-ый порт имеет сквозное соединение. # Игнорируется, если используется алгоритм по умолчанию. return [true] end
Пример:
function direct_feedthroughs()::Vector{Bool} if gain == 2 return [false] else return [true] end end
-
Константы и функции получения атрибутов
Для того чтобы узнать типы, размеры и другую вспомогательную информацию в исполняемом коде блока, используйте следующие константы внутри вашего кода Engee Function:
-
BLOCK_NAME
— имя блока. У каждого блока, добавленного на холст Engee, есть имя, по которому можно обратиться через эту константу. Например, можно обратиться к BLOCK_NAME во время инициализации ошибки, чтобы вывести в ней имя блока. -
START_TIME
— начало симуляции из настроек модели. -
END_TIME
— конец симуляции из настроек модели. -
INPUT_SIGNAL_ATTRIBUTES
— списки из атрибутов по каждому входному порту. Например, чтобы узнать атрибуты первого входного сигнала — используйтеINPUT_SIGNAL_ATTRIBUTES[1]
, где1
— первый входной порт блока Engee Function. -
OUTPUT_SIGNAL_ATTRIBUTES
— списки из атрибутов по каждому выходному порту. Например, чтобы узнать атрибуты первого выходного сигнала — используйтеOUTPUT_SIGNAL_ATTRIBUTES[1]
, где1
— первый выходной порт блока Engee Function.
Чтобы узнать дополнительную информацию конкретного порта блока, можно обратиться к атрибутам его сигнала, добавив точку . после константы INPUT_SIGNAL_ATTRIBUTES[i]
, где [i]
— номер входного порта, и OUTPUT_SIGNAL_ATTRIBUTES[i]
, где [i]
— номер выходного порта соответственно. Можно узнать дополнительную информацию через следующие функции обращения:
-
dimensions
— размерность сигнала. Можно сократить доdims
. -
type
— тип сигнала. Можно сократить доtp
. -
sample_time
— шаг расчета. Представляет собой структуру по аналогии с атрибутами сигналов, к которой можно обращаться через точку .. Доступны две функции обращения:-
period
— период шага расчета. Полная функция обращения —sample_time.period
. Можно сократить доst.p
. -
offset
— смещение шага расчета. Полная функция обращения —sample_time.offset
. Можно сократить доst.o
.
-
-
direct_feedthrough
— указывает, размыкает ли порт петли. Используется только для входных портов (проверяет атрибуты только для входного порта). Можно сократить доdf
.
Пример модели Engee Function со всеми константами и функциями обращения:
Порты
Вход
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 , ссылающийся на внешний код. Это позволяет инициализировать переменные из внешнего кода (при их наличии) в исходном коде.
|
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока. |
Пример кода
В данном примере представлена упрощенная реализация блока Discrete-Time Integrator, основанная на интеграции кода Julia в модель Engee. В качестве метода интегрирования выбран прямой метод Эйлера:
mutable struct Integrator{T} <: AbstractCausalComponent
const dt::Float64
state::T
gain::Float64
function Integrator()
dt = OUTPUT_SIGNAL_ATTRIBUTES[1].sample_time.period
state = initial_condition
gain = k
new{typeof(state)}(dt, state, gain)
end
end
(c::Integrator)(t::Real, x) = c.state
function update!(c::Integrator, t::Real, x)
c.state += x * c.dt * c.gain
return c
end
Параметры initial_condition
и k
инициализируются во вкладке Parameters настроек блока Engee Function.
На первом шаге симуляции модели внутреннее состояние блока c.state
инициализируется значением параметра initial_condition
.
После чего на каждом шаге расчета блок возвращает внутреннее состояние c.state
в качестве выходного сигнала и пересчитывает его значение в методе update!
.
Аннотации
Аннотации в Engee Function позволяют отображать параметры блока прямо под его названием в модели. Для их добавления откройте окно настроек блока Engee Function и перейдите на вкладку «Аннотация». Выберите нужные маркеры свойств блока и добавьте их в текстовый редактор.
На этой вкладке:
-
Слева отображается список доступных параметров (кроме скрытых).
-
Справа находится текстовый редактор, где можно задать аннотацию с маркерами в формате
%<ИмяПараметра>
. -
Перенос параметров возможен вручную, через автодополнение или с помощью кнопки
.
После выхода из редактора (например, при клике вне его) аннотация применяется: маркеры автоматически заменяются на фактические значения параметров, и итоговый текст отображается под названием блока (или над ним, если название размещено сверху).
Для удаления аннотаций необходимо удалить соответствующий маркер в редакторе.
Доступные маркеры
Маркер свойств автоматически заменяется на актуальное значение параметра. Доступны следующие маркеры:
-
Порты:
%<Inputs>
,%<Outputs>
— количество входных и выходных портов;%<InputPort1Type>
,%<OutputPort1Type>
,%<InputPort1Size>
,%<OutputPort1Size>
— тип данных и размерность сигналов. -
Временные характеристики:
%<SampleTime>
— дискретность;%<SampleTimeInheritanceMethod>
— метод наследования дискретности. -
Кодовые блоки:
%<ComponentStructCode>
,%<StepMethodCode>
— код структуры и метода шага. -
Параметры:
%<Parameters>
,%<Parameter1Name>
,%<Parameter1Value>
— названия и значения параметров. -
Флаги включения:
%<DefineComponentStruct>
,%<UseCommonCode>
,%<DefineStepMethod>
,%<DefineUpdateMethod>
,%<DefineTerminateMethod>
— включение соответствующих секций кода. -
Методы переопределения:
%<OverrideTypesInhMethod>
,%<OverrideDimsInhMethod>
,%<OverrideSampleTimeInhMethod>
— настройки наследования типов. -
Прочее:
%<UseExternalCache>
— использование внешнего кэша.