summary:Explains the structure of modularized UNICADO c++ modules
authors:
-Christopher Ruwisch
-Katrin Bistreck
date:2024-10-14
---
# Module Structure in c++
## Preface
This page shall give you an overview of how c++ modules in the UNICADO framework look like. The structure is valid for modularized modules (all design and analysis modules of Unicado v3.0.0). All header files which are named in the following can be found in the library [moduleBasics](#).
!!! attention
Due to a bug in versions of compilerversion <10.2.0,moduleswhichusethestd::filesystemcan'tbebuild!Tousethecode,pleaseupdatethecompilertoaversion> 10.2.0. Otherwise disable building the moduleBasics library.
## Overview
The modules are structured into three levels:
- Top level - usage of `Module` for input/output handling and program flow
- Intermediate level - usage of `Strategy` and `StrategySelector` for routing through your module to lowest level
- Low level - implementation of your fidelities, report generation and plotting
## Top level
### RuntimeIO
The RuntimeIO class (`runtimeIO.h`, header-only) handles input/output (IO) operations and provides functionality related to file and directory management.
There are several public read-only member variables which can be accessed directly:
``` c++
conststd::stringprogramname;
conststd::stringtoolVersion;
constmodiconsoleOn;
constmodilogOn;
mutableboolplotOn;
boolplotCopyOn;
boolplotDeleteOn;
constboolreportOn;
constbooltexOn;
constboolinfoOn;
constintownToolLevel;
constfs::pathgnuAccess;
constfs::pathinkAccess;
constfs::pathlogAccess;
constfs::pathacxmlAccess;
constfs::pathmoduleConfAccess;
node&acxml;
constnode&moduleConfig;
```
Node references:
-`acxml` - access for Aircraft Exchange File (opened at module start)
-`moduleConfig` - access for module config (opened at module start)
!!! IMPORTANT
`acxml` and `moduleConfig` are not saved and closed at the end. This must be done by the developer. The reason for this change is due to aspects regarding implementation of python elements during code and possible missusage of aixml::openDocument, aixml::saveDocument and aixml::closeDocument
The class provides several public methods for various operations:
-`showRuntime()` - Prints runtime information, e.g. program name, enabled flags etc.
-`showDirectories()` - shows stored directories in directories_ map.
-`getIODir()`, `getGeometryDir()` and other similar functions which return the path w/o ending seperator
-`createGeometryDir()`, `createAirfoilDataDir()` and other similar functions which create the directoryif not existent
-`checkFileExistence()` - checks if a file exists by specific argument
-`create_common_directories` - creates standard output directories if not existent
-`addDir()` - adds a directory to the directories_ map and creates it if not existent. The key will be specified in upper case letters
-`reopenAcXML()` - reopens the aircraftXML - please make sure to (save and) close the acxml before
-`saveAcXML()` - saves the current acxml node from the runtimeIO instantiation
-`closeAcXML()` - closes the current acxml node from the runtimeIO instantiation
-`saveAndCloseAcXML()` - runs saveAcXML() and closeAcXML() (in this order)
-`saveModuleConfig()` - saves the current moduleConfig node from the runtimeIO instantiation
-`closeModuleConfig()` - closes the current moduleConfig node from the runtimeIO instantiation
-`saveAndCloseModuleConfig()` - runs saveModuleConfig() and closeModuleConfig() (in this order)
-`aircraft_type()` - returns current aircraft type
-`aircraft_model()` - returns current aircraft model
-`aircraft_configuration_type()` - returns current aircraft configuration type
-`aerodynamic_technologies()` - returns whether aerodynamic technologies are integrated
-`aircraft_energy_carrier_type` - returns used energy carrier (in case more than one type is use: hybrid)
-`aircraft_energy_carriers` - returns ,ap of used energy carriers (ID; fuel type, density, volumetric energy density, gravimetric energy density)
-`aircraft_propulsor_parents` - returns map of propulsor parents and number of propulsors mounted to this parent
-`get_fuel_type`, `get_fuel_density` and other similar functions returns the fuel name / density and other properties of a given energy carrier ID from the aircraft XML
The class also handles the IO Information if the flag `info` is set to true.
Since the class access the specific config XML-file, this file must be of the following structure:
```xml
<?xml version="1.0" encoding="utf-8" ?>
<module_configuration_filename="TOOLNAME.xml">
<control_settingsdescription="General control settings for this tool">
<aircraft_exchange_file_namedescription="Specify the name of the exchange file">
<value>csmr-2020.xml</value>
</aircraft_exchange_file_name>
<aircraft_exchange_file_directorydescription="Specify the direction in which the aircraft exchange file can be found">
<value>../projects/</value>
</aircraft_exchange_file_directory>
<own_tool_leveldescription="Specify the tool level of this tool">
<plot_outputdescription="Specify the way plotting shall be handled">
<enabledescription="Switch to enable plotting. Switch: true (On) / false (Off)">
<value>true</value>
</enable>
<copy_plotting_filesdescription="Switch if plotting files shall be copied. Switch: true (On) / false (Off)">
<value>true</value>
</copy_plotting_files>
<delete_plotting_files_from_tool_folderdescription="Switch if plotting files shall be deleted from folder. Switch: true (On) / false (Off)">
<value>true</value>
</delete_plotting_files_from_tool_folder>
</plot_output>
<report_outputdescription="Switch to generate an HTML report. Switch: true (On) / false (Off)">
<value>true</value>
</report_output>
<tex_reportdescription="Switch to generate a Tex report. Switch: true (On) / false (Off)">
<value>true</value>
</tex_report>
<write_info_filesdescription="Switch to generate info files. Switch: true (On) / false (Off)">
<value>false</value>
</write_info_files>
<log_filedescription="Specify the name of the log file">
<value>TOOLNAME.log</value>
</log_file>
<inkscape_pathdescription="Path to the inkscape application (DEFAULT: Use inkscape from the UNICADO repo structure)">
<value>DEFAULT</value>
</inkscape_path>
<gnuplot_pathdescription="Path to the gnuplot application (DEFAULT: Use gnuplot from the UNICADO repo structure)">
<value>DEFAULT</value>
</gnuplot_path>
</control_settings>
<program_settings>
<!-- module specific>
</program_settings>
</module_configuration_file>
```
The **module config** XML-File will contains all information about the module configuration including (partial) the routing through your module until you reach the fidelity level. It can be very specific to your module needs.
Since the module config data is module specific, the developer has to read it at the constructor on the top level of each module.
!!! NOTE
It is possible to use allowed none SI Units in the module configuration file - see allowed units.
!!! attention
The aircraft exchange file is SI Units only!
### Module
The Module class (`module.h`, header-only) describes the basic behaviour of each module by being a template class. It structures the controlflow by five pure virtual functions:
-`initialize()` - responsible for initializing the module
-`run()` - responsible for running the module
-`update()` - responsible for updating generated data
-`report()` - responsible for report generation
-`save()` - responsible for saving data
The class has a protected instantiation of RuntimeIO as a smart-pointer `rtIO_`. This instantiation can be received by `getRuntimeIO()` which returns a shared_ptr of RuntimeIO (handle with care).
The inheritance on the top level of a new module is **MANDATORY**.
A basic usage of the module class can be seen below:
This constructor above calls module constructor, which will provide an instantiation of `myRuntimeInfo` and `rtIO_`.
The methods `initialize()`, `run()`, `update()`, `report()` and `save()` will be called in the method execute. In this method, those pure virtual methods are included in a `try catch` context, which will handle `throws` within each underlying function / method.
The `argc`, `argv` values are for commandline usage with the option `-c` (please be aware of small letter). The option lets you specify the path from the executable to the basic configuration file (e.g. *massEstimation_conf.xml*). *Please be aware that the path must include the filename as well.* If no option is used, the base path (*./<tool name>_conf.xml*) is used then.
### Modeselector
The header file `modeselector.h` is used within `module.h` for `mode_<x>`-specific strings like `mode_0`, `mode_1`, ... `mode_9`. The number of modes can be extended (initial 10 modes allowed - 0..9) by extending the `std::map<std::string, modi> selection_`. This class must be initialize at construction, since it has no possibility to change the mode afterwards due to its purpose to read only data from a config file which should not be able to be changed later. To get the selected mode of a type `modi` (overlays `int`) a `get()` method is provided.
The intermediate level, which describes the routing from top to low level, uses the so called **Strategy Design Pattern**. This pattern allows to set a specific strategy at first place and let it run through Selector, in this case the `StrategySelector` class (`strategySelector.h`, header-only).
Each strategy, such as the low-fidelity strategy or high-fidelity strategy, is implemented as a class that inherits from the base class `Strategy`. The `Strategy` base class defines the following pure virtual methods:
-`initialize()`
-`run()`
-`update()`
-`report()`
-`save()`
The code for the class:
```c++
classStrategy
{
public:
virtualvoidinitialize()=0;
virtualvoidrun()=0;
virtualvoidupdate()=0;
virtualvoidreport()=0;
virtualvoidsave()=0;
virtual~Strategy(){};
};
```
The `StrategySelector` class serves as a selector for strategies. It manages a dynamic allocation of a strategy using a `std::unique_ptr`. The class provides member functions to set the strategy, initialize the strategy with a given configuration class, run, update, report and save the strategy. Methods for this are:
The Strategy and StrategySelector classes are given as basic templates which should be used. However, an adaption of Input/Output types might be necessary but must be clearly stated.
To use these classes, a minimal example can be seen below:
With `setStrategy`, a strategy will be selected and the RuntimeIO shared smart pointer is handed over to the strategy as a reference. All other calls via `strategyholder` will call methods from the *Low class strategy*. However, the strategy is changed after the first call of `runStrategy`, so below that second `setStrategy` statement, Methods within *High Strategy* are called.
The strategy is selected via a routing table system. To improve readability for the routing table, the file `strategySelector.h` provides two overlays:
`[](const std::shared_ptr<RuntimeIO>& arg) {return std::make_unique<LowFinsBwb>(arg)` is a lambda function which handle is returned from the routing_ table.
Inside the module constructor, the strategy is set by
// read route from configuration file and store into std::vector<std::string> route_; name of vector might differ if there are more than one strategy to call
strategy_.setStrategy(routing_(route)(rtIO_));
...}
```
The parameter for strategy set is called by invoking `routing_(...)` with the `route` vector which returns the handle for the selected strategy from the routing table according to the route. With `...(rtIO_)`, the returned handle is called with the `RuntimeIO` object `rtIO_` as an argument. The result is than handed over to the setStrategy method.
If you'd like to add your own module, you can choose the structure of your routing table freely. Existing UNICADO modules use for example following layers:
- aircraft configuration (tube and wing, BWB...)
- fuel type (kerosene, hydrogen...)
- fidelity level (see [fidelities](#fidelities))
- method name
- ...
### The Fidelities {#fidelities}
The fidelity of each methods can be classified as low, mid, higher, high and own as follows:
-`low` - empirical methods
-`mid` - semi-empirical methods
-`higher` - analytical methods
-`high` - numerical methods
-`own` - own method (experimental)
## Low level
!!!note
The low level implementations members should be public only. This has different reasons:
1. There is no method next to the current
2. To keep the overhead of the class as small as possible (no setter/getter methods which must be provided for private members)
3. Due to pep8 conventions (python normally has no private / protected members)
4. isocpp: all private or all public - no mixture
On the low level, the implementation of the algorithms is focused. Here you can structure your implementations according to the five base methods:
-`initialize` - initialize your module to your needs and your output data by reading and preparing the data.
-`run` - Here happens your wizardy stuff, sometimes muggle-like, sometime not 🧙.
-`update` - lets you call the update methods within the IOData class
-`save` - if you opened any specific document during your module execution, you can save the data within this method and close the data.
### IO Data
The IO Data includes the module specific data from the *Aircraft Exchange File* (Input-Output) and the *Module Configuration File* (Input). The data from the Exchange file is stored within a dedicated data class for a specific method. The `program_settings` of the config file are also stored in a dedicated config class for a specific method.
Module data which is used within the execution can be stored here and must have methods to update specific parts of the `aircraft_exchange_file`. There might be data for xml and data for non-xml at the current state.
As an example for the data part:
```c++
/* LOW FIDELITY DATA START */
namespacelow{
classConventionalData
{
public:
// ...
};
}// namespace low
/* LOW FIDELITY DATA END */
/* HIGHER FIDELITY DATA START */
namespacehigher{
classConventionalData:publiclow::ConventionalData
{
public:
//....
}
}// namespace higher
/* HIGHER FIDELITY DATA END */
```
For the configuration data from the specific config xml, it is recommended that you have `read` methods for each element and also a method `readAll` which calls all `read` methods. If using *endnodes* - make sure that you use `EndnodeReadOnly` for configuration file content. If your configuration file holds common blocks which are used in more than one module - it is recommended to cluster the read data in an external class which is instantiated in the config class.
As an example:
```c++
/* LOW FIDELITY CONFIGS START */
namespacelow{
classConventionalConfig
{
public:
// your elements from the configuration file
};
}// namespace low
/* LOW FIDELITY CONFIGS END */
```
### Report
For report generation, a dedicated Report class (`report.h`) can be used. It offers access to HTML and Tex streams. Public methods are the following:
-`Report` - Constructor
-`setAircraftName` - Set the aircraftname for report documents
-`generateReports` - generates reports based on settings from RuntimeIO (in case a module writes more than one report, individual names can be used)
-`htmlReportStream` - returns the html report output stream
-`texReportStream` - returns the tex report output stream
-`generateHtmlReport` - generates the html report with written data to the htmlReportStream
-`generateTexReport` - generates the tex report with written data to the htmlReportStream
-`reportName` - returns standard name of the report
- Legacy Method `addPlot` - adds a plot to the plots map by the name of the plot in case `plot.h` is used
All methods for a report must be on the low level for a specific fidelity in a seperate `...Report.cpp` File. Report is an object of the main specific fidelity class and NOT an inherited class. Within your html report, can add generated plots by using the `image` function of `html.h`
### Plot
!!!attention
The methods provided by `plot.h` are legacy methods. Please use [matplot++](https://alandefreitas.github.io/matplotplusplus/) for plot generating!
The Plot class (`plot.h`) is a basic frame for SVG Plot generation. There are multiple public methods:
-`Plot` - Constructor with Name
-`getPlotName` - Get the Name of the plot object
-`getDataDestination` - Get the std::filesystem::path of the dataStream (optional)
-`getSvgDestination` - Get the std::filesystem::path of the svg (optional)
-`getScriptDestination` - Get the std::filesystem::path of the script (optional)
-`getDataStream` - Get the data stringstream
-`generatePlotData` - Generates plot data which is in the data stringstream
-`getScriptStream` - Get the script stringstream
-`generatePlotScript` - Generates plot script which is in the script stringstream
-`generateSvg` - generates .svg based on the plotname in the given directory based on reference of a vector of unique pointers which stores svgObjects.
All methods for a report must be on the low level for a specific fidelity in a seperate `...Plot.cpp` File. Plot is an object inside a method from the main specific fidelity class class and NOT an inherited class.
!!! note
In case `plot.h` is used: Plots which are not added to the report (not part of the map `plots`) will not appear on the generated report!