Engee Function
Usage of Julia code in models.
Description
Block Engee Function allows the use of Julia language code in Engee models.
For more information about the Julia programming language, see Programming. |
In the block Engee Function it is allowed to use most of the features of the Julia language. However, usage of the Pkg package manager is not allowed. |
Usage
To integrate Julia code into the Engee model you need to:
-
Add a block to the model Engee Function from the Basic/User Functions section of the block library
;
-
In settings window
on the Main tab of the block Engee Function click on the Edit Source Code button to open the source code editor (EngeeFunctionCode):
Source code cells
The EngeeFunctionCode source code editor consists of function cells with Julia code. By default, three cells are available: auxiliary (uneditable), Component struct code и Step method code (the cells can be hidden):
To connect additional source code files, you can use the
|
All the code for the block Engee Function can be written in a cell *Common code*You have full control over the component structure, signatures and number of functions. |
To add/remove other function cells, click on the "Manage Methods" button and check/uncheck the required cells:
->
Each cell is responsible for a unique block functionality Engee Function. Let’s take a closer look at them:
-
Information cell (non-editable) - automatically displays block variables *Engee Function*attributes of input and output signals (dimension, type, discreteness) and other parameters set by the user. Its content is updated dynamically depending on the block settings. The cell is always active, but is not selectable in the method control menu
. It has semi-transparent text, which is used to display tooltips.
Changing the block parameters affects not only the contents of the information cell, but also the tooltips in other cells, which are also displayed in semi-transparent text. -
Define component struct - adds the cell Component struct code*which specifies the structure of the block component *Engee Function (inherited from
AbstractCausalComponent
type). The structure fields are defined between the uneditable linesstruct Block <: AbstractCausalComponent
andend
. By default, a parameterg
is created and initialised with the block valuegain
, and theBlock()
constructor takes no arguments.
-
Use common code - adds a cell Common code*in which the code is written in free form. By default the cell is empty. For example, if the standard structure declaration in *Component struct code is not suitable (because of the uneditable
struct Block <: AbstractCausalComponent
), you can disable Define component struct to remove the cell, and define the component structure manually in the Common code. The same goes for any functions from themethod control menu - instead of standard cells, you can write your own code in . Common code.
The declaration of component and functor is mandatory for operation Engee Function. If Define component struct and Define step method are disabled, their code must be set to Common code, otherwise the block will not work. To override inheritance functions (Override, see below), you must first switch on the corresponding cell, erase its contents, and then write new code in the Common code. -
Define step method - adds a cell Step method code*which defines the Step method that calculates output signals of the block *Engee Function. The method signature is generated automatically depending on the values of the corresponding labels of ports in the Ports tab. The method is represented as a functor (see here for details) and is called at each simulation step. The fields are defined between the uneditable strings
function (c::Block)(t::Real, in1)
andend
. The first argument oft::Real
is the simulation time, then the input variables are passed in. The calculated values of the outputs are returned via thereturn
keyword. -
Define update method - adds a Update method code cell, in which the
update!
method updates the internal state of the block Engee Function at each step of the simulation. The first argumentc::Block
is the block structure, the secondt::Real
is the simulation time, then the input signals are passed. If the block has no internal state, the method can remain empty and just returnc
.If you want to define multiple update!
methods or define a method with a different signature, the Update method code cell can be disabled and write the code in the cell Common code. The compiler will automatically detect the presence of theupdate!
method and use it for simulation. -
Define terminate method code - adds a Terminate method code cell that is executed when the block simulation is terminated Engee Function (using the
terminate!
method). The first argument ofc::Block
is the structure of the block. By default, the method does not perform any additional actions and simply returnsc
. -
Override type inheritance method - adds a Types inheritance method cell that overrides the type inheritance method.
For more information about type inheritance method
Specifies the type inheritance method:
-
If unchecked (by default) - input/output port types are inherited according to the rules specified in the input/output port description.
-
If the checkbox is checked - input/output port types are inherited according to the rules specified in the
propagate_types
function of the Types inheritance method cell in the source code.-
The
propagate_types
function takes one argument - a vector of types, one type for each input signal type and returns a vector of output types.
-
By default cell code:
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
Here for type inheritance the common element of the input signal and the element of the parameters set in the block settings in section Setting parameters.
-
-
Override dimensions inheritance method - adds the Dimensions inheritance method cell, which overrides the method of dimensions inheritance.
For more information about dimensions inheritance method.
Specifies the method of dimensions inheritance:
-
If unchecked (by default) - input/output port dimensions are inherited according to the rules specified in the input/output port description.
-
If the checkbox is checked - input/output port dimensions are inherited according to the rules specified in the
propagate_dimensions
function of the Dimensions inheritance method cell in the source code.-
The
propagate_dimensions
function accepts an array of tuples (dimensions) on each input signal and returns an array of dimensions on the output signal.
-
By default cell code:
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
Here, for dimension inheritance, an array with the required dimensions (mock_input) consisting of zeros is taken and multiplied by the element of the parameter set in the block parameters in section Setting parametersand then its dimension is taken.
-
-
Use common method for types and dimensions inheritance - adds the Common types and dimensions inheritance method cell, which uses a common method to override inheritance of types and dimensions at the same time.
For more information about types and dimensions inheritance method.
Unlike specific methods for types (Types inheritance method) or dimensions (Dimensions inheritance method), the generic method includes both types and dimensions at the same time:
-
If the checkbox is unchecked (by default) - general method is ignored, dimensions and input/output port types are inherited according to the rules specified in the input/output port description or in the Override type inheritance method (if enabled) and Override dimensions inheritance method (if enabled).
-
If the checkbox is enabled - dimensions and types of input/output ports are inherited according to the rules specified in the
propagate_types_and_dimensions
function of the Common types and dimensions inheritance method cell in the source code.
By default cell code:
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
Dependencies
To use this cell, select the Override type inheritance method and Override dimensions inheritance method checkboxes.
-
-
Override sample time inheritance method - adds a Sample times inheritance method cell that overrides the calculation step inheritance method.
For more information about the calculation step inheritance method.
The code for the SampleTime
calculation step structure andpropagate_sample_times
function will not be automatically added to the EngeeFunctionCode source code of old Engee models. To finalise old models, add the calculation step structure and function yourself.Specifies the inheritance method of the calculation step:
-
If unchecked (by default) - the preset calculation step inheritance method (by default
Default
) from the Sample time inheritance method parameters of the Advanced tab is used. For more information about preset methods, see below. -
If the checkbox is checked - the preset methods of the Advanced tab are ignored (parameters are unavailable), the independent method from the Sample times inheritance method cell in the EngeeFunctionCode source code is used.
For the independent method to work, it is necessary to find the
propagate_sample_times
function line and manually set the required calculation step.By default cell code:
function propagate_sample_times(inputs_sample_times::Vector{SampleTime}, fixed_solver::Bool)::SampleTime # Функция, возвращающая время дискретизации блока. # Используется только в режиме наследования `Custom`. # Параметр fixed_solver говорит о том, используется решатель # с постоянным шагом (true) или с переменным (false). # Более сложный пример работы с наследованием времени # дискретизации блока можно посмотреть в документации. return first(inputs_sample_times) end
where the calculation step has a structure:
const SampleTime = NamedTuple{(:period, :offset, :mode), Tuple{Rational{Int64}, Rational{Int64}, Symbol}}
-
-
Override direct feedthrough setting method - adds a Direct feedthrough setting method cell that defines a direct feedthrough connection.
For more information on defining a direct feedthrough connection
Defines a direct feedthrough connection:
-
If unchecked (by default), direct through connection is not available. This means that the output signal will not be monitored by the input port value and allows the unit to open loops.
-
If the checkbox is selected, then a direct pass-through connection is available. This means that the output signal is monitored directly by the input port value.
By default cell code:
function direct_feedthroughs()::Vector{Bool} # Функция, возвращающая массив булевых значений, определяющих, # сквозные соединения. Если i-ый элемент массива равен true, # то i-ый порт имеет сквозное соединение. # Игнорируется, если используется алгоритм по умолчанию. return [true] end
Example:
function direct_feedthroughs()::Vector{Bool} if gain == 2 return [false] else return [true] end end
-
Constants and functions for obtaining attributes
To find out types, sizes, and other auxiliary information in the executable code of a block, use the following constants within your code Engee Function:
-
BLOCK_NAME
- the name of the block. Each block added to the Engee canvas has a name that can be referenced via this constant. For example, you can refer to BLOCK_NAME during error initialisation to print the block name in it. -
START_TIME
- the start of the simulation from the model settings. -
END_TIME
- end of the simulation from the model settings. -
INPUT_SIGNAL_ATTRIBUTES
- lists of attributes for each input port. For example, to find out the attributes of the first input signal - useINPUT_SIGNAL_ATTRIBUTES[1]
, where1
is the first input port of the block Engee Function. -
OUTPUT_SIGNAL_ATTRIBUTES
- lists from attributes for each output port. For example, to find out the attributes of the first output - useOUTPUT_SIGNAL_ATTRIBUTES[1]
, where1
is the first output port of the block Engee Function.
To find out additional information about a particular block port, you can refer to its signal attributes by adding the dot . after the constants INPUT_SIGNAL_ATTRIBUTES[i]
, where [i]
is the input port number, and OUTPUT_SIGNAL_ATTRIBUTES[i]
, where [i]
is the output port number, respectively. You can find out more information through the following call functions:
-
dimensions
- the dimensionality of the signal. It can be abbreviated todims
. -
type
- signal type. Can be abbreviated totp
. -
sample_time
- calculation step. It represents a structure similar to the attributes of signals, which can be accessed through the point .. Two access functions are available:-
period
- period of the calculation step. The full reference function issample_time.period
. It can be shortened tost.p
. -
offset
- offset of the calculation step. The full call function issample_time.offset
. Can be shortened tost.o
.
-
-
direct_feedthrough
- specifies whether the loop port opens. Used for input ports only (checks attributes for the input port only). Can be abbreviated todf
.
Example model Engee Function with all constants and access functions:
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
Ports
Input
Input Port -
input port
scalar
| vector
| matrix
Details
An input port specified as a scalar, vector or matrix.
Configure the input port in the Ports tab of the block using the following options:
-
Label - set the name of the input port. By default, the Label cell is not filled in (no name is set).
-
Type - input signal data type. Select one of the options:
-
Definite Type (all except
Inherit') - checks that a specific type of signal is applied to the input port. Select a specific data type for the input port. Supported signals: `Float16
,Float32
,Float64
,ComplexF32
,ComplexF64
,Bool
,Int8
,Int16
,Int32
,Int64
,Int128
,UInt8
,UInt16
,UInt32
,UInt64
,UInt128
. -
Inherit
(by default) - inherits the data type from the linked block. Can be any data type.
-
-
Size - dimension of the input signal:
-
Inherit all dimensions (
-1
by default) - inherits the dimension of the signal applied to the input port (the signal can be of any dimension). -
Defined dimensions - the input signal must have a specified number of elements. The dimensions are specified in Julia-notation (as a tuple), for example,
(2,)
for a one-dimensional signal of two elements or(2, 3, 4)
for a multidimensional one. If-1
is specified, the dimensionality is inherited. -
Inherit one of the dimensions - inherits the dimensionality of the signal applied to the input port with an explicit indication of the data structure. For example,
(-1, 2)
- it is expected that the first dimension is inherited and the second dimension is specified explicitly.
-
-
Output bus type - input bus type, replaces the input signal dimension Size in case the input signal data type Type has the
BusSignal
(bus) type selected. By default it has the valueBusSignal{(), Tuple{}, ()}
. In order for the block Engee Function understand which bus is coming to the input, it is sufficient to set Type toInherit
. Explicit specification of the type is only necessary for the output signal type.The block Engee Function does not inherit buses on output ports, although it can receive them on inputs. To inherit buses to output ports (for transmission to other blocks), the bus type must be explicitly specified in the Output bus type parameters. -
Direct feedthrough - defines direct feedthrough connection:
-
If checked (by default), a direct feedthrough connection is available. This means that the output signal is controlled directly by the value of the input port.
-
If the checkbox is unchecked, a direct pass-through connection is not available. This means that the output signal will not be controlled by the input port value and allows the unit to open loops.
-
Output
Output port -
output port
scalar
| vector
| matrix
Details
An output port specified as a scalar, vector or matrix.
Configure the output port in the Ports tab of the block using the following options:
-
Label - set the name of the output port. By default, the Label cell is not filled in (no name is set).
-
Type - the data type of the output signal type. Select one of the options:
-
Defined Type (all except
Inherit
) - define the output signal data type. Select a specific data type for the output signal type. Supported signals:Float16
,Float32
,Float64
,ComplexF32
,ComplexF64
,Bool
,Int8
,Int16
,Int32
,Int64
,Int128
,UInt8
,UInt16
,UInt32
,UInt64
,UInt128
. -
Inherit
(by default) - inherits the output signal data type. Calculates the smallest common type when there are several input signals of different types. Can be any data type.
-
-
Size - dimension of the output signal:
-
Inherit all dimensions (
-1
by default) - inherits the dimensionality of the signal applied to the output port. The output signal will have dimensions derived from the broadcast mechanism - Julia will automatically expand the dimensionality of a smaller data set to the dimensionality of a larger one to inherit the dimensionality correctly. -
Defined dimensions - the output must have a specified number of elements. The dimensions are specified in Julia notation (as a tuple), e.g.
(2,)
for a one-dimensional signal of two elements or(2, 3, 4)
for a multidimensional one. If-1
is specified, the dimensionality is inherited. -
Inherit one of the dimensions - inherits the dimensionality of the signal applied to the output port with an explicit indication of the data structure. For example,
(-1, 2)
- it is expected that the first dimension is inherited and the second dimension is specified explicitly.
-
-
Output bus type - output bus type, replaces the input signal type Size if the output signal data type Type has the
BusSignal
(bus) type selected. By default it has the valueBusSignal{(), Tuple{}, ()}
. In order for a block to Engee Function to output a bus, it is necessary to explicitly specify its type.
Parameters
Main
Number of input ports -
defines the number of input ports
1 (By default)
Details
Defines the number of input ports of the block. The value of the Number of input ports parameters will correspond to the number of input ports.
Number of output ports -
determines the number of output ports
1 (By default)
Details
Defines the number of output ports of the block. The value of the Number of output ports parameters will correspond to the number of output ports.
Sample time -
interval between calculation steps
`-1 (By default).
Details
Specify the interval between calculation steps as a non-negative number. To inherit a calculation step, set this parameter to -1
.
Advanced
Use external cache for non-scalar output — use external cache for non-scalar outputs
off (by default)
| on
Details
Specify usage of external cache for non-scalar (multidimensional) output signal to save RAM Engee. Used if the block has Engee Function has only one output port:
-
If unchecked (By default) - external cache is not used.
-
If the checkbox is checked - the output will be able to accept an additional
cache
argument, which must be taken into account in the source EngeeFunctionCode. If the block has one output port, then in the cell Step method codecache
argument is automatically added to the list of function arguments, except when the port dimension is explicitly set as()
(scalar). In the source code, it is required to write functors depending on the output dimension. Functors can be defined in the cell Common code:-
If the output signal is scalar:
function (c::Block)(t::Real, x) return c.g * x end
-
If the output signal is non-scalar:
function (c::Block)(t::Real, cache, x) cache .= c.g .* x nothing end
where
t
is the time,x
is the argument (information from the input ports). The time parameter must be specified even if it is not present in the block parameters.
-
Sample time inheritance method — definition of the calculation step inheritance method
Default
| Discrete
| Continuous
Details
The Sample time inheritance method setting disappears from the Advanced tab if the Override sample time inheritance method checkbox is selected in the EngeeFunctionCode source code. In this case the calculation step inheritance method is determined by the Sample times inheritance method code of the function cell. |
Defines the inheritance method of the calculation step depending on the selected value:
If -1 is specified in the Sample Time field, the calculation step is inherited according to the method specified in the Sample time inheritance method field depending on the selected value (Default , Discrete , Continuous ). In other cases (Sample Time is not equal to -1 and SampleTime is greater than or equal to 0 ) - the block Engee Function works with the specified value of Sample Time field, ignoring Sample time inheritance method.
|
-
Default
- inheritance method of the calculation step by default. TheDefault
inheritance method is always used in the case when the block Engee Function is not discrete or continuous. The method gets any kind of calculation step. When this method is selected, the block Engee Function will inherit the calculation step according to the following principles:-
If the block has no input ports - the output is a continuous calculation step.
-
If the input has all the same calculation steps - the output has the same calculation step as the input.
-
If there are continuous calculation steps among the input calculation steps - the output also has continuous calculation steps.
-
If there is a fixed-in-minor (FiM, Fixed-in-Minor) among the input calculation steps, there is no continuous calculation step and solver with variable step - the output is a fixed-in-minor.
-
If there are no continuous and fixed-in-small calculation steps at the input and not all calculation steps are equal - only discrete calculation steps at the input are considered, for which one of the variants is true:
-
If the greatest common divisor of the discrete computation steps is the same as one of the input computation steps or a constant-pitch solver is used - the output is the discrete computation step with the step of the greatest common divisor.
-
If the variable step solver and the greatest common divisor of the input discrete calculation steps do not coincide with any of the input calculation steps - the output is a fixed small.
-
-
-
Discrete
- inheritance method to get discrete calculation step. If this method is selected, the block Engee Function will inherit the calculation step according to the following principles:-
If the input has continuous or fixed small calculation steps - the output is a discrete calculation step with the solver step (even if the solver is a variable step).
-
If there are discrete calculation steps among the input - the output is a discrete calculation step with the largest common divisor from the input discrete calculation steps.
-
-
Continuous
- inheritance method for obtaining a continuous calculation step regardless of the input calculation steps.
Example of propagate_sample_times
function overriding with the work similar to the Default
inheritance method:
+
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
If the propagate_sample_times function returns (period = 0 // 1, offset = 0 // 1, mode = :Discrete) , then such a calculation step will be taken as discrete with the solver step.
|
Setting parameters
Number of parameters -
specify the number of parameters
1 (By default)
Details
Number of parameters used in the block.
Parameter -
defines a parameter as a variable
scalar
| vector
| array
Details
Defines the parameters for usage in the source code of the block Engee Function. Parameters can be specified in the parameter:
-
Name
- name of the parameters; -
Value
- value of the parameters. As values you can set any expressions in Julia language.
The value and name of the parameters can be changed in the Parameters tab. The name of the first parameter (present by default) is gain
, whose value is 2
. The new parameters are named parameter2
and then in ascending order. The value of the new parameters is 0
by default.
Global variables available in the Engee variables window can be set as Parameters tab variables to be inserted into the source code of the block Engee Function. If the name of the parameters and the global variable are the same, the value of the parameter will be automatically substituted from the global variable.
To set a bus parameter, set the Value
parameter to the bus value as a named tuple, e.g. (s1 = 5, s2 = 4)
.
It is important to distinguish between block parameters, which are set in the Parameters tab and are global variables in the source code Engee Function, from the Engee global variables in the Variables window ![]() |
Let’s consider the case when global variables fully correspond to the variables in the source code. For example, we have defined three global variables , , with the values 1
, 2
, 3
respectively. All three global variables are used as parameters of the block Engee Function:
Then the source code with parameters added would look like this:
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
This source code defines the Block
structure, which allows you to customise the behaviour of the component. The example uses the block parameter names a_param
, b_param
, and c_param
to define the parameters of the structure , , and respectively. The code also defines a method function(c::Block)(t::Real, x::Vector{<:Real})
that scales each element of the vector x
by the block parameter , adds , and divides the result by . This allows flexibility to modify and normalise the vector x
according to the values of the block parameters.
Consider the case where only the Parameters tab parameters are used:
struct Block <: AbstractCausalComponent end
function (c::Block)(t::Real, x::Vector{<:Real})
return (a_param .* x .+ b_param) ./ c_param
end
These parameters will be global variables in the block code. This means that they will always be available in any part of the block code without having to define them repeatedly in each function or code block. This greatly simplifies the code and makes it easy to change parameters in the Parameters tab without affecting the source code.
Consider the case where the parameters are not completely consistent with the source code. For example, there is a parameter a_param
equal to 100
. In the source code there is a field of the structure :
struct Block <: AbstractCausalComponent
a::Real
function Block()
new(a_param/10)
end
end
function (c::Block)(t::Real, x::Vector{<:Real})
c.a
end
In this code, the a_param
parameter is used to initialise the field of the struct Block
structure through its constructor, which divides the parameter value by 10
. In this case, the block functor returns the field .
Variables can be made global right in the source code:
a = 1;
b = 2;
с = 3;
struct Block <: AbstractCausalComponent; end
function (c::Block)(t::Real, x::Vector{<:Real})
return (a .* x .+ b) ./ c
end
The code creates a Block
structure and defines a function that performs mathematical operations on the global variables , and , applying them to the x
variable.
If you don’t want to use parameters from the Parameters tab, you can initialise the variables directly in the source code:
struct Block <: AbstractCausalComponent; end
function (c::Block)(t::Real, x)
a = 1;
b = 2;
c = 3;
return (a .* x .+ b) ./ c
end
The code creates a Block
structure and defines a function that performs mathematical operations on the local variables , and , applying them to the x
variable.
Let’s consider the most efficient and correct way of execution. To make the block Engee Function work correctly, set the Use external cache for non-scalar output checkbox on the Main tab of the block. Engee Function. The source code will look like this:
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
This code can be written only in a cell *Common code*because standard cells do not allow editing the component structure definition and the functor signature. |
The fields , and of the structure represent the parameters of the block, which are strictly type-checked in the constructor. Each of these parameters must be a real scalar (Real
) to ensure accurate calculations at runtime.
The Block()
constructor checks the types of the passed parameters , and . If at least one of them is not a real scalar or has mismatched dimensions (must be scalars), the constructor generates an error with an appropriate message. After checking, the constructor initialises the structure fields with the values , and of the specified types Ta
, Tb
and Tc
.
A computable function defined for Block instances takes the time t
, an external cache and a vector x
of real numbers. This function uses the fields , and of the Block
structure to compute values which are then written to the cache. This avoids unnecessary memory allocations by reusing the provided cache.
Thus, the Block
structure provides strict data type management and efficient usage of resources through the usage of an external cache to store the results of calculations.
If you want to change the parameters of a block, you must use a mutable
structure. Let’s look at an example:
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
The Counter
structure is a mutable
data type that is used to count iterations with a given limit and is strictly typed. The limit
and iter
fields of the structure represent parameters of the block:
-
limit
is the limit value of the counter; -
iter
is the current value of the counter.
In the constructor of the structure, the limit
parameters are checked (validated) whether the parameter is a scalar and real data type. After that, the iter
field is initialised with a null value of the corresponding T
type. The update!
function updates the state of the counter , increasing the value of iter
by one each time it is called. If the current value of iter
exceeds limit
, it is reset to zero and allows the counter to cycle back to the initial state.
In the source code of a block Engee Function it is possible to use include referring to external code. This allows variables from external code (if any) to be initialised in the source code.
|
The actual data type, as well as the support for possible data types, depends on the user code within the block. |
Example code
This example presents a simplified implementation of the block Discrete-Time Integrator, based on the integration of Julia code into the Engee model. The direct Euler method is chosen as the integration method. On the Advanced tab of the block Engee Function set the Discrete
value of the Sample time inheritance method parameters. Then fill in the cells of the source code as follows:
-
In the cell 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
-
In the cell. Step method code:
return c.state
-
In the Update method code cell:
c.state += in1 * c.dt * c.gain return c
The following source code will result:
The parameters initial_condition
and k
are initialised in the Parameters tab of the block settings Engee Function:
In the first step of the model simulation, the internal state of the c.state
block is initialised by the value of the initial_condition
parameters.
Then at each step of the calculation the block returns the internal state c.state
as an output and recalculates its value in the update!
method.
The structure of the component is redefined in the cell Common code*instead of in *Component struct code, because a more flexible definition is required: the structure must be mutable and parameterised with a T type corresponding to the state type. The standard definition in Component struct code is only suitable for immutable and non-parameterised structures. |
Annotations
Annotations in Engee Function allow to display parameters of a block directly under its name in the model. To add them, open settings window block Engee Function and go to the Annotation tab. Select the desired block property markers and add them in the text editor.
On this tab:
-
On the left is a list of available parameters (except hidden parameters).
-
On the right is a text editor, where you can specify an annotation with markers in the format
%<ИмяПараметра>
. -
The parameters can be transferred manually, via autocomplete or with the button
.
After exiting the editor (e.g. by clicking outside it), the annotation is applied: the markers are automatically replaced by the actual values of the parameters, and the resulting text is displayed below the block name (or above it, if the name is placed on top).
To delete annotations, the corresponding marker in the editor must be deleted.
Available markers
The property marker is automatically replaced by the current value of the parameters. The following markers are available:
-
*Ports:
%<Inputs>
,%<Outputs>
- number of input and output ports;%<InputPort1Type>
,%<OutputPort1Type>
,%<InputPort1Size>
,%<OutputPort1Size>
- data type and dimensionality of signals. -
*Temporal characteristics:
%<SampleTime>
- discretisation;%<SampleTimeInheritanceMethod>
- discretisation inheritance method. -
*Code blocks:
%<ComponentStructCode>
,%<StepMethodCode>
- step structure and method code. -
Parameters:
%<Parameters>
,%<Parameter1Name>
,%<Parameter1Value>
- names and values of parameters. -
Inclusion flags:
%<DefineComponentStruct>
,%<UseCommonCode>
,%<DefineStepMethod>
,%<DefineUpdateMethod>
,%<DefineTerminateMethod>
- inclusion of corresponding code sections. -
*Reference methods:
%<OverrideTypesInhMethod>
,%<OverrideDimsInhMethod>
,%<OverrideSampleTimeInhMethod>
- type inheritance settings. -
Other:
%<UseExternalCache>
- usage of external cache.