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. 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:
# 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
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.
1 // remove whitespace at beginning of line (if there is any)
2 // if first character is a '#', skip line (line[0] == '#')
3 // if line does not contain a '=' sign, skip line
4 // parse parameter name
5 // remove trailing spaces from parameterName
6 if (parameterName.find_first_of(" \t") != std::string::npos)
7 {
8 parameterName.erase(parameterName.find_first_of(" \t"));
9 }
10 // parse value
11 // remove whitespace at beginning of value
12 // remove comments at end of value
13 // remove whitespace at end of value
14 // parse actual value and set corresponding parameter
15 if (parameterName == "endTime")
16 {
17 ...
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:
1#pragma once
2
3#include <iostream>
4#include <array>
5
6/** All settings that parametrize a simulation run.
7 */
8struct Settings
9{
10 std::array<int,2> nCells; //< number of cells in x and y direction
11 std::array<double,2> physicalSize; //< physical size of the domain
12 double re = 1000; //< reynolds number
13 double endTime = 10.0; //< end time of the simulation
14 double tau = 0.5; //< safety factor for time step width
15 double maximumDt = 0.1; //< maximum time step width
16
17 std::array<double,2> g{0., 0.}; //< external forces
18
19 bool useDonorCell = false; //< if the donor cell scheme schould be used
20 double alpha = 0.5; //< factor for donor-cell scheme
21
22 std::array<double,2> dirichletBcBottom; //< prescribed values of u,v at bottom of domain
23 std::array<double,2> dirichletBcTop; //< prescribed values of u,v at top of domain
24 std::array<double,2> dirichletBcLeft; //< prescribed values of u,v at left of domain
25 std::array<double,2> dirichletBcRight; //< prescribed values of u,v at right of domain
26
27 std::string pressureSolver = "SOR"; //< which pressure solver to use, "GaussSeidel" or "SOR"
28 double omega = 1.0; //< overrelaxation factor
29 double epsilon = 1e-5; //< tolerance for the residual in the pressure solver
30 int maximumNumberOfIterations = 1e5; //< maximum number of iterations in the solver
31
32 //! parse a text file with settings, each line contains "<parameterName> = <value>"
33 void loadFromFile(std::string filename);
34
35 //! output all settings to console
36 void printSettings();
37};
Note, the data type std::array<int,2> is the proper C++ way of defining a two-element array of integers. Similarly, std::array<double,2> applies for double values.
The corresponding settings.cpp starts with
1#include "settings.h"
2#include <fstream>
3#include <iomanip>
4
5void Settings::loadFromFile(std::string filename)
6{
7 ...
8 else if (parameterName == "nCellsY")
9 {
10 nCells[1] = atoi(value.c_str());
11 }
12 ...
13}
14
15void Settings::printSettings()
16{
17 std::cout << "Settings: " << std::endl
18 << " physicalSize: " << physicalSize[0] << " x " << physicalSize[1] << ", nCells: " << nCells[0] << " x " << nCells[1] << std::endl
19 << " endTime: " << endTime << " s, re: " << re << ", g: (" << g[0] << "," << g[1] << "), tau: " << tau << ", maximum dt: " << maximumDt << std::endl
20 << " dirichletBC: bottom: (" << dirichletBcBottom[0] << "," << dirichletBcBottom[1] << ")"
21 << ", top: (" << dirichletBcTop[0] << "," << dirichletBcTop[1] << ")"
22 << ", left: (" << dirichletBcLeft[0] << "," << dirichletBcLeft[1] << ")"
23 << ", right: (" << dirichletBcRight[0] << "," << dirichletBcRight[1] << ")" << std::endl
24 << " useDonorCell: " << std::boolalpha << useDonorCell << ", alpha: " << alpha << std::endl
25 << " pressureSolver: " << pressureSolver << ", omega: " << omega << ", epsilon: " << epsilon << ", maximumNumberOfIterations: " << maximumNumberOfIterations << std::endl;
26}
Remember to add settings.cpp to CMakeLists.txt and call the methods somewhere (propably in main.cpp), e.g. like so:
1#include "settings.h"
2...
3Settings settings;
4// load settings from file
5settings.loadFromFile(filename);
6
7// display all settings on console
8settings.printSettings();