/* * Allocates a result array for a reduction operation, with * dimensions matching 'arr' except set to 1 with 0 stride * whereever axis_flags is True. Dropping the reduction axes * from the result must be done later by the caller once the * computation is complete. * * This function always allocates a base class ndarray. * * If 'dtype' isn't NULL, this function steals its reference. */ static PyArrayObject * allocate_reduce_result(PyArrayObject *arr, npy_bool *axis_flags, PyArray_Descr *dtype, int subok) { npy_intp strides[NPY_MAXDIMS], stride; npy_intp shape[NPY_MAXDIMS], *arr_shape = PyArray_DIMS(arr); npy_stride_sort_item strideperm[NPY_MAXDIMS]; int idim, ndim = PyArray_NDIM(arr); if (dtype == NULL) { dtype = PyArray_DTYPE(arr); Py_INCREF(dtype); } PyArray_CreateSortedStridePerm(PyArray_NDIM(arr), PyArray_SHAPE(arr), PyArray_STRIDES(arr), strideperm); /* Build the new strides and shape */ stride = dtype->elsize; memcpy(shape, arr_shape, ndim * sizeof(shape[0])); for (idim = ndim-1; idim >= 0; --idim) { npy_intp i_perm = strideperm[idim].perm; if (axis_flags[i_perm]) { strides[i_perm] = 0; shape[i_perm] = 1; } else { strides[i_perm] = stride; stride *= shape[i_perm]; } } /* Finally, allocate the array */ return (PyArrayObject *)PyArray_NewFromDescr( subok ? Py_TYPE(arr) : &PyArray_Type, dtype, ndim, shape, strides, NULL, 0, subok ? (PyObject *)arr : NULL); }
/* * This function initializes a result array for a reduction operation * which has no identity. This means it needs to copy the first element * it sees along the reduction axes to result, then return a view of * the operand which excludes that element. * * If a reduction has an identity, such as 0 or 1, the result should be * initialized by calling PyArray_AssignZero(result, NULL, NULL) or * PyArray_AssignOne(result, NULL, NULL), because this function raises an * exception when there are no elements to reduce (which appropriate iff the * reduction operation has no identity). * * This means it copies the subarray indexed at zero along each reduction axis * into 'result', then returns a view into 'operand' excluding those copied * elements. * * result : The array into which the result is computed. This must have * the same number of dimensions as 'operand', but for each * axis i where 'axis_flags[i]' is True, it has a single element. * operand : The array being reduced. * axis_flags : An array of boolean flags, one for each axis of 'operand'. * When a flag is True, it indicates to reduce along that axis. * reorderable : If True, the reduction being done is reorderable, which * means specifying multiple axes of reduction at once is ok, * and the reduction code may calculate the reduction in an * arbitrary order. The calculation may be reordered because * of cache behavior or multithreading requirements. * out_skip_first_count : This gets populated with the number of first-visit * elements that should be skipped during the * iteration loop. * funcname : The name of the reduction operation, for the purpose of * better quality error messages. For example, "numpy.max" * would be a good name for NumPy's max function. * * Returns a view which contains the remaining elements on which to do * the reduction. */ NPY_NO_EXPORT PyArrayObject * PyArray_InitializeReduceResult( PyArrayObject *result, PyArrayObject *operand, npy_bool *axis_flags, int reorderable, npy_intp *out_skip_first_count, const char *funcname) { npy_intp *strides, *shape, shape_orig[NPY_MAXDIMS]; PyArrayObject *op_view = NULL; int idim, ndim, nreduce_axes; ndim = PyArray_NDIM(operand); /* Default to no skipping first-visit elements in the iteration */ *out_skip_first_count = 0; /* * If this reduction is non-reorderable, make sure there are * only 0 or 1 axes in axis_flags. */ if (!reorderable && check_nonreorderable_axes(ndim, axis_flags, funcname) < 0) { return NULL; } /* Take a view into 'operand' which we can modify. */ op_view = (PyArrayObject *)PyArray_View(operand, NULL, &PyArray_Type); if (op_view == NULL) { return NULL; } /* * Now copy the subarray of the first element along each reduction axis, * then return a view to the rest. * * Adjust the shape to only look at the first element along * any of the reduction axes. We count the number of reduction axes * at the same time. */ shape = PyArray_SHAPE(op_view); nreduce_axes = 0; memcpy(shape_orig, shape, ndim * sizeof(npy_intp)); for (idim = 0; idim < ndim; ++idim) { if (axis_flags[idim]) { if (shape[idim] == 0) { PyErr_Format(PyExc_ValueError, "zero-size array to reduction operation %s " "which has no identity", funcname); Py_DECREF(op_view); return NULL; } shape[idim] = 1; ++nreduce_axes; } } /* * Copy the elements into the result to start. */ if (PyArray_CopyInto(result, op_view) < 0) { Py_DECREF(op_view); return NULL; } /* * If there is one reduction axis, adjust the view's * shape to only look at the remaining elements */ if (nreduce_axes == 1) { strides = PyArray_STRIDES(op_view); for (idim = 0; idim < ndim; ++idim) { if (axis_flags[idim]) { shape[idim] = shape_orig[idim] - 1; ((PyArrayObject_fields *)op_view)->data += strides[idim]; } } } /* If there are zero reduction axes, make the view empty */ else if (nreduce_axes == 0) { for (idim = 0; idim < ndim; ++idim) { shape[idim] = 0; } } /* * Otherwise iterate over the whole operand, but tell the inner loop * to skip the elements we already copied by setting the skip_first_count. */ else { *out_skip_first_count = PyArray_SIZE(result); Py_DECREF(op_view); Py_INCREF(operand); op_view = operand; } return op_view; }
static size_t wrap_send(uhd::tx_streamer *tx_stream, bp::object &np_array, bp::object &metadata, const double timeout = 0.1) { // Extract the metadata bp::extract<uhd::tx_metadata_t&> get_metadata(metadata); // TODO: throw an error here? if (not get_metadata.check()) { return 0; } // Get a numpy array object from given python object // No sanity checking possible! // Note: this increases the ref count, which we'll need to manually decrease at the end PyObject* array_obj = PyArray_FROM_OF(np_array.ptr(),NPY_ARRAY_CARRAY); PyArrayObject* array_type_obj = reinterpret_cast<PyArrayObject*>(array_obj); // Get dimensions of the numpy array const size_t dims = PyArray_NDIM(array_type_obj); const npy_intp* shape = PyArray_SHAPE(array_type_obj); // How many bytes to jump to get to the next element of the stride // (next row) const npy_intp* strides = PyArray_STRIDES(array_type_obj); const size_t channels = tx_stream->get_num_channels(); // Check if numpy array sizes are ok if (((channels > 1) && (dims != 2)) or ((size_t) shape[0] < channels)) { // Manually decrement the ref count Py_DECREF(array_obj); // If we don't have a 2D NumPy array, assume we have a 1D array size_t input_channels = (dims != 2) ? 1 : shape[0]; throw uhd::runtime_error(str(boost::format( "Number of TX channels (%d) does not match the dimensions of the data array (%d)") % channels % input_channels)); } // Get a pointer to the storage std::vector<void*> channel_storage; char* data = PyArray_BYTES(array_type_obj); for (size_t i = 0; i < channels; ++i) { channel_storage.push_back((void*)(data + i * strides[0])); } // Get data buffer and size of the array size_t nsamps_per_buff = (dims > 1) ? (size_t) shape[1] : PyArray_SIZE(array_type_obj); // Release the GIL only for the send() call const size_t result = [&]() { scoped_gil_release gil_release; // Call the real send() return tx_stream->send( channel_storage, nsamps_per_buff, get_metadata(), timeout ); }(); // Manually decrement the ref count Py_DECREF(array_obj); return result; }