diff options
author | Mark Wiebe <mwiebe@enthought.com> | 2011-05-25 18:05:16 -0500 |
---|---|---|
committer | Mark Wiebe <mwiebe@enthought.com> | 2011-05-25 18:05:16 -0500 |
commit | a12f0d1d2308044e87c9902c78d809a8fcb465f1 (patch) | |
tree | b36037310ef36303155acb1cad89f5b12992c8c3 | |
parent | a106c2932c9350420a395bd2681ef8f17e4b1e36 (diff) | |
download | numpy-a12f0d1d2308044e87c9902c78d809a8fcb465f1.tar.gz |
ENH: Generalize ufunc type resolution to be a replaceable function
The datetime64 is a parameterized type, something which can't be handled
with the simple linear list search the ufuncs currently do. This patch
adds a function pointer to the end of the ufunc object (should be
ABI compatible similar to the metadata addition in 1.4.1), which handles
type resolution. This function is set by default to a function which
executes the preexisting type resolution functionality.
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 2 | ||||
-rw-r--r-- | numpy/core/include/numpy/ufuncobject.h | 43 | ||||
-rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 162 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 56 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_object.c | 266 | ||||
-rw-r--r-- | numpy/core/src/umath/umathmodule.c.src | 32 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 29 |
7 files changed, 294 insertions, 296 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 17e739380..0b07ad762 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -809,7 +809,7 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *); /* * Size of internal buffers used for alignment Make BUFSIZE a multiple - * of sizeof(cdouble) -- ususally 16 so that ufunc buffers are aligned + * of sizeof(cdouble) -- usually 16 so that ufunc buffers are aligned */ #define NPY_MIN_BUFSIZE ((int)sizeof(cdouble)) #define NPY_MAX_BUFSIZE (((int)sizeof(cdouble))*1000000) diff --git a/numpy/core/include/numpy/ufuncobject.h b/numpy/core/include/numpy/ufuncobject.h index 34cd72707..ae8f06827 100644 --- a/numpy/core/include/numpy/ufuncobject.h +++ b/numpy/core/include/numpy/ufuncobject.h @@ -9,7 +9,41 @@ extern "C" { typedef void (*PyUFuncGenericFunction) (char **, npy_intp *, npy_intp *, void *); -typedef struct { +/* Forward declaration for the type resolution function */ +struct _tagPyUFuncObject; + +/* + * Given the operands for calling a ufunc, should determine the + * calculation input and output data types and return an inner loop function. + * This function should validate that the casting rule is being followed, + * and fail if it is not. + * + * ufunc: The ufunc object. + * casting: The 'casting' parameter provided to the ufunc. + * operands: An array of length (ufunc->nin + ufunc->nout), + * with the output parameters possibly NULL. + * type_tup: Either NULL, or the type_tup passed to the ufunc. + * out_dtypes: An array which should be populated with new + * references to (ufunc->nin + ufunc->nout) new + * dtypes, one for each input and output. + * out_innerloop: Should be populated with the correct ufunc inner + * loop for the given type. + * out_innerloopdata: Should be populated with the void* data to + * be passed into the out_innerloop function. + * + * Should return 0 on success, -1 on failure (with exception set), + * or -2 if Py_NotImplemented should be returned. + */ +typedef int (PyUFunc_TypeResolutionFunc)( + struct _tagPyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +typedef struct _tagPyUFuncObject { PyObject_HEAD /* * nin: Number of inputs @@ -70,6 +104,13 @@ typedef struct { int *core_offsets; /* signature string for printing purpose */ char *core_signature; + + /* + * A function which resolves the types and returns an inner loop. + * This is used by the regular ufunc, the reduction operations + * have a different set of rules. + */ + PyUFunc_TypeResolutionFunc *type_resolution_function; } PyUFuncObject; #include "arrayobject.h" diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index ac3da4558..e4fe2c000 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -782,28 +782,6 @@ fail: /* - * Return a Python Datetime Object from a number representing the number of - * units since the epoch (1970-01-01T00:00:00Z) ignoring leap seconds. - */ - -NPY_NO_EXPORT PyObject * -PyDateTime_FromNormalized(npy_datetime val, NPY_DATETIMEUNIT base) -{ - npy_datetimestruct ydate; - - /* Must be here to use PyDateTime_FromDateAndTime */ - PyDateTime_IMPORT; - - /* We just truncate the unused variables and don't wory about overflow */ - PyArray_DatetimeToDatetimeStruct(val, base, &ydate); - - /* FIXME?: We discard ydate.ns, ydate.ps, ydate.fs, and ydate.as */ - return PyDateTime_FromDateAndTime(ydate.year, ydate.month, ydate.day, - ydate.hour, ydate.min, ydate.sec, - ydate.us); -} - -/* * We also can lose precision and range here. Ignored. * Don't use this function if you care. */ @@ -820,53 +798,6 @@ PyTimeDelta_FromNormalized(npy_timedelta val, NPY_DATETIMEUNIT base) return PyDelta_FromDSU(td.day, td.sec, td.us); } - -NPY_NO_EXPORT PyObject * -PyDateTime_FromInt64(datetime val, PyArray_Descr *descr) -{ - PyArray_DatetimeMetaData *meta; - - meta = PyDataType_GetDatetimeMetaData(descr); - if (meta == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "metadata not set for descriptor"); - return NULL; - } - - if (meta->events > 1) { - int events, rem, div; - PyObject *obj; - - obj = PyTuple_New(2); - events = meta->events; - div = val/events; - rem = val % events; - PyTuple_SET_ITEM(obj, 1, PyInt_FromLong(rem)); - /* This resets meta->events for recursive call */ - meta->events = 1; - PyTuple_SET_ITEM(obj, 0, PyDateTime_FromInt64(div, descr)); - meta->events = events; - if (PyErr_Occurred()) { - Py_DECREF(obj); - return NULL; - } - return obj; - } - - /* - * We normalize the number to a base-unit and then return a - * Python Datetime Object - * - * FIXME? : We silently truncate if it doesn't fit, either too - * wide (e.g. 10 BC) or too narrow (nanoseconds) - */ - - /* Normalization and then conversion to Datetime */ - /* FIXME? : Check for Overflow... */ - return PyDateTime_FromNormalized(val*meta->num, meta->base); -} - - NPY_NO_EXPORT PyObject * PyTimeDelta_FromInt64(timedelta val, PyArray_Descr *descr) { @@ -902,46 +833,6 @@ PyTimeDelta_FromInt64(timedelta val, PyArray_Descr *descr) return PyTimeDelta_FromNormalized(val*meta->num, meta->base); } - - -NPY_NO_EXPORT npy_datetime -PyDateTime_AsNormalized(PyObject *obj, NPY_DATETIMEUNIT base) -{ - npy_datetimestruct ydate; - - /* Must be here to use PyDateTime_FromDateAndTime */ - PyDateTime_IMPORT; - - if (!PyDateTime_Check(obj) && !PyDate_Check(obj)) { - PyErr_SetString(PyExc_ValueError, - "Must be a datetime.date or datetime.datetime object"); - return -1; - } - - ydate.year = PyDateTime_GET_YEAR(obj); - ydate.month = PyDateTime_GET_MONTH(obj); - ydate.day = PyDateTime_GET_DAY(obj); - - if (PyDateTime_Check(obj)) { - ydate.hour = PyDateTime_DATE_GET_HOUR(obj); - ydate.min = PyDateTime_DATE_GET_MINUTE(obj); - ydate.sec = PyDateTime_DATE_GET_SECOND(obj); - ydate.us = PyDateTime_DATE_GET_MICROSECOND(obj); - } - else { - ydate.hour = 0; - ydate.min = 0; - ydate.sec = 0; - ydate.us = 0; - } - - ydate.ps = 0; - ydate.as = 0; - - /* We just truncate the unused variables and don't wory about overflow */ - return PyArray_DatetimeStructToDatetime(base, &ydate); -} - NPY_NO_EXPORT npy_timedelta PyTimeDelta_AsNormalized(PyObject *obj, NPY_DATETIMEUNIT base) { @@ -964,59 +855,6 @@ PyTimeDelta_AsNormalized(PyObject *obj, NPY_DATETIMEUNIT base) return PyArray_TimedeltaStructToTimedelta(base, &td); } - -/* - * These expect a 2-tuple if meta->events > 1 (baseobj, num-counts) - * where baseobj is a datetime object or a timedelta object respectively. - * - */ - -NPY_NO_EXPORT npy_datetime -PyDateTime_AsInt64(PyObject *obj, PyArray_Descr *descr) -{ - PyArray_DatetimeMetaData *meta; - npy_datetime res; - - meta = PyDataType_GetDatetimeMetaData(descr); - if (meta == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "metadata not set for descriptor"); - return -1; - } - - - if (meta->events > 1) { - datetime tmp; - int events; - - if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { - PyErr_SetString(PyExc_ValueError, - "need a 2-tuple on setting if events > 1"); - return -1; - } - /* Alter the dictionary and call again */ - /* FIXME: not thread safe */ - events = meta->events; - meta->events = 1; - tmp = PyDateTime_AsInt64(PyTuple_GET_ITEM(obj, 0), descr); - meta->events = events; - if (PyErr_Occurred()) { - return -1; - } - /* FIXME: Check for overflow */ - tmp *= events; - tmp += MyPyLong_AsLongLong(PyTuple_GET_ITEM(obj, 1)); - if (PyErr_Occurred()) { - return -1; - } - return tmp; - } - - res = PyDateTime_AsNormalized(obj, meta->base); - return res/meta->num; -} - - NPY_NO_EXPORT timedelta PyTimeDelta_AsInt64(PyObject *obj, PyArray_Descr *descr) { diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index 0d1ed563e..a73e55e6c 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -1852,7 +1852,8 @@ datetimestruct_timezone_offset(npy_datetimestruct *dts, int minutes) * + Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats. * + Doesn't handle leap seconds (seconds value has 60 in these cases). * + Doesn't handle 24:00:00 as synonym for midnight (00:00:00) tomorrow - * + Accepts special values "NaT" (not a time), "Today", and "Now". + * + Accepts special values "NaT" (not a time), "Today", (current + * day according to local time) and "Now" (current time in UTC). * * 'str' must be a NULL-terminated string, and 'len' must be its length. * @@ -1915,7 +1916,7 @@ parse_iso_8601_date(char *str, int len, npy_datetimestruct *out) return 0; } - /* The string "now" resolves to the current time */ + /* The string "now" resolves to the current UTC time */ if (len == 3 && tolower(str[0]) == 'n' && tolower(str[1]) == 'o' && tolower(str[2]) == 'w') { @@ -2163,10 +2164,53 @@ parse_iso_8601_date(char *str, int len, npy_datetimestruct *out) parse_timezone: if (sublen == 0) { - /* TODO: In this case, ISO 8601 states to treat - * it as a local time, but we are leaving - * it as a UTC time for now. - */ + /* Only do this timezone adjustment for recent and future years */ + if (out->year > 1900 && out->year < 10000) { + time_t rawtime = 0; + struct tm tm_; + /* + * ISO 8601 states to treat date-times without a timezone offset + * or 'Z' for UTC as local time. The C standard libary functions + * mktime and gmtime allow us to do this conversion. + */ + tm_.tm_sec = out->sec; + tm_.tm_min = out->min; + tm_.tm_hour = out->hour; + tm_.tm_mday = out->day; + tm_.tm_mon = out->month - 1; + tm_.tm_year = out->year - 1900; + tm_.tm_isdst = -1; + + /* mktime converts a local 'struct tm' into a time_t */ + rawtime = mktime(&tm_); + if (rawtime == -1) { + PyErr_SetString(PyExc_OSError, "Failed to use mktime to " + "convert local time to UTC"); + goto error; + } + + /* gmtime converts a 'time_t' into a UTC 'struct tm' */ +#if defined(_WIN32) + if (gmtime_s(&tm_, &rawtime) != 0) { + PyErr_SetString(PyExc_OSError, "Failed to use gmtime_s to " + "get a UTC time"); + goto error; + } +#else + /* Other platforms may require something else */ + if (gmtime_r(&rawtime, &tm_) == NULL) { + PyErr_SetString(PyExc_OSError, "Failed to use gmtime_r to " + "get a UTC time"); + goto error; + } +#endif + out->sec = tm_.tm_sec; + out->min = tm_.tm_min; + out->hour = tm_.tm_hour; + out->day = tm_.tm_mday; + out->month = tm_.tm_mon + 1; + out->year = tm_.tm_year + 1900; + } goto finish; } diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 930c91ca1..d93375b8e 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -577,12 +577,12 @@ _parse_signature(PyUFuncObject *self, const char *signature) } len = strlen(signature); - self->core_signature = _pya_malloc(sizeof(char) * (len+1)); + self->core_signature = PyArray_malloc(sizeof(char) * (len+1)); if (self->core_signature) { strcpy(self->core_signature, signature); } /* Allocate sufficient memory to store pointers to all dimension names */ - var_names = _pya_malloc(sizeof(char const*) * len); + var_names = PyArray_malloc(sizeof(char const*) * len); if (var_names == NULL) { PyErr_NoMemory(); return -1; @@ -590,9 +590,9 @@ _parse_signature(PyUFuncObject *self, const char *signature) self->core_enabled = 1; self->core_num_dim_ix = 0; - self->core_num_dims = _pya_malloc(sizeof(int) * self->nargs); - self->core_dim_ixs = _pya_malloc(sizeof(int) * len); /* shrink this later */ - self->core_offsets = _pya_malloc(sizeof(int) * self->nargs); + self->core_num_dims = PyArray_malloc(sizeof(int) * self->nargs); + self->core_dim_ixs = PyArray_malloc(sizeof(int) * len); /* shrink this later */ + self->core_offsets = PyArray_malloc(sizeof(int) * self->nargs); if (self->core_num_dims == NULL || self->core_dim_ixs == NULL || self->core_offsets == NULL) { PyErr_NoMemory(); @@ -677,24 +677,24 @@ _parse_signature(PyUFuncObject *self, const char *signature) parse_error = "incomplete signature: not all arguments found"; goto fail; } - self->core_dim_ixs = _pya_realloc(self->core_dim_ixs, + self->core_dim_ixs = PyArray_realloc(self->core_dim_ixs, sizeof(int)*cur_core_dim); /* check for trivial core-signature, e.g. "(),()->()" */ if (cur_core_dim == 0) { self->core_enabled = 0; } - _pya_free((void*)var_names); + PyArray_free((void*)var_names); return 0; fail: - _pya_free((void*)var_names); + PyArray_free((void*)var_names); if (parse_error) { - char *buf = _pya_malloc(sizeof(char) * (len + 200)); + char *buf = PyArray_malloc(sizeof(char) * (len + 200)); if (buf) { sprintf(buf, "%s at position %d in \"%s\"", parse_error, i, signature); PyErr_SetString(PyExc_ValueError, signature); - _pya_free(buf); + PyArray_free(buf); } else { PyErr_NoMemory(); @@ -720,8 +720,7 @@ static int get_ufunc_arguments(PyUFuncObject *self, NPY_CASTING *out_casting, PyObject **out_extobj, PyObject **out_typetup, - int *out_subok, - int *out_any_object) + int *out_subok) { npy_intp i, nargs, nin = self->nin; PyObject *obj, *context; @@ -938,8 +937,6 @@ static int get_ufunc_arguments(PyUFuncObject *self, } } - *out_any_object = any_object; - Py_XDECREF(str_key_obj); return 0; @@ -1066,25 +1063,49 @@ ufunc_loop_matches(PyUFuncObject *self, static int set_ufunc_loop_data_types(PyUFuncObject *self, PyArrayObject **op, PyArray_Descr **out_dtype, - int *types, - npy_intp buffersize, int *out_trivial_loop_ok) + int *types) { - npy_intp i, nin = self->nin, nop = nin + self->nout; + int i, nin = self->nin, nop = nin + self->nout; - *out_trivial_loop_ok = 1; /* Fill the dtypes array */ for (i = 0; i < nop; ++i) { out_dtype[i] = PyArray_DescrFromType(types[i]); if (out_dtype[i] == NULL) { + while (--i >= 0) { + Py_DECREF(out_dtype[i]); + out_dtype[i] = NULL; + } return -1; } + } + + return 0; +} + +/* + * This checks whether a trivial loop is ok, + * making copies of scalar and one dimensional operands if that will + * help. + * + * Returns 1 if a trivial loop is ok, 0 if it is not, and + * -1 if there is an error. + */ +static int +check_for_trivial_loop(PyUFuncObject *self, + PyArrayObject **op, + PyArray_Descr **dtype, + npy_intp buffersize) +{ + npy_intp i, nin = self->nin, nop = nin + self->nout; + + for (i = 0; i < nop; ++i) { /* * If the dtype doesn't match, or the array isn't aligned, * indicate that the trivial loop can't be done. */ - if (*out_trivial_loop_ok && op[i] != NULL && + if (op[i] != NULL && (!PyArray_ISALIGNED(op[i]) || - !PyArray_EquivTypes(out_dtype[i], PyArray_DESCR(op[i])) + !PyArray_EquivTypes(dtype[i], PyArray_DESCR(op[i])) )) { /* * If op[j] is a scalar or small one dimensional @@ -1095,9 +1116,9 @@ set_ufunc_loop_data_types(PyUFuncObject *self, PyArrayObject **op, (PyArray_NDIM(op[i]) == 1 && PyArray_DIM(op[i],0) <= buffersize))) { PyArrayObject *tmp; - Py_INCREF(out_dtype[i]); + Py_INCREF(dtype[i]); tmp = (PyArrayObject *) - PyArray_CastToType(op[i], out_dtype[i], 0); + PyArray_CastToType(op[i], dtype[i], 0); if (tmp == NULL) { return -1; } @@ -1105,12 +1126,12 @@ set_ufunc_loop_data_types(PyUFuncObject *self, PyArrayObject **op, op[i] = tmp; } else { - *out_trivial_loop_ok = 0; + return 0; } } } - return 0; + return 1; } /* @@ -1121,13 +1142,11 @@ find_ufunc_matching_userloop(PyUFuncObject *self, PyArrayObject **op, NPY_CASTING input_casting, NPY_CASTING output_casting, - npy_intp buffersize, int any_object, int use_min_scalar, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, void **out_innerloopdata, - int *out_trivial_loop_ok, int *out_no_castable_output, char *out_err_src_typecode, char *out_err_dst_typecode) @@ -1168,8 +1187,7 @@ find_ufunc_matching_userloop(PyUFuncObject *self, return -1; /* Found a match */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = funcdata->func; @@ -1199,13 +1217,11 @@ find_ufunc_specified_userloop(PyUFuncObject *self, int *specified_types, PyArrayObject **op, NPY_CASTING casting, - npy_intp buffersize, int any_object, int use_min_scalar, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, - void **out_innerloopdata, - int *out_trivial_loop_ok) + void **out_innerloopdata) { npy_intp i, j, nin = self->nin, nop = nin + self->nout; PyUFunc_Loop1d *funcdata; @@ -1261,8 +1277,7 @@ find_ufunc_specified_userloop(PyUFuncObject *self, &err_dst_typecode)) { /* It works */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = funcdata->func; @@ -1365,10 +1380,6 @@ should_use_min_scalar(PyArrayObject **op, int nop) /* * Does a linear search for the best inner loop of the ufunc. - * When op[i] is a scalar or a one dimensional array smaller than - * the buffersize, and needs a dtype conversion, this function - * may substitute op[i] with a version cast to the correct type. This way, - * the later trivial loop detection has a higher chance of being triggered. * * Note that if an error is returned, the caller must free the non-zero * references in out_dtype. This function does not do its own clean-up. @@ -1378,12 +1389,10 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, PyArrayObject **op, NPY_CASTING input_casting, NPY_CASTING output_casting, - npy_intp buffersize, int any_object, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, - void **out_innerloopdata, - int *out_trivial_loop_ok) + void **out_innerloopdata) { npy_intp i, j, nin = self->nin, nop = nin + self->nout; int types[NPY_MAXARGS]; @@ -1401,9 +1410,8 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, if (self->userloops) { switch (find_ufunc_matching_userloop(self, op, input_casting, output_casting, - buffersize, any_object, use_min_scalar, + any_object, use_min_scalar, out_dtype, out_innerloop, out_innerloopdata, - out_trivial_loop_ok, &no_castable_output, &err_src_typecode, &err_dst_typecode)) { /* Error */ @@ -1452,8 +1460,7 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, return -1; /* Found a match */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = self->functions[i]; @@ -1494,10 +1501,6 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, /* * Does a linear search for the inner loop of the ufunc specified by type_tup. - * When op[i] is a scalar or a one dimensional array smaller than - * the buffersize, and needs a dtype conversion, this function - * may substitute op[i] with a version cast to the correct type. This way, - * the later trivial loop detection has a higher chance of being triggered. * * Note that if an error is returned, the caller must free the non-zero * references in out_dtype. This function does not do its own clean-up. @@ -1507,12 +1510,10 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, PyObject *type_tup, PyArrayObject **op, NPY_CASTING casting, - npy_intp buffersize, int any_object, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, - void **out_innerloopdata, - int *out_trivial_loop_ok) + void **out_innerloopdata) { npy_intp i, j, n, nin = self->nin, nop = nin + self->nout; int n_specified = 0; @@ -1619,9 +1620,8 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, switch (find_ufunc_specified_userloop(self, n_specified, specified_types, op, casting, - buffersize, any_object, use_min_scalar, - out_dtype, out_innerloop, out_innerloopdata, - out_trivial_loop_ok)) { + any_object, use_min_scalar, + out_dtype, out_innerloop, out_innerloopdata)) { /* Error */ case -1: return -1; @@ -1673,8 +1673,7 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, return -1; /* It worked */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = self->functions[i]; @@ -1706,6 +1705,49 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, return -1; } +int generic_ufunc_type_resolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, nop = ufunc->nin + ufunc->nout; + int retval = 0, any_object = 0; + NPY_CASTING input_casting; + + for (i = 0; i < nop; ++i) { + if (operands[i] != NULL && + PyTypeNum_ISOBJECT(PyArray_DESCR(operands[i])->type_num)) { + any_object = 1; + break; + } + } + + /* + * Decide the casting rules for inputs and outputs. We want + * NPY_SAFE_CASTING or stricter, so that the loop selection code + * doesn't choose an integer loop for float inputs, for example. + */ + input_casting = (casting > NPY_SAFE_CASTING) ? NPY_SAFE_CASTING : casting; + + if (type_tup == NULL) { + /* Find the best ufunc inner loop, and fill in the dtypes */ + retval = find_best_ufunc_inner_loop(ufunc, operands, + input_casting, casting, any_object, + out_dtypes, out_innerloop, out_innerloopdata); + } else { + /* Find the specified ufunc inner loop, and fill in the dtypes */ + retval = find_specified_ufunc_inner_loop(ufunc, type_tup, + operands, casting, any_object, out_dtypes, + out_innerloop, out_innerloopdata); + } + + return retval; +} + + static void trivial_two_operand_loop(PyArrayObject **op, PyUFuncGenericFunction innerloop, @@ -2136,8 +2178,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, int nin, nout; int i, idim, nop; char *ufunc_name; - int retval = -1, any_object = 0, subok = 1; - NPY_CASTING input_casting; + int retval = -1, subok = 1; PyArray_Descr *dtype[NPY_MAXARGS]; @@ -2174,8 +2215,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, */ PyObject *arr_prep_args = NULL; - int trivial_loop_ok = 0; - NPY_ORDER order = NPY_KEEPORDER; /* * Many things in NumPy do unsafe casting (doing int += float, etc). @@ -2210,7 +2249,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, /* Get all the arguments */ retval = get_ufunc_arguments(self, args, kwds, - op, &order, &casting, &extobj, &type_tup, &subok, &any_object); + op, &order, &casting, &extobj, &type_tup, &subok); if (retval < 0) { goto fail; } @@ -2292,25 +2331,9 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, NPY_UF_DBG_PRINT("Finding inner loop\n"); - /* - * Decide the casting rules for inputs and outputs. We want - * NPY_SAFE_CASTING or stricter, so that the loop selection code - * doesn't choose an integer loop for float inputs, for example. - */ - input_casting = (casting > NPY_SAFE_CASTING) ? NPY_SAFE_CASTING : casting; - if (type_tup == NULL) { - /* Find the best ufunc inner loop, and fill in the dtypes */ - retval = find_best_ufunc_inner_loop(self, op, input_casting, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } else { - /* Find the specified ufunc inner loop, and fill in the dtypes */ - retval = find_specified_ufunc_inner_loop(self, type_tup, - op, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } + retval = self->type_resolution_function(self, casting, + op, type_tup, dtype, &innerloop, &innerloopdata); if (retval < 0) { goto fail; } @@ -2408,7 +2431,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, * Set up the inner strides array. Because we're not doing * buffering, the strides are fixed throughout the looping. */ - inner_strides = (npy_intp *)_pya_malloc( + inner_strides = (npy_intp *)PyArray_malloc( NPY_SIZEOF_INTP * (nop+core_dim_ixs_size)); /* The strides after the first nop match core_dim_ixs */ core_dim_ixs = self->core_dim_ixs; @@ -2504,7 +2527,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, goto fail; } - _pya_free(inner_strides); + PyArray_free(inner_strides); NpyIter_Deallocate(iter); /* The caller takes ownership of all the references in op */ for (i = 0; i < nop; ++i) { @@ -2522,7 +2545,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, fail: NPY_UF_DBG_PRINT1("Returning failure code %d\n", retval); if (inner_strides) { - _pya_free(inner_strides); + PyArray_free(inner_strides); } if (iter != NULL) { NpyIter_Deallocate(iter); @@ -2553,8 +2576,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, int nin, nout; int i, nop; char *ufunc_name; - int retval = -1, any_object = 0, subok = 1; - NPY_CASTING input_casting; + int retval = -1, subok = 1; PyArray_Descr *dtype[NPY_MAXARGS]; @@ -2577,7 +2599,6 @@ PyUFunc_GenericFunction(PyUFuncObject *self, int trivial_loop_ok = 0; - /* TODO: For 1.6, the default should probably be NPY_CORDER */ NPY_ORDER order = NPY_KEEPORDER; /* * Many things in NumPy do unsafe casting (doing int += float, etc). @@ -2593,7 +2614,6 @@ PyUFunc_GenericFunction(PyUFuncObject *self, return -1; } - /* TODO: support generalized ufunc */ if (self->core_enabled) { return PyUFunc_GeneralizedFunction(self, args, kwds, op); } @@ -2617,7 +2637,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, /* Get all the arguments */ retval = get_ufunc_arguments(self, args, kwds, - op, &order, &casting, &extobj, &type_tup, &subok, &any_object); + op, &order, &casting, &extobj, &type_tup, &subok); if (retval < 0) { goto fail; } @@ -2640,26 +2660,19 @@ PyUFunc_GenericFunction(PyUFuncObject *self, NPY_UF_DBG_PRINT("Finding inner loop\n"); + retval = self->type_resolution_function(self, casting, + op, type_tup, dtype, &innerloop, &innerloopdata); + if (retval < 0) { + goto fail; + } + /* - * Decide the casting rules for inputs and outputs. We want - * NPY_SAFE_CASTING or stricter, so that the loop selection code - * doesn't choose an integer loop for float inputs, for example. + * This checks whether a trivial loop is ok, + * making copies of scalar and one dimensional operands if that will + * help. */ - input_casting = (casting > NPY_SAFE_CASTING) ? NPY_SAFE_CASTING : casting; - - if (type_tup == NULL) { - /* Find the best ufunc inner loop, and fill in the dtypes */ - retval = find_best_ufunc_inner_loop(self, op, input_casting, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } else { - /* Find the specified ufunc inner loop, and fill in the dtypes */ - retval = find_specified_ufunc_inner_loop(self, type_tup, - op, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } - if (retval < 0) { + trivial_loop_ok = check_for_trivial_loop(self, op, dtype, buffersize); + if (trivial_loop_ok < 0) { goto fail; } @@ -2681,6 +2694,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, } } + #if NPY_UF_DBG_TRACING printf("input types:\n"); for (i = 0; i < nin; ++i) { @@ -4424,7 +4438,7 @@ PyUFunc_FromFuncAndDataAndSignature(PyUFuncGenericFunction *func, void **data, { PyUFuncObject *self; - self = _pya_malloc(sizeof(PyUFuncObject)); + self = PyArray_malloc(sizeof(PyUFuncObject)); if (self == NULL) { return NULL; } @@ -4444,6 +4458,8 @@ PyUFunc_FromFuncAndDataAndSignature(PyUFuncGenericFunction *func, void **data, self->obj = NULL; self->userloops=NULL; + self->type_resolution_function = &generic_ufunc_type_resolution; + if (name == NULL) { self->name = "?"; } @@ -4484,8 +4500,12 @@ PyUFunc_SetUsesArraysAsData(void **data, size_t i) return 0; } -/* Return 1 if the given data pointer for the loop specifies that it needs the +/* + * Return 1 if the given data pointer for the loop specifies that it needs the * arrays as the data pointer. + * + * NOTE: This is easier to specify with the type_resolution_function + * in the ufunc object. */ static int _does_loop_use_arrays(void *data) @@ -4535,8 +4555,8 @@ _free_loop1d_list(PyUFunc_Loop1d *data) { while (data != NULL) { PyUFunc_Loop1d *next = data->next; - _pya_free(data->arg_types); - _pya_free(data); + PyArray_free(data->arg_types); + PyArray_free(data); data = next; } } @@ -4586,11 +4606,11 @@ PyUFunc_RegisterLoopForType(PyUFuncObject *ufunc, if (key == NULL) { return -1; } - funcdata = _pya_malloc(sizeof(PyUFunc_Loop1d)); + funcdata = PyArray_malloc(sizeof(PyUFunc_Loop1d)); if (funcdata == NULL) { goto fail; } - newtypes = _pya_malloc(sizeof(int)*ufunc->nargs); + newtypes = PyArray_malloc(sizeof(int)*ufunc->nargs); if (newtypes == NULL) { goto fail; } @@ -4645,8 +4665,8 @@ PyUFunc_RegisterLoopForType(PyUFuncObject *ufunc, /* just replace it with new function */ current->func = function; current->data = data; - _pya_free(newtypes); - _pya_free(funcdata); + PyArray_free(newtypes); + PyArray_free(funcdata); } else { /* @@ -4669,8 +4689,8 @@ PyUFunc_RegisterLoopForType(PyUFuncObject *ufunc, fail: Py_DECREF(key); - _pya_free(funcdata); - _pya_free(newtypes); + PyArray_free(funcdata); + PyArray_free(newtypes); if (!PyErr_Occurred()) PyErr_NoMemory(); return -1; } @@ -4682,23 +4702,23 @@ static void ufunc_dealloc(PyUFuncObject *self) { if (self->core_num_dims) { - _pya_free(self->core_num_dims); + PyArray_free(self->core_num_dims); } if (self->core_dim_ixs) { - _pya_free(self->core_dim_ixs); + PyArray_free(self->core_dim_ixs); } if (self->core_offsets) { - _pya_free(self->core_offsets); + PyArray_free(self->core_offsets); } if (self->core_signature) { - _pya_free(self->core_signature); + PyArray_free(self->core_signature); } if (self->ptr) { - _pya_free(self->ptr); + PyArray_free(self->ptr); } Py_XDECREF(self->userloops); Py_XDECREF(self->obj); - _pya_free(self); + PyArray_free(self); } static PyObject * @@ -4956,7 +4976,7 @@ ufunc_get_types(PyUFuncObject *self) if (list == NULL) { return NULL; } - t = _pya_malloc(no+ni+2); + t = PyArray_malloc(no+ni+2); n = 0; for (k = 0; k < nt; k++) { for (j = 0; j<ni; j++) { @@ -4972,7 +4992,7 @@ ufunc_get_types(PyUFuncObject *self) str = PyUString_FromStringAndSize(t, no + ni + 2); PyList_SET_ITEM(list, k, str); } - _pya_free(t); + PyArray_free(t); return list; } diff --git a/numpy/core/src/umath/umathmodule.c.src b/numpy/core/src/umath/umathmodule.c.src index 8d081f85b..b4cece358 100644 --- a/numpy/core/src/umath/umathmodule.c.src +++ b/numpy/core/src/umath/umathmodule.c.src @@ -43,6 +43,32 @@ static PyUFuncGenericFunction pyfunc_functions[] = {PyUFunc_On_Om}; +static int object_ufunc_type_resolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, nop = ufunc->nin + ufunc->nout; + + out_dtypes[0] = PyArray_DescrFromType(NPY_OBJECT); + if (out_dtypes[0] == NULL) { + return -1; + } + + for (i = 1; i < nop; ++i) { + out_dtypes[i] = out_dtypes[0]; + Py_INCREF(out_dtypes[0]); + } + + *out_innerloop = ufunc->functions[0]; + *out_innerloopdata = ufunc->data[0]; + + return 0; +} + static PyObject * ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUSED(kwds)) { /* Keywords are ignored for now */ @@ -62,7 +88,7 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS PyErr_SetString(PyExc_TypeError, "function must be callable"); return NULL; } - self = _pya_malloc(sizeof(PyUFuncObject)); + self = PyArray_malloc(sizeof(PyUFuncObject)); if (self == NULL) { return NULL; } @@ -85,6 +111,8 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS self->core_offsets = NULL; self->core_signature = NULL; + self->type_resolution_function = &object_ufunc_type_resolution; + pyname = PyObject_GetAttrString(function, "__name__"); if (pyname) { (void) PyString_AsStringAndSize(pyname, &fname, &fname_len); @@ -115,7 +143,7 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS if (i) { offset[1] += (sizeof(void *)-i); } - self->ptr = _pya_malloc(offset[0] + offset[1] + sizeof(void *) + + self->ptr = PyArray_malloc(offset[0] + offset[1] + sizeof(void *) + (fname_len + 14)); if (self->ptr == NULL) { Py_XDECREF(pyname); diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 79f3e9e41..a9ff39c7d 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -117,7 +117,7 @@ class TestDateTime(TestCase): assert_equal(b.astype(object).astype(unit), b, "Error roundtripping unit %s" % unit) - def test_month_trucation(self): + def test_month_truncation(self): # Make sure that months are truncating correctly assert_equal(np.array('1945-03-01', dtype='M8[M]'), np.array('1945-03-31', dtype='M8[M]')) @@ -130,6 +130,33 @@ class TestDateTime(TestCase): assert_equal(np.array('1980-02-01', dtype='M8[M]'), np.array('1980-02-29T23:59:59.999999Z', dtype='M8[M]')) + def test_different_unit_comparison(self): + # Check some years + for unit1 in ['Y', 'M', 'D', '6h', 'h', 'm', 's', '10ms', + 'ms', 'us', 'ps']: + dt1 = np.dtype('M8[%s]' % unit1) + for unit2 in ['Y', 'M', 'D', 'h', 'm', 's', 'ms', 'us', 'ps']: + dt2 = np.dtype('M8[%s]' % unit2) + assert_equal(np.array('1945', dtype=dt1), + np.array('1945', dtype=dt2)) + assert_equal(np.array('1970', dtype=dt1), + np.array('1970', dtype=dt2)) + assert_equal(np.array('9999', dtype=dt1), + np.array('9999', dtype=dt2)) + assert_equal(np.array('10000', dtype=dt1), + np.array('10000-01-01', dtype=dt2)) + # Check some days + for unit1 in ['D', '12h', 'h', 'm', 's', '4s', 'ms', 'us', 'ps']: + dt1 = np.dtype('M8[%s]' % unit1) + for unit2 in ['D', 'h', 'm', 's', 'ms', 'us', 'ps']: + dt2 = np.dtype('M8[%s]' % unit2) + assert_equal(np.array('1932-02-17', dtype=dt1), + np.array('1932-02-17T00:00:00', dtype=dt2)) + assert_equal(np.array('10000-04-27', dtype=dt1), + np.array('10000-04-27T00:00:00', dtype=dt2)) + assert_equal(np.array('today', dtype=dt1), + np.array('today', dtype=dt2)) + def test_hours(self): t = np.ones(3, dtype='M8[s]') t[0] = 60*60*24 + 60*60*10 |