Settings parser ----------------------------- After having worked on the build infrastructure, you can now focus on some real programming. We will start with parsing the parameters for the simulation from a file. Program the parser ^^^^^^^^^^^^^^^^^^^^ Open the code again from Sec. :ref:`multiple_source_files` and complete the `loadFromFile` function such that it parses the parameter names and their values from the parameters file (print them in the console for the moment). You can use the plain `g++` command again for compilation until it works. You already have the test file `parameters.txt` as follows: .. code-block:: bash # settings endTime=1.5 # duration of the simulation re = 1000 # Reynolds number The parser should account for the following: * Empty lines * Comments starting with '#' should be ignored. * There may or may not be whitespace (space or tab) around the '=' sign. This is mainly string manipulation. The string functions `find `_, `find_first_of(\" \\t\") `_ , `find_first_not_of(\" \\t\") `_, `substr `_, `erase `_ and the functions `atof `_ and `atoi `_ can be useful. For instance, you can convert a `std::string` to an integer by .. code-block:: c++ std::string value = "1.5 other ignored text"; int i = atoi(value.c_str()); If you like to have a hint what to do, consider the following plan, otherwise do it on your own. .. code-block:: c++ :linenos: // remove whitespace at beginning of line (if there is any) // if first character is a '#', skip line (line[0] == '#') // if line does not contain a '=' sign, skip line // parse parameter name // remove trailing spaces from parameterName if (parameterName.find_first_of(" \t") != std::string::npos) { parameterName.erase(parameterName.find_first_of(" \t")); } // parse value // remove whitespace at beginning of value // remove comments at end of value // remove whitespace at end of value // parse actual value and set corresponding parameter if (parameterName == "endTime") { ... Integration into main code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When you're confident that the piece of code works you can integrate it to the project. A suggestion how to do this is given below. Generally from now on, you are free to structure your code as you like. Often, it is easier to follow one's own ideas than sticking to someone else's approach. However, there will be some more hints and template code for some subtasks in this tutorial that will require more and more adaptation if you want to use it and move further away from the suggestions. As you will see on the submission page for exercise 1, the program is required to parse several parameters. `This is an example parameter file `_. **It is important to use the parameter names of this file!** The suggestion is to create a `struct` `Settings` with all parameter values with default values and a method to parse the actual values from a given file. The header file `settings.h` will contain: .. code-block:: c++ :linenos: #pragma once #include #include /** All settings that parametrize a simulation run. */ struct Settings { std::array nCells; //< number of cells in x and y direction std::array physicalSize; //< physical size of the domain double re = 1000; //< reynolds number double endTime = 10.0; //< end time of the simulation double tau = 0.5; //< safety factor for time step width double maximumDt = 0.1; //< maximum time step width std::array g{0., 0.}; //< external forces bool useDonorCell = false; //< if the donor cell scheme schould be used double alpha = 0.5; //< factor for donor-cell scheme std::array dirichletBcBottom; //< prescribed values of u,v at bottom of domain std::array dirichletBcTop; //< prescribed values of u,v at top of domain std::array dirichletBcLeft; //< prescribed values of u,v at left of domain std::array dirichletBcRight; //< prescribed values of u,v at right of domain std::string pressureSolver = "SOR"; //< which pressure solver to use, "GaussSeidel" or "SOR" double omega = 1.0; //< overrelaxation factor double epsilon = 1e-5; //< tolerance for the residual in the pressure solver int maximumNumberOfIterations = 1e5; //< maximum number of iterations in the solver //! parse a text file with settings, each line contains " = " void loadFromFile(std::string filename); //! output all settings to console void printSettings(); }; Note, the data type `std::array` is the proper C++ way of defining a two-element array of integers. Similarly, `std::array` applies for `double` values. The corresponding `settings.cpp` starts with .. code-block:: c++ :linenos: #include "settings.h" #include #include void Settings::loadFromFile(std::string filename) { ... else if (parameterName == "nCellsY") { nCells[1] = atoi(value.c_str()); } ... } void Settings::printSettings() { std::cout << "Settings: " << std::endl << " physicalSize: " << physicalSize[0] << " x " << physicalSize[1] << ", nCells: " << nCells[0] << " x " << nCells[1] << std::endl << " endTime: " << endTime << " s, re: " << re << ", g: (" << g[0] << "," << g[1] << "), tau: " << tau << ", maximum dt: " << maximumDt << std::endl << " dirichletBC: bottom: (" << dirichletBcBottom[0] << "," << dirichletBcBottom[1] << ")" << ", top: (" << dirichletBcTop[0] << "," << dirichletBcTop[1] << ")" << ", left: (" << dirichletBcLeft[0] << "," << dirichletBcLeft[1] << ")" << ", right: (" << dirichletBcRight[0] << "," << dirichletBcRight[1] << ")" << std::endl << " useDonorCell: " << std::boolalpha << useDonorCell << ", alpha: " << alpha << std::endl << " pressureSolver: " << pressureSolver << ", omega: " << omega << ", epsilon: " << epsilon << ", maximumNumberOfIterations: " << maximumNumberOfIterations << std::endl; } Remember to add `settings.cpp` to `CMakeLists.txt` and call the methods somewhere (propably in `main.cpp`), e.g. like so: .. code-block:: c++ :linenos: #include "settings.h" ... Settings settings; // load settings from file settings.loadFromFile(filename); // display all settings on console settings.printSettings();