Skip to content
Open
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
2 changes: 1 addition & 1 deletion hist/histv7/doc/CodeArchitecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Each instance has a local `RHistStats` object to avoid contention on the global

A single bin index, which is just an integer for normal bins.
`Underflow()` and `Overflow()` are special values and not ordered with respect to others.
Objects of this type are passed by value; most notably to `GetBinContent`.
Objects of this type are passed by value; most notably to `GetBinContent` and `SetBinContent`.

### `RBinIndexRange`

Expand Down
6 changes: 4 additions & 2 deletions hist/histv7/doc/DesignImplementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ Special arguments are passed last.
For example
```cpp
template <typename... A> void Fill(const std::tuple<A...> &args, RWeight w);
template <std::size_t N, typename V> void SetBinContent(const std::array<RBinIndex, N> &indices, const V &value);
```
The same works for the variadic function templates that will check the type of the last argument.
Note that we accept mandatory arguments with a template type as well to allow automatic conversion.

For profiles, we accept the value with a template type as well to allow automatic conversion to `double`, for example from `int`.
Variadic function templates receive all arguments in a single function parameter pack.
For optional arguments, the function will check the type of the last argument to determine if it was passed.

## Miscellaneous

Expand Down
58 changes: 58 additions & 0 deletions hist/histv7/inc/ROOT/RHist.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,64 @@ public:
return fEngine.GetBinContent(args...);
}

/// Set the content of a single bin.
///
/// \code
/// ROOT::Experimental::RHist<int> hist({/* two dimensions */});
/// std::array<ROOT::Experimental::RBinIndex, 2> indices = {3, 5};
/// int value = /* ... */;
/// hist.SetBinContent(indices, value);
/// \endcode
///
/// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
/// values. See also the class documentation of RBinIndex.
///
/// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
///
/// \warning Setting the bin content will taint the global histogram statistics. Attempting to access its values, for
/// example calling GetNEntries(), will throw exceptions.
///
/// \param[in] indices the array of indices for each axis
/// \param[in] value the new value of the bin content
/// \par See also
/// the \ref SetBinContent(const A &... args) const "variadic function template overload" accepting arguments
/// directly
template <std::size_t N, typename V>
void SetBinContent(const std::array<RBinIndex, N> &indices, const V &value)
{
fEngine.SetBinContent(indices, value);
fStats.Taint();
}

/// Set the content of a single bin.
///
/// \code
/// ROOT::Experimental::RHist<int> hist({/* two dimensions */});
/// int value = /* ... */;
/// hist.SetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5), value);
/// // ... or construct the RBinIndex arguments implicitly from integers:
/// hist.SetBinContent(3, 5, value);
/// \endcode
///
/// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
/// values. See also the class documentation of RBinIndex.
///
/// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
///
/// \warning Setting the bin content will taint the global histogram statistics. Attempting to access its values, for
/// example calling GetNEntries(), will throw exceptions.
///
/// \param[in] args the arguments for each axis and the new value of the bin content
/// \par See also
/// the \ref SetBinContent(const std::array<RBinIndex, N> &indices, const V &value) const "function overload"
/// accepting `std::array`
template <typename... A>
void SetBinContent(const A &...args)
{
fEngine.SetBinContent(args...);
fStats.Taint();
}

