MarlinMT  0.1.0
RootHistV7ToV6Conversion.h
Go to the documentation of this file.
1 // ROOT7 -> ROOT6 histogram converter (minimal declaration)
2 //
3 // You can use this simplified header to speed up your builds if you only call
4 // into_root_6_hist on RHist<DIMS, T> histograms without custom statistics.
5 
6 #pragma once
7 
8 #include "TH1.h"
9 #include "TH2.h"
10 #include "TH3.h"
11 
12 #include <type_traits>
13 
14 
15 // Forward declarations for ROOT 7 types
16 namespace ROOT { namespace Experimental {
17  template <int DIMS,
18  class PRECISION,
19  template <int D_, class P_> class... STAT>
20  class RHist;
21  //
22  template <int D_, class P_>
24 } }
25 
26 
27 // Evil machinery turning ROOT 7 histograms into ROOT 6 histograms
28 namespace marlinmt
29 {
30  namespace book {
31  // Typing this gets old quickly
32  namespace RExp = ROOT::Experimental;
33 
34  // Trick for static_asserts that only fail when a template is instantiated
35  //
36  // When you write a struct template that must always be specialized, you may
37  // want to print a compiler error when the non-specialized struct is
38  // instantiated, by adding a failing static_assert to it.
39  //
40  // However, you must then prevent the compiler from firing the static_assert
41  // even when the non-specialized version of the template struct is never
42  // instantiated, by making its evaluation "depend on" the template
43  // parameters of the struct. This variable template seems to do the job.
44  //
45  template <typename T> constexpr bool always_false = false;
46 
47 
48  // === TOP-LEVEL ENTRY POINT FOR INTO_ROOT6_HIST ===
49 
50  // ROOT 7 -> ROOT 6 histogram converter
51  //
52  // Must be specialized for every supported ROOT 7 histogram type. Falling
53  // back on the base case means that a histogram conversion is not supported,
54  // and will be reported as such.
55  //
56  // Every specialization will provide a "convert()" static function that
57  // performs the conversion. That function takes the following parameters:
58  //
59  // - The ROOT 7 histogram that must be converted into a ROOT 6 one.
60  // - A ROOT 6 histogram name (used for ROOT I/O, ROOT 7 doesn't have this)
61  //
62  template <typename Input, typename Enable = void>
64  {
65  // Tell the user that we haven't implemented this conversion (yet?)
66  static_assert(always_false<Input>, "Unsupported histogram conversion");
67 
68  // Dummy conversion function to keep compiler errors bounded
69  static auto convert(const Input& src, const char* name);
70  };
71 
72 
73  // === CHECK THAT REQUIRED RHIST STATS ARE PRESENT ===
74 
75  // For a ROOT 7 histogram to be convertible to the ROOT 6 format, it must
76  // collect the RHistStatContent statistic. Let's check for this.
77  template <template <int D_, class P_> class... STAT>
78  constexpr bool stats_ok = true;
79 
80  // If the user declares an RHist with an empty stats list, ROOT silently
81  // adds RHistStatContent. So we must special-case this empty list.
82  template <>
83  constexpr bool stats_ok<> = true;
84 
85  // If there is only one stat in the list, then the assertion will succeed or
86  // fail depending on if this stat is RHistStatContent.
87  template <template <int D_, class P_> class SINGLE_STAT>
88  constexpr bool stats_ok<SINGLE_STAT> = false;
89  template <>
90  constexpr bool stats_ok<RExp::RHistStatContent> = true;
91 
92  // If there are 2+ stats in the list, then we iterate through recursion.
93  // This case won't catch the 1-stat scenario due to above specializations.
94  template <template <int D_, class P_> class STAT_HEAD,
95  template <int D_, class P_> class... STAT_TAIL>
96  constexpr bool stats_ok<STAT_HEAD, STAT_TAIL...> =
97  stats_ok<STAT_HEAD> || stats_ok<STAT_TAIL...>;
98 
99  // We'll also want a nice compiler error message in the failing case
100  template <template <int D_, class P_> class... STAT>
101  struct CheckStats : public std::bool_constant<stats_ok<STAT...>> {
102  static_assert(stats_ok<STAT...>,
103  "Only ROOT 7 histograms that record RHistStatContent "
104  "statistics may be converted into ROOT 6 histograms");
105  };
106 
107  // ...and then we can add a nicer user interface on top of that check
108  template <template <int D_, class P_> class... STAT>
109  static constexpr bool CheckStats_v = CheckStats<STAT...>::value;
110 
111 
112  // === LOOK UP THE ROOT 6 EQUIVALENT OF OUR RHIST (IF ANY) ===
113 
114  // We also need a machinery that, given a ROOT 7 histogram type, can give us
115  // the corresponding ROOT 6 histogram type, if any.
116  //
117  // This must be done via specialization, so let's define the failing case...
118  //
119  template <int DIMENSIONS, class PRECISION>
120  struct CheckRoot6Type : public std::false_type {
121  static_assert(always_false<PRECISION>,
122  "No known ROOT 6 histogram type matches the input "
123  "histogram's dimensionality and precision");
124  };
125 
126  // ...then we can define the successful cases...
127  template <>
128  struct CheckRoot6Type<1, Char_t> : public std::true_type {
129  using type = TH1C;
130  };
131  template <>
132  struct CheckRoot6Type<1, Short_t> : public std::true_type {
133  using type = TH1S;
134  };
135  template <>
136  struct CheckRoot6Type<1, Int_t> : public std::true_type {
137  using type = TH1I;
138  };
139  template <>
140  struct CheckRoot6Type<1, Float_t> : public std::true_type {
141  using type = TH1F;
142  };
143  template <>
144  struct CheckRoot6Type<1, Double_t> : public std::true_type {
145  using type = TH1D;
146  };
147 
148  template <>
149  struct CheckRoot6Type<2, Char_t> : public std::true_type {
150  using type = TH2C;
151  };
152  template <>
153  struct CheckRoot6Type<2, Short_t> : public std::true_type {
154  using type = TH2S;
155  };
156  template <>
157  struct CheckRoot6Type<2, Int_t> : public std::true_type {
158  using type = TH2I;
159  };
160  template <>
161  struct CheckRoot6Type<2, Float_t> : public std::true_type {
162  using type = TH2F;
163  };
164  template <>
165  struct CheckRoot6Type<2, Double_t> : public std::true_type {
166  using type = TH2D;
167  };
168 
169  template <>
170  struct CheckRoot6Type<3, Char_t> : public std::true_type {
171  using type = TH3C;
172  };
173  template <>
174  struct CheckRoot6Type<3, Short_t> : public std::true_type {
175  using type = TH3S;
176  };
177  template <>
178  struct CheckRoot6Type<3, Int_t> : public std::true_type {
179  using type = TH3I;
180  };
181  template <>
182  struct CheckRoot6Type<3, Float_t> : public std::true_type {
183  using type = TH3F;
184  };
185  template <>
186  struct CheckRoot6Type<3, Double_t> : public std::true_type {
187  using type = TH3D;
188  };
189 
190  // ...and then we can add a nice user interface to get the ROOT6 hist type...
191  template <int DIMENSIONS, class PRECISION>
192  using CheckRoot6Type_t =
194 
195  // ...and the truth that a suitable ROOT6 hist type exists.
196  template <int DIMENSIONS, class PRECISION>
197  static constexpr bool CheckRoot6Type_v =
199 
200 
201  // === UNCHECKED HISTOGRAM CONVERTER ===
202 
203  // Convert a ROOT 7 histogram into a ROOT 6 one
204  //
205  // This template does not validate its input type arguments. You'll get
206  // horrible C++ template error messages if you get them wrong. Use the
207  // type-checked into_root6_hist API instead to avoid this.
208  //
209  template <class Output, class Input>
210  Output convert_hist(const Input& src, const char* name);
211 
212  // Explicit instantiations are provided for all basic histogram types
213  extern template TH1C convert_hist(const RExp::RHist<1, Char_t>&, const char*);
214  extern template TH1S convert_hist(const RExp::RHist<1, Short_t>&, const char*);
215  extern template TH1I convert_hist(const RExp::RHist<1, Int_t>&, const char*);
216  extern template TH1F convert_hist(const RExp::RHist<1, Float_t>&, const char*);
217  extern template TH1D convert_hist(const RExp::RHist<1, Double_t>&, const char*);
218  //
219  extern template TH2C convert_hist(const RExp::RHist<2, Char_t>&, const char*);
220  extern template TH2S convert_hist(const RExp::RHist<2, Short_t>&, const char*);
221  extern template TH2I convert_hist(const RExp::RHist<2, Int_t>&, const char*);
222  extern template TH2F convert_hist(const RExp::RHist<2, Float_t>&, const char*);
223  extern template TH2D convert_hist(const RExp::RHist<2, Double_t>&, const char*);
224  //
225  extern template TH3C convert_hist(const RExp::RHist<3, Char_t>&, const char*);
226  extern template TH3S convert_hist(const RExp::RHist<3, Short_t>&, const char*);
227  extern template TH3I convert_hist(const RExp::RHist<3, Int_t>&, const char*);
228  extern template TH3F convert_hist(const RExp::RHist<3, Float_t>&, const char*);
229  extern template TH3D convert_hist(const RExp::RHist<3, Double_t>&, const char*);
230 
231 
232  // === CHECKED HISTOGRAM CONVERTER ===
233 
234  // This specialization of HistConverter uses SFINAE to assert that the input
235  // ROOT7 histogram can be converted into a ROOT6 histogram, then calls the
236  // unchecked convert_hist converter defined above.
237  template <int DIMS,
238  class PRECISION,
239  template <int D_, class P_> class... STAT>
240  struct HistConverter<RExp::RHist<DIMS, PRECISION, STAT...>,
241  std::enable_if_t<CheckRoot6Type_v<DIMS, PRECISION>
242  && CheckStats_v<STAT...>>>
243  {
244  private:
245  using Input = RExp::RHist<DIMS, PRECISION, STAT...>;
247 
248  public:
249  static Output convert(const Input& src, const char* name) {
250  return convert_hist<Output>(src, name);
251  }
252  };
253 
254  // TODO: Support THn someday, if someone asks for it
255  } // end namespace book
256 } // end namespace marlinmt
257 
258 
259 // High-level interface to the above conversion machinery
260 //
261 // "src" is the ROOT 7 histogram to be converted, and "name" is a ROOT 6
262 // histogram name (used for ROOT I/O, should be unique).
263 //
264 template <typename Root7Hist>
265 auto into_root6_hist(const Root7Hist& src, const char* name) {
267 }
268 // ROOT7 -> ROOT6 histogram converter (full header)
269 //
270 // You only need to use this header if you want to instantiate into_root_6_hist
271 // for a ROOT7 histogram with a custom statistics configuration (that is, a
272 // RHist<DIMS, T, STAT...> histogram with a non-empty STAT... list).
273 //
274 // See histConv.hpp.dcl for the basic declarations, which may be all you need.
275 
276 
277 #include "ROOT/RAxis.hxx"
278 #include "ROOT/RHist.hxx"
279 #include "ROOT/RHistImpl.hxx"
280 #include "TAxis.h"
281 
282 #include <cxxabi.h>
283 #include <exception>
284 #include <sstream>
285 #include <string>
286 #include <tuple>
287 #include <typeinfo>
288 #include <utility>
289 
290 
291 namespace marlinmt
292 {
293  namespace book {
294  // === BUILD A THx, FAILING AT RUNTIME IF NO CONSTRUCTOR EXISTS ===
295 
296  // TH1 and TH2 constructors enable building histograms with all supported
297  // axes configurations. TH3, however, only provide constructors for
298  // all-equidistant and all-irregular axis configurations.
299  //
300  // We do not know the axis configuration of a ROOT 7 histogram until runtime,
301  // therefore we must fail at runtime when a ROOT 7 histogram with incompatible
302  // axis configuration is requested.
303  //
304  // So, in the general case, used by TH1 and TH2, we just build a ROOT 6
305  // histogram with the specified constructor parameters...
306  //
307  template <int DIMS>
309  {
310  template <typename Output, typename... BuildParams>
311  static Output make(std::tuple<BuildParams...>&& build_params) {
312  return std::make_from_tuple<Output>(std::move(build_params));
313  }
314  };
315 
316  // ...while in the TH3 case, we detect incompatible axis configurations and
317  // fail at runtime upon encountering them.
318  template <>
319  struct MakeRoot6Hist<3>
320  {
321  // Generally speaking, we fail at runtime...
322  template <typename Output, typename... BuildParams>
323  static Output make(std::tuple<BuildParams...>&& th3_params) {
324  std::ostringstream s;
325  char * args_type_name;
326  int status;
327  args_type_name = abi::__cxa_demangle(typeid(th3_params).name(),
328  0,
329  0,
330  &status);
331  s << "Unsupported TH3 axis configuration"
332  << " (no constructor from argument-tuple " << args_type_name
333  << ')';
334  free(args_type_name);
335  throw std::runtime_error(s.str());
336  }
337 
338  // ...except in the two cases where it actually works!
339  template <typename Output>
340  static Output make(std::tuple<const char*, const char*,
341  Int_t, Double_t, Double_t,
342  Int_t, Double_t, Double_t,
343  Int_t, Double_t, Double_t>&& th3_params) {
344  return std::make_from_tuple<Output>(std::move(th3_params));
345  }
346  template <typename Output>
347  static Output make(std::tuple<const char*, const char*,
348  Int_t, const Double_t*,
349  Int_t, const Double_t*,
350  Int_t, const Double_t*>&& th3_params) {
351  return std::make_from_tuple<Output>(std::move(th3_params));
352  }
353  };
354 
355 
356  // === MISC UTILITIES TO EASE THE ROOT7-ROOT6 IMPEDANCE MISMATCH ===
357 
358  // Convert a ROOT 7 histogram title into a ROOT 6 histogram title
359  std::string convert_hist_title(const std::string& title);
360 
361  // Map from ROOT 7 axis index to ROOT 6 histogram axes
362  TAxis& get_root6_axis(TH1& hist, size_t idx);
363  TAxis& get_root6_axis(TH2& hist, size_t idx);
364  TAxis& get_root6_axis(TH3& hist, size_t idx);
365 
366  // ROOT 7-like GetBinFrom for ROOT 6
367  Double_t get_bin_from_root6(const TAxis& axis, Int_t bin);
368  std::array<Double_t, 1> get_bin_from_root6(const TH1& hist, Int_t bin);
369  std::array<Double_t, 2> get_bin_from_root6(const TH2& hist, Int_t bin);
370  std::array<Double_t, 3> get_bin_from_root6(const TH3& hist, Int_t bin);
371 
372  // Transfer histogram axis settings which are common to all axis
373  // configurations (currently equidistant, growable, irregular and labels)
374  void setup_axis_base(TAxis& dest, const RExp::RAxisBase& src);
375 
376 
377  // === MAIN CONVERSION FUNCTIONS ===
378 
379  // Shorthand for an excessively long name
380  template <int DIMS>
381  using RHistImplBase = RExp::Detail::RHistImplPrecisionAgnosticBase<DIMS>;
382 
383  // Create a ROOT 6 histogram whose global and per-axis configuration matches
384  // that of an input ROOT 7 histogram as closely as possible.
385  template <class Output, int AXIS, int DIMS, class... BuildParams>
387  std::tuple<BuildParams...>&& build_params) {
388  // This function is actually a kind of recursive loop for AXIS ranging
389  // from 0 to the dimension of the histogram, inclusive.
390  if constexpr (AXIS < DIMS) {
391  // The first iterations query the input histogram axes one by one
392  const auto axis_view = src_impl.GetAxis(AXIS);
393 
394  // Is this an equidistant axis?
395  const auto* eq_axis_ptr = axis_view.GetAsEquidistant();
396  if (eq_axis_ptr != nullptr) {
397  const auto& eq_axis = *eq_axis_ptr;
398 
399  // Append equidistant axis constructor parameters to the list of
400  // ROOT 6 histogram constructor parameters
401  auto new_build_params =
402  std::tuple_cat(
403  std::move(build_params),
404  std::make_tuple((Int_t)(eq_axis.GetNBinsNoOver()),
405  (Double_t)(eq_axis.GetMinimum()),
406  (Double_t)(eq_axis.GetMaximum()))
407  );
408 
409  // Process other axes and construct the histogram
410  auto dest =
412  AXIS+1>(src_impl,
413  std::move(new_build_params));
414 
415  // Propagate basic axis properties
416  setup_axis_base(get_root6_axis(dest, AXIS), eq_axis);
417 
418  // If the axis is labeled, propagate labels
419  //
420  // FIXME: There does not seem to be a way to go from RAxisView to axis
421  // labels at the moment. Even dynamic_cast will fail because
422  // RAxisXyz do not contain a single virtual method, and therefore
423  // do not have the required infrastructure for dcasting.
424  //
425  /* const auto* lbl_axis_ptr =
426  dynamic_cast<const RExp::RAxisLabels*>(&eq_axis);
427  if (lbl_axis_ptr) {
428  auto labels = lbl_axis_ptr->GetBinLabels();
429  for (size_t bin = 0; bin < labels.size(); ++bin) {
430  std::string label{labels[bin]};
431  dest.SetBinLabel(bin, label.c_str());
432  }
433  } */
434 
435  // Send back the histogram to caller
436  return dest;
437  }
438 
439  // Is this an irregular axis?
440  const auto* irr_axis_ptr = axis_view.GetAsIrregular();
441  if (irr_axis_ptr != nullptr) {
442  const auto& irr_axis = *irr_axis_ptr;
443 
444  // Append irregular axis constructor parameters to the list of
445  // ROOT 6 histogram constructor parameters
446  auto new_build_params =
447  std::tuple_cat(
448  std::move(build_params),
449  std::make_tuple(
450  (Int_t)(irr_axis.GetNBinsNoOver()),
451  (const Double_t*)(irr_axis.GetBinBorders().data())
452  )
453  );
454 
455  // Process other axes and construct the histogram
456  auto dest =
458  AXIS+1>(src_impl,
459  std::move(new_build_params));
460 
461  // Propagate basic axis properties
462  // There aren't any other properties for irregular axes.
463  setup_axis_base(get_root6_axis(dest, AXIS), irr_axis);
464 
465  // Send back the histogram to caller
466  return dest;
467  }
468 
469  // As of ROOT 6.18.0, there should be no other axis kind, so
470  // reaching this point indicates a bug in the code.
471  throw std::runtime_error("Unsupported histogram axis type");
472  } else if constexpr (AXIS == DIMS) {
473  // We've reached the bottom of the histogram construction recursion.
474  // All histogram constructor parameters have been collected in the
475  // build_params tuple, so we can now construct the ROOT 6 histogram.
476  return MakeRoot6Hist<DIMS>::template make<Output>(
477  std::move(build_params)
478  );
479  } else {
480  // The loop shouldn't reach this point, there's a bug in the code
481  static_assert(always_false<Output>,
482  "Invalid loop iteration in build_hist_loop");
483  }
484  }
485 
486 
487  // Check that the input and output histogram use the same binning
488  // conventions (starting index, N-d array serialization order) since we
489  // currently rely on that assumption for fast histogram bin data transfer.
490  template <class THx, int DIMS>
491  void check_binning(const THx& dest, const RHistImplBase<DIMS>& src_impl)
492  {
493  // Check that bins from ROOT 7 are "close enough" to those from ROOT 7
494  auto bins_similar = [](auto src_bins, auto dest_bins) -> bool {
495  static constexpr double TOLERANCE = 1e-6;
496  if (src_bins.size() != dest_bins.size()) return false;
497  for (size_t i = 0; i < src_bins.size(); ++i) {
498  double diff = std::abs(src_bins[i] - dest_bins[i]);
499  if (diff >= TOLERANCE * std::abs(src_bins[i])) { return false; }
500  }
501  return true;
502  };
503 
504  // Display a bunch of bins into a stringstream
505  auto print_bins = [](std::ostringstream& s, auto local_bin_indices) {
506  s << "{ ";
507  for (size_t i = 0; i < local_bin_indices.size()-1; ++i) {
508  s << local_bin_indices[i] << ", ";
509  }
510  s << local_bin_indices[local_bin_indices.size()-1] << " }";
511  };
512 
513  // If any of these test fails, ROOT 7 binning went out of sync with
514  // ROOT 6 binning, and thusly broke our simple data transfer strategy :(
515  if (!bins_similar(src_impl.GetBinFrom(0), get_bin_from_root6(dest, 0))) {
516  std::ostringstream s;
517  s << "Binning origin doesn't match"
518  << " (source histogram's first bin is at ";
519  print_bins(s, src_impl.GetBinFrom(0));
520  s << ", target histogram's first bin is at ";
521  print_bins(s, get_bin_from_root6(dest, 0));
522  s << ')';
523  throw std::runtime_error(s.str());
524  }
525  if ((src_impl.GetNBins() >=2)
526  && (!bins_similar(src_impl.GetBinFrom(1), get_bin_from_root6(dest, 1)))) {
527  std::ostringstream s;
528  s << "Binning order doesn't match"
529  << " (source histogram's second bin is at ";
530  print_bins(s, src_impl.GetBinFrom(1));
531  s << ", target histogram's second bin is at ";
532  print_bins(s, get_bin_from_root6(dest, 1));
533  s << ')';
534  throw std::runtime_error(s.str());
535  }
536  if (src_impl.GetNBins() != dest.GetNcells()) {
537  std::ostringstream s;
538  s << "Bin count doesn't match"
539  << " (source histogram has " << src_impl.GetNBins() << " bins"
540  << ", target histogram has " << dest.GetNcells() << " bins)";
541  throw std::runtime_error(s.str());
542  }
543  }
544 
545 
546  // Convert a ROOT 7 histogram into a ROOT 6 one
547  template <class Output, class Input>
548  Output convert_hist(const Input& src, const char* name) {
549  // Make sure that the input histogram's impl-pointer is set
550  const auto* impl_ptr = src.GetImpl();
551  if (impl_ptr == nullptr) {
552  throw std::runtime_error("Input histogram has a null impl pointer");
553  }
554  const auto& impl = *impl_ptr;
555 
556  // Compute the first ROOT 6 histogram constructor parameters
557  //
558  // Beware that "title" must remain a separate variable, otherwise
559  // the title string will be deallocated before use...
560  //
561  auto title = convert_hist_title(impl.GetTitle());
562  auto first_build_params = std::make_tuple(name, title.c_str());
563 
564  // Build the ROOT 6 histogram, copying src's axis configuration
565  auto dest = convert_hist_loop<Output, 0>(impl,
566  std::move(first_build_params));
567 
568  // Make sure that under- and overflow bins are included in the
569  // statistics, to match the ROOT 7 behavior (as of ROOT v6.18.0).
570  dest.SetStatOverflows(TH1::EStatOverflows::kConsider);
571 
572  // Use normal statistics for bin errors, since ROOT7 doesn't seem to support
573  // Poisson bin error computation yet.
574  dest.SetBinErrorOption(TH1::EBinErrorOpt::kNormal);
575 
576  // Set norm factor to zero (disable), since ROOT 7 doesn't seem to have this
577  dest.SetNormFactor(0);
578 
579  // Now we're ready to transfer histogram data. First of all, let's
580  // assert that ROOT 6 and ROOT 7 use the same binning convention. This
581  // seems true as of ROOT 6.18.0, but may change in the future...
582  check_binning(dest, *src.GetImpl());
583 
584  // Propagate bin uncertainties, if present.
585  //
586  // This must be done before inserting any other data in the TH1,
587  // otherwise Sumw2() will perform undesirable black magic...
588  //
589  const auto& stat = src.GetImpl()->GetStat();
590  if (stat.HasBinUncertainty()) {
591  dest.Sumw2();
592  auto& sumw2 = *dest.GetSumw2();
593  for (size_t bin = 0; bin < stat.size(); ++bin) {
594  sumw2[bin] = stat.GetBinUncertainty(bin);
595  }
596  }
597 
598  // Propagate basic histogram statistics
599  dest.SetEntries(stat.GetEntries());
600  for (size_t bin = 0; bin < stat.size(); ++bin) {
601  dest.AddBinContent(bin, stat.GetBinContent(bin));
602  }
603 
604  // Compute remaining statistics
605  //
606  // FIXME: If the input RHist computes all of...
607  // - fTsumw (total sum of weights)
608  // - fTsumw2 (total sum of square of weights)
609  // - fTsumwx (total sum of weight*x)
610  // - fTsumwx2 (total sum of weight*x*x)
611  //
612  // ...then we should propagate those statistics to the TH1. The
613  // same applies for the higher-order statistics computed by TH2+.
614  //
615  // But as of ROOT 6.18.0, we can never do this, because the
616  // RHistDataMomentUncert stats associated with fTsumwx and
617  // fTsumwx2 do not expose their contents publicly.
618  //
619  // Therefore, we must always ask TH1 to do the computation
620  // for us. It's better to do so using a GetStats/PutStats
621  // pair, as ResetStats alters more than those stats...
622  //
623  // The same problem occurs with the higher-order statistics
624  // computed by TH2+, but this approach is dimension-agnostic.
625  //
626  std::array<Double_t, TH1::kNstat> stats;
627  dest.GetStats(stats.data());
628  dest.PutStats(stats.data());
629 
630  // Return the ROOT 6 histogram to the caller
631  return dest;
632  }
633  } // end namespace book
634 } // end namespace marlinmt
std::string convert_hist_title(const std::string &title)
constexpr bool always_false
static constexpr bool CheckStats_v
void check_binning(const THx &dest, const RHistImplBase< DIMS > &src_impl)
static Output make(std::tuple< const char *, const char *, Int_t, Double_t, Double_t, Int_t, Double_t, Double_t, Int_t, Double_t, Double_t > &&th3_params)
constexpr unsigned long long value(const Flag_t &flag)
Definition: Flags.h:106
auto into_root6_hist(const Root7Hist &src, const char *name)
static constexpr bool CheckRoot6Type_v
template TH3D convert_hist(const RExp::RHist< 3, Double_t > &, const char *)
static auto convert(const Input &src, const char *name)
typename CheckRoot6Type< DIMENSIONS, PRECISION >::type CheckRoot6Type_t
std::array< Double_t, 3 > get_bin_from_root6(const TH3 &hist, Int_t bin)
Output convert_hist_loop(const RHistImplBase< DIMS > &src_impl, std::tuple< BuildParams... > &&build_params)
void setup_axis_base(TAxis &dest, const RExp::RAxisBase &src)
static Output make(std::tuple< BuildParams... > &&th3_params)
constexpr bool stats_ok< SINGLE_STAT >
TAxis & get_root6_axis(TH3 &hist, size_t idx)
static Output make(std::tuple< const char *, const char *, Int_t, const Double_t *, Int_t, const Double_t *, Int_t, const Double_t *> &&th3_params)
static Output make(std::tuple< BuildParams... > &&build_params)
RExp::Detail::RHistImplPrecisionAgnosticBase< DIMS > RHistImplBase