config.cpp 26 KB
Newer Older
1
/*!
2
 * \file Config.cpp
steffen.schotthoefer's avatar
steffen.schotthoefer committed
3
 * \brief Class for different Options in rtsn
4
5
6
7
8
 * \author S. Schotthoefer
 *
 * Disclaimer: This class structure was copied and modifed with open source permission from SU2 v7.0.3 https://su2code.github.io/
 */

9
10
11
#include "common/config.h"
#include "common/globalconstants.h"
#include "common/optionstructure.h"
12
13
#include "toolboxes/errormessages.h"
#include "toolboxes/textprocessingtoolbox.h"
14
15
16
17
18

// externals
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/spdlog.h"
19
20
21
#include <cassert>
#include <filesystem>
#include <fstream>
22
#include <mpi.h>
23
24
25

using namespace std;

26
Config::Config( string case_filename ) {
27

28
    /*--- Set the case name to the base config file name without extension ---*/
29

30
31
32
33
    auto cwd     = std::filesystem::current_path();
    auto relPath = std::filesystem::path( case_filename );
    _fileName    = relPath.filename().string();
    _inputDir    = cwd.string() + "/" + relPath.parent_path().string();
34

35
    _baseConfig = true;
36

37
38
    /*--- Store MPI rank and size ---*/
    // TODO with MPI implementation
39

40
    /*--- Initialize pointers to Null---*/
41

42
    SetPointersNull();
43

44
    /*--- Reading config options  ---*/
45

46
    SetConfigOptions();
47

48
    /*--- Parsing the config file  ---*/
49

50
    SetConfigParsing( case_filename );
51

52
    /*--- Set the default values for all of the options that weren't set ---*/
53

54
    SetDefault();
55

56
    /*--- Get the Mesh Value--- */
57

58
59
    // val_nDim = GetnDim(Mesh_FileName, Mesh_FileFormat);
    // TODO
60

61
    /*--- Configuration file postprocessing ---*/
62

63
    SetPostprocessing();
64

65
    /*--- Configuration file boundaries/markers setting ---*/
66

67
    // SetBoundary(); IDK how boundaries are implemented, but i think this should be treated here
68

69
    /*--- Configuration file output ---*/
70

71
    // if ((rank == MASTER_NODE))
72
73
74
    SetOutput();
}

75
76
Config::~Config( void ) {
    // Delete all introduced arrays!
77
78
79
80
81
}

// ---- Add Options ----

// Simple Options
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
void Config::AddBoolOption( const string name, bool& option_field, bool default_value ) {
    // Check if the key is already in the map. If this fails, it is coder error
    // and not user error, so throw.
    assert( _optionMap.find( name ) == _optionMap.end() );

    // Add this option to the list of all the options
    _allOptions.insert( pair<string, bool>( name, true ) );

    // Create the parser for a bool option with a reference to the option_field and the desired
    // default value. This will take the string in the config file, convert it to a bool, and
    // place that bool in the memory location specified by the reference.
    OptionBase* val = new OptionBool( name, option_field, default_value );

    // Create an association between the option name ("CFL") and the parser generated above.
    // During configuration, the parsing script will get the option name, and use this map
    // to find how to parse that option.
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
99
100
}

101
102
103
104
105
void Config::AddDoubleOption( const string name, double& option_field, double default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionDouble( name, option_field, default_value );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
106
107
}

108
109
110
111
112
void Config::AddIntegerOption( const string name, int& option_field, int default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionInt( name, option_field, default_value );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
113
114
}

115
116
117
118
119
void Config::AddLongOption( const string name, long& option_field, long default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionLong( name, option_field, default_value );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
120
121
}

122
123
124
125
126
void Config::AddStringOption( const string name, string& option_field, string default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionString( name, option_field, default_value );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
127
128
}

129
130
131
132
133
void Config::AddUnsignedLongOption( const string name, unsigned long& option_field, unsigned long default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionULong( name, option_field, default_value );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
134
135
}

136
137
138
void Config::AddUnsignedShortOption( const string name, unsigned short& option_field, unsigned short default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
139
    OptionBase* val = new OptionUShort( name, option_field, default_value );
140
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
141
142
143
144
}

