// Copyright 2015-2017 Hans Dembinski // // 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) #include "utility.hpp" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NUMPY #include namespace np = boost::python::numpy; #endif #include #include #include #include #include namespace bp = boost::python; namespace bh = boost::histogram; namespace bha = boost::histogram::axis; template bp::str generic_repr(const T &t) { std::ostringstream os; os << t; return os.str().c_str(); } struct generic_iterator { generic_iterator(bp::object o) : iterable(o), size(bp::len(iterable)) {} bp::object next() { if (idx == size) { PyErr_SetString(PyExc_StopIteration, "No more items."); bp::throw_error_already_set(); } return iterable[idx++]; } bp::object self() { return bp::object(*this); } bp::object iterable; unsigned idx = 0; unsigned size = 0; }; generic_iterator make_generic_iterator(bp::object self) { return generic_iterator(self); } template struct axis_interval_to_python { static PyObject* convert(const bha::interval &i) { return bp::incref(bp::make_tuple(i.lower(), i.upper()).ptr()); } }; template struct pair_int_axis_interval_to_python { static PyObject* convert(const std::pair> &p) { return bp::incref(bp::make_tuple( p.first, bp::make_tuple(p.second.lower(), p.second.upper()) ).ptr()); } }; bp::object variable_init(bp::tuple args, bp::dict kwargs) { bp::object self = args[0]; if (len(args) < 2) { PyErr_SetString(PyExc_TypeError, "require at least two arguments"); bp::throw_error_already_set(); } std::vector v; for (int i = 1, n = len(args); i < n; ++i) { v.push_back(bp::extract(args[i])); } boost::string_view label; auto uo = bha::uoflow::on; while (len(kwargs) > 0) { bp::tuple kv = kwargs.popitem(); boost::string_view k = bp::extract(kv[0])(); bp::object v = kv[1]; if (k == "label") label = bp::extract(v)(); else if (k == "uoflow") { if (!bp::extract(v)) uo = bha::uoflow::off; } else { std::stringstream s; s << "keyword " << k << " not recognized"; PyErr_SetString(PyExc_KeyError, s.str().c_str()); bp::throw_error_already_set(); } } return self.attr("__init__")( bha::variable<>(v.begin(), v.end(), label, uo)); } bp::object category_init(bp::tuple args, bp::dict kwargs) { bp::object self = args[0]; if (bp::len(args) == 1) { PyErr_SetString(PyExc_TypeError, "require at least one argument"); bp::throw_error_already_set(); } boost::string_view label; while (bp::len(kwargs) > 0) { bp::tuple kv = kwargs.popitem(); boost::string_view k = bp::extract(kv[0])(); bp::object v = kv[1]; if (k == "label") label = bp::extract(v)(); else { std::stringstream s; s << "keyword " << k << " not recognized"; PyErr_SetString(PyExc_KeyError, s.str().c_str()); bp::throw_error_already_set(); } } std::vector c; for (int i = 1, n = bp::len(args); i < n; ++i) c.push_back(bp::extract(args[i])); return self.attr("__init__")(bha::category<>(c.begin(), c.end(), label)); } template bp::object axis_getitem(const A &a, int i) { if (i < -1 * a.uoflow() || i >= a.size() + 1 * a.uoflow()) { PyErr_SetString(PyExc_IndexError, "index out of bounds"); bp::throw_error_already_set(); } return bp::object(a[i]); } template void axis_set_label(T& t, bp::str s) { t.label({bp::extract(s)(), static_cast(bp::len(s))}); } template bp::str axis_get_label(const T& t) { auto s = t.label(); return {s.data(), s.size()}; } #ifdef HAVE_NUMPY template bp::object axis_array_interface(const Axis& axis) { using T = typename std::decay::type; bp::dict d; auto shape = bp::make_tuple(axis.size()+1); d["shape"] = shape; d["typestr"] = bp::dtype_typestr(); // make new array, and pass it to Python auto a = np::empty(shape, np::dtype::get_builtin()); auto buf = reinterpret_cast(a.get_data()); for (auto i = 0; i < axis.size()+1; ++i) buf[i] = axis[i].lower(); d["data"] = a; d["version"] = 3; return d; } template <> bp::object axis_array_interface>(const bha::category<>& axis) { bp::dict d; auto shape = bp::make_tuple(axis.size()); d["shape"] = shape; d["typestr"] = bp::dtype_typestr(); // make new array, and pass it to Python auto a = np::empty(shape, np::dtype::get_builtin()); auto buf = reinterpret_cast(a.get_data()); for (auto i = 0; i < axis.size(); ++i) buf[i] = axis[i]; d["data"] = a; d["version"] = 3; return d; } #endif template struct axis_suite : public bp::def_visitor> { template static void visit(Class &cl) { cl.add_property( "shape", &T::shape, "Number of bins, including over-/underflow bins if they are present."); cl.add_property( "label", axis_get_label, axis_set_label, "Name or description for the axis."); cl.def("index", &T::index, ":param float x: value" "\n:returns: bin index for the passed value", bp::args("self", "x")); cl.def("__len__", &T::size, ":returns: number of bins, excluding over-/underflow bins.", bp::arg("self")); cl.def("__getitem__", axis_getitem, ":param integer i: bin index" "\n:returns: bin corresponding to index", bp::args("self", "i")); cl.def("__iter__", make_generic_iterator); cl.def("__repr__", generic_repr, ":returns: string representation of this axis", bp::arg("self")); cl.def(bp::self == bp::self); #ifdef HAVE_NUMPY cl.add_property("__array_interface__", &axis_array_interface); #endif } }; template bha::regular* regular_init( unsigned bin, double lower, double upper, bp::str pylabel, bool with_uoflow) { const auto uo = with_uoflow ? bha::uoflow::on : bha::uoflow::off; return new bha::regular(bin, lower, upper, {bp::extract(pylabel)(), static_cast(bp::len(pylabel))}, uo); } bha::regular* regular_pow_init( unsigned bin, double lower, double upper, double power, bp::str pylabel, bool with_uoflow) { using namespace ::boost::python; const auto uo = with_uoflow ? bha::uoflow::on : bha::uoflow::off; return new bha::regular( bin, lower, upper, {extract(pylabel)(), static_cast(len(pylabel))}, uo, power); } bha::integer<>* integer_init(int lower, int upper, bp::str pylabel, bool with_uoflow) { using namespace ::boost::python; const auto uo = with_uoflow ? bha::uoflow::on : bha::uoflow::off; return new bha::integer<>(lower, upper, {extract(pylabel)(), static_cast(len(pylabel))}, uo); } void register_axis_types() { using namespace ::boost::python; using bp::arg; // resolve ambiguity docstring_options dopt(true, true, false); to_python_converter< bha::interval, axis_interval_to_python >(); to_python_converter< bha::interval, axis_interval_to_python >(); to_python_converter< std::pair>, pair_int_axis_interval_to_python >(); to_python_converter< std::pair>, pair_int_axis_interval_to_python >(); class_("generic_iterator", init()) .def("__iter__", &generic_iterator::self) .def("next", &generic_iterator::next); class_>( "regular", "Axis for real-valued data and bins of equal width." "\nBinning is a O(1) operation.", no_init) .def("__init__", make_constructor(regular_init, default_call_policies(), (arg("bin"), arg("lower"), arg("upper"), arg("label")="", arg("uoflow")=true))) .def(axis_suite>()); #define BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(x) \ class_>( \ "regular_"#x, \ "Axis for real-valued data and bins of equal width in "#x"-space." \ "\nBinning is a O(1) operation.", \ no_init) \ .def("__init__", make_constructor(regular_init, \ default_call_policies(), \ (arg("bin"), arg("lower"), arg("upper"), \ arg("label")="", arg("uoflow")=true))) \ .def(axis_suite>()) BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(log); BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(sqrt); BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(cos); class_>( "regular_pow", "Axis for real-valued data and bins of equal width in power-space." "\nBinning is a O(1) operation.", no_init) .def("__init__", make_constructor(regular_pow_init, default_call_policies(), (arg("bin"), arg("lower"), arg("upper"), arg("power"), arg("label")="", arg("uoflow")=true))) .def(axis_suite>()); class_>( "circular", "Axis for real-valued angles." "\nThere are no overflow/underflow bins for this axis," "\nsince the axis is circular and wraps around after reaching" "\nthe perimeter value. Binning is a O(1) operation.", no_init) .def(init( (arg("self"), arg("bin"), arg("phase") = 0.0, arg("perimeter") = boost::math::double_constants::two_pi, arg("label") = ""))) .def(axis_suite>()); class_>( "variable", "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 axis.", no_init) .def("__init__", raw_function(variable_init)) .def(init &>()) .def(axis_suite>()); class_>( "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__", make_constructor(integer_init, default_call_policies(), (arg("lower"), arg("upper"), arg("label") = "", arg("uoflow") = true))) .def(axis_suite>()); class_>( "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 &>()) .def(axis_suite>()); }