Search code examples
numpypyopencl

Interaction between numpy arrays of pyOpenCL vector types and numpy arrays of floats


I find it quite convenient to use both structs and np.ndarrays using dtypes like pyopencl.cltypes.float2. This is a clear and self documenting way to pass structured data to my kernels, as opposed to just a bunch of floats.

However I find certain behavior inconvenient. For example given:

import numpy as np
import pyopencl.cltypes as cltp

a = np.zeros((3,5), dtype=cltp.float2)


>>> a
array([[(0., 0.), (0., 0.), (0., 0.), (0., 0.), (0., 0.)],
       [(0., 0.), (0., 0.), (0., 0.), (0., 0.), (0., 0.)],
       [(0., 0.), (0., 0.), (0., 0.), (0., 0.), (0., 0.)]],
       dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])

I can construct my data structure piecemeal before passing it to the kernel by doing e.g.

a[:,1] = (42,-42)
>>> a
array([[( 0.,   0.), (42., -42.), ( 0.,   0.), ( 0.,   0.), ( 0.,   0.)],
       [( 0.,   0.), (42., -42.), ( 0.,   0.), ( 0.,   0.), ( 0.,   0.)],
       [( 0.,   0.), (42., -42.), ( 0.,   0.), ( 0.,   0.), ( 0.,   0.)]],
       dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])

which is nice. However I would also like to assign an array of size 2 to a float2, yet this fails.

a[:,1] = np.array((42,-42))

ValueError: could not broadcast input array from shape (2) into shape (3)

and I have to resort to doing this

a[:,1]['x'] = 42
a[:,1]['y'] = -42

which works, but kind of defeats the purpose of the whole thing.

Am I missing some obvious syntactic difference or is this feature just not supposed to be used this way?

EDIT: In my attempt to present the most minimal example I oversimplified the problem. hpaulj's answer is correct in that if I use explicit dtype=float2 on something of shape=(2,) it will get converted to a shape=(1,) of dtype=float2. My actual question however was more general, on how to go from say an array(shape=(foo, bar, N), dtype=np.float32) into an array(shape=(foo, bar), dtype=cltp.floatN) with N=2,3,4.

Followup question that just occurred: will an actual copy necessarily occur or can this be converted 'in place'? The actual memory layout looks compatible at first glance.


Solution

  • In [99]: a=np.array([[(0., 0.), (0., 0.), (0., 0.), (0., 0.), (0., 0.)],
        ...:        [(0., 0.), (0., 0.), (0., 0.), (0., 0.), (0., 0.)],
        ...:        [(0., 0.), (0., 0.), (0., 0.), (0., 0.), (0., 0.)]],
        ...:        dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    In [100]: a.dtype
    Out[100]: dtype([(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    In [101]: a.dtype.fields
    Out[101]: 
    mappingproxy({'s0': (dtype('float32'), 0, 'x'),
                  'x': (dtype('float32'), 0, 'x'),
                  's1': (dtype('float32'), 4, 'y'),
                  'y': (dtype('float32'), 4, 'y')})
    

    Look at the intended target, a 3 element array with the compound dtype:

    In [102]: a[:,1]
    Out[102]: 
    array([(0., 0.), (0., 0.), (0., 0.)],
          dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    

    While your array is 2 element int. With this construct the use of tuple doesn't change anything.

    In [103]: np.array((42,-42))
    Out[103]: array([ 42, -42])
    

    But if we specify the correct dtype, we make a 0d array:

    In [104]: np.array((42,-42), a.dtype)
    Out[104]: array((42., -42.), dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    

    Now there's no problem assigning it.

    In [105]: a[:,1] = np.array((42,-42), a.dtype)
    

    edit

    In [3]: dt = a.dtype
    In [4]: dt
    Out[4]: dtype([(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    

    Using a recent recfunctions addition, we can make a structured array from an appropriately shape unstructured array:

    In [5]: import numpy.lib.recfunctions as rf    
    

    2d to 1d structured:

    In [7]: rf.unstructured_to_structured(np.arange(1,7).reshape(3,2), dtype=dt)
    Out[7]: 
    array([(1., 2.), (3., 4.), (5., 6.)],
          dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    

    3d to 2d structured:

    In [8]: rf.unstructured_to_structured(np.arange(24).reshape(4,3,2), dtype=dt)
    Out[8]: 
    array([[( 0.,  1.), ( 2.,  3.), ( 4.,  5.)],
           [( 6.,  7.), ( 8.,  9.), (10., 11.)],
           [(12., 13.), (14., 15.), (16., 17.)],
           [(18., 19.), (20., 21.), (22., 23.)]],
          dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    

    Or making an array from a list of tuples:

    In [10]: np.array([(0,1),(2,3)], dt)
    Out[10]: 
    array([(0., 1.), (2., 3.)],
          dtype=[(('x', 's0'), '<f4'), (('y', 's1'), '<f4')])
    

    recfunctions often makes use of field-by-field setting:

    In [16]: for name in a.dtype.names:
    ...:     a[name][:] = np.arange(15).reshape(3,5)