// enum types work differently than all of the others because there are a small number of valid
// string entries for the type. One must also provide a list of all the valid strings of that type.
145
146
147
148
149
150
template <class Tenum> void Config::AddEnumOption( const string name, Tenum& option_field, const map<string, Tenum>& enum_map, Tenum default_value ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionEnum<Tenum>( name, enum_map, option_field, default_value );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
    return;
151
152
153
}

// List Options
154
155
156
157
158
void Config::AddStringListOption( const string name, unsigned short& num_marker, std::vector<std::string>& option_field ) {
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionStringList( name, num_marker, option_field );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
159
160
}

161
162
163
164
165
166
167
168
169
170
171
172
template <class Tenum>
void Config::AddEnumListOption( const std::string name,
                                unsigned short& input_size,
                                std::vector<Tenum>& option_field,
                                const map<std::string, Tenum>& enum_map ) {
    input_size = 0;
    assert( _optionMap.find( name ) == _optionMap.end() );
    _allOptions.insert( pair<string, bool>( name, true ) );
    OptionBase* val = new OptionEnumList<Tenum>( name, enum_map, option_field, input_size );
    _optionMap.insert( pair<string, OptionBase*>( name, val ) );
}

173
174
// ---- Getter Functions ----

175
BOUNDARY_TYPE Config::GetBoundaryType( std::string name ) const {
176
177
178
179
180
181
182
183
    for( unsigned i = 0; i < _boundaries.size(); ++i ) {
        if( name == _boundaries[i].first ) return _boundaries[i].second;
    }
    return BOUNDARY_TYPE::INVALID;
}

// ---- Setter Functions ----

184
185
void Config::SetDefault() {
    /*--- Set the default values for all of the options that weren't set ---*/
186

187
188
189
    for( map<string, bool>::iterator iter = _allOptions.begin(); iter != _allOptions.end(); ++iter ) {
        if( _optionMap[iter->first]->GetValue().size() == 0 ) _optionMap[iter->first]->SetDefault();
    }
190
191
}

