The Gaudi Framework  v36r1 (3e2fb5a8)
RootHistogramSink.cpp
Go to the documentation of this file.
1 /***********************************************************************************\
2 * (c) Copyright 1998-2021 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 
12 #include "GaudiKernel/Service.h"
13 
14 #include <Gaudi/MonitoringHub.h>
15 
16 #include <TDirectory.h>
17 #include <TFile.h>
18 #include <TH1D.h>
19 #include <TH2D.h>
20 #include <TH3D.h>
21 #include <TProfile.h>
22 #include <TProfile2D.h>
23 #include <TProfile3D.h>
24 
25 #include <algorithm>
26 #include <deque>
27 #include <range/v3/numeric/accumulate.hpp>
28 #include <range/v3/range/conversion.hpp>
29 #include <range/v3/view/split_when.hpp>
30 #include <range/v3/view/transform.hpp>
31 // upstream has renamed namespace ranges::view -> ranges::views
32 #if RANGE_V3_VERSION < 900
33 namespace ranges::views {
34  using namespace ranges::view;
35 }
36 #endif
37 #include <string>
38 #include <vector>
39 
40 namespace {
41 
42  struct Axis {
43  unsigned int nBins;
44  double minValue;
45  double maxValue;
46  std::string title;
47  };
48 
49  Axis toAxis( nlohmann::json& jAxis ) {
50  return {jAxis.at( "nBins" ).get<unsigned int>(), jAxis.at( "minValue" ).get<double>(),
51  jAxis.at( "maxValue" ).get<double>(),
52  ";" + jAxis.at( "title" ).get<std::string>()}; // ";" to prepare concatenations of titles
53  }
54 
55  template <typename Traits, std::size_t... index>
56  void saveRootHistoInternal( TFile& file, std::string dir, std::string name, const nlohmann::json& j,
57  std::index_sequence<index...> ) {
58  // extract data from json
59  auto jsonAxis = j.at( "axis" );
60  auto axis = std::array{toAxis( jsonAxis[index] )...};
61  auto weights = j.at( "bins" ).get<std::vector<typename Traits::WeightType>>();
62  auto title = j.at( "title" ).get<std::string>();
63  auto nentries = j.at( "nEntries" ).get<unsigned int>();
64  // weird way ROOT has to give titles to axis
65  title += ( axis[index].title + ... );
66  // compute total number of bins, multiplying bins per axis
67  auto totNBins = ( ( axis[index].nBins + 2 ) * ... );
68  assert( weights.size() == totNBins );
69 
70  // take into account the case where name contains '/'s (e.g. "Group/Name") by
71  // moving the prefix into dir
72  if ( auto pos = name.rfind( '/' ); pos != std::string::npos ) {
73  dir += '/' + name.substr( 0, pos );
74  name = name.substr( pos + 1 );
75  }
76 
77  // remember the current directory
78  auto previousDir = gDirectory;
79 
80  // find or create the directory for the histogram
81  {
82  using namespace ranges;
83  auto is_delimiter = []( auto c ) { return c == '/' || c == '.'; };
84  auto transform_to_string = views::transform( []( auto&& rng ) { return rng | to<std::string>; } );
85 
86  auto currentDir = accumulate( dir | views::split_when( is_delimiter ) | transform_to_string,
87  file.GetDirectory( "" ), []( auto current, auto&& dir_level ) {
88  if ( current ) {
89  // try to get next level
90  auto nextDir = current->GetDirectory( dir_level.c_str() );
91  // if it does not exist, create it
92  if ( !nextDir ) nextDir = current->mkdir( dir_level.c_str() );
93  // move to next level
94  current = nextDir;
95  }
96  return current;
97  } );
98 
99  if ( !currentDir )
100  throw GaudiException( "Could not create directory " + dir, "Histogram::Sink::Root", StatusCode::FAILURE );
101  // switch to the directory
102  currentDir->cd();
103  }
104 
105  // Create Root histogram calling constructors with the args tuple
106  auto histo = std::make_from_tuple<typename Traits::Histo>(
107  std::tuple_cat( std::tuple{name.c_str(), title.c_str()},
108  std::tuple{axis[index].nBins, axis[index].minValue, axis[index].maxValue}... ) );
109  // fill Histo
110  for ( unsigned int i = 0; i < totNBins; i++ ) Traits::fill( histo, i, weights[i] );
111  auto try_set_bin_labels = [&histo, &jsonAxis]( auto idx ) {
112  if ( jsonAxis[idx].contains( "labels" ) ) {
113  TAxis* axis = nullptr;
114  switch ( idx ) {
115  case 0:
116  axis = histo.GetXaxis();
117  break;
118  case 1:
119  axis = histo.GetYaxis();
120  break;
121  case 2:
122  axis = histo.GetZaxis();
123  break;
124  default:
125  break;
126  }
127  if ( axis ) {
128  const auto labels = jsonAxis[idx].at( "labels" );
129  for ( unsigned int i = 0; i < labels.size(); i++ ) {
130  axis->SetBinLabel( i + 1, labels[i].template get<std::string>().c_str() );
131  }
132  }
133  }
134  };
135  ( try_set_bin_labels( index ), ... );
136  // Fix the number of entries, wrongly computed by ROOT from the numer of calls to fill
137  histo.SetEntries( nentries );
138  // write to file
139  histo.Write();
140 
141  // switch back to the previous directory
142  previousDir->cd();
143  }
144 
145  template <bool isProfile, typename RootHisto>
146  struct Traits;
147 
148  template <typename RootHisto>
149  struct Traits<false, RootHisto> {
150  using Histo = RootHisto;
151  using WeightType = double;
152  static auto fill( Histo& histo, unsigned int i, const WeightType& weight ) { histo.SetBinContent( i, weight ); }
153  };
154  template <typename RootHisto>
155  struct Traits<true, RootHisto> {
157  template <typename TP>
158  struct ProfileWrapper : TP {
159  using TP::TP;
160  void setBinNEntries( Int_t i, Int_t n ) { this->fBinEntries.fArray[i] = n; }
161  void setBinW2( Int_t i, Double_t v ) { this->fSumw2.fArray[i] = v; }
162  };
163  using Histo = ProfileWrapper<RootHisto>;
164  using WeightType = std::tuple<std::tuple<unsigned int, double>, double>;
165  static constexpr auto fill( Histo& histo, unsigned int i, const WeightType& weight ) {
166  auto [c, sumWeight2] = weight;
167  auto [nEntries, sumWeight] = c;
168  histo.setBinNEntries( i, nEntries );
169  histo.SetBinContent( i, sumWeight );
170  histo.setBinW2( i, sumWeight2 );
171  };
172  };
173 
174  template <unsigned int N, bool isProfile, typename ROOTHisto>
175  void saveRootHisto( TFile& file, std::string dir, std::string name, nlohmann::json& j ) {
176  saveRootHistoInternal<Traits<isProfile, ROOTHisto>>( file, std::move( dir ), std::move( name ), j,
177  std::make_index_sequence<N>() );
178  }
179 
180  using namespace std::string_literals;
181  static const auto registry =
182  std::map{std::pair{std::pair{"histogram:Histogram"s, 1}, &saveRootHisto<1, false, TH1D>},
183  std::pair{std::pair{"histogram:WeightedHistogram"s, 1}, &saveRootHisto<1, false, TH1D>},
184  std::pair{std::pair{"histogram:Histogram"s, 2}, &saveRootHisto<2, false, TH2D>},
185  std::pair{std::pair{"histogram:WeightedHistogram"s, 2}, &saveRootHisto<2, false, TH2D>},
186  std::pair{std::pair{"histogram:Histogram"s, 3}, &saveRootHisto<3, false, TH3D>},
187  std::pair{std::pair{"histogram:WeightedHistogram"s, 3}, &saveRootHisto<3, false, TH3D>},
188  std::pair{std::pair{"histogram:ProfileHistogram"s, 1}, &saveRootHisto<1, true, TProfile>},
189  std::pair{std::pair{"histogram:WeightedProfileHistogram"s, 1}, &saveRootHisto<1, true, TProfile>},
190  std::pair{std::pair{"histogram:ProfileHistogram"s, 2}, &saveRootHisto<2, true, TProfile2D>},
191  std::pair{std::pair{"histogram:WeightedProfileHistogram"s, 2}, &saveRootHisto<2, true, TProfile2D>},
192  std::pair{std::pair{"histogram:ProfileHistogram"s, 3}, &saveRootHisto<3, true, TProfile3D>},
193  std::pair{std::pair{"histogram:WeightedProfileHistogram"s, 3}, &saveRootHisto<3, true, TProfile3D>}};
194 } // namespace
195 
197 
198  class Root : public Service, public Gaudi::Monitoring::Hub::Sink {
199 
200  public:
201  using Service::Service;
202 
203  StatusCode initialize() override {
204  return Service::initialize().andThen( [&] { serviceLocator()->monitoringHub().addSink( this ); } );
205  }
206 
207  StatusCode stop() override {
208  auto ok = Service::stop();
209  if ( !ok ) return ok;
210  std::sort( begin( m_monitoringEntities ), end( m_monitoringEntities ), []( const auto& a, const auto& b ) {
211  return std::tie( a.name, a.component ) > std::tie( b.name, b.component );
212  } );
213  TFile histoFile( m_fileName.value().c_str(), "RECREATE" );
214  std::for_each( begin( m_monitoringEntities ), end( m_monitoringEntities ), [&histoFile]( auto& ent ) {
215  auto j = ent.toJSON();
216  auto dim = j.at( "dimension" ).template get<unsigned int>();
217  auto type = j.at( "type" ).template get<std::string>();
218  // cut type after last ':' if there is one. The rest is precision parameter that we do not need here
219  // as ROOT anyway treats everything as doubles in histograms
220  type = type.substr( 0, type.find_last_of( ':' ) );
221  auto saver = registry.find( {type, dim} );
222  if ( saver == registry.end() )
223  throw GaudiException( "Unknown type : " + type + " dim : " + std::to_string( dim ), "Histogram::Sink::Root",
225  ( *saver->second )( histoFile, ent.component, ent.name, j );
226  } );
227  return ok;
228  }
229 
231  if ( std::string_view( ent.type ).substr( 0, 10 ) == "histogram:" ) {
232  m_monitoringEntities.emplace_back( std::move( ent ) );
233  }
234  }
235 
236  void removeEntity( Monitoring::Hub::Entity const& ent ) override {
237  auto it = std::find( begin( m_monitoringEntities ), end( m_monitoringEntities ), ent );
238  if ( it != m_monitoringEntities.end() ) { m_monitoringEntities.erase( it ); }
239  }
240 
241  private:
243 
244  Gaudi::Property<std::string> m_fileName{this, "FileName", "testHisto.root",
245  "Name of file where to save histograms"};
246  };
247 
248  DECLARE_COMPONENT( Root )
249 
250 } // namespace Gaudi::Histograming::Sink
Gaudi::Monitoring::Hub::Sink
Interface reporting services must implement.
Definition: MonitoringHub.h:78
std::for_each
T for_each(T... args)
Service::initialize
StatusCode initialize() override
Definition: Service.cpp:118
std::string
STL class.
Gaudi::Histograming::Sink::Root::removeEntity
void removeEntity(Monitoring::Hub::Entity const &ent) override
Definition: RootHistogramSink.cpp:236
GaudiPython.HistoUtils.nEntries
nEntries
Definition: HistoUtils.py:921
StatusCode::andThen
StatusCode andThen(F &&f, ARGS &&... args) const
Chain code blocks making the execution conditional a success result.
Definition: StatusCode.h:180
std::move
T move(T... args)
MonitoringHub.h
HistoEx.histo
histo
Definition: HistoEx.py:103
std::pair
gaudirun.s
string s
Definition: gaudirun.py:328
std::vector
STL class.
std::find
T find(T... args)
GaudiException
Definition: GaudiException.h:31
ranges
Definition: FunctionalDetails.h:33
std::tuple
gaudirun.c
c
Definition: gaudirun.py:509
ranges::views
Definition: FunctionalDetails.h:33
jsonFromLHCbLog.json
dictionary json
Definition: jsonFromLHCbLog.py:86
std::sort
T sort(T... args)
Service
Definition: Service.h:46
std::tie
T tie(T... args)
GaudiPluginService.cpluginsvc.registry
def registry()
Definition: cpluginsvc.py:82
TimingHistograms.name
name
Definition: TimingHistograms.py:23
StatusCode
Definition: StatusCode.h:65
std::string::at
T at(T... args)
Gaudi::Histograming::Sink::Root::stop
StatusCode stop() override
Definition: RootHistogramSink.cpp:207
Gaudi::Histograming::Sink::Root::initialize
StatusCode initialize() override
Definition: RootHistogramSink.cpp:203
Gaudi::Histograming::Sink
Definition: RootHistogramSink.cpp:196
CLHEP::begin
double * begin(CLHEP::HepVector &v)
Definition: TupleAlg.cpp:45
std::to_string
T to_string(T... args)
std::array
STL class.
std::deque< Gaudi::Monitoring::Hub::Entity >
std::map
STL class.
Gaudi::Histograming::Sink::Root::registerEntity
void registerEntity(Monitoring::Hub::Entity ent) override
Definition: RootHistogramSink.cpp:230
GaudiPluginService.cpluginsvc.n
n
Definition: cpluginsvc.py:221
Service.h
HistoDumpEx.v
v
Definition: HistoDumpEx.py:27
gaudirun.type
type
Definition: gaudirun.py:154
Service::stop
StatusCode stop() override
Definition: Service.cpp:181
Gaudi::Histograming::Sink::Root
Definition: RootHistogramSink.cpp:198
DECLARE_COMPONENT
#define DECLARE_COMPONENT(type)
Definition: PluginServiceV1.h:46
Service::Service
Service(std::string name, ISvcLocator *svcloc)
Standard Constructor
Definition: Service.cpp:339
std::tuple_cat
T tuple_cat(T... args)
std::size_t
Gaudi::Utils::Histos::fill
GAUDI_API void fill(AIDA::IHistogram1D *histo, const double value, const double weight=1.0)
simple function to fill AIDA::IHistogram1D objects
Definition: Fill.cpp:45
IOTest.end
end
Definition: IOTest.py:123
StatusCode::FAILURE
constexpr static const auto FAILURE
Definition: StatusCode.h:101
Gaudi::Monitoring::Hub::Entity
Wrapper class for arbitrary monitoring objects.
Definition: MonitoringHub.h:40
Gaudi::Accumulators::accumulate
void accumulate(Counter &counter, const Container &container, Fun f=Identity{})
A helper function for accumulating data from a container into a counter This is internally using buff...
Definition: Accumulators.h:1178
Gaudi::Property< std::string >
Gaudi::Histograming::Sink::Root::m_monitoringEntities
std::deque< Gaudi::Monitoring::Hub::Entity > m_monitoringEntities
Definition: RootHistogramSink.cpp:242
Gaudi::Monitoring::Hub::Entity::type
std::string type
type of the entity, see comment above concerning its format and usage
Definition: MonitoringHub.h:55