src/modules/ROOTObjectWriter/ROOTObjectWriterModule.cpp
Implementation of ROOT data file writer module. More…
Detailed Description
Implementation of ROOT data file writer module.
Copyright: Copyright (c) 2017-2024 CERN and the Allpix Squared 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. SPDX-License-Identifier: MIT
Source code
#include "ROOTObjectWriterModule.hpp"
#include <fstream>
#include <string>
#include <utility>
#include <TBranchElement.h>
#include <TClass.h>
#include <TProcessID.h>
#include "core/config/ConfigReader.hpp"
#include "core/utils/log.h"
#include "core/utils/type.h"
#include "objects/Object.hpp"
#include "objects/objects.h"
#include "tools/ROOT.h"
using namespace allpix;
ROOTObjectWriterModule::ROOTObjectWriterModule(Configuration& config, Messenger* messenger, GeometryManager* geo_mgr)
: SequentialModule(config), messenger_(messenger), geo_mgr_(geo_mgr) {
// Enable multithreading of this module if multithreading is enabled
allow_multithreading();
// Bind to all messages with filter
messenger_->registerFilter(this, &ROOTObjectWriterModule::filter);
}
ROOTObjectWriterModule::~ROOTObjectWriterModule() {
// Delete all object pointers
for(auto& index_data : write_list_) {
delete index_data.second;
}
}
void ROOTObjectWriterModule::initialize() {
// Create output file
output_file_name_ = createOutputFile(config_.get<std::string>("file_name", "data"), "root", true);
output_file_ = std::make_unique<TFile>(output_file_name_.c_str(), "RECREATE");
output_file_->cd();
// Create tree to hold Event information
trees_.emplace("Event", std::make_unique<TTree>("Event", "Tree of event info"));
trees_["Event"]->Branch("ID", ¤t_event_);
trees_["Event"]->Branch("seed", ¤t_seed_);
// Check if the given type of object is contained in the inclusion or exclusion filter rules:
auto check_object_filter = [](const std::string& object, const std::set<std::string>& arr, bool inclusive) {
auto contained = arr.find(object);
if((!inclusive && contained == std::end(arr)) || (inclusive && contained != std::end(arr))) {
LOG(WARNING) << object << (inclusive ? " included" : " not excluded")
<< ", this will lead to large output files and possible performance penalties";
}
};
// Read include and exclude list
if(config_.has("include") && config_.has("exclude")) {
throw InvalidCombinationError(
config_, {"exclude", "include"}, "include and exclude parameter are mutually exclusive");
} else if(config_.has("include")) {
auto inc_arr = config_.getArray<std::string>("include");
include_.insert(inc_arr.begin(), inc_arr.end());
check_object_filter("DepositedCharge", include_, true);
check_object_filter("PropagatedCharge", include_, true);
} else if(config_.has("exclude")) {
auto exc_arr = config_.getArray<std::string>("exclude");
exclude_.insert(exc_arr.begin(), exc_arr.end());
check_object_filter("DepositedCharge", exclude_, false);
check_object_filter("PropagatedCharge", exclude_, false);
}
if(include_.empty() && exclude_.empty()) {
LOG(WARNING) << "Writing all simulation objects to file, this will lead to large output files and possible "
"performance penalties."
<< std::endl
<< "It is advised to use the include and exclude parameters to select object types specifically.";
}
}
bool ROOTObjectWriterModule::filter(const std::shared_ptr<BaseMessage>& message,
const std::string& message_name) const { // NOLINT
try {
const BaseMessage* inst = message.get();
std::string name_str = " without a name";
if(!message_name.empty()) {
name_str = " named " + message_name;
}
LOG(TRACE) << "ROOT object writer received " << allpix::demangle(typeid(*inst).name()) << name_str;
// Read the object
auto object_array = message->getObjectArray();
if(object_array.empty()) {
return false;
}
const Object& first_object = object_array[0];
std::string class_name = allpix::demangle(typeid(first_object).name());
// Check if this message should be kept
if((!include_.empty() && include_.find(class_name) == include_.cend()) ||
(!exclude_.empty() && exclude_.find(class_name) != exclude_.cend())) {
LOG(TRACE) << "ROOT object writer ignored message with object " << allpix::demangle(typeid(*inst).name())
<< " because it has been excluded or not explicitly included";
return false;
}
} catch(MessageWithoutObjectException& e) {
const BaseMessage* inst = message.get();
LOG(WARNING) << "ROOT object writer cannot process message of type" << allpix::demangle(typeid(*inst).name())
<< " with name " << message_name;
return false;
}
return true;
}
void ROOTObjectWriterModule::run(Event* event) {
auto root_lock = root_process_lock();
// Retrieve current object count:
auto object_count = TProcessID::GetObjectCount();
// Fetch filtered messages
auto messages = messenger_->fetchFilteredMessages(this, event);
// Mark objects to be stored:
for(auto& pair : messages) {
auto& message = pair.first;
auto object_array = message->getObjectArray();
for(Object& object : object_array) {
object.markForStorage();
}
}
// Add event data
current_event_ = event->number;
current_seed_ = event->getSeed();
// Generate trees and index data
for(auto& pair : messages) {
auto& message = pair.first;
auto& message_name = pair.second;
// Get the detector name
std::string detector_name;
if(message->getDetector() != nullptr) {
detector_name = message->getDetector()->getName();
}
// Read the object
auto object_array = message->getObjectArray();
// object_array emptiness is checked in the filter
const Object& first_object = object_array[0];
std::type_index type_idx = typeid(first_object);
// Create a new branch of the correct type if this message was not received before
auto index_tuple = std::make_tuple(type_idx, detector_name, message_name);
if(write_list_.find(index_tuple) == write_list_.end()) {
std::string class_name = allpix::demangle(typeid(first_object).name());
std::string class_name_with_namespace = allpix::demangle(typeid(first_object).name(), true);
// Add vector of objects to write to the write list
write_list_[index_tuple] = new std::vector<Object*>();
auto* addr = &write_list_[index_tuple];
auto new_tree = (trees_.find(class_name) == trees_.end());
if(new_tree) {
// Create new tree
output_file_->cd();
trees_.emplace(class_name,
std::make_unique<TTree>(class_name.c_str(), (std::string("Tree of ") + class_name).c_str()));
}
std::string branch_name = detector_name.empty() ? "global" : detector_name;
if(!message_name.empty()) {
branch_name += "_";
branch_name += message_name;
}
trees_[class_name]->Bronch(
branch_name.c_str(), (std::string("std::vector<") + class_name_with_namespace + "*>").c_str(), addr);
// Prefill new tree or new branch with empty records for all events that were missed since the start
auto last_event = trees_["Event"]->GetEntries();
if(last_event > 0) {
if(new_tree) {
LOG(DEBUG) << "Pre-filling new tree of " << class_name << " with " << last_event << " empty events";
for(Long64_t i = 0; i < last_event; ++i) {
trees_[class_name]->Fill();
}
} else {
LOG(DEBUG) << "Pre-filling new branch " << branch_name << " of " << class_name << " with " << last_event
<< " empty events";
auto* branch = trees_[class_name]->GetBranch(branch_name.c_str());
for(Long64_t i = 0; i < last_event; ++i) {
branch->Fill();
}
}
}
}
// Fill the branch vector
for(Object& object : object_array) {
// Trigger the creation of TRefs for cross-object references to be able to store them to file.
object.petrifyHistory();
++write_cnt_;
write_list_[index_tuple]->push_back(&object);
}
}
LOG(TRACE) << "Writing new objects to tree";
output_file_->cd();
// Fill the tree with the current received messages
for(auto& tree : trees_) {
tree.second->Fill();
}
// Clear the current message list
for(auto& index_data : write_list_) {
index_data.second->clear();
}
// We can reset the TObject count after processing this event because the TRef creation is only done here locally
// in one worker thread instead of framework wide.
TProcessID::SetObjectCount(object_count);
}
void ROOTObjectWriterModule::finalize() {
LOG(TRACE) << "Writing objects to file";
output_file_->cd();
int branch_count = 0;
for(auto& tree : trees_) {
// Update statistics
branch_count += tree.second->GetListOfBranches()->GetEntries();
}
// Create main config directory
TDirectory* config_dir = output_file_->mkdir("config");
config_dir->cd();
// Get the config manager
ConfigManager* conf_manager = getConfigManager();
// Save the main configuration to the output file
auto* global_dir = config_dir->mkdir("Allpix");
LOG(TRACE) << "Writing global configuration";
// Loop over all values in the global configuration
for(auto& key_value : conf_manager->getGlobalConfiguration().getAll()) {
global_dir->WriteObject(&key_value.second, key_value.first.c_str());
}
// Save the instance configuration to the output file
for(const auto& config : conf_manager->getInstanceConfigurations()) {
// Create a new directory per section, using the unique module name
auto unique_name = config.getName();
auto identifier = config.get<std::string>("identifier");
if(!identifier.empty()) {
unique_name += ":";
unique_name += identifier;
}
auto* section_dir = config_dir->mkdir(unique_name.c_str());
LOG(TRACE) << "Writing configuration for: " << unique_name;
// Loop over all values in the section
for(auto& key_value : config.getAll()) {
// Skip the identifier
if(key_value.first == "identifier") {
continue;
}
section_dir->WriteObject(&key_value.second, key_value.first.c_str());
}
}
// Save the detectors to the output file
auto* detectors_dir = output_file_->mkdir("detectors");
auto* models_dir = output_file_->mkdir("models");
for(auto& detector : geo_mgr_->getDetectors()) {
detectors_dir->cd();
LOG(TRACE) << "Writing detector configuration for: " << detector->getName();
auto* detector_dir = detectors_dir->mkdir(detector->getName().c_str());
auto position = detector->getPosition();
detector_dir->WriteObject(&position, "position");
auto orientation = detector->getOrientation();
detector_dir->WriteObject(&orientation, "orientation");
// Store the detector model
// NOTE We save the model for every detector separately since parameter overloading might have changed it
std::string model_name = detector->getModel()->getType() + "_" + detector->getName();
detector_dir->WriteObject(&model_name, "type");
models_dir->cd();
auto* model_dir = models_dir->mkdir(model_name.c_str());
// Get all sections of the model configuration (maon config plus support layers):
auto model_configs = detector->getModel()->getConfigurations();
std::map<std::string, int> count_configs;
for(auto& model_config : model_configs) {
auto* model_config_dir = model_dir;
if(!model_config.getName().empty()) {
model_config_dir = model_dir->mkdir(
(model_config.getName() + "_" + std::to_string(count_configs[model_config.getName()])).c_str());
count_configs[model_config.getName()]++;
}
for(auto& key_value : model_config.getAll()) {
model_config_dir->WriteObject(&key_value.second, key_value.first.c_str());
}
}
}
// Finish writing to output file
output_file_->Write();
// Print statistics
LOG(STATUS) << "Wrote " << write_cnt_ << " objects to " << branch_count << " branches in file:" << std::endl
<< output_file_name_;
}
Updated on 2024-12-13 at 08:31:37 +0000