diff --git a/bindings/pyroot/pythonizations/test/uhi_indexing.py b/bindings/pyroot/pythonizations/test/uhi_indexing.py index 31d30e15f5f9a..3a80a7079c1cd 100644 --- a/bindings/pyroot/pythonizations/test/uhi_indexing.py +++ b/bindings/pyroot/pythonizations/test/uhi_indexing.py @@ -2,6 +2,7 @@ Tests to verify that TH1 and derived histograms conform to the UHI Indexing interfaces (setting, accessing and slicing). """ +import numpy as np import pytest import ROOT from ROOT._pythonization._uhi import _get_axis, _get_processed_slices, _overflow, _shape, _underflow @@ -326,5 +327,35 @@ def test_list_iter(self, hist_setup): assert list(hist_setup) == expected.flatten().tolist() +class TestInfiniteEdges: + def setup_method(self): + # create a 2D histogram with an infinite upper edge on the Y axis + xedges = np.array([0.0, 1.0, 2.0], dtype="float64") + yedges = np.array([0.0, 1.0, 2.0, np.inf], dtype="float64") + self.h_inf = ROOT.TH2D("h_inf", "h_inf", len(xedges) - 1, xedges, len(yedges) - 1, yedges) + + for i in range(1, self.h_inf.GetNbinsX() + 1): + for j in range(1, self.h_inf.GetNbinsY() + 1): + self.h_inf.SetBinContent(i, j, 10 * i + j) + + def test_uhi_projection_preserves_content(self): + """check that UHI projection behaves like standard projection""" + # projection on X axis + proj_x_ref = self.h_inf.ProjectionX() + proj_x_uhi = self.h_inf[:, ROOT.uhi.sum] + ref_values = proj_x_ref.values() + uhi_values = proj_x_uhi.values() + + assert np.allclose(uhi_values, ref_values) + + # projection on Y axis + proj_y_ref = self.h_inf.ProjectionY() + proj_y_uhi = self.h_inf[ROOT.uhi.sum, :] + ref_values = proj_y_ref.values() + uhi_values = proj_y_uhi.values() + + assert np.allclose(uhi_values, ref_values) + + if __name__ == "__main__": raise SystemExit(pytest.main(args=[__file__])) diff --git a/hist/hist/inc/TH1.h b/hist/hist/inc/TH1.h index 91d29f041748a..1c58a1b2ff088 100644 --- a/hist/hist/inc/TH1.h +++ b/hist/hist/inc/TH1.h @@ -214,17 +214,19 @@ class TH1 : public TNamed, public TAttLine, public TAttFill, public TAttMarker { // Compute new bin counts and edges std::array nBins{}, totalBins{}; - std::array lowEdge{}, upEdge{}; + std::array, kMaxDim> edges; for (decltype(ndim) d = 0; d < ndim; ++d) { const auto &axis = (d == 0 ? fXaxis : d == 1 ? fYaxis : fZaxis); auto start = std::max(1, args[d * 2]); auto end = std::min(axis.GetNbins() + 1, args[d * 2 + 1]); nBins[d] = end - start; - lowEdge[d] = axis.GetBinLowEdge(start); - upEdge[d] = axis.GetBinLowEdge(end); totalBins[d] = axis.GetNbins() + 2; args[2 * d] = start; args[2 * d + 1] = end; + // Compute new edges + for (int b = start; b <= end; ++b) + edges[d].push_back(axis.GetBinLowEdge(b)); + edges[d].push_back(axis.GetBinUpEdge(end)); } // Compute layout sizes for slice @@ -271,13 +273,10 @@ class TH1 : public TNamed, public TAttLine, public TAttFill, public TAttMarker { dataArray = newArr; // Reconfigure Axes - if (ndim == 1) { - this->SetBins(nBins[0], lowEdge[0], upEdge[0]); - } else if (ndim == 2) { - this->SetBins(nBins[0], lowEdge[0], upEdge[0], nBins[1], lowEdge[1], upEdge[1]); - } else if (ndim == 3) { - this->SetBins(nBins[0], lowEdge[0], upEdge[0], nBins[1], lowEdge[1], upEdge[1], nBins[2], lowEdge[2], - upEdge[2]); + switch (ndim) { + case 1: this->SetBins(nBins[0], edges[0].data()); break; + case 2: this->SetBins(nBins[0], edges[0].data(), nBins[1], edges[1].data()); break; + case 3: this->SetBins(nBins[0], edges[0].data(), nBins[1], edges[1].data(), nBins[2], edges[2].data()); break; } // Update the statistics