Skip to content

[hist] Check arguments in axis constructors #19483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions hist/histv7/inc/ROOT/RRegularAxis.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <cstddef>
#include <stdexcept>
#include <string>

class TBuffer;

Expand Down Expand Up @@ -44,14 +45,20 @@ class RRegularAxis final {
public:
/// Construct a regular axis object.
///
/// \param[in] numNormalBins the number of normal bins
/// \param[in] numNormalBins the number of normal bins, must be > 0
/// \param[in] low the lower end of the axis interval (inclusive)
/// \param[in] high the upper end of the axis interval (exclusive)
/// \param[in] high the upper end of the axis interval (exclusive), must be > low
/// \param[in] enableFlowBins whether to enable underflow and overflow bins
RRegularAxis(std::size_t numNormalBins, double low, double high, bool enableFlowBins = true)
: fNumNormalBins(numNormalBins), fLow(low), fHigh(high), fEnableFlowBins(enableFlowBins)
{
// FIXME: should validate numNormalBins > 0 and low < high
if (numNormalBins == 0) {
throw std::invalid_argument("numNormalBins must be > 0");
}
if (low >= high) {
std::string msg = "high must be > low, but " + std::to_string(low) + " >= " + std::to_string(high);
throw std::invalid_argument(msg);
}
fInvBinWidth = numNormalBins / (high - low);
}

Expand Down
14 changes: 12 additions & 2 deletions hist/histv7/inc/ROOT/RVariableBinAxis.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <cstddef>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -41,12 +42,21 @@ class RVariableBinAxis final {
public:
/// Construct an axis object with variable bins.
///
/// \param[in] binEdges the (ordered) edges of the normal bins
/// \param[in] binEdges the (ordered) edges of the normal bins, must define at least one bin (i.e. size >= 2)
/// \param[in] enableFlowBins whether to enable underflow and overflow bins
RVariableBinAxis(std::vector<double> binEdges, bool enableFlowBins = true)
: fBinEdges(std::move(binEdges)), fEnableFlowBins(enableFlowBins)
{
// FIXME: should validate that fBinEdges is sorted
if (fBinEdges.size() < 2) {
throw std::invalid_argument("must have >= 2 bin edges");
}
for (std::size_t i = 1; i < fBinEdges.size(); i++) {
if (fBinEdges[i - 1] >= fBinEdges[i]) {
std::string msg = "binEdges must be sorted, but for bin " + std::to_string(i - 1) + ": ";
msg += std::to_string(fBinEdges[i - 1]) + " >= " + std::to_string(fBinEdges[i]);
throw std::invalid_argument(msg);
}
}
}

std::size_t GetNumNormalBins() const { return fBinEdges.size() - 1; }
Expand Down
23 changes: 23 additions & 0 deletions hist/histv7/test/hist_regular.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ TEST(RRegularAxis, Constructor)
EXPECT_EQ(axis.GetNumNormalBins(), Bins);
EXPECT_EQ(axis.GetTotalNumBins(), Bins);
EXPECT_FALSE(axis.HasFlowBins());

EXPECT_THROW(RRegularAxis(0, 0, Bins), std::invalid_argument);
EXPECT_THROW(RRegularAxis(Bins, 1, 1), std::invalid_argument);
}

TEST(RRegularAxis, Equality)
Expand Down Expand Up @@ -64,6 +67,16 @@ TEST(RRegularAxis, ComputeLinearizedIndex)
EXPECT_FALSE(linIndex.fValid);
}

// Exactly the lower end of the axis interval
{
auto linIndex = axis.ComputeLinearizedIndex(0);
EXPECT_EQ(linIndex.fIndex, 0);
EXPECT_TRUE(linIndex.fValid);
linIndex = axisNoFlowBins.ComputeLinearizedIndex(0);
EXPECT_EQ(linIndex.fIndex, 0);
EXPECT_TRUE(linIndex.fValid);
}

for (std::size_t i = 0; i < Bins; i++) {
auto linIndex = axis.ComputeLinearizedIndex(i + 0.5);
EXPECT_EQ(linIndex.fIndex, i);
Expand All @@ -73,6 +86,16 @@ TEST(RRegularAxis, ComputeLinearizedIndex)
EXPECT_TRUE(linIndex.fValid);
}

// Exactly the upper end of the axis interval
{
auto linIndex = axis.ComputeLinearizedIndex(Bins);
EXPECT_EQ(linIndex.fIndex, Bins + 1);
EXPECT_TRUE(linIndex.fValid);
linIndex = axisNoFlowBins.ComputeLinearizedIndex(Bins);
EXPECT_EQ(linIndex.fIndex, Bins + 1);
EXPECT_FALSE(linIndex.fValid);
}

// Overflow
static constexpr double PositiveInfinity = std::numeric_limits<double>::infinity();
static constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
Expand Down
26 changes: 26 additions & 0 deletions hist/histv7/test/hist_variable.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ TEST(RVariableBinAxis, Constructor)
EXPECT_EQ(axis.GetNumNormalBins(), Bins);
EXPECT_EQ(axis.GetTotalNumBins(), Bins);
EXPECT_FALSE(axis.HasFlowBins());

EXPECT_THROW(RVariableBinAxis({}), std::invalid_argument);
EXPECT_THROW(RVariableBinAxis({0}), std::invalid_argument);
EXPECT_THROW(RVariableBinAxis({0, 0}), std::invalid_argument);
EXPECT_THROW(RVariableBinAxis({0, 1, 0}), std::invalid_argument);
EXPECT_THROW(RVariableBinAxis({0, 1, 1}), std::invalid_argument);
}

TEST(RVariableBinAxis, Equality)
Expand Down Expand Up @@ -95,6 +101,26 @@ TEST(RVariableBinAxis, ComputeLinearizedIndex)
EXPECT_TRUE(linIndex.fValid);
}

// Exactly on the bin edges
for (std::size_t i = 0; i < Bins; i++) {
auto linIndex = axis.ComputeLinearizedIndex(i);
EXPECT_EQ(linIndex.fIndex, i);
EXPECT_TRUE(linIndex.fValid);
linIndex = axisNoFlowBins.ComputeLinearizedIndex(i);
EXPECT_EQ(linIndex.fIndex, i);
EXPECT_TRUE(linIndex.fValid);
}

// Exactly the upper end of the axis interval
{
auto linIndex = axis.ComputeLinearizedIndex(Bins);
EXPECT_EQ(linIndex.fIndex, Bins + 1);
EXPECT_TRUE(linIndex.fValid);
linIndex = axisNoFlowBins.ComputeLinearizedIndex(Bins);
EXPECT_EQ(linIndex.fIndex, Bins + 1);
EXPECT_FALSE(linIndex.fValid);
}

// Overflow
static constexpr double PositiveInfinity = std::numeric_limits<double>::infinity();
static constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
Expand Down
Loading