The Gaudi Framework  v37r0 (b608885e)
Utils.h
Go to the documentation of this file.
1 /***********************************************************************************\
2 * (c) Copyright 1998-2023 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 #pragma once
12 
14 #include <TDirectory.h>
15 #include <TFile.h>
16 #include <TH1.h>
17 #include <TH1D.h>
18 #include <TH2D.h>
19 #include <TH3D.h>
20 #include <TProfile.h>
21 #include <TProfile2D.h>
22 #include <algorithm>
23 #include <functional>
24 #include <gsl/span>
25 #include <nlohmann/json.hpp>
26 #include <range/v3/numeric/accumulate.hpp>
27 #include <range/v3/range/conversion.hpp>
28 #include <range/v3/view/split_when.hpp>
29 #include <range/v3/view/transform.hpp>
30 #include <string>
31 #include <vector>
32 
33 // upstream has renamed namespace ranges::view -> ranges::views
34 #if RANGE_V3_VERSION < 900
35 namespace ranges::views {
36  using namespace ranges::view;
37 }
38 #endif
39 
40 namespace Gaudi::Histograming::Sink {
41 
54  template <bool isProfile, typename RootHisto, unsigned int N>
55  struct Traits;
56 
63  template <typename Traits>
65  nlohmann::json const& j );
66 
72  template <typename Histo>
74 
78  template <typename Traits>
79  void saveRootHisto( TFile& file, std::string dir, std::string name, nlohmann::json const& j );
80 
87  template <unsigned int N, bool isProfile, typename ROOTHisto>
88  void saveRootHisto( TFile& file, std::string dir, std::string name, nlohmann::json const& j );
89 
90  namespace details {
91 
93  struct Axis {
94  unsigned int nBins;
95  double minValue;
96  double maxValue;
98  };
99 
101  inline Axis jsonToAxis( nlohmann::json& jAxis ) {
102  return { jAxis.at( "nBins" ).get<unsigned int>(), jAxis.at( "minValue" ).get<double>(),
103  jAxis.at( "maxValue" ).get<double>(),
104  ";" + jAxis.at( "title" ).get<std::string>() }; // ";" to prepare concatenations of titles
105  }
106 
110  template <typename Traits, std::size_t... index>
112  nlohmann::json const& j,
113  std::index_sequence<index...> ) {
114  // extract data from json
115  auto jsonAxis = j.at( "axis" );
116  auto axis = std::array{ jsonToAxis( jsonAxis[index] )... };
117  auto weights = j.at( "bins" ).get<std::vector<typename Traits::WeightType>>();
118  auto title = j.at( "title" ).get<std::string>();
119  auto nentries = j.at( "nEntries" ).get<unsigned int>();
120  // weird way ROOT has to give titles to axis
121  title += ( axis[index].title + ... );
122  // compute total number of bins, multiplying bins per axis
123  auto totNBins = ( ( axis[index].nBins + 2 ) * ... );
124  assert( weights.size() == totNBins );
125 
126  if ( name[0] == '/' ) {
127  dir = "";
128  name = name.substr( 1 );
129  }
130 
131  // take into account the case where name contains '/'s (e.g. "Group/Name") by
132  // moving the prefix into dir
133  if ( auto pos = name.rfind( '/' ); pos != std::string::npos ) {
134  dir += '/' + name.substr( 0, pos );
135  name = name.substr( pos + 1 );
136  }
137 
138  // Create Root histogram calling constructors with the args tuple
139  auto histo = Traits::create( name, title, axis[index]... );
140 
141  // fill Histo
142  for ( unsigned int i = 0; i < totNBins; i++ ) Traits::fill( histo, i, weights[i] );
143  // fill histo metadata, e.g. bins and number of entries
144  Traits::fillMetaData( histo, jsonAxis, nentries );
145 
146  return { histo, dir };
147  }
148 
154  template <typename RootHisto, unsigned int N>
155  struct TraitsBase {
156  static constexpr unsigned int Dimension{ N };
157  // hleper method for the creation of the Root histogram, using an index_sequence for handling the axis
158  template <typename AxisArray, std::size_t... index>
159  static RootHisto create( std::string& name, std::string& title, AxisArray axis, std::index_sequence<index...> ) {
160  return std::make_from_tuple<RootHisto>(
161  std::tuple_cat( std::tuple{ name.c_str(), title.c_str() },
162  std::tuple{ std::get<index>( axis ).nBins, std::get<index>( axis ).minValue,
163  std::get<index>( axis ).maxValue }... ) );
164  }
165  // Generic creation of the Root histogram from name, title and a set of axis
166  template <typename... Axis>
167  static RootHisto create( std::string& name, std::string& title, Axis&... axis ) {
168  return create( name, title, std::make_tuple( axis... ), std::make_index_sequence<sizeof...( Axis )>() );
169  }
170  // Generic fill of the metadata of the Root histogram from json data
171  static void fillMetaData( RootHisto& histo, nlohmann::json const& jsonAxis, unsigned int nentries ) {
172  auto try_set_bin_labels = [&histo, &jsonAxis]( auto idx ) {
173  if ( jsonAxis[idx].contains( "labels" ) ) {
174  TAxis* axis = nullptr;
175  switch ( idx ) {
176  case 0:
177  axis = histo.GetXaxis();
178  break;
179  case 1:
180  axis = histo.GetYaxis();
181  break;
182  case 2:
183  axis = histo.GetZaxis();
184  break;
185  default:
186  break;
187  }
188  if ( axis ) {
189  const auto labels = jsonAxis[idx].at( "labels" );
190  for ( unsigned int i = 0; i < labels.size(); i++ ) {
191  axis->SetBinLabel( i + 1, labels[i].template get<std::string>().c_str() );
192  }
193  }
194  }
195  };
196  for ( unsigned int i = 0; i < jsonAxis.size(); i++ ) try_set_bin_labels( i );
197  // Fix the number of entries, wrongly computed by ROOT from the numer of calls to fill
198  histo.SetEntries( nentries );
199  }
200  };
201 
203  template <typename TP>
204  struct ProfileWrapper : TP {
205  template <typename... Args>
206  ProfileWrapper( Args&&... args ) : TP( std::forward<Args>( args )... ) {
207  // When the static function TH1::SetDefaultSumw2 had been called (passing true), `Sumw2(true)` is automatically
208  // called by the constructor of TProfile. This is a problem because in the profiles in Gaudi::Accumulators we do
209  // not keep track of the sum of squares of weights and consequently don't set fBinSumw2 of the TProfile, which
210  // in turn leads to a self-inconsistent TProfile object. The "fix" is to disable sum of squares explicitly.
211  this->Sumw2( false );
212  }
213  ProfileWrapper( ProfileWrapper const& ) = delete;
215  ProfileWrapper( ProfileWrapper const&& ) = delete;
217  void setBinNEntries( Int_t i, Int_t n ) { this->fBinEntries.fArray[i] = n; }
218  void setBinW2( Int_t i, Double_t v ) { this->fSumw2.fArray[i] = v; }
219  };
220 
225  inline TDirectory* changeDir( TFile& file, std::string dir ) {
226  // remember the current directory
227  auto previousDir = gDirectory;
228  // find or create the directory for the histogram
229  using namespace ranges;
230  auto is_delimiter = []( auto c ) { return c == '/' || c == '.'; };
231  auto transform_to_string = views::transform( []( auto&& rng ) { return rng | to<std::string>; } );
232  auto currentDir = accumulate( dir | views::split_when( is_delimiter ) | transform_to_string,
233  file.GetDirectory( "" ), []( auto current, auto&& dir_level ) {
234  if ( current ) {
235  // try to get next level
236  auto nextDir = current->GetDirectory( dir_level.c_str() );
237  // if it does not exist, create it
238  if ( !nextDir ) nextDir = current->mkdir( dir_level.c_str() );
239  // move to next level
240  current = nextDir;
241  }
242  return current;
243  } );
244  if ( !currentDir )
245  throw GaudiException( "Could not create directory " + dir, "Histogram::Sink::Root", StatusCode::FAILURE );
246  // switch to the directory
247  currentDir->cd();
248  return previousDir;
249  }
250 
251  template <typename Histo>
252  nlohmann::json allAxisTojson( Histo const& h ) {
253  if constexpr ( std::is_base_of_v<TH3D, Histo> ) {
254  return { *h.GetXaxis(), *h.GetYaxis(), *h.GetZaxis() };
255  } else if constexpr ( std::is_base_of_v<TProfile2D, Histo> || std::is_base_of_v<TH2D, Histo> ) {
256  return { *h.GetXaxis(), *h.GetYaxis() };
257  } else {
258  return { *h.GetXaxis() };
259  }
260  }
261 
262  template <typename Histo>
263  nlohmann::json binsTojson( Histo const& h ) {
264  if constexpr ( std::is_base_of_v<TProfile, Histo> || std::is_base_of_v<TProfile2D, Histo> ) {
266  // ROOT TProfile interface being completely inconsistent, we have to play
267  // with different ways to access values of the histogram... one per value !
268  auto* sums = h.GetArray();
269  auto* sums2 = h.GetSumw2();
270  for ( unsigned long n = 0; n < (unsigned long)h.GetSize(); n++ ) {
271  j.push_back( nlohmann::json{ { h.GetBinEntries( n ), sums[n] }, sums2->At( n ) } );
272  }
273  return j;
274  } else {
275  return gsl::span{ h.GetArray(), (unsigned long)h.GetSize() };
276  }
277  }
278 
280  template <typename Histo>
282  std::string type = std::is_base_of_v<TProfile, Histo> || std::is_base_of_v<TProfile2D, Histo>
283  ? "histogram:ProfileHistogram:double"
284  : "histogram:Histogram:double";
285  return nlohmann::json{ { "type", type },
286  { "title", h.GetTitle() },
287  { "dimension", h.GetDimension() },
288  { "empty", (int)h.GetEntries() == 0 },
289  { "nEntries", (int)h.GetEntries() },
290  { "axis", allAxisTojson( h ) },
291  { "bins", binsTojson( h ) } };
292  }
293 
294  } // namespace details
295 
299  template <typename RootHisto, unsigned int N>
300  struct Traits<false, RootHisto, N> : details::TraitsBase<RootHisto, N> {
301  using Histo = RootHisto;
302  using WeightType = double;
303  static void fill( Histo& histo, unsigned int i, const WeightType& weight ) { histo.SetBinContent( i, weight ); }
304  };
305 
309  template <typename RootHisto, unsigned int N>
310  struct Traits<true, RootHisto, N> : details::TraitsBase<details::ProfileWrapper<RootHisto>, N> {
313  static constexpr void fill( Histo& histo, unsigned int i, const WeightType& weight ) {
314  auto [c, sumWeight2] = weight;
315  auto [nEntries, sumWeight] = c;
316  histo.setBinNEntries( i, nEntries );
317  histo.SetBinContent( i, sumWeight );
318  histo.setBinW2( i, sumWeight2 );
319  };
320  };
321 
322  template <typename Traits>
324  nlohmann::json const& j ) {
325  return details::jsonToRootHistogramInternal<Traits>( dir, name, j, std::make_index_sequence<Traits::Dimension>() );
326  }
327 
328  template <typename Traits>
329  void saveRootHisto( TFile& file, std::string dir, std::string name, nlohmann::json const& j ) {
330  // get the Root histogram
331  auto [histo, newDir] = jsonToRootHistogram<Traits>( dir, name, j );
332  // Change to the proper directory in the ROOT file
333  auto previousDir = details::changeDir( file, newDir );
334  // write to file
335  histo.Write();
336  // switch back to the previous directory
337  previousDir->cd();
338  }
339 
340  template <unsigned int N, bool isProfile, typename ROOTHisto>
341  void saveRootHisto( TFile& file, std::string dir, std::string name, nlohmann::json const& j ) {
342  saveRootHisto<Traits<isProfile, ROOTHisto, N>>( file, std::move( dir ), std::move( name ), j );
343  }
344 
345 } // namespace Gaudi::Histograming::Sink
346 
347 namespace nlohmann {
349  inline void to_json( nlohmann::json& j, TAxis const& axis ) {
350  j = nlohmann::json{ { "nBins", axis.GetNbins() },
351  { "minValue", axis.GetXmin() },
352  { "maxValue", axis.GetXmax() },
353  { "title", axis.GetTitle() } };
354  }
356  inline void to_json( nlohmann::json& j, TH1D const& h ) {
358  }
359  inline void to_json( nlohmann::json& j, TH2D const& h ) {
361  }
362  inline void to_json( nlohmann::json& j, TH3D const& h ) {
364  }
365  inline void to_json( nlohmann::json& j, TProfile const& h ) {
367  }
368  inline void to_json( nlohmann::json& j, TProfile2D const& h ) {
370  }
371 
372 } // namespace nlohmann
Gaudi::Histograming::Sink::details::TraitsBase::create
static RootHisto create(std::string &name, std::string &title, AxisArray axis, std::index_sequence< index... >)
Definition: Utils.h:159
std::make_tuple
T make_tuple(T... args)
Gaudi::Histograming::Sink::Traits< false, RootHisto, N >::fill
static void fill(Histo &histo, unsigned int i, const WeightType &weight)
Definition: Utils.h:303
std::string
STL class.
IOTest.N
N
Definition: IOTest.py:110
Gaudi::Histograming::Sink::Traits
templated Traits dealing with Root Histogram filling for standard histograms
Definition: Utils.h:55
std::move
T move(T... args)
bug_34121.name
name
Definition: bug_34121.py:20
GaudiException.h
Properties.long
long
(c) Copyright 1998-2020 CERN for the benefit of the LHCb and ATLAS collaborations # # This software i...
Definition: Properties.py:15
std::vector
STL class.
Gaudi::Histograming::Sink::details::Axis::minValue
double minValue
Definition: Utils.h:95
jsonFromLHCbLog.json
json
Definition: jsonFromLHCbLog.py:87
GaudiException
Definition: GaudiException.h:31
Gaudi::Histograming::Sink::details::ProfileWrapper::setBinNEntries
void setBinNEntries(Int_t i, Int_t n)
Definition: Utils.h:217
nlohmann
Definition: Accumulators.h:223
ranges
Definition: details.h:30
Gaudi::Histograming::Sink::details::jsonToRootHistogramInternal
std::tuple< typename Traits::Histo, std::string > jsonToRootHistogramInternal(std::string &dir, std::string &name, nlohmann::json const &j, std::index_sequence< index... >)
generic function to convert json to a ROOT Histogram - internal implementation
Definition: Utils.h:111
Gaudi::Histograming::Sink::jsonToRootHistogram
std::tuple< typename Traits::Histo, std::string > jsonToRootHistogram(std::string &dir, std::string &name, nlohmann::json const &j)
generic function to convert json to a ROOT Histogram
Definition: Utils.h:323
std::tuple
gaudirun.c
c
Definition: gaudirun.py:527
ranges::views
Definition: details.h:30
Gaudi::Histograming::Sink::details::Axis::title
std::string title
Definition: Utils.h:97
Gaudi::Histograming::Sink::details::ProfileWrapper::ProfileWrapper
ProfileWrapper(ProfileWrapper const &)=delete
Gaudi::Histograming::Sink::details::ProfileWrapper::ProfileWrapper
ProfileWrapper(ProfileWrapper const &&)=delete
Gaudi::Histograming::Sink::details::changeDir
TDirectory * changeDir(TFile &file, std::string dir)
changes to the ROOT directory given in the current ROOT file and returns the current directory before...
Definition: Utils.h:225
Gaudi::Histograming::Sink::Traits< true, RootHisto, N >::fill
static constexpr void fill(Histo &histo, unsigned int i, const WeightType &weight)
Definition: Utils.h:313
Gaudi::Histograming::Sink::details::rootHistogramToJson
nlohmann::json rootHistogramToJson(Histo const &h)
automatic translation of Root Histograms to json
Definition: Utils.h:281
Gaudi::Histograming::Sink::saveRootHisto
void saveRootHisto(TFile &file, std::string dir, std::string name, nlohmann::json const &j)
generic method to save regular histograms to files
Definition: Utils.h:341
Gaudi::Histograming::Sink::details::ProfileWrapper::operator=
ProfileWrapper & operator=(ProfileWrapper const &&)=delete
details
Definition: AnyDataWrapper.h:18
Gaudi::Histograming::Sink::details::binsTojson
nlohmann::json binsTojson(Histo const &h)
Definition: Utils.h:263
ProduceConsume.j
j
Definition: ProduceConsume.py:101
Gaudi::Histograming::Sink
Definition: RootHistogramSink.cpp:22
std::string::c_str
T c_str(T... args)
AlgSequencer.h
h
Definition: AlgSequencer.py:32
Gaudi::Histograming::Sink::details::ProfileWrapper::ProfileWrapper
ProfileWrapper(Args &&... args)
Definition: Utils.h:206
std::array
STL class.
GaudiAlg.HistoUtils.nEntries
nEntries
Definition: HistoUtils.py:939
to_json
void to_json(nlohmann::json &j, StatEntity const &s)
Definition: StatEntity.cpp:14
Gaudi::Histograming::Sink::details::TraitsBase
Common base for Traits dealing with Histogram conversions to Root Provides generic implementation for...
Definition: Utils.h:155
Gaudi::Histograming::Sink::details::allAxisTojson
nlohmann::json allAxisTojson(Histo const &h)
Definition: Utils.h:252
GaudiPluginService.cpluginsvc.n
n
Definition: cpluginsvc.py:235
Gaudi::Histograming::Sink::details::Axis
Small helper struct representing the Axis of an Histogram.
Definition: Utils.h:93
Gaudi::Histograming::Sink::details::ProfileWrapper::operator=
ProfileWrapper & operator=(ProfileWrapper const &)=delete
Gaudi::Histograming::Sink::details::jsonToAxis
Axis jsonToAxis(nlohmann::json &jAxis)
extract an Axis from json data
Definition: Utils.h:101
gaudirun.type
type
Definition: gaudirun.py:162
Gaudi::Histograming::Sink::details::Axis::nBins
unsigned int nBins
Definition: Utils.h:94
Gaudi::Histograming::Sink::details::ProfileWrapper
helper Wrapper around TProfileX to be able to fill it
Definition: Utils.h:204
gaudirun.args
args
Definition: gaudirun.py:338
std
STL namespace.
Properties.v
v
Definition: Properties.py:123
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::Histograming::Sink::details::TraitsBase::fillMetaData
static void fillMetaData(RootHisto &histo, nlohmann::json const &jsonAxis, unsigned int nentries)
Definition: Utils.h:171
Gaudi::Histograming::Sink::details::ProfileWrapper::setBinW2
void setBinW2(Int_t i, Double_t v)
Definition: Utils.h:218
Gaudi::Histograming::Sink::Traits< false, RootHisto, N >::WeightType
double WeightType
Definition: Utils.h:302
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:1202
Gaudi::Histograming::Sink::details::Axis::maxValue
double maxValue
Definition: Utils.h:96
Gaudi::Histograming::Sink::rootHistogramTojson
nlohmann::json rootHistogramTojson(Histo const &)
generic function to convert a ROOT Histogram to json
Gaudi::Histograming::Sink::Traits< false, RootHisto, N >::Histo
RootHisto Histo
Definition: Utils.h:301
Gaudi::Histograming::Sink::details::TraitsBase::create
static RootHisto create(std::string &name, std::string &title, Axis &... axis)
Definition: Utils.h:167