/// Add all bin contents and statistics of another histogram.
///
/// Throws an exception if the axes configurations are not identical.
Expand Down
97 changes: 72 additions & 25 deletions hist/histv7/inc/ROOT/RHistEngine.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ class TBuffer;
namespace ROOT {
namespace Experimental {

// forward declarations for friend declaration
template <typename BinContentType>
class RHistEngine;
namespace Internal {
template <typename T, std::size_t N>
static void SetBinContent(RHistEngine<T> &hist, const std::array<RBinIndex, N> &indices, const T &value);
} // namespace Internal

/**
A histogram data structure to bin data along multiple dimensions.

Expand Down Expand Up @@ -72,9 +64,6 @@ class RHistEngine final {
template <typename U>
friend class RHistEngine;

template <typename T, std::size_t N>
friend void Internal::SetBinContent(RHistEngine<T> &, const std::array<RBinIndex, N> &, const T &);

/// The axis configuration for this histogram. Relevant methods are forwarded from the public interface.
Internal::RAxes fAxes;
/// The bin contents for this histogram
Expand Down Expand Up @@ -208,6 +197,78 @@ public:
return GetBinContent(indices);
}

/// Set the content of a single bin.
///
/// \code
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
/// std::array<ROOT::Experimental::RBinIndex, 2> indices = {3, 5};
/// int value = /* ... */;
/// hist.SetBinContent(indices, value);
/// \endcode
///
/// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
/// values. See also the class documentation of RBinIndex.
///
/// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
///
/// \param[in] indices the array of indices for each axis
/// \param[in] value the new value of the bin content
/// \par See also
/// the \ref SetBinContent(const A &... args) const "variadic function template overload" accepting arguments
/// directly
template <std::size_t N, typename V>
void SetBinContent(const std::array<RBinIndex, N> &indices, const V &value)
{
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
// be confusing for users.
if (N != GetNDimensions()) {
throw std::invalid_argument("invalid number of indices passed to SetBinContent");
}
RLinearizedIndex index = fAxes.ComputeGlobalIndex(indices);
if (!index.fValid) {
throw std::invalid_argument("bin not found in SetBinContent");
}
assert(index.fIndex < fBinContents.size());
// To allow conversion, we have to accept value with a template type V to capture any argument. Otherwise it would
// select the variadic function template...
fBinContents[index.fIndex] = value;
}

private:
template <typename... A, std::size_t... I>
void SetBinContentImpl(const std::tuple<A...> &args, std::index_sequence<I...>)
{
std::array<RBinIndex, sizeof...(A) - 1> indices{std::get<I>(args)...};
SetBinContent(indices, std::get<sizeof...(A) - 1>(args));
}

public:
/// Set the content of a single bin.
///
/// \code
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
/// int value = /* ... */;
/// hist.SetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5), value);
/// // ... or construct the RBinIndex arguments implicitly from integers:
/// hist.SetBinContent(3, 5, value);
/// \endcode
///
/// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
/// values. See also the class documentation of RBinIndex.
///
/// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
///
/// \param[in] args the arguments for each axis and the new value of the bin content
/// \par See also
/// the \ref SetBinContent(const std::array<RBinIndex, N> &indices, const V &value) const "function overload"
/// accepting `std::array`
template <typename... A>
void SetBinContent(const A &...args)
{
auto t = std::forward_as_tuple(args...);
SetBinContentImpl(t, std::make_index_sequence<sizeof...(A) - 1>());
}

/// Add all bin contents of another histogram.
///
/// Throws an exception if the axes configurations are not identical.
Expand Down Expand Up @@ -543,20 +604,6 @@ public:
void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); }
};

namespace Internal {
/// %Internal function to set the content of a single bin.
template <typename T, std::size_t N>
static void SetBinContent(RHistEngine<T> &hist, const std::array<RBinIndex, N> &indices, const T &value)
{
RLinearizedIndex index = hist.fAxes.ComputeGlobalIndex(indices);
if (!index.fValid) {
throw std::invalid_argument("bin not found in SetBinContent");
}
assert(index.fIndex < hist.fBinContents.size());
hist.fBinContents[index.fIndex] = value;
}
} // namespace Internal

} // namespace Experimental
} // namespace ROOT

Expand Down
54 changes: 47 additions & 7 deletions hist/histv7/inc/ROOT/RHistStats.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ private:
double fSumW2 = 0.0;
/// The sums per dimension
std::vector<RDimensionStats> fDimensionStats;
/// Whether this object is tainted
bool fTainted = false;

void ThrowIfTainted() const
{
if (fTainted) {
throw std::logic_error("statistics are tainted");
}
}

public:
/// Construct a statistics object.
Expand All @@ -129,9 +138,21 @@ public:

std::size_t GetNDimensions() const { return fDimensionStats.size(); }

std::uint64_t GetNEntries() const { return fNEntries; }
double GetSumW() const { return fSumW; }
double GetSumW2() const { return fSumW2; }
std::uint64_t GetNEntries() const
{
ThrowIfTainted();
return fNEntries;
}
double GetSumW() const
{
ThrowIfTainted();
return fSumW;
}
double GetSumW2() const
{
ThrowIfTainted();
return fSumW2;
}

