improving examples, and fixed bug in Python histogram constructor, now all axis types are recognized

This commit is contained in:
Hans Dembinski 2017-11-09 11:38:51 +01:00
parent 9dd822b6e7
commit b40a68bb54
12 changed files with 228 additions and 229 deletions

View File

@ -1,9 +1,11 @@
[section Getting started]
To get you started, here are some commented usage examples.
To get you started quickly, here are some heavily commented examples to copy paste from. If you prefer a more traditional, structured exposition, check out the [link histogram.guide full user guide].
[section Make and use a static 1d-histogram in C++]
If possible, use the static histogram. It is faster and user errors are caught at compile time.
[c++]``
#include <boost/histogram.hpp>
#include <iostream>
@ -12,13 +14,15 @@ int main(int, char**) {
namespace bh = boost::histogram;
using namespace bh::literals; // enables _c suffix
// create static 1d-histogram with 10 equidistant bins from -1.0 to 2.0,
// with axis of histogram labeled 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")
);
// fill histogram with data
// fill histogram with data, typically this would happen in a loop
h.fill(-1.5); // put in underflow bin
h.fill(-1.0); // included in first bin, bin interval is semi-open
h.fill(-0.5);
@ -29,37 +33,50 @@ int main(int, char**) {
h.fill(20.0); // put in overflow bin
/*
instead of calling h.fill(...) with same argument N times,
use bh::count, which accepts an integer argument N
use bh::count(N) if you would otherwise call h.fill(...) with
*same* argument N times, N is an integer argument
*/
h.fill(1.0, bh::count(4));
/*
to fill a weighted entry, use bh::weight, which accepts a double
argument; don't confuse with bh::count, it has a different effect
on the variance (see Rationale for a section explaining weighted fills)
do a weighted fill using bh::weight, which accepts a double
- don't mix this with bh::count, both have a different effect on the
variance (see Rationale for an explanation regarding weights)
- if you don't know what this is good for, use bh::count instead,
it is most likeliy what you want and it is more efficient
*/
h.fill(0.1, bh::weight(2.5));
/*
iterate over bins, loop excludes under- and overflow bins
- index 0_c is a compile-time number to make axis(...) return
a different type for each axis
- for-loop yields std::pair<[bin index], [bin type]>, where
[bin type] usually is a semi-open interval representing the bin,
whose edges can be accessed with methods lower() and upper(), but
the [bin type] depends on the axis and could be something else
- value(index) method returns the bin count at index,
- variance(index) method returns a variance estimate of the bin count
at index (see Rationale for a section explaining the variance)
- 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 `std::pair<int, bin_type>`, where
`bin_type` usually is a semi-open interval representing the bin,
whose edges can be accessed with methods `lower()` and `upper()`,
but the [bin type] depends on the axis, look it up in the reference
- `value(index)` method returns the bin count at index
- `variance(index)` method returns a variance estimate of the bin
count at index (see Rationale section for what this means)
*/
for (const auto& bin : h.axis(0_c)) {
std::cout << "bin " << bin.first
<< " x in [" << bin.second.lower() << ", " << bin.second.upper() << "): "
<< h.value(bin.first) << " +/- " << std::sqrt(h.variance(bin.first))
std::cout << "bin " << bin.first << " x in ["
<< bin.second.lower() << ", " << bin.second.upper() << "): "
<< h.value(bin.first) << " +/- "
<< std::sqrt(h.variance(bin.first))
<< 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.value(-1) << " +/- " << std::sqrt(h.variance(-1))
<< std::endl;
std::cout << "overflow bin [" << h.axis(0_c)[10].lower()
<< ", " << h.axis(0_c)[10].upper() << "): "
<< h.value(10) << " +/- " << std::sqrt(h.variance(10))
<< std::endl;
/* program output:
bin 0 x in [-1, -0.7): 1 +/- 1
@ -72,6 +89,8 @@ int main(int, char**) {
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
*/
}
@ -79,57 +98,71 @@ int main(int, char**) {
[endsect]
[section Make and use a dynamic 2d-histogram in C++]
[section Make and use a dynamic 3d-histogram in C++]
Here we fill the histogram with some random numbers.
Dynamic histograms are a bit slower than static histograms, but still faster than other libraries. Use a dynamic histogram when you only know at runtime how to layout the histogram, for example, because you wrote a graphical user interface that uses Boost.Histogram underneath.
[c++]``
#include <boost/histogram.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <cstdlib>
#include <string>
namespace br = boost::random;
namespace bh = boost::histogram;
int main() {
/*
create dynamic histogram using `make_dynamic_histogram`
- axis can be passed directly, just like for `make_static_histogram`
- in addition, also accepts iterators over a sequence of axes
create a dynamic histogram with the factory `make_dynamic_histogram`
- axis can be passed directly just like for `make_static_histogram`
- in addition, the factory also accepts iterators over a sequence of
axis::any, the polymorphic type that can hold concrete axis types
*/
std::vector<bh::axis::any<>> axes = {bh::axis::regular<>(5, -5, 5, "x"),
bh::axis::regular<>(5, -5, 5, "y")};
std::vector<bh::axis::any<>> axes;
axes.emplace_back(bh::axis::category<std::string>({"red", "blue"}));
axes.emplace_back(bh::axis::regular<>(5, -5, 5, "x"));
axes.emplace_back(bh::axis::regular<>(5, -5, 5, "y"));
auto h = bh::make_dynamic_histogram(axes.begin(), axes.end());
// fill histogram, random numbers are generated on the fly
// fill histogram with random numbers
br::mt19937 gen;
br::normal_distribution<> norm;
for (int i = 0; i < 1000; ++i)
h.fill(norm(gen), norm(gen));
h.fill(i % 2 ? "red" : "blue", norm(gen), norm(gen));
/*
print histogram
print dynamic histogram by iterating over bins
- for most axis types, the for loop looks just like for a static
histogram, except that we can pass runtime numbers, too
- in contrast to the static histogram, we need to cast axis::any
to the held axis type before looping, if the [bin type] is not
convertible to a double interval
- if the [bin type] of the axis is not convertible to a
double interval, one needs to cast axis::any before looping;
this is here the case for the category axis
*/
for (const auto& ybin : h.axis(1)) { // rows
for (const auto& xbin : h.axis(0)) { // columns
std::printf("%3.0f ", h.value(xbin.first, ybin.first));
using cas = bh::axis::category<std::string>;
for (const auto& cbin : bh::axis::cast<cas>(h.axis(0))) {
std::printf("%s\n", cbin.second.c_str());
for (const auto& ybin : h.axis(2)) { // rows
for (const auto& xbin : h.axis(1)) { // columns
std::printf("%3.0f ", h.value(cbin.first, xbin.first, ybin.first));
}
std::printf("\n");
}
std::printf("\n");
}
}
``
[note
If you care about maximum performance: In this example, `axis::category<std::string>` is used with two string labels "red" and "blue". It is faster to use an enum, `enum { red, blue };` and a `axis::category<>` axis.
]
[endsect]
[section Make and use a 2d-histogram in Python]
You need to build the library with Numpy support to run this example.
[python]`import histogram as hg
[python]``
import histogram as hg
import numpy as np
# create 2d-histogram with two axes with 10 equidistant bins from -3 to 3
@ -153,7 +186,7 @@ y = np.array(h.axis(1))
# creates a view of the counts (no copy involved)
count_matrix = np.asarray(h)
# cut off the under- and overflow bins (no copy involved)
# cut off the under- and overflow bins to not confuse matplotib (no copy)
reduced_count_matrix = count_matrix[:-2,:-2]
try:
@ -164,8 +197,44 @@ try:
plt.ylabel(h.axis(1).label)
plt.savefig("example_2d_python.png")
except ImportError:
# ok, no matplotlib, then just print it
# ok, no matplotlib, then just print the full count matrix
print count_matrix
# output of the print looks something like this, the two right-most rows
# and two down-most columns represent under-/overflow bins
# [[ 0 0 0 1 5 0 0 1 0 0 0 0]
# [ 0 0 0 1 17 11 6 0 0 0 0 0]
# [ 0 0 0 5 31 26 4 1 0 0 0 0]
# [ 0 0 3 20 59 62 26 4 0 0 0 0]
# [ 0 0 1 26 96 89 16 1 0 0 0 0]
# [ 0 0 4 21 86 84 20 1 0 0 0 0]
# [ 0 0 1 24 71 50 15 2 0 0 0 0]
# [ 0 0 0 6 26 37 7 0 0 0 0 0]
# [ 0 0 0 0 11 10 2 0 0 0 0 0]
# [ 0 0 0 1 2 3 1 0 0 0 0 0]
# [ 0 0 0 0 0 2 0 0 0 0 0 0]
# [ 0 0 0 0 0 1 0 0 0 0 0 0]]
``
[endsect]
[section Make and use a 1d-histogram in Python without Numpy]
Building the library with Numpy support is highly recommended, but just for completeness, here is an example on how to use the library without Numpy support.
[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"))
# fill histogram with numbers
for x in (1e0, 1e1, 1e2, 1e3, 1e4):
h.fill(x)
# iterate over bins
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)
``
[endsect]

View File

@ -1,4 +1,4 @@
[section User guide]
[section:guide User guide]
How to create and work with histograms is described here. This library is designed to make simple things simple, yet complex things possible. For a quick start, you don't need to read the complete user guide; have a look into the tutorial and the examples instead. This guide covers the basic and more advanced usage of the library.
@ -72,7 +72,6 @@ ranges from `begin` to `end`, where `end` is also not included.
When you work with [classref boost::histogram::histogram<Dynamic,...>], you can also create a histogram from a run-time compiled collection of axis objects:
[c++]``
// also see examples/create_dynamic_histogram.cpp
#include <boost/histogram.hpp>
#include <vector>
@ -131,7 +130,6 @@ int main() {
Here is a second example which using a weighted fill in a functional programming style. The input values are taken from a container:
[c++]``
// also see examples/create_dynamic_histogram.cpp
#include <boost/histogram.hpp>
#include <algorithm>
#include <vector>

View File

@ -1,51 +0,0 @@
#include <boost/histogram.hpp>
#include <boost/histogram/histogram_ostream_operators.hpp>
#include <vector>
#include <cstdlib>
namespace bh = boost::histogram;
/*
command line usage: cmd [i a b|r n x y]
a,b,n: integers
x,y : floats
*/
int main(int argc, char** argv) {
auto v = std::vector<bh::axis::any<>>();
// parse arguments
auto argi = 1;
while (argi < argc) {
switch (argv[argi][0]) {
case 'i': {
++argi;
auto a = std::atoi(argv[argi]);
++argi;
auto b = std::atoi(argv[argi]);
++argi;
v.push_back(bh::axis::integer<>(a, b));
}
break;
case 'r': {
++argi;
auto n = std::atoi(argv[argi]);
++argi;
auto x = std::atof(argv[argi]);
++argi;
auto y = std::atof(argv[argi]);
++argi;
v.push_back(bh::axis::regular<>(n, x, y));
}
break;
default:
std::cerr << "unknown argument " << argv[argi] << std::endl;
return 1;
}
}
auto h = bh::histogram<bh::Dynamic, bh::axis::builtins>(v.begin(), v.end());
// do something with h
std::cout << "you created the following histogram:\n" << h << std::endl;
}

View File

@ -5,13 +5,15 @@ int main(int, char**) {
namespace bh = boost::histogram;
using namespace bh::literals; // enables _c suffix
// create static 1d-histogram with 10 equidistant bins from -1.0 to 2.0,
// with axis of histogram labeled 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")
);
// fill histogram with data
// fill histogram with data, typically this would happen in a loop
h.fill(-1.5); // put in underflow bin
h.fill(-1.0); // included in first bin, bin interval is semi-open
h.fill(-0.5);
@ -22,37 +24,50 @@ int main(int, char**) {
h.fill(20.0); // put in overflow bin
/*
instead of calling h.fill(...) with same argument N times,
use bh::count, which accepts an integer argument N
use bh::count(N) if you would otherwise call h.fill(...) with
*same* argument N times, N is an integer argument
*/
h.fill(1.0, bh::count(4));
/*
to fill a weighted entry, use bh::weight, which accepts a double
argument; don't confuse with bh::count, it has a different effect
on the variance (see Rationale for a section explaining weighted fills)
do a weighted fill using bh::weight, which accepts a double
- don't mix this with bh::count, both have a different effect on the
variance (see Rationale for an explanation regarding weights)
- if you don't know what this is good for, use bh::count instead,
it is most likeliy what you want and it is more efficient
*/
h.fill(0.1, bh::weight(2.5));
/*
iterate over bins, loop excludes under- and overflow bins
- index 0_c is a compile-time number to make axis(...) return
a different type for each axis
- for-loop yields std::pair<[bin index], [bin type]>, where
[bin type] usually is a semi-open interval representing the bin,
whose edges can be accessed with methods lower() and upper(), but
the [bin type] depends on the axis and could be something else
- value(index) method returns the bin count at index,
- variance(index) method returns a variance estimate of the bin count
at index (see Rationale for a section explaining the variance)
- 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 `std::pair<int, bin_type>`, where
`bin_type` usually is a semi-open interval representing the bin,
whose edges can be accessed with methods `lower()` and `upper()`,
but the [bin type] depends on the axis, look it up in the reference
- `value(index)` method returns the bin count at index
- `variance(index)` method returns a variance estimate of the bin
count at index (see Rationale section for what this means)
*/
for (const auto& bin : h.axis(0_c)) {
std::cout << "bin " << bin.first
<< " x in [" << bin.second.lower() << ", " << bin.second.upper() << "): "
<< h.value(bin.first) << " +/- " << std::sqrt(h.variance(bin.first))
std::cout << "bin " << bin.first << " x in ["
<< bin.second.lower() << ", " << bin.second.upper() << "): "
<< h.value(bin.first) << " +/- "
<< std::sqrt(h.variance(bin.first))
<< 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.value(-1) << " +/- " << std::sqrt(h.variance(-1))
<< std::endl;
std::cout << "overflow bin [" << h.axis(0_c)[10].lower()
<< ", " << h.axis(0_c)[10].upper() << "): "
<< h.value(10) << " +/- " << std::sqrt(h.variance(10))
<< std::endl;
/* program output:
bin 0 x in [-1, -0.7): 1 +/- 1
@ -65,6 +80,8 @@ int main(int, char**) {
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
*/
}

View File

@ -2,38 +2,46 @@
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <cstdlib>
#include <string>
namespace br = boost::random;
namespace bh = boost::histogram;
int main() {
/*
create dynamic histogram using `make_dynamic_histogram`
- axis can be passed directly, just like for `make_static_histogram`
- in addition, also accepts iterators over a sequence of axes
create a dynamic histogram with the factory `make_dynamic_histogram`
- axis can be passed directly just like for `make_static_histogram`
- in addition, the factory also accepts iterators over a sequence of
axis::any, the polymorphic type that can hold concrete axis types
*/
std::vector<bh::axis::any<>> axes = {bh::axis::regular<>(5, -5, 5, "x"),
bh::axis::regular<>(5, -5, 5, "y")};
std::vector<bh::axis::any<>> axes;
axes.emplace_back(bh::axis::category<std::string>({"red", "blue"}));
axes.emplace_back(bh::axis::regular<>(5, -5, 5, "x"));
axes.emplace_back(bh::axis::regular<>(5, -5, 5, "y"));
auto h = bh::make_dynamic_histogram(axes.begin(), axes.end());
// fill histogram, random numbers are generated on the fly
// fill histogram with random numbers
br::mt19937 gen;
br::normal_distribution<> norm;
for (int i = 0; i < 1000; ++i)
h.fill(norm(gen), norm(gen));
h.fill(i % 2 ? "red" : "blue", norm(gen), norm(gen));
/*
print histogram
print dynamic histogram by iterating over bins
- for most axis types, the for loop looks just like for a static
histogram, except that we can pass runtime numbers, too
- in contrast to the static histogram, we need to cast axis::any
to the held axis type before looping, if the [bin type] is not
convertible to a double interval
- if the [bin type] of the axis is not convertible to a
double interval, one needs to cast axis::any before looping;
this is here the case for the category axis
*/
for (const auto& ybin : h.axis(1)) { // rows
for (const auto& xbin : h.axis(0)) { // columns
std::printf("%3.0f ", h.value(xbin.first, ybin.first));
using cas = bh::axis::category<std::string>;
for (const auto& cbin : bh::axis::cast<cas>(h.axis(0))) {
std::printf("%s\n", cbin.second.c_str());
for (const auto& ybin : h.axis(2)) { // rows
for (const auto& xbin : h.axis(1)) { // columns
std::printf("%3.0f ", h.value(cbin.first, xbin.first, ybin.first));
}
std::printf("\n");
}
std::printf("\n");
}
}

View File

@ -22,7 +22,7 @@ y = np.array(h.axis(1))
# creates a view of the counts (no copy involved)
count_matrix = np.asarray(h)
# cut off the under- and overflow bins (no copy involved)
# cut off the under- and overflow bins to not confuse matplotib (no copy)
reduced_count_matrix = count_matrix[:-2,:-2]
try:
@ -33,5 +33,20 @@ try:
plt.ylabel(h.axis(1).label)
plt.savefig("example_2d_python.png")
except ImportError:
# ok, no matplotlib, then just print it
# ok, no matplotlib, then just print the full count matrix
print count_matrix
# output of the print looks something like this, the two right-most rows
# and two down-most columns represent under-/overflow bins
# [[ 0 0 0 1 5 0 0 1 0 0 0 0]
# [ 0 0 0 1 17 11 6 0 0 0 0 0]
# [ 0 0 0 5 31 26 4 1 0 0 0 0]
# [ 0 0 3 20 59 62 26 4 0 0 0 0]
# [ 0 0 1 26 96 89 16 1 0 0 0 0]
# [ 0 0 4 21 86 84 20 1 0 0 0 0]
# [ 0 0 1 24 71 50 15 2 0 0 0 0]
# [ 0 0 0 6 26 37 7 0 0 0 0 0]
# [ 0 0 0 0 11 10 2 0 0 0 0 0]
# [ 0 0 0 1 2 3 1 0 0 0 0 0]
# [ 0 0 0 0 0 2 0 0 0 0 0 0]
# [ 0 0 0 0 0 1 0 0 0 0 0 0]]

View File

@ -1,4 +1,3 @@
// also see examples/create_dynamic_histogram.cpp
#include <boost/histogram.hpp>
#include <vector>

View File

@ -1,4 +1,3 @@
// also see examples/create_dynamic_histogram.cpp
#include <boost/histogram.hpp>
#include <algorithm>
#include <vector>

View File

@ -1,4 +1,3 @@
// also see examples/create_dynamic_histogram.cpp
#include <boost/histogram.hpp>
#include <algorithm>
#include <vector>

View File

@ -1,19 +0,0 @@
import histogram as hg
import numpy as np
h = hg.histogram(hg.axis.regular(10, -3, 3, "x"))
h.fill(np.random.randn(1000))
x = np.array(h.axis(0)) # axis instances behave like sequences
y = np.asarray(h) # creates a view (no copy involved)
y = y[:len(h.axis(0))] # cut off underflow/overflow bins; y[:-2] also works
y = np.append(y, [0]) # extra zero needed by matplotlib's plot(...) function
try:
import matplotlib.pyplot as plt
plt.plot(x, y, drawstyle="steps-post")
plt.xlabel(h.axis(0).label)
plt.ylabel("counts")
plt.savefig("example_1d_python.png")
except ImportError:
pass

View File

@ -41,66 +41,33 @@ namespace axis {
enum class uoflow { off = false, on = true };
namespace detail {
// similar to boost::reference_wrapper, but with default ctor
template <typename T> class cref {
public:
cref() = default;
cref(const cref &o) : ptr_(o.ptr_) {}
cref &operator=(const cref &o) {
ptr_ = o.ptr_;
return *this;
}
cref(const T &t) : ptr_(&t) {}
cref &operator=(const T &t) {
ptr_ = &t;
return *this;
}
operator const T &() const { return *ptr_; }
private:
const T *ptr_ = nullptr;
};
template <typename Axis>
using axis_iterator_value_t = std::pair<
int, typename conditional<
is_reference<typename Axis::bin_type>::value,
cref<typename remove_reference<typename Axis::bin_type>::type>,
typename Axis::bin_type>::type>;
} // namespace detail
template <typename Axis>
class axis_iterator
: public iterator_facade<axis_iterator<Axis>,
detail::axis_iterator_value_t<Axis>,
random_access_traversal_tag> {
std::pair<int, typename Axis::bin_type>,
random_access_traversal_tag,
std::pair<int, typename Axis::bin_type>> {
public:
using value_type = detail::axis_iterator_value_t<Axis>;
explicit axis_iterator(const Axis &axis, int idx) : axis_(axis) {
value_.first = idx;
}
explicit axis_iterator(const Axis &axis, int idx) : axis_(axis), idx_(idx) {}
axis_iterator(const axis_iterator &o) = default;
axis_iterator &operator=(const axis_iterator &o) = default;
private:
void increment() noexcept { ++value_.first; }
void decrement() noexcept { --value_.first; }
void advance(int n) noexcept { value_.first += n; }
void increment() noexcept { ++idx_; }
void decrement() noexcept { --idx_; }
void advance(int n) noexcept { idx_ += n; }
int distance_to(const axis_iterator &other) const noexcept {
return other.value_.first - value_.first;
return other.idx_ - idx_;
}
bool equal(const axis_iterator &other) const noexcept {
return value_.first == other.value_.first;
return idx_ == other.idx_;
}
value_type &dereference() const {
value_.second = axis_[value_.first];
return value_;
std::pair<int, typename Axis::bin_type> dereference() const {
return std::make_pair(idx_, axis_[idx_]);
}
const Axis &axis_;
mutable value_type value_;
const Axis& axis_;
int idx_;
friend class boost::iterator_core_access;
};
@ -552,7 +519,7 @@ template <typename T> class category : public axis_base {
public:
using value_type = T;
using bin_type = const value_type &;
using bin_type = T;
using const_iterator = axis_iterator<category<T>>;
category() = default;

View File

@ -15,6 +15,7 @@
#include <boost/shared_ptr.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
#include <boost/mpl/for_each.hpp>
#ifdef HAVE_NUMPY
#include <boost/python/numpy.hpp>
namespace np = boost::python::numpy;
@ -121,6 +122,22 @@ struct axis_visitor : public static_visitor<python::object> {
}
};
struct axes_appender {
python::object obj;
std::vector<dynamic_histogram::any_axis_type>& axes;
bool& success;
axes_appender(python::object o, std::vector<dynamic_histogram::any_axis_type>& a,
bool& s) : obj(o), axes(a), success(s) {}
template <typename A> void operator()(const A&) const {
if (success) return;
python::extract<const A&> get_axis(obj);
if (get_axis.check()) {
axes.emplace_back(get_axis());
success = true;
}
}
};
python::object histogram_axis(const dynamic_histogram &self, int i) {
if (i < 0)
i += self.dim();
@ -147,35 +164,16 @@ python::object histogram_init(python::tuple args, python::dict kwargs) {
std::vector<dynamic_histogram::any_axis_type> axes;
for (unsigned i = 0; i < dim; ++i) {
python::object pa = args[i + 1];
python::extract<axis::regular<>> er(pa);
if (er.check()) {
axes.push_back(er());
continue;
bool success = false;
mpl::for_each<dynamic_histogram::any_axis_type::types>(
axes_appender(pa, axes, success)
);
if (!success) {
std::string msg = "require an axis object, got ";
msg += python::extract<std::string>(pa.attr("__class__"))();
PyErr_SetString(PyExc_TypeError, msg.c_str());
python::throw_error_already_set();
}
python::extract<axis::circular<>> ep(pa);
if (ep.check()) {
axes.push_back(ep());
continue;
}
python::extract<axis::variable<>> ev(pa);
if (ev.check()) {
axes.push_back(ev());
continue;
}
python::extract<axis::integer<>> ei(pa);
if (ei.check()) {
axes.push_back(ei());
continue;
}
python::extract<axis::category<>> ec(pa);
if (ec.check()) {
axes.push_back(ec());
continue;
}
std::string msg = "require an axis object, got ";
msg += python::extract<std::string>(pa.attr("__class__").attr("__name__"))();
PyErr_SetString(PyExc_TypeError, msg.c_str());
python::throw_error_already_set();
}
dynamic_histogram h(axes.begin(), axes.end());
return pyinit(h);