Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ROOT-based event exporter #900

Merged
merged 28 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3eff886
Draft dump to root option to HepMC3PrimaryGenerator.cc
stognini Aug 10, 2023
a591721
Move functionality to a separate class
stognini Aug 10, 2023
6a919c6
Change to per-event tree entries
stognini Aug 11, 2023
c7039fa
Add HepMC3RootReader; WIP on other fronts
stognini Aug 11, 2023
389d5cb
Update HepMC3RootReader to return events
stognini Aug 11, 2023
9fdd0c5
Reorg writer/reader
stognini Aug 15, 2023
b514630
Complete reader
stognini Aug 15, 2023
58a32be
Add UPRootAutoSave custom deleter; Clean up code
stognini Aug 17, 2023
5365993
Add offloadRootOutputFile macro cmd option
stognini Aug 17, 2023
bc4d486
Merge branch 'develop' into dump-primaries
stognini Aug 17, 2023
d822921
Address comments
stognini Aug 18, 2023
c14d2fb
Use template specialization for UPRootWritable<TTree>
stognini Aug 18, 2023
b0e9f43
Remove mutex from writer/reader
stognini Aug 18, 2023
8db8f19
Fix types
stognini Aug 18, 2023
2ba1132
Merge branch 'develop' into dump-primaries
stognini Aug 18, 2023
2b68f96
Refactor event IO test for reusability
sethrj Aug 19, 2023
473b5c8
Add root IO test
sethrj Aug 19, 2023
95d6885
Prevent move/copy for event reader and writer
sethrj Aug 19, 2023
e0bd0ab
Fix missing symbols error
stognini Aug 19, 2023
5b5340e
Update root unique ptrs
stognini Aug 19, 2023
0bfede8
Improve reader; Add ScopedRootErrorHandler; Remove TrackId branch
stognini Aug 19, 2023
3ac3ced
Add scoped error handler; clean up
stognini Aug 21, 2023
9f99cff
Update RootEventWriter to store contiguous event ids
stognini Aug 21, 2023
798b407
Fixup
stognini Aug 21, 2023
47c7618
Fixup; Minor improvements
stognini Aug 21, 2023
ea48c6a
Merge branch 'develop' into dump-primaries
stognini Aug 22, 2023
b5fa3d9
Merge remote-tracking branch 'upstream/develop' into dump-primaries
sethrj Aug 23, 2023
1ec6e33
Revert EventReader; Address comments
stognini Aug 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/celer-g4/celer-g4.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
#include <G4UImanager.hh>
#include <G4Version.hh>

#include "accel/HepMC3PrimaryGenerator.hh"

#include "GlobalSetup.hh"

#if G4VERSION_NUMBER >= 1100
# include <G4RunManagerFactory.hh>
#else
Expand Down Expand Up @@ -81,7 +85,7 @@ void run(int argc, char** argv)
CELER_LOG(info) << "Run manager type: "
<< TypeDemangler<G4RunManager>{}(*run_manager);

// Make global setup commands available to UI
// Construct singleton, also making it available to UI
GlobalSetup::Instance();

// Read user input
Expand Down
2 changes: 2 additions & 0 deletions src/accel/LocalTransporter.hh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace detail
{
class HitManager;
class OffloadWriter;
class RootEventWriter;
} // namespace detail

struct SetupOptions;
Expand Down Expand Up @@ -76,6 +77,7 @@ class LocalTransporter
private:
using SPHitManger = std::shared_ptr<detail::HitManager>;
using SPOffloadWriter = std::shared_ptr<detail::OffloadWriter>;
using SPRootOffloadWriter = std::shared_ptr<detail::RootEventWriter>;

struct HMFinalizer
{
Expand Down
2 changes: 1 addition & 1 deletion src/accel/SetupOptionsMessenger.cc
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ SetupOptionsMessenger::SetupOptionsMessenger(SetupOptions* options)
"Filename for ROOT dump of physics data");
add_cmd(&options->offload_output_file,
"offloadOutputFile",
"Filename for HepMC3 copy of offloaded tracks as events");
"Filename for copy of offloaded tracks as events");
add_cmd(&options->max_num_tracks,
"maxNumTracks",
"Number of track \"slots\" to be transported simultaneously");
Expand Down
2 changes: 2 additions & 0 deletions src/celeritas/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ if(CELERITAS_USE_ROOT)
ext/RootUniquePtr.root.cc
ext/RootFileManager.cc
user/RootStepWriter.cc
io/RootEventReader.cc
io/RootEventWriter.cc
"${CMAKE_CURRENT_BINARY_DIR}/CeleritasRootInterface.cxx"
)

Expand Down
2 changes: 1 addition & 1 deletion src/celeritas/ext/RootExporter.hh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class RootExporter
void operator()(ImportData const& data);

private:
UPRootWritable<TFile> root_output_;
UPRootFileWritable root_output_;

