Engee documentation

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 a variety of 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 means 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.:

vectorization x 1

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 a size of 2x2 and the other has a size of 1x2, then when added together 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] it 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.:

broadcast 1

broadcast 2

The output result of the block will be [5, 7; 7, 9], because 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 BusSignal type. This type allows you to set the bus structure for blocks that work with similar data. Named tuples (NamedTuple) are used as bus objects in Engee, for example:

bus = (s1 = 4, s2 = 5.5, s3 = [1, 2, 3])

The 'BusSignal` type is parameterized by signal names (Names), base types (BaseTypes), and dimensions (`Dims'). For example, a three-signal bus can be described as:

bus_type = BusSignal{(:s1, :s2, :s3), Tuple{Int, Float64, Vector{Int}}, ((), (), (3,))}

here:

  • :s1, :s2, :s3 are the names of the signals;

  • Tuple{Int, Float64, Vector{Int}} — signal data types;

  • (), (), (3,) — the dimensions of the signals (for example, 's3' is an array of length 3).

    Any characters other than quotation marks (") are allowed in the name of the signal and bus.

You can create bus objects through functions or set the bus type as a variable to use in block settings. To simplify working with bus types, functions are available that extract names, data types, and dimensions.:

get_names_types_dims(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}

get_bus_names(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}

get_bus_types(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}

get_bus_dimensions(::Type{BusSignal{Names, Types, Dimensions}}) where {Names, Types, Dimensions}

The first function allows you to get information about all bus parameters from the bus type at once, and the next three functions get information about names, types and dimensions of the bus 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 get_bus_signal_type method 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, specify the full type of embedded bus 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 allow you to create tire objects, ensuring that their structure matches a given type of tire. The following options can be used to create tire objects:

  • With a named tuple:

    BusSignal{Names, Types, Dimensions}(x::NamedTuple{TupleNames, TupleTypes}) where {Names, Types, Dimensions, TupleNames, TupleTypes}
  • With named arguments:

    BusSignal{Names, Types, Dimensions}(kwargs...) where {Names, Types, Dimensions}
  • With a regular tuple:

    BusSignal{Names, Types, Dimensions}(x::Tuple) where {Names, Types, Dimensions}
example of creating a bus object

Consider creating a bus object with the type:

bus_type = BusSignal{(:s1, :s2), Tuple{Int64, Float64}, ((), ())}
  • For a named tuple:

    bus1 = bus_type((s1 = 5, s2 = 6.4))

    A named tuple is passed here, the structure of which fully corresponds to the type `bus_type'.

  • For named arguments:

    bus2 = bus_type(s1 = 5, s2 = 6.4)

    Here, the bus structure is created from named arguments. The names of the signals ('s1`, s2) and their types must match the definition of `bus_type'.

  • For a regular tuple:

    bus3 = bus_type(5, 6.4)

    Here, the arguments are passed in the order defined by the bus_type type. The number, types, and sizes of arguments must match the bus parameters.

So, in each example, a bus of the same type `bus_type' will be created. However, it should be taken into account that:

  • For bus1 and bus2, the names, types, and sizes of the signals matched the bus parameters.;

  • For bus3, the number, types, and sizes of arguments passed corresponded to the bus type requirements.


For blocks such as Constant, you can specify the value of the bus type through the parameter Output data types, and then set its structure in Output bus type.

user bus signal

For example, let bus_value = (a = 10, b = [2.5, 3.5], c = (x = 1, y = 2))`. Then its type is set as:

bus_type = BusSignal{(:a, :b, :c), Tuple{Int, Vector{Float64}, NamedTuple{(:x, :y), Tuple{Int, Int}}, (), (2,), ((), ())}}

A list of blocks that support working with custom bus types: