The Gaudi Framework  master (b9786168)
Loading...
Searching...
No Matches
GenericNTupleWriter.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 2024-2025 CERN for the benefit of the LHCb and ATLAS collaborations *
3* *
4* This software is distributed under the terms of the Apache version 2 licence, *
5* copied verbatim in the file "LICENSE". *
6* *
7* In applying this licence, CERN does not waive the privileges and immunities *
8* granted to it by virtue of its status as an Intergovernmental Organization *
9* or submit itself to any jurisdiction. *
10\***********************************************************************************/
11#include <Gaudi/Algorithm.h>
14#include <TFile.h>
15#include <TTree.h>
16#include <format>
17#include <functional>
18#include <gsl/pointers>
19#include <mutex>
20#include <string>
21#include <vector>
22
23namespace {
24 // Extract the type name from a string, handles types marked as UNKNOWN_CLASS
25 auto getTypeName( std::string_view dependency ) {
26 auto unknownClassPos = dependency.find( "UNKNOWN_CLASS:" );
27 return ( unknownClassPos != std::string::npos )
28 ? std::string( dependency.substr( unknownClassPos + std::string( "UNKNOWN_CLASS:" ).length() ) )
29 : std::string( dependency );
30 }
31
32 // Extract the name from a path in the TES string by returning the last part after a slash
33 auto getNameFromLoc( std::string_view loc ) {
34 auto lastSlashPos = loc.find_last_of( '/' );
35 return std::string{ lastSlashPos != loc.npos ? loc.substr( lastSlashPos + 1 ) : loc };
36 }
37} // namespace
38
39namespace Gaudi::NTuple {
47
49 public:
50 GenericWriter( const std::string& n, ISvcLocator* l )
51 : Algorithm( n, l ), m_fileId( this, "OutputFile", "NTuple", "Identifier for the TFile to write to." ) {}
52
53 // Initialize the algorithm, set up the ROOT file and a TTree branch for each input location
55 return Gaudi::Algorithm::initialize().andThen( [this]() {
56 if ( m_ntupleTname.empty() ) m_ntupleTname = name();
57
58 const auto& extraInputs = extraInputDeps();
59 if ( extraInputs.empty() ) {
60 error() << "No extra inputs locations specified. Please define extra inputs for the NTuple writer." << endmsg;
62 }
63
65 if ( !m_fileSvc ) {
66 error() << "Failed to retrieve FileSvc." << endmsg;
68 }
69
70 m_file = m_fileSvc->getFile( m_fileId );
71 if ( !m_file ) {
72 error() << "Failed to retrieve TFile." << endmsg;
74 }
75
76 m_tree = std::make_unique<TTree>( m_ntupleTname.value().c_str(), "Tree of GenericWriter Algorithm" ).release();
77
78 createBranches( extraInputs );
79
80 // eventSvc guarantees that the pointer is not null
82
84 } );
85 }
86
87 // Execute the algorithm for each event, retrieving data from the event store and writing it to the TTree
88 StatusCode execute( const EventContext& ) const override {
89 std::vector<DataObject*> pObjs( m_branchWrappers.size(), nullptr );
90
91 for ( std::size_t i = 0; const auto& wrapper : m_branchWrappers ) {
92 m_eventSvc->retrieveObject( wrapper.getLocation(), pObjs[i++] )
93 .orThrow( std::format( "Failed to retrieve object for location '{}'. Ensure the location is correct and "
94 "the object exists.",
95 wrapper.getLocation() ),
96 name() );
97 }
98
99 {
100 std::scoped_lock lock{ m_mtx };
101 for ( std::size_t i = 0; auto& wrapper : m_branchWrappers ) { wrapper.setBranchData( pObjs[i++] ); }
102 m_tree->Fill();
103 }
104
105 return StatusCode::SUCCESS;
106 }
107
108 // Finalize the algorithm by writing the TTree to the file, resetting the branches and closing the file
109 StatusCode finalize() override {
110 m_file->cd();
111 if ( m_tree->Write() <= 0 ) {
112 error() << "Failed to write TTree to ROOT file." << endmsg;
113 return StatusCode::FAILURE;
114 }
115 m_tree = nullptr;
116 info() << "TTree written to ROOT file. File closed." << endmsg;
117
119 }
120
121 // Create branches for each input location specified in the configuration
122 void createBranches( const DataObjIDColl& extraInputs ) {
123 m_branchWrappers.reserve( m_branchWrappers.size() + extraInputs.size() );
124 for ( const auto& dep : extraInputs ) {
125 auto typeName = getTypeName( dep.className() );
126 auto branchName = getNameFromLoc( dep.key() );
127 m_branchWrappers.emplace_back( m_tree, typeName, branchName, dep.key(), name() );
128 }
129 }
130
131 // Getters, Setters
132 size_t getBranchWrappersSize() const { return m_branchWrappers.size(); }
133
134 std::unordered_set<std::string> getBranchesClassNames() const {
135 std::unordered_set<std::string> classNames;
136 for ( auto& wrapper : m_branchWrappers ) { classNames.insert( wrapper.getClassName() ); }
137 return classNames;
138 }
139
140 void setTree( TTree* tree ) { m_tree = tree; }
141
142 private:
143 Gaudi::Property<std::string> m_fileId; // Property to hold the the identifier of where the TTree will be saved
145 "Name of the TTree" }; // Property to hold the name of the TTree
146 std::shared_ptr<TFile> m_file = nullptr; // Smart pointer to the ROOT TFile object
147 TTree* m_tree = nullptr; // Pointer to the ROOT TTree object
148 IDataProviderSvc* m_eventSvc = nullptr; // Pointer to the event service interface for data retrieval
150 mutable std::mutex m_mtx; // Mutex for thread-safe operations on the GenericWriter object
151 mutable std::vector<Gaudi::details::BranchWrapper> m_branchWrappers{}; // Container for all BranchWrapper instances
152 };
153
154 DECLARE_COMPONENT( GenericWriter )
155} // namespace Gaudi::NTuple
156
157#ifdef UNIT_TESTS
158
159# define BOOST_TEST_MODULE test_GenericNTupleWriter
160# include <boost/test/unit_test.hpp>
161
166class MockISvcLocator : public ISvcLocator {
167public:
168 virtual const std::list<IService*>& getServices() const override {
169 static std::list<IService*> dummyServices;
170 return dummyServices;
171 }
172
173 virtual bool existsService( std::string_view ) const override { return false; }
174
175 virtual SmartIF<IService>& service( const Gaudi::Utils::TypeNameString&, const bool ) override {
176 static SmartIF<IService> dummyService;
177 return dummyService;
178 }
179
180 virtual unsigned long addRef() const override { return 1; }
181
182 virtual unsigned long release() const override { return 1; }
183
184 virtual StatusCode queryInterface( const InterfaceID&, void** ) override { return StatusCode::FAILURE; }
185
186 virtual std::vector<std::string> getInterfaceNames() const override { return {}; }
187
188 virtual void const* i_cast( const InterfaceID& ) const override { return nullptr; }
189
190 virtual unsigned long refCount() const override { return 1; }
191
192 virtual unsigned long decRef() const override { return 1; }
193};
194
195// Utility function tests
196BOOST_AUTO_TEST_CASE( testGetTypeName ) {
197 // Test type name extraction from various formats
198 BOOST_CHECK_EQUAL( getTypeName( "MyClass" ), "MyClass" );
199 BOOST_CHECK_EQUAL( getTypeName( "std::vector<double>" ), "std::vector<double>" );
200 BOOST_CHECK_EQUAL( getTypeName( "UNKNOWN_CLASS:MyCustomClass" ), "MyCustomClass" );
201}
202
203BOOST_AUTO_TEST_CASE( testGetNameFromLoc ) {
204 // Test name extraction from location strings
205 BOOST_CHECK_EQUAL( getNameFromLoc( "/Event/MyAlg/MyData" ), "MyData" );
206 BOOST_CHECK_EQUAL( getNameFromLoc( "MyAlg/MyData" ), "MyData" );
207 BOOST_CHECK_EQUAL( getNameFromLoc( "MyData" ), "MyData" );
208 BOOST_CHECK_EQUAL( getNameFromLoc( "" ), "" );
209}
210
211// Test instantiation of the GenericWriter
212BOOST_AUTO_TEST_CASE( testInit ) {
213 MockISvcLocator mockLocator;
214 Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
215
216 // Verify we can instanciate a writer
217 BOOST_CHECK_EQUAL( writer.name(), "test_writer" );
218}
219
220// Test branch creation with empty dependencies
221BOOST_AUTO_TEST_CASE( testCreateBranches_EmptyDeps ) {
222 MockISvcLocator mockLocator;
223 Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
224
225 // Expect an failure when no dependencies are provided
226 BOOST_CHECK_EQUAL( writer.initialize(), StatusCode::FAILURE );
227}
228
229// Test branch creation with an invalid type
230BOOST_AUTO_TEST_CASE( testCreateBranches_InvalidType ) {
231 MockISvcLocator mockLocator;
232 auto tree = std::make_unique<TTree>( "testTree", "test tree" );
233 Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
234 writer.setTree( tree.get() );
235
236 DataObjIDColl invalidDeps{ { "InvalidType", "loc" } };
237
238 // Expect an exception when an invalid type is provided
239 BOOST_CHECK_EXCEPTION( writer.createBranches( invalidDeps ), GaudiException, []( const GaudiException& e ) {
240 return e.message() == "Cannot create branch loc for unknown class: InvalidType. Provide a dictionary please.";
241 } );
242}
243
244// Test branch creation for fundamental types
245BOOST_AUTO_TEST_CASE( testCreateBranches_BasicTypes ) {
246 MockISvcLocator mockLocator;
247 auto tree = std::make_unique<TTree>( "testTree", "test tree" );
248 Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
249 writer.setTree( tree.get() );
250
251 DataObjIDColl dependencies{ { "int", "loc1" }, { "double", "loc2" }, { "std::string", "loc3" } };
252 std::unordered_set<std::string> expectedTypes{ "int", "double", "std::string" };
253 writer.createBranches( dependencies );
254
255 // Verify that the branch wrappers' class names match the expected types
256 BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() );
257 BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() );
258}
259
260// Test branch creation for ROOT-known non-fundamental types
261BOOST_AUTO_TEST_CASE( testCreateBranches_ROOTKnownTypes ) {
262 MockISvcLocator mockLocator;
263 auto tree = std::make_unique<TTree>( "testTree", "test tree" );
264 Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
265 writer.setTree( tree.get() );
266
267 DataObjIDColl dependencies{ { "std::vector<double>", "vectorDoubleLoc" }, { "TH1D", "hist1DLoc" } };
268 std::unordered_set<std::string> expectedTypes{ "std::vector<double>", "TH1D" };
269 writer.createBranches( dependencies );
270
271 // Verify that the branch wrappers' class names match the expected types
272 BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() );
273 BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() );
274}
275
276#endif
std::unordered_set< DataObjID, DataObjID_Hasher > DataObjIDColl
Definition DataObjID.h:121
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition MsgStream.h:198
#define DECLARE_COMPONENT(type)
MsgStream & error() const
shortcut for the method msgStream(MSG::ERROR)
MsgStream & info() const
shortcut for the method msgStream(MSG::INFO)
This class represents an entry point to all the event specific data.
Base class from which all concrete algorithm classes should be derived.
Definition Algorithm.h:87
SmartIF< IDataProviderSvc > & eventSvc() const
The standard event data service.
Algorithm(std::string name, ISvcLocator *svcloc, std::string version=PACKAGE_VERSION)
Constructor.
Definition Algorithm.h:98
StatusCode initialize() override
the default (empty) implementation of IStateful::initialize() method
Definition Algorithm.h:175
StatusCode finalize() override
the default (empty) implementation of IStateful::finalize() method
Definition Algorithm.h:181
const std::string & name() const override
The identifying name of the algorithm object.
SmartIF< IService > service(std::string_view name, const bool createIf=true, const bool quiet=false) const
Return a pointer to the service identified by name (or "type/name")
A Gaudi algorithm for writing data of any type from N locations in the event store to a TTree.
GenericWriter(const std::string &n, ISvcLocator *l)
Gaudi::Property< std::string > m_ntupleTname
Gaudi::Interfaces::IFileSvc * m_fileSvc
std::vector< Gaudi::details::BranchWrapper > m_branchWrappers
std::unordered_set< std::string > getBranchesClassNames() const
StatusCode execute(const EventContext &) const override
Gaudi::Property< std::string > m_fileId
std::shared_ptr< TFile > m_file
void createBranches(const DataObjIDColl &extraInputs)
Implementation of property with value of concrete type.
Definition PropertyFwd.h:27
Define general base for Gaudi exception.
Data provider interface definition.
virtual StatusCode queryInterface(const InterfaceID &ti, void **pp)=0
Set the void** to the pointer to the requested interface of the instance.
virtual void const * i_cast(const InterfaceID &) const =0
virtual unsigned long refCount() const =0
Current reference count.
virtual std::vector< std::string > getInterfaceNames() const =0
Returns a vector of strings containing the names of all the implemented interfaces.
virtual unsigned long decRef() const =0
Decrement reference count and return the new reference count.
virtual unsigned long addRef() const =0
Increment the reference count of Interface instance.
virtual unsigned long release() const =0
Release Interface instance.
The ISvcLocator is the interface implemented by the Service Factory in the Application Manager to loc...
Definition ISvcLocator.h:42
virtual const std::list< IService * > & getServices() const =0
Return the list of Services.
virtual bool existsService(std::string_view name) const =0
Check the existence of a service given a service name.
virtual SmartIF< IService > & service(const Gaudi::Utils::TypeNameString &typeName, const bool createIf=true)=0
Returns a smart pointer to a service.
This class is used for returning status codes from appropriate routines.
Definition StatusCode.h:64
StatusCode andThen(F &&f, ARGS &&... args) const
Chain code blocks making the execution conditional a success result.
Definition StatusCode.h:163
constexpr static const auto SUCCESS
Definition StatusCode.h:99
constexpr static const auto FAILURE
Definition StatusCode.h:100
STL class.
STL namespace.
Interface for a component that manages file access within Gaudi applications.
Definition IFileSvc.h:27