interface break for axis, new category axis

This commit is contained in:
Hans Dembinski
2017-11-02 21:43:04 +01:00
parent e0304abc71
commit 757d4b0ca4
17 changed files with 610 additions and 518 deletions

View File

@@ -10,10 +10,17 @@
#include <boost/python.hpp>
#include <boost/python/def_visitor.hpp>
#include <boost/python/raw_function.hpp>
#ifdef HAVE_NUMPY
#define NO_IMPORT_ARRAY
#define PY_ARRAY_UNIQUE_SYMBOL boost_histogram_ARRAY_API
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>
#endif
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
#include <utility>
namespace boost {
namespace histogram {
@@ -82,31 +89,27 @@ python::object category_init(python::tuple args, python::dict kwargs) {
}
}
std::vector<std::string> c;
std::vector<int> c;
for (int i = 1, n = len(args); i < n; ++i)
c.push_back(extract<std::string>(args[i]));
c.push_back(extract<int>(args[i]));
return self.attr("__init__")(axis::category(c.begin(), c.end(), label));
return self.attr("__init__")(axis::category<>(c.begin(), c.end(), label));
}
template <typename T> int axis_len(const T &t) {
return t.bins() + int(std::is_floating_point<typename T::value_type>::value);
}
template <typename T> python::object axis_getitem(const T &t, int i) {
if (i == axis_len(t)) {
template <typename A> python::object axis_getitem(const A &a, int i) {
if (i == a.size()) {
PyErr_SetString(PyExc_StopIteration, "no more");
python::throw_error_already_set();
}
return python::object(t[i]);
return python::object(a[i]);
}
template <> python::object axis_getitem(const axis::category &a, int i) {
if (i == axis_len(a)) {
template <> python::object axis_getitem(const axis::category<> &a, int i) {
if (i == a.size()) {
PyErr_SetString(PyExc_StopIteration, "no more");
python::throw_error_already_set();
}
return python::object(a[i].data());
return python::object(a[i]);
}
template <typename T> std::string axis_repr(const T &t) {
@@ -126,31 +129,75 @@ template <typename T> python::str axis_get_label(const T& t) {
return {s.data(), s.size()};
}
#ifdef HAVE_NUMPY
template <typename Axis> python::object axis_array_interface(const Axis& axis) {
python::dict d;
auto shape = python::make_tuple(axis.size()+1);
d["shape"] = shape;
// d["typestr"] = dtype_typestr<typename Axis::bin_type>();
d["typestr"] = "|f8";
// make new array, and pass it to Python
auto dim = 1;
npy_intp shapes2[1] = { axis.size()+1 };
auto *a = (PyArrayObject*)PyArray_SimpleNew(dim, shapes2, NPY_DOUBLE);
auto *buf = (double *)PyArray_DATA(a);
PyArray_CLEARFLAGS(a, NPY_ARRAY_WRITEABLE);
// auto a = python::numpy::empty(shape, python::numpy::dtype::get_builtin<typename Axis::bin_type>());
// auto buf = reinterpret_cast<typename Axis::bin_type*>(axis.get_data());
for (auto i = 0; i < axis.size()+1; ++i)
buf[i] = axis[i].lower();
d["data"] = python::object(python::handle<>((PyObject*)a));
d["version"] = 3;
return d;
}
template <> python::object axis_array_interface<axis::category<>>(const axis::category<>& axis) {
python::dict d;
auto shape = python::make_tuple(axis.size());
d["shape"] = shape;
// d["typestr"] = dtype_typestr<typename Axis::bin_type>();
d["typestr"] = "|i4";
// make new array, and pass it to Python
auto dim = 1;
npy_intp shapes2[1] = { axis.size() };
auto *a = (PyArrayObject*)PyArray_SimpleNew(dim, shapes2, NPY_INT);
auto *buf = (int *)PyArray_DATA(a);
PyArray_CLEARFLAGS(a, NPY_ARRAY_WRITEABLE);
// auto a = python::numpy::empty(shape, python::numpy::dtype::get_builtin<typename Axis::bin_type>());
// auto buf = reinterpret_cast<typename Axis::bin_type*>(axis.get_data());
for (auto i = 0; i < axis.size(); ++i)
buf[i] = axis[i];
d["data"] = python::object(python::handle<>((PyObject*)a));
d["version"] = 3;
return d;
}
#endif
template <class T>
struct axis_suite : public python::def_visitor<axis_suite<T>> {
template <class Class> static void visit(Class &cl) {
cl.add_property("bins", &T::bins, "Number of bins.");
cl.add_property(
"shape", &T::shape,
"Number of bins, including possible over- and underflow bins.");
"Number of bins, including over-/underflow bins if they are present.");
cl.add_property(
"label", axis_get_label<T>, axis_set_label<T>,
"Name or description for the axis.");
cl.def("index", &T::index, ":param float x: value"
"\n:returns: bin index for the passed value",
python::args("self", "x"));
cl.def("__len__", axis_len<T>, ":returns: number of bins for this axis",
cl.def("__len__", &T::size,
":returns: number of bins, excluding over-/underflow bins.",
python::arg("self"));
cl.def("__getitem__", axis_getitem<T>,
is_same<T, axis::integer<>>::value
? ":returns: integer mapped to passed bin index"
: is_same<T, axis::category>::value
? ":returns: category mapped to passed bin index"
: ":returns: low edge of the bin",
python::args("self", "index"));
":param integer i: bin index"
"\n:returns: bin corresponding to index",
python::args("self", "i"));
cl.def("__repr__", axis_repr<T>,
":returns: string representation of this axis", python::arg("self"));
cl.def(python::self == python::self);
#ifdef HAVE_NUMPY
cl.add_property("__array_interface__", &axis_array_interface<T>);
#endif
}
};
@@ -161,12 +208,35 @@ void register_axis_types() {
using python::arg;
docstring_options dopt(true, true, false);
class_<axis::regular<>>("regular",
"An axis for real-valued data and bins of equal width."
"\nBinning is a O(1) operation.",
no_init)
class_<interval<double>>(
"interval_double",
no_init)
.add_property("lower",
make_function(&interval<double>::lower,
return_value_policy<return_by_value>()))
.add_property("upper",
make_function(&interval<double>::upper,
return_value_policy<return_by_value>()))
;
class_<interval<int>>(
"interval_int",
no_init)
.add_property("lower",
make_function(&interval<int>::lower,
return_value_policy<return_by_value>()))
.add_property("upper",
make_function(&interval<int>::upper,
return_value_policy<return_by_value>()))
;
class_<axis::regular<>>(
"regular",
"An axis for real-valued data and bins of equal width."
"\nBinning is a O(1) operation.",
no_init)
.def(init<unsigned, double, double, const std::string &, bool>(
(arg("self"), arg("bin"), arg("min"), arg("max"),
(arg("self"), arg("bin"), arg("lower"), arg("upper"),
arg("label") = std::string(), arg("uoflow") = true)))
.def(axis_suite<axis::regular<>>());
@@ -187,32 +257,33 @@ void register_axis_types() {
"variable",
"An axis for real-valued data and bins of varying width."
"\nBinning is a O(log(N)) operation. If speed matters and"
"\nthe problem domain allows it, prefer a regular.",
"\nthe problem domain allows it, prefer a regular axis.",
no_init)
.def("__init__", raw_function(variable_init))
.def(init<const axis::variable<> &>())
.def(axis_suite<axis::variable<>>());
class_<axis::integer<>>("integer",
"An axis for a contiguous range of integers."
"\nThere are no underflow/overflow bins for this axis."
"\nBinning is a O(1) operation.",
no_init)
class_<axis::integer<>>(
"integer",
"An axis for a contiguous range of integers with bins"
"\nthat are one integer wide. Faster than a regular axis."
"\nBinning is a O(1) operation.",
no_init)
.def(init<int, int, const std::string &, bool>(
(arg("self"), arg("min"), arg("max"), arg("label") = std::string(),
(arg("self"), arg("lower"), arg("upper"), arg("label") = std::string(),
arg("uoflow") = true)))
.def(axis_suite<axis::integer<>>());
class_<axis::category>("category",
"An axis for enumerated categories. The axis stores the"
"\ncategory labels, and expects that they are addressed"
"\nusing an integer from 0 to n-1. There are no"
"\nunderflow/overflow bins for this axis."
"\nBinning is a O(1) operation.",
no_init)
class_<axis::category<>>(
"category",
"An axis for set of unique integer values. Each value is mapped to"
"\na corresponding bin, following the order of the arguments in"
"\nthe constructor."
"\nBinning is a O(1) operation.",
no_init)
.def("__init__", raw_function(category_init))
.def(init<const axis::category &>())
.def(axis_suite<axis::category>());
.def(init<const axis::category<> &>())
.def(axis_suite<axis::category<>>());
}
}
}

View File

@@ -205,7 +205,7 @@ python::object histogram_init(python::tuple args, python::dict kwargs) {
axes.push_back(ei());
continue;
}
python::extract<axis::category> ec(pa);
python::extract<axis::category<>> ec(pa);
if (ec.check()) {
axes.push_back(ec());
continue;
@@ -354,7 +354,7 @@ python::object histogram_value(python::tuple args, python::dict kwargs) {
for (unsigned i = 0; i < self.dim(); ++i)
idx[i] = python::extract<int>(args[1 + i]);
return python::object(self.value(idx + 0, idx + self.dim()));
return python::object(self.value(idx, idx + self.dim()));
}
python::object histogram_variance(python::tuple args, python::dict kwargs) {
@@ -383,7 +383,7 @@ python::object histogram_variance(python::tuple args, python::dict kwargs) {
for (unsigned i = 0; i < self.dim(); ++i)
idx[i] = python::extract<int>(args[1 + i]);
return python::object(self.variance(idx + 0, idx + self.dim()));
return python::object(self.variance(idx, idx + self.dim()));
}
std::string histogram_repr(const dynamic_histogram &h) {

View File

@@ -4,8 +4,8 @@
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef _BOOST_PYTHON_SERIALIZATION_SUITE_HPP_
#define _BOOST_PYTHON_SERIALIZATION_SUITE_HPP_
#ifndef _BOOST_HISTOGRAM_PYTHON_SERIALIZATION_SUITE_HPP_
#define _BOOST_HISTOGRAM_PYTHON_SERIALIZATION_SUITE_HPP_
#include <algorithm>
#include <boost/archive/text_iarchive.hpp>