Working with signals of different dimensions
Vectorization and broadcast are important techniques when working with models in Engee that allow efficient processing of signals of different dimensions. Using vectorization, you can perform calculations on arrays entirely, avoiding explicit element loops, which speeds up calculations and simplifies the code. Broadcast, in turn, provides automatic dimension matching between arrays when performing operations, allowing you to conveniently work with data in different formats.
The use of vectorization and broadcast in modeling allows efficient processing and analysis of multidimensional data. Vectorization reduces the number of operations by working with entire arrays, and broadcast eliminates the need for manual dimension matching.
Vectorization in modeling
Vectorization in modeling allows you to immediately evaluate the behavior of a model for multiple parameter values, which is especially useful in sensitivity analysis and testing. This means that instead of running separate runs for each value, you can set a parameter matrix and get results for all values at the same time.
For example, if you set the Amplitude parameter of the block Sine Wave the matrix of values [[1, 2]; [3, 4]], this will mean that the block generates several signals with the amplitudes specified in the matrix. The image below shows how the Sine Wave block generates several signals with a given amplitude, which allows you to immediately see the result for different values of the Amplitude parameter.:

Broadcast
Broadcast allows you to coordinate the dimensions of arrays for performing operations on them. In Engee, this mechanism automatically expands the dimensions of arrays, allowing operations to be performed on seemingly incompatible arrays. For example, if one array has the size 2x2 And the other one — 1x2, then when added, the arrays will be reduced to compatible dimensions.
Consider an example with two Constant blocks and one Add block. The Constant block with the parameter [3, 4; 5, 6] is added to the Constant block with the parameter [2, 3]. At the same time, broadcast automatically adjusts arrays to compatible sizes and performs element-wise addition.:
|
|
The output result of the block will be [5, 7; 7, 9], since the lines were automatically matched. This approach makes it easy to perform operations between arrays of different sizes and greatly simplifies working with multidimensional signals in modeling.
Custom Tire Types
| Before studying the section on custom tire types, it is recommended that you first familiarize yourself with the blocks Bus Creator and Bus Selector. |
In Engee, there is support for custom data types for buses represented by the type BusSignal. This type allows you to set the bus structure for blocks that work with similar data. Named tuples are used as bus objects in Engee (NamedTuple), for example:
bus = (s1 = 4, s2 = 5.5, s3 = [1, 2, 3])
Type BusSignal it is parameterized by the names of the signals (Names), basic types (BaseTypes), dimensions (Dims), and the name of the bus (BusName). For example, a bus with three signals can be described as:
bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Float64}, ((), (), (3,)), :MyBus}
In BaseTypes only basic types are specified (for example, Int, Float64), not containers (for example, Vector{Int}).
The dimensions of arrays are set separately in Dims.
|
Here:
-
:s1,:s2,:s3— signal names; -
Tuple{Int, Float64, Float64}— types of signal data; -
(), (), (3,)— the dimensions of the signals (for example,s3— array of length3). -
MyBus— the name of the tire.Any characters other than quotation marks are allowed in the name of the signal and bus ( ").
There are two ways to work with buses: create bus objects (with specific data) or set the type of bus (structure only, without data) in order to use it in the block settings. These two approaches differ in syntax — curly ones are used. {} and round () staples.
When to use curly and round brackets
| Syntax | When to use | Example |
|---|---|---|
|
To describe a parametric bus type or create a bus object with data |
|
|
To create a bus type value (a structure without data) — to use it in block settings |
|
|
In In |
The signatures and examples of calling these constructors are shown below.
Creating a Bus object
The following structure constructors are used to create a bus object BusSignal:
BusSignal{Names, Types, Dimensions, BusName}(x::NamedTuple{TupleNames, TupleTypes}) where {Names, Types, Dimensions, BusName, TupleNames, TupleTypes}
BusSignal{Names, Types, Dimensions, BusName}(; kwargs...) where {Names, Types, Dimensions, BusName}
BusSignal{Names, Types, Dimensions, BusName}(x::Tuple) where {Names, Types, Dimensions, BusName}
BusSignal{Names, Types, Dimensions, BusName}(varargs...) where {Names, Types, Dimensions, BusName}
Examples:
# via NamedTuple
bus_obj = BusSignal{(:a, :b), Tuple{Int, Float64}, ((), (3,)), :MyBus}((a=1, b=[1.0, 2.0, 3.0]))
# using key arguments
bus_obj = BusSignal{(:x, :y), Tuple{Int, Int}, ((), ()), :Coords}(x=10, y=20)
# through the tuple
bus_obj = BusSignal{(:p, :q), Tuple{Float64, Float64}, ((), ()), :Pair}((1.5, 2.5))
# via varargs
bus_obj = BusSignal{(:m, :n), Tuple{Int, Int}, ((), ()), :Nums}(5, 6)
Creating a type of bus
The following methods are used to create a bus type (a structure without data):
BusSignal(names::Vector{Symbol}, types::Vector{DataType}, dims::Vector{<:Dims}, bus_name::Symbol=:CustomBusSignal)
BusSignal(names::NTuple{N, Symbol}, types::NTuple{N, DataType}, dims::NTuple{N, Dims}, bus_name::Symbol=:CustomBusSignal) where N
Examples:
# through vectors
bus_type = BusSignal([:a, :b], [Int, Float64], [(), (3,)], :MyBus)
# through tuples
bus_type = BusSignal((:x, :y), (Int, Int), ((), ()), :Coords)
# embedded bus
bus_type = BusSignal((:a, :b, :c), (Int, Float64, NamedTuple{(:x, :y), Tuple{Int, Int}}), ((), (2,), ()))
Getting information about the bus
To extract information about the bus parameters, you can use the following functions:
get_names_types_dims(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}
get_bus_names(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}
get_bus_types(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}
get_bus_dimensions(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}
get_bus_name(::Type{BusSignal{Names, Types, Dimensions, BusName}}) where {Names, Types, Dimensions, BusName}
The first function (get_names_types_dims) returns all the bus parameters at once, and the rest allow you to get names, types, dimensions, and the bus name separately.
examples of using functions
bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int64, Float64, Int8}, ((), (2,), (2, 2))}
# An instance of this type of bus could look like this: (s1 = 5, s2 = [4.3, 5.4], s3 = Int8[1 2; 3 4])
get_names_types_dims(but_type)
# Result: ((:s1, :s2, :s3), (Int64, Float64, Int8), ((), (2,), (2, 2))) - a tuple of three tuples, each of which describes its own bus type parameter
get_names(but_type)
# Result: (:s1, :s2, :s3)
get_types(but_type)
# Result: Tuple{Int64, Float64, Int8}, that is, the parameter is still returned in its original form, which is somewhat inconvenient. In one of the next tasks, it will be fixed and the result will be a tuple (Int64, Float64, Int8)
get_dimensions(but_type)
# The result: ((), (2,), (2, 2))
For example, the method get_bus_signal_type converts a named tuple to the appropriate bus type.:
bus = (s1 = 5, s2 = [4.3, 5.4], s3 = (a = 4, b = 5.5))
bus_type = get_bus_signal_type(bus)
# bus_type is equal to BusSignal{(:s1, :s2, :s3), Tuple{Int64, Float64, BusSignal{(:a, :b), Tuple{Int64, Float64}, ((), ())}}, ((), (2,), ())}
|
Nested structures are also supported, where an element of one bus can be another bus (example above). To specify the type of embedded bus, the full type of embedded bus must be specified among the types of its elements. Despite the fact that bus objects in Engee are named tuples, special constructor functions are provided for their creation. These functions ensure that the objects being created match their bus type declaration. |
The following structure constructors can be used to create bus objects BusSignal:
BusSignal{Names, Types, Dimensions, BusName}(x::NamedTuple{TupleNames, TupleTypes}) where {Names, Types, Dimensions, BusName, TupleNames, TupleTypes}
BusSignal{Names, Types, Dimensions, BusName}(; kwargs...) where {Names, Types, Dimensions, BusName}
BusSignal{Names, Types, Dimensions, BusName}(x::Tuple) where {Names, Types, Dimensions, BusName}
BusSignal{Names, Types, Dimensions, BusName}(varargs...) where {Names, Types, Dimensions, BusName}
examples of creating a bus object
First, let’s create the type of bus that the data should correspond to.:
bus_type = BusSignal((:s1, :s2), (Int64, Float64), ((), ()), :MyBus)
Now you can create bus objects of this type in different ways.:
-
With a named tuple:
bus1 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}((s1 = 5, s2 = 6.4))Transmitted
NamedTuple, the structure of which fully corresponds to the typebus_type. -
With named arguments:
bus2 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}(s1 = 5, s2 = 6.4)The bus structure is created from named arguments. Names and types of signals (
s1,s2) must match the parametersbus_type. -
With a regular tuple:
bus3 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}((5, 6.4))The arguments are passed in the order specified by the type.
bus_type. The quantity, types, and dimensions must match the bus parameters. -
Via varargs:
bus4 = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ()), :MyBus}(5, 6.4)Values are passed as positional arguments without packing into a tuple.
Thus, objects of the same bus are created in all the examples. :MyBus. At the same time:
-
For
bus1andbus2The names, types, and sizes of the signals must strictly match the bus parameters. -
For
bus3andbus4The number, types, and sizes of arguments passed must meet the requirements of the bus type.
For blocks such as Constant, you can specify the value of the bus type through the parameter Output data type, and then set its structure in Output bus type.

For example, let’s say the bus value (parameter Constant Value, bus_value) is equal to (a = 10, b = [2.5, 3.5], c = (x = 1, y = 2)), then its type (Output bus type) is set as:
BusSignal((:a,:b,:c),(Int,Float64,BusSignal((:x,:y),(Int,Int),((),()),:InnerBus)),((),(2,),()),:MyBus)
Here:
-
BusSignal((:a,:b,:c), …)— enumeration of bus signals::a,:b,:c. -
The second argument
(Int, Float64, BusSignal:x,:y),(Int,Int),((),(,:InnerBus))— basic types of fields:-
a :: Int— a scalar integer. -
b :: Float64— the basic element type; the vector is set not here, but in Dims. -
c— nested bus with names(:x,:y), types(Int,Int)and dimensions),(; her own name —:InnerBus.
-
-
The third argument
), (2,), (— dimensions (Dims):-
a— scalar(). -
b— vector of length 2(2,), therefore , the value should be of the form[2.5, 3.5]. -
c— scalar embedded bus(). Its internal dimensions are set inside the description:InnerBus.
-
-
The fourth argument
:MyBus— the symbolic name of the tire.
|
A list of blocks that support working with custom bus types: |

