Skip to content

Commit 1e0c278

Browse files
authored
refactor: boolean axis with reduce and int fill (#422)
* refactor: use custom boolean, faster compile, simpler fill * fix: restore boolean behavior, slighly faster too * refactor: drop boolean workaround, allow slicing
1 parent 2aa0e0f commit 1e0c278

File tree

5 files changed

+69
-30
lines changed

5 files changed

+69
-30
lines changed

include/bh_python/axis.hpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,27 @@ using category_str_growth
108108
BHP_SPECIALIZE_NAME(category_str)
109109
BHP_SPECIALIZE_NAME(category_str_growth)
110110

111-
using boolean = bh::axis::boolean<metadata_t>;
111+
class boolean : public bh::axis::integer<int, metadata_t, option::none_t> {
112+
public:
113+
explicit boolean(metadata_t meta = {})
114+
: integer(0, 2, std::move(meta)) {}
115+
explicit boolean(const boolean& src,
116+
bh::axis::index_type begin,
117+
bh::axis::index_type end,
118+
unsigned merge)
119+
: integer(src, begin, end, merge) {}
120+
boolean(const boolean& other)
121+
: integer(other) {}
122+
123+
bh::axis::index_type index(int x) const noexcept {
124+
return integer::index(x == 0 ? 0 : 1);
125+
}
126+
127+
// We can't specify inclusive, since this could be sliced
128+
};
129+
130+
// Built-in boolean requires bool fill, slower compile, not reducible
131+
// using boolean = bh::axis::boolean<metadata_t>;
112132
BHP_SPECIALIZE_NAME(boolean)
113133

114134
// Axis defined elsewhere
@@ -154,6 +174,11 @@ select(Continuous&&, Discrete&&, Integer&& i, const bh::axis::integer<int, Ts...
154174
return std::forward<Integer>(i)(ax);
155175
}
156176

177+
template <class Continuous, class Discrete, class Boolean, class... Ts>
178+
decltype(auto) select(Continuous&&, Discrete&&, Boolean&& i, const boolean& ax) {
179+
return std::forward<Boolean>(i)(ax);
180+
}
181+
157182
/// Return bin center for continuous axis and bin value for discrete axis
158183
template <class A>
159184
double unchecked_center(const A& ax, bh::axis::index_type i) {

include/bh_python/fill.hpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ namespace variant = boost::variant2;
3232
static_assert(
3333
mp11::mp_empty<mp11::mp_set_difference<
3434
mp11::mp_unique<mp11::mp_transform<bh::axis::traits::value_type, axis_variant>>,
35-
mp11::mp_list<double, int, std::string, bool>>>::value,
36-
"supported value types are double, int, std::string, bool; "
35+
mp11::mp_list<double, int, std::string>>>::value,
36+
"supported value types are double, int, std::string; "
3737
"new axis was added with different value type");
3838

3939
template <class T>
@@ -60,7 +60,7 @@ struct c_array_t<std::string> : std::vector<std::string> {
6060
}
6161
};
6262

63-
// for int, double, bool
63+
// for int, double
6464
template <class T>
6565
bool is_value(py::handle h) {
6666
if(py::isinstance<py::array>(h) && py::cast<py::array>(h).ndim() > 0)
@@ -99,8 +99,6 @@ using arg_t = variant::variant<c_array_t<double>,
9999
double,
100100
c_array_t<int>,
101101
int,
102-
c_array_t<bool>,
103-
bool,
104102
c_array_t<std::string>,
105103
std::string>;
106104

@@ -116,7 +114,7 @@ inline auto get_vargs(const vector_axis_variant& axes, const py::args& args) {
116114
axes,
117115
[args_it = args.begin(), vargs_it = vargs.begin()](const auto& ax) mutable {
118116
using T = bh::axis::traits::value_type<std::decay_t<decltype(ax)>>;
119-
// T is one of: int, double, std::string, bool
117+
// T is one of: int, double, std::string
120118

121119
const auto& x = *args_it++;
122120
auto& v = *vargs_it++;

src/boost_histogram/_internal/axis.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,17 @@ def __init__(self, **kwargs):
616616

617617
def _repr_args(self):
618618
"Return inner part of signature for use in repr"
619-
return ""
619+
if self.size == 2:
620+
return ""
621+
elif self.size == 0:
622+
return "<empty>"
623+
elif self.size == 1 and self.centers[0] < 0.75:
624+
return "<False>"
625+
elif self.size == 1:
626+
return "<True>"
627+
else:
628+
# Shouldn't be possible, can't grow
629+
"<unknown>"
620630

621631
def _repr_kwargs(self):
622632
"""

src/boost_histogram/_internal/hist.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,6 @@ def __getitem__(self, index): # noqa: C901
552552

553553
indexes = self._compute_commonindex(index)
554554

555-
# Workaround for missing "pick" and no reduction on bool axes
556-
pick_bool = {}
557-
558555
# If this is (now) all integers, return the bin contents
559556
# But don't try *dict!
560557
if not hasattr(indexes, "items"):
@@ -569,11 +566,7 @@ def __getitem__(self, index): # noqa: C901
569566
# Compute needed slices and projections
570567
for i, ind in enumerate(indexes):
571568
if hasattr(ind, "__index__"):
572-
if isinstance(self.axes[i]._ax, _core.axis.boolean):
573-
pick_bool[i] = ind
574-
ind = slice(None, None, sum)
575-
else:
576-
ind = slice(ind.__index__(), ind.__index__() + 1, sum)
569+
ind = slice(ind.__index__(), ind.__index__() + 1, sum)
577570

578571
elif not isinstance(ind, slice):
579572
raise IndexError(
@@ -614,13 +607,6 @@ def __getitem__(self, index): # noqa: C901
614607

615608
slices.append(_core.algorithm.slice_and_rebin(i, start, stop, merge))
616609

617-
for i, select in pick_bool.items():
618-
self = self.copy()
619-
view = self.view(flow=True)
620-
view[
621-
tuple(~select if j == i else slice(None) for j in range(self.ndim))
622-
] = 0
623-
624610
reduced = self._hist.reduce(*slices)
625611

626612
if not integrations:

tests/test_histogram.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -667,16 +667,36 @@ def test_pickle_1():
667667
assert a == b
668668

669669

670+
def test_fill_bool_not_bool():
671+
h = bh.Histogram(bh.axis.Boolean())
672+
673+
h.fill([0, 1, 1, 7, -3])
674+
675+
assert_array_equal(h.view(), [1, 4])
676+
677+
670678
def test_pick_bool():
671-
a = bh.Histogram(bh.axis.Boolean(), bh.axis.Boolean(metadata={"one": 1}))
679+
h = bh.Histogram(bh.axis.Boolean(), bh.axis.Boolean(metadata={"one": 1}))
672680

673-
a.fill([True, True, False, False], [True, False, True, True])
674-
a.fill([True, True, True], True)
681+
h.fill([True, True, False, False], [True, False, True, True])
682+
h.fill([True, True, True], True)
683+
684+
assert_array_equal(h[True, :].view(), [1, 4])
685+
assert_array_equal(h[False, :].view(), [0, 2])
686+
assert_array_equal(h[:, False].view(), [0, 1])
687+
assert_array_equal(h[:, True].view(), [2, 4])
688+
689+
690+
def test_slice_bool():
691+
h = bh.Histogram(bh.axis.Boolean())
692+
h.fill([0, 0, 0, 1, 3, 4, -2])
693+
694+
assert_array_equal(h.view(), [3, 4])
695+
assert_array_equal(h[1:].view(), [4])
696+
assert_array_equal(h[:1].view(), [3])
675697

676-
assert_array_equal(a[True, :].view(), [1, 4])
677-
assert_array_equal(a[False, :].view(), [0, 2])
678-
assert_array_equal(a[:, False].view(), [0, 1])
679-
assert_array_equal(a[:, True].view(), [2, 4])
698+
assert_array_equal(h[:1].axes[0].centers, [0.5])
699+
assert_array_equal(h[1:].axes[0].centers, [1.5])
680700

681701

682702
def test_pickle_bool():

0 commit comments

Comments
 (0)