192
void Config::SetConfigOptions() {
193

194
195
    /* BEGIN_CONFIG_OPTIONS */

steffen.schotthoefer's avatar
steffen.schotthoefer committed
196
    /*! @par CONFIG_CATEGORY: Problem Definition @ingroup Config */
197
198
199
    /*--- Options related to problem definition and partitioning ---*/

    // File Structure related options
steffen.schotthoefer's avatar
steffen.schotthoefer committed
200
    /*! @brief OUTPUT_DIR \n DESCRIPTION: Relative Directory of output files \n DEFAULT "/out" @ingroup Config.*/
201
    AddStringOption( "OUTPUT_DIR", _outputDir, string( "/out" ) );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
202
    /*! @brief OUTPUT_FILE \n DESCRIPTION: Name of output file \n DEFAULT "output" @ingroup Config.*/
203
    AddStringOption( "OUTPUT_FILE", _outputFile, string( "output" ) );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
204
    /*! @brief LOG_DIR \n DESCRIPTION: Relative Directory of log files \n DEFAULT "/out" @ingroup Config.*/
205
    AddStringOption( "LOG_DIR", _logDir, string( "/out" ) );
206
    /*! @brief MESH_FILE \n DESCRIPTION: Name of mesh file \n DEFAULT "" \ingroup Config.*/
207
    AddStringOption( "MESH_FILE", _meshFile, string( "mesh.su2" ) );
208
209
    /*! @brief MESH_FILE \n DESCRIPTION: Name of mesh file \n DEFAULT "" \ingroup Config.*/
    AddStringOption( "CT_FILE", _ctFile, string( "phantom.png" ) );
210
211

    // Quadrature relatated options
steffen.schotthoefer's avatar
steffen.schotthoefer committed
212
    /*! @brief QUAD_TYPE \n DESCRIPTION: Type of Quadrature rule \n Options: see @link QUAD_NAME \endlink \n DEFAULT: QUAD_MonteCarlo \ingroup
213
     * Config*/
214
215
    AddEnumOption( "QUAD_TYPE", _quadName, Quadrature_Map, QUAD_MonteCarlo );
    /*!\brief QUAD_ORDER \n DESCRIPTION: Order of Quadrature rule \n DEFAULT 2 \ingroup Config.*/
216
    AddUnsignedShortOption( "QUAD_ORDER", _quadOrder, 1 );
217
218

    // Solver related options
219
    /*! @brief MAX_MOMENT_ORDER \n: DESCRIPTON: Specifies the maximal order of Moments for PN and SN Solver */
220
    AddUnsignedShortOption( "MAX_MOMENT_SOLVER", _maxMomentDegree, 1 );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
221
    /*! @brief CFL \n DESCRIPTION: CFL number \n DEFAULT 1.0 @ingroup Config.*/
222
    AddDoubleOption( "CFL_NUMBER", _CFL, 1.0 );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
223
    /*! @brief TIME_FINAL \n DESCRIPTION: Final time for simulation \n DEFAULT 1.0 @ingroup Config.*/
224
    AddDoubleOption( "TIME_FINAL", _tEnd, 1.0 );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
225
    /*! @brief Problem \n DESCRIPTION: Type of problem setting \n DEFAULT PROBLEM_ElectronRT @ingroup Config.*/
226
    AddEnumOption( "PROBLEM", _problemName, Problem_Map, PROBLEM_ElectronRT );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
227
    /*! @brief Solver \n DESCRIPTION: Solver used for problem \n DEFAULT SN_SOLVER @ingroup Config. */
228
    AddEnumOption( "SOLVER", _solverName, Solver_Map, SN_SOLVER );
229
    /*! @brief RECONS_ORDER \n DESCRIPTION: Reconstruction order for solver \n DEFAULT 1 \ingroup Config.*/
vavrines's avatar
vavrines committed
230
    AddUnsignedShortOption( "RECONS_ORDER", _reconsOrder, 1 );
steffen.schotthoefer's avatar
steffen.schotthoefer committed
231
232
233
    /*! @brief CleanFluxMatrices \n DESCRIPTION:  If true, very low entries (10^-10 or smaller) of the flux matrices will be set to zero,
     * to improve floating point accuracy \n DEFAULT false \ingroup Config */
    AddBoolOption( "CLEAN_FLUX_MATRICES", _cleanFluxMat, false );
Jonas Kusch's avatar
Jonas Kusch committed
234
235
236
    /*! @brief ContinuousSlowingDown \n DESCRIPTION: If true, the program uses the continuous slowing down approximation to treat energy dependent
     * problems. \n DEFAULT false \ingroup Config */
    AddBoolOption( "CONTINUOUS_SLOWING_DOWN", _csd, false );
jannick.wolters's avatar
jannick.wolters committed
237
238
239
240
241
242
    /*! @brief HydogenFile \n DESCRIPTION: If the continuous slowing down approximation is used, this referes to the cross section file for hydrogen.
     * . \n DEFAULT "h.dat" \ingroup Config */
    AddStringOption( "HYDROGEN_FILE", _hydrogenFile, string( "ENDL_H.txt" ) );
    /*! @brief OxygenFile \n DESCRIPTION: If the continuous slowing down approximation is used, this referes to the cross section file for oxygen.
     * . \n DEFAULT "o.dat" \ingroup Config */
    AddStringOption( "OXYGEN_FILE", _oxygenFile, string( "ENDL_O.txt" ) );
243
244
    /*! @brief SN_ALL_GAUSS_PTS \n DESCRIPTION: If true, the SN Solver uses all Gauss Quadrature Points for 2d. \n DEFAULT false \ingroup Config */
    AddBoolOption( "SN_ALL_GAUSS_PTS", _allGaussPts, false );
245

246
    // Entropy related options
247
248
249
250
    /*! @brief Entropy Functional \n DESCRIPTION: Entropy functional used for the MN_Solver \n DEFAULT QUADRTATIC @ingroup Config. */
    AddEnumOption( "ENTROPY_FUNCTIONAL", _entropyName, Entropy_Map, QUADRATIC );
    /*! @brief Optimizer Name \n DESCRIPTION:  Optimizer used to determine the minimal Entropy reconstruction \n DEFAULT NEWTON \ingroup Config */
    AddEnumOption( "ENTROPY_OPTIMIZER", _entropyOptimizerName, Optimizer_Map, NEWTON );
251
252

    // Newton optimizer related options
253
    /*! @brief Newton Optimizer Epsilon \n DESCRIPTION:  Convergencce Epsilon for Newton Optimizer \n DEFAULT 1e-3 \ingroup Config */
254
    AddDoubleOption( "NEWTON_EPSILON", _optimizerEpsilon, 0.001 );
255
    /*! @brief Max Iter Newton Optmizers \n DESCRIPTION: Max number of newton iterations \n DEFAULT 10 \ingroup Config */
256
257
258
259
260
261
262
263
264
    AddUnsignedShortOption( "NEWTON_ITER", _newtonIter, 100 );
    /*! @brief Step Size Newton Optmizers \n DESCRIPTION: Step size for Newton optimizer \n DEFAULT 10 \ingroup Config */
    AddDoubleOption( "NEWTON_STEP_SIZE", _newtonStepSize, 0.1 );
    /*! @brief Max Iter for line search in Newton Optmizers \n DESCRIPTION: Max number of line search iter for newton optimizer \n DEFAULT 10 \ingroup
     * Config */
    AddUnsignedShortOption( "NEWTON_LINE_SEARCH_ITER", _newtonLineSearchIter, 100 );
    /*! @brief Newton Fast mode \n DESCRIPTION:  If true, we skip the Newton optimizer for Quadratic entropy and set alpha = u \n DEFAULT false
     * \ingroup Config */
    AddBoolOption( "NEWTON_FAST_MODE", _newtonFastMode, false );
265
266
267
268
269

    // Mesh related options
    // Boundary Markers
    /*!\brief BC_DIRICHLET\n DESCRIPTION: Dirichlet wall boundary marker(s) \ingroup Config*/
    AddStringListOption( "BC_DIRICHLET", _nMarkerDirichlet, _MarkerDirichlet );
270
    AddStringListOption( "BC_NEUMANN", _nMarkerNeumann, _MarkerNeumann );
jannick.wolters's avatar
jannick.wolters committed
271
272

    AddEnumOption( "KERNEL", _kernelName, Kernel_Map, KERNEL_Isotropic );
273
274
275

    // Output related options
    AddEnumListOption( "VOLUME_OUTPUT", _nVolumeOutput, _volumeOutput, VolOutput_Map );
276
277
}