/// Get the statistics object for one dimension.
///
Expand All @@ -141,6 +162,8 @@ public:
/// \return the statistics object
const RDimensionStats &GetDimensionStats(std::size_t dim = 0) const
{
ThrowIfTainted();

const RDimensionStats &stats = fDimensionStats.at(dim);
if (!stats.fEnabled) {
throw std::invalid_argument("dimension is disabled");
Expand All @@ -157,13 +180,22 @@ public:

bool IsEnabled(std::size_t dim) const { return fDimensionStats.at(dim).fEnabled; }

/// Taint this statistics object.
///
/// It can still be filled, but any read access will throw until Clear() is called.
void Taint() { fTainted = true; }

bool IsTainted() const { return fTainted; }

/// Add all entries from another statistics object.
///
/// Throws an exception if the number of dimensions are not identical.
///
/// \param[in] other another statistics object
void Add(const RHistStats &other)
{
// NB: this method does *not* call ThrowIfTainted() to allow adding RHist which may contain a tainted statistics
// object.
if (fDimensionStats.size() != other.fDimensionStats.size()) {
throw std::invalid_argument("number of dimensions not identical in Add");
}
Expand All @@ -178,6 +210,7 @@ public:
fDimensionStats[i].Add(other.fDimensionStats[i]);
}
}
fTainted |= other.fTainted;
}

/// Add all entries from another statistics object using atomic instructions.
Expand All @@ -187,6 +220,8 @@ public:
/// \param[in] other another statistics object that must not be modified during the operation
void AddAtomic(const RHistStats &other)
{
// NB: this method does *not* call ThrowIfTainted() to allow adding RHist which may contain a tainted statistics
// object.
if (fDimensionStats.size() != other.fDimensionStats.size()) {
throw std::invalid_argument("number of dimensions not identical in Add");
}
Expand All @@ -201,6 +236,7 @@ public:
fDimensionStats[i].AddAtomic(other.fDimensionStats[i]);
}
}
fTainted |= other.fTainted;
}

/// Clear this statistics object.
Expand All @@ -212,6 +248,7 @@ public:
for (std::size_t i = 0; i < fDimensionStats.size(); i++) {
fDimensionStats[i].Clear();
}
fTainted = false;
}

/// Compute the number of effective entries.
Expand All @@ -223,6 +260,7 @@ public:
/// \return the number of effective entries
double ComputeNEffectiveEntries() const
{
ThrowIfTainted();
if (fSumW2 == 0) {
return std::numeric_limits<double>::signaling_NaN();
}
Expand All @@ -240,7 +278,7 @@ public:
double ComputeMean(std::size_t dim = 0) const
{
// First get the statistics, which includes checking the argument.
auto &stats = fDimensionStats.at(dim);
auto &stats = GetDimensionStats(dim);
if (fSumW == 0) {
return std::numeric_limits<double>::signaling_NaN();
}
Expand All @@ -266,7 +304,7 @@ public:
double ComputeVariance(std::size_t dim = 0) const
{
// First get the statistics, which includes checking the argument.
auto &stats = fDimensionStats.at(dim);
auto &stats = GetDimensionStats(dim);
if (fSumW == 0) {
return std::numeric_limits<double>::signaling_NaN();
}
Expand Down Expand Up @@ -310,7 +348,7 @@ public:
double ComputeSkewness(std::size_t dim = 0) const
{
// First get the statistics, which includes checking the argument.
auto &stats = fDimensionStats.at(dim);
auto &stats = GetDimensionStats(dim);
if (fSumW == 0) {
return std::numeric_limits<double>::signaling_NaN();
}
Expand Down Expand Up @@ -347,7 +385,7 @@ public:
double ComputeKurtosis(std::size_t dim = 0) const
{
// First get the statistics, which includes checking the argument.
auto &stats = fDimensionStats.at(dim);
auto &stats = GetDimensionStats(dim);
if (fSumW == 0) {
return std::numeric_limits<double>::signaling_NaN();
}
Expand Down Expand Up @@ -495,6 +533,8 @@ public:
/// \param[in] factor the scale factor
void Scale(double factor)
{
// NB: this method does *not* call ThrowIfTainted() to allow scaling RHist which may contain a tainted statistics
// object.
fSumW *= factor;
fSumW2 *= factor * factor;
for (std::size_t i = 0; i < fDimensionStats.size(); i++) {
Expand Down
Loading
Loading