diff --git a/examples/getting_started_listing_01.cpp b/examples/getting_started_listing_01.cpp index f854dcc8..1d080a2a 100644 --- a/examples/getting_started_listing_01.cpp +++ b/examples/getting_started_listing_01.cpp @@ -8,75 +8,71 @@ int main(int, char**) { using namespace bh::literals; // enables _c suffix /* - create a static 1d-histogram with an axis that has 10 equidistant - bins on the real line from -1.0 to 2.0, and label it as "x" + create a static 1d-histogram with an axis that has 10 equidistant + bins on the real line from -1.0 to 2.0, and label it as "x" */ auto h = bh::make_static_histogram( - bh::axis::regular<>(10, -1.0, 2.0, "x") + bh::axis::regular<>(6, -1.0, 2.0, "x") ); // fill histogram with data, typically this happens in a loop - h(-1.5); // put in underflow bin - h(-1.0); // included in first bin, bin interval is semi-open - h(2.0); // put in overflow bin, bin interval is semi-open - h(20.0); // put in overflow bin - // STL algorithms are supported auto data = { -0.5, 1.1, 0.3, 1.7 }; std::for_each(data.begin(), data.end(), h); /* - do a weighted fill using bh::weight, a wrapper for any type, - which may appear at the beginning of the argument list + a regular axis is a sequence of semi-open bins; extra under- and + overflow bins extend the axis in the default configuration + index : -1 0 1 2 3 4 5 6 + bin edge: -inf -1.0 -0.5 0.0 0.5 1.0 1.5 2.0 inf + */ + h(-1.5); // put in underflow bin + h(-1.0); // included in first bin, bin interval is semi-open + h(2.0); // put in overflow bin, bin interval is semi-open + h(20.0); // put in overflow bin + + /* + do a weighted fill using bh::weight, a wrapper for any type, + which may appear at the beginning of the argument list */ h(bh::weight(1.0), 0.1); /* - iterate over bins, loop excludes under- and overflow bins - - index 0_c is a compile-time number, the only way in C++ to make - axis(...) to return a different type for each index - - for-loop yields instances of `bin_type`, usually is a semi-open - interval representing the bin, whose edges can be accessed with - methods `lower()` and `upper()`, but the choice depends on the - axis type, please look it up in the reference - - `operator()` returns the bin counter at index, you can then - access its `value() and `variance()` methods; the first returns the - actual count, the second returns a variance estimate of the count; - a bin_type is convertible into an index - (see Rationale section for what this means) + iterate over bins with a fancy histogram iterator + - order in which bins are iterated over is an implementation detail + - iterator dereferences to histogram::element_type, which is defined by + its storage class; by default something with value() and + variance() methods; the first returns the + actual count, the second returns a variance estimate of the count; + a bin_type is convertible into an index + (see Rationale section for what this means) + - idx(N) method returns the index of the N-th axis + - bin(N_c) method returns current bin of N-th axis; the suffx _c turns + the argument into a compile-time number, which is needed to return + different `bin_type`s for different axes + - `bin_type` usually is a semi-open interval representing the bin, whose + edges can be accessed with methods `lower()` and `upper()`, but the + implementation depends on the axis, please look it up in the reference */ - for (auto bin : h.axis(0_c)) { - std::cout << "bin " << bin.idx() << " x in [" - << bin.lower() << ", " << bin.upper() << "): " - << h.at(bin).value() << " +/- " - << std::sqrt(h.at(bin).variance()) - << std::endl; + for (auto it = h.begin(); it != h.end(); ++it) { + const auto bin = it.bin(0_c); + std::cout << "bin " << it.idx(0) << " x in [" + << bin.lower() << ", " << bin.upper() << "): " + << it->value() << " +/- " + << std::sqrt(it->variance()) + << std::endl; } - // accessing under- and overflow bins is easy, use indices -1 and 10 - std::cout << "underflow bin [" << h.axis(0_c)[-1].lower() - << ", " << h.axis(0_c)[-1].upper() << "): " - << h.at(-1).value() << " +/- " << std::sqrt(h.at(-1).variance()) - << std::endl; - std::cout << "overflow bin [" << h.axis(0_c)[10].lower() - << ", " << h.axis(0_c)[10].upper() << "): " - << h.at(10).value() << " +/- " << std::sqrt(h.at(10).variance()) - << std::endl; + /* program output: (note that under- and overflow bins appear at the end) - /* program output: - - bin 0 x in [-1, -0.7): 1 +/- 1 - bin 1 x in [-0.7, -0.4): 1 +/- 1 - bin 2 x in [-0.4, -0.1): 0 +/- 0 - bin 3 x in [-0.1, 0.2): 2.5 +/- 2.5 - bin 4 x in [0.2, 0.5): 1 +/- 1 - bin 5 x in [0.5, 0.8): 0 +/- 0 - bin 6 x in [0.8, 1.1): 4 +/- 2 - bin 7 x in [1.1, 1.4): 1 +/- 1 - bin 8 x in [1.4, 1.7): 0 +/- 0 - bin 9 x in [1.7, 2): 1 +/- 1 - underflow bin [-inf, -1): 1 +/- 1 - overflow bin [2, inf): 2 +/- 1.41421 + bin 0 x in [-1.0, -0.5): 1 +/- 1 + bin 1 x in [-0.5, 0.0): 0 +/- 0 + bin 2 x in [ 0.0, 0.5): 1 +/- 1 + bin 3 x in [ 0.5, 1.0): 0 +/- 0 + bin 4 x in [ 1.0, 1.5): 1 +/- 1 + bin 5 x in [ 1.5, 2.0): 0 +/- 0 + bin 6 x in [ 2.0, inf): 2 +/- 1.41421 + bin -1 x in [-inf, -1): 1 +/- 1 */ } diff --git a/include/boost/histogram/dynamic_histogram.hpp b/include/boost/histogram/dynamic_histogram.hpp index ef64ec47..71709a3f 100644 --- a/include/boost/histogram/dynamic_histogram.hpp +++ b/include/boost/histogram/dynamic_histogram.hpp @@ -200,6 +200,12 @@ public: return at_impl(detail::classify_container_t(), std::forward(t)); } + template + const_reference operator[](T&&t) const { + // check whether T is unpackable + return at_impl(detail::classify_container_t(), std::forward(t)); + } + /// Number of axes (dimensions) of histogram unsigned dim() const noexcept { return axes_.size(); } diff --git a/include/boost/histogram/static_histogram.hpp b/include/boost/histogram/static_histogram.hpp index 452dc034..37830c2f 100644 --- a/include/boost/histogram/static_histogram.hpp +++ b/include/boost/histogram/static_histogram.hpp @@ -195,6 +195,12 @@ public: return storage_[idx]; } + template + const_reference operator[](T&&t) const { + // check whether we need to unpack argument + return at_impl(detail::classify_container_t(), std::forward(t)); + } + template const_reference at(T&&t) const { // check whether we need to unpack argument diff --git a/src/python/histogram.cpp b/src/python/histogram.cpp index 03171892..dc529754 100644 --- a/src/python/histogram.cpp +++ b/src/python/histogram.cpp @@ -302,10 +302,18 @@ bp::object histogram_fill(bp::tuple args, bp::dict kwargs) { return bp::object(); } -bp::object histogram_at(bp::tuple args, bp::dict kwargs) { - const pyhistogram & self = bp::extract(args[0]); +bp::object histogram_getitem(const pyhistogram& self, bp::object args) { + bp::extract get_int(args); + if (get_int.check()) { + if (self.dim() != 1) { + PyErr_SetString(PyExc_RuntimeError, "wrong number of arguments"); + bp::throw_error_already_set(); + } - const unsigned dim = bp::len(args) - 1; + return bp::object(self[get_int()]); + } + + const unsigned dim = bp::len(args); if (self.dim() != dim) { PyErr_SetString(PyExc_RuntimeError, "wrong number of arguments"); bp::throw_error_already_set(); @@ -318,19 +326,23 @@ bp::object histogram_at(bp::tuple args, bp::dict kwargs) { bp::throw_error_already_set(); } + int idx[BOOST_HISTOGRAM_AXIS_LIMIT]; + for (unsigned i = 0; i < dim; ++i) + idx[i] = bp::extract(args[i]); + + return bp::object(self.at(span{idx, self.dim()})); +} + +bp::object histogram_at(bp::tuple args, bp::dict kwargs) { + const pyhistogram & self = bp::extract(args[0]); + if (kwargs) { PyErr_SetString(PyExc_RuntimeError, "no keyword arguments allowed"); bp::throw_error_already_set(); } - int idx[BOOST_HISTOGRAM_AXIS_LIMIT]; - for (unsigned i = 0; i < dim; ++i) - idx[i] = bp::extract(args[1 + i]); - - if (dim == 1) - return bp::object(self.at(idx[0])); - else - return bp::object(self.at(span{idx, self.dim()})); + bp::object a = args.slice(1, bp::_); + return histogram_getitem(self, bp::extract(a)); } bp::object histogram_reduce_to(bp::tuple args, bp::dict kwargs) { @@ -411,7 +423,7 @@ void register_histogram() { .def("at", bp::raw_function(histogram_at), ":param int args: indices of the bin (number must match dimension)" "\n:return: bin content") - .def("__getitem__", bp::raw_function(histogram_at), + .def("__getitem__", histogram_getitem, ":param int args: indices of the bin (number must match dimension)" "\n:return: bin content") .def("reduce_to", bp::raw_function(histogram_reduce_to), diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index 60cbe4ed..c8fc5704 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -62,10 +62,6 @@ int expected_moved_from_dim(dynamic_tag, int) { return 0; } template typename Histogram::element_type sum(const Histogram& h) { - // auto result = typename Histogram::element_type(0); - // for (auto x : h) - // result += x; - // return result; return std::accumulate(h.begin(), h.end(), typename Histogram::element_type(0)); } @@ -856,8 +852,9 @@ template void run_tests() { // pair out BOOST_TEST_EQ(h.at(std::make_pair(0, 0)), 1); + BOOST_TEST_EQ(h[std::make_pair(0, 0)], 1); // tuple out - BOOST_TEST_EQ(h.at(std::make_tuple(1, 1)), 1); + BOOST_TEST_EQ(h[std::make_tuple(1, 1)], 1); // vector in, weights h(weight(2), std::vector({0, 2})); @@ -866,8 +863,11 @@ template void run_tests() { // vector BOOST_TEST_EQ(h.at(std::vector({0, 0})).value(), 3); - // // initializer_list - // BOOST_TEST_EQ(h.at({1, 1}).variance(), 5); + BOOST_TEST_EQ(h[std::vector({0, 0})].value(), 3); + // initializer_list + auto i = {1, 1}; + BOOST_TEST_EQ(h.at(i).variance(), 5); + BOOST_TEST_EQ(h[i].variance(), 5); } // bin args out of range @@ -878,6 +878,10 @@ template void run_tests() { BOOST_TEST_THROWS(h1.at(3), std::out_of_range); BOOST_TEST_THROWS(h1.at(std::make_tuple(-2)), std::out_of_range); BOOST_TEST_THROWS(h1.at(std::vector({3})), std::out_of_range); + BOOST_TEST_THROWS(h1[-2], std::out_of_range); + BOOST_TEST_THROWS(h1[3], std::out_of_range); + BOOST_TEST_THROWS(h1[std::make_tuple(-2)], std::out_of_range); + BOOST_TEST_THROWS(h1[std::vector({3})], std::out_of_range); auto h2 = make_histogram(Type(), axis::integer<>(0, 2), @@ -885,6 +889,8 @@ template void run_tests() { BOOST_TEST_THROWS(h2.at(0, -2), std::out_of_range); BOOST_TEST_THROWS(h2.at(std::make_tuple(0, -2)), std::out_of_range); BOOST_TEST_THROWS(h2.at(std::vector({0, -2})), std::out_of_range); + BOOST_TEST_THROWS(h2[std::make_tuple(0, -2)], std::out_of_range); + BOOST_TEST_THROWS(h2[std::vector({0, -2})], std::out_of_range); } // pass histogram to function diff --git a/test/python_suite_test.py b/test/python_suite_test.py index 79d5041e..d552dfb9 100644 --- a/test/python_suite_test.py +++ b/test/python_suite_test.py @@ -11,8 +11,7 @@ import os sys.path.append(os.getcwd()) import unittest from math import pi -from histogram import HAVE_NUMPY -from histogram import histogram +from histogram import HAVE_NUMPY, histogram from histogram.axis import (regular, regular_log, regular_sqrt, regular_cos, regular_pow, circular, variable, category, integer) @@ -470,40 +469,36 @@ class test_histogram(unittest.TestCase): self.assertNotEqual(id(b), id(c)) def test_fill_1d(self): - h0 = histogram(integer(-1, 2, uoflow=False)) - h1 = histogram(integer(-1, 2, uoflow=True)) - for h in (h0, h1): + for uoflow in (False, True): + h = histogram(integer(-1, 2, uoflow=uoflow)) with self.assertRaises(ValueError): h() with self.assertRaises(ValueError): h(1, 2) - h(-10) - h(-1) - h(-1) - h(0) - h(1) - h(1) - h(1) - h(10) - self.assertEqual(hsum(h0).value, 6) - self.assertEqual(h0.axis(0).shape, 3) - self.assertEqual(hsum(h1).value, 8) - self.assertEqual(h1.axis(0).shape, 5) + for x in (-10, -1, -1, 0, 1, 1, 1, 10): + h(x) + self.assertEqual(hsum(h).value, {False: 6, True: 8}[uoflow]) + self.assertEqual(h.axis(0).shape, {False: 3, True: 5}[uoflow]) - for h in (h0, h1): - self.assertEqual(h.at(0).value, 2) - self.assertEqual(h.at(1).value, 1) - self.assertEqual(h.at(2).value, 3) - with self.assertRaises(RuntimeError): - h.at(0, 1).value with self.assertRaises(RuntimeError): h.at(0, foo=None) - self.assertEqual(h.at(0).variance, 2) - self.assertEqual(h.at(1).variance, 1) - self.assertEqual(h.at(2).variance, 3) + with self.assertRaises(RuntimeError): + h.at(0, 1) + with self.assertRaises(RuntimeError): + h[0, 1] - self.assertEqual(h1.at(-1).value, 1) - self.assertEqual(h1.at(3).value, 1) + for get in (lambda h, arg: h.at(arg), + lambda h, arg: h[arg]): + self.assertEqual(get(h, 0).value, 2) + self.assertEqual(get(h, 1).value, 1) + self.assertEqual(get(h, 2).value, 3) + self.assertEqual(get(h, 0).variance, 2) + self.assertEqual(get(h, 1).variance, 1) + self.assertEqual(get(h, 2).variance, 3) + + if uoflow is True: + self.assertEqual(get(h, -1).value, 1) + self.assertEqual(get(h, 3).value, 1) def test_growth(self): h = histogram(integer(-1, 2)) @@ -542,9 +537,11 @@ class test_histogram(unittest.TestCase): [0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]] - for i in range(-uoflow, len(h.axis(0)) + uoflow): - for j in range(-uoflow, len(h.axis(1)) + uoflow): - self.assertEqual(h.at(i, j).value, m[i][j]) + for get in (lambda h, x, y: h.at(x, y), + lambda h, x, y: h[x, y]): + for i in range(-uoflow, len(h.axis(0)) + uoflow): + for j in range(-uoflow, len(h.axis(1)) + uoflow): + self.assertEqual(get(h, i, j).value, m[i][j]) def test_add_2d(self): for uoflow in (False, True):