278
void Config::SetConfigParsing( string case_filename ) {
279
280
281
282
283
    string text_line, option_name;
    ifstream case_file;
    vector<string> option_value;

    /*--- Read the configuration file ---*/
284

285
    case_file.open( case_filename, ios::in );
286

287
288
289
    if( case_file.fail() ) {
        ErrorMessages::Error( "The configuration file (.cfg) is missing!!", CURRENT_FUNCTION );
    }
290

291
    string errorString;
292

293
294
    int err_count     = 0;     // How many errors have we found in the config file
    int max_err_count = 30;    // Maximum number of errors to print before stopping
295

296
    map<string, bool> included_options;
297

298
    /*--- Parse the configuration file and set the options ---*/
299

300
    while( getline( case_file, text_line ) ) {
301

302
303
        if( err_count >= max_err_count ) {
            errorString.append( "too many errors. Stopping parse" );
304

305
306
307
            cout << errorString << endl;
            throw( 1 );
        }
308

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
        if( TokenizeString( text_line, option_name, option_value ) ) {

            /*--- See if it's a python option ---*/

            if( _optionMap.find( option_name ) == _optionMap.end() ) {
                string newString;
                newString.append( option_name );
                newString.append( ": invalid option name" );
                newString.append( ". Check current RTSN options in config_template.cfg." );
                newString.append( "\n" );
                errorString.append( newString );
                err_count++;
                continue;
            }

            /*--- Option exists, check if the option has already been in the config file ---*/
325

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
            if( included_options.find( option_name ) != included_options.end() ) {
                string newString;
                newString.append( option_name );
                newString.append( ": option appears twice" );
                newString.append( "\n" );
                errorString.append( newString );
                err_count++;
                continue;
            }

            /*--- New found option. Add it to the map, and delete from all options ---*/

            included_options.insert( pair<string, bool>( option_name, true ) );
            _allOptions.erase( option_name );

            /*--- Set the value and check error ---*/

            string out = _optionMap[option_name]->SetValue( option_value );
            if( out.compare( "" ) != 0 ) {
                errorString.append( out );
                errorString.append( "\n" );
                err_count++;
            }
        }
350
351
    }

352
    /*--- See if there were any errors parsing the config file ---*/
353

354
    if( errorString.size() != 0 ) {
Steffen Schotthöfer's avatar
Steffen Schotthöfer committed
355
        ErrorMessages::ParsingError( errorString, CURRENT_FUNCTION );
356
    }
357

358
    case_file.close();
359
360
}

