ModuleManager.cpp 29.4 KB
Newer Older
1
2
3
4
5
6
7
8
/** @file
 *  @brief Interface to the core framework
 *  @copyright Copyright (c) 2017 CERN and the Corryvreckan authors.
 * This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
 * In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
 * Intergovernmental Organization or submit itself to any jurisdiction.
 */

9
// ROOT include files
10
11
12
13
#include <Math/DisplacementVector2D.h>
#include <Math/Vector2D.h>
#include <Math/Vector3D.h>
#include <TFile.h>
14
#include <TSystem.h>
15
16

// Local include files
17
#include "Analysis.hpp"
18
#include "module/exceptions.h"
19
#include "utils/log.h"
20

21
#include <chrono>
22
23
#include <dlfcn.h>
#include <fstream>
24
#include <iomanip>
25

26
27
#define CORRYVRECKAN_MODULE_PREFIX "libCorryvreckanModule"
#define CORRYVRECKAN_GENERATOR_FUNCTION "corryvreckan_module_generator"
28
29
#define CORRYVRECKAN_GLOBAL_FUNCTION "corryvreckan_module_is_global"
#define CORRYVRECKAN_DUT_FUNCTION "corryvreckan_module_is_dut"
30

31
32
using namespace corryvreckan;

33
// Default constructor
34
Analysis::Analysis(std::string config_file_name, std::vector<std::string> options) : m_terminate(false) {
35

Simon Spannagel's avatar
Simon Spannagel committed
36
37
38
39
40
    LOG(TRACE) << "Loading Corryvreckan";

    // Put welcome message
    LOG(STATUS) << "Welcome to Corryvreckan " << CORRYVRECKAN_PROJECT_VERSION;

Simon Spannagel's avatar
Simon Spannagel committed
41
42
    // Load the global configuration
    conf_mgr_ = std::make_unique<corryvreckan::ConfigManager>(std::move(config_file_name));
43

Simon Spannagel's avatar
Simon Spannagel committed
44
45
46
47
    // Configure the standard special sections
    conf_mgr_->setGlobalHeaderName("Corryvreckan");
    conf_mgr_->addGlobalHeaderName("");
    conf_mgr_->addIgnoreHeaderName("Ignore");
48

49
50
51
52
53
    // Parse all command line options
    for(auto& option : options) {
        conf_mgr_->parseOption(option);
    }

Simon Spannagel's avatar
Simon Spannagel committed
54
    // Fetch the global configuration
Simon Spannagel's avatar
Simon Spannagel committed
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    global_config = conf_mgr_->getGlobalConfiguration();

    // Set the log level from config if not specified earlier
    std::string log_level_string;
    if(Log::getReportingLevel() == LogLevel::NONE) {
        log_level_string = global_config.get<std::string>("log_level", "INFO");
        std::transform(log_level_string.begin(), log_level_string.end(), log_level_string.begin(), ::toupper);
        try {
            LogLevel log_level = Log::getLevelFromString(log_level_string);
            Log::setReportingLevel(log_level);
        } catch(std::invalid_argument& e) {
            LOG(ERROR) << "Log level \"" << log_level_string
                       << "\" specified in the configuration is invalid, defaulting to INFO instead";
            Log::setReportingLevel(LogLevel::INFO);
        }
    } else {
        log_level_string = Log::getStringFromLevel(Log::getReportingLevel());
    }

    // Set the log format from config
    std::string log_format_string = global_config.get<std::string>("log_format", "DEFAULT");
    std::transform(log_format_string.begin(), log_format_string.end(), log_format_string.begin(), ::toupper);
    try {
        LogFormat log_format = Log::getFormatFromString(log_format_string);
        Log::setFormat(log_format);
    } catch(std::invalid_argument& e) {
        LOG(ERROR) << "Log format \"" << log_format_string
                   << "\" specified in the configuration is invalid, using DEFAULT instead";
        Log::setFormat(LogFormat::DEFAULT);
    }

    // Open log file to write output to
    if(global_config.has("log_file")) {
        // NOTE: this stream should be available for the duration of the logging
        log_file_.open(global_config.getPath("log_file"), std::ios_base::out | std::ios_base::trunc);
        Log::addStream(log_file_);
    }

    // Wait for the first detailed messages until level and format are properly set
    LOG(TRACE) << "Global log level is set to " << log_level_string;
    LOG(TRACE) << "Global log format is set to " << log_format_string;
96

Simon Spannagel's avatar
Simon Spannagel committed
97
98
    // New clipboard for storage:
    m_clipboard = new Clipboard();
99
100
}

