Skip to content

BUG: PyArray_CheckAxis possibly not working correctly #27194

Closed

Description

@brendan-m-murphy

Describe the issue:

I'm trying to fix some bugs in Pytensor's C code after moving to numpy 2.0. There is a function that essentially calls PyArray_CumSum with axis = None (which I have updated to mean axis = NPY_RAVEL_AXIS), and there is an error if NPY_RAVEL_AXIS is passed for axis, along with a 2D input array. If I ravel the input array and pass 0 for axis, then the test that calls Pytensor's cumsum function (which calls PyArray_CumSum) passes.

Since PyArray_CheckAxis should ravel in the input array and set axis to 0, it seems that perhaps it is failing.

The error message (below) is actually raised in the accumulate ufunc when trying to get an NpyIter object. From reading that code, I got the impression that for axis = None, it shouldn't use an iterator. I'm not sure what condition is setting "need_outer_iterator" to true. Also, I can't see from ufunc code (lines 2663-2666 in _cor/src/umath/ufunc_object.c) how op_axes[0][0] could be 1, since it seems to be set to 0.

Reproduce the code example:

// this fails:
PyArray_CumSum(X, NPY_RAVEL_AXIS, PyArray_TYPE(X), Z);

// this works:
PyArray_CumSum((PyArrayObject *) PyArray_Ravel(X, (NPY_ORDER) 0), 0, PyArray_TYPE(X), Z);

Error message:

ValueError: Iterator input op_axes[0][0] (==1) is not a valid axis of op[0], which has 1 dimensions

Python and NumPy Versions:

python 3.10.14
numpy 2.0.1

Runtime Environment:

No response

Context for the issue:

No response

Activity

  1. seberg commented on Aug 13, 2024

    @seberg
    Member

    Can you please share your updated code? I don't see what or even why it could be wrong in NumPy so before trying to see anything weird there, would prefer to see that part. Unless maybe you are passing in matrices? But even then I don't think anything changed.

  2. seberg commented on Aug 13, 2024

    @seberg
    Member

    So this creates the error:

    np.add.accumulate(np.ones((1, 3)), out=np.zeros(3), axis=0)  # or axis=1
    

    since I don't see anything obviously wrong in your code.

    So maybe still related to matrices? Those would definitely create the situation where the input should be raveled, but is not, causing this type of situation:

    np.cumsum(np.matrix([1, 2, 3]), out=np.zeros(3), axis=None)
    

    But that isn't new in NumPy 2, the above fails identically on 1.26.

    So, I am sorry, but I think I have to ask you to check exactly what values are passing through. (In case you are not aware of it, the simplest way to add a print is: PySys_FormatStdout()) (EDIT: sorry had WriteStdout first, FormatStdout of course).

  3. brendan-m-murphy commented on Aug 13, 2024

    @brendan-m-murphy
    Author

    Thanks for the suggestions, I'm a bit out of my depth here (and in fact, was using C assert statements for debugging rather than printing...). I agree that it seems unlikely to be a numpy issue at root.

    PyTensor matrices are built on ndarrays (as far as I can see), so I don't think that's the issue, but I'll see what the print statements say. Also, if I force the test to use the Python implementation, the test passes. The python code just calls out = np.cumsum(in, axis=None).

    I pushed the updates to extra_ops to my fork: https://github.com/brendan-m-murphy/pytensor/pull/2/files#diff-f290a044a176b9391cf80035cb2ef33add7070cc67aa85f29b62b1d1270f7091

    (For now, I'm not using params->c_axis in the case that axis is None because changes to the c_axis function don't seem to be picking up the right value for NPY_RAVEL_AXIS. So for axis = None (in python), I'm explicitly setting int axis = NPY_RAVEL_AXIS; in the C code.)

    I'll get back to you with more details after I debug further. Thanks again.

  4. brendan-m-murphy commented on Aug 13, 2024

    @brendan-m-murphy
    Author

    This is what I get from the print outs (I'm using X and Z where pytensor/tensor/extra_ops.py::CumSum.c_code has {x} and {z}):

    System info:
    	NPY_FEATURE_VERSION = d
    	NPY_2_0_API_VERSION = 12
    	NPY_ABI_VERSION = 2000000
    	PyArray_RUNTIME_VERSION = 12
    	NPY_RAVEL_AXIS = -1
    
    CumSum input info:
    axis = -1
    X = array([[0.5283243 , 0.10099647],
           [0.08438218, 0.22787064]])
    X ndims = 2
    X dims = (2, 2 )
    Z = array([0.5283243 , 0.10099647, 0.08438218, 0.22787064])
    Z ndims = 1
    Z dims = (4, )
    

    The function call is then:

    PyObject* t = PyArray_CumSum(X, axis, PyArray_TYPE(X), Z);                    
    

    The variable Z is NULL initially, and since axis == NPY_RAVEL_AXIS, it is set via

    npy_intp shape[1] = { PyArray_SIZE(X) };
    Z = (PyArrayObject*) PyArray_SimpleNew(1, shape, PyArray_TYPE(X));
    

    I thought it was odd that the values in the newly assigned Z are the same as the values in X, but the same error occurs if instead

    Z = (PyArrayObject*) PyArray_ZEROS(1, shape, PyArray_TYPE(Z), PyArray_IS_F_CONTIGUOUS(Z));
    

    After this assignment, Z contains zeros and X contains its original values.

    Incidentally, the value I set for c_axis to replace np.MAXDIMS wasn't matching NPY_RAVEL_AXIS because this is -1 instead of the smallest int32. I pip updated numpy from 2.0.0rc to 2.0.1 at some point. Could that be causing a problem? (I have another test that fails only if axis = -1, but passes if axis = ndim - 1, which could be explained by NPY_RAVEL_AXIS == -1.)

  5. seberg commented on Aug 13, 2024

    @seberg
    Member

    OK, that clearly explains what is wrong. And like a bug/typo :/. It needs to be defined to NPY_INT_MIN, and is not. Do you vendor the compat.h? In that case, please modify it to read:

            (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_MIN_INT : 32)
    
  6. seberg commented on Aug 13, 2024

    @seberg
    Member

    Sorry, modifying wouldn't really work, since it would use the NumPy version if it exists, I guess. Could only copy the definition with a different name I guess.

    Should fix this for the 2.1 (and 2.0.2 release), though.

  7. added this to the 2.1.0 release milestone on Aug 13, 2024
  8. brendan-m-murphy commented on Aug 13, 2024

    @brendan-m-murphy
    Author

    Yes, modifying the compat header doesn't seem to help. That would be great if it's fixed in an upcoming release, thanks!

  9. seberg commented on Aug 13, 2024

    @seberg
    Member

    If it unblocks you in the moment, you can use that exact line I think, just need to define to a custom name, unfortunately:

    #define RAVEL_AX (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_MIN_INT : 32)
    
  10. brendan-m-murphy commented on Aug 13, 2024

    @brendan-m-murphy
    Author

    Ah okay, I see what you mean, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions