diff --git a/docs/developer/style/cpp-modularization.md b/docs/developer/style/cpp-modularization.md index e5da1bcb697f1ae690412c22bf85761dbcc7592c..dba820df64bb6ae7c15db6fe087b5a563e678f62 100644 --- a/docs/developer/style/cpp-modularization.md +++ b/docs/developer/style/cpp-modularization.md @@ -1 +1,533 @@ -@ todo take from Phabricator \ No newline at end of file +--- +title: Module Structure in c++ +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, modules which use the std::filesystem can't be build! To use the code, please update the compiler to a version > 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++ +const std::string programname; +const std::string toolVersion; +const modi consoleOn; +const modi logOn; +mutable bool plotOn; +bool plotCopyOn; +bool plotDeleteOn; +const bool reportOn; +const bool texOn; +const bool infoOn; +const int ownToolLevel; +const fs::path gnuAccess; +const fs::path inkAccess; +const fs::path logAccess; +const fs::path acxmlAccess; +const fs::path moduleConfAccess; +node& acxml; +const node& 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_file name="TOOLNAME.xml"> + <control_settings description="General control settings for this tool"> + <aircraft_exchange_file_name description="Specify the name of the exchange file"> + <value>csmr-2020.xml</value> + </aircraft_exchange_file_name> + <aircraft_exchange_file_directory description="Specify the direction in which the aircraft exchange file can be found"> + <value>../projects/</value> + </aircraft_exchange_file_directory> + <own_tool_level description="Specify the tool level of this tool"> + <value>3</value> + </own_tool_level> + <console_output description="Selector to specify the console output. Selector: mode_0 (Off) / mode_1 (only out/err/warn) / mode_2 (1 + info) / mode_3 (2 + debug)"> + <value>mode_1</value> + </console_output> + <log_file_output description="Selector to specify the log file output. Selector: mode_0 (Off) / mode_1 (only out/err/warn) / mode_2 (1 + info) / mode_3 (2 + debug)"> + <value>mode_1</value> + </log_file_output> + <plot_output description="Specify the way plotting shall be handled"> + <enable description="Switch to enable plotting. Switch: true (On) / false (Off)"> + <value>true</value> + </enable> + <copy_plotting_files description="Switch if plotting files shall be copied. Switch: true (On) / false (Off)"> + <value>true</value> + </copy_plotting_files> + <delete_plotting_files_from_tool_folder description="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_output description="Switch to generate an HTML report. Switch: true (On) / false (Off)"> + <value>true</value> + </report_output> + <tex_report description="Switch to generate a Tex report. Switch: true (On) / false (Off)"> + <value>true</value> + </tex_report> + <write_info_files description="Switch to generate info files. Switch: true (On) / false (Off)"> + <value>false</value> + </write_info_files> + <log_file description="Specify the name of the log file"> + <value>TOOLNAME.log</value> + </log_file> + <inkscape_path description="Path to the inkscape application (DEFAULT: Use inkscape from the UNICADO repo structure)"> + <value>DEFAULT</value> + </inkscape_path> + <gnuplot_path description="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: + +```c++ +... + +class MyModule : public Module +{ +public: + MyModule(int argc, char *argv[], const std::string& toolName, const std::string& toolVersion); + ~MyModule() = default; + + void initialize(); + void run(); + void update(); + void report(); + void save(); + +private: + ... +} +``` + +The constructor and the five methods `initialize()` , `run()`, `update()`, `report()`, `save()` must be implemented by the user. + +The constructor will be used in the following way: + +``` c++ +MyModule(int argc, char *argv[], const std::string& toolName, const std::string& toolVersion) : + Module(argc, argv, toolName, toolVersion) +{ + // <-- read module config information here +} +``` + +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. + +As an example, the main function will look like: + +```c++ +#include "toolinfo.h" // <-- TOOL_NAME, TOOL_VERSION +#include "MyModule.h" // <-- MyModule class + +int main(int argc, char *argv[]) { + MyModule myModule(argc, argv, TOOL_NAME, TOOL_VERSION); + return myModule.execute(); +} +``` +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. + +Example usage: + +```c++ +modeSelection = Modeselector("mode_2") + +modi selectedMode = modeSelection.get(); // selectedMode = 2 +``` + +## Intermediate level + +### Strategy and StrategySelector + +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++ +class Strategy +{ +public: + virtual void initialize() = 0; + virtual void run() = 0; + virtual void update() = 0; + virtual void report() = 0; + virtual void save() = 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: + +- `setStrategy()` +- `initializeStrategy()` +- `runStrategy()` +- `updateStrategy()` +- `reportStrategy()` +- `saveStrategy()` + +The code for the class: + +```c++ +class StrategySelector +{ +public: + void setStrategy(std::unique_ptr<Strategy> strategy) { + strategy_ = std::move(strategy); + } + void initializeStrategy() { + strategy_->initialize(); + } + void runStrategy() { + strategy_->run(); + } + void updateStrategy() { + strategy_->update(); + } + void reportStrategy() { + strategy_->report(); + } + void saveStrategy() { + strategy_->save(); + } +private: + std::unique_ptr<Strategy<> strategy_; +}; +``` + +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: + +```c++ +#include "strategySelector.h" + + +class Low : public Strategy +{ +public: + Low(const std::shared_ptr<RuntimeIO>& rtIO) : this->rtIO{rtIO} {}; + void initialize() { std::cout << "initialize low" << std::endl; } + void run() { std::cout << "run low" << std::endl; } + void update() { std::cout << "update low" << std::endl; } + void report() { std::cout << "report low" << std::endl; } + void save() { std::cout << "save low" << std::endl; } + + const std::shared_ptr<RuntimeIO>& rtIO; +}; + +class High : public Strategy +{ +public: + High(const std::shared_ptr<RuntimeIO>& rtIO) : this->rtIO{rtIO} {}; + void initialize() { std::cout << "initialize high" << std::endl; } + void run() { std::cout << "run high" << std::endl; } + void update() { std::cout << "update high" << std::endl; } + void report() { std::cout << "report high" << std::endl; } + void save() { std::cout << "save high" << std::endl; } + + const std::shared_ptr<RuntimeIO>& rtIO; +}; + +int main(void) { + + StrategySelector strategyholder; + const std::shared_ptr<RuntimeIO> rtIO = std::make_shared<RuntimeIO>(.....); + strategyholder.setStrategy(std::make_unique<Low>(rtIO)); + + strategyholder.runStrategy(); + + strategyholder.setStrategy(std::make_unique<High>()); + strategyholder.runStrategy(); + strategyholder.saveStrategy(); + return 0; +} +``` + +Output generated: + +```bash +$ ./a.exe +run low +run high +save high +``` +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: + +- `strategyptr` - `std::unique_ptr<Strategy>(const std::shared_ptr<RuntimeIO>&` +- `strategyaccess` - `std::function<strategyptr>` + +A routing table is defined in a method inside the derived module class. As an example for empennage_design: + +```c++ +strategyaccess EmpennageDesign::routing_(const std::vector<std::string>& route) { + + /* Routing table */ + std::map<std::string,std::map<std::string,std::map<std::string,strategyaccess>>> table = { + {"TAW", + std::map<std::string,std::map<std::string,strategyaccess>>{ + {"CONVENTIONAL", + std::map<std::string,strategyaccess>{ + {"LOW",[](const std::shared_ptr<RuntimeIO>& arg) {return std::make_unique<LowConventionalTaw>(arg);}}, + } + }, + } + }, + {"BWB", + std::map<std::string,std::map<std::string,strategyaccess>>{ + {"FINS", + std::map<std::string,strategyaccess>{ + {"LOW",[](const std::shared_ptr<RuntimeIO>& arg) {return std::make_unique<LowFinsBwb>(arg);}}, + {"MID",[](const std::shared_ptr<RuntimeIO>& arg) {return std::make_unique<MidFinsBwb>(arg);}} + } + } + } + } + }; + + return table[route.at(0)][route.at(1)][route.at(2)]; +} +``` + +`[](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 + +```c++ +EmpennageDesign::EmpennageDesing(...) : Module(...) { + // 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 */ +namespace low { +class ConventionalData +{ +public: + // ... +}; + +} // namespace low +/* LOW FIDELITY DATA END */ + +/* HIGHER FIDELITY DATA START */ +namespace higher { +class ConventionalData : public low::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 */ + +namespace low { + +class ConventionalConfig +{ +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! + + + +