From 0923ef3be20251d28de904b8d311154bb9f8582f Mon Sep 17 00:00:00 2001 From: Hans Dembinski Date: Thu, 9 Nov 2017 12:10:16 +0100 Subject: [PATCH] support count keyword on the python side --- doc/getting_started.qbk | 13 +++--- examples/getting_started_listing_4.py | 13 ++++++ src/python/histogram.cpp | 65 +++++++++++++++++---------- test/python_suite_test.py | 9 ++-- 4 files changed, 66 insertions(+), 34 deletions(-) create mode 100644 examples/getting_started_listing_4.py diff --git a/doc/getting_started.qbk b/doc/getting_started.qbk index 8bd13cc4..94b0b19c 100644 --- a/doc/getting_started.qbk +++ b/doc/getting_started.qbk @@ -225,16 +225,17 @@ Building the library with Numpy support is highly recommended, but just for comp [python]`` import histogram as hg -# make 1-d histogram with 10 logarithmic bins from 1e0 to 1e5 -h = hg.histogram(hg.axis.regular_log(10, 1e0, 1e5, "x")) +# make 1-d histogram with 5 logarithmic bins from 1e0 to 1e5 +h = hg.histogram(hg.axis.regular_log(5, 1e0, 1e5, "x")) # fill histogram with numbers -for x in (1e0, 1e1, 1e2, 1e3, 1e4): - h.fill(x) +for x in (2e0, 2e1, 2e2, 2e3, 2e4): + h.fill(x, count=2) -# iterate over bins +# iterate over bins and access bin counter for idx, (lower, upper) in enumerate(h.axis(0)): - print "bin %i x in [%g, %g): %g +/- %g" % (idx, lower, upper, h.value(idx), h.variance(idx) ** 0.5) + print "bin {0} x in [{1}, {2}): {3} +/- {4}".format( + idx, lower, upper, h.value(idx), h.variance(idx) ** 0.5) `` [endsect] diff --git a/examples/getting_started_listing_4.py b/examples/getting_started_listing_4.py new file mode 100644 index 00000000..e2bab209 --- /dev/null +++ b/examples/getting_started_listing_4.py @@ -0,0 +1,13 @@ +import histogram as hg + +# make 1-d histogram with 5 logarithmic bins from 1e0 to 1e5 +h = hg.histogram(hg.axis.regular_log(5, 1e0, 1e5, "x")) + +# fill histogram with numbers +for x in (2e0, 2e1, 2e2, 2e3, 2e4): + h.fill(x, count=2) + +# iterate over bins and access bin counter +for idx, (lower, upper) in enumerate(h.axis(0)): + print "bin {0} x in [{1}, {2}): {3} +/- {4}".format( + idx, lower, upper, h.value(idx), h.variance(idx) ** 0.5) diff --git a/src/python/histogram.cpp b/src/python/histogram.cpp index 5558002b..a1c9adc9 100644 --- a/src/python/histogram.cpp +++ b/src/python/histogram.cpp @@ -179,32 +179,33 @@ python::object histogram_init(python::tuple args, python::dict kwargs) { return pyinit(h); } +template struct fetcher { - long n = 0; + long n = -1; union { - double value = 0; - const double* carray; + T value = 0; + const T* carray; }; python::object keep_alive; void assign(python::object o) { // skipping check for currently held type, since it is always value - python::extract get_double(o); - if (get_double.check()) { - value = get_double(); + python::extract get_value(o); + if (get_value.check()) { + value = get_value(); n = 0; return; } #ifdef HAVE_NUMPY - np::ndarray a = np::from_object(o, np::dtype::get_builtin(), 1); - carray = reinterpret_cast(a.get_data()); + np::ndarray a = np::from_object(o, np::dtype::get_builtin(), 1); + carray = reinterpret_cast(a.get_data()); n = a.shape(0); keep_alive = a; // this may be a temporary object return; #endif throw std::invalid_argument("argument must be a number"); } - double get(long i) const noexcept { + T get(long i) const noexcept { if (n > 0) return carray[i]; return value; @@ -228,7 +229,7 @@ python::object histogram_fill(python::tuple args, python::dict kwargs) { python::throw_error_already_set(); } - fetcher fetch[BOOST_HISTOGRAM_AXIS_LIMIT]; + fetcher fetch[BOOST_HISTOGRAM_AXIS_LIMIT]; long n = 0; for (auto d = 0u; d < dim; ++d) { fetch[d].assign(args[1 + d]); @@ -241,22 +242,37 @@ python::object histogram_fill(python::tuple args, python::dict kwargs) { } } - fetcher fetch_weight; - bool use_weight = false; + fetcher fetch_weight; + fetcher fetch_count; const auto nkwargs = python::len(kwargs); if (nkwargs > 0) { - if (nkwargs > 1 || !kwargs.has_key("weight")) { - PyErr_SetString(PyExc_RuntimeError, "only keyword weight allowed"); + const bool use_weight = kwargs.has_key("weight"); + const bool use_count = kwargs.has_key("count"); + if (nkwargs > 1 || (use_weight == use_count)) { // may not be both true or false + PyErr_SetString(PyExc_RuntimeError, "only keyword weight or count allowed"); python::throw_error_already_set(); } - use_weight = true; - fetch_weight.assign(kwargs.get("weight")); - if (fetch_weight.n > 0) { - if (n > 0 && fetch_weight.n != n) { - PyErr_SetString(PyExc_ValueError, "length of weight sequence does not match"); - python::throw_error_already_set(); + + if (use_weight) { + fetch_weight.assign(kwargs.get("weight")); + if (fetch_weight.n > 0) { + if (n > 0 && fetch_weight.n != n) { + PyErr_SetString(PyExc_ValueError, "length of weight sequence does not match"); + python::throw_error_already_set(); + } + n = fetch_weight.n; + } + } + + if (use_count) { + fetch_count.assign(kwargs.get("count")); + if (fetch_count.n > 0) { + if (n > 0 && fetch_count.n != n) { + PyErr_SetString(PyExc_ValueError, "length of count sequence does not match"); + python::throw_error_already_set(); + } + n = fetch_count.n; } - n = fetch_weight.n; } } @@ -265,11 +281,12 @@ python::object histogram_fill(python::tuple args, python::dict kwargs) { for (auto i = 0l; i < n; ++i) { for (auto d = 0u; d < dim; ++d) v[d] = fetch[d].get(i); - if (use_weight) { + if (fetch_weight.n >= 0) self.fill(v, v + dim, weight(fetch_weight.get(i))); - } else { + else if (fetch_count.n >= 0) + self.fill(v, v + dim, count(fetch_count.get(i))); + else self.fill(v, v + dim); - } } return python::object(); diff --git a/test/python_suite_test.py b/test/python_suite_test.py index 7076bc24..d80bbbbf 100644 --- a/test/python_suite_test.py +++ b/test/python_suite_test.py @@ -662,8 +662,9 @@ class test_histogram(unittest.TestCase): @unittest.skipUnless(have_numpy, "requires build with numpy-support") def test_numpy_conversion_0(self): a = histogram(integer(0, 3, uoflow=False)) - for i in range(100): + for i in range(10): a.fill(1) + a.fill(1, count=90) c = numpy.array(a) # a copy v = numpy.asarray(a) # a view @@ -671,14 +672,14 @@ class test_histogram(unittest.TestCase): self.assertEqual(t.dtype, numpy.uint8) self.assertTrue(numpy.all(t == numpy.array((0, 100, 0)))) - for i in range(100): + for i in range(20): a.fill(1) + a.fill(1, count=2 * numpy.ones(40, dtype=numpy.uint32)) # copy does not change, but view does self.assertTrue(numpy.all(c == numpy.array((0, 100, 0)))) self.assertTrue(numpy.all(v == numpy.array((0, 200, 0)))) - for i in range(100): - a.fill(1) + a.fill(1, count=100) c = numpy.array(a) self.assertEqual(c.dtype, numpy.uint16) self.assertTrue(numpy.all(c == numpy.array((0, 300, 0))))