101
void Analysis::load() {
102
103
104

    add_units();

105
    load_detectors();
106
    load_modules();
107
108
109
110
}

void Analysis::load_detectors() {

111
112
113
    // Flag for the reference detector
    bool found_reference = false;

114
    std::vector<std::string> detectors_files = global_config.getPathArray("detectors_file");
115

116
117
    for(auto& detectors_file : detectors_files) {
        std::fstream file(detectors_file);
118
119
120
121
        ConfigManager det_mgr(detectors_file);
        det_mgr.addIgnoreHeaderName("Ignore");

        for(auto& detector : det_mgr.getConfigurations()) {
122
123
124
125

            std::string name = detector.getName();

            // Check if we have a duplicate:
126
127
128
            if(std::find_if(detectors.begin(), detectors.end(), [&name](std::shared_ptr<Detector> obj) {
                   return obj->name() == name;
               }) != detectors.end()) {
129
130
131
132
                throw InvalidValueError(
                    global_config, "detectors_file", "Detector " + detector.getName() + " defined twice");
            }

133
            LOG_PROGRESS(STATUS, "DET_LOAD_LOOP") << "Loading detector " << name;
134
            auto det_parm = std::make_shared<Detector>(detector);
135

136
137
138
139
140
141
            // Check if we already found a reference plane:
            if(found_reference && det_parm->role() == DetectorRole::REFERENCE) {
                throw InvalidValueError(global_config, "detectors_file", "Found more than one reference detector");
            }

            // Switch flag if we found the reference plane:
142
143
144
145
            if(det_parm->role() == DetectorRole::REFERENCE) {
                found_reference = true;
                m_reference = det_parm;
            }
146

147
148
149
            // Add the new detector to the global list:
            detectors.push_back(det_parm);
        }
150
    }
151

152
153
154
155
156
    // Check that exactly one detector is marked as reference:
    if(!found_reference) {
        throw InvalidValueError(global_config, "detectors_file", "Found no detector marked as reference");
    }

157
    LOG_PROGRESS(STATUS, "DET_LOAD_LOOP") << "Loaded " << detectors.size() << " detectors";
158
159

    // Finally, sort the list of detectors by z position (from lowest to highest)
160
    std::sort(detectors.begin(), detectors.end(), [](std::shared_ptr<Detector> det1, std::shared_ptr<Detector> det2) {
161
        return det1->displacement().Z() < det2->displacement().Z();
Simon Spannagel's avatar
Simon Spannagel committed
162
    });
163
}
164