361
362
void Config::SetPointersNull( void ) {
    // All pointer valued options should be set to NULL here
363
364
}

365
366
367
368
369
void Config::SetPostprocessing() {
    // append '/' to all dirs to allow for simple path addition
    if( _logDir[_logDir.size() - 1] != '/' ) _logDir.append( "/" );
    if( _outputDir[_outputDir.size() - 1] != '/' ) _outputDir.append( "/" );
    if( _inputDir[_inputDir.size() - 1] != '/' ) _inputDir.append( "/" );
370

371
    // setup relative paths
jannick.wolters's avatar
jannick.wolters committed
372
373
374
375
376
377
378
    _logDir       = _inputDir + _logDir;
    _outputDir    = _inputDir + _outputDir;
    _meshFile     = _inputDir + _meshFile;
    _outputFile   = _outputDir + _outputFile;
    _ctFile       = _inputDir + _ctFile;
    _hydrogenFile = _inputDir + _hydrogenFile;
    _oxygenFile   = _inputDir + _oxygenFile;
379

380
381
    // create directories if they dont exist
    if( !std::filesystem::exists( _outputDir ) ) std::filesystem::create_directory( _outputDir );
382

Steffen Schotthöfer's avatar
Steffen Schotthöfer committed
383
    // init logger
384
    InitLogger();
385

386
387
388
389
    // Regroup Boundary Conditions to  std::vector<std::pair<std::string, BOUNDARY_TYPE>> _boundaries;
    for( int i = 0; i < _nMarkerDirichlet; i++ ) {
        _boundaries.push_back( std::pair<std::string, BOUNDARY_TYPE>( _MarkerDirichlet[i], DIRICHLET ) );
    }
390
391
392
    for( int i = 0; i < _nMarkerNeumann; i++ ) {
        _boundaries.push_back( std::pair<std::string, BOUNDARY_TYPE>( _MarkerNeumann[i], NEUMANN ) );
    }
393

394
    // Check, if mesh file exists
395
396
397
    // if( !std::filesystem::exists( _meshFile ) ) {
    //    ErrorMessages::Error( "Path to mesh file <" + _meshFile + "> does not exist. Please check your config file.", CURRENT_FUNCTION );
    //}
jannick.wolters's avatar
jannick.wolters committed
398

steffen.schotthoefer's avatar
steffen.schotthoefer committed
399
    if( this->GetIsCSD() ) {
jannick.wolters's avatar
jannick.wolters committed
400
401
402
403
404
405
406
407
408
        if( !std::filesystem::exists( this->GetHydrogenFile() ) ) {
            ErrorMessages::Error( "Path to mesh file <" + this->GetHydrogenFile() + "> does not exist. Please check your config file.",
                                  CURRENT_FUNCTION );
        }
        if( !std::filesystem::exists( this->GetOxygenFile() ) ) {
            ErrorMessages::Error( "Path to mesh file <" + this->GetOxygenFile() + "> does not exist. Please check your config file.",
                                  CURRENT_FUNCTION );
        }
    }
409
410

    // Output Postprocessing
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
    // check for doublicates and remove them
    std::map<VOLUME_OUTPUT, int> dublicate_map;

    for( unsigned short idx_volOutput = 0; idx_volOutput < _nVolumeOutput; idx_volOutput++ ) {
        std::map<VOLUME_OUTPUT, int>::iterator it = dublicate_map.find( _volumeOutput[idx_volOutput] );
        it->second++;
    }
    for( auto& e : dublicate_map ) {
        std::cout << '{' << e.first << ", " << e.second << '}' << '\n';
        if( e.second > 0 ) {
            ErrorMessages::Error( "Each output group for option VOLUME_OUTPUT can only be set once. \n Please check your .cfg file.",
                                  CURRENT_FUNCTION );
        }
    }

    // Set default volume output
427
428
429
430
    if( _nVolumeOutput == 0 ) {    // If no specific output is chosen,  use "MINIMAL"
        _nVolumeOutput = 1;
        _volumeOutput.push_back( MINIMAL );
    }
431
432
}

