Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 050127a

Browse files
committed
ENH: Added countbits (popcount)
ENH, DOC: Added countbits (popcount) ENH: Popcount implementation ENH: Add popcount to umath ENH: Added countbits (popcount) to umath `__all__` ENH: Refined popcount logic DOC: Added `bit_count` Co-authored-by: Eric Wieser <[email protected]> MAINT: Renamed `countbits` to `bit_count` MAINT: Fixed 4 1s magic number DOC: Added `popcount` to docstring ENH: Added bit_count annotations ENH: Added GNU/CLANG popcount DOC: Added `popcount` language example ENH, BUG: Moved `bitcount` to npy_math.h as `popcount` | Fixed final right shift ENH: Enable `popcount` for signed TST: Tests for `bit_count` BUG, DOC: (BUG) Added missing typecast causing an unwanted upcast (DOC) Added more details on `popcount` implementation MAINT, BUG: (MAINT) Refined `popcount` TC to use typecode (BUG) Fixed ufunc.ntypes to include signed ints ENH: Added windows builtin support ENH: Added `popcount` implementation for big python ints natively [1/2] `popcount` object loop changes ENH: Object loop for `bit_count` [2/2] `popcount` object loop changes TST: Refined `bit_count` tests and added object type ENH: Added `bit_count` to `np.int*` DOC: Added `np.bit_count` (#19355) MAINT: Various linting and minor fixes: 1. Fixed passing all args to _internals umath bitcount. Note: We use kwargs here that might hinder performance 2. Fixed linting errors. 3. Improved verbosity of logs 4. Made a generic TO_BITS_LEN macro to accomdate more length based functions in future BENCH: Added bit_count (popcount) MAINT: Style nits | Added signed case DOC, MAINT: Improved example ENH: Added annotations for bit_count TST: Added annotations tests for bit_count MAINT: Fixed linting errors MAINT: Moved Magic constants to npy_math_internal MAINT: Remove python implementation | Added 3.10 check to tests DOC: Added abs value usage to doc MAINT: Resolved merge conflicts
1 parent d4b2d4f commit 050127a

File tree

17 files changed

+172
-8
lines changed

17 files changed

+172
-8
lines changed

benchmarks/benchmarks/bench_ufunc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
ufuncs = ['abs', 'absolute', 'add', 'arccos', 'arccosh', 'arcsin', 'arcsinh',
7-
'arctan', 'arctan2', 'arctanh', 'bitwise_and', 'bitwise_not',
7+
'arctan', 'arctan2', 'arctanh', 'bit_count', 'bitwise_and', 'bitwise_not',
88
'bitwise_or', 'bitwise_xor', 'cbrt', 'ceil', 'conj', 'conjugate',
99
'copysign', 'cos', 'cosh', 'deg2rad', 'degrees', 'divide', 'divmod',
1010
'equal', 'exp', 'exp2', 'expm1', 'fabs', 'float_power', 'floor',
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
`np.bit_count` to compute the number of 1-bits in an integer
2+
------------------------------------------------------------
3+
4+
This new function counts the number of 1-bits in a number.
5+
These work on all the numpy integer types, as well as the
6+
builtin arbitrary-precision `Decimal` and `long` types.
7+
8+
.. code-block:: python
9+
10+
>>> a = np.array([2**i - 1 for i in range(16)])
11+
>>> np.bit_count(a)
12+
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])

doc/source/reference/routines.math.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,5 @@ Miscellaneous
179179
real_if_close
180180

181181
interp
182+
183+
bit_count

numpy/__init__.pyi

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,6 +2517,17 @@ class ndarray(_ArrayOrScalarCommon, Generic[_ShapeType, _DType_co]):
25172517
def __dlpack__(self: NDArray[number[Any]], *, stream: None = ...) -> _PyCapsule: ...
25182518
def __dlpack_device__(self) -> tuple[int, L[0]]: ...
25192519

2520+
def bit_count(
2521+
self,
2522+
out: None | NDArray[Any] = ...,
2523+
*,
2524+
where: _ArrayLikeBool_co = ...,
2525+
casting: _CastingKind = ...,
2526+
order: _OrderKACF = ...,
2527+
dtype: DTypeLike = ...,
2528+
subok: bool = ...,
2529+
) -> NDArray[Any]: ...
2530+
25202531
# Keep `dtype` at the bottom to avoid name conflicts with `np.dtype`
25212532
@property
25222533
def dtype(self) -> _DType_co: ...
@@ -2660,6 +2671,17 @@ class generic(_ArrayOrScalarCommon):
26602671
self: _ScalarType, *shape: SupportsIndex, order: _OrderACF = ...
26612672
) -> ndarray[Any, _dtype[_ScalarType]]: ...
26622673