165
void Analysis::load_modules() {
Simon Spannagel's avatar
Simon Spannagel committed
166
167
168
    std::vector<Configuration> configs = conf_mgr_->getConfigurations();

    // Create histogram output file
169
170
171
    global_config.setAlias("histogram_file", "histogramFile");
    std::string histogramFile = global_config.getPath("histogram_file");

172
    m_histogramFile = std::make_unique<TFile>(histogramFile.c_str(), "RECREATE");
Simon Spannagel's avatar
Simon Spannagel committed
173
174
    m_directory = m_histogramFile->mkdir("corryvreckan");
    if(m_histogramFile->IsZombie()) {
175
        throw RuntimeError("Cannot create main ROOT file " + histogramFile);
Simon Spannagel's avatar
Simon Spannagel committed
176
177
    }

178
    LOG(DEBUG) << "Start loading modules, have " << configs.size() << " configurations.";
Simon Spannagel's avatar
Simon Spannagel committed
179
180
    // Loop through all non-global configurations
    for(auto& config : configs) {
181
        // Load library for each module. Libraries are named (by convention + CMAKE libCorryvreckanModule Name.suffix
Simon Spannagel's avatar
Simon Spannagel committed
182
        std::string lib_name =
183
            std::string(CORRYVRECKAN_MODULE_PREFIX).append(config.getName()).append(SHARED_LIBRARY_SUFFIX);
184
        LOG_PROGRESS(STATUS, "MOD_LOAD_LOOP") << "Loading module " << config.getName();
Simon Spannagel's avatar
Simon Spannagel committed
185
186
187
188
189
190
191

        void* lib = nullptr;
        bool load_error = false;
        dlerror();
        if(loaded_libraries_.count(lib_name) == 0) {
            // If library is not loaded then try to load it first from the config
            // directories
Simon Spannagel's avatar
Simon Spannagel committed
192
193
            if(global_config.has("library_directories")) {
                std::vector<std::string> lib_paths = global_config.getPathArray("library_directories", true);
Simon Spannagel's avatar
Simon Spannagel committed
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
                for(auto& lib_path : lib_paths) {
                    std::string full_lib_path = lib_path;
                    full_lib_path += "/";
                    full_lib_path += lib_name;

                    // Check if the absolute file exists and try to load if it exists
                    std::ifstream check_file(full_lib_path);
                    if(check_file.good()) {
                        lib = dlopen(full_lib_path.c_str(), RTLD_NOW);
                        if(lib != nullptr) {
                            LOG(DEBUG) << "Found library in configuration specified directory at " << full_lib_path;
                        } else {
                            load_error = true;
                        }
                        break;
                    }
                }
            }

            // Otherwise try to load from the standard paths if not found already
            if(!load_error && lib == nullptr) {
                lib = dlopen(lib_name.c_str(), RTLD_NOW);

                if(lib != nullptr) {
                    LOG(TRACE) << "Opened library";
                    Dl_info dl_info;
                    dl_info.dli_fname = "";

                    // workaround to get the location of the library
                    int ret = dladdr(dlsym(lib, CORRYVRECKAN_GENERATOR_FUNCTION), &dl_info);
                    if(ret != 0) {
                        LOG(DEBUG) << "Found library during global search in runtime paths at " << dl_info.dli_fname;
                    } else {
                        LOG(WARNING) << "Found library during global search but could not "
                                        "deduce location, likely broken library";
                    }
                } else {
                    load_error = true;
                }
233
234
            }
        } else {
Simon Spannagel's avatar
Simon Spannagel committed
235
236
            // Otherwise just fetch it from the cache
            lib = loaded_libraries_[lib_name];
237
238
        }

Simon Spannagel's avatar
Simon Spannagel committed
239
240
241
        // If library did not load then throw exception
        if(load_error) {
            const char* lib_error = dlerror();
242

Simon Spannagel's avatar
Simon Spannagel committed
243
244
245
246
247
248
249
            // Find the name of the loaded library if it exists
            std::string lib_error_str = lib_error;
            size_t end_pos = lib_error_str.find(':');
            std::string problem_lib;
            if(end_pos != std::string::npos) {
                problem_lib = lib_error_str.substr(0, end_pos);
            }
250

Simon Spannagel's avatar
Simon Spannagel committed
251
252
253
254
255
256
257
258
259
260
            // FIXME is checking the error in this way portable?
            if(lib_error != nullptr && std::strstr(lib_error, "cannot allocate memory in static TLS block") != nullptr) {
                LOG(ERROR) << "Library could not be loaded: not enough thread local storage "
                              "available"
                           << std::endl
                           << "Try one of below workarounds:" << std::endl
                           << "- Rerun library with the environmental variable LD_PRELOAD='" << problem_lib << "'"
                           << std::endl
                           << "- Recompile the library " << problem_lib << " with tls-model=global-dynamic";
            } else if(lib_error != nullptr && std::strstr(lib_error, "cannot open shared object file") != nullptr &&
261
                      problem_lib.find(CORRYVRECKAN_MODULE_PREFIX) == std::string::npos) {
Simon Spannagel's avatar
Simon Spannagel committed
262
263
264
265
266
267
268
269
270
271
272
273
                LOG(ERROR) << "Library could not be loaded: one of its dependencies is missing" << std::endl
                           << "The name of the missing library is " << problem_lib << std::endl
                           << "Please make sure the library is properly initialized and try "
                              "again";
            } else {
                LOG(ERROR) << "Library could not be loaded: it is not available" << std::endl
                           << " - Did you enable the library during building? " << std::endl
                           << " - Did you spell the library name correctly (case-sensitive)? ";
                if(lib_error != nullptr) {
                    LOG(DEBUG) << "Detailed error: " << lib_error;
                }
            }
274

275
            throw corryvreckan::DynamicLibraryError("Error loading " + config.getName());
Simon Spannagel's avatar
Simon Spannagel committed
276
277
278
        }
        // Remember that this library was loaded
        loaded_libraries_[lib_name] = lib;
279

280
        // Check if this module is produced once, or once per detector
281
282
283
284
        bool global = true;
        bool dut_only = false;
        void* globalFunction = dlsym(loaded_libraries_[lib_name], CORRYVRECKAN_GLOBAL_FUNCTION);
        void* dutFunction = dlsym(loaded_libraries_[lib_name], CORRYVRECKAN_DUT_FUNCTION);
285

286
287
        // If the global function was not found, throw an error
        if(globalFunction == nullptr) {
288
289
290
            LOG(ERROR) << "Module library is invalid or outdated: required interface function not found!";
            throw corryvreckan::DynamicLibraryError(config.getName());
        } else {
291
292
293
294
295
296
297
298
299
            global = reinterpret_cast<bool (*)()>(globalFunction)(); // NOLINT
        }

        // If the DUT function was not found, throw an error
        if(dutFunction == nullptr) {
            LOG(ERROR) << "Module library is invalid or outdated: required interface function not found!";
            throw corryvreckan::DynamicLibraryError(config.getName());
        } else {
            dut_only = reinterpret_cast<bool (*)()>(dutFunction)(); // NOLINT
300
301
        }

302
303
304
        // Apply the module specific options to the module configuration
        conf_mgr_->applyOptions(config.getName(), config);

Simon Spannagel's avatar
Simon Spannagel committed
305
306
307
        // Add the global internal parameters to the configuration
        std::string global_dir = gSystem->pwd();
        config.set<std::string>("_global_dir", global_dir);
308

309
        // Merge the global configuration into the modules config:
310
311
        config.merge(global_config);

312
        // Create the modules from the library
313
        if(global) {
314
315
            m_modules.emplace_back(create_unique_module(loaded_libraries_[lib_name], config));
        } else {
316
            auto modules = create_detector_modules(loaded_libraries_[lib_name], config, dut_only);
317
318
319
320
            for(const auto& mod : modules) {
                m_modules.push_back(mod);
            }
        }
Simon Spannagel's avatar
Simon Spannagel committed
321
    }
322
323
324
325
326

    // Set the reference detector:
    for(auto& module : m_modules) {
        module->setReference(m_reference);
    }
327
    LOG_PROGRESS(STATUS, "MOD_LOAD_LOOP") << "Loaded " << configs.size() << " modules";
328
329
}

