|
18 | 18 | #include "numpy/npy_3kcompat.h"
|
19 | 19 |
|
20 | 20 | #include "lowlevel_strided_loops.h"
|
| 21 | +#include "na_mask.h" |
21 | 22 | #include "reduction.h"
|
22 | 23 |
|
23 | 24 | /*
|
@@ -269,6 +270,241 @@ check_nonreorderable_axes(int ndim, npy_bool *axis_flags, const char *funcname)
|
269 | 270 | return 0;
|
270 | 271 | }
|
271 | 272 |
|
| 273 | +/* |
| 274 | + * Initializes the reduce result for skipna reductions where operand |
| 275 | + * has more than one dimension. |
| 276 | + * |
| 277 | + * 'operand must have an NA mask, 'result' may or may not have an |
| 278 | + * NA mask, and 'skipna' must be True to call this function. |
| 279 | + */ |
| 280 | +static PyArrayObject * |
| 281 | +initialize_reduce_result_noidentity_skipna( |
| 282 | + PyArrayObject *operand, PyArrayObject *result, |
| 283 | + npy_bool *axis_flags, const char *funcname) |
| 284 | +{ |
| 285 | + PyArrayObject *initialized = NULL; |
| 286 | + npy_intp initialized_countdown; |
| 287 | + npy_intp op_itemsize; |
| 288 | + PyArray_Descr *bool_dtype; |
| 289 | + |
| 290 | + /* Iterator parameters */ |
| 291 | + NpyIter *iter = NULL; |
| 292 | + PyArrayObject *op[3]; |
| 293 | + npy_uint32 flags, op_flags[3]; |
| 294 | + npy_intp fixed_strides[4]; |
| 295 | + |
| 296 | + /* Data transfer function */ |
| 297 | + PyArray_StridedUnaryOp *stransfer = NULL; |
| 298 | + NpyAuxData *transferdata = NULL; |
| 299 | + int needs_api; |
| 300 | + |
| 301 | + op_itemsize = PyArray_DTYPE(operand)->elsize; |
| 302 | + |
| 303 | + /* |
| 304 | + * Create a view of operand which owns its its own mask, so that |
| 305 | + * we can change it. |
| 306 | + */ |
| 307 | + operand = (PyArrayObject *)PyArray_View(operand, NULL, &PyArray_Type); |
| 308 | + if (operand == NULL) { |
| 309 | + goto fail; |
| 310 | + } |
| 311 | + if (PyArray_AllocateMaskNA(operand, 1, 0, 1) < 0) { |
| 312 | + goto fail; |
| 313 | + } |
| 314 | + |
| 315 | + /* |
| 316 | + * Allocate a flag array to keep track of which elements in the result |
| 317 | + * have already been initialized. |
| 318 | + * |
| 319 | + * This reference to bool_dtype gets stolen by NewLikeArray. |
| 320 | + */ |
| 321 | + bool_dtype = PyArray_DescrFromType(NPY_BOOL); |
| 322 | + if (bool_dtype == NULL) { |
| 323 | + goto fail; |
| 324 | + } |
| 325 | + initialized = (PyArrayObject *)PyArray_NewLikeArray(result, |
| 326 | + NPY_KEEPORDER, bool_dtype, 0); |
| 327 | + if (initialized == NULL) { |
| 328 | + goto fail; |
| 329 | + } |
| 330 | + if (PyArray_AssignZero(initialized, NULL, 0, NULL) < 0) { |
| 331 | + Py_DECREF(initialized); |
| 332 | + goto fail; |
| 333 | + } |
| 334 | + |
| 335 | + /* Set up the iterator for copying the elements */ |
| 336 | + op[0] = operand; |
| 337 | + op[1] = result; |
| 338 | + op[2] = initialized; |
| 339 | + op_flags[0] = NPY_ITER_READWRITE | NPY_ITER_USE_MASKNA; |
| 340 | + op_flags[1] = NPY_ITER_READWRITE | NPY_ITER_IGNORE_MASKNA; |
| 341 | + op_flags[2] = NPY_ITER_READWRITE; |
| 342 | + flags = NPY_ITER_EXTERNAL_LOOP | |
| 343 | + NPY_ITER_REFS_OK | |
| 344 | + NPY_ITER_REDUCE_OK | |
| 345 | + NPY_ITER_ZEROSIZE_OK | |
| 346 | + NPY_ITER_DONT_NEGATE_STRIDES; |
| 347 | + |
| 348 | + iter = NpyIter_MultiNew(3, op, flags, |
| 349 | + NPY_KEEPORDER, NPY_UNSAFE_CASTING, |
| 350 | + op_flags, |
| 351 | + NULL); |
| 352 | + if (iter == NULL) { |
| 353 | + goto fail; |
| 354 | + } |
| 355 | + NpyIter_GetInnerFixedStrideArray(iter, fixed_strides); |
| 356 | + needs_api = NpyIter_IterationNeedsAPI(iter); |
| 357 | + |
| 358 | + /* Get a function for copying the elements */ |
| 359 | + if (PyArray_GetDTypeTransferFunction( |
| 360 | + PyArray_ISALIGNED(operand) && PyArray_ISALIGNED(result), |
| 361 | + fixed_strides[0], fixed_strides[1], |
| 362 | + PyArray_DTYPE(operand), PyArray_DTYPE(result), |
| 363 | + 0, |
| 364 | + &stransfer, &transferdata, |
| 365 | + &needs_api) != NPY_SUCCEED) { |
| 366 | + goto fail; |
| 367 | + } |
| 368 | + |
| 369 | + if (NpyIter_GetIterSize(iter) != 0) { |
| 370 | + NpyIter_IterNextFunc *iternext; |
| 371 | + char **dataptr; |
| 372 | + npy_intp *strideptr; |
| 373 | + npy_intp *countptr, count, subcount; |
| 374 | + NPY_BEGIN_THREADS_DEF; |
| 375 | + |
| 376 | + iternext = NpyIter_GetIterNext(iter, NULL); |
| 377 | + if (iternext == NULL) { |
| 378 | + goto fail; |
| 379 | + } |
| 380 | + dataptr = NpyIter_GetDataPtrArray(iter); |
| 381 | + strideptr = NpyIter_GetInnerStrideArray(iter); |
| 382 | + countptr = NpyIter_GetInnerLoopSizePtr(iter); |
| 383 | + |
| 384 | + /* |
| 385 | + * Track how many initializations we've done, both to |
| 386 | + * short circuit completion and to raise an error if |
| 387 | + * any remained uninitialized. |
| 388 | + */ |
| 389 | + initialized_countdown = PyArray_SIZE(result); |
| 390 | + |
| 391 | + if (!needs_api) { |
| 392 | + NPY_BEGIN_THREADS; |
| 393 | + } |
| 394 | + |
| 395 | + do { |
| 396 | + char *op_d = dataptr[0], *res_d = dataptr[1]; |
| 397 | + char *init_d = dataptr[2], *op_namask_d = dataptr[3]; |
| 398 | + npy_intp op_s = strideptr[0], res_s = strideptr[1]; |
| 399 | + npy_intp init_s = strideptr[2], op_namask_s = strideptr[3]; |
| 400 | + |
| 401 | + count = *countptr; |
| 402 | + |
| 403 | + /* If the result stride is 0, copy at most one value */ |
| 404 | + if (res_s == 0) { |
| 405 | + npy_intp i; |
| 406 | + for (i = 0; i < count; ++i) { |
| 407 | + if (*init_d == 0 && NpyMaskValue_IsExposed( |
| 408 | + *(npy_mask *)op_namask_d)) { |
| 409 | + |
| 410 | + /* Mark it as initialized */ |
| 411 | + *init_d = 1; |
| 412 | + stransfer(res_d, 0, op_d + i * op_s, op_s, |
| 413 | + 1, op_itemsize, transferdata); |
| 414 | + |
| 415 | + --initialized_countdown; |
| 416 | + if (initialized_countdown == 0) { |
| 417 | + goto finish_loop; |
| 418 | + } |
| 419 | + break; |
| 420 | + } |
| 421 | + |
| 422 | + init_d += init_s; |
| 423 | + op_namask_d += op_namask_s; |
| 424 | + } |
| 425 | + } |
| 426 | + /* Otherwise process the data in runs as large as possible */ |
| 427 | + else { |
| 428 | + do { |
| 429 | + /* Skip values that are initialized or masked */ |
| 430 | + subcount = 0; |
| 431 | + while (subcount < count && (*init_d == 1 || |
| 432 | + !NpyMaskValue_IsExposed( |
| 433 | + *(npy_mask *)op_namask_d))) { |
| 434 | + ++subcount; |
| 435 | + init_d += init_s; |
| 436 | + op_namask_d += op_namask_s; |
| 437 | + } |
| 438 | + op_d += subcount * op_s; |
| 439 | + res_d += subcount * res_s; |
| 440 | + count -= subcount; |
| 441 | + |
| 442 | + /* Transfer values that are uninitialized and exposed */ |
| 443 | + subcount = 0; |
| 444 | + while (subcount < count && (*init_d == 0 && |
| 445 | + NpyMaskValue_IsExposed( |
| 446 | + *(npy_mask *)op_namask_d))) { |
| 447 | + ++subcount; |
| 448 | + /* Mark it as initialized */ |
| 449 | + *init_d = 1; |
| 450 | + init_d += init_s; |
| 451 | + op_namask_d += op_namask_s; |
| 452 | + } |
| 453 | + stransfer(res_d, res_s, op_d, op_s, |
| 454 | + subcount, op_itemsize, transferdata); |
| 455 | + op_d += subcount * op_s; |
| 456 | + res_d += subcount * res_s; |
| 457 | + count -= subcount; |
| 458 | + |
| 459 | + initialized_countdown -= subcount; |
| 460 | + if (initialized_countdown == 0) { |
| 461 | + goto finish_loop; |
| 462 | + } |
| 463 | + } while (count > 0); |
| 464 | + } |
| 465 | + } while (iternext(iter)); |
| 466 | + |
| 467 | + finish_loop: |
| 468 | + if (!needs_api) { |
| 469 | + NPY_END_THREADS; |
| 470 | + } |
| 471 | + } |
| 472 | + |
| 473 | + if (needs_api && PyErr_Occurred()) { |
| 474 | + goto fail; |
| 475 | + } |
| 476 | + |
| 477 | + /* Since this ufunc has no identity, all elements must be initialized */ |
| 478 | + if (initialized_countdown != 0) { |
| 479 | + PyErr_Format(PyExc_ValueError, |
| 480 | + "reduction operation %s with skipna=True " |
| 481 | + "had an output element with all its inputs NA", funcname); |
| 482 | + goto fail; |
| 483 | + } |
| 484 | + |
| 485 | + /* If 'result' has an NA mask, set it to all exposed */ |
| 486 | + if (PyArray_HASMASKNA(result)) { |
| 487 | + if (PyArray_AssignMaskNA(result, 1, NULL, 0, NULL) < 0) { |
| 488 | + goto fail; |
| 489 | + } |
| 490 | + } |
| 491 | + |
| 492 | + Py_DECREF(initialized); |
| 493 | + NpyIter_Deallocate(iter); |
| 494 | + NPY_AUXDATA_FREE(transferdata); |
| 495 | + return operand; |
| 496 | + |
| 497 | +fail: |
| 498 | + Py_XDECREF(operand); |
| 499 | + Py_XDECREF(initialized); |
| 500 | + if (iter != NULL) { |
| 501 | + NpyIter_Deallocate(iter); |
| 502 | + } |
| 503 | + NPY_AUXDATA_FREE(transferdata); |
| 504 | + |
| 505 | + return NULL; |
| 506 | +} |
| 507 | + |
272 | 508 | /*
|
273 | 509 | * This function initializes a result array for a reduction operation
|
274 | 510 | * which has no identity. This means it needs to copy the first element
|
@@ -423,10 +659,8 @@ PyArray_InitializeReduceResult(
|
423 | 659 | * case
|
424 | 660 | */
|
425 | 661 | else {
|
426 |
| - PyErr_SetString(PyExc_ValueError, |
427 |
| - "skipna=True with a non-identity reduction " |
428 |
| - "and an array with ndim > 1 isn't implemented yet"); |
429 |
| - return NULL; |
| 662 | + return initialize_reduce_result_noidentity_skipna( |
| 663 | + operand, result, axis_flags, funcname); |
430 | 664 | }
|
431 | 665 |
|
432 | 666 | /*
|
|
0 commit comments