From ea2c381a1d929bac650b9cd9a727ff5e1146419f Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Fri, 1 Nov 2019 09:13:17 -0700 Subject: [PATCH 01/20] Allow direct memory access, no longer throws error/segfault --- include/boost/histogram/python/histogram.hpp | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/include/boost/histogram/python/histogram.hpp b/include/boost/histogram/python/histogram.hpp index a84219ede..21c25a15d 100644 --- a/include/boost/histogram/python/histogram.hpp +++ b/include/boost/histogram/python/histogram.hpp @@ -13,10 +13,54 @@ #include namespace pybind11 { + +/// The descriptor for atomic_* is the same as the descriptor for *, as long this uses +/// standard layout template struct format_descriptor> : format_descriptor { static_assert(std::is_standard_layout>::value, ""); }; + +/// This descriptor depends on the memory format for mean remaining unchanged +template <> +struct format_descriptor> { + static std::string format() { + return std::string("T{" + "d:sum_:" + "d:mean_:" + "d:sum_of_deltas_squared_:}"); + } +}; + +// If made public, could be: +// PYBIND11_NUMPY_DTYPE(mean, sum_, mean_, sum_of_deltas_squared_); + +/// This descriptor depends on the memory format for weighted_mean remaining +/// unchanged +template <> +struct format_descriptor> { + static std::string format() { + return std::string("T{" + "d:sum_of_weights_:" + "d:sum_of_weights_squared_:" + "d:weighted_mean_:" + "d:sum_of_weighted_deltas_squared_:" + "}"); + } +}; + +/// This descriptor depends on the memory format for weighted_sum remaining +/// unchanged +template <> +struct format_descriptor> { + static std::string format() { + return std::string("T{" + "d:sum_of_weights_:" + "d:sum_of_weights_squared_:" + "}"); + } +}; + } // namespace pybind11 namespace detail { From c1227ac3ab9dcee7347fa337b1a15fc46546c1bd Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 6 Nov 2019 11:40:22 +1030 Subject: [PATCH 02/20] Internalize accumulators --- .../histogram/python/accumulators/mean.hpp | 128 ++++++++++++++++++ .../python/accumulators/weighted_mean.hpp | 124 +++++++++++++++++ .../python/accumulators/weighted_sum.hpp | 117 ++++++++++++++++ .../histogram/python/accumulators_ostream.hpp | 13 +- include/boost/histogram/python/storage.hpp | 12 +- src/register_accumulators.cpp | 12 +- 6 files changed, 392 insertions(+), 14 deletions(-) create mode 100644 include/boost/histogram/python/accumulators/mean.hpp create mode 100644 include/boost/histogram/python/accumulators/weighted_mean.hpp create mode 100644 include/boost/histogram/python/accumulators/weighted_sum.hpp diff --git a/include/boost/histogram/python/accumulators/mean.hpp b/include/boost/histogram/python/accumulators/mean.hpp new file mode 100644 index 000000000..c4e1c8002 --- /dev/null +++ b/include/boost/histogram/python/accumulators/mean.hpp @@ -0,0 +1,128 @@ +// Copyright 2015-2019 Hans Dembinski and Henry Schreiner +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Based on boost/histogram/accumulators/mean.hpp + +#pragma once + +#include +#include +#include // for mean<> +#include +#include +#include + +namespace boost { +namespace histogram { +namespace python { + +/** Calculates mean and variance of sample. + + Uses Welfords's incremental algorithm to improve the numerical + stability of mean and variance computation. +*/ +template +class mean { + public: + mean() = default; + mean(const RealType &n, const RealType &mean, const RealType &variance) noexcept + : sum_(n) + , mean_(mean) + , sum_of_deltas_squared_(variance * (n - 1)) {} + + void operator()(const RealType &x) noexcept { + sum_ += static_cast(1); + const auto delta = x - mean_; + mean_ += delta / sum_; + sum_of_deltas_squared_ += delta * (x - mean_); + } + + void operator()(const weight_type &w, const RealType &x) noexcept { + sum_ += w.value; + const auto delta = x - mean_; + mean_ += w.value * delta / sum_; + sum_of_deltas_squared_ += w.value * delta * (x - mean_); + } + + template + mean &operator+=(const mean &rhs) noexcept { + if(sum_ != 0 || rhs.sum_ != 0) { + const auto tmp = mean_ * sum_ + static_cast(rhs.mean_ * rhs.sum_); + sum_ += rhs.sum_; + mean_ = tmp / sum_; + } + sum_of_deltas_squared_ += static_cast(rhs.sum_of_deltas_squared_); + return *this; + } + + mean &operator*=(const RealType &s) noexcept { + mean_ *= s; + sum_of_deltas_squared_ *= s * s; + return *this; + } + + template + bool operator==(const mean &rhs) const noexcept { + return sum_ == rhs.sum_ && mean_ == rhs.mean_ + && sum_of_deltas_squared_ == rhs.sum_of_deltas_squared_; + } + + template + bool operator!=(const mean &rhs) const noexcept { + return !operator==(rhs); + } + + const RealType &count() const noexcept { return sum_; } + const RealType &value() const noexcept { return mean_; } + RealType variance() const noexcept { return sum_of_deltas_squared_ / (sum_ - 1); } + + template + void serialize(Archive &ar, unsigned version) { + if(version == 0) { + // read only + std::size_t sum; + ar &make_nvp("sum", sum); + sum_ = static_cast(sum); + } else { + ar &make_nvp("sum", sum_); + } + ar &make_nvp("mean", mean_); + ar &make_nvp("sum_of_deltas_squared", sum_of_deltas_squared_); + } + + private: + RealType sum_ = 0, mean_ = 0, sum_of_deltas_squared_ = 0; +}; + +} // namespace python +} // namespace histogram +} // namespace boost + +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED + +namespace boost { +namespace serialization { + +template +struct version; + +// version 1 for boost::histogram::accumulators::mean +template +struct version> : std::integral_constant {}; + +} // namespace serialization +} // namespace boost + +namespace std { +template +/// Specialization for boost::histogram::accumulators::mean. +struct common_type, + boost::histogram::accumulators::mean> { + using type = boost::histogram::accumulators::mean>; +}; +} // namespace std + +#endif diff --git a/include/boost/histogram/python/accumulators/weighted_mean.hpp b/include/boost/histogram/python/accumulators/weighted_mean.hpp new file mode 100644 index 000000000..65ca602eb --- /dev/null +++ b/include/boost/histogram/python/accumulators/weighted_mean.hpp @@ -0,0 +1,124 @@ +// Copyright 2018-2019 Hans Dembinski and Henry Schreiner +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Based on boost/histogram/accumulators/weighted_mean.hpp + +#pragma once + +#include +#include +#include // for weighted_mean<> +#include +#include + +namespace boost { +namespace histogram { +namespace python { + +/** + Calculates mean and variance of weighted sample. + + Uses West's incremental algorithm to improve numerical stability + of mean and variance computation. +*/ +template +class weighted_mean { + public: + weighted_mean() = default; + weighted_mean(const RealType &wsum, + const RealType &wsum2, + const RealType &mean, + const RealType &variance) + : sum_of_weights_(wsum) + , sum_of_weights_squared_(wsum2) + , weighted_mean_(mean) + , sum_of_weighted_deltas_squared_( + variance + * (sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_)) {} + + void operator()(const RealType &x) { operator()(weight(1), x); } + + void operator()(const weight_type &w, const RealType &x) { + sum_of_weights_ += w.value; + sum_of_weights_squared_ += w.value * w.value; + const auto delta = x - weighted_mean_; + weighted_mean_ += w.value * delta / sum_of_weights_; + sum_of_weighted_deltas_squared_ += w.value * delta * (x - weighted_mean_); + } + + template + weighted_mean &operator+=(const weighted_mean &rhs) { + if(sum_of_weights_ != 0 || rhs.sum_of_weights_ != 0) { + const auto tmp + = weighted_mean_ * sum_of_weights_ + + static_cast(rhs.weighted_mean_ * rhs.sum_of_weights_); + sum_of_weights_ += static_cast(rhs.sum_of_weights_); + sum_of_weights_squared_ + += static_cast(rhs.sum_of_weights_squared_); + weighted_mean_ = tmp / sum_of_weights_; + } + sum_of_weighted_deltas_squared_ + += static_cast(rhs.sum_of_weighted_deltas_squared_); + return *this; + } + + weighted_mean &operator*=(const RealType &s) { + weighted_mean_ *= s; + sum_of_weighted_deltas_squared_ *= s * s; + return *this; + } + + template + bool operator==(const weighted_mean &rhs) const noexcept { + return sum_of_weights_ == rhs.sum_of_weights_ + && sum_of_weights_squared_ == rhs.sum_of_weights_squared_ + && weighted_mean_ == rhs.weighted_mean_ + && sum_of_weighted_deltas_squared_ + == rhs.sum_of_weighted_deltas_squared_; + } + + template + bool operator!=(const T &rhs) const noexcept { + return !operator==(rhs); + } + + const RealType &sum_of_weights() const noexcept { return sum_of_weights_; } + const RealType &sum_of_weights_squared() const noexcept { + return sum_of_weights_squared_; + } + const RealType &value() const noexcept { return weighted_mean_; } + RealType variance() const { + return sum_of_weighted_deltas_squared_ + / (sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_); + } + + template + void serialize(Archive &ar, unsigned /* version */) { + ar &make_nvp("sum_of_weights", sum_of_weights_); + ar &make_nvp("sum_of_weights_squared", sum_of_weights_squared_); + ar &make_nvp("weighted_mean", weighted_mean_); + ar &make_nvp("sum_of_weighted_deltas_squared", sum_of_weighted_deltas_squared_); + } + + private: + RealType sum_of_weights_ = RealType(), sum_of_weights_squared_ = RealType(), + weighted_mean_ = RealType(), sum_of_weighted_deltas_squared_ = RealType(); +}; + +} // namespace python +} // namespace histogram +} // namespace boost + +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED +namespace std { +template +/// Specialization for boost::histogram::accumulators::weighted_mean. +struct common_type, + boost::histogram::accumulators::weighted_mean> { + using type = boost::histogram::accumulators::weighted_mean>; +}; +} // namespace std +#endif diff --git a/include/boost/histogram/python/accumulators/weighted_sum.hpp b/include/boost/histogram/python/accumulators/weighted_sum.hpp new file mode 100644 index 000000000..58c19c35f --- /dev/null +++ b/include/boost/histogram/python/accumulators/weighted_sum.hpp @@ -0,0 +1,117 @@ +// Copyright 2015-2019 Hans Dembinski and Henry Schreiner +// +// Distributed under the Boost Software License, version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Based on boost/histogram/accumulators/weighted_mean.hpp + +#pragma once + +#include +#include // for weighted_sum<> +#include + +namespace boost { +namespace histogram { +namespace python { + +/// Holds sum of weights and its variance estimate +template +class weighted_sum { + public: + weighted_sum() = default; + explicit weighted_sum(const RealType &value) noexcept + : sum_of_weights_(value) + , sum_of_weights_squared_(value) {} + weighted_sum(const RealType &value, const RealType &variance) noexcept + : sum_of_weights_(value) + , sum_of_weights_squared_(variance) {} + + /// Increment by one. + weighted_sum &operator++() { return operator+=(1); } + + /// Increment by value. + template + weighted_sum &operator+=(const T &value) { + sum_of_weights_ += value; + sum_of_weights_squared_ += value * value; + return *this; + } + + /// Added another weighted sum. + template + weighted_sum &operator+=(const weighted_sum &rhs) { + sum_of_weights_ += static_cast(rhs.sum_of_weights_); + sum_of_weights_squared_ += static_cast(rhs.sum_of_weights_squared_); + return *this; + } + + /// Scale by value. + weighted_sum &operator*=(const RealType &x) { + sum_of_weights_ *= x; + sum_of_weights_squared_ *= x * x; + return *this; + } + + bool operator==(const RealType &rhs) const noexcept { + return sum_of_weights_ == rhs && sum_of_weights_squared_ == rhs; + } + + template + bool operator==(const weighted_sum &rhs) const noexcept { + return sum_of_weights_ == rhs.sum_of_weights_ + && sum_of_weights_squared_ == rhs.sum_of_weights_squared_; + } + + template + bool operator!=(const T &rhs) const noexcept { + return !operator==(rhs); + } + + /// Return value of the sum. + const RealType &value() const noexcept { return sum_of_weights_; } + + /// Return estimated variance of the sum. + const RealType &variance() const noexcept { return sum_of_weights_squared_; } + + // lossy conversion must be explicit + template + explicit operator T() const { + return static_cast(sum_of_weights_); + } + + template + void serialize(Archive &ar, unsigned /* version */) { + ar &make_nvp("sum_of_weights", sum_of_weights_); + ar &make_nvp("sum_of_weights_squared", sum_of_weights_squared_); + } + + private: + RealType sum_of_weights_ = RealType(); + RealType sum_of_weights_squared_ = RealType(); +}; + +} // namespace python +} // namespace histogram +} // namespace boost + +#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED +namespace std { +template +struct common_type, + boost::histogram::accumulators::weighted_sum> { + using type = boost::histogram::accumulators::weighted_sum>; +}; + +template +struct common_type, U> { + using type = boost::histogram::accumulators::weighted_sum>; +}; + +template +struct common_type> { + using type = boost::histogram::accumulators::weighted_sum>; +}; +} // namespace std +#endif diff --git a/include/boost/histogram/python/accumulators_ostream.hpp b/include/boost/histogram/python/accumulators_ostream.hpp index b7856d869..1beee322c 100644 --- a/include/boost/histogram/python/accumulators_ostream.hpp +++ b/include/boost/histogram/python/accumulators_ostream.hpp @@ -8,6 +8,10 @@ #pragma once +#include +#include +#include + #include #include #include @@ -40,10 +44,11 @@ handle_nonzero_width(std::basic_ostream &os, const T &x) { } // namespace detail -namespace accumulators { +namespace python { + template std::basic_ostream &operator<<(std::basic_ostream &os, - const sum &x) { + const accumulators::sum &x) { if(os.width() == 0) return os << "sum(" << x.large() << " + " << x.small() << ")"; return detail::handle_nonzero_width(os, x); @@ -79,10 +84,10 @@ std::basic_ostream &operator<<(std::basic_ostream template std::basic_ostream &operator<<(std::basic_ostream &os, - const thread_safe &x) { + const accumulators::thread_safe &x) { os << x.load(); return os; } -} // namespace accumulators +} // namespace python } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/python/storage.hpp b/include/boost/histogram/python/storage.hpp index 647a5adbd..2a38cc116 100644 --- a/include/boost/histogram/python/storage.hpp +++ b/include/boost/histogram/python/storage.hpp @@ -7,7 +7,11 @@ #include -#include +#include +#include +#include +#include +#include #include #include @@ -20,9 +24,9 @@ using int_ = bh::dense_storage; using atomic_int = bh::dense_storage>; using double_ = bh::dense_storage; using unlimited = bh::unlimited_storage<>; -using weight = bh::weight_storage; -using mean = bh::profile_storage; -using weighted_mean = bh::weighted_profile_storage; +using weight = bh::dense_storage>; +using mean = bh::dense_storage>; +using weighted_mean = bh::dense_storage>; // Allow repr to show python name template diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index 7f138dab3..ddeb5a6c3 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -5,10 +5,10 @@ #include -#include #include -#include -#include +#include +#include +#include #include #include #include @@ -52,7 +52,7 @@ decltype(auto) make_mean_call() { } void register_accumulators(py::module &accumulators) { - using weighted_sum = bh::accumulators::weighted_sum; + using weighted_sum = bh::python::weighted_sum; register_accumulator(accumulators, "weighted_sum") @@ -115,7 +115,7 @@ void register_accumulators(py::module &accumulators) { ; - using weighted_mean = bh::accumulators::weighted_mean; + using weighted_mean = bh::python::weighted_mean; register_accumulator(accumulators, "weighted_mean") .def(py::init(), @@ -140,7 +140,7 @@ void register_accumulators(py::module &accumulators) { ; - using mean = bh::accumulators::mean; + using mean = bh::python::mean; register_accumulator(accumulators, "mean") .def(py::init(), From 3c9b2af6344725399561c65fd79d188aba2e2f85 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Thu, 7 Nov 2019 00:56:00 +1030 Subject: [PATCH 03/20] First working version of conversion --- boost_histogram/_internal/hist.py | 5 +- boost_histogram/_internal/view.py | 92 +++++++++++++++++++ .../histogram/python/accumulators/mean.hpp | 11 ++- .../python/accumulators/weighted_mean.hpp | 13 ++- .../python/accumulators/weighted_sum.hpp | 3 +- include/boost/histogram/python/histogram.hpp | 44 +-------- src/register_accumulators.cpp | 24 +++++ 7 files changed, 144 insertions(+), 48 deletions(-) create mode 100644 boost_histogram/_internal/view.py diff --git a/boost_histogram/_internal/hist.py b/boost_histogram/_internal/hist.py index 669063b06..37984dc73 100644 --- a/boost_histogram/_internal/hist.py +++ b/boost_histogram/_internal/hist.py @@ -4,6 +4,7 @@ from .. import _core from .axis import _to_axis, Axis +from .view import _to_view from .axistuple import AxesTuple from .sig_tools import inject_signature from .storage import Double @@ -135,7 +136,7 @@ def __repr__(self): return self.__class__.__name__ + repr(self._hist)[9:] def __array__(self): - return np.asarray(self._hist) + return _to_view(np.asarray(self._hist)) # TODO: .view does not seem to return an editable view # so we have to use the buffer interface here @@ -296,7 +297,7 @@ def view(self, flow=False): """ Return a view into the data, optionally with overflow turned on. """ - return self._hist.view(flow) + return _to_view(self._hist.view(flow)) def reset(self): """ diff --git a/boost_histogram/_internal/view.py b/boost_histogram/_internal/view.py new file mode 100644 index 000000000..224f3fac9 --- /dev/null +++ b/boost_histogram/_internal/view.py @@ -0,0 +1,92 @@ +from __future__ import absolute_import, division, print_function + +from ..accumulators import Mean, WeightedMean, WeightedSum + +import numpy as np + + +class View(np.ndarray): + __slots__ = () + + def __getitem__(self, ind): + sliced = super(View, self).__getitem__(ind) + if sliced.shape: + return sliced + else: + return self._PARENT._make(*sliced) + + def __repr__(self): + return repr(self.view(np.ndarray)) + + def __str__(self): + return str(self.view(np.ndarray)) + + +class MeanView(View): + __slots__ = () + _PARENT = Mean + _FIELDS = ("sum_", "mean_", "sum_of_deltas_squared_") + + @property + def count(self): + return self["mean_"] + + @property + def value(self): + return self["sum_"] + + # Variance is a computation + @property + def variance(self): + return self["sum_of_deltas_squared_"] / (self["sum_"] - 1) + + +class WeightedSumView(View): + __slots__ = () + _PARENT = WeightedSum + _FIELDS = ("sum_of_weights_", "sum_of_weights_squared_") + + @property + def sum_of_weights(self): + return self["sum_of_weights_"] + + @property + def sum_of_weights_squared(self): + return self["sum_of_weights_squared_"] + + +class WeightedMeanView(View): + __slots__ = () + _PARENT = WeightedMean + _FIELDS = ( + "sum_of_weights_", + "sum_of_weights_squared_", + "weighted_mean_", + "sum_of_weighted_deltas_squared_", + ) + + @property + def sum_of_weights(self): + return self["sum_of_weights_"] + + @property + def sum_of_weights_squared(self): + return self["sum_of_weights_squared_"] + + @property + def value(self): + return self["weighted_mean_"] + + @property + def variance(self): + return self["sum_of_weighted_deltas_squared_"] / ( + self["sum_of_weights_"] + - self["sum_of_weights_squared_"] / self["sum_of_weights_"] + ) + + +def _to_view(item): + for cls in View.__subclasses__(): + if cls._FIELDS == item.dtype.fields: + return item.view(cls) + return item diff --git a/include/boost/histogram/python/accumulators/mean.hpp b/include/boost/histogram/python/accumulators/mean.hpp index c4e1c8002..2dccb7b6f 100644 --- a/include/boost/histogram/python/accumulators/mean.hpp +++ b/include/boost/histogram/python/accumulators/mean.hpp @@ -25,7 +25,7 @@ namespace python { stability of mean and variance computation. */ template -class mean { +struct mean { public: mean() = default; mean(const RealType &n, const RealType &mean, const RealType &variance) noexcept @@ -33,6 +33,14 @@ class mean { , mean_(mean) , sum_of_deltas_squared_(variance * (n - 1)) {} + mean(const RealType &sum, + const RealType &mean, + const RealType &sum_of_deltas_squared, + bool /* Tag to trigger python internal constructor */) + : sum_(sum) + , mean_(mean) + , sum_of_deltas_squared_(sum_of_deltas_squared) {} + void operator()(const RealType &x) noexcept { sum_ += static_cast(1); const auto delta = x - mean_; @@ -93,7 +101,6 @@ class mean { ar &make_nvp("sum_of_deltas_squared", sum_of_deltas_squared_); } - private: RealType sum_ = 0, mean_ = 0, sum_of_deltas_squared_ = 0; }; diff --git a/include/boost/histogram/python/accumulators/weighted_mean.hpp b/include/boost/histogram/python/accumulators/weighted_mean.hpp index 65ca602eb..81a4a93ca 100644 --- a/include/boost/histogram/python/accumulators/weighted_mean.hpp +++ b/include/boost/histogram/python/accumulators/weighted_mean.hpp @@ -25,7 +25,7 @@ namespace python { of mean and variance computation. */ template -class weighted_mean { +struct weighted_mean { public: weighted_mean() = default; weighted_mean(const RealType &wsum, @@ -39,6 +39,16 @@ class weighted_mean { variance * (sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_)) {} + weighted_mean(const RealType &wsum, + const RealType &wsum2, + const RealType &mean, + const RealType &sum_of_weighted_deltas_squared, + bool) + : sum_of_weights_(wsum) + , sum_of_weights_squared_(wsum2) + , weighted_mean_(mean) + , sum_of_weighted_deltas_squared_(sum_of_weighted_deltas_squared) {} + void operator()(const RealType &x) { operator()(weight(1), x); } void operator()(const weight_type &w, const RealType &x) { @@ -103,7 +113,6 @@ class weighted_mean { ar &make_nvp("sum_of_weighted_deltas_squared", sum_of_weighted_deltas_squared_); } - private: RealType sum_of_weights_ = RealType(), sum_of_weights_squared_ = RealType(), weighted_mean_ = RealType(), sum_of_weighted_deltas_squared_ = RealType(); }; diff --git a/include/boost/histogram/python/accumulators/weighted_sum.hpp b/include/boost/histogram/python/accumulators/weighted_sum.hpp index 58c19c35f..cef96ad16 100644 --- a/include/boost/histogram/python/accumulators/weighted_sum.hpp +++ b/include/boost/histogram/python/accumulators/weighted_sum.hpp @@ -18,7 +18,7 @@ namespace python { /// Holds sum of weights and its variance estimate template -class weighted_sum { +struct weighted_sum { public: weighted_sum() = default; explicit weighted_sum(const RealType &value) noexcept @@ -87,7 +87,6 @@ class weighted_sum { ar &make_nvp("sum_of_weights_squared", sum_of_weights_squared_); } - private: RealType sum_of_weights_ = RealType(); RealType sum_of_weights_squared_ = RealType(); }; diff --git a/include/boost/histogram/python/histogram.hpp b/include/boost/histogram/python/histogram.hpp index 21c25a15d..cba1699bc 100644 --- a/include/boost/histogram/python/histogram.hpp +++ b/include/boost/histogram/python/histogram.hpp @@ -7,6 +7,10 @@ #include +#include +#include +#include + #include #include #include @@ -21,46 +25,6 @@ struct format_descriptor> : format_descriptor>::value, ""); }; -/// This descriptor depends on the memory format for mean remaining unchanged -template <> -struct format_descriptor> { - static std::string format() { - return std::string("T{" - "d:sum_:" - "d:mean_:" - "d:sum_of_deltas_squared_:}"); - } -}; - -// If made public, could be: -// PYBIND11_NUMPY_DTYPE(mean, sum_, mean_, sum_of_deltas_squared_); - -/// This descriptor depends on the memory format for weighted_mean remaining -/// unchanged -template <> -struct format_descriptor> { - static std::string format() { - return std::string("T{" - "d:sum_of_weights_:" - "d:sum_of_weights_squared_:" - "d:weighted_mean_:" - "d:sum_of_weighted_deltas_squared_:" - "}"); - } -}; - -/// This descriptor depends on the memory format for weighted_sum remaining -/// unchanged -template <> -struct format_descriptor> { - static std::string format() { - return std::string("T{" - "d:sum_of_weights_:" - "d:sum_of_weights_squared_:" - "}"); - } -}; - } // namespace pybind11 namespace detail { diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index ddeb5a6c3..957e0ea3c 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -53,6 +53,7 @@ decltype(auto) make_mean_call() { void register_accumulators(py::module &accumulators) { using weighted_sum = bh::python::weighted_sum; + PYBIND11_NUMPY_DTYPE(weighted_sum, sum_of_weights_, sum_of_weights_squared_); register_accumulator(accumulators, "weighted_sum") @@ -85,6 +86,10 @@ void register_accumulators(py::module &accumulators) { "value"_a, "Fill the accumulator with values. Optional variance parameter.") + .def_static("_make", py::vectorize([](const double &a, const double &b) { + return weighted_sum(a, b); + })) + ; using sum = bh::accumulators::sum; @@ -116,6 +121,11 @@ void register_accumulators(py::module &accumulators) { ; using weighted_mean = bh::python::weighted_mean; + PYBIND11_NUMPY_DTYPE(weighted_mean, + sum_of_weights_, + sum_of_weights_squared_, + weighted_mean_, + sum_of_weighted_deltas_squared_); register_accumulator(accumulators, "weighted_mean") .def(py::init(), @@ -138,9 +148,17 @@ void register_accumulators(py::module &accumulators) { "value"_a, "Fill the accumulator with values. Optional weight parameter.") + .def_static( + "_make", + py::vectorize( + [](const double &a, const double &b, const double &c, double &d) { + return weighted_mean(a, b, c, d, true); + })) + ; using mean = bh::python::mean; + PYBIND11_NUMPY_DTYPE(mean, sum_, mean_, sum_of_deltas_squared_); register_accumulator(accumulators, "mean") .def(py::init(), @@ -162,5 +180,11 @@ void register_accumulators(py::module &accumulators) { "value"_a, "Fill the accumulator with values. Optional weight parameter.") + .def_static( + "_make", + py::vectorize([](const double &a, const double &b, const double &c) { + return mean(a, b, c, true); + })) + ; } From 8dc503cd0c2a927616496cbf4fba37acb086117b Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Thu, 7 Nov 2019 17:41:45 +1030 Subject: [PATCH 04/20] Fix several bugs, and change the meaning of np.asarray() slightly --- boost_histogram/_internal/hist.py | 4 ++-- boost_histogram/_internal/view.py | 14 +++++++++++--- tests/test_public_hist.py | 1 - 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/boost_histogram/_internal/hist.py b/boost_histogram/_internal/hist.py index 37984dc73..496539a82 100644 --- a/boost_histogram/_internal/hist.py +++ b/boost_histogram/_internal/hist.py @@ -136,7 +136,7 @@ def __repr__(self): return self.__class__.__name__ + repr(self._hist)[9:] def __array__(self): - return _to_view(np.asarray(self._hist)) + return _to_view(np.asarray(self._hist), value=True) # TODO: .view does not seem to return an editable view # so we have to use the buffer interface here @@ -407,5 +407,5 @@ def __getitem__(self, index): ) def __setitem__(self, index, value): - indexes = _compute_commonindex(self._hist, index, expand=True) + indexes = _compute_commonindex(self._hist, index, value) self._hist._at_set(value, *indexes) diff --git a/boost_histogram/_internal/view.py b/boost_histogram/_internal/view.py index 224f3fac9..2b8d3c73d 100644 --- a/boost_histogram/_internal/view.py +++ b/boost_histogram/_internal/view.py @@ -50,6 +50,10 @@ class WeightedSumView(View): def sum_of_weights(self): return self["sum_of_weights_"] + @property + def value(self): + return self["sum_of_weights_"] + @property def sum_of_weights_squared(self): return self["sum_of_weights_squared_"] @@ -85,8 +89,12 @@ def variance(self): ) -def _to_view(item): +def _to_view(item, value=False): for cls in View.__subclasses__(): - if cls._FIELDS == item.dtype.fields: - return item.view(cls) + if cls._FIELDS == item.dtype.names: + ret = item.view(cls) + if value and ret.shape: + return ret.value + else: + return ret return item diff --git a/tests/test_public_hist.py b/tests/test_public_hist.py index ef9847d2e..2bbcecf70 100644 --- a/tests/test_public_hist.py +++ b/tests/test_public_hist.py @@ -136,7 +136,6 @@ def test_fill_1d(flow): assert get(h, bh.overflow) == 1 -# TODO: atomic_int not supported @pytest.mark.parametrize("storage", [bh.storage.Int, bh.storage.Double]) def test_setting(storage): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=storage()) From f1e6c898204efd4a1b6f3ac8c548ba4cd894ba30 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 11 Nov 2019 12:46:39 -0500 Subject: [PATCH 05/20] Fix view not returning an editable view --- boost_histogram/_internal/hist.py | 4 +--- include/boost/histogram/python/register_histogram.hpp | 9 ++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/boost_histogram/_internal/hist.py b/boost_histogram/_internal/hist.py index 496539a82..79f6fa2b3 100644 --- a/boost_histogram/_internal/hist.py +++ b/boost_histogram/_internal/hist.py @@ -136,9 +136,7 @@ def __repr__(self): return self.__class__.__name__ + repr(self._hist)[9:] def __array__(self): - return _to_view(np.asarray(self._hist), value=True) - # TODO: .view does not seem to return an editable view - # so we have to use the buffer interface here + return np.asarray(self._hist) def __add__(self, other): return self.__class__(self._hist + other._hist) diff --git a/include/boost/histogram/python/register_histogram.hpp b/include/boost/histogram/python/register_histogram.hpp index eb89e49bd..7b50a982e 100644 --- a/include/boost/histogram/python/register_histogram.hpp +++ b/include/boost/histogram/python/register_histogram.hpp @@ -148,7 +148,14 @@ register_histogram(py::module &m, const char *name, const char *desc) { .def( "view", - [](histogram_t &h, bool flow) { return py::array(make_buffer(h, flow)); }, + [](py::object self, bool flow) { + auto &h = py::cast(self); + auto info = make_buffer(h, flow); + return py::array( + pybind11::dtype(info), info.shape, info.strides, info.ptr, self); + // Note that, due to the missing signature py::array(info, self), we + // have to write this out fully here. TODO: Make PR to PyBind11 + }, "flow"_a = false) .def( From 3e55a80dc7038b342229b5af5602aecfee366143 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 11 Nov 2019 14:44:40 -0500 Subject: [PATCH 06/20] Normalize names --- boost_histogram/_internal/view.py | 78 ++++++++++--------- .../histogram/python/accumulators/mean.hpp | 3 + .../python/accumulators/weighted_mean.hpp | 4 + .../python/accumulators/weighted_sum.hpp | 4 + src/register_accumulators.cpp | 32 ++++++-- 5 files changed, 77 insertions(+), 44 deletions(-) diff --git a/boost_histogram/_internal/view.py b/boost_histogram/_internal/view.py index 2b8d3c73d..699658ec5 100644 --- a/boost_histogram/_internal/view.py +++ b/boost_histogram/_internal/view.py @@ -22,70 +22,51 @@ def __str__(self): return str(self.view(np.ndarray)) -class MeanView(View): - __slots__ = () - _PARENT = Mean - _FIELDS = ("sum_", "mean_", "sum_of_deltas_squared_") - - @property - def count(self): - return self["mean_"] - - @property - def value(self): - return self["sum_"] - - # Variance is a computation - @property - def variance(self): - return self["sum_of_deltas_squared_"] / (self["sum_"] - 1) - - class WeightedSumView(View): __slots__ = () _PARENT = WeightedSum - _FIELDS = ("sum_of_weights_", "sum_of_weights_squared_") - - @property - def sum_of_weights(self): - return self["sum_of_weights_"] + _FIELDS = ("value", "variance") @property def value(self): - return self["sum_of_weights_"] + return self["value"] @property - def sum_of_weights_squared(self): - return self["sum_of_weights_squared_"] + def variance(self): + return self["variance"] class WeightedMeanView(View): __slots__ = () _PARENT = WeightedMean _FIELDS = ( - "sum_of_weights_", - "sum_of_weights_squared_", - "weighted_mean_", - "sum_of_weighted_deltas_squared_", + "sum_of_weights", + "sum_of_weights_squared", + "value", + "sum_of_weighted_deltas_squared", ) @property def sum_of_weights(self): - return self["sum_of_weights_"] + return self["sum_of_weights"] @property def sum_of_weights_squared(self): - return self["sum_of_weights_squared_"] + return self["sum_of_weights_squared"] @property def value(self): - return self["weighted_mean_"] + return self["value"] + + @property + def sum_of_weighted_deltas_squared(self): + return self["sum_of_weighted_deltas_squared"] @property def variance(self): - return self["sum_of_weighted_deltas_squared_"] / ( - self["sum_of_weights_"] - - self["sum_of_weights_squared_"] / self["sum_of_weights_"] + return self["sum_of_weighted_deltas_squared"] / ( + self["sum_of_weights"] + - self["sum_of_weights_squared"] / self["sum_of_weights"] ) @@ -98,3 +79,26 @@ def _to_view(item, value=False): else: return ret return item + + +class MeanView(View): + __slots__ = () + _PARENT = Mean + _FIELDS = ("count", "value", "sum_of_deltas_squared") + + @property + def count(self): + return self["count"] + + @property + def value(self): + return self["value"] + + @property + def sum_of_deltas_squared(self): + return self["sum_of_deltas_squared"] + + # Variance is a computation + @property + def variance(self): + return self["sum_of_deltas_squared"] / (self["count"] - 1) diff --git a/include/boost/histogram/python/accumulators/mean.hpp b/include/boost/histogram/python/accumulators/mean.hpp index 2dccb7b6f..a0ebfd449 100644 --- a/include/boost/histogram/python/accumulators/mean.hpp +++ b/include/boost/histogram/python/accumulators/mean.hpp @@ -5,6 +5,9 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // // Based on boost/histogram/accumulators/mean.hpp +// Changes: +// * Internal values are public for access from Python +// * A special constructor added for construction from Python #pragma once diff --git a/include/boost/histogram/python/accumulators/weighted_mean.hpp b/include/boost/histogram/python/accumulators/weighted_mean.hpp index 81a4a93ca..922d99e28 100644 --- a/include/boost/histogram/python/accumulators/weighted_mean.hpp +++ b/include/boost/histogram/python/accumulators/weighted_mean.hpp @@ -5,6 +5,10 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // // Based on boost/histogram/accumulators/weighted_mean.hpp +// +// Changes: +// * Internal values are public for access from Python +// * A special constructor added for construction from Python #pragma once diff --git a/include/boost/histogram/python/accumulators/weighted_sum.hpp b/include/boost/histogram/python/accumulators/weighted_sum.hpp index cef96ad16..e5df8c70d 100644 --- a/include/boost/histogram/python/accumulators/weighted_sum.hpp +++ b/include/boost/histogram/python/accumulators/weighted_sum.hpp @@ -5,6 +5,10 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // // Based on boost/histogram/accumulators/weighted_mean.hpp +// +// Changes: +// * Internal values are public for access from Python +// * A special constructor added for construction from Python #pragma once diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index 957e0ea3c..f8dca2acd 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -52,8 +52,16 @@ decltype(auto) make_mean_call() { } void register_accumulators(py::module &accumulators) { + // Naming convention: + // If a value is publically available in Boost.Histogram accumulators + // as a method, it has the same name in the numpy record array. + // If it is not available except through a computation, it has + // the same name as the private property without the trailing _. + using weighted_sum = bh::python::weighted_sum; - PYBIND11_NUMPY_DTYPE(weighted_sum, sum_of_weights_, sum_of_weights_squared_); + + PYBIND11_NUMPY_DTYPE_EX( + weighted_sum, sum_of_weights_, "value", sum_of_weights_squared_, "variance"); register_accumulator(accumulators, "weighted_sum") @@ -121,11 +129,15 @@ void register_accumulators(py::module &accumulators) { ; using weighted_mean = bh::python::weighted_mean; - PYBIND11_NUMPY_DTYPE(weighted_mean, - sum_of_weights_, - sum_of_weights_squared_, - weighted_mean_, - sum_of_weighted_deltas_squared_); + PYBIND11_NUMPY_DTYPE_EX(weighted_mean, + sum_of_weights_, + "sum_of_weights", + sum_of_weights_squared_, + "sum_of_weights_squared", + weighted_mean_, + "value", + sum_of_weighted_deltas_squared_, + "sum_of_weighted_deltas_squared"); register_accumulator(accumulators, "weighted_mean") .def(py::init(), @@ -158,7 +170,13 @@ void register_accumulators(py::module &accumulators) { ; using mean = bh::python::mean; - PYBIND11_NUMPY_DTYPE(mean, sum_, mean_, sum_of_deltas_squared_); + PYBIND11_NUMPY_DTYPE_EX(mean, + sum_, + "count", + mean_, + "value", + sum_of_deltas_squared_, + "sum_of_deltas_squared"); register_accumulator(accumulators, "mean") .def(py::init(), From f1dd6a6d5a6f856362be69b448d86656999e7288 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 11 Nov 2019 15:01:04 -0500 Subject: [PATCH 07/20] Nicer repr and str for views --- boost_histogram/_internal/view.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/boost_histogram/_internal/view.py b/boost_histogram/_internal/view.py index 699658ec5..031dd6002 100644 --- a/boost_histogram/_internal/view.py +++ b/boost_histogram/_internal/view.py @@ -16,10 +16,18 @@ def __getitem__(self, ind): return self._PARENT._make(*sliced) def __repr__(self): - return repr(self.view(np.ndarray)) + # Numpy starts the ndarray class name with "array", so we replace it + # with our class name + return ( + "{self.__class__.__name__}(\n ".format(self=self) + + repr(self.view(np.ndarray))[6:] + ) def __str__(self): - return str(self.view(np.ndarray)) + fields = ", ".join(self._FIELDS) + return "{self.__class__.__name__}: ({fields})\n{arr}".format( + self=self, fields=fields, arr=self.view(np.ndarray) + ) class WeightedSumView(View): From b1f154b5ebcf49bff301ef3745e7bf8eeee0b2db Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 11 Nov 2019 15:59:23 -0500 Subject: [PATCH 08/20] Cleanup views with a decorator --- boost_histogram/_internal/view.py | 122 ++++++++++++++++-------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/boost_histogram/_internal/view.py b/boost_histogram/_internal/view.py index 031dd6002..1f768c79c 100644 --- a/boost_histogram/_internal/view.py +++ b/boost_histogram/_internal/view.py @@ -10,10 +10,16 @@ class View(np.ndarray): def __getitem__(self, ind): sliced = super(View, self).__getitem__(ind) - if sliced.shape: - return sliced - else: + + # If the shape is empty, return the parent type + if not sliced.shape: return self._PARENT._make(*sliced) + # If the dtype has changed, return a normal array (no longer a record) + elif sliced.dtype != self.dtype: + return np.asarray(sliced) + # Otherwise, no change, return the same View type + else: + return sliced def __repr__(self): # Numpy starts the ndarray class name with "array", so we replace it @@ -30,45 +36,59 @@ def __str__(self): ) +def fields(*names): + """ + This decorator adds the name to the _FIELDS + class property (for printing in reprs), and + adds a property that looks like this: + + @property + def name(self): + return self["name"] + @name.setter + def name(self, value): + self["name"] = value + """ + + def injector(cls): + if hasattr(cls, "_FIELDS"): + raise RuntimeError( + "{0} already has had a fields decorator applied".format( + self.__class__.__name__ + ) + ) + fields = [] + for name in names: + fields.append(name) + + def fget(self): + return self[name] + + def fset(self, value): + self[name] = value + + setattr(cls, name, property(fget, fset)) + cls._FIELDS = tuple(fields) + return cls + + return injector + + +@fields("value", "variance") class WeightedSumView(View): __slots__ = () _PARENT = WeightedSum - _FIELDS = ("value", "variance") - - @property - def value(self): - return self["value"] - - @property - def variance(self): - return self["variance"] +@fields( + "sum_of_weights", + "sum_of_weights_squared", + "value", + "sum_of_weighted_deltas_squared", +) class WeightedMeanView(View): __slots__ = () _PARENT = WeightedMean - _FIELDS = ( - "sum_of_weights", - "sum_of_weights_squared", - "value", - "sum_of_weighted_deltas_squared", - ) - - @property - def sum_of_weights(self): - return self["sum_of_weights"] - - @property - def sum_of_weights_squared(self): - return self["sum_of_weights_squared"] - - @property - def value(self): - return self["value"] - - @property - def sum_of_weighted_deltas_squared(self): - return self["sum_of_weighted_deltas_squared"] @property def variance(self): @@ -78,6 +98,17 @@ def variance(self): ) +@fields("count", "value", "sum_of_deltas_squared") +class MeanView(View): + __slots__ = () + _PARENT = Mean + + # Variance is a computation + @property + def variance(self): + return self["sum_of_deltas_squared"] / (self["count"] - 1) + + def _to_view(item, value=False): for cls in View.__subclasses__(): if cls._FIELDS == item.dtype.names: @@ -87,26 +118,3 @@ def _to_view(item, value=False): else: return ret return item - - -class MeanView(View): - __slots__ = () - _PARENT = Mean - _FIELDS = ("count", "value", "sum_of_deltas_squared") - - @property - def count(self): - return self["count"] - - @property - def value(self): - return self["value"] - - @property - def sum_of_deltas_squared(self): - return self["sum_of_deltas_squared"] - - # Variance is a computation - @property - def variance(self): - return self["sum_of_deltas_squared"] / (self["count"] - 1) From d61118ab3231bf89eb1874000d0467903af22977 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Mon, 11 Nov 2019 21:02:20 -0500 Subject: [PATCH 09/20] Some small polish --- boost_histogram/_internal/hist.py | 2 +- tests/test_public_hist.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/boost_histogram/_internal/hist.py b/boost_histogram/_internal/hist.py index 79f6fa2b3..ac7266f8b 100644 --- a/boost_histogram/_internal/hist.py +++ b/boost_histogram/_internal/hist.py @@ -136,7 +136,7 @@ def __repr__(self): return self.__class__.__name__ + repr(self._hist)[9:] def __array__(self): - return np.asarray(self._hist) + return self.view() def __add__(self, other): return self.__class__(self._hist + other._hist) diff --git a/tests/test_public_hist.py b/tests/test_public_hist.py index 2bbcecf70..0d19699bf 100644 --- a/tests/test_public_hist.py +++ b/tests/test_public_hist.py @@ -136,6 +136,7 @@ def test_fill_1d(flow): assert get(h, bh.overflow) == 1 +# TODO: atomic_int, unlimited not supported @pytest.mark.parametrize("storage", [bh.storage.Int, bh.storage.Double]) def test_setting(storage): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=storage()) From e198e25f9c20c7a8cd3696c01e9220b68a569760 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 12 Nov 2019 06:08:22 -0500 Subject: [PATCH 10/20] Fix #195, unlimited and atomic single access --- .../histogram/python/register_histogram.hpp | 2 +- src/register_histograms.cpp | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/include/boost/histogram/python/register_histogram.hpp b/include/boost/histogram/python/register_histogram.hpp index 7b50a982e..e0f783b83 100644 --- a/include/boost/histogram/python/register_histogram.hpp +++ b/include/boost/histogram/python/register_histogram.hpp @@ -172,7 +172,7 @@ register_histogram(py::module &m, const char *name, const char *desc) { py::return_value_policy::move) .def("at", - [](const histogram_t &self, py::args &args) { + [](const histogram_t &self, py::args &args) -> value_type { auto int_args = py::cast>(args); return self.at(int_args); }) diff --git a/src/register_histograms.cpp b/src/register_histograms.cpp index 255d2cfe2..cc290adb8 100644 --- a/src/register_histograms.cpp +++ b/src/register_histograms.cpp @@ -67,6 +67,33 @@ inline void copy_in<>(bh::histogram +struct type_caster { + public: + PYBIND11_TYPE_CASTER(storage::atomic_int::value_type, _("atomic_int")); + + bool load(handle src, bool) { + PyObject *source = src.ptr(); + PyObject *tmp = PyNumber_Long(source); + if(!tmp) + return false; + value.store(PyLong_AsUnsignedLongLong(tmp)); + Py_DECREF(tmp); + return !PyErr_Occurred(); + } + + static handle cast(storage::atomic_int::value_type src, + return_value_policy /* policy */, + handle /* parent */) { + return PyLong_FromUnsignedLongLong(src.load()); + } +}; +} // namespace detail +} // namespace pybind11 + void register_histograms(py::module &hist) { hist.attr("_axes_limit") = BOOST_HISTOGRAM_DETAIL_AXES_LIMIT; From 29808b0f8dfac70814a5ec8ca05fab8a4f09a1c6 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 12 Nov 2019 07:00:40 -0500 Subject: [PATCH 11/20] Adding tests for storages --- tests/test_storage.py | 103 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/test_storage.py diff --git a/tests/test_storage.py b/tests/test_storage.py new file mode 100644 index 000000000..f3dadd06b --- /dev/null +++ b/tests/test_storage.py @@ -0,0 +1,103 @@ +import pytest +import boost_histogram as bh +import numpy as np +from numpy.testing import assert_array_equal + + +@pytest.mark.parametrize( + "storage", + [bh.storage.Int, bh.storage.Double, bh.storage.AtomicInt, bh.storage.Unlimited], +) +def test_setting(storage): + h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=storage()) + h[bh.underflow] = 1 + h[0] = 2 + h[1] = 3 + h[bh.loc(0.55)] = 4 + h[-1] = 5 + h[bh.overflow] = 6 + + assert h[bh.underflow] == 1 + assert h[0] == 2 + assert h[1] == 3 + assert h[bh.loc(0.55)] == 4 + assert h[5] == 4 + assert h[-1] == 5 + assert h[9] == 5 + assert h[bh.overflow] == 6 + + assert_array_equal(h.view(flow=True), [1, 2, 3, 0, 0, 0, 4, 0, 0, 0, 5, 6]) + + +def test_setting_weight(): + h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.Weight()) + + h.fill([0.3, 0.3, 0.4, 1.2]) + + assert h[0] == bh.accumulators.WeightedSum(3, 3) + assert h[1] == bh.accumulators.WeightedSum(1, 1) + + h[0] = bh.accumulators.WeightedSum(value=2, variance=2) + assert h[0] == bh.accumulators.WeightedSum(2, 2) + + a = h.view() + + assert a[0] == h[0] + + b = np.asarray(h) + assert b["value"][0] == h[0].value + + h[0] = bh.accumulators.WeightedSum(value=3, variance=1) + + assert a[0] == h[0] + assert b["value"][0] == h[0].value + + +def test_setting_profile(): + h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.Mean()) + + h.fill([0.3, 0.3, 0.4, 1.2, 1.6], sample=[1, 2, 3, 4, 4]) + + assert h[0] == bh.accumulators.Mean(count=3, value=2, variance=1) + assert h[1] == bh.accumulators.Mean(count=2, value=4, variance=0) + + h[0] = bh.accumulators.Mean(count=12, value=11, variance=10) + assert h[0] == bh.accumulators.Mean(count=12, value=11, variance=10) + + a = h.view() + + assert a[0] == h[0] + + b = np.asarray(h) + assert b["value"][0] == h[0].value + + h[0] = bh.accumulators.Mean(count=6, value=3, variance=2) + + assert a[0] == h[0] + assert b["value"][0] == h[0].value + + +def test_setting_weighted_profile(): + h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.WeightedMean()) + + h.fill([0.3, 0.3, 0.4, 1.2, 1.6], sample=[1, 2, 3, 4, 4], weight=[1, 1, 1, 1, 2]) + + assert h[0] == bh.accumulators.WeightedMean(wsum=3, wsum2=3, value=2, variance=1) + assert h[1] == bh.accumulators.WeightedMean(wsum=3, wsum2=5, value=4, variance=0) + + h[0] = bh.accumulators.WeightedMean(wsum=12, wsum2=15, value=11, variance=10) + assert h[0] == bh.accumulators.WeightedMean( + wsum=12, wsum2=15, value=11, variance=10 + ) + + a = h.view() + + assert a[0] == h[0] + + b = np.asarray(h) + assert b["value"][0] == h[0].value + + h[0] = bh.accumulators.WeightedMean(wsum=6, wsum2=12, value=3, variance=2) + + assert a[0] == h[0] + assert b["value"][0] == h[0].value From eab4378deb2a659660c783e17d310129e349cd07 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 12 Nov 2019 07:02:39 -0500 Subject: [PATCH 12/20] README update --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ef375aa1d..d04bea968 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Python bindings for [Boost::Histogram][] ([source][Boost::Histogram source]), a > Join the [discussion on gitter][gitter-link] or [open an issue](https://github.com/scikit-hep/boost-histogram/issues)! > > #### Known issues (develop): -> * Non-simple storages do not support `.view()` or the buffer interface; you can access and set one element at a time. > * Setting with an array is not yet supported (`h[...] = np.array(...)`). From 09c6ec0e5a3d9df53d2ebfa261d06309e643a0c0 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 12 Nov 2019 07:26:17 -0500 Subject: [PATCH 13/20] CHANGELOG and renable a test --- CHANGELOG.md | 5 +++++ tests/test_public_hist.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116a162f8..4fad97bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### User changes * Histogram and Axis classes now follow PEP 8 naming scheme (`histogram`->`Histogram`, `regular`->`Regular`, etc.) [#192][] +* You can now view a histogram with accumulators, with property access such as `h.view().value` [#194][] * Added axes transforms [#192][] * You can now sum over a range with endpoints [#185][] * You can now access the functional regular axis directly, `regular_sqrt` becomes `regular.sqrt`, etc.. [#183][] @@ -12,6 +13,10 @@ * Signatures are much nicer in Python 3 [#188][] +#### Bug fixes: +* Unlimited and AtomicInt storages now allow single item access [#194][] +* `.view()` now no longer makes a copy [#194][] + #### Developer changes * The hist/axis classes are now pure Python, with a C++ object inside [#183][] diff --git a/tests/test_public_hist.py b/tests/test_public_hist.py index 0d19699bf..8ed1b4061 100644 --- a/tests/test_public_hist.py +++ b/tests/test_public_hist.py @@ -136,8 +136,10 @@ def test_fill_1d(flow): assert get(h, bh.overflow) == 1 -# TODO: atomic_int, unlimited not supported -@pytest.mark.parametrize("storage", [bh.storage.Int, bh.storage.Double]) +@pytest.mark.parametrize( + "storage", + [bh.storage.Int, bh.storage.Double, bh.storage.Unlimited, bh.storage.AtomicInt], +) def test_setting(storage): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=storage()) h[bh.underflow] = 1 From c88b1a2690cdbd7e175f4b2c52c529012f084b4f Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 12 Nov 2019 08:04:18 -0500 Subject: [PATCH 14/20] Fix unneeded change --- boost_histogram/_internal/hist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boost_histogram/_internal/hist.py b/boost_histogram/_internal/hist.py index ac7266f8b..2cc0116a0 100644 --- a/boost_histogram/_internal/hist.py +++ b/boost_histogram/_internal/hist.py @@ -405,5 +405,5 @@ def __getitem__(self, index): ) def __setitem__(self, index, value): - indexes = _compute_commonindex(self._hist, index, value) + indexes = _compute_commonindex(self._hist, index, expand=False) self._hist._at_set(value, *indexes) From f0ca2e7873bfa6f11e9ac43003e3afbef752707d Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 12 Nov 2019 14:01:59 -0500 Subject: [PATCH 15/20] Address review comments --- boost_histogram/_internal/hist.py | 8 +- .../histogram/python/accumulators/mean.hpp | 108 ++++++----------- .../python/accumulators/weighted_mean.hpp | 112 +++++++----------- .../python/accumulators/weighted_sum.hpp | 74 ++++-------- .../histogram/python/accumulators_ostream.hpp | 48 ++++---- .../histogram/python/register_accumulator.hpp | 7 +- include/boost/histogram/python/storage.hpp | 6 +- src/register_accumulators.cpp | 49 ++++---- 8 files changed, 159 insertions(+), 253 deletions(-) diff --git a/boost_histogram/_internal/hist.py b/boost_histogram/_internal/hist.py index 2cc0116a0..6984a0e09 100644 --- a/boost_histogram/_internal/hist.py +++ b/boost_histogram/_internal/hist.py @@ -54,13 +54,13 @@ def _expand_ellipsis(indexes, rank): raise IndexError("an index can only have a single ellipsis ('...')") -def _compute_commonindex(hist, index, expand): +def _compute_commonindex(hist, index, expand_ellipsis): # Normalize -> h[i] == h[i,] if not isinstance(index, tuple): index = (index,) # Now a list - if expand: + if expand_ellipsis: indexes = _expand_ellipsis(index, hist.rank()) else: indexes = list(index) @@ -333,7 +333,7 @@ def size(self): def __getitem__(self, index): - indexes = _compute_commonindex(self._hist, index, expand=True) + indexes = _compute_commonindex(self._hist, index, expand_ellipsis=True) # If this is (now) all integers, return the bin contents try: @@ -405,5 +405,5 @@ def __getitem__(self, index): ) def __setitem__(self, index, value): - indexes = _compute_commonindex(self._hist, index, expand=False) + indexes = _compute_commonindex(self._hist, index, expand_ellipsis=False) self._hist._at_set(value, *indexes) diff --git a/include/boost/histogram/python/accumulators/mean.hpp b/include/boost/histogram/python/accumulators/mean.hpp index a0ebfd449..787193032 100644 --- a/include/boost/histogram/python/accumulators/mean.hpp +++ b/include/boost/histogram/python/accumulators/mean.hpp @@ -13,14 +13,12 @@ #include #include -#include // for mean<> +#include #include #include #include -namespace boost { -namespace histogram { -namespace python { +namespace accumulators { /** Calculates mean and variance of sample. @@ -29,56 +27,57 @@ namespace python { */ template struct mean { - public: mean() = default; mean(const RealType &n, const RealType &mean, const RealType &variance) noexcept - : sum_(n) - , mean_(mean) - , sum_of_deltas_squared_(variance * (n - 1)) {} + : count(n) + , value(mean) + , sum_of_deltas_squared(variance * (n - 1)) {} mean(const RealType &sum, const RealType &mean, const RealType &sum_of_deltas_squared, bool /* Tag to trigger python internal constructor */) - : sum_(sum) - , mean_(mean) - , sum_of_deltas_squared_(sum_of_deltas_squared) {} + : count(sum) + , value(mean) + , sum_of_deltas_squared(sum_of_deltas_squared) {} void operator()(const RealType &x) noexcept { - sum_ += static_cast(1); - const auto delta = x - mean_; - mean_ += delta / sum_; - sum_of_deltas_squared_ += delta * (x - mean_); + count += static_cast(1); + const auto delta = x - value; + value += delta / count; + sum_of_deltas_squared += delta * (x - value); } - void operator()(const weight_type &w, const RealType &x) noexcept { - sum_ += w.value; - const auto delta = x - mean_; - mean_ += w.value * delta / sum_; - sum_of_deltas_squared_ += w.value * delta * (x - mean_); + void operator()(const boost::histogram::weight_type &w, + const RealType &x) noexcept { + count += w.value; + const auto delta = x - value; + value += w.value * delta / count; + sum_of_deltas_squared += w.value * delta * (x - value); } template mean &operator+=(const mean &rhs) noexcept { - if(sum_ != 0 || rhs.sum_ != 0) { - const auto tmp = mean_ * sum_ + static_cast(rhs.mean_ * rhs.sum_); - sum_ += rhs.sum_; - mean_ = tmp / sum_; + if(count != 0 || rhs.count != 0) { + const auto tmp + = value * count + static_cast(rhs.value * rhs.count); + count += rhs.count; + value = tmp / count; } - sum_of_deltas_squared_ += static_cast(rhs.sum_of_deltas_squared_); + sum_of_deltas_squared += static_cast(rhs.sum_of_deltas_squared); return *this; } mean &operator*=(const RealType &s) noexcept { - mean_ *= s; - sum_of_deltas_squared_ *= s * s; + value *= s; + sum_of_deltas_squared *= s * s; return *this; } template bool operator==(const mean &rhs) const noexcept { - return sum_ == rhs.sum_ && mean_ == rhs.mean_ - && sum_of_deltas_squared_ == rhs.sum_of_deltas_squared_; + return count == rhs.count && value == rhs.value + && sum_of_deltas_squared == rhs.sum_of_deltas_squared; } template @@ -86,53 +85,16 @@ struct mean { return !operator==(rhs); } - const RealType &count() const noexcept { return sum_; } - const RealType &value() const noexcept { return mean_; } - RealType variance() const noexcept { return sum_of_deltas_squared_ / (sum_ - 1); } + RealType variance() const noexcept { return sum_of_deltas_squared / (count - 1); } template - void serialize(Archive &ar, unsigned version) { - if(version == 0) { - // read only - std::size_t sum; - ar &make_nvp("sum", sum); - sum_ = static_cast(sum); - } else { - ar &make_nvp("sum", sum_); - } - ar &make_nvp("mean", mean_); - ar &make_nvp("sum_of_deltas_squared", sum_of_deltas_squared_); + void serialize(Archive &ar, unsigned) { + ar &boost::make_nvp("count", count); + ar &boost::make_nvp("value", value); + ar &boost::make_nvp("sum_of_deltas_squared", sum_of_deltas_squared); } - RealType sum_ = 0, mean_ = 0, sum_of_deltas_squared_ = 0; -}; - -} // namespace python -} // namespace histogram -} // namespace boost - -#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED - -namespace boost { -namespace serialization { - -template -struct version; - -// version 1 for boost::histogram::accumulators::mean -template -struct version> : std::integral_constant {}; - -} // namespace serialization -} // namespace boost - -namespace std { -template -/// Specialization for boost::histogram::accumulators::mean. -struct common_type, - boost::histogram::accumulators::mean> { - using type = boost::histogram::accumulators::mean>; + RealType count = 0, value = 0, sum_of_deltas_squared = 0; }; -} // namespace std -#endif +} // namespace accumulators diff --git a/include/boost/histogram/python/accumulators/weighted_mean.hpp b/include/boost/histogram/python/accumulators/weighted_mean.hpp index 922d99e28..df5cf6a3d 100644 --- a/include/boost/histogram/python/accumulators/weighted_mean.hpp +++ b/include/boost/histogram/python/accumulators/weighted_mean.hpp @@ -14,13 +14,10 @@ #include #include -#include // for weighted_mean<> #include #include -namespace boost { -namespace histogram { -namespace python { +namespace accumulators { /** Calculates mean and variance of weighted sample. @@ -30,68 +27,64 @@ namespace python { */ template struct weighted_mean { - public: weighted_mean() = default; weighted_mean(const RealType &wsum, const RealType &wsum2, const RealType &mean, const RealType &variance) - : sum_of_weights_(wsum) - , sum_of_weights_squared_(wsum2) - , weighted_mean_(mean) - , sum_of_weighted_deltas_squared_( - variance - * (sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_)) {} + : sum_of_weights(wsum) + , sum_of_weights_squared(wsum2) + , value(mean) + , sum_of_weighted_deltas_squared( + variance * (sum_of_weights - sum_of_weights_squared / sum_of_weights)) {} weighted_mean(const RealType &wsum, const RealType &wsum2, const RealType &mean, const RealType &sum_of_weighted_deltas_squared, bool) - : sum_of_weights_(wsum) - , sum_of_weights_squared_(wsum2) - , weighted_mean_(mean) - , sum_of_weighted_deltas_squared_(sum_of_weighted_deltas_squared) {} - - void operator()(const RealType &x) { operator()(weight(1), x); } - - void operator()(const weight_type &w, const RealType &x) { - sum_of_weights_ += w.value; - sum_of_weights_squared_ += w.value * w.value; - const auto delta = x - weighted_mean_; - weighted_mean_ += w.value * delta / sum_of_weights_; - sum_of_weighted_deltas_squared_ += w.value * delta * (x - weighted_mean_); + : sum_of_weights(wsum) + , sum_of_weights_squared(wsum2) + , value(mean) + , sum_of_weighted_deltas_squared(sum_of_weighted_deltas_squared) {} + + void operator()(const RealType &x) { operator()(boost::histogram::weight(1), x); } + + void operator()(const boost::histogram::weight_type &w, + const RealType &x) { + sum_of_weights += w.value; + sum_of_weights_squared += w.value * w.value; + const auto delta = x - value; + value += w.value * delta / sum_of_weights; + sum_of_weighted_deltas_squared += w.value * delta * (x - value); } template weighted_mean &operator+=(const weighted_mean &rhs) { - if(sum_of_weights_ != 0 || rhs.sum_of_weights_ != 0) { - const auto tmp - = weighted_mean_ * sum_of_weights_ - + static_cast(rhs.weighted_mean_ * rhs.sum_of_weights_); - sum_of_weights_ += static_cast(rhs.sum_of_weights_); - sum_of_weights_squared_ - += static_cast(rhs.sum_of_weights_squared_); - weighted_mean_ = tmp / sum_of_weights_; + if(sum_of_weights != 0 || rhs.sum_of_weights != 0) { + const auto tmp = value * sum_of_weights + + static_cast(rhs.value * rhs.sum_of_weights); + sum_of_weights += static_cast(rhs.sum_of_weights); + sum_of_weights_squared += static_cast(rhs.sum_of_weights_squared); + value = tmp / sum_of_weights; } - sum_of_weighted_deltas_squared_ - += static_cast(rhs.sum_of_weighted_deltas_squared_); + sum_of_weighted_deltas_squared + += static_cast(rhs.sum_of_weighted_deltas_squared); return *this; } weighted_mean &operator*=(const RealType &s) { - weighted_mean_ *= s; - sum_of_weighted_deltas_squared_ *= s * s; + value *= s; + sum_of_weighted_deltas_squared *= s * s; return *this; } template bool operator==(const weighted_mean &rhs) const noexcept { - return sum_of_weights_ == rhs.sum_of_weights_ - && sum_of_weights_squared_ == rhs.sum_of_weights_squared_ - && weighted_mean_ == rhs.weighted_mean_ - && sum_of_weighted_deltas_squared_ - == rhs.sum_of_weighted_deltas_squared_; + return sum_of_weights == rhs.sum_of_weights + && sum_of_weights_squared == rhs.sum_of_weights_squared + && value == rhs.value + && sum_of_weighted_deltas_squared == rhs.sum_of_weighted_deltas_squared; } template @@ -99,39 +92,22 @@ struct weighted_mean { return !operator==(rhs); } - const RealType &sum_of_weights() const noexcept { return sum_of_weights_; } - const RealType &sum_of_weights_squared() const noexcept { - return sum_of_weights_squared_; - } - const RealType &value() const noexcept { return weighted_mean_; } RealType variance() const { - return sum_of_weighted_deltas_squared_ - / (sum_of_weights_ - sum_of_weights_squared_ / sum_of_weights_); + return sum_of_weighted_deltas_squared + / (sum_of_weights - sum_of_weights_squared / sum_of_weights); } template void serialize(Archive &ar, unsigned /* version */) { - ar &make_nvp("sum_of_weights", sum_of_weights_); - ar &make_nvp("sum_of_weights_squared", sum_of_weights_squared_); - ar &make_nvp("weighted_mean", weighted_mean_); - ar &make_nvp("sum_of_weighted_deltas_squared", sum_of_weighted_deltas_squared_); + ar &boost::make_nvp("sum_of_weights", sum_of_weights); + ar &boost::make_nvp("sum_of_weights_squared", sum_of_weights_squared); + ar &boost::make_nvp("value", value); + ar &boost::make_nvp("sum_of_weighted_deltas_squared", + sum_of_weighted_deltas_squared); } - RealType sum_of_weights_ = RealType(), sum_of_weights_squared_ = RealType(), - weighted_mean_ = RealType(), sum_of_weighted_deltas_squared_ = RealType(); + RealType sum_of_weights = RealType(), sum_of_weights_squared = RealType(), + value = RealType(), sum_of_weighted_deltas_squared = RealType(); }; -} // namespace python -} // namespace histogram -} // namespace boost - -#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED -namespace std { -template -/// Specialization for boost::histogram::accumulators::weighted_mean. -struct common_type, - boost::histogram::accumulators::weighted_mean> { - using type = boost::histogram::accumulators::weighted_mean>; -}; -} // namespace std -#endif +} // namespace accumulators diff --git a/include/boost/histogram/python/accumulators/weighted_sum.hpp b/include/boost/histogram/python/accumulators/weighted_sum.hpp index e5df8c70d..74d4cdfaa 100644 --- a/include/boost/histogram/python/accumulators/weighted_sum.hpp +++ b/include/boost/histogram/python/accumulators/weighted_sum.hpp @@ -13,59 +13,55 @@ #pragma once #include -#include // for weighted_sum<> +#include #include -namespace boost { -namespace histogram { -namespace python { +namespace accumulators { /// Holds sum of weights and its variance estimate template struct weighted_sum { - public: weighted_sum() = default; explicit weighted_sum(const RealType &value) noexcept - : sum_of_weights_(value) - , sum_of_weights_squared_(value) {} + : value(value) + , variance(value) {} weighted_sum(const RealType &value, const RealType &variance) noexcept - : sum_of_weights_(value) - , sum_of_weights_squared_(variance) {} + : value(value) + , variance(variance) {} /// Increment by one. weighted_sum &operator++() { return operator+=(1); } /// Increment by value. template - weighted_sum &operator+=(const T &value) { - sum_of_weights_ += value; - sum_of_weights_squared_ += value * value; + weighted_sum &operator+=(const T &val) { + value += val; + variance += val * val; return *this; } /// Added another weighted sum. template weighted_sum &operator+=(const weighted_sum &rhs) { - sum_of_weights_ += static_cast(rhs.sum_of_weights_); - sum_of_weights_squared_ += static_cast(rhs.sum_of_weights_squared_); + value += static_cast(rhs.value); + variance += static_cast(rhs.variance); return *this; } /// Scale by value. weighted_sum &operator*=(const RealType &x) { - sum_of_weights_ *= x; - sum_of_weights_squared_ *= x * x; + value *= x; + variance *= x * x; return *this; } bool operator==(const RealType &rhs) const noexcept { - return sum_of_weights_ == rhs && sum_of_weights_squared_ == rhs; + return value == rhs && variance == rhs; } template bool operator==(const weighted_sum &rhs) const noexcept { - return sum_of_weights_ == rhs.sum_of_weights_ - && sum_of_weights_squared_ == rhs.sum_of_weights_squared_; + return value == rhs.value && variance == rhs.variance; } template @@ -73,48 +69,20 @@ struct weighted_sum { return !operator==(rhs); } - /// Return value of the sum. - const RealType &value() const noexcept { return sum_of_weights_; } - - /// Return estimated variance of the sum. - const RealType &variance() const noexcept { return sum_of_weights_squared_; } - // lossy conversion must be explicit template explicit operator T() const { - return static_cast(sum_of_weights_); + return static_cast(value); } template void serialize(Archive &ar, unsigned /* version */) { - ar &make_nvp("sum_of_weights", sum_of_weights_); - ar &make_nvp("sum_of_weights_squared", sum_of_weights_squared_); + ar &boost::make_nvp("value", value); + ar &boost::make_nvp("variance", variance); } - RealType sum_of_weights_ = RealType(); - RealType sum_of_weights_squared_ = RealType(); -}; - -} // namespace python -} // namespace histogram -} // namespace boost - -#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED -namespace std { -template -struct common_type, - boost::histogram::accumulators::weighted_sum> { - using type = boost::histogram::accumulators::weighted_sum>; -}; - -template -struct common_type, U> { - using type = boost::histogram::accumulators::weighted_sum>; + RealType value = RealType(); + RealType variance = RealType(); }; -template -struct common_type> { - using type = boost::histogram::accumulators::weighted_sum>; -}; -} // namespace std -#endif +} // namespace accumulators diff --git a/include/boost/histogram/python/accumulators_ostream.hpp b/include/boost/histogram/python/accumulators_ostream.hpp index 1beee322c..ad2fc0889 100644 --- a/include/boost/histogram/python/accumulators_ostream.hpp +++ b/include/boost/histogram/python/accumulators_ostream.hpp @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -16,17 +17,14 @@ #include #include -namespace boost { -namespace histogram { - -namespace detail { +namespace { template std::basic_ostream & handle_nonzero_width(std::basic_ostream &os, const T &x) { const auto w = os.width(); os.width(0); - counting_streambuf cb; + boost::histogram::detail::counting_streambuf cb; const auto saved = os.rdbuf(&cb); os << x; os.rdbuf(saved); @@ -42,52 +40,54 @@ handle_nonzero_width(std::basic_ostream &os, const T &x) { return os; } -} // namespace detail +} // anonymous namespace + +namespace accumulators { -namespace python { +// Note that the names are *not* included here, so they can be added in Pybind11. template -std::basic_ostream &operator<<(std::basic_ostream &os, - const accumulators::sum &x) { +std::basic_ostream & +operator<<(std::basic_ostream &os, + const ::boost::histogram::accumulators::sum &x) { if(os.width() == 0) - return os << "sum(" << x.large() << " + " << x.small() << ")"; - return detail::handle_nonzero_width(os, x); + return os << "(" << x.large() << " + " << x.small() << ")"; + return handle_nonzero_width(os, x); } template std::basic_ostream &operator<<(std::basic_ostream &os, const weighted_sum &x) { if(os.width() == 0) - return os << "weighted_sum(value=" << x.value() << ", variance=" << x.variance() - << ")"; - return detail::handle_nonzero_width(os, x); + return os << "(value=" << x.value << ", variance=" << x.variance << ")"; + return handle_nonzero_width(os, x); } template std::basic_ostream &operator<<(std::basic_ostream &os, const mean &x) { if(os.width() == 0) - return os << "mean(count=" << x.count() << ", value=" << x.value() + return os << "(count=" << x.count << ", value=" << x.value << ", variance=" << x.variance() << ")"; - return detail::handle_nonzero_width(os, x); + return handle_nonzero_width(os, x); } template std::basic_ostream &operator<<(std::basic_ostream &os, const weighted_mean &x) { if(os.width() == 0) - return os << "weighted_mean(wsum=" << x.sum_of_weights() - << ", wsum2=" << x.sum_of_weights_squared() << ", value=" << x.value() + return os << "(wsum=" << x.sum_of_weights + << ", wsum2=" << x.sum_of_weights_squared << ", value=" << x.value << ", variance=" << x.variance() << ")"; - return detail::handle_nonzero_width(os, x); + return handle_nonzero_width(os, x); } template -std::basic_ostream &operator<<(std::basic_ostream &os, - const accumulators::thread_safe &x) { +std::basic_ostream & +operator<<(std::basic_ostream &os, + const ::boost::histogram::accumulators::thread_safe &x) { os << x.load(); return os; } -} // namespace python -} // namespace histogram -} // namespace boost + +} // namespace accumulators diff --git a/include/boost/histogram/python/register_accumulator.hpp b/include/boost/histogram/python/register_accumulator.hpp index 1d5041e52..8a418fc56 100644 --- a/include/boost/histogram/python/register_accumulator.hpp +++ b/include/boost/histogram/python/register_accumulator.hpp @@ -24,7 +24,12 @@ py::class_ register_accumulator(py::module acc, Args &&... args) { .def(py::self *= double()) - .def("__repr__", &shift_to_string) + .def("__repr__", + [](py::object self) { + const A &item = py::cast(self); + return py::str("{0}{1}").format( + self.attr("__class__").attr("__name__"), shift_to_string(item)); + }) .def("__copy__", [](const A &self) { return A(self); }) .def("__deepcopy__", [](const A &self, py::object) { return A(self); }) diff --git a/include/boost/histogram/python/storage.hpp b/include/boost/histogram/python/storage.hpp index 2a38cc116..773268bf3 100644 --- a/include/boost/histogram/python/storage.hpp +++ b/include/boost/histogram/python/storage.hpp @@ -24,9 +24,9 @@ using int_ = bh::dense_storage; using atomic_int = bh::dense_storage>; using double_ = bh::dense_storage; using unlimited = bh::unlimited_storage<>; -using weight = bh::dense_storage>; -using mean = bh::dense_storage>; -using weighted_mean = bh::dense_storage>; +using weight = bh::dense_storage>; +using mean = bh::dense_storage>; +using weighted_mean = bh::dense_storage>; // Allow repr to show python name template diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index f8dca2acd..76c9b6f2a 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -58,18 +58,17 @@ void register_accumulators(py::module &accumulators) { // If it is not available except through a computation, it has // the same name as the private property without the trailing _. - using weighted_sum = bh::python::weighted_sum; + using weighted_sum = accumulators::weighted_sum; - PYBIND11_NUMPY_DTYPE_EX( - weighted_sum, sum_of_weights_, "value", sum_of_weights_squared_, "variance"); + PYBIND11_NUMPY_DTYPE(weighted_sum, value, variance); register_accumulator(accumulators, "weighted_sum") .def(py::init(), "value"_a) .def(py::init(), "value"_a, "variance"_a) - .def_property_readonly("value", &weighted_sum::value) - .def_property_readonly("variance", &weighted_sum::variance) + .def_readonly("value", &weighted_sum::value) + .def_readonly("variance", &weighted_sum::variance) .def(py::self += double()) @@ -128,16 +127,12 @@ void register_accumulators(py::module &accumulators) { ; - using weighted_mean = bh::python::weighted_mean; - PYBIND11_NUMPY_DTYPE_EX(weighted_mean, - sum_of_weights_, - "sum_of_weights", - sum_of_weights_squared_, - "sum_of_weights_squared", - weighted_mean_, - "value", - sum_of_weighted_deltas_squared_, - "sum_of_weighted_deltas_squared"); + using weighted_mean = accumulators::weighted_mean; + PYBIND11_NUMPY_DTYPE(weighted_mean, + sum_of_weights, + sum_of_weights_squared, + value, + sum_of_weighted_deltas_squared); register_accumulator(accumulators, "weighted_mean") .def(py::init(), @@ -146,8 +141,12 @@ void register_accumulators(py::module &accumulators) { "value"_a, "variance"_a) - .def_property_readonly("sum_of_weights", &weighted_mean::sum_of_weights) - .def_property_readonly("value", &weighted_mean::value) + .def_readonly("sum_of_weights", &weighted_mean::sum_of_weights) + .def_readonly("sum_of_weights_squared", &weighted_mean::sum_of_weights_squared) + .def_readonly("value", &weighted_mean::value) + .def_readonly("sum_of_weighted_deltas_squared", + &weighted_mean::sum_of_weighted_deltas_squared) + .def_property_readonly("variance", &weighted_mean::variance) .def("__call__", @@ -169,14 +168,8 @@ void register_accumulators(py::module &accumulators) { ; - using mean = bh::python::mean; - PYBIND11_NUMPY_DTYPE_EX(mean, - sum_, - "count", - mean_, - "value", - sum_of_deltas_squared_, - "sum_of_deltas_squared"); + using mean = accumulators::mean; + PYBIND11_NUMPY_DTYPE(mean, count, value, sum_of_deltas_squared); register_accumulator(accumulators, "mean") .def(py::init(), @@ -184,8 +177,10 @@ void register_accumulators(py::module &accumulators) { "value"_a, "variance"_a) - .def_property_readonly("count", &mean::count) - .def_property_readonly("value", &mean::value) + .def_readonly("count", &mean::count) + .def_readonly("value", &mean::value) + .def_readonly("sum_of_deltas_squared", &mean::sum_of_deltas_squared) + .def_property_readonly("variance", &mean::variance) .def("__call__", From 7fc7f6b16cf5df78f14ee278e85cfe941d930bdb Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 12 Nov 2019 14:07:18 -0500 Subject: [PATCH 16/20] Move ostream, remove some extra headers --- include/boost/histogram/python/accumulators/mean.hpp | 4 ---- .../{accumulators_ostream.hpp => accumulators/ostream.hpp} | 0 include/boost/histogram/python/accumulators/weighted_mean.hpp | 2 -- include/boost/histogram/python/accumulators/weighted_sum.hpp | 1 - include/boost/histogram/python/histogram_ostream.hpp | 2 +- include/boost/histogram/python/register_accumulator.hpp | 2 +- src/register_accumulators.cpp | 2 +- 7 files changed, 3 insertions(+), 10 deletions(-) rename include/boost/histogram/python/{accumulators_ostream.hpp => accumulators/ostream.hpp} (100%) diff --git a/include/boost/histogram/python/accumulators/mean.hpp b/include/boost/histogram/python/accumulators/mean.hpp index 787193032..0f46c04e8 100644 --- a/include/boost/histogram/python/accumulators/mean.hpp +++ b/include/boost/histogram/python/accumulators/mean.hpp @@ -11,12 +11,8 @@ #pragma once -#include #include #include -#include -#include -#include namespace accumulators { diff --git a/include/boost/histogram/python/accumulators_ostream.hpp b/include/boost/histogram/python/accumulators/ostream.hpp similarity index 100% rename from include/boost/histogram/python/accumulators_ostream.hpp rename to include/boost/histogram/python/accumulators/ostream.hpp diff --git a/include/boost/histogram/python/accumulators/weighted_mean.hpp b/include/boost/histogram/python/accumulators/weighted_mean.hpp index df5cf6a3d..518fd890b 100644 --- a/include/boost/histogram/python/accumulators/weighted_mean.hpp +++ b/include/boost/histogram/python/accumulators/weighted_mean.hpp @@ -12,10 +12,8 @@ #pragma once -#include #include #include -#include namespace accumulators { diff --git a/include/boost/histogram/python/accumulators/weighted_sum.hpp b/include/boost/histogram/python/accumulators/weighted_sum.hpp index 74d4cdfaa..a5f79172e 100644 --- a/include/boost/histogram/python/accumulators/weighted_sum.hpp +++ b/include/boost/histogram/python/accumulators/weighted_sum.hpp @@ -14,7 +14,6 @@ #include #include -#include namespace accumulators { diff --git a/include/boost/histogram/python/histogram_ostream.hpp b/include/boost/histogram/python/histogram_ostream.hpp index 57ec0024f..fbe9f5ced 100644 --- a/include/boost/histogram/python/histogram_ostream.hpp +++ b/include/boost/histogram/python/histogram_ostream.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include #include diff --git a/include/boost/histogram/python/register_accumulator.hpp b/include/boost/histogram/python/register_accumulator.hpp index 8a418fc56..31b4b8038 100644 --- a/include/boost/histogram/python/register_accumulator.hpp +++ b/include/boost/histogram/python/register_accumulator.hpp @@ -7,7 +7,7 @@ #include -#include +#include #include #include diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index 76c9b6f2a..6ce58571d 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -7,9 +7,9 @@ #include #include +#include #include #include -#include #include #include #include From 164f66e025e695fe79a28b00ddcb1bb1a08187db Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 12 Nov 2019 15:13:09 -0500 Subject: [PATCH 17/20] Fix reprs, add missing tests --- .../histogram/python/accumulators/ostream.hpp | 9 +++-- .../histogram/python/register_accumulator.hpp | 6 ++- tests/test_storage.py | 39 ++++++++++++++----- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/include/boost/histogram/python/accumulators/ostream.hpp b/include/boost/histogram/python/accumulators/ostream.hpp index ad2fc0889..26fbf7404 100644 --- a/include/boost/histogram/python/accumulators/ostream.hpp +++ b/include/boost/histogram/python/accumulators/ostream.hpp @@ -51,7 +51,7 @@ std::basic_ostream & operator<<(std::basic_ostream &os, const ::boost::histogram::accumulators::sum &x) { if(os.width() == 0) - return os << "(" << x.large() << " + " << x.small() << ")"; + return os << "sum(" << x.large() << " + " << x.small() << ")"; return handle_nonzero_width(os, x); } @@ -59,7 +59,8 @@ template std::basic_ostream &operator<<(std::basic_ostream &os, const weighted_sum &x) { if(os.width() == 0) - return os << "(value=" << x.value << ", variance=" << x.variance << ")"; + return os << "weighted_sum(value=" << x.value << ", variance=" << x.variance + << ")"; return handle_nonzero_width(os, x); } @@ -67,7 +68,7 @@ template std::basic_ostream &operator<<(std::basic_ostream &os, const mean &x) { if(os.width() == 0) - return os << "(count=" << x.count << ", value=" << x.value + return os << "mean(count=" << x.count << ", value=" << x.value << ", variance=" << x.variance() << ")"; return handle_nonzero_width(os, x); } @@ -76,7 +77,7 @@ template std::basic_ostream &operator<<(std::basic_ostream &os, const weighted_mean &x) { if(os.width() == 0) - return os << "(wsum=" << x.sum_of_weights + return os << "weighted_mean(wsum=" << x.sum_of_weights << ", wsum2=" << x.sum_of_weights_squared << ", value=" << x.value << ", variance=" << x.variance() << ")"; return handle_nonzero_width(os, x); diff --git a/include/boost/histogram/python/register_accumulator.hpp b/include/boost/histogram/python/register_accumulator.hpp index 31b4b8038..419713375 100644 --- a/include/boost/histogram/python/register_accumulator.hpp +++ b/include/boost/histogram/python/register_accumulator.hpp @@ -24,11 +24,13 @@ py::class_ register_accumulator(py::module acc, Args &&... args) { .def(py::self *= double()) + // The c++ name is replaced with the Python name here .def("__repr__", [](py::object self) { const A &item = py::cast(self); - return py::str("{0}{1}").format( - self.attr("__class__").attr("__name__"), shift_to_string(item)); + py::str str = shift_to_string(item); + str = str.attr("split")("(", 2).attr("__getitem__")(1); + return py::str("{0.__class__.__name__}({1}").format(self, str); }) .def("__copy__", [](const A &self) { return A(self); }) diff --git a/tests/test_storage.py b/tests/test_storage.py index f3dadd06b..8c9589bb4 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -10,23 +10,16 @@ ) def test_setting(storage): h = bh.Histogram(bh.axis.Regular(10, 0, 1), storage=storage()) - h[bh.underflow] = 1 + h[0] = 2 h[1] = 3 - h[bh.loc(0.55)] = 4 h[-1] = 5 - h[bh.overflow] = 6 - assert h[bh.underflow] == 1 assert h[0] == 2 assert h[1] == 3 - assert h[bh.loc(0.55)] == 4 - assert h[5] == 4 - assert h[-1] == 5 assert h[9] == 5 - assert h[bh.overflow] == 6 - assert_array_equal(h.view(flow=True), [1, 2, 3, 0, 0, 0, 4, 0, 0, 0, 5, 6]) + assert_array_equal(h.view(), [2, 3, 0, 0, 0, 0, 0, 0, 0, 5]) def test_setting_weight(): @@ -46,11 +39,17 @@ def test_setting_weight(): b = np.asarray(h) assert b["value"][0] == h[0].value + assert b["variance"][0] == h[0].variance h[0] = bh.accumulators.WeightedSum(value=3, variance=1) + assert h[0].value == 3 + assert h[0].variance == 1 + assert a[0] == h[0] + assert b["value"][0] == h[0].value + assert b["variance"][0] == h[0].variance def test_setting_profile(): @@ -69,12 +68,20 @@ def test_setting_profile(): assert a[0] == h[0] b = np.asarray(h) + assert b["value"][0] == h[0].value + assert b["count"][0] == h[0].count + assert b["sum_of_deltas_squared"][0] == h[0].sum_of_deltas_squared h[0] = bh.accumulators.Mean(count=6, value=3, variance=2) + assert h[0].count == 6 + assert h[0].value == 3 + assert h[0].variance == 2 assert a[0] == h[0] assert b["value"][0] == h[0].value + assert b["count"][0] == h[0].count + assert b["sum_of_deltas_squared"][0] == h[0].sum_of_deltas_squared def test_setting_weighted_profile(): @@ -95,9 +102,23 @@ def test_setting_weighted_profile(): assert a[0] == h[0] b = np.asarray(h) + assert b["value"][0] == h[0].value + assert b["sum_of_weights"][0] == h[0].sum_of_weights + assert b["sum_of_weights_squared"][0] == h[0].sum_of_weights_squared + assert b["sum_of_weighted_deltas_squared"][0] == h[0].sum_of_weighted_deltas_squared h[0] = bh.accumulators.WeightedMean(wsum=6, wsum2=12, value=3, variance=2) assert a[0] == h[0] + + assert h[0].value == 3 + assert h[0].variance == 2 + assert h[0].sum_of_weights == 6 + assert h[0].sum_of_weights_squared == 12 + assert h[0].sum_of_weighted_deltas_squared == 8 + assert b["value"][0] == h[0].value + assert b["sum_of_weights"][0] == h[0].sum_of_weights + assert b["sum_of_weights_squared"][0] == h[0].sum_of_weights_squared + assert b["sum_of_weighted_deltas_squared"][0] == h[0].sum_of_weighted_deltas_squared From 91c033d370f4c94cce5b6992ffb4313152646f30 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 12 Nov 2019 15:38:41 -0500 Subject: [PATCH 18/20] Adding accum[key] access --- src/register_accumulators.cpp | 99 +++++++++++++++++++++++++++++++++++ tests/test_storage.py | 30 +++++++++++ 2 files changed, 129 insertions(+) diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index 6ce58571d..7f2e63257 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -97,6 +97,30 @@ void register_accumulators(py::module &accumulators) { return weighted_sum(a, b); })) + .def("__getitem__", + [](const weighted_sum &self, py::str key) { + if(key.equal(py::str("value"))) + return self.value; + else if(key.equal(py::str("variance"))) + return self.variance; + else + throw py::key_error( + py::str("{0} not one of value, variance").format(key)); + }) + .def("__setitem__", + [](weighted_sum &self, py::str key, double value) { + if(key.equal(py::str("value"))) + self.value = value; + else if(key.equal(py::str("variance"))) + self.variance = value; + else + throw py::key_error( + py::str("{0} not one of value, variance").format(key)); + }) + + .def("_ipython_key_completions_", + [](py::object /* self */) { return py::make_tuple("value", "variance"); }) + ; using sum = bh::accumulators::sum; @@ -166,6 +190,49 @@ void register_accumulators(py::module &accumulators) { return weighted_mean(a, b, c, d, true); })) + .def("__getitem__", + [](const weighted_mean &self, py::str key) { + if(key.equal(py::str("value"))) + return self.value; + else if(key.equal(py::str("sum_of_weights"))) + return self.sum_of_weights; + else if(key.equal(py::str("sum_of_weights_squared"))) + return self.sum_of_weights_squared; + else if(key.equal(py::str("sum_of_weighted_deltas_squared"))) + return self.sum_of_weighted_deltas_squared; + else + throw py::key_error( + py::str( + "{0} not one of value, sum_of_weights, " + "sum_of_weights_squared, sum_of_weighted_deltas_squared") + .format(key)); + }) + .def("__setitem__", + [](weighted_mean &self, py::str key, double value) { + if(key.equal(py::str("value"))) + self.value = value; + else if(key.equal(py::str("sum_of_weights"))) + self.sum_of_weights = value; + else if(key.equal(py::str("sum_of_weights_squared"))) + self.sum_of_weights_squared = value; + else if(key.equal(py::str("sum_of_weighted_deltas_squared"))) + self.sum_of_weighted_deltas_squared = value; + else + throw py::key_error( + py::str( + "{0} not one of value, sum_of_weights, " + "sum_of_weights_squared, sum_of_weighted_deltas_squared") + .format(key)); + }) + + .def("_ipython_key_completions_", + [](py::object /* self */) { + return py::make_tuple("value", + "sum_of_weights", + "sum_of_weights_squared", + "sum_of_weighted_deltas_squared"); + }) + ; using mean = accumulators::mean; @@ -199,5 +266,37 @@ void register_accumulators(py::module &accumulators) { return mean(a, b, c, true); })) + .def("__getitem__", + [](const mean &self, py::str key) { + if(key.equal(py::str("count"))) + return self.count; + else if(key.equal(py::str("value"))) + return self.value; + else if(key.equal(py::str("sum_of_deltas_squared"))) + return self.sum_of_deltas_squared; + else + throw py::key_error( + py::str("{0} not one of count, value, sum_of_deltas_squared") + .format(key)); + }) + .def("__setitem__", + [](mean &self, py::str key, double value) { + if(key.equal(py::str("count"))) + self.count = value; + else if(key.equal(py::str("value"))) + self.value = value; + else if(key.equal(py::str("sum_of_deltas_squared"))) + self.sum_of_deltas_squared = value; + else + throw py::key_error( + py::str("{0} not one of count, value, sum_of_deltas_squared") + .format(key)); + }) + + .def("_ipython_key_completions_", + [](py::object /* self */) { + return py::make_tuple("count", "value", "sum_of_deltas_squared"); + }) + ; } diff --git a/tests/test_storage.py b/tests/test_storage.py index 8c9589bb4..8f52fa539 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -38,6 +38,7 @@ def test_setting_weight(): assert a[0] == h[0] b = np.asarray(h) + assert b["value"][0] == h[0].value assert b["variance"][0] == h[0].variance @@ -51,6 +52,12 @@ def test_setting_weight(): assert b["value"][0] == h[0].value assert b["variance"][0] == h[0].variance + assert b[0]["value"] == a[0]["value"] + assert b[0]["variance"] == a[0]["variance"] + + assert b["value"][0] == a["value"][0] + assert b["variance"][0] == a["variance"][0] + def test_setting_profile(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.Mean()) @@ -79,10 +86,19 @@ def test_setting_profile(): assert h[0].variance == 2 assert a[0] == h[0] + assert b["value"][0] == h[0].value assert b["count"][0] == h[0].count assert b["sum_of_deltas_squared"][0] == h[0].sum_of_deltas_squared + assert b[0]["value"] == a[0]["value"] + assert b[0]["count"] == a[0]["count"] + assert b[0]["sum_of_deltas_squared"] == a[0]["sum_of_deltas_squared"] + + assert b[0]["value"] == a["value"][0] + assert b[0]["count"] == a["count"][0] + assert b[0]["sum_of_deltas_squared"] == a["sum_of_deltas_squared"][0] + def test_setting_weighted_profile(): h = bh.Histogram(bh.axis.Regular(10, 0, 10), storage=bh.storage.WeightedMean()) @@ -122,3 +138,17 @@ def test_setting_weighted_profile(): assert b["sum_of_weights"][0] == h[0].sum_of_weights assert b["sum_of_weights_squared"][0] == h[0].sum_of_weights_squared assert b["sum_of_weighted_deltas_squared"][0] == h[0].sum_of_weighted_deltas_squared + + assert b[0]["value"] == a[0]["value"] + assert b[0]["sum_of_weights"] == a[0]["sum_of_weights"] + assert b[0]["sum_of_weights_squared"] == a[0]["sum_of_weights_squared"] + assert ( + b[0]["sum_of_weighted_deltas_squared"] == a[0]["sum_of_weighted_deltas_squared"] + ) + + assert b[0]["value"] == a["value"][0] + assert b[0]["sum_of_weights"] == a["sum_of_weights"][0] + assert b[0]["sum_of_weights_squared"] == a["sum_of_weights_squared"][0] + assert ( + b[0]["sum_of_weighted_deltas_squared"] == a["sum_of_weighted_deltas_squared"][0] + ) From 7f41e94b40e38472006966165f493a4fe65ead10 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 13 Nov 2019 07:34:06 -0500 Subject: [PATCH 19/20] Addressing second review --- include/boost/histogram/python/accumulators/mean.hpp | 2 +- include/boost/histogram/python/accumulators/ostream.hpp | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/include/boost/histogram/python/accumulators/mean.hpp b/include/boost/histogram/python/accumulators/mean.hpp index 0f46c04e8..32212e094 100644 --- a/include/boost/histogram/python/accumulators/mean.hpp +++ b/include/boost/histogram/python/accumulators/mean.hpp @@ -18,7 +18,7 @@ namespace accumulators { /** Calculates mean and variance of sample. - Uses Welfords's incremental algorithm to improve the numerical + Uses Welford's incremental algorithm to improve the numerical stability of mean and variance computation. */ template diff --git a/include/boost/histogram/python/accumulators/ostream.hpp b/include/boost/histogram/python/accumulators/ostream.hpp index 26fbf7404..045a3b74a 100644 --- a/include/boost/histogram/python/accumulators/ostream.hpp +++ b/include/boost/histogram/python/accumulators/ostream.hpp @@ -17,8 +17,6 @@ #include #include -namespace { - template std::basic_ostream & handle_nonzero_width(std::basic_ostream &os, const T &x) { @@ -40,8 +38,6 @@ handle_nonzero_width(std::basic_ostream &os, const T &x) { return os; } -} // anonymous namespace - namespace accumulators { // Note that the names are *not* included here, so they can be added in Pybind11. From bcd53cbf8ed58bf314d67a963b11e0ce45ece3fa Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 13 Nov 2019 07:51:09 -0500 Subject: [PATCH 20/20] Rename wsum and wsum2 --- .../histogram/python/accumulators/ostream.hpp | 6 +++--- src/register_accumulators.cpp | 4 ++-- tests/test_storage.py | 18 +++++++++++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/boost/histogram/python/accumulators/ostream.hpp b/include/boost/histogram/python/accumulators/ostream.hpp index 045a3b74a..4e4f26b89 100644 --- a/include/boost/histogram/python/accumulators/ostream.hpp +++ b/include/boost/histogram/python/accumulators/ostream.hpp @@ -73,9 +73,9 @@ template std::basic_ostream &operator<<(std::basic_ostream &os, const weighted_mean &x) { if(os.width() == 0) - return os << "weighted_mean(wsum=" << x.sum_of_weights - << ", wsum2=" << x.sum_of_weights_squared << ", value=" << x.value - << ", variance=" << x.variance() << ")"; + return os << "weighted_mean(sum_of_weights=" << x.sum_of_weights + << ", sum_of_weights_squared=" << x.sum_of_weights_squared + << ", value=" << x.value << ", variance=" << x.variance() << ")"; return handle_nonzero_width(os, x); } diff --git a/src/register_accumulators.cpp b/src/register_accumulators.cpp index 7f2e63257..e8387f35b 100644 --- a/src/register_accumulators.cpp +++ b/src/register_accumulators.cpp @@ -160,8 +160,8 @@ void register_accumulators(py::module &accumulators) { register_accumulator(accumulators, "weighted_mean") .def(py::init(), - "wsum"_a, - "wsum2"_a, + "sum_of_weights"_a, + "sum_of_weights_squared"_a, "value"_a, "variance"_a) diff --git a/tests/test_storage.py b/tests/test_storage.py index 8f52fa539..d9077f07c 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -105,12 +105,18 @@ def test_setting_weighted_profile(): h.fill([0.3, 0.3, 0.4, 1.2, 1.6], sample=[1, 2, 3, 4, 4], weight=[1, 1, 1, 1, 2]) - assert h[0] == bh.accumulators.WeightedMean(wsum=3, wsum2=3, value=2, variance=1) - assert h[1] == bh.accumulators.WeightedMean(wsum=3, wsum2=5, value=4, variance=0) + assert h[0] == bh.accumulators.WeightedMean( + sum_of_weights=3, sum_of_weights_squared=3, value=2, variance=1 + ) + assert h[1] == bh.accumulators.WeightedMean( + sum_of_weights=3, sum_of_weights_squared=5, value=4, variance=0 + ) - h[0] = bh.accumulators.WeightedMean(wsum=12, wsum2=15, value=11, variance=10) + h[0] = bh.accumulators.WeightedMean( + sum_of_weights=12, sum_of_weights_squared=15, value=11, variance=10 + ) assert h[0] == bh.accumulators.WeightedMean( - wsum=12, wsum2=15, value=11, variance=10 + sum_of_weights=12, sum_of_weights_squared=15, value=11, variance=10 ) a = h.view() @@ -124,7 +130,9 @@ def test_setting_weighted_profile(): assert b["sum_of_weights_squared"][0] == h[0].sum_of_weights_squared assert b["sum_of_weighted_deltas_squared"][0] == h[0].sum_of_weighted_deltas_squared - h[0] = bh.accumulators.WeightedMean(wsum=6, wsum2=12, value=3, variance=2) + h[0] = bh.accumulators.WeightedMean( + sum_of_weights=6, sum_of_weights_squared=12, value=3, variance=2 + ) assert a[0] == h[0]