Models represent entities that the RSIS framework can load, connect, and schedule. For those who are familiar with Simulink, this is similar to the idea of a block.
Models can be developed in both Rust & C++. Meson build system integration is provided for C++ models, and C++ models are compiled against the C++17 standard.
Ports are the method through which models are exposed to the framework. The top level of the model interface has a required structure with specific intent:
- inputs
- Represents model inputs
- Each input port can be connected to a single output port
- outputs
- Represents model outputs
- Each output port can be connected to multiple input ports
- data
- Exposes read-only internal state for logging and debugging purposes
- params
- Parameters that can be set before sim initialization
Ports can additionally be further organized into logical groupings; these map directly to structs in Rust & classes in C++. Permissions and intent for these further subdivisions are inheritied from the top level breakdown.
RSIS provides a YAML based interface code generate process to simplify the process of model development. The following are autogenerated for each language:
| Language | Generated Files | Contains |
|---|---|---|
| Rust | *_interface.rs |
Struct and reflection functions |
| C++ | *_interface.hxx |
Class declarations, reflection function declarations |
| C++ | *_interface.cxx |
Class constructor/destructors definitions, reflection function definitions |
Example model interface:
model: Model
ModelIn:
power: {type: Float32, value: 0.0, unit: V, desc: Voltage power input to model }
ModelOut:
counter: {type: Int32, dims: [2], value: 0, desc: Randomly increments when on }
ModelData:
on: {type: Bool, value: false, desc: Model On state }
ModelParams:
turn_on: {type: Float32, value: 3.0, unit: V, desc: Base voltage requirement for turn on }
Model:
inputs: {class: ModelIn }
outputs: {class: ModelOut }
data: {class: ModelData }
params: {class: ModelParams }The interface file is required to have a model element, specifying another element in the file that is the root of the model interface definition. That class is required to have the following elements: inputs, outputs, data, params. Each class member definition must be a dictionary.
The type key in a member definition indicates that the datatype is one of the supported data types, as listed here. The class key in a member definition indicates that the datatype is a user-defined type. The table below lists optional keys that can be defined.
| key | Meaning | Definition | Example |
|---|---|---|---|
| dims | Array dimension. Defaults to scalar | List of positive integers, or [-1] | [2,3,2] |
| unit | Units. Defaults to nothing | See Units | kg^2/m |
| desc | Description. Defaults to nothing | String | Jet engine thrust output |
If the array dimension is [-1], this is interpreted as a variable length one-dimension array. The following table describes how this is transformed:
| Language | Container |
|---|---|
| Rust | Vec<> |
| C++ | std::vector<> |
| Fortran | ?? |
The value key can also be specified for non user defined types, indicating a default value that the member must take.
- Referencing other YAML files
- Multiple model declarations in a single YAML file
- Pointer type definitions
- Possible additional datatype support:
std::array<>void *char8_t(C++20)
- Only adding
#include <complex>when needed. Currently added always
The following table represents how Julia datatypes correspond to datatypes in Rust and C++:
| Julia | Rust | C++ | Fortran 2008 |
|---|---|---|---|
| Char | char | char | character |
| String | String | std::string | character (len=:), allocatable |
| Bool | bool | bool | logical |
| Int8 | i8 | int8_t | integer (int8) |
| Int16 | i16 | int16_t | integer (int16) |
| Int32 | i32 | int32_t | integer (int32) |
| Int64 | i64 | int64_t | integer (int64) |
| UInt8 | u8 | uint8_t | na |
| UInt16 | u16 | uint16_t | na |
| UInt32 | u32 | uint32_t | na |
| UInt64 | u64 | uint64_t | na |
| Float32 | f32 | float | real (real32) |
| Float64 | f64 | double | real (real64) |
| Complex{Float32} | Complex{f32} | std::complex | complex*8 |
| Complex{Float64} | Complex{f64} | std::complex | complex*16 |
Notes:
- Complex values in Rust are supported with the
num-complexcrate. Complex values in C++ are supported with the<complex>header.] - Fortran integration utilizes
use ISO_FORTRAN_ENVto specify integers
The Unitful package is used to define units for ports. Connected ports that both have a defined unit must have matching units (this rule is relaxed if any or both ports don't have a unit). In order to convert between units, it is suggested that the user add a Unit Conversion block provided in the Framework Library.
The default symbols provided by Unitful are immediately available for model usage.
Input & output model ports can be connected via the Julia connect command (See Julia help for more info), creating connection objects in the simulation run-time. During the simulation, the connection objects copy output port data to connected input ports, which are called by the models containing those inputs.
A variety of hooks are exposed for the user to extend:
- initModel
- Called during initialization of the simulation. The user must extend this.
- configModel
- Called when model parameters are updated.
- Called once during each step of the simulation. The user must extend this.
- stepModel
- destroyModel
- Called once during the end of the simulation
Models are compiled as shared libraries that are loaded in during runtime. Library loading is handled within the Julia runtime. Currently, a C API is utilized to pass data from the model libraries into the RSIS core framework, though this may change in the future.
TODO
TODO
TODO