2674+
def bit_count(
2675+
self,
2676+
out: None | NDArray[Any] = ...,
2677+
*,
2678+
where: _ArrayLikeBool_co = ...,
2679+
casting: _CastingKind = ...,
2680+
order: _OrderKACF = ...,
2681+
dtype: DTypeLike = ...,
2682+
subok: bool = ...,
2683+
) -> Any: ...
2684+
26632685
def squeeze(
26642686
self: _ScalarType, axis: None | L[0] | tuple[()] = ...
26652687
) -> _ScalarType: ...
@@ -3229,6 +3251,7 @@ arcsinh: _UFunc_Nin1_Nout1[L['arcsinh'], L[8], None]
32293251
arctan2: _UFunc_Nin2_Nout1[L['arctan2'], L[5], None]
32303252
arctan: _UFunc_Nin1_Nout1[L['arctan'], L[8], None]
32313253
arctanh: _UFunc_Nin1_Nout1[L['arctanh'], L[8], None]
3254+
bit_count: _UFunc_Nin1_Nout1[L['bit_count'], L[11], None]
32323255
bitwise_and: _UFunc_Nin2_Nout1[L['bitwise_and'], L[12], L[-1]]
32333256
bitwise_not: _UFunc_Nin1_Nout1[L['invert'], L[12], None]
32343257
bitwise_or: _UFunc_Nin2_Nout1[L['bitwise_or'], L[12], L[0]]

numpy/core/_methods.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
umr_minimum = um.minimum.reduce
2121
umr_sum = um.add.reduce
2222
umr_prod = um.multiply.reduce
23+
umr_bit_count = um.bit_count
2324
umr_any = um.logical_or.reduce
2425
umr_all = um.logical_and.reduce
2526

@@ -295,3 +296,8 @@ def _dump(self, file, protocol=2):
295296

296297
def _dumps(self, protocol=2):
297298
return pickle.dumps(self, protocol=protocol)
299+
300+
def _bit_count(a, out=None, *, where=True, casting='same_kind',
301+
order='K', dtype=None, subok=True):
302+
return umr_bit_count(a, out, where=where, casting=casting,
303+
order=order, dtype=dtype, subok=subok)