330
Module* Analysis::create_unique_module(void* library, Configuration config) {
331
    LOG(TRACE) << "Creating module " << config.getName() << ", using generator \"" << CORRYVRECKAN_GENERATOR_FUNCTION
Simon Spannagel's avatar
Simon Spannagel committed
332
333
334
335
336
337
               << "\"";

    // Get the generator function for this module
    void* generator = dlsym(library, CORRYVRECKAN_GENERATOR_FUNCTION);
    // If the generator function was not found, throw an error
    if(generator == nullptr) {
338
        LOG(ERROR) << "Module library is invalid or outdated: required "
Simon Spannagel's avatar
Simon Spannagel committed
339
                      "interface function not found!";
340
        throw corryvreckan::RuntimeError("Error instantiating module from " + config.getName());
Simon Spannagel's avatar
Simon Spannagel committed
341
    }
342

Simon Spannagel's avatar
Simon Spannagel committed
343
    // Convert to correct generator function
344
345
    auto module_generator =
        reinterpret_cast<Module* (*)(Configuration, std::vector<std::shared_ptr<Detector>>)>(generator); // NOLINT
346

347
    // Figure out which detectors should run on this module:
348
    std::vector<std::shared_ptr<Detector>> module_det;
349
    if(!config.has("detectors")) {
350
        module_det = detectors;
351
352
353
354
355
    } else {
        std::vector<std::string> det_list = config.getArray<std::string>("detectors");

        for(auto& d : detectors) {
            if(std::find(det_list.begin(), det_list.end(), d->name()) != det_list.end()) {
356
                module_det.push_back(d);
357
358
359
360
            }
        }
    }

361
362
363
364
    // Check for potentially masked detectors:
    if(config.has("masked")) {
        std::vector<std::string> mask_list = config.getArray<std::string>("masked");

365
        for(auto it = module_det.begin(); it != module_det.end();) {
366
367
            // Remove detectors which are masked:
            if(std::find(mask_list.begin(), mask_list.end(), (*it)->name()) != mask_list.end()) {
368
                it = module_det.erase(it);
369
370
371
372
373
374
            } else {
                it++;
            }
        }
    }

375
376
377
378
379
380
    // Set the log section header
    std::string old_section_name = Log::getSection();
    std::string section_name = "C:";
    section_name += config.getName();
    Log::setSection(section_name);
    // Set module specific log settings
381
382
383
    auto old_settings = set_module_before(config.getName(), config);
    // Build module
    Module* module = module_generator(config, module_det);
384
385
    // Reset log
    Log::setSection(old_section_name);
386
    set_module_after(old_settings);
387

388
389
    // Return the module to the analysis
    return module;
390
391
}

