From 01abd86bd1f3a46eb8685702d756fbb6c46ec667 Mon Sep 17 00:00:00 2001 From: Elvand Lie Nababan Date: Wed, 21 Oct 2026 01:00:37 +0700 Subject: [PATCH 1/2] [hist] Reset all statistics in TProfile2D/3D::Divide Both Divide overloads rebuilt higher-axis moments on top of the target's old statistics. Reset every sum their bin loops accumulate before rebuilding the result. In TProfile3D, use the Z bin index in the two-input overload and accumulate the Z first and second moments correctly in the single-input overload. Cover both overloads with distinct axis centers and previously filled targets. Also check an empty 3D target and the in-place Z variance. Assisted-by: codex:gpt-6.1-sol Assisted-by: claude-code:claude-opus-5-5 --- hist/hist/src/TProfile2D.cxx | 4 +- hist/hist/src/TProfile3D.cxx | 16 ++++---- hist/hist/test/test_TProfile.cxx | 63 +++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/hist/hist/src/TProfile2D.cxx b/hist/hist/src/TProfile2D.cxx index fa2e5ef736c6d..4ade81085221f 100644 --- a/hist/hist/src/TProfile2D.cxx +++ b/hist/hist/src/TProfile2D.cxx @@ -485,7 +485,7 @@ Bool_t TProfile2D::Divide(const TH1 *h1) } // Reset statistics - fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = 0; + fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = fTsumwy = fTsumwy2 = fTsumwxy = fTsumwz = fTsumwz2 = 0; // Loop on bins (including underflows/overflows) Int_t bin,binx,biny; @@ -580,7 +580,7 @@ Bool_t TProfile2D::Divide(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2 } // Reset statistics - fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = 0; + fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = fTsumwy = fTsumwy2 = fTsumwxy = fTsumwz = fTsumwz2 = 0; // Loop on bins (including underflows/overflows) Int_t bin,binx,biny; diff --git a/hist/hist/src/TProfile3D.cxx b/hist/hist/src/TProfile3D.cxx index 4756da07a4778..e8888d3ac5f66 100644 --- a/hist/hist/src/TProfile3D.cxx +++ b/hist/hist/src/TProfile3D.cxx @@ -449,9 +449,10 @@ Bool_t TProfile3D::Divide(const TH1 *h1) } // Reset statistics - fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = 0; + fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = fTsumwy = fTsumwy2 = fTsumwxy = fTsumwz = fTsumwz2 = fTsumwxz = + fTsumwyz = fTsumwt = fTsumwt2 = 0; -// Loop on bins (including underflows/overflows) + // Loop on bins (including underflows/overflows) Int_t bin,binx,biny,binz; Double_t *cu1 = p1->GetW(); Double_t *er1 = p1->GetW2(); @@ -478,8 +479,8 @@ Bool_t TProfile3D::Divide(const TH1 *h1) fTsumwy += u*y; fTsumwy2 += u*y*y; fTsumwxy += u*x*y; - fTsumwz += u; - fTsumwz2 += u*z; + fTsumwz += u * z; + fTsumwz2 += u * z * z; fTsumwxz += u*x*z; fTsumwyz += u*y*z; fTsumwt += u; @@ -553,9 +554,10 @@ Bool_t TProfile3D::Divide(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2 } // Reset statistics - fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = 0; + fEntries = fTsumw = fTsumw2 = fTsumwx = fTsumwx2 = fTsumwy = fTsumwy2 = fTsumwxy = fTsumwz = fTsumwz2 = fTsumwxz = + fTsumwyz = fTsumwt = fTsumwt2 = 0; -// Loop on bins (including underflows/overflows) + // Loop on bins (including underflows/overflows) Int_t bin,binx,biny,binz; Double_t *cu1 = p1->GetW(); Double_t *cu2 = p2->GetW(); @@ -578,7 +580,7 @@ Bool_t TProfile3D::Divide(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2 u = TMath::Abs(w); x = fXaxis.GetBinCenter(binx); y = fYaxis.GetBinCenter(biny); - z = fZaxis.GetBinCenter(biny); + z = fZaxis.GetBinCenter(binz); fEntries++; fTsumw += u; fTsumw2 += u*u; diff --git a/hist/hist/test/test_TProfile.cxx b/hist/hist/test/test_TProfile.cxx index c08d0d2563608..f7058c1f7cb0f 100644 --- a/hist/hist/test/test_TProfile.cxx +++ b/hist/hist/test/test_TProfile.cxx @@ -90,4 +90,65 @@ TEST(TProfile3D, Chi2Test) } runTest(reference, sameDistr, differentDistr); -} \ No newline at end of file +} + +TEST(TProfile2D, DivideStatistics) +{ + TProfile2D target("target", "", 4, 0, 4, 5, 0, 10); + TProfile2D denominator("denominator", "", 4, 0, 4, 5, 0, 10); + target.Fill(0.5, 1., 2.); + target.Fill(3.5, 9., 1.); + denominator.Fill(0.5, 1., 1.); + + ASSERT_TRUE(target.Divide(&denominator)); + EXPECT_DOUBLE_EQ(0.5, target.GetMean(1)); + EXPECT_DOUBLE_EQ(1., target.GetMean(2)); +} + +TEST(TProfile2D, DivideTwoProfilesStatistics) +{ + TProfile2D numerator("numerator", "", 4, 0, 4, 5, 0, 10); + TProfile2D denominator("denominator", "", 4, 0, 4, 5, 0, 10); + TProfile2D target("target", "", 4, 0, 4, 5, 0, 10); + numerator.Fill(0.5, 1., 2.); + denominator.Fill(0.5, 1., 1.); + target.Fill(3.5, 9., 1.); + + ASSERT_TRUE(target.Divide(&numerator, &denominator)); + EXPECT_DOUBLE_EQ(0.5, target.GetMean(1)); + EXPECT_DOUBLE_EQ(1., target.GetMean(2)); +} + +TEST(TProfile3D, DivideStatistics) +{ + TProfile3D target("target", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); + TProfile3D denominator("denominator", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); + target.Fill(0.5, 1., 25., 2.); + target.Fill(3.5, 9., 5., 1.); + denominator.Fill(0.5, 1., 25., 1.); + + ASSERT_TRUE(target.Divide(&denominator)); + EXPECT_DOUBLE_EQ(0.5, target.GetMean(1)); + EXPECT_DOUBLE_EQ(1., target.GetMean(2)); + EXPECT_DOUBLE_EQ(25., target.GetMean(3)); + EXPECT_DOUBLE_EQ(0., target.GetStdDev(3)); +} + +TEST(TProfile3D, DivideTwoProfilesStatistics) +{ + for (bool prefill : {false, true}) { + SCOPED_TRACE(prefill); + TProfile3D numerator("numerator", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); + TProfile3D denominator("denominator", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); + TProfile3D target("target", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); + numerator.Fill(0.5, 1., 25., 2.); + denominator.Fill(0.5, 1., 25., 1.); + if (prefill) + target.Fill(3.5, 9., 5., 1.); + + ASSERT_TRUE(target.Divide(&numerator, &denominator)); + EXPECT_DOUBLE_EQ(0.5, target.GetMean(1)); + EXPECT_DOUBLE_EQ(1., target.GetMean(2)); + EXPECT_DOUBLE_EQ(25., target.GetMean(3)); + } +} From fbe78fa0c89ea45aa2bb7bf39727b2a6a0438738 Mon Sep 17 00:00:00 2001 From: Elvand Lie Nababan Date: Sun, 25 Oct 2026 02:34:38 +0700 Subject: [PATCH 2/2] [hist] Check the Z spread after TProfile3D::Divide Use two surviving Z bins with equal weights, giving a deviation of 10. This fails if the second moment uses u*z and its variance clamps to zero. Assisted-by: codex:gpt-6.1-sol Assisted-by: claude-code:claude-opus-5-5 --- hist/hist/test/test_TProfile.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hist/hist/test/test_TProfile.cxx b/hist/hist/test/test_TProfile.cxx index f7058c1f7cb0f..9da3b601789db 100644 --- a/hist/hist/test/test_TProfile.cxx +++ b/hist/hist/test/test_TProfile.cxx @@ -124,14 +124,16 @@ TEST(TProfile3D, DivideStatistics) TProfile3D target("target", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); TProfile3D denominator("denominator", "", 4, 0, 4, 5, 0, 10, 3, 0, 30); target.Fill(0.5, 1., 25., 2.); + target.Fill(0.5, 1., 5., 2.); target.Fill(3.5, 9., 5., 1.); denominator.Fill(0.5, 1., 25., 1.); + denominator.Fill(0.5, 1., 5., 1.); ASSERT_TRUE(target.Divide(&denominator)); EXPECT_DOUBLE_EQ(0.5, target.GetMean(1)); EXPECT_DOUBLE_EQ(1., target.GetMean(2)); - EXPECT_DOUBLE_EQ(25., target.GetMean(3)); - EXPECT_DOUBLE_EQ(0., target.GetStdDev(3)); + EXPECT_DOUBLE_EQ(15., target.GetMean(3)); + EXPECT_DOUBLE_EQ(10., target.GetStdDev(3)); } TEST(TProfile3D, DivideTwoProfilesStatistics)