Engee Function
Использование кода Julia в моделях.
Описание
Блок Engee Function позволяет в моделях Engee использовать код на языке Julia.
Подробнее о языке Julia смотрите здесь. |
Использование
Для интеграции кода Julia в модель Engee необходимо:
-
Добавить в модель блок Engee Function из раздела Базовые/Пользовательские функции библиотеки блоков;
-
Нажать на кнопку Редактировать исходный код, после чего будет открыт редактор с примером минимально необходимого исходного кода:
Во вкладке Executable code (ExeCode) редактора исходного кода определяется объект с методом вызова (такие объекты называются функторами, подробнее здесь). Для этого:
-
Определяется структура, наследуемая от типа
AbstractCausalComponent
. -
В структуре перечисляются имена и поля. Данный конструктор не должен принимать аргументы.
-
Определяется функция, предназначенная для вычисления выходов блока, возвращаемых посредством ключевого слова
return
. Первым аргументом в нее передается время симуляции (t::Real
); затем перечисляются переменные, соответствующие входным сигналам блока. -
Если блок имеет внутренние состояния, для обновления их значений в ходе симуляции необходимо определить метод
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 со всеми константами и функциями обращения:
Порты
Вход
Input Port — входной порт
скаляр
| вектор
| матрица
Входной порт, заданный в виде скаляра, вектора или матрицы.
Настройте входной порт во вкладке Ports блока с помощью следующих опций:
-
Label — задайте имя входного порта. По умолчанию ячейка Label не заполнена (имя не задано).
-
Type — тип данных входного сигнала. Выберите один из вариантов:
-
Определенный тип (все, кроме
Inherit
) — проверяется, что на входной порт подается сигнал определенного типа. Выберите конкретный тип данных для входного порта. Поддерживаемые сигналы:Float16
,Float32
,Float64
,ComplexF32
,ComplexF64
,Bool
,Int8
,Int16
,Int32
,Int64
,Int128
,UInt8
,UInt16
,UInt32
,UInt64
,UInt128
. -
Inherit
(по умолчанию) — наследует тип данных от связанного блока. Может быть любым типом данных.
-
-
Size — размерность входного сигнала:
-
Наследование всех размерностей (
-1
по умолчанию) — наследует размерность поданного на входной порт сигнала (сигнал может иметь любую размерность). -
Определенные размерности — входной сигнал должен иметь заданное количество элементов. Например,
2
— сигнал из двух элементов. -
Наследование одной из размерностей — наследует размерность поданного на входной порт сигнала с явным указанием структуры данных. Например,
(-1, 2)
— ожидается, что первая размерность наследуется, а вторая задается явно.
-
-
Direct feedthrough — определяет прямое сквозное соединение:
-
Если флажок установлен (по умолчанию), то доступно прямое сквозное соединение. Это означает, что выходной сигнал контролируется непосредственно значением входного порта.
-
Если флажок снят, то прямое сквозное соединение недоступно. Это означает, что выходной сигнал не будет контролироваться значением входного порта и позволяет блоку размыкать петли.
-
Выход
Output port — выходной порт
скаляр
| вектор
| матрица
Выходной порт, заданный в виде скаляра, вектора или матрицы.
Настройте выходной порт во вкладке Ports блока с помощью следующих опций:
-
Label — задайте имя выходного порта. По умолчанию ячейка Label не заполнена (имя не задано).
-
Type — тип данных выходного сигнала. Выберите один из вариантов:
-
Определенный тип (все, кроме
Inherit
) — определяем тип данных выходного сигнала. Выберите конкретный тип данных для выходного сигнала. Поддерживаемые сигналы:Float16
,Float32
,Float64
,ComplexF32
,ComplexF64
,Bool
,Int8
,Int16
,Int32
,Int64
,Int128
,UInt8
,UInt16
,UInt32
,UInt64
,UInt128
. -
Inherit
(по умолчанию) — наследует тип данных выходного сигнала. Вычисляет наименьший общий тип при наличии нескольких входных сигналов разного типа. Может быть любым типом данных.
-
-
Size — размерность выходного сигнала:
-
Наследование всех размерностей (
-1
по умолчанию) — наследует размерность поданного на выходной порт сигнала. Выходной сигнал будет иметь размерности, полученные в результате broadcast механизма — Julia автоматически расширит размерность меньшего массива данных до размерности большего, чтобы корректно унаследовать размерность. -
Определенные размерности — выходной сигнал должен иметь заданное количество элементов. Например,
2
— сигнал из двух элементов. -
Наследование одной из размерностей — наследует размерность поданного на выходной порт сигнала с явным указанием структуры данных. Например,
(-1, 2)
— ожидается, что первая размерность наследуется, а вторая задается явно.
-
Параметры
Основные
Number of input ports — определяет количество входных портов
1 (по умолчанию)
Определяет количество входных портов блока. Значение параметра Number of input ports будет соответствовать количеству входных портов.
Number of output ports — определяет количество выходных портов
1 (по умолчанию)
Определяет количество выходных портов блока. Значение параметра Number of output ports будет соответствовать количеству выходных портов.
Override type inheritance method — включение или отключение метода наследования типов
выключено (по умолчанию)
| включено
Задает метод наследования типов:
-
Если флажок снят (по умолчанию) — типы входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов соответственно.
-
Если флажок установлен — типы входных/выходных портов наследуются по правилам, указанным в функции propagate_types раздела исходного кода InhMethodsCode.
-
Функция propagate_types принимает в себя один аргумент — вектор типов, по одному типу на каждый входной сигнал и возвращает вектор выходных типов.
-
Пример:
function propagate_types(inputs_types::Vector{DataType})::Vector{DataType}
input_type = first(inputs_types)
# promote_type возвращает тип, к которому приводятся типы-аргументы
# при арифметических операциях с объектами этих типов.
output_type = promote_type(input_type, eltype(gain))
return [output_type]
end
В данном примере для наследования типов берется общий элемент входного сигнала и элемент параметра, заданного в настройках блока в разделе Parameters.
Override dimensions inheritance method — включение или отключение метода наследования размерностей
выключено (по умолчанию)
| включено
Задает метод наследования размерностей:
-
Если флажок снят (по умолчанию) — размерности входных/выходных портов наследуются по правилам, указанным в описании входных/выходных портов соответственно.
-
Если флажок установлен — размерности входных/выходных портов наследуются по правилам, указанным в функции propagate_dimensions раздела исходного кода InhMethodsCode.
-
Функция propagate_dimensions принимает в себя массив кортежей (размерностей) на каждый входной сигнал и возвращает массив размерностей на выходном.
-
Пример:
function propagate_dimensions(inputs_dimensions::Vector{<:Dimensions})::Vector{<:Dimensions}
input_dimensions = first(inputs_dimensions)
mock_input = zeros(input_dimensions)
mock_output = mock_input .* gain
return [size(mock_output)]
end
В данном примере для наследования размерностей берется массив с нужными размерностями (mock_input), состоящий из нулей и умножается на элемент параметра, заданного в настройках блока в разделе Parameters, после чего берется его размерность.
Override direct feedthrough setting method — определяет прямое сквозное соединение
выключено (по умолчанию)
| включено
Определяет прямое сквозное соединение:
-
Если флажок снят (по умолчанию), то прямое сквозное соединение недоступно. Это означает, что выходной сигнал не будет контролироваться значением входного порта и позволяет блоку размыкать петли.
-
Если флажок установлен, то доступно прямое сквозное соединение. Это означает, что выходной сигнал контролируется непосредственно значением входного порта.
Пример:
function direct_feedthroughs()::Vector{Bool}
if gain == 2
return [false]
else
return [true]
end
end
Use external cache for non-scalar output — использовать внешний кэш для нескалярных выходных сигналов
выключено (по умолчанию)
| включено
Укажите использование внешнего кэша для нескалярного (многомерного) выходного сигнала для экономии оперативной памяти Engee. Используется если у блока Engee Function только один выходной порт:
-
Если флажок снят (по умолчанию) — внешний кэш не используется.
-
Если флажок установлен — сигнал сможет принимать еще один аргумент cache, который нужно внести в ExeCode. В коде требуется написание функторов в зависимости от размерности выходного сигнала:
-
Если выходной сигнал скалярный:
function (c::Block)(t::Real, x) return c.g * x end
-
Если выходной сигнал нескалярный:
function (c::Block)(t::Real, cache, x) cache .= c.g .* x nothing end
где
t
— время,x
— аргумент (информация с входных портов). Параметр времени должен указываться, даже если он отсутствует в параметрах блока.
-
Sample time inheritance method — определение метода наследования шага расчета
Default (по умолчанию)
| Discrete
| Continuous
| Custom
Определяет метод наследования шага расчета в зависимости от выбранного значения:
Способ наследования Default всегда используется в случае, когда блок Engee Function не является дискретным или непрерывным.
|
-
Default
— способ наследования шага расчета по умолчанию, когда значение параметра Sample time (-1 for inherited) равно-1
. Способ получает любой вид шага расчета. При выборе этого способа блок Engee Function будет наследовать шаг расчета по следующим принципам:-
Если у блока нет входных портов — на выходе непрерывный шаг расчета.
-
Если на входе все шаги расчета одинаковы — на выходе шаг расчета совпадает со входом.
-
Если среди входных шагов расчета есть непрерывные — на выходе тоже есть непрерывные шаги расчета.
-
Если среди входных шагов расчета есть фиксированный шаг расчета с малым шагом (FiM), нет непрерывного шага расчета и решатель с переменным шагом — на выходе фиксированный шаг расчета с малым шагом.
-
Если на входе нет непрерывного и фиксированного шагов расчета и не все шаги расчета равны — рассматриваются только дискретные шаги расчета на входе для которых справедлив один из вариантов:
-
Если наибольший общий делитель дискретных шагов расчета совпадает с одним из входных шагов расчета или используется решатель с постоянным шагом — на выходе дискретный шаг расчета с шагом наибольшего общего делителя.
-
Если решатель с переменным шагом и наибольший общий делитель входных дискретных шагов расчета не совпадают ни с одним из входных шагов расчета — на выходе фиксированный шаг расчета с малым шагом.
-
-
-
Discrete
— способ наследования для получения дискретного шага расчета. При выборе этого способа блок Engee Function будет наследовать шаг расчета по следующим принципам:-
Если на входе есть непрерывный или фиксированный (FiM) шаги расчета — на выходе дискретный шаг расчета с шагом решателя (даже если решатель с переменным шагом).
-
Если на входе среди шагов расчета есть дискретные — на выходе дискретный шаг расчета с наибольшим общим делителем от входных дискретных шагов расчета.
-
-
Continuous
— способ наследования для получения непрерывного шага расчета независимо от входных шагов расчета. -
Custom
— способ самостоятельного переопределения наследования шага расчета. Для работы способа необходимо зайти в редактор кода блока Engee Function (Редактировать исходный код) во вкладку InhMethodsCode и найти строку функцииpropagate_sample_times
и вручную задать нужный шаг расчета. По умолчанию:function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime return first(inputs_sample_times) end
где шаг расчета имеет структуру:
# Функция, возвращающая время дискретизации блока. # Используется только в режиме наследования `Custom`. # Параметр fixed_solver говорит о том, используется решатель # с постоянным шагом (true) или с переменным (false). const SampleTime = NamedTuple{(:period, :offset, :mode), Tuple{Rational{Int64}, Rational{Int64}, Symbol}}
Пример переопределения функции
propagate_sample_times
с работой, аналогичной способу наследованияDefault
:function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime nonnegative_sample_times = filter( st -> st.period >= 0, collect(values(inputs_sample_times)), ) finite_periods = filter( st -> !isinf(st.period), nonnegative_sample_times, ) .|> (st -> st.period) output_sample_time = if !isempty(nonnegative_sample_times) if allequal(nonnegative_sample_times) first(nonnegative_sample_times) elseif any(st -> st.period == 0 // 1 && st.mode == :Continuous, nonnegative_sample_times) (period = 0 // 1, offset = 0 // 1, mode = :Continuous) elseif any(st -> st.mode == :FiM, nonnegative_sample_times) && !fixed_solver (period = 0 // 1, offset = 0 // 1, mode = :FiM) elseif ( all(x -> x.period > 0 // 1, nonnegative_sample_times) && (fixed_solver || gcd(finite_periods) in finite_periods) ) (period = gcd(finite_periods), offset = 0 // 1, mode = :Discrete) else (period = 0 // 1, offset = 0 // 1, mode = :FiM) end else (period = 0 // 1, offset = 0 // 1, mode = :Continuous) end return output_sample_time end
Eсли функция propagate_sample_times возвращает (period = 0 // 1, offset = 0 // 1, mode = :Discrete) , то такой шаг расчета будет воспринят как дискретный с шагом решателя.
|
Sample time (−1 for inherited) — интервал между шагами расчета
−1 (по умолчанию)
Укажите интервал между шагами расчета как неотрицательное число. Чтобы наследовать шаг расчета, установите для этого параметра значение −1
.
Настройка параметров
Number of parameters — укажите число параметров
1 (по умолчанию)
Число параметров, используемых в блоке.
Parameter — определяет параметр как переменную
2 (по умолчанию)
Определяет параметр как переменную для использования в исходном коде. Значение и имя параметра могут быть изменены. Имя первого параметра по умолчанию — gain. Новые параметры называются parameter2 и далее по возрастанию. Значение новых параметров равно нулю.
Фактический тип данных, как и поддержка возможных типов данных, зависит от пользовательского кода внутри блока. |
Пример кода
В данном примере представлена упрощенная реализация блока Discrete-Time Integrator, основанная на интеграции кода Julia в модель Engee. В качестве метода интегрирования выбран прямой метод Эйлера:
mutable struct Integrator{T} <: AbstractCausalComponent
const dt::Float64
state::T
gain::Float64
function Integrator()
dt = OUTPUT_SIGNAL_ATTRIBUTES[1].sample_time.period
state = initial_condition
gain = k
new{typeof(state)}(dt, state, gain)
end
end
(c::Integrator)(t::Real, x) = c.state
function update!(c::Integrator, t::Real, x)
c.state += x * c.dt * c.gain
return c
end
Параметры initial_condition
и k
инициализируются во вкладке Parameters настроек блока Engee Function.
На первом шаге симуляции модели внутреннее состояние блока c.state
инициализируется значением параметра initial_condition
.
После чего на каждом шаге расчета блок возвращает внутреннее состояние c.state
в качестве выходного сигнала и пересчитывает его значение в методе update!
.