diff --git a/hist/histv7/benchmark/CMakeLists.txt b/hist/histv7/benchmark/CMakeLists.txt index 31d2b07487bcf..f7cfa93eca030 100644 --- a/hist/histv7/benchmark/CMakeLists.txt +++ b/hist/histv7/benchmark/CMakeLists.txt @@ -1,5 +1,8 @@ add_executable(hist_benchmark_axes hist_benchmark_axes.cxx) target_link_libraries(hist_benchmark_axes ROOTHist benchmark::benchmark) +add_executable(hist_benchmark_engine hist_benchmark_engine.cxx) +target_link_libraries(hist_benchmark_engine ROOTHist benchmark::benchmark) + add_executable(hist_benchmark_regular hist_benchmark_regular.cxx) target_link_libraries(hist_benchmark_regular ROOTHist benchmark::benchmark) diff --git a/hist/histv7/benchmark/hist_benchmark_engine.cxx b/hist/histv7/benchmark/hist_benchmark_engine.cxx new file mode 100644 index 0000000000000..e4c060599eb76 --- /dev/null +++ b/hist/histv7/benchmark/hist_benchmark_engine.cxx @@ -0,0 +1,69 @@ +#include +#include + +#include + +#include +#include + +struct RHistEngine_int_Regular1 : public benchmark::Fixture { + // The objects are stored and constructed in the fixture to avoid compiler optimizations in the benchmark body taking + // advantage of the (constant) constructor parameters. + ROOT::Experimental::RRegularAxis axis{20, 0.0, 1.0}; + ROOT::Experimental::RHistEngine engine{{axis}}; + std::vector fNumbers; + + // Avoid GCC warning + using benchmark::Fixture::SetUp; + void SetUp(benchmark::State &state) final + { + std::mt19937 gen; + std::uniform_real_distribution<> dis; + fNumbers.resize(state.range(0)); + for (std::size_t i = 0; i < fNumbers.size(); i++) { + fNumbers[i] = dis(gen); + } + } +}; + +BENCHMARK_DEFINE_F(RHistEngine_int_Regular1, Fill)(benchmark::State &state) +{ + for (auto _ : state) { + for (double number : fNumbers) { + engine.Fill(number); + } + } +} +BENCHMARK_REGISTER_F(RHistEngine_int_Regular1, Fill)->Range(0, 32768); + +struct RHistEngine_int_Regular2 : public benchmark::Fixture { + // The objects are stored and constructed in the fixture to avoid compiler optimizations in the benchmark body taking + // advantage of the (constant) constructor parameters. + ROOT::Experimental::RRegularAxis axis{20, 0.0, 1.0}; + ROOT::Experimental::RHistEngine engine{{axis, axis}}; + std::vector fNumbers; + + // Avoid GCC warning + using benchmark::Fixture::SetUp; + void SetUp(benchmark::State &state) final + { + std::mt19937 gen; + std::uniform_real_distribution<> dis; + fNumbers.resize(2 * state.range(0)); + for (std::size_t i = 0; i < fNumbers.size(); i++) { + fNumbers[i] = dis(gen); + } + } +}; + +BENCHMARK_DEFINE_F(RHistEngine_int_Regular2, Fill)(benchmark::State &state) +{ + for (auto _ : state) { + for (std::size_t i = 0; i < fNumbers.size(); i += 2) { + engine.Fill(fNumbers[i], fNumbers[i + 1]); + } + } +} +BENCHMARK_REGISTER_F(RHistEngine_int_Regular2, Fill)->Range(0, 32768); + +BENCHMARK_MAIN(); diff --git a/hist/histv7/doc/DesignImplementation.md b/hist/histv7/doc/DesignImplementation.md index 7ccbffb7aaa06..f0e3407efb58d 100644 --- a/hist/histv7/doc/DesignImplementation.md +++ b/hist/histv7/doc/DesignImplementation.md @@ -4,7 +4,7 @@ This document describes key design decisions and implementation choices. ## Templating -Classes are only templated if required for data members, in particular the bin content type `T`. +Classes are only templated if required for data members, in particular the `BinContentType`. We use member function templates to accept variable number of arguments (see also below). Classes are **not** templated to improve performance, in particular not on the axis type(s). This avoids an explosion of types and simplifies serialization. @@ -28,7 +28,7 @@ Many member functions have two overloads: one accepting a function parameter pac ### Arguments with Different Types Functions that take arguments with different types expect a `std::tuple`. -An example is `template void Fill(const std::tuple &args)`. +An example is `template void Fill(const std::tuple &args)`. For user-convenience, a variadic function template forwards to the `std::tuple` overload: ```cpp @@ -41,13 +41,13 @@ This will forward the arguments as references, so no copy-constructors are calle ### Arguments with Same Type In this case, the function has a `std::size_t N` template argument and accepts a `std::array`. -An example is `template const T &GetBinContent(const std::array &args)` +An example is `template const BinContentType &GetBinContent(const std::array &indices)` For user-convenience, a variadic function template forwards to the `std::array` overload: ```cpp -template const T &GetBinContent(const A &...args) { - std::array a{args...}; - return GetBinContent(a); +template const BinContentType &GetBinContent(const A &...args) { + std::array indices{args...}; + return GetBinContent(indices); } ``` This will copy the arguments, which is fine in this case because `RBinIndex` is small (see below). @@ -58,7 +58,7 @@ Special arguments are passed last. Examples include ```cpp template void Fill(const std::tuple &args, RWeight w); -template void SetBinContent(const std::array &args, const T &content); +template void SetBinContent(const std::array &indices, const BinContentType &content); ``` The same works for the variadic function templates that will check the type of the last argument. diff --git a/hist/histv7/headers.cmake b/hist/histv7/headers.cmake index 20c4e80674198..2300e720482a6 100644 --- a/hist/histv7/headers.cmake +++ b/hist/histv7/headers.cmake @@ -2,6 +2,7 @@ set(histv7_headers ROOT/RAxes.hxx ROOT/RBinIndex.hxx ROOT/RBinIndexRange.hxx + ROOT/RHistEngine.hxx ROOT/RLinearizedIndex.hxx ROOT/RRegularAxis.hxx ROOT/RVariableBinAxis.hxx diff --git a/hist/histv7/inc/LinkDef.h b/hist/histv7/inc/LinkDef.h index 69adac7caf80e..ff41b88af1065 100644 --- a/hist/histv7/inc/LinkDef.h +++ b/hist/histv7/inc/LinkDef.h @@ -1,3 +1,11 @@ +// For RHistEngine, we request dictionaries for the most commonly used bin content types. This results in proper error +// messages when trying to stream. Other instantiations will be caught by the RAxes member. +#pragma link C++ class ROOT::Experimental::RHistEngine-; +#pragma link C++ class ROOT::Experimental::RHistEngine-; +#pragma link C++ class ROOT::Experimental::RHistEngine-; +#pragma link C++ class ROOT::Experimental::RHistEngine-; +#pragma link C++ class ROOT::Experimental::RHistEngine-; + #pragma link C++ class ROOT::Experimental::RRegularAxis-; #pragma link C++ class ROOT::Experimental::RVariableBinAxis-; #pragma link C++ class ROOT::Experimental::Internal::RAxes-; diff --git a/hist/histv7/inc/ROOT/RAxes.hxx b/hist/histv7/inc/ROOT/RAxes.hxx index 4c141ea874605..ffe5304b0f740 100644 --- a/hist/histv7/inc/ROOT/RAxes.hxx +++ b/hist/histv7/inc/ROOT/RAxes.hxx @@ -22,21 +22,21 @@ class TBuffer; namespace ROOT { namespace Experimental { + +/// Variant of all supported axis types. +using RAxisVariant = std::variant; + namespace Internal { /** Bin configurations for all dimensions of a histogram. */ class RAxes final { -public: - using AxisVariant = std::variant; - -private: - std::vector fAxes; + std::vector fAxes; public: /// \param[in] axes the axis objects, must have size > 0 - explicit RAxes(std::vector axes) : fAxes(std::move(axes)) + explicit RAxes(std::vector axes) : fAxes(std::move(axes)) { if (fAxes.empty()) { throw std::invalid_argument("must have at least 1 axis object"); @@ -44,7 +44,7 @@ public: } std::size_t GetNDimensions() const { return fAxes.size(); } - const std::vector &Get() const { return fAxes; } + const std::vector &Get() const { return fAxes; } friend bool operator==(const RAxes &lhs, const RAxes &rhs) { return lhs.fAxes == rhs.fAxes; } diff --git a/hist/histv7/inc/ROOT/RHistEngine.hxx b/hist/histv7/inc/ROOT/RHistEngine.hxx new file mode 100644 index 0000000000000..5f974ddd72308 --- /dev/null +++ b/hist/histv7/inc/ROOT/RHistEngine.hxx @@ -0,0 +1,212 @@ +/// \file +/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes. +/// Feedback is welcome! + +#ifndef ROOT_RHistEngine +#define ROOT_RHistEngine + +#include "RAxes.hxx" +#include "RBinIndex.hxx" +#include "RLinearizedIndex.hxx" +#include "RRegularAxis.hxx" + +#include +#include +#include +#include +#include +#include + +class TBuffer; + +namespace ROOT { +namespace Experimental { + +/** +A histogram data structure to bin data along multiple dimensions. + +Every call to \ref Fill(const A &... args) "Fill" bins the data according to the axis configuration and increments the +bin content: +\code +ROOT::Experimental::RHistEngine hist(10, 5, 15); +hist.Fill(8.5); +// hist.GetBinContent(ROOT::Experimental::RBinIndex(3)) will return 1 +\endcode + +The class is templated on the bin content type. For counting, as in the example above, it may be an integer type such as +`int` or `long`. Narrower types such as `unsigned char` or `short` are supported, but may overflow due to their limited +range and must be used with care. For weighted filling, the bin content type must be a floating-point type such as +`float` or `double`. Note that `float` has a limited significant precision of 24 bits. + +An object can have arbitrary dimensionality determined at run-time. The axis configuration is passed as a vector of +RAxisVariant: +\code +std::vector axes; +axes.push_back(ROOT::Experimental::RRegularAxis(10, 5, 15)); +axes.push_back(ROOT::Experimental::RVariableBinAxis({1, 10, 100, 1000})); +ROOT::Experimental::RHistEngine hist(axes); +// hist.GetNDimensions() will return 2 +\endcode + +\warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes. +Feedback is welcome! +*/ +template +class RHistEngine final { + /// The axis configuration for this histogram. Relevant methods are forwarded from the public interface. + Internal::RAxes fAxes; + /// The bin contents for this histogram + std::vector fBinContents; + +public: + /// Construct a histogram engine. + /// + /// \param[in] axes the axis objects, must have size > 0 + explicit RHistEngine(std::vector axes) : fAxes(std::move(axes)) + { + fBinContents.resize(fAxes.ComputeTotalNBins()); + } + + /// Construct a one-dimensional histogram engine with a regular axis. + /// + /// \param[in] nNormalBins 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), must be > low + /// \par See also + /// the \ref RRegularAxis::RRegularAxis(std::size_t nNormalBins, double low, double high, bool enableFlowBins) + /// "constructor of RRegularAxis" + RHistEngine(std::size_t nNormalBins, double low, double high) : RHistEngine({RRegularAxis(nNormalBins, low, high)}) + { + } + + // Copy constructor and assignment operator are deleted to avoid surprises. + RHistEngine(const RHistEngine &) = delete; + RHistEngine(RHistEngine &&) = default; + RHistEngine &operator=(const RHistEngine &) = delete; + RHistEngine &operator=(RHistEngine &&) = default; + ~RHistEngine() = default; + + const std::vector &GetAxes() const { return fAxes.Get(); } + std::size_t GetNDimensions() const { return fAxes.GetNDimensions(); } + std::size_t GetTotalNBins() const { return fBinContents.size(); } + + /// Get the content of a single bin. + /// + /// \code + /// ROOT::Experimental::RHistEngine hist({/* two dimensions */}); + /// std::array indices = {3, 5}; + /// int content = hist.GetBinContent(indices); + /// \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 + /// \return the bin content + /// \par See also + /// the \ref GetBinContent(const A &... args) const "variadic function template overload" accepting arguments + /// directly + template + const BinContentType &GetBinContent(const std::array &indices) const + { + // 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 GetBinContent"); + } + RLinearizedIndex index = fAxes.ComputeGlobalIndex(indices); + if (!index.fValid) { + throw std::invalid_argument("bin not found in GetBinContent"); + } + assert(index.fIndex < fBinContents.size()); + return fBinContents[index.fIndex]; + } + + /// Get the content of a single bin. + /// + /// \code + /// ROOT::Experimental::RHistEngine hist({/* two dimensions */}); + /// int content = hist.GetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5)); + /// // ... or construct the RBinIndex arguments implicitly from integers: + /// content = hist.GetBinContent(3, 5); + /// \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 + /// \return the bin content + /// \par See also + /// the \ref GetBinContent(const std::array &indices) const "function overload" accepting + /// `std::array` + template + const BinContentType &GetBinContent(const A &...args) const + { + std::array indices{args...}; + return GetBinContent(indices); + } + + /// Fill an entry into the histogram. + /// + /// \code + /// ROOT::Experimental::RHistEngine hist({/* two dimensions */}); + /// auto args = std::make_tuple(8.5, 10.5); + /// hist.Fill(args); + /// \endcode + /// + /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently + /// discarded. + /// + /// Throws an exception if the number of arguments does not match the axis configuration. + /// + /// \param[in] args the arguments for each axis + /// \par See also + /// the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly + template + void Fill(const std::tuple &args) + { + // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might + // be confusing for users. + if (sizeof...(A) != GetNDimensions()) { + throw std::invalid_argument("invalid number of arguments to Fill"); + } + RLinearizedIndex index = fAxes.ComputeGlobalIndex(args); + if (index.fValid) { + assert(index.fIndex < fBinContents.size()); + fBinContents[index.fIndex]++; + } + } + + /// Fill an entry into the histogram. + /// + /// \code + /// ROOT::Experimental::RHistEngine hist({/* two dimensions */}); + /// hist.Fill(8.5, 10.5); + /// \endcode + /// + /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently + /// discarded. + /// + /// Throws an exception if the number of arguments does not match the axis configuration. + /// + /// \param[in] args the arguments for each axis + /// \par See also + /// the \ref Fill(const std::tuple &args) "function overload" accepting `std::tuple` + template + void Fill(const A &...args) + { + Fill(std::forward_as_tuple(args...)); + } + + /// %ROOT Streamer function to throw when trying to store an object of this class. + void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); } +}; + +} // namespace Experimental +} // namespace ROOT + +#endif diff --git a/hist/histv7/test/CMakeLists.txt b/hist/histv7/test/CMakeLists.txt index a9d0ecfae1a5c..4f75b4f4f13b7 100644 --- a/hist/histv7/test/CMakeLists.txt +++ b/hist/histv7/test/CMakeLists.txt @@ -1,4 +1,5 @@ HIST_ADD_GTEST(hist_axes hist_axes.cxx) +HIST_ADD_GTEST(hist_engine hist_engine.cxx) HIST_ADD_GTEST(hist_index hist_index.cxx) HIST_ADD_GTEST(hist_regular hist_regular.cxx) HIST_ADD_GTEST(hist_variable hist_variable.cxx) diff --git a/hist/histv7/test/hist_axes.cxx b/hist/histv7/test/hist_axes.cxx index 122480c28648c..98b883b0d2a09 100644 --- a/hist/histv7/test/hist_axes.cxx +++ b/hist/histv7/test/hist_axes.cxx @@ -27,7 +27,7 @@ TEST(RAxes, Constructor) EXPECT_TRUE(std::get_if(&v[0]) != nullptr); EXPECT_TRUE(std::get_if(&v[1]) != nullptr); - std::vector newAxes{variableBinAxis, regularAxis}; + std::vector newAxes{variableBinAxis, regularAxis}; axes = RAxes(newAxes); EXPECT_EQ(axes.GetNDimensions(), 2); diff --git a/hist/histv7/test/hist_engine.cxx b/hist/histv7/test/hist_engine.cxx new file mode 100644 index 0000000000000..de5322f8b1092 --- /dev/null +++ b/hist/histv7/test/hist_engine.cxx @@ -0,0 +1,160 @@ +#include "hist_test.hxx" + +#include +#include +#include + +TEST(RHistEngine, Constructor) +{ + static constexpr std::size_t BinsX = 20; + const RRegularAxis regularAxis(BinsX, 0, BinsX); + static constexpr std::size_t BinsY = 30; + std::vector bins; + for (std::size_t i = 0; i < BinsY; i++) { + bins.push_back(i); + } + bins.push_back(BinsY); + const RVariableBinAxis variableBinAxis(bins); + + RHistEngine engine({regularAxis, variableBinAxis}); + EXPECT_EQ(engine.GetNDimensions(), 2); + const auto &axes = engine.GetAxes(); + ASSERT_EQ(axes.size(), 2); + EXPECT_EQ(axes[0].index(), 0); + EXPECT_EQ(axes[1].index(), 1); + EXPECT_TRUE(std::get_if(&axes[0]) != nullptr); + EXPECT_TRUE(std::get_if(&axes[1]) != nullptr); + + // Both axes include underflow and overflow bins. + EXPECT_EQ(engine.GetTotalNBins(), (BinsX + 2) * (BinsY + 2)); + + engine = RHistEngine(BinsX, 0, BinsX); + ASSERT_EQ(engine.GetNDimensions(), 1); + auto *regular = std::get_if(&engine.GetAxes()[0]); + ASSERT_TRUE(regular != nullptr); + EXPECT_EQ(regular->GetNNormalBins(), BinsX); + EXPECT_EQ(regular->GetLow(), 0); + EXPECT_EQ(regular->GetHigh(), BinsX); +} + +TEST(RHistEngine, GetBinContentInvalidNumberOfArguments) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + const RHistEngine engine1({axis}); + ASSERT_EQ(engine1.GetNDimensions(), 1); + const RHistEngine engine2({axis, axis}); + ASSERT_EQ(engine2.GetNDimensions(), 2); + + EXPECT_NO_THROW(engine1.GetBinContent(1)); + EXPECT_THROW(engine1.GetBinContent(1, 2), std::invalid_argument); + + EXPECT_THROW(engine2.GetBinContent(1), std::invalid_argument); + EXPECT_NO_THROW(engine2.GetBinContent(1, 2)); + EXPECT_THROW(engine2.GetBinContent(1, 2, 3), std::invalid_argument); +} + +TEST(RHistEngine, GetBinContentNotFound) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + const RHistEngine engine({axis}); + + EXPECT_THROW(engine.GetBinContent(Bins), std::invalid_argument); +} + +TEST(RHistEngine, Fill) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + RHistEngine engine({axis}); + + engine.Fill(-100); + for (std::size_t i = 0; i < Bins; i++) { + engine.Fill(i); + } + engine.Fill(100); + + EXPECT_EQ(engine.GetBinContent(RBinIndex::Underflow()), 1); + for (auto index : axis.GetNormalRange()) { + EXPECT_EQ(engine.GetBinContent(index), 1); + } + EXPECT_EQ(engine.GetBinContent(RBinIndex::Overflow()), 1); +} + +TEST(RHistEngine, FillDiscard) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins, /*enableFlowBins=*/false); + RHistEngine engine({axis}); + + engine.Fill(-100); + for (std::size_t i = 0; i < Bins; i++) { + engine.Fill(i); + } + engine.Fill(100); + + for (auto index : axis.GetNormalRange()) { + EXPECT_EQ(engine.GetBinContent(index), 1); + } +} + +TEST(RHistEngine, FillOnlyInner) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + RHistEngine engine({axis}); + const RRegularAxis axisNoFlowBins(Bins, 0, Bins, /*enableFlowBins=*/false); + RHistEngine engineNoFlowBins({axisNoFlowBins}); + + for (std::size_t i = 0; i < Bins; i++) { + engine.Fill(i); + engineNoFlowBins.Fill(i); + } + + EXPECT_EQ(engine.GetBinContent(RBinIndex::Underflow()), 0); + for (auto index : axis.GetNormalRange()) { + EXPECT_EQ(engine.GetBinContent(index), 1); + EXPECT_EQ(engineNoFlowBins.GetBinContent(index), 1); + } + EXPECT_EQ(engine.GetBinContent(RBinIndex::Overflow()), 0); +} + +TEST(RHistEngine, FillTuple) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + RHistEngine engine({axis}); + + engine.Fill(std::make_tuple(-100)); + for (std::size_t i = 0; i < Bins; i++) { + engine.Fill(std::make_tuple(i)); + } + engine.Fill(std::make_tuple(100)); + + std::array indices = {RBinIndex::Underflow()}; + EXPECT_EQ(engine.GetBinContent(indices), 1); + for (auto index : axis.GetNormalRange()) { + indices[0] = index; + EXPECT_EQ(engine.GetBinContent(indices), 1); + } + indices[0] = RBinIndex::Overflow(); + EXPECT_EQ(engine.GetBinContent(indices), 1); +} + +TEST(RHistEngine, FillInvalidNumberOfArguments) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + RHistEngine engine1({axis}); + ASSERT_EQ(engine1.GetNDimensions(), 1); + RHistEngine engine2({axis, axis}); + ASSERT_EQ(engine2.GetNDimensions(), 2); + + EXPECT_NO_THROW(engine1.Fill(1)); + EXPECT_THROW(engine1.Fill(1, 2), std::invalid_argument); + + EXPECT_THROW(engine2.Fill(1), std::invalid_argument); + EXPECT_NO_THROW(engine2.Fill(1, 2)); + EXPECT_THROW(engine2.Fill(1, 2, 3), std::invalid_argument); +} diff --git a/hist/histv7/test/hist_io.cxx b/hist/histv7/test/hist_io.cxx index 63faea579f41a..8e225e2dee5af 100644 --- a/hist/histv7/test/hist_io.cxx +++ b/hist/histv7/test/hist_io.cxx @@ -50,3 +50,29 @@ TEST(RAxes, Streamer) const RAxes axes({regularAxis, variableBinAxis}); ExpectThrowOnWriteObject(axes); } + +TEST(RHistEngine, Streamer) +{ + static constexpr std::size_t Bins = 20; + const RRegularAxis axis(Bins, 0, Bins); + + // We don't request a dictionary for RHistEngine, and we generally don't recommend such narrow bin + // content types. If used, the RAxes member will prevent streaming. + const RHistEngine engineC({axis}); + ExpectThrowOnWriteObject(engineC); + + const RHistEngine engineI({axis}); + ExpectThrowOnWriteObject(engineI); + + const RHistEngine engineL({axis}); + ExpectThrowOnWriteObject(engineL); + + const RHistEngine engineLL({axis}); + ExpectThrowOnWriteObject(engineLL); + + const RHistEngine engineF({axis}); + ExpectThrowOnWriteObject(engineF); + + const RHistEngine engineD({axis}); + ExpectThrowOnWriteObject(engineD); +} diff --git a/hist/histv7/test/hist_test.hxx b/hist/histv7/test/hist_test.hxx index 494dee904b61b..e7778fe615058 100644 --- a/hist/histv7/test/hist_test.hxx +++ b/hist/histv7/test/hist_test.hxx @@ -4,13 +4,16 @@ #include #include #include +#include #include #include #include "gtest/gtest.h" +using ROOT::Experimental::RAxisVariant; using ROOT::Experimental::RBinIndex; using ROOT::Experimental::RBinIndexRange; +using ROOT::Experimental::RHistEngine; using ROOT::Experimental::RRegularAxis; using ROOT::Experimental::RVariableBinAxis; using ROOT::Experimental::Internal::RAxes;