392
std::vector<Module*> Analysis::create_detector_modules(void* library, Configuration config, bool dut_only) {
393
394
395
396
397
398
399
400
401
402
403
404
405
    LOG(TRACE) << "Creating instantiations for module " << config.getName() << ", using generator \""
               << CORRYVRECKAN_GENERATOR_FUNCTION << "\"";

    // Get the generator function for this module
    void* generator = dlsym(library, CORRYVRECKAN_GENERATOR_FUNCTION);
    // If the generator function was not found, throw an error
    if(generator == nullptr) {
        LOG(ERROR) << "Module library is invalid or outdated: required "
                      "interface function not found!";
        throw corryvreckan::RuntimeError("Error instantiating module from " + config.getName());
    }

    // Convert to correct generator function
406
    auto module_generator = reinterpret_cast<Module* (*)(Configuration, std::shared_ptr<Detector>)>(generator); // NOLINT
407
408

    // Figure out which detectors should run on this module:
409
    std::vector<std::shared_ptr<Detector>> module_det;
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
    if(!config.has("detectors")) {
        module_det = detectors;
    } else {
        std::vector<std::string> det_list = config.getArray<std::string>("detectors");

        for(auto& d : detectors) {
            if(std::find(det_list.begin(), det_list.end(), d->name()) != det_list.end()) {
                module_det.push_back(d);
            }
        }
    }

    // Check for potentially masked detectors:
    if(config.has("masked")) {
        std::vector<std::string> mask_list = config.getArray<std::string>("masked");

        for(auto it = module_det.begin(); it != module_det.end();) {
            // Remove detectors which are masked:
            if(std::find(mask_list.begin(), mask_list.end(), (*it)->name()) != mask_list.end()) {
                it = module_det.erase(it);
            } else {
                it++;
            }
        }
    }

    std::vector<Module*> modules;
    auto module_base_name = config.getName();
    for(const auto& detector : module_det) {
        // Set the identifier for this module:
        config.setName(module_base_name + "_" + detector->name());
441
442
443
444
445
446

        // If this should only be instantiated for DUTs, skip otherwise:
        if(dut_only && !detector->isDUT()) {
            LOG(TRACE) << "Skipping instantiation \"" << config.getName() << "\", detector is no DUT";
            continue;
        }
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
        LOG(TRACE) << "Creating instantiation \"" << config.getName() << "\"";

        // Set the log section header
        std::string old_section_name = Log::getSection();
        std::string section_name = "C:";
        section_name += config.getName();
        Log::setSection(section_name);
        // Set module specific log settings
        auto old_settings = set_module_before(config.getName(), config);
        // Build module
        modules.emplace_back(module_generator(config, detector));
        // Reset log
        Log::setSection(old_section_name);
        set_module_after(old_settings);
    }

    // Return the list of modules to the analysis
    return modules;
}