private:
// ROOT TTree name
Expand Down
4 changes: 2 additions & 2 deletions src/celeritas/ext/RootFileManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ RootFileManager::RootFileManager(char const* filename)
* To expand this class to write multiple root files (one per thread), add a
* `tid` input parameter and call `tfile_[tid].get()`.
*/
UPRootWritable<TTree>
UPRootTreeWritable
RootFileManager::make_tree(char const* name, char const* title)
{
CELER_EXPECT(tfile_->IsOpen());

int const split_level = 99;
UPRootWritable<TTree> uptree;
UPRootTreeWritable uptree;
uptree.reset(new TTree(name, title, split_level, tfile_.get()));
return uptree;
}
Expand Down
7 changes: 3 additions & 4 deletions src/celeritas/ext/RootFileManager.hh
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ class RootFileManager
explicit RootFileManager(char const* filename);

// Create tree by passing a name and title
UPRootWritable<TTree> make_tree(char const* name, char const* title);
UPRootTreeWritable make_tree(char const* name, char const* title);

// Manually write TFile
void write();

public:
UPRootWritable<TFile> tfile_;
UPRootFileWritable tfile_;
};

//---------------------------------------------------------------------------//
Expand All @@ -49,8 +49,7 @@ inline RootFileManager::RootFileManager(char const*)
CELER_NOT_CONFIGURED("ROOT");
}

inline UPRootWritable<TTree>
RootFileManager::make_tree(char const*, char const*)
inline UPRootTreeWritable RootFileManager::make_tree(char const*, char const*)
{
CELER_NOT_CONFIGURED("ROOT");
}
Expand Down
34 changes: 25 additions & 9 deletions src/celeritas/ext/RootUniquePtr.hh
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,28 @@
#include "celeritas_config.h"
#include "corecel/Assert.hh"

class TFile;
class TTree;

