The Gaudi Framework  v38r3 (c3fc9673)
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>
13 #include <TFile.h>
14 #include <TTree.h>
15 #include <fmt/format.h>
16 #include <functional>
17 #include <gsl/pointers>
18 #include <mutex>
19 #include <string>
20 #include <vector>
21 
22 namespace {
23  // Extract the type name from a string, handles types marked as UNKNOWN_CLASS
24  auto getTypeName( std::string_view dependency ) {
25  auto unknownClassPos = dependency.find( "UNKNOWN_CLASS:" );
26  return ( unknownClassPos != std::string::npos )
27  ? std::string( dependency.substr( unknownClassPos + std::string( "UNKNOWN_CLASS:" ).length() ) )
28  : std::string( dependency );
29  }
30 
31  // Extract the name from a path in the TES string by returning the last part after a slash
32  auto getNameFromLoc( std::string_view loc ) {
33  auto lastSlashPos = loc.find_last_of( '/' );
34  return std::string{ lastSlashPos != loc.npos ? loc.substr( lastSlashPos + 1 ) : loc };
35  }
36 } // namespace
37 
38 namespace Gaudi::NTuple {
48  public:
50  : Algorithm( n, l )
51  , m_filename( this, "TreeFilename", "generic_ntuple_writer_tree.root",
52  "Filename where the NTuple writer alg writes the ttree." ) {}
53 
54  // Initialize the algorithm, set up the ROOT file and a TTree branch for each input location
55  StatusCode initialize() override {
56  return Gaudi::Algorithm::initialize().andThen( [this]() {
57  const auto& extraInputs = extraInputDeps();
58  if ( extraInputs.empty() ) {
59  throw GaudiException(
60  "No extra inputs locations specified. Please define extra inputs for the NTuple writer.", name(),
62  }
63 
64  auto tempFile = std::make_unique<TFile>( m_filename.value().c_str(), "RECREATE" );
65  if ( !tempFile || tempFile->IsZombie() ) {
66  throw GaudiException(
67  fmt::format( "Failed to open file '{}'. Check file path and permissions.", m_filename.value() ), name(),
69  }
70 
71  m_tree = std::make_unique<TTree>( "GenericWriterTree", "Tree of GenericWriter Algorithm" ).release();
72  if ( !m_tree ) {
73  throw GaudiException( "Failed to create TTree. Ensure sufficient resources and permissions.", name(),
75  }
76 
77  createBranches( extraInputs );
78 
79  m_eventSvc = eventSvc();
80  if ( !m_eventSvc ) {
81  throw GaudiException(
82  "Failed to retrieve the event service. Ensure the Event Service is properly configured and available.",
84  }
85 
86  m_file = std::move( tempFile );
87 
88  return StatusCode::SUCCESS;
89  } );
90  }
91 
92  // Execute the algorithm for each event, retrieving data from the event store and writing it to the TTree
93  StatusCode execute( const EventContext& ) const override {
95 
96  DataObject* pObj = nullptr;
97  for ( auto& wrapper : m_branchWrappers ) {
98  auto sc = m_eventSvc->retrieveObject( wrapper.getLocation(), pObj );
99 
100  if ( sc.isFailure() ) {
101  throw GaudiException(
102  fmt::format(
103  "Failed to retrieve object for location '{}'. Ensure the location is correct and the object exists.",
104  wrapper.getLocation() ),
106  }
107 
108  wrapper.setBranchData( pObj );
109  }
110 
111  m_tree->Fill();
112 
113  return StatusCode::SUCCESS;
114  }
115 
116  // Finalize the algorithm by writing the TTree to the file, resetting the branches and closing the file
117  StatusCode finalize() override {
118  m_file->cd();
119  if ( m_tree->Write() <= 0 ) {
120  throw GaudiException( "Failed to write TTree to ROOT file.", name(), StatusCode::FAILURE );
121  }
122  m_tree = nullptr;
123  info() << "TTree written to ROOT file. File closed." << endmsg;
124 
126  }
127 
128  // Create branches for each input location specified in the configuration
129  void createBranches( const DataObjIDColl& extraInputs ) {
130  m_branchWrappers.reserve( m_branchWrappers.size() + extraInputs.size() );
131  for ( const auto& dep : extraInputs ) {
132  auto typeName = getTypeName( dep.className() );
133  auto branchName = getNameFromLoc( dep.key() );
134  m_branchWrappers.emplace_back( m_tree, typeName, branchName, dep.key(), *this );
135  }
136  }
137 
138  // Getters, Setters
139  size_t getBranchWrappersSize() const { return m_branchWrappers.size(); }
140 
143  for ( auto& wrapper : m_branchWrappers ) { classNames.insert( wrapper.getClassName() ); }
144  return classNames;
145  }
146 
147  void setTree( TTree* tree ) { m_tree = tree; }
148 
149  private:
150  Gaudi::Property<std::string> m_filename; // Property to hold the filename where the TTree will be saved
151  std::unique_ptr<TFile> m_file = nullptr; // Smart pointer to the ROOT TFile object
152  TTree* m_tree = nullptr; // Pointer to the ROOT TTree object
153  IDataProviderSvc* m_eventSvc = nullptr; // Pointer to the event service interface for data retrieval
154  mutable std::mutex m_mtx; // Mutex for thread-safe operations on the GenericWriter object
155  mutable std::vector<Gaudi::details::BranchWrapper> m_branchWrappers{}; // Container for all BranchWrapper instances
156  };
157 
158  DECLARE_COMPONENT( GenericWriter )
159 } // namespace Gaudi::NTuple
160 
161 #ifdef UNIT_TESTS
162 
163 # define BOOST_TEST_MODULE test_GenericNTupleWriter
164 # include <boost/test/unit_test.hpp>
165 
170 class MockISvcLocator : public ISvcLocator {
171 public:
172  virtual StatusCode getService( const Gaudi::Utils::TypeNameString&, const InterfaceID&, IInterface*& ) override {
173  return StatusCode::SUCCESS;
174  }
175 
176  virtual StatusCode getService( const Gaudi::Utils::TypeNameString&, IService*&, const bool ) override {
177  return StatusCode::SUCCESS;
178  }
179 
180  virtual const std::list<IService*>& getServices() const override {
181  static std::list<IService*> dummyServices;
182  return dummyServices;
183  }
184 
185  virtual bool existsService( std::string_view ) const override { return false; }
186 
187  virtual SmartIF<IService>& service( const Gaudi::Utils::TypeNameString&, const bool ) override {
188  static SmartIF<IService> dummyService;
189  return dummyService;
190  }
191 
192  virtual unsigned long addRef() override { return 0; }
193 
194  virtual unsigned long release() override { return 0; }
195 
196  virtual StatusCode queryInterface( const InterfaceID&, void** ) override { return StatusCode::SUCCESS; }
197 
198  virtual std::vector<std::string> getInterfaceNames() const override { return {}; }
199 
200  virtual void* i_cast( const InterfaceID& ) const override { return nullptr; }
201 
202  virtual unsigned long refCount() const override { return 1; }
203 };
204 
205 // Utility function tests
206 BOOST_AUTO_TEST_CASE( testGetTypeName ) {
207  // Test type name extraction from various formats
208  BOOST_CHECK_EQUAL( getTypeName( "MyClass" ), "MyClass" );
209  BOOST_CHECK_EQUAL( getTypeName( "std::vector<double>" ), "std::vector<double>" );
210  BOOST_CHECK_EQUAL( getTypeName( "UNKNOWN_CLASS:MyCustomClass" ), "MyCustomClass" );
211 }
212 
213 BOOST_AUTO_TEST_CASE( testGetNameFromLoc ) {
214  // Test name extraction from location strings
215  BOOST_CHECK_EQUAL( getNameFromLoc( "/Event/MyAlg/MyData" ), "MyData" );
216  BOOST_CHECK_EQUAL( getNameFromLoc( "MyAlg/MyData" ), "MyData" );
217  BOOST_CHECK_EQUAL( getNameFromLoc( "MyData" ), "MyData" );
218  BOOST_CHECK_EQUAL( getNameFromLoc( "" ), "" );
219 }
220 
221 // Test instantiation of the GenericWriter
222 BOOST_AUTO_TEST_CASE( testInit ) {
223  MockISvcLocator mockLocator;
224  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
225 
226  // Verify we can instanciate a writer
227  BOOST_CHECK_EQUAL( writer.name(), "test_writer" );
228 }
229 
230 // Test branch creation with empty dependencies
231 BOOST_AUTO_TEST_CASE( testCreateBranches_EmptyDeps ) {
232  MockISvcLocator mockLocator;
233  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
234 
235  // Expect an exception when no dependencies are provided
236  BOOST_CHECK_EXCEPTION( writer.initialize().ignore(), GaudiException, []( const GaudiException& e ) {
237  return e.message() == "No extra inputs locations specified. Please define extra inputs for the NTuple writer.";
238  } );
239 }
240 
241 // Test branch creation with an invalid type
242 BOOST_AUTO_TEST_CASE( testCreateBranches_InvalidType ) {
243  MockISvcLocator mockLocator;
244  auto tree = std::make_unique<TTree>( "testTree", "test tree" );
245  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
246  writer.setTree( tree.get() );
247 
248  DataObjIDColl invalidDeps{ { "InvalidType", "loc" } };
249 
250  // Expect an exception when an invalid type is provided
251  BOOST_CHECK_EXCEPTION( writer.createBranches( invalidDeps ), GaudiException, []( const GaudiException& e ) {
252  return e.message() == "Cannot create branch loc for unknown class: InvalidType. Provide a dictionary please.";
253  } );
254 }
255 
256 // Test branch creation for fundamental types
257 BOOST_AUTO_TEST_CASE( testCreateBranches_BasicTypes ) {
258  MockISvcLocator mockLocator;
259  auto tree = std::make_unique<TTree>( "testTree", "test tree" );
260  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
261  writer.setTree( tree.get() );
262 
263  DataObjIDColl dependencies{ { "int", "loc1" }, { "double", "loc2" }, { "std::string", "loc3" } };
264  std::unordered_set<std::string> expectedTypes{ "int", "double", "std::string" };
265  writer.createBranches( dependencies );
266 
267  // Verify that the branch wrappers' class names match the expected types
268  BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() );
269  BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() );
270 }
271 
272 // Test branch creation for ROOT-known non-fundamental types
273 BOOST_AUTO_TEST_CASE( testCreateBranches_ROOTKnownTypes ) {
274  MockISvcLocator mockLocator;
275  auto tree = std::make_unique<TTree>( "testTree", "test tree" );
276  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
277  writer.setTree( tree.get() );
278 
279  DataObjIDColl dependencies{ { "std::vector<double>", "vectorDoubleLoc" }, { "TH1D", "hist1DLoc" } };
280  std::unordered_set<std::string> expectedTypes{ "std::vector<double>", "TH1D" };
281  writer.createBranches( dependencies );
282 
283  // Verify that the branch wrappers' class names match the expected types
284  BOOST_CHECK_EQUAL( writer.getBranchWrappersSize(), expectedTypes.size() );
285  BOOST_CHECK( expectedTypes == writer.getBranchesClassNames() );
286 }
287 
288 // Test for GaudiException when the file cannot be opened
289 BOOST_AUTO_TEST_CASE( testFileOpenException ) {
290  MockISvcLocator mockLocator;
291  Gaudi::NTuple::GenericWriter writer( "test_writer", &mockLocator );
292  DataObjIDColl dependencies{ { "float", "loc" } };
293 
294  BOOST_CHECK( writer.setProperty( "ExtraInputs", dependencies ).isSuccess() );
295  BOOST_CHECK( writer.setProperty( "TreeFilename", "/invalid/path/to/file.root" ).isSuccess() );
296  BOOST_CHECK_EXCEPTION( writer.initialize().ignore(), GaudiException, []( const GaudiException& e ) {
297  return e.message() == "Failed to open file '/invalid/path/to/file.root'. Check file path and permissions.";
298  } );
299 }
300 
301 #endif
IService
Definition: IService.h:28
Gaudi::NTuple::GenericWriter::getBranchWrappersSize
size_t getBranchWrappersSize() const
Definition: GenericNTupleWriter.cpp:139
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::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
std::move
T move(T... args)
Gaudi::Algorithm::name
const std::string & name() const override
The identifying name of the algorithm object.
Definition: Algorithm.cpp:528
Gaudi::Algorithm::eventSvc
SmartIF< IDataProviderSvc > & eventSvc() const
The standard event data service.
Definition: Algorithm.cpp:561
std::unordered_set< DataObjID, DataObjID_Hasher >
std::vector::reserve
T reserve(T... args)
std::vector< Gaudi::details::BranchWrapper >
std::string::length
T length(T... args)
ISvcLocator
Definition: ISvcLocator.h:46
Gaudi::NTuple::GenericWriter::m_mtx
std::mutex m_mtx
Definition: GenericNTupleWriter.cpp:154
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:155
std::lock_guard
STL class.
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:153
Gaudi::NTuple::GenericWriter::finalize
StatusCode finalize() override
Definition: GenericNTupleWriter.cpp:117
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:56
Gaudi::NTuple::GenericWriter::m_filename
Gaudi::Property< std::string > m_filename
Definition: GenericNTupleWriter.cpp:150
Gaudi::NTuple::GenericWriter::initialize
StatusCode initialize() override
Definition: GenericNTupleWriter.cpp:55
Gaudi::NTuple::GenericWriter::createBranches
void createBranches(const DataObjIDColl &extraInputs)
Definition: GenericNTupleWriter.cpp:129
Gaudi::Utils::TypeNameString
Helper class to parse a string of format "type/name".
Definition: TypeNameString.h:20
StatusCode
Definition: StatusCode.h:65
IInterface::i_cast
virtual void * i_cast(const InterfaceID &) const =0
main cast function
IDataProviderSvc::retrieveObject
virtual StatusCode retrieveObject(IRegistry *pDirectory, std::string_view path, DataObject *&pObject)=0
Retrieve object identified by its directory entry.
Gaudi::Algorithm
Base class from which all concrete algorithm classes should be derived.
Definition: Algorithm.h:90
Gaudi::NTuple
Definition: Writer.h:27
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:97
Gaudi::Property::value
const ValueType & value() const
Definition: Property.h:239
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:147
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:203
Gaudi::NTuple::GenericWriter::GenericWriter
GenericWriter(const std::string &n, ISvcLocator *l)
Definition: GenericNTupleWriter.cpp:49
GaudiPluginService.cpluginsvc.n
n
Definition: cpluginsvc.py:234
Gaudi::NTuple::GenericWriter::execute
StatusCode execute(const EventContext &) const override
Definition: GenericNTupleWriter.cpp:93
std::string::substr
T substr(T... args)
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
Gaudi::NTuple::GenericWriter::m_file
std::unique_ptr< TFile > m_file
Definition: GenericNTupleWriter.cpp:151
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:581
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:47
std::unordered_set::insert
T insert(T... args)
IInterface
Definition: IInterface.h:237
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.
DataObject
Definition: DataObject.h:36
std::mutex
STL class.
IDataProviderSvc
Definition: IDataProviderSvc.h:53
Gaudi::NTuple::GenericWriter::m_tree
TTree * m_tree
Definition: GenericNTupleWriter.cpp:152
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.
std::unique_ptr< TFile >
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:141