Skip to content

Commit

Permalink
Merge pull request numpy#14464 from mattip/matmul-bool
Browse files Browse the repository at this point in the history
BUG: add a specialized loop for boolean matmul
  • Loading branch information
seberg authored Sep 12, 2019
2 parents 46621d2 + 7ff2f96 commit 0006674
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 6 deletions.
6 changes: 6 additions & 0 deletions doc/release/upcoming_changes/14464.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
`numpy.matmul` with boolean output now converts to boolean values
-----------------------------------------------------------------
Calling `numpy.matmul` where the output is a boolean array would fill the array
with uint8 equivalents of the result, rather than 0/1. Now it forces the output
to 0 or 1 (``NPY_TRUE`` or ``NPY_FALSE``).

47 changes: 41 additions & 6 deletions numpy/core/src/umath/matmul.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,14 @@ NPY_NO_EXPORT void
* FLOAT, DOUBLE, HALF,
* CFLOAT, CDOUBLE, CLONGDOUBLE,
* UBYTE, USHORT, UINT, ULONG, ULONGLONG,
* BYTE, SHORT, INT, LONG, LONGLONG,
* BOOL#
* BYTE, SHORT, INT, LONG, LONGLONG#
* #typ = npy_longdouble,
* npy_float,npy_double,npy_half,
* npy_cfloat, npy_cdouble, npy_clongdouble,
* npy_ubyte, npy_ushort, npy_uint, npy_ulong, npy_ulonglong,
* npy_byte, npy_short, npy_int, npy_long, npy_longlong,
* npy_bool#
* #IS_COMPLEX = 0, 0, 0, 0, 1, 1, 1, 0*11#
* #IS_HALF = 0, 0, 0, 1, 0*14#
* npy_byte, npy_short, npy_int, npy_long, npy_longlong#
* #IS_COMPLEX = 0, 0, 0, 0, 1, 1, 1, 0*10#
* #IS_HALF = 0, 0, 0, 1, 0*13#
*/

NPY_NO_EXPORT void
Expand Down Expand Up @@ -266,7 +264,44 @@ NPY_NO_EXPORT void
}

/**end repeat**/
NPY_NO_EXPORT void
BOOL_matmul_inner_noblas(void *_ip1, npy_intp is1_m, npy_intp is1_n,
void *_ip2, npy_intp is2_n, npy_intp is2_p,
void *_op, npy_intp os_m, npy_intp os_p,
npy_intp dm, npy_intp dn, npy_intp dp)

{
npy_intp m, n, p;
npy_intp ib2_p, ob_p;
char *ip1 = (char *)_ip1, *ip2 = (char *)_ip2, *op = (char *)_op;

ib2_p = is2_p * dp;
ob_p = os_p * dp;

for (m = 0; m < dm; m++) {
for (p = 0; p < dp; p++) {
char *ip1tmp = ip1;
char *ip2tmp = ip2;
*(npy_bool *)op = NPY_FALSE;
for (n = 0; n < dn; n++) {
npy_bool val1 = (*(npy_bool *)ip1tmp);
npy_bool val2 = (*(npy_bool *)ip2tmp);
if (val1 != 0 && val2 != 0) {
*(npy_bool *)op = NPY_TRUE;
break;
}
ip2tmp += is2_n;
ip1tmp += is1_n;
}
op += os_p;
ip2 += is2_p;
}
op -= ob_p;
ip2 -= ib2_p;
ip1 += is1_m;
op += os_m;
}
}

NPY_NO_EXPORT void
OBJECT_matmul_inner_noblas(void *_ip1, npy_intp is1_m, npy_intp is1_n,
Expand Down
17 changes: 17 additions & 0 deletions numpy/core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -6276,6 +6276,23 @@ def __mul__(self, other):
with assert_raises(TypeError):
b = np.matmul(a, a)

def test_matmul_bool(self):
# gh-14439
a = np.array([[1, 0],[1, 1]], dtype=bool)
assert np.max(a.view(np.uint8)) == 1
b = np.matmul(a, a)
# matmul with boolean output should always be 0, 1
assert np.max(b.view(np.uint8)) == 1

rg = np.random.default_rng(np.random.PCG64(43))
d = rg.integers(2, size=4*5, dtype=np.int8)
d = d.reshape(4, 5) > 0
out1 = np.matmul(d, d.reshape(5, 4))
out2 = np.dot(d, d.reshape(5, 4))
assert_equal(out1, out2)

c = np.matmul(np.zeros((2, 0), dtype=bool), np.zeros(0, dtype=bool))
assert not np.any(c)


if sys.version_info[:2] >= (3, 5):
Expand Down

0 comments on commit 0006674

Please sign in to comment.