namespace celeritas
{
//---------------------------------------------------------------------------//
/*!
* Call `TObject->Write()` before deletion. Used by TFile and TTree writer
* classes.
* Call `TFile->Write()` before deletion.
*/
template<class T>
struct RootWritableDeleter
struct RootFileWritableDeleter
{
void operator()(T* ptr);
void operator()(TFile* ptr);
};

//---------------------------------------------------------------------------//
/*!
* Call `TTree->AutoSave()` before deletion in order to update `gDirectory`
* accordingly before writing the TTree.
*/
struct RootTreeAutoSaveDeleter
{
void operator()(TTree* ptr);
};

//---------------------------------------------------------------------------//
Expand All @@ -39,15 +50,20 @@ struct ExternDeleter

//---------------------------------------------------------------------------//
// Type aliases
template<class T>
using UPRootWritable = std::unique_ptr<T, RootWritableDeleter<T>>;
using UPRootFileWritable = std::unique_ptr<TFile, RootFileWritableDeleter>;
using UPRootTreeWritable = std::unique_ptr<TTree, RootTreeAutoSaveDeleter>;

template<class T>
using UPExtern = std::unique_ptr<T, ExternDeleter<T>>;

//---------------------------------------------------------------------------//
#if !CELERITAS_USE_ROOT
template<class T>
void RootWritableDeleter<T>::operator()(T*)
inline void RootFileWritableDeleter::operator()(TFile*)
{
CELER_NOT_CONFIGURED("ROOT");
}

inline void RootTreeAutoSaveDeleter::operator()(TTree*)
{
CELER_NOT_CONFIGURED("ROOT");
}
Expand Down
23 changes: 16 additions & 7 deletions src/celeritas/ext/RootUniquePtr.root.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ namespace celeritas
{
//---------------------------------------------------------------------------//
/*!
* Call `TObject->Write()` before deletion. Used by TFile and TTree writer
* classes.
* Call `TFile->Write()` before deletion.
*/
template<class T>
void RootWritableDeleter<T>::operator()(T* ptr)
void RootFileWritableDeleter::operator()(TFile* ptr)
{
CELER_EXPECT(ptr);
CELER_LOG(debug) << "Writing " << ptr->ClassName() << " '"
Expand All @@ -29,6 +27,20 @@ void RootWritableDeleter<T>::operator()(T* ptr)
delete ptr;
}

//---------------------------------------------------------------------------//
/*!
* Call `TTree->AutoSave()` before deletion. This ensures that `gDirectory` is
* updated accordingly when multiple TFiles are open at the same time.
*/
void RootTreeAutoSaveDeleter::operator()(TTree* ptr)
{
CELER_EXPECT(ptr);
CELER_LOG(debug) << "Autosaving " << ptr->ClassName() << " '"
<< ptr->GetName() << "'";
ptr->AutoSave();
sethrj marked this conversation as resolved.
Show resolved Hide resolved
delete ptr;
}

//---------------------------------------------------------------------------//
/*!
* Custom deleter to avoid propagating any dependency-specific implementation
Expand All @@ -46,9 +58,6 @@ void ExternDeleter<T>::operator()(T* ptr)
//---------------------------------------------------------------------------//
// EXPLICIT INSTANTIATIONS
//---------------------------------------------------------------------------//
template struct RootWritableDeleter<TFile>;
template struct RootWritableDeleter<TTree>;

template struct ExternDeleter<TFile>;
template struct ExternDeleter<TTree>;

Expand Down
118 changes: 118 additions & 0 deletions src/celeritas/io/RootEventReader.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//----------------------------------*-C++-*----------------------------------//
// Copyright 2023 UT-Battelle, LLC, and other Celeritas developers.
// See the top-level COPYRIGHT file for details.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file celeritas/io/RootEventReader.cc
//---------------------------------------------------------------------------//
#include "RootEventReader.hh"

#include <TFile.h>
#include <TLeaf.h>
#include <TTree.h>

#include "corecel/io/Logger.hh"
#include "celeritas/ext/ScopedRootErrorHandler.hh"
#include "celeritas/phys/ParticleParams.hh"

namespace celeritas
{
//---------------------------------------------------------------------------//
/*!
* Construct with ROOT input filename.
*/
RootEventReader::RootEventReader(std::string const& filename,
SPConstParticles params)
: params_(std::move(params))
{
CELER_EXPECT(!filename.empty());
sethrj marked this conversation as resolved.
Show resolved Hide resolved
ScopedRootErrorHandler scoped_root_error;

tfile_.reset(TFile::Open(filename.c_str(), "read"));
CELER_ASSERT(tfile_->IsOpen());
ttree_.reset(tfile_->Get<TTree>(tree_name()));
CELER_ASSERT(ttree_);
num_entries_ = ttree_->GetEntries();
CELER_ASSERT(num_entries_ > 0);

scoped_root_error.throw_if_errors();
}

//---------------------------------------------------------------------------//
/*!
* Read single event from the primaries tree.
*/
auto RootEventReader::operator()() -> result_type
{
CELER_EXPECT(entry_count_ <= num_entries_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add another ScopedRootErrorHandler here too.

ScopedRootErrorHandler scoped_root_error;

EventId expected_evt_id{0};
TrackId track_id{0};
result_type primaries;

for (; entry_count_ < num_entries_; entry_count_++)
{
ttree_->GetEntry(entry_count_);

auto entry_evt_id = EventId{this->from_leaf<size_type>("event_id")};
if (primaries.empty())
{
// First entry; set current event id
expected_evt_id = entry_evt_id;
}
if (entry_evt_id != expected_evt_id)
{
// End of primaries in this event
break;
}

Primary primary;
primary.track_id = track_id;
primary.event_id = expected_evt_id;
primary.particle_id
= params_->find(PDGNumber{this->from_leaf<int>("particle")});
primary.energy
= units::MevEnergy{this->from_leaf<real_type>("energy")};
primary.time = this->from_leaf<real_type>("time");
primary.position = this->from_array_leaf("pos");
primary.direction = this->from_array_leaf("dir");
primaries.push_back(std::move(primary));

track_id++;
}

scoped_root_error.throw_if_errors();
CELER_LOG_LOCAL(debug) << "Read event " << expected_evt_id.get()
<< " with " << primaries.size() << " primaries";
return primaries;
}

//---------------------------------------------------------------------------//
/*!
* Helper function to fetch leaves.
*/
template<class T>
auto RootEventReader::from_leaf(char const* leaf_name) -> T
{
CELER_EXPECT(ttree_);
auto const leaf = ttree_->GetLeaf(leaf_name);
CELER_ASSERT(leaf);
return static_cast<T>(leaf->GetValue());
}

//---------------------------------------------------------------------------//
/*!
* Helper function to fetch leaves containing `std::array<double, 3>`.
*/
Real3 RootEventReader::from_array_leaf(char const* leaf_name)
{
CELER_EXPECT(ttree_);
auto const leaf = ttree_->GetLeaf(leaf_name);
CELER_ASSERT(leaf);
CELER_ASSERT(leaf->GetLen() == 3);
return {leaf->GetValue(0), leaf->GetValue(1), leaf->GetValue(2)};
}
Comment on lines +91 to +115
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on how often we write a non-dictionary ROOT file it might be h

class RootLeafReader
{
explicit RootLeafReader(TTree* tree);

template<class T>
void operator()(char const* leaf_name, T* value) { /* default implementation */ }

template<class T, class N>
void from_leaf(char const* leaf_name, Array<T, N>* value) { /* call GetValue on each item */ }

template<class T>
void from_leaf(char const* leaf_name, OpaqueId<T>* value) {...}

so that you can make a local RootLeafReader read_leaf{ttree_.get()}; read_leaf("pos", p.pos); etc.

Probably not worth it for this small file but something to keep in mind.


//---------------------------------------------------------------------------//
} // namespace celeritas
Loading