The Gaudi Framework  master (37c0b60a)
GenericNTupleWriter.cpp
Go to the documentation of this file.
1 /***********************************************************************************\
2 * (c) Copyright 2024 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 <fmt/format.h>
17 #include <functional>
18 #include <gsl/pointers>
19 #include <mutex>
20 #include <string>
21 #include <vector>
22 
23 namespace {
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 
39 namespace Gaudi::NTuple {
49  public:
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
54  StatusCode initialize() override {
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;
61  return StatusCode::FAILURE;
62  }
63 
64  m_fileSvc = service<Gaudi::Interfaces::IFileSvc>( "FileSvc" );
65  if ( !m_fileSvc ) {
66  error() << "Failed to retrieve FileSvc." << endmsg;
67  return StatusCode::FAILURE;
68  }
69 
71  if ( !m_file ) {
72  error() << "Failed to retrieve TFile." << endmsg;
73  return StatusCode::FAILURE;
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
81  m_eventSvc = eventSvc();
82 
83  return StatusCode::SUCCESS;
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 {
90 
91  for ( std::size_t i = 0; const auto& wrapper : m_branchWrappers ) {
92  m_eventSvc->retrieveObject( wrapper.getLocation(), pObjs[i++] )
93  .orThrow( fmt::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 
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 
166 class MockISvcLocator : public ISvcLocator {
167 public:
168  virtual StatusCode getService( const Gaudi::Utils::TypeNameString&, const InterfaceID&, IInterface*& ) override {
169  return StatusCode::SUCCESS;
170  }
171 
172  virtual StatusCode getService( const Gaudi::Utils::TypeNameString&, IService*&, const bool ) override {
173  return StatusCode::SUCCESS;
174  }
175 
176  virtual const std::list<IService*>& getServices() const override {
177  static std::list<IService*> dummyServices;
178  return dummyServices;
179  }
180 
181  virtual bool existsService( std::string_view ) const override { return false; }
182 
183  virtual SmartIF<IService>& service( const Gaudi::Utils::TypeNameString&, const bool ) override {
184  static SmartIF<IService> dummyService;
185  return dummyService;
186  }
187 
188  virtual unsigned long addRef() override { return 0; }
189 
190  virtual unsigned long release() override { return 0; }
191 
192  virtual StatusCode queryInterface( const InterfaceID&, void** ) override { return StatusCode::SUCCESS; }
193 
194  virtual std::vector<std::string> getInterfaceNames() const override { return {}; }
195 
196  virtual void* i_cast( const InterfaceID& ) const override { return nullptr; }
197 
198  virtual unsigned long refCount() const override { return 1; }
199 };
200 
201 // Utility function tests
202 BOOST_AUTO_TEST_CASE( testGetTypeName ) {
203  // Test type name extraction from various formats
204  BOOST_CHECK_EQUAL( getTypeName( "MyClass" ), "MyClass" );
205  BOOST_CHECK_EQUAL( getTypeName( "std::vector<double>" ), "std::vector<double>" );
206  BOOST_CHECK_EQUAL( getTypeName( "UNKNOWN_CLASS:MyCustomClass" ), "MyCustomClass" );
207 }
208 
209 BOOST_AUTO_TEST_CASE( testGetNameFromLoc ) {
210  // Test name extraction from location strings
211  BOOST_CHECK_EQUAL( getNameFromLoc( "/Event/MyAlg/MyData" ), "MyData" );
212  BOOST_CHECK_EQUAL( getNameFromLoc( "MyAlg/MyData" ), "MyData" );
213  BOOST_CHECK_EQUAL( getNameFromLoc( "MyData" ), "MyData" );
214  BOOST_CHECK_EQUAL( getNameFromLoc( "" ), "" );
215 }
216 
217 // Test instantiation of the GenericWriter
218 BOOST_AUTO_TEST_CASE( testInit ) {
219  MockISvcLocator mockLocator;
220  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
221 
222  // Verify we can instanciate a writer
223  BOOST_CHECK_EQUAL( writer.name(), "test_writer" );
224 }
225 
226 // Test branch creation with empty dependencies
227 BOOST_AUTO_TEST_CASE( testCreateBranches_EmptyDeps ) {
228  MockISvcLocator mockLocator;
229  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
230 
231  // Expect an failure when no dependencies are provided
232  BOOST_CHECK_EQUAL( writer.initialize(), StatusCode::FAILURE );
233 }
234 
235 // Test branch creation with an invalid type
236 BOOST_AUTO_TEST_CASE( testCreateBranches_InvalidType ) {
237  MockISvcLocator mockLocator;
238  auto tree = std::make_unique<TTree>( "testTree", "test tree" );
239  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
240  writer.setTree( tree.get() );
241 
242  DataObjIDColl invalidDeps{ { "InvalidType", "loc" } };
243 
244  // Expect an exception when an invalid type is provided
245  BOOST_CHECK_EXCEPTION( writer.createBranches( invalidDeps ), GaudiException, []( const GaudiException& e ) {
246  return e.message() == "Cannot create branch loc for unknown class: InvalidType. Provide a dictionary please.";
247  } );
248 }
249 
250 // Test branch creation for fundamental types
251 BOOST_AUTO_TEST_CASE( testCreateBranches_BasicTypes ) {
252  MockISvcLocator mockLocator;
253  auto tree = std::make_unique<TTree>( "testTree", "test tree" );
254  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
255  writer.setTree( tree.get() );
256 
257  DataObjIDColl dependencies{ { "int", "loc1" }, { "double", "loc2" }, { "std::string", "loc3" } };
258  std::unordered_set<std::string> expectedTypes{ "int", "double", "std::string" };
259  writer.createBranches( dependencies );
260 
261  // Verify that the branch wrappers' class names match the expected types
262  BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() );
263  BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() );
264 }
265 
266 // Test branch creation for ROOT-known non-fundamental types
267 BOOST_AUTO_TEST_CASE( testCreateBranches_ROOTKnownTypes ) {
268  MockISvcLocator mockLocator;
269  auto tree = std::make_unique<TTree>( "testTree", "test tree" );
270  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
271  writer.setTree( tree.get() );
272 
273  DataObjIDColl dependencies{ { "std::vector<double>", "vectorDoubleLoc" }, { "TH1D", "hist1DLoc" } };
274  std::unordered_set<std::string> expectedTypes{ "std::vector<double>", "TH1D" };
275  writer.createBranches( dependencies );
276 
277  // Verify that the branch wrappers' class names match the expected types
278  BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() );
279  BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() );
280 }
281 
282 #endif
IService
Definition: IService.h:28
Gaudi::NTuple::GenericWriter::getBranchWrappersSize
size_t getBranchWrappersSize() const
Definition: GenericNTupleWriter.cpp:132
ISvcLocator::getServices
virtual const std::list< IService * > & getServices() const =0
Get a reference to a service and create it if it does not exists.
WriteWhiteBoard.writer
writer
Definition: WriteWhiteBoard.py:57
std::string
STL class.
std::shared_ptr< TFile >
std::list< IService * >
StatusCode::andThen
StatusCode andThen(F &&f, ARGS &&... args) const
Chain code blocks making the execution conditional a success result.
Definition: StatusCode.h:163
StatusCode::orThrow
const StatusCode & orThrow(std::string_view message, std::string_view tag) const
Throw a GaudiException in case of failures.
Definition: StatusCode.h:206
Gaudi::Algorithm::name
const std::string & name() const override
The identifying name of the algorithm object.
Definition: Algorithm.cpp:526
Gaudi::Algorithm::eventSvc
SmartIF< IDataProviderSvc > & eventSvc() const
The standard event data service.
Definition: Algorithm.cpp:559
std::unordered_set< DataObjID, DataObjID_Hasher >
std::vector::reserve
T reserve(T... args)
std::vector< DataObject * >
std::string::length
T length(T... args)
ISvcLocator
Definition: ISvcLocator.h:46
Gaudi::NTuple::GenericWriter::m_mtx
std::mutex m_mtx
Definition: GenericNTupleWriter.cpp:150
Gaudi::Algorithm::initialize
StatusCode initialize() override
the default (empty) implementation of IStateful::initialize() method
Definition: Algorithm.h:178
GaudiException
Definition: GaudiException.h:31
Gaudi::NTuple::GenericWriter::m_branchWrappers
std::vector< Gaudi::details::BranchWrapper > m_branchWrappers
Definition: GenericNTupleWriter.cpp:151
Gaudi::NTuple::GenericWriter::m_file
std::shared_ptr< TFile > m_file
Definition: GenericNTupleWriter.cpp:146
IInterface::queryInterface
virtual StatusCode queryInterface(const InterfaceID &ti, void **pp)=0
Set the void** to the pointer to the requested interface of the instance.
Gaudi::NTuple::GenericWriter::m_eventSvc
IDataProviderSvc * m_eventSvc
Definition: GenericNTupleWriter.cpp:148
Gaudi::NTuple::GenericWriter::finalize
StatusCode finalize() override
Definition: GenericNTupleWriter.cpp:109
ISvcLocator::getService
virtual StatusCode getService(const Gaudi::Utils::TypeNameString &typeName, IService *&svc, const bool createIf=true)
Get a reference to the service given a service name.
Definition: ISvcLocator.h:57
Gaudi::NTuple::GenericWriter::initialize
StatusCode initialize() override
Definition: GenericNTupleWriter.cpp:54
Gaudi::NTuple::GenericWriter::createBranches
void createBranches(const DataObjIDColl &extraInputs)
Definition: GenericNTupleWriter.cpp:122
Gaudi::Utils::TypeNameString
Helper class to parse a string of format "type/name".
Definition: TypeNameString.h:20
Gaudi::NTuple::GenericWriter::m_fileId
Gaudi::Property< std::string > m_fileId
Definition: GenericNTupleWriter.cpp:143
StatusCode
Definition: StatusCode.h:65
IInterface::i_cast
virtual void * i_cast(const InterfaceID &) const =0
main cast function
Gaudi::NTuple::GenericWriter::m_fileSvc
Gaudi::Interfaces::IFileSvc * m_fileSvc
Definition: GenericNTupleWriter.cpp:149
IDataProviderSvc::retrieveObject
virtual StatusCode retrieveObject(IRegistry *pDirectory, std::string_view path, DataObject *&pObject)=0
Retrieve object identified by its directory entry.
IFileSvc.h
Gaudi::Algorithm
Base class from which all concrete algorithm classes should be derived.
Definition: Algorithm.h:90
Gaudi::NTuple
Definition: Writer.h:28
ISvcLocator::service
StatusCode service(const Gaudi::Utils::TypeNameString &name, T *&svc, bool createIf=true)
Templated method to access a service by name.
Definition: ISvcLocator.h:98
Gaudi::Property::value
const ValueType & value() const
Definition: Property.h:237
Algorithm.h
DataHandleHolderBase< PropertyHolder< CommonMessaging< implements< IAlgorithm, IDataHandleHolder, IProperty, IStateful > > > >::extraInputDeps
virtual const DataObjIDColl & extraInputDeps() const override
Definition: DataHandleHolderBase.h:45
SmartIF< IService >
Gaudi::NTuple::GenericWriter::setTree
void setTree(TTree *tree)
Definition: GenericNTupleWriter.cpp:140
format
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:119
endmsg
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition: MsgStream.h:202
Gaudi::NTuple::GenericWriter::GenericWriter
GenericWriter(const std::string &n, ISvcLocator *l)
Definition: GenericNTupleWriter.cpp:50
cpluginsvc.n
n
Definition: cpluginsvc.py:234
Gaudi::NTuple::GenericWriter::execute
StatusCode execute(const EventContext &) const override
Definition: GenericNTupleWriter.cpp:88
std::string::substr
T substr(T... args)
Gaudi::NTuple::GenericWriter::m_ntupleTname
Gaudi::Property< std::string > m_ntupleTname
Definition: GenericNTupleWriter.cpp:144
std::vector::emplace_back
T emplace_back(T... args)
Gaudi::Algorithm::finalize
StatusCode finalize() override
the default (empty) implementation of IStateful::finalize() method
Definition: Algorithm.h:184
StatusCode::SUCCESS
constexpr static const auto SUCCESS
Definition: StatusCode.h:100
GaudiDict::typeName
std::string typeName(const std::type_info &typ)
Definition: Dictionary.cpp:31
gaudirun.l
dictionary l
Definition: gaudirun.py:583
BranchWrapper.h
std
STL namespace.
DECLARE_COMPONENT
#define DECLARE_COMPONENT(type)
Definition: PluginServiceV1.h:46
Gaudi::NTuple::GenericWriter
A Gaudi algorithm for writing data of any type from N locations in the event store to a TTree....
Definition: GenericNTupleWriter.cpp:48
std::unordered_set::insert
T insert(T... args)
IInterface
Definition: IInterface.h:239
EventContext
Definition: EventContext.h:34
IInterface::getInterfaceNames
virtual std::vector< std::string > getInterfaceNames() const =0
Returns a vector of strings containing the names of all the implemented interfaces.
Gaudi::Interfaces::IFileSvc::getFile
virtual std::shared_ptr< TFile > getFile(const std::string &identifier)=0
std::mutex
STL class.
std::size_t
Gaudi::Interfaces::IFileSvc
Interface for a component that manages file access within Gaudi applications.
Definition: IFileSvc.h:27
IDataProviderSvc
Definition: IDataProviderSvc.h:53
Gaudi::NTuple::GenericWriter::m_tree
TTree * m_tree
Definition: GenericNTupleWriter.cpp:147
InterfaceID
Definition: IInterface.h:39
StatusCode::FAILURE
constexpr static const auto FAILURE
Definition: StatusCode.h:101
IInterface::refCount
virtual unsigned long refCount() const =0
Current reference count.
IInterface::release
virtual unsigned long release()=0
Release Interface instance.
IInterface::addRef
virtual unsigned long addRef()=0
Increment the reference count of Interface instance.
ISvcLocator::existsService
virtual bool existsService(std::string_view name) const =0
Check the existence of a service given a service name.
Gaudi::Property< std::string >
Gaudi::NTuple::GenericWriter::getBranchesClassNames
std::unordered_set< std::string > getBranchesClassNames() const
Definition: GenericNTupleWriter.cpp:134