From 575be4fe03aa3a19007e6c24b91e2356d759b158 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Thu, 9 Apr 2026 16:20:43 +0200 Subject: [PATCH 1/6] [hist] Rewrite test of ConvertToTH1I with SetBinContent This provides more stringent checks of the bin contents for integer types. For floating-point bin contents, the tests fill with weights, which results in different values for each bin already. --- hist/histv7util/test/hist_convert_TH1.cxx | 26 ++++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/hist/histv7util/test/hist_convert_TH1.cxx b/hist/histv7util/test/hist_convert_TH1.cxx index fc4d87bf4e4f7..1d283eb4f689a 100644 --- a/hist/histv7util/test/hist_convert_TH1.cxx +++ b/hist/histv7util/test/hist_convert_TH1.cxx @@ -12,14 +12,11 @@ TEST(ConvertToTH1I, RHistEngine) static constexpr std::size_t Bins = 20; RHistEngine engine(Bins, {0, Bins}); - engine.Fill(-100); + engine.SetBinContent(RBinIndex::Underflow(), 100); for (std::size_t i = 0; i < Bins; i++) { - engine.Fill(i); + engine.SetBinContent(i, i + 1); } - engine.Fill(100); - - // Fill bin 7 twice to test against accidental shifts. - engine.Fill(7); + engine.SetBinContent(RBinIndex::Overflow(), 200); auto th1i = ConvertToTH1I(engine); ASSERT_TRUE(th1i); @@ -29,14 +26,11 @@ TEST(ConvertToTH1I, RHistEngine) ASSERT_EQ(th1i->GetNbinsY(), 1); ASSERT_EQ(th1i->GetNbinsZ(), 1); - for (std::size_t i = 0; i < Bins + 2; i++) { - // Bin 7 was filled twice. - if (i == 7 + 1) { - EXPECT_EQ(th1i->GetBinContent(i), 2); - } else { - EXPECT_EQ(th1i->GetBinContent(i), 1); - } + EXPECT_EQ(th1i->GetBinContent(0), 100); + for (std::size_t i = 1; i <= Bins; i++) { + EXPECT_EQ(th1i->GetBinContent(i), i); } + EXPECT_EQ(th1i->GetBinContent(Bins + 1), 200); EXPECT_EQ(th1i->GetEntries(), 0); Double_t stats[4]; @@ -53,10 +47,12 @@ TEST(ConvertToTH1I, RHistEngineNoFlowBins) const RRegularAxis axis(Bins, {0, Bins}, /*enableFlowBins=*/false); RHistEngine engine({axis}); + // Flow bins are disabled, so this fill will be discarded. engine.Fill(-100); for (std::size_t i = 0; i < Bins; i++) { - engine.Fill(i); + engine.SetBinContent(i, i + 1); } + // Flow bins are disabled, so this fill will be discarded. engine.Fill(100); auto th1i = ConvertToTH1I(engine); @@ -64,7 +60,7 @@ TEST(ConvertToTH1I, RHistEngineNoFlowBins) EXPECT_EQ(th1i->GetBinContent(0), 0); for (std::size_t i = 1; i <= Bins; i++) { - EXPECT_EQ(th1i->GetBinContent(i), 1); + EXPECT_EQ(th1i->GetBinContent(i), i); } EXPECT_EQ(th1i->GetBinContent(Bins + 1), 0); } From 2ea678d29d4c4f83a963b94bf7f7ae3ff657f431 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Thu, 9 Apr 2026 16:23:08 +0200 Subject: [PATCH 2/6] [hist] Fix typo in test comment --- hist/histv7util/test/hist_convert_TH1.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hist/histv7util/test/hist_convert_TH1.cxx b/hist/histv7util/test/hist_convert_TH1.cxx index 1d283eb4f689a..e32dc23958764 100644 --- a/hist/histv7util/test/hist_convert_TH1.cxx +++ b/hist/histv7util/test/hist_convert_TH1.cxx @@ -172,7 +172,7 @@ TEST(ConvertToTH1L, RHistEngine) ASSERT_TRUE(th1l); // Get the value via TArrayL::At and store into a variable to be sure about the type. During direct comparison, a - // double return value may automatically promate Large to a double as well, introducing the truncation we want to + // double return value may automatically promote Large to a double as well, introducing the truncation we want to // test against. const long long value = th1l->At(2); EXPECT_EQ(value, Large); From 619d018650de3705cabf77ab61dbd045ecc9a84a Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Thu, 9 Apr 2026 16:45:12 +0200 Subject: [PATCH 3/6] [hist] Move variable in ConvertToTH1Impl --- hist/histv7util/src/ConvertToTH1.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hist/histv7util/src/ConvertToTH1.cxx b/hist/histv7util/src/ConvertToTH1.cxx index 76bad7d8f06bd..67ef38bfe55e8 100644 --- a/hist/histv7util/src/ConvertToTH1.cxx +++ b/hist/histv7util/src/ConvertToTH1.cxx @@ -31,7 +31,8 @@ std::unique_ptr ConvertToTH1Impl(const RHistEngine &engine) auto ret = std::make_unique(); ret->SetDirectory(nullptr); - ROOT::Experimental::Hist::Internal::ConvertAxis(*ret->GetXaxis(), engine.GetAxes()[0]); + const auto &axis = engine.GetAxes()[0]; + ROOT::Experimental::Hist::Internal::ConvertAxis(*ret->GetXaxis(), axis); ret->SetBinsLength(); Double_t *sumw2 = nullptr; @@ -51,7 +52,6 @@ std::unique_ptr ConvertToTH1Impl(const RHistEngine &engine) }; // Copy the bin contents, accounting for TH1 numbering conventions. - const auto &axis = engine.GetAxes()[0]; for (auto index : axis.GetFullRange()) { if (index.IsUnderflow()) { copyBinContent(0, index); From 7eb60f7c61cf79e47bad03f141b9850d34f66c72 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Thu, 9 Apr 2026 16:50:34 +0200 Subject: [PATCH 4/6] [hist] Protect conversion of disabled RHistStats dimension --- hist/histv7util/src/ConvertToTH1.cxx | 8 ++++++-- hist/histv7util/test/hist_convert_TH1.cxx | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/hist/histv7util/src/ConvertToTH1.cxx b/hist/histv7util/src/ConvertToTH1.cxx index 67ef38bfe55e8..32eba3e56ef00 100644 --- a/hist/histv7util/src/ConvertToTH1.cxx +++ b/hist/histv7util/src/ConvertToTH1.cxx @@ -78,9 +78,13 @@ void ConvertGlobalStatistics(Hist &h, const RHistStats &stats) Double_t hStats[4] = { stats.GetSumW(), stats.GetSumW2(), - stats.GetDimensionStats(0).fSumWX, - stats.GetDimensionStats(0).fSumWX2, + 0, + 0, }; + if (stats.IsEnabled(0)) { + hStats[2] = stats.GetDimensionStats(0).fSumWX; + hStats[3] = stats.GetDimensionStats(0).fSumWX2; + } h.PutStats(hStats); } } // namespace diff --git a/hist/histv7util/test/hist_convert_TH1.cxx b/hist/histv7util/test/hist_convert_TH1.cxx index e32dc23958764..3b58f5fbabc9c 100644 --- a/hist/histv7util/test/hist_convert_TH1.cxx +++ b/hist/histv7util/test/hist_convert_TH1.cxx @@ -117,6 +117,29 @@ TEST(ConvertToTH1I, RHistSetBinContentTainted) EXPECT_EQ(stats[3], 0); } +TEST(ConvertToTH1I, RHistCategoricalAxis) +{ + const std::vector categories = {"a", "b", "c"}; + const RCategoricalAxis axis(categories); + RHist hist(axis); + ASSERT_FALSE(hist.GetStats().IsEnabled(0)); + + hist.Fill("a"); + + auto th1i = ConvertToTH1I(hist); + ASSERT_TRUE(th1i); + + EXPECT_EQ(th1i->GetBinContent(1), 1); + + EXPECT_EQ(th1i->GetEntries(), 1); + Double_t stats[4]; + th1i->GetStats(stats); + EXPECT_EQ(stats[0], 1); + EXPECT_EQ(stats[1], 1); + EXPECT_EQ(stats[2], 0); + EXPECT_EQ(stats[3], 0); +} + TEST(ConvertToTH1C, RHistEngine) { static constexpr std::size_t Bins = 20; From 1abebf1cbad487ddcae583a64ee720f089a9c791 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Fri, 10 Apr 2026 09:48:01 +0200 Subject: [PATCH 5/6] [hist] Move SetBinContent out of loop --- hist/histv7/test/hist_slice.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hist/histv7/test/hist_slice.cxx b/hist/histv7/test/hist_slice.cxx index 5805e49d201bc..1e474b5f6f2aa 100644 --- a/hist/histv7/test/hist_slice.cxx +++ b/hist/histv7/test/hist_slice.cxx @@ -439,11 +439,11 @@ TEST(RHistEngine, SliceSum) engine.SetBinContent(RBinIndex::Underflow(), 0, 1000); engine.SetBinContent(RBinIndex::Underflow(), 2, 2000); for (std::size_t i = 0; i < Bins; i++) { + engine.SetBinContent(i, RBinIndex::Underflow(), 100 * i); for (std::size_t j = 0; j < Bins; j++) { - engine.SetBinContent(i, RBinIndex::Underflow(), 100 * i); - engine.SetBinContent(i, RBinIndex(j), i * Bins + j); - engine.SetBinContent(i, RBinIndex::Overflow(), 200 * i); + engine.SetBinContent(i, j, i * Bins + j); } + engine.SetBinContent(i, RBinIndex::Overflow(), 200 * i); } engine.SetBinContent(RBinIndex::Overflow(), 3, 3000); engine.SetBinContent(RBinIndex::Overflow(), 6, 4000); @@ -470,11 +470,11 @@ TEST(RHistEngine, SliceRangeSum) engine.SetBinContent(RBinIndex::Underflow(), 0, 1000); engine.SetBinContent(RBinIndex::Underflow(), 2, 2000); for (std::size_t i = 0; i < Bins; i++) { + engine.SetBinContent(i, RBinIndex::Underflow(), 100 * i); for (std::size_t j = 0; j < Bins; j++) { - engine.SetBinContent(i, RBinIndex::Underflow(), 100 * i); - engine.SetBinContent(i, RBinIndex(j), i * Bins + j); - engine.SetBinContent(i, RBinIndex::Overflow(), 200 * i); + engine.SetBinContent(i, j, i * Bins + j); } + engine.SetBinContent(i, RBinIndex::Overflow(), 200 * i); } engine.SetBinContent(RBinIndex::Overflow(), 3, 3000); engine.SetBinContent(RBinIndex::Overflow(), 6, 4000); From 0e1a45cc375dec9ee7ec8c51a2dfb7e94417db17 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Fri, 10 Apr 2026 10:09:32 +0200 Subject: [PATCH 6/6] [hist] Conversion functions to TH2* --- hist/histv7util/CMakeLists.txt | 2 + .../histv7util/inc/ROOT/Hist/ConvertToTH2.hxx | 120 ++++++ hist/histv7util/src/ConvertToTH2.cxx | 217 +++++++++++ hist/histv7util/test/CMakeLists.txt | 1 + hist/histv7util/test/hist_convert_TH2.cxx | 368 ++++++++++++++++++ hist/histv7util/test/histutil_test.hxx | 1 + 6 files changed, 709 insertions(+) create mode 100644 hist/histv7util/inc/ROOT/Hist/ConvertToTH2.hxx create mode 100644 hist/histv7util/src/ConvertToTH2.cxx create mode 100644 hist/histv7util/test/hist_convert_TH2.cxx diff --git a/hist/histv7util/CMakeLists.txt b/hist/histv7util/CMakeLists.txt index f901b90de1a94..66ea3f74ca1a5 100644 --- a/hist/histv7util/CMakeLists.txt +++ b/hist/histv7util/CMakeLists.txt @@ -2,9 +2,11 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTHistUtil HEADERS ROOT/Hist/ConversionUtils.hxx ROOT/Hist/ConvertToTH1.hxx + ROOT/Hist/ConvertToTH2.hxx SOURCES ConversionUtils.cxx ConvertToTH1.cxx + ConvertToTH2.cxx DEPENDENCIES Core Hist diff --git a/hist/histv7util/inc/ROOT/Hist/ConvertToTH2.hxx b/hist/histv7util/inc/ROOT/Hist/ConvertToTH2.hxx new file mode 100644 index 0000000000000..efec44f2e616f --- /dev/null +++ b/hist/histv7util/inc/ROOT/Hist/ConvertToTH2.hxx @@ -0,0 +1,120 @@ +/// \file +/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes. +/// Feedback is welcome! + +#ifndef ROOT_Hist_ConvertToTH2 +#define ROOT_Hist_ConvertToTH2 + +#include +#include +#include + +class TH2C; +class TH2S; +class TH2I; +class TH2L; +class TH2F; +class TH2D; + +#include + +namespace ROOT { +namespace Experimental { +namespace Hist { + +/// Convert a one-dimensional histogram to TH2C. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2C(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2S. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2S(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2I. +/// +/// As RHistEngine does not have global statistics, the number of entries and the total sum of weights will be unset. +/// +/// Throws an exception if the histogram has more than one dimension. +/// +/// \param[in] engine the RHistEngine to convert +/// \return the converted TH2 +std::unique_ptr ConvertToTH2I(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2L. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2L(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2L. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2L(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2F. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2F(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2D. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2D(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2D. +/// +/// \copydetails ConvertToTH2I(const RHistEngine &engine) +std::unique_ptr ConvertToTH2D(const RHistEngine &engine); + +/// Convert a one-dimensional histogram to TH2C. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2C(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2S. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2S(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2I. +/// +/// If the RHistStats are tainted, for example after setting bin contents, the number of entries and the total sum of +/// weights will be unset. +/// +/// Throws an exception if the histogram has more than one dimension. +/// +/// \param[in] hist the RHist to convert +/// \return the converted TH2 +std::unique_ptr ConvertToTH2I(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2L. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2L(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2L. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2L(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2F. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2F(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2D. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2D(const RHist &hist); + +/// Convert a one-dimensional histogram to TH2D. +/// +/// \copydetails ConvertToTH2I(const RHist &hist) +std::unique_ptr ConvertToTH2D(const RHist &hist); + +} // namespace Hist +} // namespace Experimental +} // namespace ROOT + +#endif diff --git a/hist/histv7util/src/ConvertToTH2.cxx b/hist/histv7util/src/ConvertToTH2.cxx new file mode 100644 index 0000000000000..ca2c0cca74cc1 --- /dev/null +++ b/hist/histv7util/src/ConvertToTH2.cxx @@ -0,0 +1,217 @@ +/// \file +/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes. +/// Feedback is welcome! + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace ROOT::Experimental; + +namespace { +template +std::unique_ptr ConvertToTH2Impl(const RHistEngine &engine) +{ + if (engine.GetNDimensions() != 2) { + throw std::invalid_argument("TH2 requires two dimensions"); + } + + auto ret = std::make_unique(); + ret->SetDirectory(nullptr); + + const auto &axis0 = engine.GetAxes()[0]; + ROOT::Experimental::Hist::Internal::ConvertAxis(*ret->GetXaxis(), axis0); + const auto &axis1 = engine.GetAxes()[1]; + ROOT::Experimental::Hist::Internal::ConvertAxis(*ret->GetYaxis(), axis1); + ret->SetBinsLength(); + + Double_t *sumw2 = nullptr; + auto copyBinContent = [&ret, &engine, &sumw2](Int_t i, RBinIndex index0, RBinIndex index1) { + if constexpr (std::is_same_v) { + if (sumw2 == nullptr) { + ret->Sumw2(); + sumw2 = ret->GetSumw2()->GetArray(); + } + const RBinWithError &c = engine.GetBinContent(index0, index1); + ret->GetArray()[i] = c.fSum; + sumw2[i] = c.fSum2; + } else { + (void)sumw2; + ret->GetArray()[i] = engine.GetBinContent(index0, index1); + } + }; + + // Copy the bin contents, accounting for TH2 numbering conventions. + for (auto index0 : axis0.GetFullRange()) { + Int_t i0 = 0; + if (index0.IsUnderflow()) { + i0 = 0; + } else if (index0.IsOverflow()) { + i0 = axis0.GetNNormalBins() + 1; + } else { + assert(index0.IsNormal()); + i0 = index0.GetIndex() + 1; + } + Int_t n0 = ret->GetXaxis()->GetNbins() + 2; + + for (auto index1 : axis1.GetFullRange()) { + if (index1.IsUnderflow()) { + copyBinContent(i0, index0, index1); + } else if (index1.IsOverflow()) { + copyBinContent(i0 + n0 * (axis1.GetNNormalBins() + 1), index0, index1); + } else { + assert(index1.IsNormal()); + copyBinContent(i0 + n0 * (index1.GetIndex() + 1), index0, index1); + } + } + } + + return ret; +} + +template +void ConvertGlobalStatistics(Hist &h, const RHistStats &stats) +{ + if (stats.IsTainted()) { + return; + } + + h.SetEntries(stats.GetNEntries()); + + Double_t hStats[7] = { + stats.GetSumW(), + stats.GetSumW2(), + 0, + 0, + 0, + 0, + // We do not have sumwxy + 0, + }; + if (stats.IsEnabled(0)) { + hStats[2] = stats.GetDimensionStats(0).fSumWX; + hStats[3] = stats.GetDimensionStats(0).fSumWX2; + } + if (stats.IsEnabled(1)) { + hStats[4] = stats.GetDimensionStats(1).fSumWX; + hStats[5] = stats.GetDimensionStats(1).fSumWX2; + } + h.PutStats(hStats); +} +} // namespace + +namespace ROOT { +namespace Experimental { +namespace Hist { + +std::unique_ptr ConvertToTH2C(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2S(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2I(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2L(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2L(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2F(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2D(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2D(const RHistEngine &engine) +{ + return ConvertToTH2Impl(engine); +} + +std::unique_ptr ConvertToTH2C(const RHist &hist) +{ + auto ret = ConvertToTH2C(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2S(const RHist &hist) +{ + auto ret = ConvertToTH2S(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2I(const RHist &hist) +{ + auto ret = ConvertToTH2I(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2L(const RHist &hist) +{ + auto ret = ConvertToTH2L(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2L(const RHist &hist) +{ + auto ret = ConvertToTH2L(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2F(const RHist &hist) +{ + auto ret = ConvertToTH2F(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2D(const RHist &hist) +{ + auto ret = ConvertToTH2D(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +std::unique_ptr ConvertToTH2D(const RHist &hist) +{ + auto ret = ConvertToTH2D(hist.GetEngine()); + ConvertGlobalStatistics(*ret, hist.GetStats()); + return ret; +} + +} // namespace Hist +} // namespace Experimental +} // namespace ROOT diff --git a/hist/histv7util/test/CMakeLists.txt b/hist/histv7util/test/CMakeLists.txt index dcc66418dd6b4..089c556239280 100644 --- a/hist/histv7util/test/CMakeLists.txt +++ b/hist/histv7util/test/CMakeLists.txt @@ -1,2 +1,3 @@ ROOT_ADD_GTEST(hist_conversion_utils hist_conversion_utils.cxx LIBRARIES ROOTHist ROOTHistUtil) ROOT_ADD_GTEST(hist_convert_TH1 hist_convert_TH1.cxx LIBRARIES ROOTHist ROOTHistUtil) +ROOT_ADD_GTEST(hist_convert_TH2 hist_convert_TH2.cxx LIBRARIES ROOTHist ROOTHistUtil) diff --git a/hist/histv7util/test/hist_convert_TH2.cxx b/hist/histv7util/test/hist_convert_TH2.cxx new file mode 100644 index 0000000000000..d21cf45fc5bc7 --- /dev/null +++ b/hist/histv7util/test/hist_convert_TH2.cxx @@ -0,0 +1,368 @@ +#include "histutil_test.hxx" + +#include + +#include +#include +#include +#include + +TEST(ConvertToTH2I, RHistEngine) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + RHistEngine engine(axis, axis); + + engine.SetBinContent(RBinIndex::Underflow(), 0, 1000); + engine.SetBinContent(RBinIndex::Underflow(), 2, 2000); + for (std::size_t i = 0; i < Bins; i++) { + engine.SetBinContent(i, RBinIndex::Underflow(), 100 * i); + for (std::size_t j = 0; j < Bins; j++) { + engine.SetBinContent(i, j, i * Bins + j); + } + engine.SetBinContent(i, RBinIndex::Overflow(), 200 * i); + } + engine.SetBinContent(RBinIndex::Overflow(), 3, 3000); + engine.SetBinContent(RBinIndex::Overflow(), 6, 4000); + + auto th2i = ConvertToTH2I(engine); + ASSERT_TRUE(th2i); + EXPECT_TRUE(th2i->GetDirectory() == nullptr); + ASSERT_EQ(th2i->GetDimension(), 2); + ASSERT_EQ(th2i->GetNbinsX(), Bins); + ASSERT_EQ(th2i->GetNbinsY(), Bins); + ASSERT_EQ(th2i->GetNbinsZ(), 1); + + EXPECT_EQ(th2i->GetBinContent(0, 1), 1000); + EXPECT_EQ(th2i->GetBinContent(0, 3), 2000); + for (std::size_t i = 0; i < Bins; i++) { + EXPECT_EQ(th2i->GetBinContent(i + 1, 0), 100 * i); + for (std::size_t j = 0; j < Bins; j++) { + EXPECT_EQ(th2i->GetBinContent(i + 1, j + 1), i * Bins + j); + } + EXPECT_EQ(th2i->GetBinContent(i + 1, Bins + 1), 200 * i); + } + EXPECT_EQ(th2i->GetBinContent(Bins + 1, 4), 3000); + EXPECT_EQ(th2i->GetBinContent(Bins + 1, 7), 4000); + + EXPECT_EQ(th2i->GetEntries(), 0); + Double_t stats[7]; + th2i->GetStats(stats); + EXPECT_EQ(stats[0], 0); + EXPECT_EQ(stats[1], 0); + EXPECT_EQ(stats[2], 0); + EXPECT_EQ(stats[3], 0); + EXPECT_EQ(stats[4], 0); + EXPECT_EQ(stats[5], 0); + EXPECT_EQ(stats[6], 0); +} + +TEST(ConvertToTH2I, RHistEngineNoFlowBins) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}, /*enableFlowBins=*/false); + RHistEngine engine(axis, axis); + + // Flow bins are disabled, so these fills will be discarded. + engine.Fill(-100, 0.5); + engine.Fill(-100, 2.5); + for (std::size_t i = 0; i < Bins; i++) { + // Flow bins are disabled, so this fill will be discarded. + engine.Fill(i + 0.5, -100); + for (std::size_t j = 0; j < Bins; j++) { + engine.SetBinContent(i, j, i * Bins + j); + } + // Flow bins are disabled, so this fill will be discarded. + engine.Fill(i + 0.5, 100); + } + // Flow bins are disabled, so these fills will be discarded. + engine.Fill(100, 3.5); + engine.Fill(100, 6.5); + + auto th2i = ConvertToTH2I(engine); + ASSERT_TRUE(th2i); + + EXPECT_EQ(th2i->GetBinContent(0, 1), 0); + EXPECT_EQ(th2i->GetBinContent(0, 3), 0); + for (std::size_t i = 0; i < Bins; i++) { + EXPECT_EQ(th2i->GetBinContent(i + 1, 0), 0); + for (std::size_t j = 0; j < Bins; j++) { + EXPECT_EQ(th2i->GetBinContent(i + 1, j + 1), i * Bins + j); + } + EXPECT_EQ(th2i->GetBinContent(i + 1, Bins + 1), 0); + } + EXPECT_EQ(th2i->GetBinContent(Bins + 1, 4), 0); + EXPECT_EQ(th2i->GetBinContent(Bins + 1, 7), 0); +} + +TEST(ConvertToTH2I, RHistEngineInvalid) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHistEngine engine(axis); + + EXPECT_THROW(ConvertToTH2I(engine), std::invalid_argument); +} + +TEST(ConvertToTH2I, RHist) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + RHist hist(axis, axis); + + for (std::size_t i = 0; i < Bins; i++) { + hist.Fill(i, 2 * i); + } + + auto th2i = ConvertToTH2I(hist); + ASSERT_TRUE(th2i); + + ASSERT_EQ(hist.GetNEntries(), Bins); + EXPECT_EQ(th2i->GetEntries(), Bins); + Double_t stats[7]; + th2i->GetStats(stats); + EXPECT_EQ(stats[0], Bins); + EXPECT_EQ(stats[1], Bins); + EXPECT_EQ(stats[2], 190); + EXPECT_EQ(stats[3], 2470); + EXPECT_EQ(stats[4], 2 * 190); + EXPECT_EQ(stats[5], 4 * 2470); + EXPECT_EQ(stats[6], 0); +} + +TEST(ConvertToTH2I, RHistSetBinContentTainted) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + RHist hist(axis, axis); + const std::array indices = {1, 2}; + hist.SetBinContent(indices, 42); + ASSERT_TRUE(hist.GetStats().IsTainted()); + + auto th2i = ConvertToTH2I(hist); + ASSERT_TRUE(th2i); + + EXPECT_EQ(th2i->GetBinContent(2 + (Bins + 2) * 3), 42); + + EXPECT_EQ(th2i->GetEntries(), 0); + Double_t stats[7]; + th2i->GetStats(stats); + EXPECT_EQ(stats[0], 0); + EXPECT_EQ(stats[1], 0); + EXPECT_EQ(stats[2], 0); + EXPECT_EQ(stats[3], 0); + EXPECT_EQ(stats[4], 0); + EXPECT_EQ(stats[5], 0); + EXPECT_EQ(stats[6], 0); +} + +TEST(ConvertToTH2I, RHistCategoricalAxis) +{ + const std::vector categories = {"a", "b", "c"}; + const RCategoricalAxis axis(categories); + RHist hist(axis, axis); + ASSERT_FALSE(hist.GetStats().IsEnabled(0)); + ASSERT_FALSE(hist.GetStats().IsEnabled(1)); + + hist.Fill("a", "b"); + + auto th2i = ConvertToTH2I(hist); + ASSERT_TRUE(th2i); + + EXPECT_EQ(th2i->GetBinContent(1 + 5 * 2), 1); + + EXPECT_EQ(th2i->GetEntries(), 1); + Double_t stats[7]; + th2i->GetStats(stats); + EXPECT_EQ(stats[0], 1); + EXPECT_EQ(stats[1], 1); + EXPECT_EQ(stats[2], 0); + EXPECT_EQ(stats[3], 0); + EXPECT_EQ(stats[4], 0); + EXPECT_EQ(stats[5], 0); + EXPECT_EQ(stats[6], 0); +} + +TEST(ConvertToTH2C, RHistEngine) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHistEngine engine(axis, axis); + + auto th2c = ConvertToTH2C(engine); + ASSERT_TRUE(th2c); +} + +TEST(ConvertToTH2C, RHist) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHist hist(axis, axis); + + auto th2c = ConvertToTH2C(hist); + ASSERT_TRUE(th2c); +} + +TEST(ConvertToTH2S, RHistEngine) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHistEngine engine(axis, axis); + + auto th2s = ConvertToTH2S(engine); + ASSERT_TRUE(th2s); +} + +TEST(ConvertToTH2S, RHist) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHist hist(axis, axis); + + auto th2s = ConvertToTH2S(hist); + ASSERT_TRUE(th2s); +} + +TEST(ConvertToTH2L, RHistEngine) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHistEngine engineL(axis, axis); + + auto th2l = ConvertToTH2L(engineL); + ASSERT_TRUE(th2l); + + RHistEngine engineLL(axis, axis); + + // Set one 64-bit long long value larger than what double can exactly represent. + static constexpr long long Large = (1LL << 60) - 1; + const std::array indices = {1, 2}; + engineLL.SetBinContent(indices, Large); + + th2l = ConvertToTH2L(engineLL); + ASSERT_TRUE(th2l); + + // Get the value via TArrayL::At and store into a variable to be sure about the type. During direct comparison, a + // double return value may automatically promote Large to a double as well, introducing the truncation we want to + // test against. + const long long value = th2l->At(2 + (Bins + 2) * 3); + EXPECT_EQ(value, Large); +} + +TEST(ConvertToTH2L, RHist) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHist histL(axis, axis); + + auto th2l = ConvertToTH2L(histL); + ASSERT_TRUE(th2l); + + const RHist histLL(axis, axis); + th2l = ConvertToTH2L(histLL); + ASSERT_TRUE(th2l); +} + +TEST(ConvertToTH2F, RHistEngine) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + RHistEngine engine(axis, axis); + + engine.Fill(-100, 0.5, RWeight(0.25)); + for (std::size_t i = 0; i < Bins / 2; i++) { + engine.Fill(i, 2 * i, RWeight(0.1 + i * 0.03)); + } + engine.Fill(100, Bins - 0.5, RWeight(0.75)); + + auto th2f = ConvertToTH2F(engine); + ASSERT_TRUE(th2f); + + EXPECT_FLOAT_EQ(th2f->GetBinContent(0, 1), 0.25); + for (std::size_t i = 0; i < Bins / 2; i++) { + EXPECT_FLOAT_EQ(th2f->GetBinContent(i + 1, 2 * i + 1), 0.1 + i * 0.03); + } + EXPECT_EQ(th2f->GetBinContent(Bins + 1, Bins), 0.75); +} + +TEST(ConvertToTH2F, RHist) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + RHist hist(axis, axis); + + for (std::size_t i = 0; i < Bins; i++) { + hist.Fill(i, 2 * i, RWeight(0.1 + i * 0.03)); + } + + auto th2f = ConvertToTH2F(hist); + ASSERT_TRUE(th2f); + + ASSERT_EQ(hist.GetNEntries(), Bins); + EXPECT_EQ(th2f->GetEntries(), Bins); + Double_t stats[7]; + th2f->GetStats(stats); + EXPECT_DOUBLE_EQ(stats[0], 7.7); + EXPECT_DOUBLE_EQ(stats[1], 3.563); + EXPECT_DOUBLE_EQ(stats[2], 93.1); + EXPECT_DOUBLE_EQ(stats[3], 1330.0); + EXPECT_DOUBLE_EQ(stats[4], 2 * 93.1); + EXPECT_DOUBLE_EQ(stats[5], 4 * 1330.0); + EXPECT_DOUBLE_EQ(stats[6], 0); +} + +TEST(ConvertToTH2D, RHistEngine) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHistEngine engineD(axis, axis); + + auto th2d = ConvertToTH2D(engineD); + ASSERT_TRUE(th2d); + + RHistEngine engineE(axis, axis); + for (std::size_t i = 0; i < Bins / 2; i++) { + engineE.Fill(i, 2 * i, RWeight(0.1 + i * 0.03)); + } + + th2d = ConvertToTH2D(engineE); + ASSERT_TRUE(th2d); + const Double_t *sumw2 = th2d->GetSumw2()->GetArray(); + ASSERT_TRUE(sumw2 != nullptr); + + for (std::size_t i = 0; i < Bins / 2; i++) { + const double weight = 0.1 + i * 0.03; + EXPECT_EQ(th2d->GetBinContent(i + 1, 2 * i + 1), weight); + EXPECT_EQ(sumw2[i + 1 + (Bins + 2) * (2 * i + 1)], weight * weight); + } +} + +TEST(ConvertToTH2D, RHist) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, {0, Bins}); + const RHist histD(axis, axis); + + auto th2d = ConvertToTH2D(histD); + ASSERT_TRUE(th2d); + + RHist histE(axis, axis); + for (std::size_t i = 0; i < Bins; i++) { + histE.Fill(i, 2 * i, RWeight(0.1 + i * 0.03)); + } + + th2d = ConvertToTH2D(histE); + ASSERT_TRUE(th2d); + + ASSERT_EQ(histE.GetNEntries(), Bins); + EXPECT_EQ(th2d->GetEntries(), Bins); + Double_t stats[7]; + th2d->GetStats(stats); + EXPECT_DOUBLE_EQ(stats[0], 7.7); + EXPECT_DOUBLE_EQ(stats[1], 3.563); + EXPECT_DOUBLE_EQ(stats[2], 93.1); + EXPECT_DOUBLE_EQ(stats[3], 1330.0); + EXPECT_DOUBLE_EQ(stats[4], 2 * 93.1); + EXPECT_DOUBLE_EQ(stats[5], 4 * 1330.0); + EXPECT_DOUBLE_EQ(stats[6], 0); +} diff --git a/hist/histv7util/test/histutil_test.hxx b/hist/histv7util/test/histutil_test.hxx index 9aed14aed5ba9..4015e60da3490 100644 --- a/hist/histv7util/test/histutil_test.hxx +++ b/hist/histv7util/test/histutil_test.hxx @@ -3,6 +3,7 @@ #include #include +#include #include #include #include