Loading [MathJax]/extensions/tex2jax.js
The Gaudi Framework  v36r7 (7f57a304)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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  if ( name[0] == '/' ) {
71  dir = "";
72  name = name.substr( 1 );
73  }
74 
75  // take into account the case where name contains '/'s (e.g. "Group/Name") by
76  // moving the prefix into dir
77  if ( auto pos = name.rfind( '/' ); pos != std::string::npos ) {
78  dir += '/' + name.substr( 0, pos );
79  name = name.substr( pos + 1 );
80  }
81 
82  // remember the current directory
83  auto previousDir = gDirectory;
84 
85  // find or create the directory for the histogram
86  {
87  using namespace ranges;
88  auto is_delimiter = []( auto c ) { return c == '/' || c == '.'; };
89  auto transform_to_string = views::transform( []( auto&& rng ) { return rng | to<std::string>; } );
90 
91  auto currentDir = accumulate( dir | views::split_when( is_delimiter ) | transform_to_string,
92  file.GetDirectory( "" ), []( auto current, auto&& dir_level ) {
93  if ( current ) {
94  // try to get next level
95  auto nextDir = current->GetDirectory( dir_level.c_str() );
96  // if it does not exist, create it
97  if ( !nextDir ) nextDir = current->mkdir( dir_level.c_str() );
98  // move to next level
99  current = nextDir;
100  }
101  return current;
102  } );
103 
104  if ( !currentDir )
105  throw GaudiException( "Could not create directory " + dir, "Histogram::Sink::Root", StatusCode::FAILURE );
106  // switch to the directory
107  currentDir->cd();
108  }
109 
110  // Create Root histogram calling constructors with the args tuple
111  auto histo = std::make_from_tuple<typename Traits::Histo>(
112  std::tuple_cat( std::tuple{ name.c_str(), title.c_str() },
113  std::tuple{ axis[index].nBins, axis[index].minValue, axis[index].maxValue }... ) );
114  // fill Histo
115  for ( unsigned int i = 0; i < totNBins; i++ ) Traits::fill( histo, i, weights[i] );
116  auto try_set_bin_labels = [&histo, &jsonAxis]( auto idx ) {
117  if ( jsonAxis[idx].contains( "labels" ) ) {
118  TAxis* axis = nullptr;
119  switch ( idx ) {
120  case 0:
121  axis = histo.GetXaxis();
122  break;
123  case 1:
124  axis = histo.GetYaxis();
125  break;
126  case 2:
127  axis = histo.GetZaxis();
128  break;
129  default:
130  break;
131  }
132  if ( axis ) {
133  const auto labels = jsonAxis[idx].at( "labels" );
134  for ( unsigned int i = 0; i < labels.size(); i++ ) {
135  axis->SetBinLabel( i + 1, labels[i].template get<std::string>().c_str() );
136  }
137  }
138  }
139  };
140  ( try_set_bin_labels( index ), ... );
141  // Fix the number of entries, wrongly computed by ROOT from the numer of calls to fill
142  histo.SetEntries( nentries );
143  // write to file
144  histo.Write();
145 
146  // switch back to the previous directory
147  previousDir->cd();
148  }
149 
150  template <bool isProfile, typename RootHisto>
151  struct Traits;
152 
153  template <typename RootHisto>
154  struct Traits<false, RootHisto> {
155  using Histo = RootHisto;
156  using WeightType = double;
157  static auto fill( Histo& histo, unsigned int i, const WeightType& weight ) { histo.SetBinContent( i, weight ); }
158  };
159  template <typename RootHisto>
160  struct Traits<true, RootHisto> {
162  template <typename TP>
163  struct ProfileWrapper : TP {
164  using TP::TP;
165  void setBinNEntries( Int_t i, Int_t n ) { this->fBinEntries.fArray[i] = n; }
166  void setBinW2( Int_t i, Double_t v ) { this->fSumw2.fArray[i] = v; }
167  };
168  using Histo = ProfileWrapper<RootHisto>;
169  using WeightType = std::tuple<std::tuple<unsigned int, double>, double>;
170  static constexpr auto fill( Histo& histo, unsigned int i, const WeightType& weight ) {
171  auto [c, sumWeight2] = weight;
172  auto [nEntries, sumWeight] = c;
173  histo.setBinNEntries( i, nEntries );
174  histo.SetBinContent( i, sumWeight );
175  histo.setBinW2( i, sumWeight2 );
176  };
177  };
178 
179  template <unsigned int N, bool isProfile, typename ROOTHisto>
180  void saveRootHisto( TFile& file, std::string dir, std::string name, nlohmann::json& j ) {
181  saveRootHistoInternal<Traits<isProfile, ROOTHisto>>( file, std::move( dir ), std::move( name ), j,
182  std::make_index_sequence<N>() );
183  }
184 
185  using namespace std::string_literals;
186  static const auto registry = std::map{
187  std::pair{ std::pair{ "histogram:Histogram"s, 1 }, &saveRootHisto<1, false, TH1D> },
188  std::pair{ std::pair{ "histogram:WeightedHistogram"s, 1 }, &saveRootHisto<1, false, TH1D> },
189  std::pair{ std::pair{ "histogram:Histogram"s, 2 }, &saveRootHisto<2, false, TH2D> },
190  std::pair{ std::pair{ "histogram:WeightedHistogram"s, 2 }, &saveRootHisto<2, false, TH2D> },
191  std::pair{ std::pair{ "histogram:Histogram"s, 3 }, &saveRootHisto<3, false, TH3D> },
192  std::pair{ std::pair{ "histogram:WeightedHistogram"s, 3 }, &saveRootHisto<3, false, TH3D> },
193  std::pair{ std::pair{ "histogram:ProfileHistogram"s, 1 }, &saveRootHisto<1, true, TProfile> },
194  std::pair{ std::pair{ "histogram:WeightedProfileHistogram"s, 1 }, &saveRootHisto<1, true, TProfile> },
195  std::pair{ std::pair{ "histogram:ProfileHistogram"s, 2 }, &saveRootHisto<2, true, TProfile2D> },
196  std::pair{ std::pair{ "histogram:WeightedProfileHistogram"s, 2 }, &saveRootHisto<2, true, TProfile2D> },
197  std::pair{ std::pair{ "histogram:ProfileHistogram"s, 3 }, &saveRootHisto<3, true, TProfile3D> },
198  std::pair{ std::pair{ "histogram:WeightedProfileHistogram"s, 3 }, &saveRootHisto<3, true, TProfile3D> } };
199 } // namespace
200 
202 
203  class Root : public Service, public Gaudi::Monitoring::Hub::Sink {
204 
205  public:
206  using Service::Service;
207 
208  StatusCode initialize() override {
209  return Service::initialize().andThen( [&] { serviceLocator()->monitoringHub().addSink( this ); } );
210  }
211 
212  StatusCode stop() override {
213  auto ok = Service::stop();
214  if ( !ok ) return ok;
215  std::sort( begin( m_monitoringEntities ), end( m_monitoringEntities ), []( const auto& a, const auto& b ) {
216  return std::tie( a.name, a.component ) > std::tie( b.name, b.component );
217  } );
218  TFile histoFile( m_fileName.value().c_str(), "RECREATE" );
219  std::for_each( begin( m_monitoringEntities ), end( m_monitoringEntities ), [&histoFile]( auto& ent ) {
220  auto j = ent.toJSON();
221  auto dim = j.at( "dimension" ).template get<unsigned int>();
222  auto type = j.at( "type" ).template get<std::string>();
223  // cut type after last ':' if there is one. The rest is precision parameter that we do not need here
224  // as ROOT anyway treats everything as doubles in histograms
225  type = type.substr( 0, type.find_last_of( ':' ) );
226  auto saver = registry.find( { type, dim } );
227  if ( saver == registry.end() )
228  throw GaudiException( "Unknown type : " + type + " dim : " + std::to_string( dim ), "Histogram::Sink::Root",
230  ( *saver->second )( histoFile, ent.component, ent.name, j );
231  } );
232  return ok;
233  }
234 
236  if ( std::string_view( ent.type ).substr( 0, 10 ) == "histogram:" ) {
237  m_monitoringEntities.emplace_back( std::move( ent ) );
238  }
239  }
240 
241  void removeEntity( Monitoring::Hub::Entity const& ent ) override {
242  auto it = std::find( begin( m_monitoringEntities ), end( m_monitoringEntities ), ent );
243  if ( it != m_monitoringEntities.end() ) { m_monitoringEntities.erase( it ); }
244  }
245 
246  private:
248 
249  Gaudi::Property<std::string> m_fileName{ this, "FileName", "testHisto.root",
250  "Name of file where to save histograms" };
251  };
252 
253  DECLARE_COMPONENT( Root )
254 
255 } // namespace Gaudi::Histograming::Sink
Gaudi::Monitoring::Hub::Sink
Interface reporting services must implement.
Definition: MonitoringHub.h:129
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:241
GaudiPython.HistoUtils.nEntries
nEntries
Definition: HistoUtils.py:939
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)
MonitoringHub.h
HistoEx.histo
histo
Definition: HistoEx.py:105
std::pair
gaudirun.s
string s
Definition: gaudirun.py:346
std::vector
STL class.
std::find
T find(T... args)
GaudiException
Definition: GaudiException.h:31
ranges
Definition: FunctionalDetails.h:34
std::tuple
gaudirun.c
c
Definition: gaudirun.py:525
ranges::views
Definition: FunctionalDetails.h:34
jsonFromLHCbLog.json
dictionary json
Definition: jsonFromLHCbLog.py:87
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:84
TimingHistograms.name
name
Definition: TimingHistograms.py:25
StatusCode
Definition: StatusCode.h:65
std::string::at
T at(T... args)
Gaudi::Histograming::Sink::Root::stop
StatusCode stop() override
Definition: RootHistogramSink.cpp:212
Gaudi::Histograming::Sink::Root::initialize
StatusCode initialize() override
Definition: RootHistogramSink.cpp:208
Gaudi::Histograming::Sink
Definition: RootHistogramSink.cpp:201
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:235
IOTest.end
def end
Definition: IOTest.py:128
GaudiPluginService.cpluginsvc.n
n
Definition: cpluginsvc.py:235
Service.h
HistoDumpEx.v
v
Definition: HistoDumpEx.py:27
gaudirun.type
type
Definition: gaudirun.py:160
Service::stop
StatusCode stop() override
Definition: Service.cpp:181
Gaudi::Histograming::Sink::Root
Definition: RootHistogramSink.cpp:203
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
StatusCode::FAILURE
constexpr static const auto FAILURE
Definition: StatusCode.h:101
Gaudi::Monitoring::Hub::Entity
Wrapper class for arbitrary monitoring objects.
Definition: MonitoringHub.h:71
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:1182
Gaudi::Property< std::string >
Gaudi::Histograming::Sink::Root::m_monitoringEntities
std::deque< Gaudi::Monitoring::Hub::Entity > m_monitoringEntities
Definition: RootHistogramSink.cpp:247
Gaudi::Monitoring::Hub::Entity::type
std::string type
type of the entity, see comment above concerning its format and usage
Definition: MonitoringHub.h:90