numpy/core/code_generators/generate_umath.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,13 @@ def english_upper(s):
981981
TD(ints),
982982
TD('O', f='npy_ObjectLCM'),
983983
),
984+
'bit_count':
985+
Ufunc(1, 1, None,
986+
docstrings.get('numpy.core.umath.bit_count'),
987+
None,
988+
TD(ints),
989+
TD('O', f='npy_ObjectPopCount'),
990+
),
984991
'matmul' :
985992
Ufunc(2, 1, None,
986993
docstrings.get('numpy.core.umath.matmul'),

numpy/core/code_generators/ufunc_docstrings.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4214,3 +4214,37 @@ def add_newdoc(place, name, doc):
42144214
array([ 0, 20, 20, 60, 20, 20])
42154215
42164216
""")
4217+
4218+
add_newdoc('numpy.core.umath', 'bit_count',
4219+
"""
4220+
Computes the number of 1-bits in the absolute value of ``x``.
4221+
Analogous to the builtin `int.bit_count` or ``popcount`` in C++.
4222+
4223+
Parameters
4224+
----------
4225+
x : array_like, unsigned int
4226+
Input array.
4227+
$PARAMS
4228+
4229+
Returns
4230+
-------
4231+
y : ndarray
4232+
The corresponding number of 1-bits in the input.
4233+
$OUT_SCALAR_1
4234+
4235+
References
4236+
----------
4237+
.. [1] https://stackoverflow.com/a/109025/5671364
4238+
4239+
.. [2] Wikipedia, "Hamming weight",
4240+
https://en.wikipedia.org/wiki/Hamming_weight
4241+
4242+
Examples
4243+
--------
4244+
>>> np.bit_count(1023)
4245+
10
4246+
>>> a = np.array([2**i - 1 for i in range(16)])
4247+
>>> np.bit_count(a)
4248+
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
4249+
4250+
""")

numpy/core/src/multiarray/methods.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ array_ptp(PyArrayObject *self, PyObject *args, PyObject *kwds)
354354
NPY_FORWARD_NDARRAY_METHOD("_ptp");
355355
}
356356

357+
static PyObject *
358+
array_bit_count(PyArrayObject *self, PyObject *args, PyObject *kwds)
359+
{
360+
NPY_FORWARD_NDARRAY_METHOD("_bit_count");
361+
}
362+
357363

358364
static PyObject *
359365
array_swapaxes(PyArrayObject *self, PyObject *args)
@@ -3076,9 +3082,11 @@ NPY_NO_EXPORT PyMethodDef array_methods[] = {
30763082
{"__dlpack__",
30773083
(PyCFunction)array_dlpack,
30783084
METH_FASTCALL | METH_KEYWORDS, NULL},
3079-
30803085
{"__dlpack_device__",
30813086
(PyCFunction)array_dlpack_device,
30823087
METH_NOARGS, NULL},
3088+
{"bit_count",
3089+
(PyCFunction)array_bit_count,
3090+
METH_VARARGS | METH_KEYWORDS, NULL},
30833091
{NULL, NULL, 0, NULL} /* sentinel */
30843092
};

numpy/core/src/multiarray/scalartypes.c.src

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1566,7 +1566,7 @@ gentype_byteswap(PyObject *self, PyObject *args, PyObject *kwds)
15661566
* std, var, sum, cumsum, prod, cumprod, compress, sort, argsort,
15671567
* round, argmax, argmin, max, min, ptp, any, all, astype, resize,
15681568
* reshape, choose, tostring, tobytes, copy, searchsorted, view,
1569-
* flatten, ravel, squeeze#
1569+
* flatten, ravel, squeeze, bit_count#
15701570
*/
15711571
static PyObject *
15721572
gentype_@name@(PyObject *self, PyObject *args, PyObject *kwds)
@@ -2192,6 +2192,9 @@ static PyMethodDef gentype_methods[] = {
21922192
{"sum",
21932193
(PyCFunction)gentype_sum,
21942194
METH_VARARGS | METH_KEYWORDS, NULL},
2195+
{"bit_count",
2196+
(PyCFunction)gentype_bit_count,
2197+
METH_VARARGS | METH_KEYWORDS, NULL},
21952198
{"cumsum",
21962199
(PyCFunction)gentype_cumsum,
21972200
METH_VARARGS | METH_KEYWORDS, NULL},

numpy/core/src/npymath/npy_math_internal.h.src

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,6 @@ npy_rshift@u@@c@(npy_@u@@type@ a, npy_@u@@type@ b)
678678
/**end repeat1**/
679679
/**end repeat**/
680680

681-
682681
#define __popcnt32 __popcnt
683682
/**begin repeat
684683
*

numpy/core/src/umath/funcs.inc.src

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,21 @@ npy_ObjectClip(PyObject *arr, PyObject *min, PyObject *max) {
267267
return o;
268268
}
269269

270+
static PyObject *
271+
npy_ObjectPopCount(PyObject *obj) {
272+
PyObject *result = NULL;
273+
274+
/* Try to use inbuilt popcount if available */
275+
static PyObject *builtin_popcount_func = NULL;
276+
builtin_popcount_func = PyObject_GetAttrString(obj, "bit_count");
277+
278+
if (builtin_popcount_func != NULL) {
279+
result = PyObject_CallFunction(builtin_popcount_func, NULL);
280+
}
281+
282+
return result;
283+
}
284+
270285
/*
271286
*****************************************************************************
272287
** COMPLEX FUNCTIONS **

numpy/core/src/umath/loops.c.src

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,15 @@ NPY_NO_EXPORT void
569569
UNARY_LOOP_FAST(@type@, @type@, *out = +in);
570570
}
571571

572+
NPY_NO_EXPORT void
573+
@TYPE@_bit_count(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
574+
{
575+
UNARY_LOOP {
576+
const @type@ in1 = *(@type@ *)ip1;
577+
*((@type@ *)op1) = npy_popcount@c@(in1);
578+
}
579+
}
580+
572581
/**begin repeat1
573582
* #isa = , _avx2#
574583
* #CHK = 1, defined(HAVE_ATTRIBUTE_TARGET_AVX2)#

numpy/core/src/umath/loops.h.src

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,10 @@ NPY_NO_EXPORT void
202202
@S@@TYPE@_@kind@(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func));
203203
/**end repeat2**/
204204

205-
/**end repeat1**/
205+
NPY_NO_EXPORT void
206+
@S@@TYPE@_bit_count(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func));
206207

208+
/**end repeat1**/
207209
/**end repeat**/
208210

209211
/*

numpy/core/tests/test_umath.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2361,7 +2361,15 @@ def test_reduce(self):
23612361

23622362
class TestBitwiseUFuncs:
23632363

2364-
bitwise_types = [np.dtype(c) for c in '?' + 'bBhHiIlLqQ' + 'O']
2364+
_all_ints_bits = [
2365+
np.dtype(c).itemsize * 8 for c in np.typecodes["AllInteger"]]
2366+
bitwise_types = [
2367+
np.dtype(c) for c in '?' + np.typecodes["AllInteger"] + 'O']
2368+
bitwise_bits = [
2369+
2, # boolean type
2370+
*_all_ints_bits, # All integers
2371+
max(_all_ints_bits) + 1, # Object_ type
2372+
]
23652373

23662374
def test_values(self):
23672375
for dt in self.bitwise_types:
@@ -2442,6 +2450,30 @@ def test_reduction(self):
24422450
btype = np.array([True], dtype=object)
24432451
assert_(type(f.reduce(btype)) is bool, msg)
24442452

2453+
@pytest.mark.parametrize("input_dtype_obj, bitsize",
2454+
zip(bitwise_types, bitwise_bits))
2455+
def test_popcount(self, input_dtype_obj, bitsize):
2456+
input_dtype = input_dtype_obj.type
2457+
2458+
# bit_count is only in-built in 3.10+
2459+
if sys.version_info < (3, 10) and input_dtype == np.object_:
2460+
pytest.skip()
2461+
2462+
for i in range(1, bitsize):
2463+
num = 2**i - 1
2464+
msg = f"bit_count for {num}"
2465+
assert i == np.bit_count(input_dtype(num)), msg
2466+
if np.issubdtype(
2467+
input_dtype, np.signedinteger) or input_dtype == np.object_:
2468+
assert i == np.bit_count(input_dtype(-num)), msg
2469+
2470+
a = np.array([2**i-1 for i in range(1, bitsize)], dtype=input_dtype)
2471+
bit_count_a = np.bit_count(a)
2472+
expected = np.arange(1, bitsize, dtype=input_dtype)
2473+
2474+
msg = f"array bit_count for {input_dtype}"
2475+
assert all(bit_count_a == expected), msg
2476+
24452477

24462478
class TestInt:
24472479
def test_logical_not(self):

numpy/core/umath.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
'UFUNC_PYVALS_NAME', '_add_newdoc_ufunc', 'absolute', 'add',
2323
'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh',
2424
'bitwise_and', 'bitwise_or', 'bitwise_xor', 'cbrt', 'ceil', 'conj',
25-
'conjugate', 'copysign', 'cos', 'cosh', 'deg2rad', 'degrees', 'divide',
25+
'conjugate', 'copysign', 'cos', 'cosh', 'bit_count', 'deg2rad', 'degrees', 'divide',
2626
'divmod', 'e', 'equal', 'euler_gamma', 'exp', 'exp2', 'expm1', 'fabs',
2727
'floor', 'floor_divide', 'float_power', 'fmax', 'fmin', 'fmod', 'frexp',
2828
'frompyfunc', 'gcd', 'geterrobj', 'greater', 'greater_equal', 'heaviside',

numpy/matrixlib/tests/test_defmatrix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ def test_instance_methods(self):
286286
'partition', 'argpartition',
287287
'take', 'tofile', 'tolist', 'tostring', 'tobytes', 'all', 'any',
288288
'sum', 'argmax', 'argmin', 'min', 'max', 'mean', 'var', 'ptp',
289-
'prod', 'std', 'ctypes', 'itemset',
289+
'prod', 'std', 'ctypes', 'itemset', 'bit_count',
290290
]
291291
for attrib in dir(a):
292292
if attrib.startswith('_') or attrib in excluded_methods:

numpy/typing/tests/data/reveal/ufuncs.pyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import numpy as np
22
import numpy.typing as npt
33

4+
i8: np.int64
45
f8: np.float64
56
AR_f8: npt.NDArray[np.float64]
67
AR_i8: npt.NDArray[np.int64]
@@ -66,3 +67,14 @@ reveal_type(np.matmul.signature) # E: Literal['(n?,k),(k,m?)->(n?,m?)']
6667
reveal_type(np.matmul.identity) # E: None
6768
reveal_type(np.matmul(AR_f8, AR_f8)) # E: Any
6869
reveal_type(np.matmul(AR_f8, AR_f8, axes=[(0, 1), (0, 1), (0, 1)])) # E: Any
70+
71+
reveal_type(np.bit_count.__name__) # E: Literal['bit_count']
72+
reveal_type(np.bit_count.ntypes) # E: Literal[11]
73+
reveal_type(np.bit_count.identity) # E: None
74+
reveal_type(np.bit_count.nin) # E: Literal[1]
75+
reveal_type(np.bit_count.nout) # E: Literal[1]
76+
reveal_type(np.bit_count.nargs) # E: Literal[2]
77+
reveal_type(np.bit_count.signature) # E: None
78+
reveal_type(np.bit_count.identity) # E: None
79+
reveal_type(np.bit_count(i8)) # E: Any
80+
reveal_type(np.bit_count(AR_i8)) # E: Any

0 commit comments

Comments
 (0)