467
// Run the analysis loop - this initialises, runs and finalises all modules
Simon Spannagel's avatar
Simon Spannagel committed
468
469
void Analysis::run() {

470
    // Check if we have an event or track limit:
Simon Spannagel's avatar
Simon Spannagel committed
471
    int number_of_events = global_config.get<int>("number_of_events", -1);
472
    int number_of_tracks = global_config.get<int>("number_of_tracks", -1);
473
    auto run_time = global_config.get<double>("run_time", static_cast<double>(Units::convert(-1.0, "s")));
474

475
    // Loop over all events, running each module on each "event"
Simon Spannagel's avatar
Simon Spannagel committed
476
477
    LOG(STATUS) << "========================| Event loop |========================";
    m_events = 1;
478
    int events_prev = 1;
479
480
    m_tracks = 0;
    int skipped = 0;
481
    int skipped_prev = 0;
482

Simon Spannagel's avatar
Simon Spannagel committed
483
484
485
    while(1) {
        bool run = true;
        bool noData = false;
486

Simon Spannagel's avatar
Simon Spannagel committed
487
        // Check if we have reached the maximum number of events
488

Simon Spannagel's avatar
Simon Spannagel committed
489
490
491
        if(number_of_events > -1 && m_events >= number_of_events)
            break;

492
        if(run_time > 0.0 && (m_clipboard->get_persistent("eventStart")) >= run_time)
493
494
            break;

495
496
497
498
        // Check if we have reached the maximum number of tracks
        if(number_of_tracks > -1 && m_tracks >= number_of_tracks)
            break;

499
500
        // Run all modules
        for(auto& module : m_modules) {
501
502
503
            // Get current time
            auto start = std::chrono::steady_clock::now();

504
505
506
            // Set run module section header
            std::string old_section_name = Log::getSection();
            std::string section_name = "R:";
507
            section_name += module->getName();
508
509
            Log::setSection(section_name);
            // Set module specific settings
510
            auto old_settings = set_module_before(module->getName(), module->getConfig());
Simon Spannagel's avatar
Simon Spannagel committed
511
            // Change to the output file directory
512
513
            m_directory->cd(module->getName().c_str());
            StatusCode check = module->run(m_clipboard);
514
515
            // Reset logging
            Log::setSection(old_section_name);
516
            set_module_after(old_settings);
517
518
519
520
521

            // Update execution time
            auto end = std::chrono::steady_clock::now();
            module_execution_time_[module] += static_cast<std::chrono::duration<long double>>(end - start).count();

Simon Spannagel's avatar
Simon Spannagel committed
522
523
            if(check == NoData) {
                noData = true;
524
                skipped++;
Simon Spannagel's avatar
Simon Spannagel committed
525
526
                break;
            } // Nothing to be done in this event
527
            if(check == Failure) {
Simon Spannagel's avatar
Simon Spannagel committed
528
                run = false;
529
            }
Simon Spannagel's avatar
Simon Spannagel committed
530
531
        }

532
        // Print statistics:
533
        Tracks* tracks = reinterpret_cast<Tracks*>(m_clipboard->get("tracks"));
534
        m_tracks += (tracks == nullptr ? 0 : static_cast<int>(tracks->size()));
535
536
537
538
539
540
541
542
543
544
545
546

        bool update_progress = false;
        if(m_events % 100 == 0 && m_events != events_prev) {
            update_progress = true;
        }
        if(skipped % 1000 == 0 && skipped != skipped_prev) {
            update_progress = true;
        }

        if(update_progress) {
            skipped_prev = skipped;
            events_prev = m_events;
547
            LOG_PROGRESS(STATUS, "event_loop")
548
                << "Ev: +" << m_events << " \\" << skipped << " Tr: " << m_tracks << " (" << std::setprecision(3)
549
                << (static_cast<double>(m_tracks) / m_events) << "/ev)"
550
551
552
                << (m_clipboard->has_persistent("eventStart")
                        ? " t = " + Units::display(m_clipboard->get_persistent("eventStart"), {"ns", "us", "ms", "s"})
                        : "");
553
554
        }

Simon Spannagel's avatar
Simon Spannagel committed
555
556
        // Clear objects from this iteration from the clipboard
        m_clipboard->clear();
557

558
        // Check if any of the modules return a value saying it should stop
559
        if(!run) {
Simon Spannagel's avatar
Simon Spannagel committed
560
            break;
561
562
        }

Simon Spannagel's avatar
Simon Spannagel committed
563
        // Increment event number
564
        if(!noData) {
Simon Spannagel's avatar
Simon Spannagel committed
565
            m_events++;
566
        }
567
568
569

        // Check for user termination and stop the event loop:
        if(m_terminate) {
570
            break;
571
        }
572
573
574
    }
}

