8000 ENH: add isinf, isnan, fmin, fmax loops for datetime64, timedelta64 (… · numpy/numpy@5923592 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5923592

Browse files
mattipWarrenWeckesser
authored andcommitted
ENH: add isinf, isnan, fmin, fmax loops for datetime64, timedelta64 (#14841)
`np.datetime('NaT')` should behave more like `float('Nan')`. Add needed infrastructure so `np.isinf(a)` and `np.isnan(a)` will run on `datetime64` and `timedelta64` dtypes. Also added specific loops for `numpy.fmin` and `numpy.fmax` that mask `NaT`.
1 parent 48e0ded commit 5923592

File tree

6 files changed

+101
-38
lines changed
Filter options

6 files changed

+101
-38
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Add more ufunc loops for ``datetime64``, ``timedelta64``
2+
--------------------------------------------------------
3+
``np.datetime('NaT')`` should behave more like ``float('Nan')``. Add needed
4+
infrastructure so ``np.isinf(a)`` and ``np.isnan(a)`` will run on
5+
``datetime64`` and ``timedelta64`` dtypes. Also added specific loops for
6+
`numpy.fmin` and `numpy.fmax` that mask ``NaT``. This may require adjustment to user-
7+
facing code. Specifically, code that either disallowed the calls to
8+
`numpy.isinf` or `numpy.isnan` or checked that they raised an exception will
9+
require adaptation, and code that mistakenly called `numpy.fmax` and
10+
`numpy.fmin` instead of `numpy.maximum` or `numpy.minimum` respectively will
11+
requre adjustment. This also affects `numpy.nanmax` and `numpy.nanmin`.

numpy/core/code_generators/generate_umath.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -858,8 +858,8 @@ def english_upper(s):
858858
'isnan':
859859
Ufunc(1, 1, None,
860860
docstrings.get('numpy.core.umath.isnan'),
861-
None,
862-
TD(nodatetime_or_obj, out='?'),
861+
'PyUFunc_IsFiniteTypeResolver',
862+
TD(noobj, out='?'),
863863
),
864864
'isnat':
865865
Ufunc(1, 1, None,
@@ -870,8 +870,8 @@ def english_upper(s):
870870
'isinf':
871871
Ufunc(1, 1, None,
872872
docstrings.get('numpy.core.umath.isinf'),
873-
None,
874-
TD(nodatetime_or_obj, out='?'),
873+
'PyUFunc_IsFiniteTypeResolver',
874+
TD(noobj, out='?'),
875875
),
876876
'isfinite':
877877
Ufunc(1, 1, None,

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,12 @@ NPY_NO_EXPORT void
10961096
}
10971097
}
10981098

1099+
NPY_NO_EXPORT void
1100+
@TYPE@_isinf(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func))
1101+
{
1102+
UNARY_LOOP_FAST(npy_bool, npy_bool, (void)in; *out = NPY_FALSE);
1103+
}
1104+
10991105
NPY_NO_EXPORT void
11001106
@TYPE@__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data))
11011107
{
@@ -1157,6 +1163,29 @@ NPY_NO_EXPORT void
11571163
}
11581164
/**end repeat1**/
11591165

1166+
/**begin repeat1
1167+
* #kind = fmax, fmin#
1168+
* #OP = >=, <=#
1169+
**/
1170+
NPY_NO_EXPORT void
1171+
@TYPE@_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func))
1172+
{
1173+
BINARY_LOOP {
1174+
const @type@ in1 = *(@type@ *)ip1;
1175+
const @type@ in2 = *(@type@ *)ip2;
1176+
if (in1 == NPY_DATETIME_NAT) {
1177+
*((@type@ *)op1) = in2;
1178+
}
1179+
else if (in2 == NPY_DATETIME_NAT) {
1180+
*((@type@ *)op1) = in1;
1181+
}
1182+
else {
1183+
*((@type@ *)op1) = in1 @OP@ in2 ? in1 : in2;
1184+
}
1185+
}
1186+
}
1187+
/**end repeat1**/
1188+
11601189
/**end repeat**/
11611190

11621191
NPY_NO_EXPORT void

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,11 @@ NPY_NO_EXPORT void
477477
NPY_NO_EXPORT void
478478
@TYPE@_isfinite(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func));
479479

480+
NPY_NO_EXPORT void
481+
@TYPE@_isinf(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func));
482+
483+
#define @TYPE@_isnan @TYPE@_isnat
484+
480485
NPY_NO_EXPORT void
481486
@TYPE@__ones_like(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(data));
482487

@@ -489,8 +494,7 @@ NPY_NO_EXPORT void
489494
/**end repeat1**/
490495