433
434
void Config::SetOutput() {
    // Set Output for settings, i.e. feedback on what has been chosen
435
436
}

437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
bool Config::TokenizeString( string& str, string& option_name, vector<string>& option_value ) {
    const string delimiters( " (){}:,\t\n\v\f\r" );
    // check for comments or empty string
    string::size_type pos, last_pos;
    pos = str.find_first_of( "%" );
    if( ( str.length() == 0 ) || ( pos == 0 ) ) {
        // str is empty or a comment line, so no option here
        return false;
    }
    if( pos != string::npos ) {
        // remove comment at end if necessary
        str.erase( pos );
    }

    // look for line composed on only delimiters (usually whitespace)
    pos = str.find_first_not_of( delimiters );
    if( pos == string::npos ) {
        return false;
    }

    // find the equals sign and split string
    string name_part, value_part;
    pos = str.find( "=" );
    if( pos == string::npos ) {
        cerr << "Error in TokenizeString(): "
             << "line in the configuration file with no \"=\" sign." << endl;
        cout << "Look for: " << str << endl;
        cout << "str.length() = " << str.length() << endl;
        throw( -1 );
    }
    name_part  = str.substr( 0, pos );
    value_part = str.substr( pos + 1, string::npos );

    // the first_part should consist of one string with no interior delimiters
    last_pos = name_part.find_first_not_of( delimiters, 0 );
    pos      = name_part.find_first_of( delimiters, last_pos );
    if( ( name_part.length() == 0 ) || ( last_pos == string::npos ) ) {
        cerr << "Error in CConfig::TokenizeString(): "
             << "line in the configuration file with no name before the \"=\" sign." << endl;
        throw( -1 );
    }
    if( pos == string::npos ) pos = name_part.length();
    option_name = name_part.substr( last_pos, pos - last_pos );
    last_pos    = name_part.find_first_not_of( delimiters, pos );
    if( last_pos != string::npos ) {
        cerr << "Error in TokenizeString(): "
             << "two or more options before an \"=\" sign in the configuration file." << endl;
        throw( -1 );
    }
    TextProcessingToolbox::StringToUpperCase( option_name );

    // now fill the option value vector
    option_value.clear();
    last_pos = value_part.find_first_not_of( delimiters, 0 );
    pos      = value_part.find_first_of( delimiters, last_pos );
    while( string::npos != pos || string::npos != last_pos ) {
        // add token to the vector<string>
        option_value.push_back( value_part.substr( last_pos, pos - last_pos ) );
        // skip delimiters
        last_pos = value_part.find_first_not_of( delimiters, pos );
        // find next "non-delimiter"
        pos = value_part.find_first_of( delimiters, last_pos );
    }
    if( option_value.size() == 0 ) {
        cerr << "Error in TokenizeString(): "
             << "option " << option_name << " in configuration file with no value assigned." << endl;
        throw( -1 );
    }
505

506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
    // look for ';' DV delimiters attached to values
    vector<string>::iterator it;
    it = option_value.begin();
    while( it != option_value.end() ) {
        if( it->compare( ";" ) == 0 ) {
            it++;
            continue;
        }

        pos = it->find( ';' );
        if( pos != string::npos ) {
            string before_semi = it->substr( 0, pos );
            string after_semi  = it->substr( pos + 1, string::npos );
            if( before_semi.empty() ) {
                *it = ";";
                it++;
                option_value.insert( it, after_semi );
            }
            else {
                *it = before_semi;
                it++;
                vector<string> to_insert;
                to_insert.push_back( ";" );
                if( !after_semi.empty() ) to_insert.push_back( after_semi );
                option_value.insert( it, to_insert.begin(), to_insert.end() );
            }
            it = option_value.begin();    // go back to beginning; not efficient
            continue;
        }
        else {
            it++;
        }
538
    }
539

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
    // remove any consecutive ";"
    it                = option_value.begin();
    bool semi_at_prev = false;
    while( it != option_value.end() ) {
        if( semi_at_prev ) {
            if( it->compare( ";" ) == 0 ) {
                option_value.erase( it );
                it           = option_value.begin();
                semi_at_prev = false;
                continue;
            }
        }
        if( it->compare( ";" ) == 0 ) {
            semi_at_prev = true;
        }
        else {
            semi_at_prev = false;
        }
        it++;
559
560
    }

561
    return true;
562
}
563