575
576
577
578
void Analysis::terminate() {
    m_terminate = true;
}

579
// Initalise all modules
Simon Spannagel's avatar
Simon Spannagel committed
580
void Analysis::initialiseAll() {
581
582
583
    // Loop over all modules and initialise them
    LOG(STATUS) << "=================| Initialising modules |==================";
    for(auto& module : m_modules) {
584
585
586
        // Set init module section header
        std::string old_section_name = Log::getSection();
        std::string section_name = "I:";
587
        section_name += module->getName();
588
589
        Log::setSection(section_name);
        // Set module specific settings
590
        auto old_settings = set_module_before(module->getName(), module->getConfig());
591

Simon Spannagel's avatar
Simon Spannagel committed
592
593
        // Make a new folder in the output file
        m_directory->cd();
594
595
596
597
598
        m_directory->mkdir(module->getName().c_str());
        m_directory->cd(module->getName().c_str());
        LOG(INFO) << "Initialising \"" << module->getName() << "\"";
        // Initialise the module
        module->initialise();
599
600
601

        // Reset logging
        Log::setSection(old_section_name);
602
        set_module_after(old_settings);
Simon Spannagel's avatar
Simon Spannagel committed
603
    }
604
605
}

606
// Finalise all modules
Simon Spannagel's avatar
Simon Spannagel committed
607
608
void Analysis::finaliseAll() {

609
610
611
    // Loop over all modules and finalise them
    LOG(STATUS) << "===================| Finalising modules |===================";
    for(auto& module : m_modules) {
612
613
        // Set init module section header
        std::string old_section_name = Log::getSection();
Simon Spannagel's avatar
Simon Spannagel committed
614
        std::string section_name = "F:";
615
        section_name += module->getName();
616
617
        Log::setSection(section_name);
        // Set module specific settings
618
        auto old_settings = set_module_before(module->getName(), module->getConfig());
619

Simon Spannagel's avatar
Simon Spannagel committed
620
        // Change to the output file directory
621
622
623
        m_directory->cd(module->getName().c_str());
        // Finalise the module
        module->finalise();
624
625
        // Reset logging
        Log::setSection(old_section_name);
626
        set_module_after(old_settings);
Simon Spannagel's avatar
Simon Spannagel committed
627
    }
Simon Spannagel's avatar
Simon Spannagel committed
628

Simon Spannagel's avatar
Simon Spannagel committed
629
630
631
    // Write the output histogram file
    m_directory->cd();
    m_directory->Write();
632

Simon Spannagel's avatar
Simon Spannagel committed
633
    m_histogramFile->Close();
634
    LOG(STATUS) << "Wrote histogram output file to " << global_config.getPath("histogramFile");
635

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
    // Write out update detectors file:
    if(global_config.has("detectors_file_updated")) {
        std::string file_name = global_config.getPath("detectors_file_updated");
        // Check if the file exists
        std::ofstream file(file_name);
        if(!file) {
            throw ConfigFileUnavailableError(file_name);
        }

        ConfigReader final_detectors;
        for(auto& detector : detectors) {
            final_detectors.addConfiguration(detector->getConfiguration());
        }

        final_detectors.write(file);
        LOG(STATUS) << "Wrote updated detector configuration to " << file_name;
    }

Simon Spannagel's avatar
Simon Spannagel committed
654
655
    // Check the timing for all events
    timing();
656
657
}