491496
/**begin repeat1
492-
* #kind = maximum, minimum#
493-
* #OP = >, <#
497+
* #kind = maximum, minimum, fmin, fmax#
494498
**/
495499
NPY_NO_EXPORT void
496500
@TYPE@_@kind@(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func));
@@ -554,10 +558,6 @@ TIMEDELTA_mm_qm_divmod(char **args, npy_intp *dimensions, npy_intp *steps, void
554558
#define TIMEDELTA_mq_m_floor_divide TIMEDELTA_mq_m_divide
555559
#define TIMEDELTA_md_m_floor_divide TIMEDELTA_md_m_divide
556560
/* #define TIMEDELTA_mm_d_floor_divide TIMEDELTA_mm_d_divide */
557-
#define TIMEDELTA_fmin TIMEDELTA_minimum
558-
#define TIMEDELTA_fmax TIMEDELTA_maximum
559-
#define DATETIME_fmin DATETIME_minimum
560-
#define DATETIME_fmax DATETIME_maximum
561561

562562
/*
563563
*****************************************************************************

numpy/core/tests/test_datetime.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,6 +1361,10 @@ def test_datetime_minmax(self):
13611361
assert_equal(np.minimum(dtnat, a), dtnat)
13621362
assert_equal(np.maximum(a, dtnat), dtnat)
13631363
assert_equal(np.maximum(dtnat, a), dtnat)
1364+
assert_equal(np.fmin(dtnat, a), a)
1365+
assert_equal(np.fmin(a, dtnat), a)
1366+
assert_equal(np.fmax(dtnat, a), a)
1367+
assert_equal(np.fmax(a, dtnat), a)
13641368

13651369
# Also do timedelta
13661370
a = np.array(3, dtype='m8[h]')
@@ -2232,26 +2236,33 @@ def test_isnat_error(self):
22322236
continue
22332237
assert_raises(TypeError, np.isnat, np.zeros(10, t))
22342238

2235-
def test_isfinite(self):
2239+
def test_isfinite_scalar(self):
22362240
assert_(not np.isfinite(np.datetime64('NaT', 'ms')))
22372241
assert_(not np.isfinite(np.datetime64('NaT', 'ns')))
22382242
assert_(np.isfinite(np.datetime64('2038-01-19T03:14:07')))
22392243

22402244
assert_(not np.isfinite(np.timedelta64('NaT', "ms")))
22412245
assert_(np.isfinite(np.timedelta64(34, "ms")))
22422246

2243-
res = np.array([True, True, False])
2244-
for unit in ['Y', 'M', 'W', 'D',
2245-
'h', 'm', 's', 'ms', 'us',
2246-
'ns', 'ps', 'fs', 'as']:
2247-
arr = np.array([123, -321, "NaT"], dtype='<datetime64[%s]' % unit)
2248-
assert_equal(np.isfinite(arr), res)
2249-
arr = np.array([123, -321, "NaT"], dtype='>datetime64[%s]' % unit)
2250-
assert_equal(np.isfinite(arr), res)
2251-
arr = np.array([123, -321, "NaT"], dtype='<timedelta64[%s]' % unit)
2252-
assert_equal(np.isfinite(arr), res)
2253-
arr = np.array([123, -321, "NaT"], dtype='>timedelta64[%s]' % unit)
2254-
assert_equal(np.isfinite(arr), res)
2247+
@pytest.mark.parametrize('unit', ['Y', 'M', 'W', 'D', 'h', 'm', 's', 'ms',
2248+
'us', 'ns', 'ps', 'fs', 'as'])
2249+
@pytest.mark.parametrize('dstr', ['<datetime64[%s]', '>datetime64[%s]',
2250+
'<timedelta64[%s]', '>timedelta64[%s]'])
2251+
def test_isfinite_isinf_isnan_units(self, unit, dstr):
2252+
'''check isfinite, isinf, isnan for all units of <M, >M, <m, >m dtypes
2253+
'''
2254+
arr_val = [123, -321, "NaT"]
2255+
arr = np.array(arr_val, dtype= dstr % unit)
2256+
pos = np.array([True, True, False])
2257+
neg = np.array([False, False, True])
2258+
false = np.array([False, False, False])
2259+
assert_equal(np.isfinite(arr), pos)
2260+
assert_equal(np.isinf(arr), false)
2261+
assert_equal(np.isnan(arr), neg)
2262+
2263+
def test_assert_equal(self):
2264+
assert_raises(AssertionError, assert_equal,
2265+
np.datetime64('nat'), np.timedelta64('nat'))
22552266

22562267
def test_corecursive_input(self):
22572268
# construct a co-recursive list

numpy/testing/_private/utils.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -378,21 +378,6 @@ def assert_equal(actual, desired, err_msg='', verbose=True):
378378
if isscalar(desired) != isscalar(actual):
379379
raise AssertionError(msg)
380380

381-
# Inf/nan/negative zero handling
382-
try:
383-
isdesnan = gisnan(desired)
384-
isactnan = gisnan(actual)
385-
if isdesnan and isactnan:
386-
return # both nan, so equal
387-
388-
# handle signed zero specially for floats
389-
if desired == 0 and actual == 0:
390-
if not signbit(desired) == signbit(actual):
391-
raise AssertionError(msg)
392-
393-
except (TypeError, ValueError, NotImplementedError):
394-
pass
395-
396381
try:
397382
isdesnat = isnat(desired)
398383
isactnat = isnat(actual)
@@ -408,6 +393,33 @@ def assert_equal(actual, desired, err_msg='', verbose=True):
408393
except (TypeError, ValueError, NotImplementedError):
409394
pass
410395

396+
# Inf/nan/negative zero handling
397+
try:
398+
isdesnan = gisnan(desired)
399+
isactnan = gisnan(actual)
400+
if isdesnan and isactnan:
401+
return # both nan, so equal
402+
403+
# handle signed zero specially for floats
404+
array_actual = array(actual)
405+
array_desired = array(desired)
406+
if (array_actual.dtype.char in 'Mm' or
407+
array_desired.dtype.char in 'Mm'):
408+
# version 1.18
409+
# until this version, gisnan failed for datetime64 and timedelta64.
410+
# Now it succeeds but comparison to scalar with a different type
411+
# emits a DeprecationWarning.
412+
# Avoid that by skipping the next check
413+
raise NotImplementedError('cannot compare to a scalar '
414+
'with a different type')
415+
416+
if desired == 0 and actual == 0:
417+
if not signbit(desired) == signbit(actual):
418+
raise AssertionError(msg)
419+
420+
except (TypeError, ValueError, NotImplementedError):
421+
pass
422+
411423
try:
412424
# Explicitly use __eq__ for comparison, gh-2552
413425
if not (desired == actual):

0 commit comments

Comments
 (0)
0