564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
void Config::InitLogger() {

    // Declare Logger
    spdlog::level::level_enum terminalLogLvl;
    spdlog::level::level_enum fileLogLvl;

    // Choose Logger
#ifdef BUILD_TESTING
    terminalLogLvl = spdlog::level::err;
    fileLogLvl     = spdlog::level::off;
#else
    terminalLogLvl = spdlog::level::info;
    fileLogLvl     = spdlog::level::info;
#endif

579
580
581
582
583
    // create log dir if not existent
    if( !std::filesystem::exists( _logDir ) ) {
        std::filesystem::create_directory( _logDir );
    }

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
    if( spdlog::get( "event" ) == nullptr ) {
        // create sinks if level is not off
        std::vector<spdlog::sink_ptr> sinks;
        if( terminalLogLvl != spdlog::level::off ) {
            // create spdlog terminal sink
            auto terminalSink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
            terminalSink->set_level( terminalLogLvl );
            terminalSink->set_pattern( "%v" );
            sinks.push_back( terminalSink );
        }
        if( fileLogLvl != spdlog::level::off ) {
            // define filename on root
            int pe;
            MPI_Comm_rank( MPI_COMM_WORLD, &pe );
            char cfilename[1024];
            if( pe == 0 ) {
                // get date and time
                time_t now = time( nullptr );
                struct tm tstruct;
                char buf[80];
                tstruct = *localtime( &now );
                strftime( buf, sizeof( buf ), "%Y-%m-%d_%X", &tstruct );

                // set filename to date and time
                std::string filename = buf;

                // in case of existing files append '_#'
                int ctr = 0;
                if( std::filesystem::exists( _logDir + filename ) ) {
                    filename += "_" + std::to_string( ++ctr );
                }
                while( std::filesystem::exists( _logDir + filename ) ) {
                    filename.pop_back();
                    filename += std::to_string( ++ctr );
                }
                strncpy( cfilename, filename.c_str(), sizeof( cfilename ) );
                cfilename[sizeof( cfilename ) - 1] = 0;
621
            }
622
623
624
625
626
627
628
629
            MPI_Bcast( &cfilename, sizeof( cfilename ), MPI_CHAR, 0, MPI_COMM_WORLD );
            MPI_Barrier( MPI_COMM_WORLD );

            // create spdlog file sink
            auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>( _logDir + cfilename );
            fileSink->set_level( fileLogLvl );
            fileSink->set_pattern( "%Y-%m-%d %H:%M:%S.%f | %v" );
            sinks.push_back( fileSink );
630
631
        }

632
633
634
635
636
        // register all sinks
        auto event_logger = std::make_shared<spdlog::logger>( "event", begin( sinks ), end( sinks ) );
        spdlog::register_logger( event_logger );
        spdlog::flush_every( std::chrono::seconds( 5 ) );
    }
637
}