658
// Display timing statistics for each module, over all events and per event
Simon Spannagel's avatar
Simon Spannagel committed
659
void Analysis::timing() {
660
    LOG(STATUS) << "===============| Wall-clock timing (seconds) |================";
661
662
    for(auto& module : m_modules) {
        LOG(STATUS) << std::setw(25) << module->getName() << "  --  " << std::fixed << std::setprecision(5)
663
664
                    << module_execution_time_[module] << " = " << std::setprecision(9)
                    << module_execution_time_[module] / m_events << " s/evt";
Simon Spannagel's avatar
Simon Spannagel committed
665
    }
666
    LOG(STATUS) << "==============================================================";
667
}
668
669

// Helper functions to set the module specific log settings if necessary
670
std::tuple<LogLevel, LogFormat> Analysis::set_module_before(const std::string&, const Configuration& config) {
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
    // Set new log level if necessary
    LogLevel prev_level = Log::getReportingLevel();
    if(config.has("log_level")) {
        std::string log_level_string = config.get<std::string>("log_level");
        std::transform(log_level_string.begin(), log_level_string.end(), log_level_string.begin(), ::toupper);
        try {
            LogLevel log_level = Log::getLevelFromString(log_level_string);
            if(log_level != prev_level) {
                LOG(TRACE) << "Local log level is set to " << log_level_string;
                Log::setReportingLevel(log_level);
            }
        } catch(std::invalid_argument& e) {
            throw InvalidValueError(config, "log_level", e.what());
        }
    }

    // Set new log format if necessary
    LogFormat prev_format = Log::getFormat();
    if(config.has("log_format")) {
        std::string log_format_string = config.get<std::string>("log_format");
        std::transform(log_format_string.begin(), log_format_string.end(), log_format_string.begin(), ::toupper);
        try {
            LogFormat log_format = Log::getFormatFromString(log_format_string);
            if(log_format != prev_format) {
                LOG(TRACE) << "Local log format is set to " << log_format_string;
                Log::setFormat(log_format);
            }
        } catch(std::invalid_argument& e) {
            throw InvalidValueError(config, "log_format", e.what());
        }
    }

    return std::make_tuple(prev_level, prev_format);
}
705
void Analysis::set_module_after(std::tuple<LogLevel, LogFormat> prev) {
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
    // Reset the previous log level
    LogLevel cur_level = Log::getReportingLevel();
    LogLevel old_level = std::get<0>(prev);
    if(cur_level != old_level) {
        Log::setReportingLevel(old_level);
        LOG(TRACE) << "Reset log level to global level of " << Log::getStringFromLevel(old_level);
    }

    // Reset the previous log format
    LogFormat cur_format = Log::getFormat();
    LogFormat old_format = std::get<1>(prev);
    if(cur_format != old_format) {
        Log::setFormat(old_format);
        LOG(TRACE) << "Reset log format to global level of " << Log::getStringFromFormat(old_format);
    }
}
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767

void Analysis::add_units() {

    LOG(TRACE) << "Adding physical units";

    // LENGTH
    Units::add("nm", 1e-6);
    Units::add("um", 1e-3);
    Units::add("mm", 1);
    Units::add("cm", 1e1);
    Units::add("dm", 1e2);
    Units::add("m", 1e3);
    Units::add("km", 1e6);

    // TIME
    Units::add("ps", 1e-3);
    Units::add("ns", 1);
    Units::add("us", 1e3);
    Units::add("ms", 1e6);
    Units::add("s", 1e9);

    // TEMPERATURE
    Units::add("K", 1);

    // ENERGY
    Units::add("eV", 1e-6);
    Units::add("keV", 1e-3);
    Units::add("MeV", 1);
    Units::add("GeV", 1e3);

    // CHARGE
    Units::add("e", 1);
    Units::add("ke", 1e3);
    Units::add("C", 1.6021766208e-19);

    // VOLTAGE
    // NOTE: fixed by above
    Units::add("V", 1e-6);
    Units::add("kV", 1e-3);

    // ANGLES
    // NOTE: these are fake units
    Units::add("deg", 0.01745329252);
    Units::add("rad", 1);
    Units::add("mrad", 1e-3);
}