Search code examples
multidimensional-arraybooleancython

Cannot convert Cython memoryviewslice to ndarray


I am trying to write an explicit Successive Overrelaxation Function over a 2D matrix. In this case for an electrostatic potential.

When trying to optimize this in Cython I seem to get an error that I am not quite sure I understand.

%%cython
cimport cython
import numpy as np
cimport numpy as np
from libc.math cimport pi

#SOR function
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.initializedcheck(False)
@cython.nonecheck(False)
def SOR_potential(np.float64_t[:, :] potential, mask, int max_iter, float error_threshold, float alpha):
    
    #the ints
    cdef int height = potential.shape[0]
    cdef int width = potential.shape[1] #more general non quadratic
    cdef int it = 0
    
    #the floats
    cdef float error = 0.0
    cdef float sor_adjustment
    
    #the copy array we will iterate over and return
    cdef np.ndarray[np.float64_t, ndim=2] input_matrix = potential.copy()
    
    #set the ideal alpha if user input is 0.0
    
    if alpha == 0.0:
        alpha = 2/(1+(pi/((height+width)*0.5)))
    
    
    #start the SOR loop. The for loops omit the 0 and -1 index\
    #because they are *shadow points* used for neuman boundary conditions\
    cdef int row, col
    #iteration loop
    while True:
        #2-stencil loop
        for row in range(1, height-1):
            for col in range(1, width-1):
                
                if not(mask[row][col]):
                    
                    potential[row][col] = 0.25*(input_matrix[row-1][col] + \
                                                input_matrix[row+1][col] + \
                                                input_matrix[row][col-1] + \
                                                input_matrix[row][col+1])
                    
                    sor_adjustment = alpha * (potential[row][col] - input_matrix[row][col])
                    
                    input_matrix[row][col] = sor_adjustment + input_matrix[row][col]
                    
                    error += np.abs(input_matrix[row][col] - potential[row][col])
                    #by the end of this loop input_matrix and potential have diff values
        if error<error_threshold:
            break
        elif it>max_iter:
            break
        else:
            error = 0
        it = it + 1
    return input_matrix, error, it

and I used a very simple example for an array to see if it would give an error output.

test = [[True, False], [True, False]]
pot = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float64)

SOR_potential(pot, test, 50, 0.1, 0.0)

Gives out this error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [30], line 1
----> 1 SOR_potential(pot, test, 50, 0.1, 0.0)

File _cython_magic_6c09a5060df996862b8e35adacc0e25c.pyx:21, in _cython_magic_6c09a5060df996862b8e35adacc0e25c.SOR_potential()

TypeError: Cannot convert _cython_magic_6c09a5060df996862b8e35adacc0e25c._memoryviewslice to numpy.ndarray

But when I delete the np.float64_t[:, :] part from

def SOR_potential(np.float64_t[:, :] potential,...)

the code works. Of course, the simple 2x2 matrix will not converge but it gives no errors. Where is the mistake here?

I also tried importing the modules differently as suggested here

Cython: how to resolve TypeError: Cannot convert memoryviewslice to numpy.ndarray?

but I got 2 errors instead of 1 where there were type mismatches.

Note: I would also like to ask, how would I define a numpy array of booleans to put in front of the "mask" input in the function?


Solution

  • A minimal reproducible example of your error message would look like this:

    def foo(np.float64_t[:, :] A):
        cdef np.ndarray[np.float64_t, ndim=2] B = A.copy()
        # ... do something with B ...
        return B
    

    The problem is, that A is a memoryview while B is a np.ndarray. If both A and B are memoryviews, i.e.

    def foo(np.float64_t[:, :] A):
        cdef np.float64_t[:, :] B = A.copy()
        # ... do something with B ...
        return np.asarray(B)
    

    your example will compile without errors. Note that you then need to call np.asarray if you want to return a np.ndarray.

    Regarding your second question: You could use a memoryview with dtype np.uint8_t

    def foo(np.float64_t[:, :] A, np.uint8_t[:, :] mask):
        cdef np.float64_t[:, :] B = A.copy()
        # ... do something with B and mask ...
        return np.asarray(B)
    

    and call it like this from Python:

    mask = np.array([[True, True], [False, False]], dtype=bool)
    A = np.ones((2,2), dtype=np.float64)
    foo(A, mask)
    

    PS: If your array's buffers are guaranteed to be C-Contiguous, you can use contiguous memoryviews for better performance:

    def foo(np.float64_t[:, ::1] A, np.uint8_t[:, ::1] mask):
        cdef np.float64_t[:, ::1] B = A.copy()
        # ... do something with B and mask ...
        return np.asarray(B)