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();